From 9de99202e9427973c7983940fcdea9e4580a79bd Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 18 Jan 2023 19:49:07 +0000 Subject: [PATCH] fix(@angular-devkit/core): handle number like strings in workspace writer The workspace writer previously transformed number like strings to numbers which causes failures when a project is named using a number like name. Closes #24541 (cherry picked from commit f6f5d79199613b9f9fa82680cdafd4a622ff4be0) --- .../json/test/cases/AddProject3.json | 141 ++++++++++++++++++ .../core/src/workspace/json/writer.ts | 4 +- .../core/src/workspace/json/writer_spec.ts | 49 ++---- 3 files changed, 155 insertions(+), 39 deletions(-) create mode 100644 packages/angular_devkit/core/src/workspace/json/test/cases/AddProject3.json diff --git a/packages/angular_devkit/core/src/workspace/json/test/cases/AddProject3.json b/packages/angular_devkit/core/src/workspace/json/test/cases/AddProject3.json new file mode 100644 index 000000000000..1766edbd5fb3 --- /dev/null +++ b/packages/angular_devkit/core/src/workspace/json/test/cases/AddProject3.json @@ -0,0 +1,141 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "my-app": { + "root": "", + "projectType": "application", + "prefix": "app", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + }, + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/my-app", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.app.json", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "aot": true, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + } + ] + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "options": { + "browserTarget": "my-app:build" + }, + "configurations": { + "production": { + "browserTarget": "my-app:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "my-app:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "src/tsconfig.spec.json", + "karmaConfig": "src/karma.conf.js", + "styles": [ + "src/styles.scss" + ], + "scripts": [], + "assets": [ + "src/favicon.ico", + "src/assets" + ] + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "my-app-e2e": { + "root": "e2e/", + "projectType": "application", + "prefix": "", + "architect": { + "e2e": { + "builder": "@angular-devkit/build-angular:protractor", + "options": { + "protractorConfig": "e2e/protractor.conf.js", + "devServerTarget": "my-app:serve" + }, + "configurations": { + "production": { + "devServerTarget": "my-app:serve:production" + } + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": "e2e/tsconfig.e2e.json", + "exclude": [ + "**/node_modules/**" + ] + } + } + } + }, + "1": { + "root": "src" + } + }, + "defaultProject": "my-app" +} diff --git a/packages/angular_devkit/core/src/workspace/json/writer.ts b/packages/angular_devkit/core/src/workspace/json/writer.ts index 7d5d5df05c7f..a5d0fb145a06 100644 --- a/packages/angular_devkit/core/src/workspace/json/writer.ts +++ b/packages/angular_devkit/core/src/workspace/json/writer.ts @@ -156,12 +156,10 @@ function updateJsonWorkspace(metadata: JsonWorkspaceMetadata): string { jsonPath[2] = 'architect'; } - // modify - const newJsonPath = jsonPath.map((v) => (isFinite(+v) ? +v : v)); // TODO: `modify` re-parses the content every time. // See: https://github.com/microsoft/node-jsonc-parser/blob/35d94cd71bd48f9784453b2439262c938e21d49b/src/impl/edit.ts#L18 // Ideally this should accept a string or an AST to avoid the potentially expensive repeat parsing operation. - const edits = modify(content, newJsonPath, normalizeValue(value, type), { + const edits = modify(content, jsonPath, normalizeValue(value, type), { formattingOptions: { insertSpaces: true, tabSize: 2, diff --git a/packages/angular_devkit/core/src/workspace/json/writer_spec.ts b/packages/angular_devkit/core/src/workspace/json/writer_spec.ts index d03d8eb3635b..0212945aab50 100644 --- a/packages/angular_devkit/core/src/workspace/json/writer_spec.ts +++ b/packages/angular_devkit/core/src/workspace/json/writer_spec.ts @@ -43,7 +43,7 @@ function createTestCaseHost(inputData = '') { require.resolve(join(__dirname, 'test', 'cases', path) + '.json'), 'utf8', ); - expect(data).toEqual(testCase); + expect(data.trim()).toEqual(testCase.trim()); } catch (e) { fail(`Unable to load test case '${path}': ${e instanceof Error ? e.message : e}`); } @@ -186,7 +186,6 @@ describe('writeJsonWorkpaceFile', () => { it('retains comments and formatting when modifying the workspace', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-baz'] = 10; @@ -196,7 +195,6 @@ describe('writeJsonWorkpaceFile', () => { it('adds a project to workspace without any projects', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.projects.add({ @@ -209,7 +207,6 @@ describe('writeJsonWorkpaceFile', () => { it('adds a project to workspace with existing projects', async () => { const host = createTestCaseHost(representativeFile); - const workspace = await readJsonWorkspace('', host); workspace.projects.add({ @@ -220,9 +217,20 @@ describe('writeJsonWorkpaceFile', () => { await writeJsonWorkspace(workspace, host, 'AddProject2'); }); + it('adds a project to workspace with existing projects when name is number like', async () => { + const host = createTestCaseHost(representativeFile); + const workspace = await readJsonWorkspace('', host); + + workspace.projects.add({ + name: '1', + root: 'src', + }); + + await writeJsonWorkspace(workspace, host, 'AddProject3'); + }); + it('adds a project with targets', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.projects.add({ @@ -246,7 +254,6 @@ describe('writeJsonWorkpaceFile', () => { it('adds a project with targets using reference to workspace', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.projects.add({ @@ -278,7 +285,6 @@ describe('writeJsonWorkpaceFile', () => { it("modifies a project's properties", async () => { const host = createTestCaseHost(representativeFile); - const workspace = await readJsonWorkspace('', host); const project = workspace.projects.get('my-app'); @@ -295,7 +301,6 @@ describe('writeJsonWorkpaceFile', () => { it("sets a project's properties", async () => { const host = createTestCaseHost(representativeFile); - const workspace = await readJsonWorkspace('', host); const project = workspace.projects.get('my-app'); @@ -312,7 +317,6 @@ describe('writeJsonWorkpaceFile', () => { it('adds a target to an existing project', async () => { const host = createTestCaseHost(representativeFile); - const workspace = await readJsonWorkspace('', host); const project = workspace.projects.get('my-app'); @@ -332,7 +336,6 @@ describe('writeJsonWorkpaceFile', () => { it('deletes a target from an existing project', async () => { const host = createTestCaseHost(representativeFile); - const workspace = await readJsonWorkspace('', host); const project = workspace.projects.get('my-app'); @@ -349,7 +352,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports adding an empty array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-array'] = []; @@ -359,7 +361,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports adding an array with values', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-array'] = [5, 'a', false, null, true, 9.9]; @@ -369,7 +370,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports adding an empty array then pushing as an extension', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-array'] = []; @@ -380,7 +380,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports pushing to an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -391,7 +390,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports unshifting to an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -402,7 +400,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports shifting from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -413,7 +410,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports splicing an existing array without new values', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -424,7 +420,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports splicing an existing array with new values', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -435,7 +430,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports popping from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -446,7 +440,6 @@ describe('writeJsonWorkpaceFile', () => { it('supports sorting from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -457,7 +450,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces a value at zero index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -468,7 +460,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces a value at inner index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -479,7 +470,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces a value at last index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -490,7 +480,6 @@ describe('writeJsonWorkpaceFile', () => { it('deletes a value at zero index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -501,7 +490,6 @@ describe('writeJsonWorkpaceFile', () => { it('deletes a value at inner index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -512,7 +500,6 @@ describe('writeJsonWorkpaceFile', () => { it('deletes and then adds a value at inner index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -524,7 +511,6 @@ describe('writeJsonWorkpaceFile', () => { it('deletes a value at last index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -535,7 +521,6 @@ describe('writeJsonWorkpaceFile', () => { it('deletes and then adds a value at last index from an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); const array = (workspace.extensions['x-foo'] as JsonObject)['is'] as JsonArray; @@ -547,7 +532,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces an existing array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); (workspace.extensions['x-foo'] as JsonObject)['is'] = ['value']; @@ -557,7 +541,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces an existing array with an empty array', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); (workspace.extensions['x-foo'] as JsonObject)['is'] = []; @@ -567,7 +550,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces an existing object with a new object', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-foo'] = { replacement: true }; @@ -577,7 +559,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces an existing object with an empty object', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-foo'] = {}; @@ -587,7 +568,6 @@ describe('writeJsonWorkpaceFile', () => { it('replaces an existing object with a different value type', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-foo'] = null; @@ -597,7 +577,6 @@ describe('writeJsonWorkpaceFile', () => { it('removes a property when property value is set to undefined', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); workspace.extensions['x-baz'] = undefined; @@ -607,7 +586,6 @@ describe('writeJsonWorkpaceFile', () => { it('removes a property when using delete operator', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); delete workspace.extensions['x-baz']; @@ -617,7 +595,6 @@ describe('writeJsonWorkpaceFile', () => { it('removes multiple properties when using delete operator', async () => { const host = createTestCaseHost(basicFile); - const workspace = await readJsonWorkspace('', host); delete workspace.extensions['x-baz'];