diff --git a/src/lib/library/library.factory.test.ts b/src/lib/library/library.factory.test.ts index 9f1257fe7..17c32c67c 100644 --- a/src/lib/library/library.factory.test.ts +++ b/src/lib/library/library.factory.test.ts @@ -1,3 +1,4 @@ +import { EmptyTree, Tree } from '@angular-devkit/schematics'; import { SchematicTestRunner, UnitTestTree, @@ -80,4 +81,53 @@ describe('Library Factory', () => { '/libs/project/src/project.service.spec.js', ]); }); + + it('should sort library names in nest-cli.json, package.json and tsconfig.json', async () => { + const options: LibraryOptions[] = [ + { + name: 'c', + language: 'ts', + prefix: 'app', + }, + { + name: 'a', + language: 'ts', + prefix: 'app', + }, + { + name: 'b', + language: 'ts', + prefix: 'app', + } + ]; + + let tree: Tree = new EmptyTree(); + tree.create('/package.json', `{"name": "my-pacakge","version": "1.0.0","jest": {}}`); + tree.create('/tsconfig.json', `{compilerOptions: {}}`); + + + for (const o of options) { + tree = await runner.runSchematic('library', o, tree); + } + + const packageJson = tree.readJson('/package.json'); + expect(packageJson['jest']['moduleNameMapper']).toEqual([ + '^app/a(|/.*)$', + '^app/b(|/.*)$', + '^app/c(|/.*)$' + ]); // Sorted jest.moduleNameMapper by keys + + const tsConfigJson = tree.readJson('/tsconfig.json'); + expect(tsConfigJson['compilerOptions']['paths']).toEqual([ + 'app/a', + 'app/a/*', + 'app/b', + 'app/b/*', + 'app/c', + 'app/c/*' + ]); // Sorted compilerOptions.paths by keys + + const config = tree.readJson('/nest-cli.json'); + expect(Object.keys(config['projects'])).toEqual(['a', 'b', 'c']); // Sorted + }); }); diff --git a/src/lib/library/library.factory.ts b/src/lib/library/library.factory.ts index b95f822ea..9a8284c01 100644 --- a/src/lib/library/library.factory.ts +++ b/src/lib/library/library.factory.ts @@ -13,7 +13,7 @@ import { url, } from '@angular-devkit/schematics'; import { parse } from 'jsonc-parser'; -import { normalizeToKebabOrSnakeCase } from '../../utils/formatting'; +import { inPlaceSortByKeys, normalizeToKebabOrSnakeCase } from '../../utils'; import { DEFAULT_LANGUAGE, DEFAULT_LIB_PATH, @@ -133,6 +133,8 @@ function updateJestConfig( const packageKeyRegex = '^' + packageKey + '(|/.*)$'; const packageRoot = join('' as Path, distRoot); jestOptions.moduleNameMapper[packageKeyRegex] = join(packageRoot, '$1'); + + inPlaceSortByKeys(jestOptions.moduleNameMapper); } function updateNpmScripts( @@ -181,6 +183,8 @@ function updateJestEndToEnd(options: LibraryOptions) { const packageRoot = '/../' + distRoot; jestOptions.moduleNameMapper[deepPackagePath] = packageRoot + '/$1'; jestOptions.moduleNameMapper[packageKey] = packageRoot; + + inPlaceSortByKeys(jestOptions.moduleNameMapper); }, ); }; @@ -238,6 +242,8 @@ function updateTsConfig( tsconfig.compilerOptions.paths[deepPackagePath] = []; } tsconfig.compilerOptions.paths[deepPackagePath].push(distRoot + '/*'); + + inPlaceSortByKeys(tsconfig.compilerOptions.paths); }, ); }; @@ -284,6 +290,8 @@ function addLibraryToCliOptions( ); } optionsFile.projects[projectName] = project; + + inPlaceSortByKeys(optionsFile.projects); }, ); }; diff --git a/src/lib/sub-app/sub-app.factory.test.ts b/src/lib/sub-app/sub-app.factory.test.ts index ad974d672..5ec8c7337 100644 --- a/src/lib/sub-app/sub-app.factory.test.ts +++ b/src/lib/sub-app/sub-app.factory.test.ts @@ -1,8 +1,10 @@ +import { EmptyTree, Tree } from '@angular-devkit/schematics'; import { SchematicTestRunner, UnitTestTree, } from '@angular-devkit/schematics/testing'; import * as path from 'path'; +import { LibraryOptions } from '../library/library.schema'; import { SubAppOptions } from './sub-app.schema'; describe('SubApp Factory', () => { @@ -126,4 +128,31 @@ describe('SubApp Factory', () => { ].sort(), ); }); + + it('should sort sub-app names in nest-cli.json', async () => { + const options: SubAppOptions[] = [ + { + name: 'c', + language: 'ts', + }, + { + name: 'a', + language: 'ts', + }, + { + name: 'b', + language: 'ts', + } + ]; + + let tree: Tree = new EmptyTree(); + tree.create('/nest-cli.json', `{"monorepo": true, "projects": {}}`); + + for (const o of options) { + tree = await runner.runSchematic('sub-app', o, tree); + } + + const config = tree.readJson('/nest-cli.json'); + expect(Object.keys(config['projects'])).toEqual(['a', 'b', 'c']); // Sorted + }); }); diff --git a/src/lib/sub-app/sub-app.factory.ts b/src/lib/sub-app/sub-app.factory.ts index 124a2b25c..9b1321000 100644 --- a/src/lib/sub-app/sub-app.factory.ts +++ b/src/lib/sub-app/sub-app.factory.ts @@ -16,7 +16,7 @@ import { } from '@angular-devkit/schematics'; import { existsSync, readFileSync } from 'fs'; import { parse, stringify } from 'comment-json'; -import { normalizeToKebabOrSnakeCase } from '../../utils/formatting'; +import { inPlaceSortByKeys, normalizeToKebabOrSnakeCase } from '../../utils'; import { DEFAULT_APPS_PATH, DEFAULT_APP_NAME, @@ -150,6 +150,8 @@ function updateTsConfig() { if (!tsconfig.compilerOptions.paths) { tsconfig.compilerOptions.paths = {}; } + + inPlaceSortByKeys(tsconfig.compilerOptions.paths); }, ); }; @@ -324,6 +326,8 @@ function addAppsToCliOptions( ); } optionsFile.projects[projectName] = project; + + inPlaceSortByKeys(optionsFile.projects); }, ); }; diff --git a/src/utils/index.ts b/src/utils/index.ts index ffb5e02ac..9dcf720da 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from './name.parser'; export * from './path.solver'; export * from './source-root.helpers'; export * from './formatting'; +export * from './object-sorting'; diff --git a/src/utils/object-sorting.ts b/src/utils/object-sorting.ts new file mode 100644 index 000000000..c5cea8fd8 --- /dev/null +++ b/src/utils/object-sorting.ts @@ -0,0 +1,17 @@ +/** + * In-place sort object entities by their keys so that it can be serialized to json with sorted order. + * @param object + * @returns The original object with modified entities order. + */ +export function inPlaceSortByKeys(object: Record): Record { + const sorted: Record = {}; + + const keys = Object.keys(object); + keys.sort(); + for (const key of keys) { + sorted[key] = object[key]; + delete object[key]; + } + + return Object.assign(object, sorted); +} diff --git a/test/utils/object-sorting.test.ts b/test/utils/object-sorting.test.ts new file mode 100644 index 000000000..764d8fed3 --- /dev/null +++ b/test/utils/object-sorting.test.ts @@ -0,0 +1,14 @@ +import { inPlaceSortByKeys } from '../../src/utils/object-sorting'; + + +describe('inPlaceSortByKeys', () => { + it('should in-place sort the entities by their keys', () => { + const input = { z: 'z', b: 'b', c: 'c', a: 'a', }; + expect(Object.keys(input)).toEqual(['z', 'b', 'c', 'a']); + + const got = inPlaceSortByKeys(input); + + expect(got).toBe(input); // Same object + expect(Object.keys(got)).toEqual(['a', 'b', 'c', 'z']); + }) +}) \ No newline at end of file