Skip to content

Commit 3476674

Browse files
clydinKeen Yee Liau
authored and
Keen Yee Liau
committed
fix(@schematics/angular): improve i18n output path option migration
Output paths that use the locale within a locale specific configuration will now be automatically removed. This will prevent the potential for the migrated configuration to generate an output path with double locale segments.
1 parent a9b947c commit 3476674

File tree

2 files changed

+98
-17
lines changed

2 files changed

+98
-17
lines changed

packages/schematics/angular/migrations/update-9/update-workspace-config.ts

Lines changed: 65 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import { JsonAstObject } from '@angular-devkit/core';
8+
import { JsonAstObject, logging } from '@angular-devkit/core';
99
import { Rule, Tree, UpdateRecorder } from '@angular-devkit/schematics';
10+
import { posix } from 'path';
1011
import { getWorkspacePath } from '../../utility/config';
1112
import { NodeDependencyType, addPackageJsonDependency, getPackageJsonDependency } from '../../utility/dependencies';
1213
import {
@@ -25,23 +26,23 @@ export const ANY_COMPONENT_STYLE_BUDGET = {
2526
};
2627

2728
export function updateWorkspaceConfig(): Rule {
28-
return (tree: Tree) => {
29+
return (tree, context) => {
2930
const workspacePath = getWorkspacePath(tree);
3031
const workspace = getWorkspace(tree);
3132
const recorder = tree.beginUpdate(workspacePath);
3233

33-
for (const { target, project } of getTargets(workspace, 'build', Builders.Browser)) {
34+
for (const { target } of getTargets(workspace, 'build', Builders.Browser)) {
3435
updateStyleOrScriptOption('styles', recorder, target);
3536
updateStyleOrScriptOption('scripts', recorder, target);
3637
addAnyComponentStyleBudget(recorder, target);
3738
updateAotOption(tree, recorder, target);
38-
addBuilderI18NOptions(recorder, target, project);
39+
addBuilderI18NOptions(recorder, target, context.logger);
3940
}
4041

41-
for (const { target, project } of getTargets(workspace, 'test', Builders.Karma)) {
42+
for (const { target } of getTargets(workspace, 'test', Builders.Karma)) {
4243
updateStyleOrScriptOption('styles', recorder, target);
4344
updateStyleOrScriptOption('scripts', recorder, target);
44-
addBuilderI18NOptions(recorder, target, project);
45+
addBuilderI18NOptions(recorder, target, context.logger);
4546
}
4647

4748
for (const { target } of getTargets(workspace, 'server', Builders.Server)) {
@@ -148,7 +149,11 @@ function addProjectI18NOptions(
148149
}
149150
}
150151

151-
function addBuilderI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstObject, projectConfig: JsonAstObject) {
152+
function addBuilderI18NOptions(
153+
recorder: UpdateRecorder,
154+
builderConfig: JsonAstObject,
155+
logger: logging.LoggerApi,
156+
) {
152157
const options = getAllOptions(builderConfig);
153158
const mainOptions = findPropertyInAstObject(builderConfig, 'options');
154159
const mainBaseHref =
@@ -160,31 +165,75 @@ function addBuilderI18NOptions(recorder: UpdateRecorder, builderConfig: JsonAstO
160165

161166
for (const option of options) {
162167
const localeId = findPropertyInAstObject(option, 'i18nLocale');
168+
const i18nFile = findPropertyInAstObject(option, 'i18nFile');
169+
170+
// The format is always auto-detected now
171+
const i18nFormat = findPropertyInAstObject(option, 'i18nFormat');
172+
if (i18nFormat) {
173+
removePropertyInAstObject(recorder, option, 'i18nFormat');
174+
}
175+
176+
const outputPath = findPropertyInAstObject(option, 'outputPath');
177+
if (
178+
localeId &&
179+
localeId.kind === 'string' &&
180+
i18nFile &&
181+
outputPath &&
182+
outputPath.kind === 'string'
183+
) {
184+
// This first block was intended to remove the redundant output path field
185+
// but due to defects in the recorder, removing the option will cause malformed json
186+
// if (
187+
// mainOutputPathValue &&
188+
// outputPath.value.match(
189+
// new RegExp(`[/\\\\]?${mainOutputPathValue}[/\\\\]${localeId.value}[/\\\\]?$`),
190+
// )
191+
// ) {
192+
// removePropertyInAstObject(recorder, option, 'outputPath');
193+
// } else
194+
if (outputPath.value.match(new RegExp(`[/\\\\]${localeId.value}[/\\\\]?$`))) {
195+
const newOutputPath = outputPath.value.replace(
196+
new RegExp(`[/\\\\]${localeId.value}[/\\\\]?$`),
197+
'',
198+
);
199+
const { start, end } = outputPath;
200+
recorder.remove(start.offset, end.offset - start.offset);
201+
recorder.insertLeft(start.offset, `"${newOutputPath}"`);
202+
} else {
203+
logger.warn(
204+
`Output path value "${outputPath.value}" for locale "${localeId.value}" is not supported with the new localization system. ` +
205+
`With the current value, the localized output would be written to "${posix.join(
206+
outputPath.value,
207+
localeId.value,
208+
)}". ` +
209+
`Keeping existing options for the target configuration of locale "${localeId.value}".`,
210+
);
211+
212+
continue;
213+
}
214+
}
215+
163216
if (localeId && localeId.kind === 'string') {
164217
// add new localize option
165218
insertPropertyInAstObjectInOrder(recorder, option, 'localize', [localeId.value], 12);
166219
removePropertyInAstObject(recorder, option, 'i18nLocale');
167220
}
168221

169-
const i18nFile = findPropertyInAstObject(option, 'i18nFile');
170222
if (i18nFile) {
171223
removePropertyInAstObject(recorder, option, 'i18nFile');
172224
}
173225

174-
const i18nFormat = findPropertyInAstObject(option, 'i18nFormat');
175-
if (i18nFormat) {
176-
removePropertyInAstObject(recorder, option, 'i18nFormat');
177-
}
178-
179226
// localize base HREF values are controlled by the i18n configuration
180227
const baseHref = findPropertyInAstObject(option, 'baseHref');
181228
if (localeId && i18nFile && baseHref) {
182-
removePropertyInAstObject(recorder, option, 'baseHref');
183-
184229
// if the main option set has a non-default base href,
185230
// ensure that the augmented base href has the correct base value
186231
if (hasMainBaseHref) {
187-
insertPropertyInAstObjectInOrder(recorder, option, 'baseHref', '/', 12);
232+
const { start, end } = baseHref;
233+
recorder.remove(start.offset, end.offset - start.offset);
234+
recorder.insertLeft(start.offset, `"/"`);
235+
} else {
236+
removePropertyInAstObject(recorder, option, 'baseHref');
188237
}
189238
}
190239
}

packages/schematics/angular/migrations/update-9/update-workspace-config_spec.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ describe('Migration to version 9', () => {
301301
describe('i18n configuration', () => {
302302
function getI18NConfig(localId: string): object {
303303
return {
304-
outputPath: `dist/my-project-${localId}/`,
304+
outputPath: `dist/my-project/${localId}/`,
305305
i18nFile: `src/locale/messages.${localId}.xlf`,
306306
i18nFormat: 'xlf',
307307
i18nLocale: localId,
@@ -362,6 +362,38 @@ describe('Migration to version 9', () => {
362362
expect(config.configurations.de.baseHref).toBeUndefined();
363363
});
364364

365+
it('should remove outputPath option when used with i18n options and contains locale code', async () => {
366+
let config = getWorkspaceTargets(tree);
367+
config.build.options.outputPath = 'dist';
368+
config.build.configurations.fr = { ...getI18NConfig('fr'), outputPath: 'dist/fr' };
369+
config.build.configurations.de = { ...getI18NConfig('de'), outputPath: '/dist/de/' };
370+
updateWorkspaceTargets(tree, config);
371+
372+
const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
373+
config = getWorkspaceTargets(tree2).build;
374+
expect(config.configurations.fr.outputPath).toBe('dist');
375+
expect(config.configurations.de.outputPath).toBe('/dist');
376+
});
377+
378+
it('should keep old i18n options except format when output path is not supported', async () => {
379+
let config = getWorkspaceTargets(tree);
380+
config.build.options.outputPath = 'dist';
381+
config.build.configurations.fr = { ...getI18NConfig('fr'), outputPath: 'dist/abc' };
382+
config.build.configurations.de = { ...getI18NConfig('de'), outputPath: '/dist/123' };
383+
updateWorkspaceTargets(tree, config);
384+
385+
const tree2 = await schematicRunner.runSchematicAsync('workspace-version-9', {}, tree.branch()).toPromise();
386+
config = getWorkspaceTargets(tree2).build;
387+
expect(config.configurations.fr.outputPath).toBe('dist/abc');
388+
expect(config.configurations.fr.i18nFormat).toBeUndefined();
389+
expect(config.configurations.fr.i18nFile).toBeDefined();
390+
expect(config.configurations.fr.i18nLocale).toBe('fr');
391+
expect(config.configurations.de.outputPath).toBe('/dist/123');
392+
expect(config.configurations.de.i18nFormat).toBeUndefined();
393+
expect(config.configurations.de.i18nFile).toBeDefined();
394+
expect(config.configurations.de.i18nLocale).toBe('de');
395+
});
396+
365397
it('should keep baseHref option when not used with i18n options', async () => {
366398
let config = getWorkspaceTargets(tree);
367399
config.build.options = getI18NConfig('fr');

0 commit comments

Comments
 (0)