diff --git a/.cz-config.js b/.cz-config.js index d620fd03b7..b6b1246928 100644 --- a/.cz-config.js +++ b/.cz-config.js @@ -21,6 +21,7 @@ module.exports = { { name: 'nextjs', description: 'anything Next specific' }, { name: 'nest', description: 'anything Nest specific' }, { name: 'node', description: 'anything Node specific' }, + { name: 'express', description: 'anything Express specific' }, { name: 'nx-plugin', description: 'anything Nx Plugin specific' }, { name: 'react', description: 'anything React specific' }, { name: 'web', description: 'anything Web specific' }, diff --git a/docs/angular/api-express/schematics/application.md b/docs/angular/api-express/schematics/application.md index e33628c4d7..fa4f5a0ae5 100644 --- a/docs/angular/api-express/schematics/application.md +++ b/docs/angular/api-express/schematics/application.md @@ -48,6 +48,14 @@ Type: `string` Frontend project that needs to access this application. This sets up proxy configuration. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -64,6 +72,16 @@ Type: `string` The name of the application. +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/angular/api-node/schematics/application.md b/docs/angular/api-node/schematics/application.md index aad7053f2a..e5beb3332a 100644 --- a/docs/angular/api-node/schematics/application.md +++ b/docs/angular/api-node/schematics/application.md @@ -48,6 +48,14 @@ Type: `string` Frontend project that needs to access this application. This sets up proxy configuration. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -64,6 +72,16 @@ Type: `string` The name of the application. +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/angular/api-node/schematics/library.md b/docs/angular/api-node/schematics/library.md index f201328e96..40b589c3f7 100644 --- a/docs/angular/api-node/schematics/library.md +++ b/docs/angular/api-node/schematics/library.md @@ -66,6 +66,14 @@ Type: `string` The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -82,6 +90,16 @@ Type: `string` Library name +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### publishable Type: `boolean` diff --git a/docs/angular/api-workspace/schematics/library.md b/docs/angular/api-workspace/schematics/library.md index 5127694fd3..08cbd36929 100644 --- a/docs/angular/api-workspace/schematics/library.md +++ b/docs/angular/api-workspace/schematics/library.md @@ -80,6 +80,16 @@ Type: `string` Library name +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/node/api-express/schematics/application.md b/docs/node/api-express/schematics/application.md index 64a6d9ead6..c801960e6a 100644 --- a/docs/node/api-express/schematics/application.md +++ b/docs/node/api-express/schematics/application.md @@ -48,6 +48,14 @@ Type: `string` Frontend project that needs to access this application. This sets up proxy configuration. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -64,6 +72,16 @@ Type: `string` The name of the application. +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/node/api-node/schematics/application.md b/docs/node/api-node/schematics/application.md index ea6975ad0c..5ed7f18cd4 100644 --- a/docs/node/api-node/schematics/application.md +++ b/docs/node/api-node/schematics/application.md @@ -48,6 +48,14 @@ Type: `string` Frontend project that needs to access this application. This sets up proxy configuration. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -64,6 +72,16 @@ Type: `string` The name of the application. +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/node/api-node/schematics/library.md b/docs/node/api-node/schematics/library.md index 479e101f5f..1320501c10 100644 --- a/docs/node/api-node/schematics/library.md +++ b/docs/node/api-node/schematics/library.md @@ -66,6 +66,14 @@ Type: `string` The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -82,6 +90,16 @@ Type: `string` Library name +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### publishable Type: `boolean` diff --git a/docs/node/api-workspace/schematics/library.md b/docs/node/api-workspace/schematics/library.md index 645f5ce1e1..681c06659f 100644 --- a/docs/node/api-workspace/schematics/library.md +++ b/docs/node/api-workspace/schematics/library.md @@ -80,6 +80,16 @@ Type: `string` Library name +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/react/api-express/schematics/application.md b/docs/react/api-express/schematics/application.md index 64a6d9ead6..c801960e6a 100644 --- a/docs/react/api-express/schematics/application.md +++ b/docs/react/api-express/schematics/application.md @@ -48,6 +48,14 @@ Type: `string` Frontend project that needs to access this application. This sets up proxy configuration. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -64,6 +72,16 @@ Type: `string` The name of the application. +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/react/api-node/schematics/application.md b/docs/react/api-node/schematics/application.md index ea6975ad0c..5ed7f18cd4 100644 --- a/docs/react/api-node/schematics/application.md +++ b/docs/react/api-node/schematics/application.md @@ -48,6 +48,14 @@ Type: `string` Frontend project that needs to access this application. This sets up proxy configuration. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -64,6 +72,16 @@ Type: `string` The name of the application. +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/docs/react/api-node/schematics/library.md b/docs/react/api-node/schematics/library.md index 479e101f5f..1320501c10 100644 --- a/docs/react/api-node/schematics/library.md +++ b/docs/react/api-node/schematics/library.md @@ -66,6 +66,14 @@ Type: `string` The library name used to import it, like @myorg/my-awesome-lib. Must be a valid npm name. +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + ### linter Default: `eslint` @@ -82,6 +90,16 @@ Type: `string` Library name +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### publishable Type: `boolean` diff --git a/docs/react/api-workspace/schematics/library.md b/docs/react/api-workspace/schematics/library.md index 645f5ce1e1..681c06659f 100644 --- a/docs/react/api-workspace/schematics/library.md +++ b/docs/react/api-workspace/schematics/library.md @@ -80,6 +80,16 @@ Type: `string` Library name +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case file names. + ### skipFormat Default: `false` diff --git a/e2e/workspace/src/custom-layout.test.ts b/e2e/workspace/src/custom-layout.test.ts index 17abcdf830..dcdbdef3be 100644 --- a/e2e/workspace/src/custom-layout.test.ts +++ b/e2e/workspace/src/custom-layout.test.ts @@ -60,9 +60,7 @@ forEachCli('nx', () => { expect(workspaceJson).not.toContain('libs/'); const libTestResults = await runCLIAsync(`test ${expressLib}`); - expect(libTestResults.stdout).toContain( - 'No tests found, exiting with code 0' - ); + expect(libTestResults.stdout).toContain(`nx run ${expressLib}:test`); const appBuildResults = await runCLIAsync(`build ${expressApp}`); expect(appBuildResults.stdout).toContain(`nx run ${expressApp}:build`); diff --git a/packages/express/src/schematics/application/application.spec.ts b/packages/express/src/schematics/application/application.spec.ts index 240e752922..4610680d45 100644 --- a/packages/express/src/schematics/application/application.spec.ts +++ b/packages/express/src/schematics/application/application.spec.ts @@ -3,6 +3,8 @@ import { createEmptyWorkspace } from '@nrwl/workspace/testing'; import { runSchematic } from '../../utils/testing'; import { readJsonInTree } from '@nrwl/workspace'; +import { Schema } from './schema.d'; + describe('app', () => { let appTree: Tree; @@ -12,26 +14,88 @@ describe('app', () => { }); it('should generate files', async () => { - const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); - expect(tree.readContent('apps/my-node-app/src/main.ts')).toContain( - `import * as express from 'express';` + const tree = await runSchematic( + 'app', + { name: 'myNodeApp' } as Schema, + appTree ); + + const mainFile = tree.readContent('apps/my-node-app/src/main.ts'); + expect(mainFile).toContain(`import * as express from 'express';`); + + const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + expect(tsconfig).toMatchInlineSnapshot(` + Object { + "extends": "../../tsconfig.base.json", + "files": Array [], + "include": Array [], + "references": Array [ + Object { + "path": "./tsconfig.app.json", + }, + Object { + "path": "./tsconfig.spec.json", + }, + ], + } + `); }); it('should add types to the tsconfig.app.json', async () => { - const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); + const tree = await runSchematic( + 'app', + { name: 'myNodeApp' } as Schema, + appTree + ); const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.app.json'); expect(tsconfig.compilerOptions.types).toContain('express'); + expect(tsconfig).toMatchInlineSnapshot(` + Object { + "compilerOptions": Object { + "outDir": "../../dist/out-tsc", + "types": Array [ + "node", + "express", + ], + }, + "exclude": Array [ + "**/*.spec.ts", + ], + "extends": "./tsconfig.json", + "include": Array [ + "**/*.ts", + ], + } + `); }); - it('should update tsconfig', async () => { - const tree = await runSchematic('app', { name: 'myNodeApp' }, appTree); - const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); - expect(tsconfig.references).toContainEqual({ - path: './tsconfig.app.json', - }); - expect(tsconfig.references).toContainEqual({ - path: './tsconfig.spec.json', + describe('--js flag', () => { + it('should generate js files instead of ts files', async () => { + const tree = await runSchematic( + 'app', + { + name: 'myNodeApp', + js: true, + } as Schema, + appTree + ); + + expect(tree.exists('apps/my-node-app/src/main.js')).toBeTruthy(); + expect(tree.readContent('apps/my-node-app/src/main.js')).toContain( + `import * as express from 'express';` + ); + + const tsConfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + expect(tsConfig.compilerOptions).toEqual({ + allowJs: true, + }); + + const tsConfigApp = readJsonInTree( + tree, + 'apps/my-node-app/tsconfig.app.json' + ); + expect(tsConfigApp.include).toEqual(['**/*.ts', '**/*.js']); + expect(tsConfigApp.exclude).toEqual(['**/*.spec.ts', '**/*.spec.js']); }); }); }); diff --git a/packages/express/src/schematics/application/application.ts b/packages/express/src/schematics/application/application.ts index 39a0469f52..a04d2cf41c 100644 --- a/packages/express/src/schematics/application/application.ts +++ b/packages/express/src/schematics/application/application.ts @@ -11,6 +11,7 @@ import { updateJsonInTree } from '@nrwl/workspace'; import { toFileName, formatFiles } from '@nrwl/workspace'; import init from '../init/init'; import { appsDir } from '@nrwl/workspace/src/utils/ast-utils'; +import { maybeJs } from '@nrwl/workspace/src/utils/rules/to-js'; interface NormalizedSchema extends Schema { appProjectRoot: Path; @@ -24,10 +25,10 @@ function addTypes(options: NormalizedSchema): Rule { }); } -function addMainFile(options: NormalizedSchema): Rule { +function addAppFiles(options: NormalizedSchema): Rule { return (host: Tree) => { host.overwrite( - join(options.appProjectRoot, 'src/main.ts'), + maybeJs(options, join(options.appProjectRoot, 'src/main.ts')), `/** * This is not a production server yet! * This is only a minimal backend to get started. @@ -57,7 +58,7 @@ export default function (schema: Schema): Rule { return chain([ init({ ...options, skipFormat: true }), externalSchematic('@nrwl/node', 'application', schema), - addMainFile(options), + addAppFiles(options), addTypes(options), formatFiles(options), ])(host, context); diff --git a/packages/express/src/schematics/application/schema.d.ts b/packages/express/src/schematics/application/schema.d.ts index 5830b78e65..6b17559453 100644 --- a/packages/express/src/schematics/application/schema.d.ts +++ b/packages/express/src/schematics/application/schema.d.ts @@ -11,4 +11,6 @@ export interface Schema { linter: Linter; frontendProject?: string; babelJest?: boolean; + js: boolean; + pascalCaseFiles: boolean; } diff --git a/packages/express/src/schematics/application/schema.json b/packages/express/src/schematics/application/schema.json index 0f0d2a6c99..612908cfe3 100644 --- a/packages/express/src/schematics/application/schema.json +++ b/packages/express/src/schematics/application/schema.json @@ -52,6 +52,17 @@ "type": "boolean", "description": "Use babel instead ts-jest", "default": false + }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case file names.", + "alias": "P", + "default": false + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files.", + "default": false } }, "required": [] diff --git a/packages/node/src/builders/package/utils/update-package-json.ts b/packages/node/src/builders/package/utils/update-package-json.ts index b9d9d9f3f1..dc24ff9f3d 100644 --- a/packages/node/src/builders/package/utils/update-package-json.ts +++ b/packages/node/src/builders/package/utils/update-package-json.ts @@ -8,7 +8,7 @@ export default function updatePackageJson( options: NormalizedBuilderOptions, context: BuilderContext ) { - const mainFile = basename(options.main, '.ts'); + const mainFile = basename(options.main).replace(/\.[tj]s$/, ''); const typingsFile = `${mainFile}.d.ts`; const mainJsFile = `${mainFile}.js`; const packageJson = readJsonFile( diff --git a/packages/node/src/schematics/application/application.spec.ts b/packages/node/src/schematics/application/application.spec.ts index c669acefba..9e1d13010e 100644 --- a/packages/node/src/schematics/application/application.spec.ts +++ b/packages/node/src/schematics/application/application.spec.ts @@ -1,6 +1,5 @@ import { Tree } from '@angular-devkit/schematics'; -import * as stripJsonComments from 'strip-json-comments'; -import { createEmptyWorkspace, getFileContent } from '@nrwl/workspace/testing'; +import { createEmptyWorkspace } from '@nrwl/workspace/testing'; import { runSchematic } from '../../utils/testing'; import { NxJson, readJsonInTree } from '@nrwl/workspace'; // to break the dependency @@ -24,7 +23,7 @@ describe('app', () => { const project = workspaceJson.projects['my-node-app']; expect(project.root).toEqual('apps/my-node-app'); expect(project.architect).toEqual( - jasmine.objectContaining({ + expect.objectContaining({ build: { builder: '@nrwl/node:build', options: { @@ -85,37 +84,31 @@ describe('app', () => { expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy(); expect(tree.exists('apps/my-node-app/src/main.ts')).toBeTruthy(); - expect(tree.readContent('apps/my-node-app/tsconfig.json')) - .toMatchInlineSnapshot(` - "{ - \\"extends\\": \\"../../tsconfig.base.json\\", - \\"files\\": [], - \\"include\\": [], - \\"references\\": [ - { - \\"path\\": \\"./tsconfig.app.json\\" + const tsconfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + expect(tsconfig).toMatchInlineSnapshot(` + Object { + "extends": "../../tsconfig.base.json", + "files": Array [], + "include": Array [], + "references": Array [ + Object { + "path": "./tsconfig.app.json", }, - { - \\"path\\": \\"./tsconfig.spec.json\\" - } - ] + Object { + "path": "./tsconfig.spec.json", + }, + ], } - " `); - const tsconfigApp = JSON.parse( - stripJsonComments( - getFileContent(tree, 'apps/my-node-app/tsconfig.app.json') - ) + const tsconfigApp = readJsonInTree( + tree, + 'apps/my-node-app/tsconfig.app.json' ); expect(tsconfigApp.compilerOptions.outDir).toEqual('../../dist/out-tsc'); expect(tsconfigApp.extends).toEqual('./tsconfig.json'); - const eslintrc = JSON.parse( - stripJsonComments( - getFileContent(tree, 'apps/my-node-app/.eslintrc.json') - ) - ); + const eslintrc = readJsonInTree(tree, 'apps/my-node-app/.eslintrc.json'); expect(eslintrc.extends).toEqual('../../.eslintrc.json'); }); }); @@ -162,8 +155,7 @@ describe('app', () => { it('should generate files', async () => { const hasJsonValue = ({ path, expectedValue, lookupFn }) => { - const content = getFileContent(tree, path); - const config = JSON.parse(stripJsonComments(content)); + const config = readJsonInTree(tree, path); expect(lookupFn(config)).toEqual(expectedValue); }; @@ -243,7 +235,7 @@ describe('app', () => { ); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); - const serve = JSON.parse(tree.readContent('workspace.json')).projects[ + const serve = readJsonInTree(tree, 'workspace.json').projects[ 'my-frontend' ].architect.serve; expect(serve.options.proxyConfig).toEqual( @@ -261,7 +253,7 @@ describe('app', () => { ); expect(tree.exists('apps/my-frontend/proxy.conf.json')).toBeTruthy(); - const serve = JSON.parse(tree.readContent('workspace.json')).projects[ + const serve = readJsonInTree(tree, 'workspace.json').projects[ 'my-frontend' ].architect.serve; expect(serve.options.proxyConfig).toEqual( @@ -313,4 +305,75 @@ describe('app', () => { `); }); }); + describe('--js flag', () => { + it('should generate js files instead of ts files', async () => { + const tree = await runSchematic( + 'app', + { + name: 'myNodeApp', + js: true, + } as Schema, + appTree + ); + + expect(tree.exists(`apps/my-node-app/jest.config.js`)).toBeTruthy(); + expect(tree.exists('apps/my-node-app/src/main.js')).toBeTruthy(); + + const tsConfig = readJsonInTree(tree, 'apps/my-node-app/tsconfig.json'); + expect(tsConfig.compilerOptions).toEqual({ + allowJs: true, + }); + + const tsConfigApp = readJsonInTree( + tree, + 'apps/my-node-app/tsconfig.app.json' + ); + expect(tsConfigApp.include).toEqual(['**/*.ts', '**/*.js']); + expect(tsConfigApp.exclude).toEqual(['**/*.spec.ts', '**/*.spec.js']); + }); + + it('should update workspace.json', async () => { + const tree = await runSchematic( + 'app', + { name: 'myNodeApp', js: true } as Schema, + appTree + ); + const workspaceJson = readJsonInTree(tree, '/workspace.json'); + const project = workspaceJson.projects['my-node-app']; + const buildTarget = project.architect.build; + + expect(buildTarget.options.main).toEqual('apps/my-node-app/src/main.js'); + expect(buildTarget.configurations.production.fileReplacements).toEqual([ + { + replace: 'apps/my-node-app/src/environments/environment.js', + with: 'apps/my-node-app/src/environments/environment.prod.js', + }, + ]); + }); + + it('should generate js files for nested libs as well', async () => { + const tree = await runSchematic( + 'app', + { name: 'myNodeApp', directory: 'myDir', js: true } as Schema, + appTree + ); + expect( + tree.exists(`apps/my-dir/my-node-app/jest.config.js`) + ).toBeTruthy(); + expect(tree.exists('apps/my-dir/my-node-app/src/main.js')).toBeTruthy(); + }); + }); + + describe('--pascalCaseFiles', () => { + it(`should notify that this flag doesn't do anything`, async () => { + const tree = await runSchematic( + 'app', + { name: 'myNodeApp', pascalCaseFiles: true } as Schema, + appTree + ); + + // @TODO how to spy on context ? + // expect(contextLoggerSpy).toHaveBeenCalledWith('NOTE: --pascalCaseFiles is a noop') + }); + }); }); diff --git a/packages/node/src/schematics/application/application.ts b/packages/node/src/schematics/application/application.ts index d7c5018ea4..d75ee3d983 100644 --- a/packages/node/src/schematics/application/application.ts +++ b/packages/node/src/schematics/application/application.ts @@ -25,6 +25,11 @@ import { getProjectConfig } from '@nrwl/workspace'; import { offsetFromRoot } from '@nrwl/workspace'; import init from '../init/init'; import { appsDir } from '@nrwl/workspace/src/utils/ast-utils'; +import { + toJS, + updateTsConfigsToJs, + maybeJs, +} from '@nrwl/workspace/src/utils/rules/to-js'; interface NormalizedSchema extends Schema { appProjectRoot: Path; @@ -48,7 +53,7 @@ function getBuildConfig(project: any, options: NormalizedSchema) { builder: '@nrwl/node:build', options: { outputPath: join(normalize('dist'), options.appProjectRoot), - main: join(project.sourceRoot, 'main.ts'), + main: maybeJs(options, join(project.sourceRoot, 'main.ts')), tsConfig: join(options.appProjectRoot, 'tsconfig.app.json'), assets: [join(project.sourceRoot, 'assets')], }, @@ -59,8 +64,14 @@ function getBuildConfig(project: any, options: NormalizedSchema) { inspect: false, fileReplacements: [ { - replace: join(project.sourceRoot, 'environments/environment.ts'), - with: join(project.sourceRoot, 'environments/environment.prod.ts'), + replace: maybeJs( + options, + join(project.sourceRoot, 'environments/environment.ts') + ), + with: maybeJs( + options, + join(project.sourceRoot, 'environments/environment.prod.ts') + ), }, ], }, @@ -106,17 +117,26 @@ function updateWorkspaceJson(options: NormalizedSchema): Rule { } function addAppFiles(options: NormalizedSchema): Rule { - return mergeWith( - apply(url(`./files/app`), [ - template({ - tmpl: '', - name: options.name, - root: options.appProjectRoot, - offset: offsetFromRoot(options.appProjectRoot), - }), - move(options.appProjectRoot), - ]) - ); + return chain([ + mergeWith( + apply(url(`./files/app`), [ + template({ + tmpl: '', + name: options.name, + root: options.appProjectRoot, + offset: offsetFromRoot(options.appProjectRoot), + }), + move(options.appProjectRoot), + options.js ? toJS() : noop(), + ]) + ), + options.pascalCaseFiles + ? (tree, context) => { + context.logger.warn('NOTE: --pascalCaseFiles is a noop'); + return tree; + } + : noop(), + ]); } function addProxy(options: NormalizedSchema): Rule { @@ -147,6 +167,18 @@ function addProxy(options: NormalizedSchema): Rule { }; } +function addJest(options: NormalizedSchema) { + return options.unitTestRunner === 'jest' + ? externalSchematic('@nrwl/jest', 'jest-project', { + project: options.name, + setupFile: 'none', + skipSerializers: true, + supportTsx: options.js, + babelJest: options.babelJest, + }) + : noop(); +} + export default function (schema: Schema): Rule { return (host: Tree, context: SchematicContext) => { const options = normalizeOptions(host, schema); @@ -157,16 +189,12 @@ export default function (schema: Schema): Rule { }), addLintFiles(options.appProjectRoot, options.linter), addAppFiles(options), + options.js + ? updateTsConfigsToJs({ projectRoot: options.appProjectRoot }) + : noop, updateWorkspaceJson(options), updateNxJson(options), - options.unitTestRunner === 'jest' - ? externalSchematic('@nrwl/jest', 'jest-project', { - project: options.name, - setupFile: 'none', - skipSerializers: true, - babelJest: options.babelJest, - }) - : noop(), + addJest(options), options.frontendProject ? addProxy(options) : noop(), formatFiles(options), ])(host, context); diff --git a/packages/node/src/schematics/application/schema.d.ts b/packages/node/src/schematics/application/schema.d.ts index a3ee57d744..75a21542c2 100644 --- a/packages/node/src/schematics/application/schema.d.ts +++ b/packages/node/src/schematics/application/schema.d.ts @@ -10,4 +10,6 @@ export interface Schema { tags?: string; frontendProject?: string; babelJest?: boolean; + js: boolean; + pascalCaseFiles: boolean; } diff --git a/packages/node/src/schematics/application/schema.json b/packages/node/src/schematics/application/schema.json index adfd84621a..f77fdc014c 100644 --- a/packages/node/src/schematics/application/schema.json +++ b/packages/node/src/schematics/application/schema.json @@ -51,6 +51,17 @@ "type": "boolean", "description": "Use babel instead ts-jest", "default": false + }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case file names.", + "alias": "P", + "default": false + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files.", + "default": false } }, "required": [] diff --git a/packages/node/src/schematics/library/library.spec.ts b/packages/node/src/schematics/library/library.spec.ts index 21bfdb5208..d5434a003e 100644 --- a/packages/node/src/schematics/library/library.spec.ts +++ b/packages/node/src/schematics/library/library.spec.ts @@ -71,15 +71,21 @@ describe('lib', () => { it('should create a local tsconfig.json', async () => { const tree = await runSchematic('lib', { name: 'myLib' }, appTree); const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json'); - expect(tsconfigJson.extends).toEqual('../../tsconfig.base.json'); - expect(tsconfigJson.references).toEqual([ - { - path: './tsconfig.lib.json', - }, - { - path: './tsconfig.spec.json', - }, - ]); + expect(tsconfigJson).toMatchInlineSnapshot(` + Object { + "extends": "../../tsconfig.base.json", + "files": Array [], + "include": Array [], + "references": Array [ + Object { + "path": "./tsconfig.lib.json", + }, + Object { + "path": "./tsconfig.spec.json", + }, + ], + } + `); }); it('should extend the local tsconfig.json with tsconfig.spec.json', async () => { @@ -280,7 +286,21 @@ describe('lib', () => { expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); - expect(workspaceJson.projects['my-lib'].architect.build).toBeDefined(); + expect(workspaceJson.projects['my-lib'].architect.build) + .toMatchInlineSnapshot(` + Object { + "builder": "@nrwl/node:package", + "options": Object { + "assets": Array [ + "libs/my-lib/*.md", + ], + "main": "libs/my-lib/src/index.ts", + "outputPath": "dist/libs/my-lib", + "packageJson": "libs/my-lib/package.json", + "tsConfig": "libs/my-lib/tsconfig.lib.json", + }, + } + `); }); }); @@ -413,4 +433,102 @@ describe('lib', () => { `); }); }); + describe('--js flag', () => { + it('should generate js files instead of ts files', async () => { + const tree = await runSchematic( + 'lib', + { + name: 'myLib', + js: true, + } as Schema, + appTree + ); + + expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.js')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.js')).toBeTruthy(); + + expect( + readJsonInTree(tree, 'libs/my-lib/tsconfig.json').compilerOptions + ).toEqual({ + allowJs: true, + }); + expect( + readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').include + ).toEqual(['**/*.ts', '**/*.js']); + expect( + readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').exclude + ).toEqual(['**/*.spec.ts', '**/*.spec.js']); + }); + + it('should update root tsconfig.json with a js file path', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', js: true } as Schema, + appTree + ); + const tsconfigJson = readJsonInTree(tree, '/tsconfig.base.json'); + expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([ + 'libs/my-lib/src/index.js', + ]); + }); + + it('should update architect builder when --buildable', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', buildable: true, js: true } as Schema, + appTree + ); + const workspaceJson = readJsonInTree(tree, '/workspace.json'); + + expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); + + expect( + workspaceJson.projects['my-lib'].architect.build.options.main + ).toEqual('libs/my-lib/src/index.js'); + }); + + it('should generate js files for nested libs as well', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', directory: 'myDir', js: true } as Schema, + appTree + ); + expect(tree.exists(`libs/my-dir/my-lib/jest.config.js`)).toBeTruthy(); + expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.js') + ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.js') + ).toBeTruthy(); + }); + }); + + describe('--pascalCaseFiles', () => { + it('should generate files with upper case names', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', pascalCaseFiles: true } as Schema, + appTree + ); + expect(tree.exists('libs/my-lib/src/lib/MyLib.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/MyLib.spec.ts')).toBeTruthy(); + }); + + it('should generate files with upper case names for nested libs as well', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', directory: 'myDir', pascalCaseFiles: true } as Schema, + appTree + ); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.ts') + ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.spec.ts') + ).toBeTruthy(); + }); + }); }); diff --git a/packages/node/src/schematics/library/library.ts b/packages/node/src/schematics/library/library.ts index baef95527d..6920f66116 100644 --- a/packages/node/src/schematics/library/library.ts +++ b/packages/node/src/schematics/library/library.ts @@ -25,6 +25,11 @@ import { } from '@nrwl/workspace'; import { Schema } from './schema'; import { libsDir } from '@nrwl/workspace/src/utils/ast-utils'; +import { + toJS, + updateTsConfigsToJs, + maybeJs, +} from '@nrwl/workspace/src/utils/rules/to-js'; export interface NormalizedSchema extends Schema { name: string; @@ -51,6 +56,7 @@ export default function (schema: NormalizedSchema): Rule { importPath: options.importPath, }), createFiles(options), + options.js ? updateTsConfigsToJs(options) : noop(), addProject(options), formatFiles(options), ]); @@ -103,6 +109,7 @@ function createFiles(options: NormalizedSchema): Rule { options.publishable || options.buildable ? noop() : filter((file) => !file.endsWith('package.json')), + options.js ? toJS() : noop(), ]), MergeStrategy.Overwrite ); @@ -122,7 +129,7 @@ function addProject(options: NormalizedSchema): Rule { outputPath: `dist/${libsDir(host)}/${options.projectDirectory}`, tsConfig: `${options.projectRoot}/tsconfig.lib.json`, packageJson: `${options.projectRoot}/package.json`, - main: `${options.projectRoot}/src/index.ts`, + main: maybeJs(options, `${options.projectRoot}/src/index.ts`), assets: [`${options.projectRoot}/*.md`], }, }; diff --git a/packages/node/src/schematics/library/schema.d.ts b/packages/node/src/schematics/library/schema.d.ts index de252fd666..2ab8cdf7f6 100644 --- a/packages/node/src/schematics/library/schema.d.ts +++ b/packages/node/src/schematics/library/schema.d.ts @@ -14,4 +14,6 @@ export interface Schema { testEnvironment: 'jsdom' | 'node'; rootDir?: string; babelJest?: boolean; + js: boolean; + pascalCaseFiles: boolean; } diff --git a/packages/node/src/schematics/library/schema.json b/packages/node/src/schematics/library/schema.json index bfd8e09b8f..72712fca97 100644 --- a/packages/node/src/schematics/library/schema.json +++ b/packages/node/src/schematics/library/schema.json @@ -79,6 +79,17 @@ "type": "boolean", "description": "Use babel instead ts-jest", "default": false + }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case file names.", + "alias": "P", + "default": false + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files.", + "default": false } }, "required": ["name"] diff --git a/packages/workspace/src/schematics/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ b/packages/workspace/src/schematics/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ new file mode 100644 index 0000000000..35b0948b95 --- /dev/null +++ b/packages/workspace/src/schematics/library/files/lib/src/lib/__fileName__.spec.ts__tmpl__ @@ -0,0 +1,7 @@ +import { <%= propertyName %> } from './<%= fileName %>'; + +describe('<%= propertyName %>', () => { + it('should work', () => { + expect(<%= propertyName %>()).toEqual('<%= name %>'); + }) +}) \ No newline at end of file diff --git a/packages/workspace/src/schematics/library/files/lib/src/lib/__fileName__.ts__tmpl__ b/packages/workspace/src/schematics/library/files/lib/src/lib/__fileName__.ts__tmpl__ index e69de29bb2..ae311e3ac4 100644 --- a/packages/workspace/src/schematics/library/files/lib/src/lib/__fileName__.ts__tmpl__ +++ b/packages/workspace/src/schematics/library/files/lib/src/lib/__fileName__.ts__tmpl__ @@ -0,0 +1,3 @@ +export function <%= propertyName %>(): string { + return '<%= name %>'; +} diff --git a/packages/workspace/src/schematics/library/library.spec.ts b/packages/workspace/src/schematics/library/library.spec.ts index 78aebeda77..650eaed3a1 100644 --- a/packages/workspace/src/schematics/library/library.spec.ts +++ b/packages/workspace/src/schematics/library/library.spec.ts @@ -70,14 +70,21 @@ describe('lib', () => { it('should create a local tsconfig.json', async () => { const tree = await runSchematic('lib', { name: 'myLib' }, appTree); const tsconfigJson = readJsonInTree(tree, 'libs/my-lib/tsconfig.json'); - expect(tsconfigJson.references).toEqual([ - { - path: './tsconfig.lib.json', - }, - { - path: './tsconfig.spec.json', - }, - ]); + expect(tsconfigJson).toMatchInlineSnapshot(` + Object { + "extends": "../../tsconfig.base.json", + "files": Array [], + "include": Array [], + "references": Array [ + Object { + "path": "./tsconfig.lib.json", + }, + Object { + "path": "./tsconfig.spec.json", + }, + ], + } + `); }); it('should extend the local tsconfig.json with tsconfig.spec.json', async () => { @@ -126,6 +133,7 @@ describe('lib', () => { `); expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy(); expect(tree.exists('libs/my-lib/src/lib/my-lib.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeTruthy(); expect(tree.exists('libs/my-lib/README.md')).toBeTruthy(); const ReadmeContent = tree.readContent('libs/my-lib/README.md'); @@ -183,6 +191,9 @@ describe('lib', () => { expect( tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.ts') ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.ts') + ).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.ts')).toBeTruthy(); expect(tree.exists(`libs/my-dir/my-lib/.eslintrc.json`)).toBeTruthy(); }); @@ -255,14 +266,19 @@ describe('lib', () => { }); describe('--unit-test-runner none', () => { - it('should not generate test configuration', async () => { + it('should not generate test configuration nor spec file', async () => { const resultTree = await runSchematic( 'lib', { name: 'myLib', unitTestRunner: 'none' }, appTree ); + expect(resultTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy(); expect(resultTree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); + expect( + resultTree.exists('libs/my-lib/src/lib/my-lib.spec.ts') + ).toBeFalsy(); + const workspaceJson = readJsonInTree(resultTree, 'workspace.json'); expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined(); expect(workspaceJson.projects['my-lib'].architect.lint) @@ -334,6 +350,31 @@ describe('lib', () => { expect(tree.exists(`libs/my-lib/jest.config.js`)).toBeTruthy(); expect(tree.exists('libs/my-lib/src/index.js')).toBeTruthy(); expect(tree.exists('libs/my-lib/src/lib/my-lib.js')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.js')).toBeTruthy(); + }); + + it('should update tsconfig.json with compilerOptions.allowJs: true', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', js: true }, + appTree + ); + expect( + readJsonInTree(tree, 'libs/my-lib/tsconfig.json').compilerOptions + ).toEqual({ + allowJs: true, + }); + }); + + it('should update tsconfig.lib.json include with **/*.js glob', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', js: true }, + appTree + ); + expect( + readJsonInTree(tree, 'libs/my-lib/tsconfig.lib.json').include + ).toEqual(['**/*.ts', '**/*.js']); }); it('should update root tsconfig.json with a js file path', async () => { @@ -359,6 +400,9 @@ describe('lib', () => { expect( tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.js') ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/my-dir-my-lib.spec.js') + ).toBeTruthy(); expect(tree.exists('libs/my-dir/my-lib/src/index.js')).toBeTruthy(); }); }); @@ -407,4 +451,29 @@ describe('lib', () => { `); }); }); + describe('--pascalCaseFiles', () => { + it('should generate files with upper case names', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', pascalCaseFiles: true }, + appTree + ); + expect(tree.exists('libs/my-lib/src/lib/MyLib.ts')).toBeTruthy(); + expect(tree.exists('libs/my-lib/src/lib/MyLib.spec.ts')).toBeTruthy(); + }); + + it('should generate files with upper case names for nested libs as well', async () => { + const tree = await runSchematic( + 'lib', + { name: 'myLib', directory: 'myDir', pascalCaseFiles: true }, + appTree + ); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.ts') + ).toBeTruthy(); + expect( + tree.exists('libs/my-dir/my-lib/src/lib/MyDirMyLib.spec.ts') + ).toBeTruthy(); + }); + }); }); diff --git a/packages/workspace/src/schematics/library/library.ts b/packages/workspace/src/schematics/library/library.ts index 05aafc65a1..8e4db67163 100644 --- a/packages/workspace/src/schematics/library/library.ts +++ b/packages/workspace/src/schematics/library/library.ts @@ -11,20 +11,25 @@ import { move, noop, SchematicsException, + filter, } from '@angular-devkit/schematics'; import { join, normalize } from '@angular-devkit/core'; import { Schema } from './schema'; -import { updateWorkspaceInTree, getNpmScope } from '@nrwl/workspace'; -import { updateJsonInTree } from '@nrwl/workspace'; -import { toFileName, names } from '@nrwl/workspace'; -import { formatFiles } from '@nrwl/workspace'; -import { offsetFromRoot } from '@nrwl/workspace'; +import { + updateWorkspaceInTree, + getNpmScope, + toFileName, + names, + updateJsonInTree, + formatFiles, + offsetFromRoot, +} from '@nrwl/workspace'; import { generateProjectLint, addLintFiles } from '../../utils/lint'; import { addProjectToNxJsonInTree, libsDir } from '../../utils/ast-utils'; import { cliCommand } from '../../core/file-utils'; -import { toJS } from '../../utils/rules/to-js'; +import { toJS, updateTsConfigsToJs, maybeJs } from '../../utils/rules/to-js'; export interface NormalizedSchema extends Schema { name: string; @@ -82,17 +87,22 @@ function updateTsConfig(options: NormalizedSchema): Rule { } function createFiles(options: NormalizedSchema): Rule { + const { className, name, propertyName } = names(options.name); + return mergeWith( apply(url(`./files/lib`), [ template({ ...options, - ...names(options.name), + className, + name, + propertyName, cliCommand: cliCommand(), tmpl: '', offsetFromRoot: offsetFromRoot(options.projectRoot), hasUnitTestRunner: options.unitTestRunner !== 'none', }), move(options.projectRoot), + addTestFiles(options), options.js ? toJS() : noop(), ]) ); @@ -102,6 +112,19 @@ function updateNxJson(options: NormalizedSchema): Rule { return addProjectToNxJsonInTree(options.name, { tags: options.parsedTags }); } +function addJest(options: NormalizedSchema) { + return options.unitTestRunner !== 'none' + ? externalSchematic('@nrwl/jest', 'jest-project', { + project: options.name, + setupFile: 'none', + supportTsx: true, + babelJest: options.babelJest, + skipSerializers: true, + testEnvironment: options.testEnvironment, + }) + : noop(); +} + export default function (schema: Schema): Rule { return (host: Tree, context: SchematicContext) => { const options = normalizeOptions(host, schema); @@ -109,19 +132,11 @@ export default function (schema: Schema): Rule { return chain([ addLintFiles(options.projectRoot, options.linter), createFiles(options), + options.js ? updateTsConfigsToJs(options) : noop(), !options.skipTsConfig ? updateTsConfig(options) : noop(), addProject(options), updateNxJson(options), - options.unitTestRunner !== 'none' - ? externalSchematic('@nrwl/jest', 'jest-project', { - project: options.name, - setupFile: 'none', - supportTsx: true, - babelJest: options.babelJest, - skipSerializers: true, - testEnvironment: options.testEnvironment, - }) - : noop(), + addJest(options), formatFiles(options), ])(host, context); }; @@ -134,9 +149,11 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { : name; const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); - const fileName = options.simpleModuleName ? name : projectName; + const fileName = getCaseAwareFileName({ + fileName: options.simpleModuleName ? name : projectName, + pascalCaseFiles: options.pascalCaseFiles, + }); - // const projectRoot = `libs/${projectDirectory}`; const projectRoot = `${libsDir(host)}/${projectDirectory}`; const parsedTags = options.tags @@ -157,8 +174,17 @@ function normalizeOptions(host: Tree, options: Schema): NormalizedSchema { }; } -function maybeJs(options: NormalizedSchema, path: string): string { - return options.js && (path.endsWith('.ts') || path.endsWith('.tsx')) - ? path.replace(/\.tsx?$/, '.js') - : path; +function getCaseAwareFileName(options: { + pascalCaseFiles: boolean; + fileName: string; +}) { + const normalized = names(options.fileName); + + return options.pascalCaseFiles ? normalized.className : normalized.fileName; +} + +function addTestFiles(options: Pick) { + return options.unitTestRunner === 'none' + ? filter((path) => !(path.endsWith('.ts') || path.endsWith('.tsx'))) + : noop(); } diff --git a/packages/workspace/src/schematics/library/schema.d.ts b/packages/workspace/src/schematics/library/schema.d.ts index 9b8a838b24..a323ea975a 100644 --- a/packages/workspace/src/schematics/library/schema.d.ts +++ b/packages/workspace/src/schematics/library/schema.d.ts @@ -11,6 +11,7 @@ export interface Schema { linter: Linter; testEnvironment: 'jsdom' | 'node'; importPath?: string; - js?: boolean; + js: boolean; babelJest?: boolean; + pascalCaseFiles: boolean; } diff --git a/packages/workspace/src/schematics/library/schema.json b/packages/workspace/src/schematics/library/schema.json index e6a558684d..05846c9002 100644 --- a/packages/workspace/src/schematics/library/schema.json +++ b/packages/workspace/src/schematics/library/schema.json @@ -47,8 +47,8 @@ }, "skipTsConfig": { "type": "boolean", - "default": false, - "description": "Do not update tsconfig.json for development experience." + "description": "Do not update tsconfig.json for development experience.", + "default": false }, "testEnvironment": { "type": "string", @@ -65,6 +65,12 @@ "description": "Use babel instead ts-jest", "default": false }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case file names.", + "alias": "P", + "default": false + }, "js": { "type": "boolean", "description": "Generate JavaScript files rather than TypeScript files", diff --git a/packages/workspace/src/utils/rules/to-js.ts b/packages/workspace/src/utils/rules/to-js.ts index 99937c2cbd..05a855fa8b 100644 --- a/packages/workspace/src/utils/rules/to-js.ts +++ b/packages/workspace/src/utils/rules/to-js.ts @@ -1,23 +1,94 @@ import { transpile, JsxEmit, ScriptTarget } from 'typescript'; -import { forEach, Rule, when } from '@angular-devkit/schematics'; +import { + forEach, + Rule, + when, + chain, + Tree, + SchematicsException, +} from '@angular-devkit/schematics'; import { normalize } from '@angular-devkit/core'; +import { updateJsonInTree } from '../ast-utils'; export function toJS(): Rule { - return forEach( - when( - (path) => path.endsWith('.ts') || path.endsWith('.tsx'), - (entry) => { - const original = entry.content.toString('utf-8'); - const result = transpile(original, { - allowJs: true, - jsx: JsxEmit.Preserve, - target: ScriptTarget.ESNext, - }); - return { - content: Buffer.from(result, 'utf-8'), - path: normalize(entry.path.replace(/\.tsx?$/, '.js')), - }; - } - ) - ); + return chain([ + forEach( + when( + (path) => path.endsWith('.ts') || path.endsWith('.tsx'), + (entry) => { + const original = entry.content.toString('utf-8'); + const result = transpile(original, { + allowJs: true, + jsx: JsxEmit.Preserve, + target: ScriptTarget.ESNext, + }); + return { + content: Buffer.from(result, 'utf-8'), + path: normalize(entry.path.replace(/\.tsx?$/, '.js')), + }; + } + ) + ), + ]); +} + +export function updateTsConfigsToJs(options: { projectRoot: string }): Rule { + const paths = { + tsConfig: normalize(`${options.projectRoot}/tsconfig.json`), + tsConfigLib: normalize(`${options.projectRoot}/tsconfig.lib.json`), + tsConfigApp: normalize(`${options.projectRoot}/tsconfig.app.json`), + }; + + const getProjectType = (tree: Tree) => { + if (tree.exists(paths.tsConfigApp)) { + return 'application'; + } + if (tree.exists(paths.tsConfigLib)) { + return 'library'; + } + + throw new SchematicsException( + `project is missing tsconfig.lib.json or tsconfig.app.json` + ); + }; + + const getConfigFileForUpdate = (tree: Tree) => { + const projectType = getProjectType(tree); + if (projectType === 'library') { + return paths.tsConfigLib; + } + if (projectType === 'application') { + return paths.tsConfigApp; + } + }; + + return chain([ + updateJsonInTree(paths.tsConfig, (json) => { + if (json.compilerOptions) { + json.compilerOptions.allowJs = true; + } else { + json.compilerOptions = { allowJs: true }; + } + + return json; + }), + (tree) => { + const updateConfigPath = getConfigFileForUpdate(tree); + + return updateJsonInTree(updateConfigPath, (json) => { + json.include = uniq([...json.include, '**/*.js']); + json.exclude = uniq([...json.exclude, '**/*.spec.js']); + + return json; + }); + }, + ]); +} + +const uniq = (value: T) => [...new Set(value)] as T; + +export function maybeJs(options: { js: boolean }, path: string): string { + return options.js && (path.endsWith('.ts') || path.endsWith('.tsx')) + ? path.replace(/\.tsx?$/, '.js') + : path; }