Skip to content

Commit 0da5863

Browse files
authored
fix(@schematics/angular): migrate serviceWorker: true to a config path in use-application-builder
The browser builder accepts `serviceWorker: true` without `ngswConfigPath` and falls back to `<projectRoot>/ngsw-config.json`, but the application builder only accepts a path or `false`. The migration kept `true` when `ngswConfigPath` was not set in the same options object, so the first `ng build` after migrating failed schema validation.
1 parent ef43725 commit 0da5863

2 files changed

Lines changed: 90 additions & 2 deletions

File tree

‎packages/schematics/angular/migrations/use-application-builder/migration.ts‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@ import {
3939

4040
function* updateBuildTarget(
4141
projectName: string,
42+
projectRoot: string,
4243
buildTarget: TargetDefinition,
4344
serverTarget: TargetDefinition | undefined,
4445
tree: Tree,
4546
context: SchematicContext,
4647
): Iterable<Rule> {
4748
// Update builder target and options
4849
buildTarget.builder = Builders.Application;
50+
const baseServiceWorker = buildTarget.options?.['serviceWorker'];
51+
const baseNgswConfigPath = buildTarget.options?.['ngswConfigPath'];
4952

5053
for (const [, options] of allTargetOptions(buildTarget, false)) {
5154
if (options['index'] === '') {
@@ -57,7 +60,18 @@ function* updateBuildTarget(
5760
if (serverTarget && typeof options['browser'] === 'string') {
5861
options['server'] = dirname(options['browser']) + '/main.server.ts';
5962
}
60-
options['serviceWorker'] = options['ngswConfigPath'] ?? options['serviceWorker'];
63+
if (options['serviceWorker'] === true) {
64+
// The application builder only accepts a path, while the browser builder defaults it in code.
65+
options['serviceWorker'] =
66+
options['ngswConfigPath'] ?? baseNgswConfigPath ?? join(projectRoot, 'ngsw-config.json');
67+
} else if (
68+
options['serviceWorker'] === undefined &&
69+
baseServiceWorker === true &&
70+
typeof options['ngswConfigPath'] === 'string'
71+
) {
72+
// A configuration that only changes the config path of the service worker enabled in the base options.
73+
options['serviceWorker'] = options['ngswConfigPath'];
74+
}
6175

6276
if (typeof options['polyfills'] === 'string') {
6377
options['polyfills'] = [options['polyfills']];
@@ -197,7 +211,9 @@ function updateProjects(tree: Tree, context: SchematicContext) {
197211

198212
const serverTarget = project.targets.get('server');
199213

200-
rules.push(...updateBuildTarget(name, buildTarget, serverTarget, tree, context));
214+
rules.push(
215+
...updateBuildTarget(name, project.root, buildTarget, serverTarget, tree, context),
216+
);
201217

202218
// Delete all redundant targets
203219
for (const [key, target] of project.targets) {

‎packages/schematics/angular/migrations/use-application-builder/migration_spec.ts‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,78 @@ describe(`Migration to use the application builder`, () => {
113113
});
114114
});
115115

116+
it(`should replace 'serviceWorker: true' with the default 'ngsw-config.json' path`, async () => {
117+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
118+
const workspaceContent = tree.readJson('/angular.json') as Record<string, any>;
119+
workspaceContent['projects']['app']['architect']['build']['options']['serviceWorker'] = true;
120+
tree.overwrite('/angular.json', JSON.stringify(workspaceContent));
121+
122+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
123+
const {
124+
projects: { app },
125+
} = JSON.parse(newTree.readContent('/angular.json'));
126+
127+
const { serviceWorker, ngswConfigPath } = app.architect['build'].options;
128+
expect(serviceWorker).toBe('/project/app/ngsw-config.json');
129+
expect(ngswConfigPath).toBeUndefined();
130+
});
131+
132+
it(`should replace 'serviceWorker: true' in a configuration with the base 'ngswConfigPath'`, async () => {
133+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
134+
const workspaceContent = tree.readJson('/angular.json') as Record<string, any>;
135+
const build = workspaceContent['projects']['app']['architect']['build'];
136+
build['options']['ngswConfigPath'] = 'src/ngsw-config.json';
137+
build['configurations'] = { production: { serviceWorker: true } };
138+
tree.overwrite('/angular.json', JSON.stringify(workspaceContent));
139+
140+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
141+
const {
142+
projects: { app },
143+
} = JSON.parse(newTree.readContent('/angular.json'));
144+
145+
expect(app.architect['build'].configurations.production.serviceWorker).toBe(
146+
'src/ngsw-config.json',
147+
);
148+
expect(app.architect['build'].options.serviceWorker).toBeUndefined();
149+
});
150+
151+
it(`should not enable the service worker when 'serviceWorker' is false`, async () => {
152+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
153+
const workspaceContent = tree.readJson('/angular.json') as Record<string, any>;
154+
const build = workspaceContent['projects']['app']['architect']['build'];
155+
build['options']['serviceWorker'] = false;
156+
build['options']['ngswConfigPath'] = 'src/ngsw-config.json';
157+
tree.overwrite('/angular.json', JSON.stringify(workspaceContent));
158+
159+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
160+
const {
161+
projects: { app },
162+
} = JSON.parse(newTree.readContent('/angular.json'));
163+
164+
const { serviceWorker, ngswConfigPath } = app.architect['build'].options;
165+
expect(serviceWorker).toBeFalse();
166+
expect(ngswConfigPath).toBeUndefined();
167+
});
168+
169+
it(`should use a configuration 'ngswConfigPath' when the base options enable the service worker`, async () => {
170+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
171+
const workspaceContent = tree.readJson('/angular.json') as Record<string, any>;
172+
const build = workspaceContent['projects']['app']['architect']['build'];
173+
build['options']['serviceWorker'] = true;
174+
build['configurations'] = { production: { ngswConfigPath: 'src/ngsw-config.prod.json' } };
175+
tree.overwrite('/angular.json', JSON.stringify(workspaceContent));
176+
177+
const newTree = await schematicRunner.runSchematic(schematicName, {}, tree);
178+
const {
179+
projects: { app },
180+
} = JSON.parse(newTree.readContent('/angular.json'));
181+
182+
expect(app.architect['build'].options.serviceWorker).toBe('/project/app/ngsw-config.json');
183+
expect(app.architect['build'].configurations.production.serviceWorker).toBe(
184+
'src/ngsw-config.prod.json',
185+
);
186+
});
187+
116188
it(`should remove 'builderMode' from karma options`, async () => {
117189
addWorkspaceTarget(tree, 'test', {
118190
'builder': Builders.Karma,

0 commit comments

Comments
 (0)