mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-19 12:34:32 +08:00
feat(@schematics/angular): update SSR and application builder migration schematics to work with new outputPath
In #26675 we introduced a long-form variant of `outputPath`, this commit updates the application builder migration and ssr schematics to handle this change.
This commit is contained in:
parent
69d2dfdb50
commit
a708dccff3
@ -14,7 +14,7 @@ import {
|
|||||||
chain,
|
chain,
|
||||||
externalSchematic,
|
externalSchematic,
|
||||||
} from '@angular-devkit/schematics';
|
} from '@angular-devkit/schematics';
|
||||||
import { dirname } from 'node:path';
|
import { dirname, join } from 'node:path/posix';
|
||||||
import { JSONFile } from '../../utility/json-file';
|
import { JSONFile } from '../../utility/json-file';
|
||||||
import { TreeWorkspaceHost, allTargetOptions, getWorkspace } from '../../utility/workspace';
|
import { TreeWorkspaceHost, allTargetOptions, getWorkspace } from '../../utility/workspace';
|
||||||
import { Builders, ProjectType } from '../../utility/workspace-models';
|
import { Builders, ProjectType } from '../../utility/workspace-models';
|
||||||
@ -68,8 +68,33 @@ export default function (): Rule {
|
|||||||
options['polyfills'] = [options['polyfills']];
|
options['polyfills'] = [options['polyfills']];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof options['outputPath'] === 'string') {
|
let outputPath = options['outputPath'];
|
||||||
options['outputPath'] = options['outputPath']?.replace(/\/browser\/?$/, '');
|
if (typeof outputPath === 'string') {
|
||||||
|
if (!/\/browser\/?$/.test(outputPath)) {
|
||||||
|
// TODO: add prompt.
|
||||||
|
context.logger.warn(
|
||||||
|
`The output location of the browser build has been updated from "${outputPath}" to ` +
|
||||||
|
`"${join(outputPath, 'browser')}". ` +
|
||||||
|
'You might need to adjust your deployment pipeline or, as an alternative, ' +
|
||||||
|
'set outputPath.browser to "" in order to maintain the previous functionality.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
outputPath = outputPath.replace(/\/browser\/?$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
options['outputPath'] = {
|
||||||
|
base: outputPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof options['resourcesOutputPath'] === 'string') {
|
||||||
|
const media = options['resourcesOutputPath'].replaceAll('/', '');
|
||||||
|
if (media && media !== 'media') {
|
||||||
|
options['outputPath'] = {
|
||||||
|
base: outputPath,
|
||||||
|
media: media,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removed options
|
// Delete removed options
|
||||||
@ -189,13 +214,5 @@ function usesNoLongerSupportedOptions(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof resourcesOutputPath === 'string' && /^\/?media\/?$/.test(resourcesOutputPath)) {
|
|
||||||
hasUsage = true;
|
|
||||||
context.logger.warn(
|
|
||||||
`Skipping migration for project "${projectName}". "resourcesOutputPath" option is not available in the application builder.` +
|
|
||||||
`Media files will be output into a "media" directory within the output location.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasUsage;
|
return hasUsage;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,104 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google LLC All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EmptyTree } from '@angular-devkit/schematics';
|
||||||
|
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||||
|
import { Builders, ProjectType, WorkspaceSchema } from '../../utility/workspace-models';
|
||||||
|
|
||||||
|
function createWorkSpaceConfig(tree: UnitTestTree) {
|
||||||
|
const angularConfig: WorkspaceSchema = {
|
||||||
|
version: 1,
|
||||||
|
projects: {
|
||||||
|
app: {
|
||||||
|
root: '/project/lib',
|
||||||
|
sourceRoot: '/project/app/src',
|
||||||
|
projectType: ProjectType.Application,
|
||||||
|
prefix: 'app',
|
||||||
|
architect: {
|
||||||
|
build: {
|
||||||
|
builder: Builders.Browser,
|
||||||
|
options: {
|
||||||
|
tsConfig: 'src/tsconfig.app.json',
|
||||||
|
main: 'src/main.ts',
|
||||||
|
polyfills: 'src/polyfills.ts',
|
||||||
|
outputPath: 'dist/project',
|
||||||
|
resourcesOutputPath: '/resources',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
tree.create('/angular.json', JSON.stringify(angularConfig, undefined, 2));
|
||||||
|
tree.create('/tsconfig.json', JSON.stringify({}, undefined, 2));
|
||||||
|
tree.create('/package.json', JSON.stringify({}, undefined, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe(`Migration to use the application builder`, () => {
|
||||||
|
const schematicName = 'use-application-builder';
|
||||||
|
const schematicRunner = new SchematicTestRunner(
|
||||||
|
'migrations',
|
||||||
|
require.resolve('../migration-collection.json'),
|
||||||
|
);
|
||||||
|
|
||||||
|
let tree: UnitTestTree;
|
||||||
|
beforeEach(() => {
|
||||||
|
tree = new UnitTestTree(new EmptyTree());
|
||||||
|
createWorkSpaceConfig(tree);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should replace 'outputPath' to string if 'resourcesOutputPath' is set to 'media'`, async () => {
|
||||||
|
// Replace resourcesOutputPath
|
||||||
|
tree.overwrite('angular.json', tree.readContent('angular.json').replace('/resources', 'media'));
|
||||||
|
|
||||||
|
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||||
|
const {
|
||||||
|
projects: { app },
|
||||||
|
} = JSON.parse(newTree.readContent('/angular.json'));
|
||||||
|
|
||||||
|
const { outputPath, resourcesOutputPath } = app.architect['build'].options;
|
||||||
|
expect(outputPath).toEqual({
|
||||||
|
base: 'dist/project',
|
||||||
|
});
|
||||||
|
expect(resourcesOutputPath).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should set 'outputPath.media' if 'resourcesOutputPath' is set and is not 'media'`, async () => {
|
||||||
|
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||||
|
const {
|
||||||
|
projects: { app },
|
||||||
|
} = JSON.parse(newTree.readContent('/angular.json'));
|
||||||
|
|
||||||
|
const { outputPath, resourcesOutputPath } = app.architect['build'].options;
|
||||||
|
expect(outputPath).toEqual({
|
||||||
|
base: 'dist/project',
|
||||||
|
media: 'resources',
|
||||||
|
});
|
||||||
|
expect(resourcesOutputPath).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should remove 'browser' portion from 'outputPath'`, async () => {
|
||||||
|
// Replace outputPath
|
||||||
|
tree.overwrite(
|
||||||
|
'angular.json',
|
||||||
|
tree.readContent('angular.json').replace('dist/project/', 'dist/project/browser/'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
|
||||||
|
const {
|
||||||
|
projects: { app },
|
||||||
|
} = JSON.parse(newTree.readContent('/angular.json'));
|
||||||
|
|
||||||
|
const { outputPath } = app.architect['build'].options;
|
||||||
|
expect(outputPath).toEqual({
|
||||||
|
base: 'dist/project',
|
||||||
|
media: 'resources',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -9,7 +9,7 @@ import <% if (isStandalone) { %>bootstrap<% } else { %>AppServerModule<% } %> fr
|
|||||||
export function app(): express.Express {
|
export function app(): express.Express {
|
||||||
const server = express();
|
const server = express();
|
||||||
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
const serverDistFolder = dirname(fileURLToPath(import.meta.url));
|
||||||
const browserDistFolder = resolve(serverDistFolder, '../browser');
|
const browserDistFolder = resolve(serverDistFolder, '../<%= browserDistDirectory %>');
|
||||||
const indexHtml = join(serverDistFolder, 'index.server.html');
|
const indexHtml = join(serverDistFolder, 'index.server.html');
|
||||||
|
|
||||||
const commonEngine = new CommonEngine();
|
const commonEngine = new CommonEngine();
|
||||||
@ -19,7 +19,7 @@ export function app(): express.Express {
|
|||||||
|
|
||||||
// Example Express Rest API endpoints
|
// Example Express Rest API endpoints
|
||||||
// server.get('/api/**', (req, res) => { });
|
// server.get('/api/**', (req, res) => { });
|
||||||
// Serve static files from /browser
|
// Serve static files from /<%= browserDistDirectory %>
|
||||||
server.get('*.*', express.static(browserDistFolder, {
|
server.get('*.*', express.static(browserDistFolder, {
|
||||||
maxAge: '1y'
|
maxAge: '1y'
|
||||||
}));
|
}));
|
||||||
|
@ -6,9 +6,10 @@
|
|||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { join, normalize, strings } from '@angular-devkit/core';
|
import { isJsonObject, join, normalize, strings } from '@angular-devkit/core';
|
||||||
import {
|
import {
|
||||||
Rule,
|
Rule,
|
||||||
|
SchematicContext,
|
||||||
SchematicsException,
|
SchematicsException,
|
||||||
Tree,
|
Tree,
|
||||||
apply,
|
apply,
|
||||||
@ -19,6 +20,7 @@ import {
|
|||||||
schematic,
|
schematic,
|
||||||
url,
|
url,
|
||||||
} from '@angular-devkit/schematics';
|
} from '@angular-devkit/schematics';
|
||||||
|
import { posix } from 'node:path';
|
||||||
import { Schema as ServerOptions } from '../server/schema';
|
import { Schema as ServerOptions } from '../server/schema';
|
||||||
import { DependencyType, addDependency, readWorkspace, updateWorkspace } from '../utility';
|
import { DependencyType, addDependency, readWorkspace, updateWorkspace } from '../utility';
|
||||||
import { JSONFile } from '../utility/json-file';
|
import { JSONFile } from '../utility/json-file';
|
||||||
@ -33,8 +35,11 @@ import { Schema as SSROptions } from './schema';
|
|||||||
|
|
||||||
const SERVE_SSR_TARGET_NAME = 'serve-ssr';
|
const SERVE_SSR_TARGET_NAME = 'serve-ssr';
|
||||||
const PRERENDER_TARGET_NAME = 'prerender';
|
const PRERENDER_TARGET_NAME = 'prerender';
|
||||||
|
const DEFAULT_BROWSER_DIR = 'browser';
|
||||||
|
const DEFAULT_MEDIA_DIR = 'media';
|
||||||
|
const DEFAULT_SERVER_DIR = 'server';
|
||||||
|
|
||||||
async function getOutputPath(
|
async function getLegacyOutputPaths(
|
||||||
host: Tree,
|
host: Tree,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
target: 'server' | 'build',
|
target: 'server' | 'build',
|
||||||
@ -42,12 +47,12 @@ async function getOutputPath(
|
|||||||
// Generate new output paths
|
// Generate new output paths
|
||||||
const workspace = await readWorkspace(host);
|
const workspace = await readWorkspace(host);
|
||||||
const project = workspace.projects.get(projectName);
|
const project = workspace.projects.get(projectName);
|
||||||
const serverTarget = project?.targets.get(target);
|
const architectTarget = project?.targets.get(target);
|
||||||
if (!serverTarget || !serverTarget.options) {
|
if (!architectTarget?.options) {
|
||||||
throw new SchematicsException(`Cannot find 'options' for ${projectName} ${target} target.`);
|
throw new SchematicsException(`Cannot find 'options' for ${projectName} ${target} target.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { outputPath } = serverTarget.options;
|
const { outputPath } = architectTarget.options;
|
||||||
if (typeof outputPath !== 'string') {
|
if (typeof outputPath !== 'string') {
|
||||||
throw new SchematicsException(
|
throw new SchematicsException(
|
||||||
`outputPath for ${projectName} ${target} target is not a string.`,
|
`outputPath for ${projectName} ${target} target is not a string.`,
|
||||||
@ -57,6 +62,52 @@ async function getOutputPath(
|
|||||||
return outputPath;
|
return outputPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getApplicationBuilderOutputPaths(
|
||||||
|
host: Tree,
|
||||||
|
projectName: string,
|
||||||
|
): Promise<{ browser: string; server: string; base: string }> {
|
||||||
|
// Generate new output paths
|
||||||
|
const target = 'build';
|
||||||
|
const workspace = await readWorkspace(host);
|
||||||
|
const project = workspace.projects.get(projectName);
|
||||||
|
const architectTarget = project?.targets.get(target);
|
||||||
|
|
||||||
|
if (!architectTarget?.options) {
|
||||||
|
throw new SchematicsException(`Cannot find 'options' for ${projectName} ${target} target.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { outputPath } = architectTarget.options;
|
||||||
|
if (outputPath === null || outputPath === undefined) {
|
||||||
|
throw new SchematicsException(
|
||||||
|
`outputPath for ${projectName} ${target} target is undeined or null.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultDirs = {
|
||||||
|
server: DEFAULT_SERVER_DIR,
|
||||||
|
browser: DEFAULT_BROWSER_DIR,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (outputPath && isJsonObject(outputPath)) {
|
||||||
|
return {
|
||||||
|
...defaultDirs,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
...(outputPath as any),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof outputPath !== 'string') {
|
||||||
|
throw new SchematicsException(
|
||||||
|
`outputPath for ${projectName} ${target} target is not a string.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
base: outputPath,
|
||||||
|
...defaultDirs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function addScriptsRule({ project }: SSROptions, isUsingApplicationBuilder: boolean): Rule {
|
function addScriptsRule({ project }: SSROptions, isUsingApplicationBuilder: boolean): Rule {
|
||||||
return async (host) => {
|
return async (host) => {
|
||||||
const pkgPath = '/package.json';
|
const pkgPath = '/package.json';
|
||||||
@ -66,11 +117,11 @@ function addScriptsRule({ project }: SSROptions, isUsingApplicationBuilder: bool
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isUsingApplicationBuilder) {
|
if (isUsingApplicationBuilder) {
|
||||||
const distPath = await getOutputPath(host, project, 'build');
|
const { base, server } = await getApplicationBuilderOutputPaths(host, project);
|
||||||
pkg.scripts ??= {};
|
pkg.scripts ??= {};
|
||||||
pkg.scripts[`serve:ssr:${project}`] = `node ${distPath}/server/server.mjs`;
|
pkg.scripts[`serve:ssr:${project}`] = `node ${posix.join(base, server)}/server.mjs`;
|
||||||
} else {
|
} else {
|
||||||
const serverDist = await getOutputPath(host, project, 'server');
|
const serverDist = await getLegacyOutputPaths(host, project, 'server');
|
||||||
pkg.scripts = {
|
pkg.scripts = {
|
||||||
...pkg.scripts,
|
...pkg.scripts,
|
||||||
'dev:ssr': `ng run ${project}:${SERVE_SSR_TARGET_NAME}`,
|
'dev:ssr': `ng run ${project}:${SERVE_SSR_TARGET_NAME}`,
|
||||||
@ -111,6 +162,7 @@ function updateApplicationBuilderTsConfigRule(options: SSROptions): Rule {
|
|||||||
function updateApplicationBuilderWorkspaceConfigRule(
|
function updateApplicationBuilderWorkspaceConfigRule(
|
||||||
projectRoot: string,
|
projectRoot: string,
|
||||||
options: SSROptions,
|
options: SSROptions,
|
||||||
|
{ logger }: SchematicContext,
|
||||||
): Rule {
|
): Rule {
|
||||||
return updateWorkspace((workspace) => {
|
return updateWorkspace((workspace) => {
|
||||||
const buildTarget = workspace.projects.get(options.project)?.targets.get('build');
|
const buildTarget = workspace.projects.get(options.project)?.targets.get('build');
|
||||||
@ -118,8 +170,32 @@ function updateApplicationBuilderWorkspaceConfigRule(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let outputPath = buildTarget.options?.outputPath;
|
||||||
|
if (outputPath && isJsonObject(outputPath)) {
|
||||||
|
if (outputPath.browser === '') {
|
||||||
|
const base = outputPath.base as string;
|
||||||
|
logger.warn(
|
||||||
|
`The output location of the browser build has been updated from "${base}" to "${posix.join(
|
||||||
|
base,
|
||||||
|
DEFAULT_BROWSER_DIR,
|
||||||
|
)}".
|
||||||
|
You might need to adjust your deployment pipeline.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
(outputPath.media && outputPath.media !== DEFAULT_MEDIA_DIR) ||
|
||||||
|
(outputPath.server && outputPath.server !== DEFAULT_SERVER_DIR)
|
||||||
|
) {
|
||||||
|
delete outputPath.browser;
|
||||||
|
} else {
|
||||||
|
outputPath = outputPath.base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTarget.options = {
|
buildTarget.options = {
|
||||||
...buildTarget.options,
|
...buildTarget.options,
|
||||||
|
outputPath,
|
||||||
prerender: true,
|
prerender: true,
|
||||||
ssr: {
|
ssr: {
|
||||||
entry: join(normalize(projectRoot), 'server.ts'),
|
entry: join(normalize(projectRoot), 'server.ts'),
|
||||||
@ -238,23 +314,22 @@ function addDependencies(isUsingApplicationBuilder: boolean): Rule {
|
|||||||
|
|
||||||
function addServerFile(options: ServerOptions, isStandalone: boolean): Rule {
|
function addServerFile(options: ServerOptions, isStandalone: boolean): Rule {
|
||||||
return async (host) => {
|
return async (host) => {
|
||||||
|
const projectName = options.project;
|
||||||
const workspace = await readWorkspace(host);
|
const workspace = await readWorkspace(host);
|
||||||
const project = workspace.projects.get(options.project);
|
const project = workspace.projects.get(projectName);
|
||||||
if (!project) {
|
if (!project) {
|
||||||
throw new SchematicsException(`Invalid project name (${options.project})`);
|
throw new SchematicsException(`Invalid project name (${projectName})`);
|
||||||
}
|
}
|
||||||
|
const isUsingApplicationBuilder =
|
||||||
|
project?.targets?.get('build')?.builder === Builders.Application;
|
||||||
|
|
||||||
const browserDistDirectory = await getOutputPath(host, options.project, 'build');
|
const browserDistDirectory = isUsingApplicationBuilder
|
||||||
|
? (await getApplicationBuilderOutputPaths(host, projectName)).browser
|
||||||
|
: await getLegacyOutputPaths(host, projectName, 'build');
|
||||||
|
|
||||||
return mergeWith(
|
return mergeWith(
|
||||||
apply(
|
apply(
|
||||||
url(
|
url(`./files/${isUsingApplicationBuilder ? 'application-builder' : 'server-builder'}`),
|
||||||
`./files/${
|
|
||||||
project?.targets?.get('build')?.builder === Builders.Application
|
|
||||||
? 'application-builder'
|
|
||||||
: 'server-builder'
|
|
||||||
}`,
|
|
||||||
),
|
|
||||||
[
|
[
|
||||||
applyTemplates({
|
applyTemplates({
|
||||||
...strings,
|
...strings,
|
||||||
@ -270,7 +345,7 @@ function addServerFile(options: ServerOptions, isStandalone: boolean): Rule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function (options: SSROptions): Rule {
|
export default function (options: SSROptions): Rule {
|
||||||
return async (host) => {
|
return async (host, context) => {
|
||||||
const browserEntryPoint = await getMainFilePath(host, options.project);
|
const browserEntryPoint = await getMainFilePath(host, options.project);
|
||||||
const isStandalone = isStandaloneApp(host, browserEntryPoint);
|
const isStandalone = isStandaloneApp(host, browserEntryPoint);
|
||||||
|
|
||||||
@ -289,7 +364,7 @@ export default function (options: SSROptions): Rule {
|
|||||||
}),
|
}),
|
||||||
...(isUsingApplicationBuilder
|
...(isUsingApplicationBuilder
|
||||||
? [
|
? [
|
||||||
updateApplicationBuilderWorkspaceConfigRule(clientProject.root, options),
|
updateApplicationBuilderWorkspaceConfigRule(clientProject.root, options, context),
|
||||||
updateApplicationBuilderTsConfigRule(options),
|
updateApplicationBuilderTsConfigRule(options),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
|
@ -143,6 +143,52 @@ describe('SSR Schematic', () => {
|
|||||||
|
|
||||||
expect(scripts['serve:ssr:test-app']).toBe(`node dist/test-app/server/server.mjs`);
|
expect(scripts['serve:ssr:test-app']).toBe(`node dist/test-app/server/server.mjs`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('works when using a custom "outputPath.browser" and "outputPath.server" values', async () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const config = appTree.readJson('/angular.json') as any;
|
||||||
|
const build = config.projects['test-app'].architect.build;
|
||||||
|
|
||||||
|
build.options.outputPath = {
|
||||||
|
base: build.options.outputPath,
|
||||||
|
browser: 'public',
|
||||||
|
server: 'node-server',
|
||||||
|
};
|
||||||
|
|
||||||
|
appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
|
||||||
|
const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);
|
||||||
|
|
||||||
|
const { scripts } = tree.readJson('/package.json') as { scripts: Record<string, string> };
|
||||||
|
expect(scripts['serve:ssr:test-app']).toBe(`node dist/test-app/node-server/server.mjs`);
|
||||||
|
|
||||||
|
const serverFileContent = tree.readContent('/projects/test-app/server.ts');
|
||||||
|
expect(serverFileContent).toContain(`resolve(serverDistFolder, '../public')`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`removes "outputPath.browser" when it's an empty string`, async () => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const config = appTree.readJson('/angular.json') as any;
|
||||||
|
const build = config.projects['test-app'].architect.build;
|
||||||
|
|
||||||
|
build.options.outputPath = {
|
||||||
|
base: build.options.outputPath,
|
||||||
|
browser: '',
|
||||||
|
server: 'node-server',
|
||||||
|
};
|
||||||
|
|
||||||
|
appTree.overwrite('/angular.json', JSON.stringify(config, undefined, 2));
|
||||||
|
const tree = await schematicRunner.runSchematic('ssr', defaultOptions, appTree);
|
||||||
|
|
||||||
|
const { scripts } = tree.readJson('/package.json') as { scripts: Record<string, string> };
|
||||||
|
expect(scripts['serve:ssr:test-app']).toBe(`node dist/test-app/node-server/server.mjs`);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const updatedConfig = tree.readJson('/angular.json') as any;
|
||||||
|
expect(updatedConfig.projects['test-app'].architect.build.options.outputPath).toEqual({
|
||||||
|
base: 'dist/test-app',
|
||||||
|
server: 'node-server',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Legacy browser builder', () => {
|
describe('Legacy browser builder', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user