diff --git a/docs/generated/packages/express/generators/application.json b/docs/generated/packages/express/generators/application.json index eacbd36bd225f6..78107a36585def 100644 --- a/docs/generated/packages/express/generators/application.json +++ b/docs/generated/packages/express/generators/application.json @@ -33,14 +33,17 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint"], - "default": "eslint" + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["jest", "none"], "description": "Test runner to use for unit tests.", - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?" }, "tags": { "type": "string", diff --git a/docs/generated/packages/nest/generators/application.json b/docs/generated/packages/nest/generators/application.json index c77f46a1aac854..27826e6cfe0374 100644 --- a/docs/generated/packages/nest/generators/application.json +++ b/docs/generated/packages/nest/generators/application.json @@ -37,13 +37,16 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "none"], - "default": "eslint" + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "description": "Test runner to use for unit tests.", "type": "string", "enum": ["jest", "none"], - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?" }, "e2eTestRunner": { "type": "string", diff --git a/docs/generated/packages/nest/generators/library.json b/docs/generated/packages/nest/generators/library.json index f8e967fa0818d9..9a98b70016afdd 100644 --- a/docs/generated/packages/nest/generators/library.json +++ b/docs/generated/packages/nest/generators/library.json @@ -31,13 +31,17 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "none"], - "default": "eslint" + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "description": "Test runner to use for unit tests.", "type": "string", "enum": ["jest", "none"], - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?", + "x-priority": "important" }, "tags": { "description": "Add tags to the library (used for linting).", diff --git a/docs/generated/packages/node/generators/application.json b/docs/generated/packages/node/generators/application.json index 903ead6c595e6d..0c465538781b8d 100644 --- a/docs/generated/packages/node/generators/application.json +++ b/docs/generated/packages/node/generators/application.json @@ -36,14 +36,18 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint"], - "default": "eslint" + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["jest", "none"], "description": "Test runner to use for unit tests.", - "default": "jest" + "default": "none", + "x-priority": "important", + "x-prompt": "Which unit test runner would you like to use?" }, "tags": { "type": "string", diff --git a/docs/generated/packages/node/generators/library.json b/docs/generated/packages/node/generators/library.json index fada543625113c..ddc4e4301a8f14 100644 --- a/docs/generated/packages/node/generators/library.json +++ b/docs/generated/packages/node/generators/library.json @@ -36,14 +36,18 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint"], - "default": "eslint" + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["jest", "none"], "description": "Test runner to use for unit tests.", - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?", + "x-priority": "important" }, "tags": { "type": "string", diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 86fb208d971eb8..4b63a40c697e50 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -988,6 +988,10 @@ async function determineNodeOptions( let appName: string; let framework: 'express' | 'fastify' | 'koa' | 'nest' | 'none'; let docker: boolean; + let linter: undefined | 'none' | 'eslint'; + let formatter: undefined | 'none' | 'prettier'; + + const workspaces = parsedArgs.workspaces ?? false; if (parsedArgs.preset) { preset = parsedArgs.preset; @@ -1010,7 +1014,9 @@ async function determineNodeOptions( } else { framework = await determineNodeFramework(parsedArgs); - const workspaceType = await determineStandaloneOrMonorepo(); + const workspaceType = workspaces + ? 'monorepo' + : await determineStandaloneOrMonorepo(); if (workspaceType === 'standalone') { preset = Preset.NodeStandalone; appName = parsedArgs.name; @@ -1045,11 +1051,22 @@ async function determineNodeOptions( docker = reply.docker === 'Yes'; } + if (workspaces) { + linter = await determineLinterOptions(parsedArgs); + formatter = await determineFormatterOptions(parsedArgs); + } else { + linter = 'eslint'; + formatter = 'prettier'; + } + return { preset, appName, framework, docker, + linter, + formatter, + workspaces, }; } diff --git a/packages/express/src/generators/application/application.spec.ts b/packages/express/src/generators/application/application.spec.ts index 954fc1ba1d0e3a..0e02ca6c3850c7 100644 --- a/packages/express/src/generators/application/application.spec.ts +++ b/packages/express/src/generators/application/application.spec.ts @@ -1,4 +1,4 @@ -import { readJson, Tree } from '@nx/devkit'; +import { readJson, Tree, updateJson, writeJson } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { applicationGenerator } from './application'; import { Schema } from './schema'; @@ -134,4 +134,181 @@ describe('app', () => { ]); }); }); + + describe('TS solution setup', () => { + beforeEach(() => { + appTree = createTreeWithEmptyWorkspace(); + updateJson(appTree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(appTree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(appTree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await applicationGenerator(appTree, { + directory: 'myapp', + } as Schema); + + expect(readJson(appTree, 'tsconfig.json').references) + .toMatchInlineSnapshot(` + [ + { + "path": "./myapp", + }, + ] + `); + expect(readJson(appTree, 'myapp/package.json')).toMatchInlineSnapshot(` + { + "name": "@proj/myapp", + "nx": { + "name": "myapp", + "projectType": "application", + "sourceRoot": "myapp/src", + "tags": [], + "targets": { + "build": { + "configurations": { + "development": {}, + "production": {}, + }, + "defaultConfiguration": "production", + "executor": "@nx/webpack:webpack", + "options": { + "assets": [ + "myapp/src/assets", + ], + "compiler": "tsc", + "generatePackageJson": true, + "main": "myapp/src/main.ts", + "outputPath": "dist/myapp", + "target": "node", + "tsConfig": "myapp/tsconfig.app.json", + "webpackConfig": "myapp/webpack.config.js", + }, + "outputs": [ + "{options.outputPath}", + ], + }, + "lint": { + "executor": "@nx/eslint:lint", + }, + "serve": { + "configurations": { + "development": { + "buildTarget": "myapp:build:development", + }, + "production": { + "buildTarget": "myapp:build:production", + }, + }, + "defaultConfiguration": "development", + "dependsOn": [ + "build", + ], + "executor": "@nx/js:node", + "options": { + "buildTarget": "myapp:build", + "runBuildTargetDependencies": false, + }, + }, + "test": { + "executor": "@nx/jest:jest", + "options": { + "jestConfig": "myapp/jest.config.ts", + }, + "outputs": [ + "{projectRoot}/test-output/jest/coverage", + ], + }, + }, + }, + "private": true, + "version": "0.0.1", + } + `); + expect(readJson(appTree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "esModuleInterop": true, + }, + "extends": "../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(appTree, 'myapp/tsconfig.app.json')) + .toMatchInlineSnapshot(` + { + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "out-tsc/myapp", + "rootDir": "src", + "types": [ + "node", + "express", + ], + }, + "exclude": [ + "dist", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + "src/**/*.ts", + ], + } + `); + expect(readJson(appTree, 'myapp/tsconfig.spec.json')) + .toMatchInlineSnapshot(` + { + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "./out-tsc/jest", + "types": [ + "jest", + "node", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.app.json", + }, + ], + } + `); + }); + }); }); diff --git a/packages/express/src/generators/application/application.ts b/packages/express/src/generators/application/application.ts index a4721b25bf1c13..594a4bfbb4b7e6 100644 --- a/packages/express/src/generators/application/application.ts +++ b/packages/express/src/generators/application/application.ts @@ -11,7 +11,6 @@ import { determineProjectNameAndRootOptions, ensureProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { applicationGenerator as nodeApplicationGenerator } from '@nx/node'; import { tslibVersion } from '@nx/node/src/utils/versions'; import { join } from 'path'; @@ -75,8 +74,6 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { } export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { - assertNotUsingTsSolutionSetup(tree, 'express', 'application'); - const options = await normalizeOptions(tree, schema); const tasks: GeneratorCallback[] = []; diff --git a/packages/express/src/generators/application/schema.json b/packages/express/src/generators/application/schema.json index a8a24d18cab041..0397e89e757456 100644 --- a/packages/express/src/generators/application/schema.json +++ b/packages/express/src/generators/application/schema.json @@ -33,14 +33,17 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint"], - "default": "eslint" + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["jest", "none"], "description": "Test runner to use for unit tests.", - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?" }, "tags": { "type": "string", diff --git a/packages/express/src/generators/init/init.ts b/packages/express/src/generators/init/init.ts index bf6d07deca2dc0..3321e30dc2a0d5 100644 --- a/packages/express/src/generators/init/init.ts +++ b/packages/express/src/generators/init/init.ts @@ -6,7 +6,6 @@ import { runTasksInSerial, Tree, } from '@nx/devkit'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { expressVersion, nxVersion } from '../../utils/versions'; import type { Schema } from './schema'; @@ -29,8 +28,6 @@ function updateDependencies(tree: Tree, schema: Schema) { } export async function initGenerator(tree: Tree, schema: Schema) { - assertNotUsingTsSolutionSetup(tree, 'express', 'init'); - let installTask: GeneratorCallback = () => {}; if (!schema.skipPackageJson) { installTask = updateDependencies(tree, schema); diff --git a/packages/nest/src/generators/application/application.spec.ts b/packages/nest/src/generators/application/application.spec.ts index 3be9fd03f1e180..b83a2cc3949a9c 100644 --- a/packages/nest/src/generators/application/application.spec.ts +++ b/packages/nest/src/generators/application/application.spec.ts @@ -1,4 +1,4 @@ -import type { Tree } from '@nx/devkit'; +import { readJson, updateJson, writeJson, type Tree } from '@nx/devkit'; import * as devkit from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { applicationGenerator } from './application'; @@ -162,4 +162,162 @@ describe('application generator', () => { expect(projectConfigurations.get(`${appDirectory}-e2e`)).toBeUndefined(); }); }); + + describe('TS solution setup', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + unitTestRunner: 'jest', + addPlugin: true, + }); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./myapp", + }, + ] + `); + expect(readJson(tree, 'myapp/package.json')).toMatchInlineSnapshot(` + { + "name": "@proj/myapp", + "nx": { + "name": "myapp", + "projectType": "application", + "sourceRoot": "myapp/src", + "tags": [], + "targets": { + "build": { + "configurations": { + "development": { + "args": [ + "node-env=development", + ], + }, + }, + "executor": "nx:run-commands", + "options": { + "args": [ + "node-env=production", + ], + "command": "webpack-cli build", + }, + }, + "serve": { + "configurations": { + "development": { + "buildTarget": "myapp:build:development", + }, + "production": { + "buildTarget": "myapp:build:production", + }, + }, + "defaultConfiguration": "development", + "dependsOn": [ + "build", + ], + "executor": "@nx/js:node", + "options": { + "buildTarget": "myapp:build", + "runBuildTargetDependencies": false, + }, + }, + }, + }, + "private": true, + "version": "0.0.1", + } + `); + expect(readJson(tree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "esModuleInterop": true, + }, + "extends": "../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(tree, 'myapp/tsconfig.app.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "emitDecoratorMetadata": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "out-tsc/myapp", + "rootDir": "src", + "target": "es2021", + "types": [ + "node", + ], + }, + "exclude": [ + "dist", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + "src/**/*.ts", + ], + } + `); + expect(readJson(tree, 'myapp/tsconfig.spec.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "./out-tsc/jest", + "types": [ + "jest", + "node", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.app.json", + }, + ], + } + `); + }); + }); }); diff --git a/packages/nest/src/generators/application/application.ts b/packages/nest/src/generators/application/application.ts index 015bb798a06884..c7bc9861ca6431 100644 --- a/packages/nest/src/generators/application/application.ts +++ b/packages/nest/src/generators/application/application.ts @@ -1,6 +1,5 @@ import type { GeneratorCallback, Tree } from '@nx/devkit'; import { formatFiles, runTasksInSerial } from '@nx/devkit'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { applicationGenerator as nodeApplicationGenerator } from '@nx/node'; import { initGenerator } from '../init/init'; @@ -27,8 +26,6 @@ export async function applicationGeneratorInternal( tree: Tree, rawOptions: ApplicationGeneratorOptions ): Promise { - assertNotUsingTsSolutionSetup(tree, 'nest', 'application'); - const options = await normalizeOptions(tree, rawOptions); const tasks: GeneratorCallback[] = []; diff --git a/packages/nest/src/generators/application/schema.d.ts b/packages/nest/src/generators/application/schema.d.ts index 05d781e9d612f3..560673120495bc 100644 --- a/packages/nest/src/generators/application/schema.d.ts +++ b/packages/nest/src/generators/application/schema.d.ts @@ -15,6 +15,7 @@ export interface ApplicationGeneratorOptions { rootProject?: boolean; strict?: boolean; addPlugin?: boolean; + useTsSolution?: boolean; } interface NormalizedOptions extends ApplicationGeneratorOptions { diff --git a/packages/nest/src/generators/application/schema.json b/packages/nest/src/generators/application/schema.json index f74ea1d563a719..0eb3ee8764f600 100644 --- a/packages/nest/src/generators/application/schema.json +++ b/packages/nest/src/generators/application/schema.json @@ -37,13 +37,16 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "none"], - "default": "eslint" + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "description": "Test runner to use for unit tests.", "type": "string", "enum": ["jest", "none"], - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?" }, "e2eTestRunner": { "type": "string", diff --git a/packages/nest/src/generators/init/init.ts b/packages/nest/src/generators/init/init.ts index 5224b3330a9030..876319c42687b4 100644 --- a/packages/nest/src/generators/init/init.ts +++ b/packages/nest/src/generators/init/init.ts @@ -1,6 +1,5 @@ import type { GeneratorCallback, Tree } from '@nx/devkit'; import { formatFiles } from '@nx/devkit'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { addDependencies } from './lib'; import type { InitGeneratorOptions } from './schema'; @@ -9,8 +8,6 @@ export async function initGenerator( tree: Tree, options: InitGeneratorOptions ): Promise { - assertNotUsingTsSolutionSetup(tree, 'nest', 'init'); - let installPackagesTask: GeneratorCallback = () => {}; if (!options.skipPackageJson) { installPackagesTask = addDependencies(tree, options); diff --git a/packages/nest/src/generators/library/library.spec.ts b/packages/nest/src/generators/library/library.spec.ts index e71093a8ae464e..235b4cf2f10747 100644 --- a/packages/nest/src/generators/library/library.spec.ts +++ b/packages/nest/src/generators/library/library.spec.ts @@ -1,6 +1,6 @@ import type { Tree } from '@nx/devkit'; import * as devkit from '@nx/devkit'; -import { readJson, readProjectConfiguration } from '@nx/devkit'; +import { readJson, readProjectConfiguration, writeJson } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { libraryGenerator } from './library'; @@ -345,4 +345,114 @@ describe('lib', () => { ).toBeTruthy(); }); }); + + describe('TS solution setup', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + devkit.updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + unitTestRunner: 'jest', + }); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./mylib", + }, + ] + `); + expect(readJson(tree, 'mylib/tsconfig.json')).toMatchInlineSnapshot(` + { + "extends": "../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(tree, 'mylib/tsconfig.lib.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "baseUrl": ".", + "emitDeclarationOnly": false, + "forceConsistentCasingInFileNames": true, + "importHelpers": true, + "module": "nodenext", + "moduleResolution": "nodenext", + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "strictBindCallApply": true, + "strictNullChecks": true, + "target": "es6", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "types": [ + "node", + ], + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + ], + "extends": "../tsconfig.base.json", + "include": [ + "src/**/*.ts", + ], + "references": [], + } + `); + expect(readJson(tree, 'mylib/tsconfig.spec.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "outDir": "./out-tsc/jest", + "types": [ + "jest", + "node", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.lib.json", + }, + ], + } + `); + }); + }); }); diff --git a/packages/nest/src/generators/library/library.ts b/packages/nest/src/generators/library/library.ts index 9c48d74cb06874..4f9731d14a2817 100644 --- a/packages/nest/src/generators/library/library.ts +++ b/packages/nest/src/generators/library/library.ts @@ -1,7 +1,6 @@ import type { GeneratorCallback, Tree } from '@nx/devkit'; import { formatFiles, runTasksInSerial } from '@nx/devkit'; import { libraryGenerator as jsLibraryGenerator } from '@nx/js'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { addExportsToBarrelFile, addProject, @@ -30,8 +29,6 @@ export async function libraryGeneratorInternal( tree: Tree, rawOptions: LibraryGeneratorOptions ): Promise { - assertNotUsingTsSolutionSetup(tree, 'nest', 'library'); - const options = await normalizeOptions(tree, rawOptions); await jsLibraryGenerator(tree, toJsLibraryGeneratorOptions(options)); const initTask = await initGenerator(tree, rawOptions); diff --git a/packages/nest/src/generators/library/schema.json b/packages/nest/src/generators/library/schema.json index 63e70db440af04..5ad79a7373f6f2 100644 --- a/packages/nest/src/generators/library/schema.json +++ b/packages/nest/src/generators/library/schema.json @@ -31,13 +31,17 @@ "description": "The tool to use for running lint checks.", "type": "string", "enum": ["eslint", "none"], - "default": "eslint" + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "description": "Test runner to use for unit tests.", "type": "string", "enum": ["jest", "none"], - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?", + "x-priority": "important" }, "tags": { "description": "Add tags to the library (used for linting).", diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index d4e1a1af545d0b..8c9087167c1c47 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -6,6 +6,8 @@ import { readJson, readProjectConfiguration, Tree, + updateJson, + writeJson, } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; @@ -548,4 +550,145 @@ describe('app', () => { } }); }); + + describe('TS solution setup', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + bundler: 'webpack', + unitTestRunner: 'jest', + addPlugin: true, + }); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./myapp", + }, + ] + `); + expect(readJson(tree, 'myapp/package.json')).toMatchInlineSnapshot(` + { + "name": "@proj/myapp", + "nx": { + "name": "myapp", + "projectType": "application", + "sourceRoot": "myapp/src", + "tags": [], + "targets": { + "serve": { + "configurations": { + "development": { + "buildTarget": "myapp:build:development", + }, + "production": { + "buildTarget": "myapp:build:production", + }, + }, + "defaultConfiguration": "development", + "dependsOn": [ + "build", + ], + "executor": "@nx/js:node", + "options": { + "buildTarget": "myapp:build", + "runBuildTargetDependencies": false, + }, + }, + }, + }, + "private": true, + "version": "0.0.1", + } + `); + expect(readJson(tree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "esModuleInterop": true, + }, + "extends": "../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(tree, 'myapp/tsconfig.app.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "out-tsc/myapp", + "rootDir": "src", + "types": [ + "node", + ], + }, + "exclude": [ + "dist", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + "eslint.config.js", + "eslint.config.cjs", + "eslint.config.mjs", + ], + "extends": "../tsconfig.base.json", + "include": [ + "src/**/*.ts", + ], + } + `); + expect(readJson(tree, 'myapp/tsconfig.spec.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "./out-tsc/jest", + "types": [ + "jest", + "node", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.app.json", + }, + ], + } + `); + }); + }); }); diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index 58b808302c2e4c..a87d8a87bfd586 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -1,3 +1,4 @@ +import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { addDependenciesToPackageJson, addProjectConfiguration, @@ -19,6 +20,7 @@ import { updateJson, updateProjectConfiguration, updateTsConfigsToJs, + writeJson, } from '@nx/devkit'; import { determineProjectNameAndRootOptions, @@ -30,7 +32,6 @@ import { initGenerator as jsInitGenerator, tsConfigBaseOptions, } from '@nx/js'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { esbuildVersion } from '@nx/js/src/utils/versions'; import { Linter, lintProjectGenerator } from '@nx/eslint'; import { join } from 'path'; @@ -54,11 +55,16 @@ import { Schema } from './schema'; import { hasWebpackPlugin } from '../../utils/has-webpack-plugin'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; +import { + isUsingTsSolutionSetup, + updateTsconfigFiles, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; export interface NormalizedSchema extends Schema { appProjectRoot: string; parsedTags: string[]; outputPath: string; + isUsingTsSolutionConfig: boolean; } function getWebpackBuildConfig( @@ -198,16 +204,27 @@ function addProject(tree: Tree, options: NormalizedSchema) { } project.targets.serve = getServeConfig(options); - addProjectConfiguration( - tree, - options.name, - project, - options.standaloneConfig - ); + if (options.isUsingTsSolutionConfig) { + writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { + name: getImportPath(tree, options.name), + version: '0.0.1', + private: true, + nx: { + name: options.name, + ...project, + }, + }); + } else { + addProjectConfiguration( + tree, + options.name, + project, + options.standaloneConfig + ); + } } function addAppFiles(tree: Tree, options: NormalizedSchema) { - const sourceRoot = joinPathFragments(options.appProjectRoot, 'src'); generateFiles( tree, join(__dirname, './files/common'), @@ -410,10 +427,17 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { } export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { - assertNotUsingTsSolutionSetup(tree, 'node', 'application'); + const tasks: GeneratorCallback[] = []; + + const jsInitTask = await jsInitGenerator(tree, { + ...schema, + tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', + skipFormat: true, + addTsPlugin: schema.useTsSolution, + }); + tasks.push(jsInitTask); const options = await normalizeOptions(tree, schema); - const tasks: GeneratorCallback[] = []; if (options.framework === 'nest') { // nx-ignore-next-line @@ -442,12 +466,6 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { ); } - const jsInitTask = await jsInitGenerator(tree, { - ...schema, - tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json', - skipFormat: true, - }); - tasks.push(jsInitTask); const initTask = await initGenerator(tree, { ...schema, skipFormat: true, @@ -544,6 +562,21 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { await formatFiles(tree); } + if (options.isUsingTsSolutionConfig) { + updateTsconfigFiles( + tree, + options.appProjectRoot, + 'tsconfig.app.json', + { + module: 'nodenext', + moduleResolution: 'nodenext', + }, + options.linter === 'eslint' + ? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs'] + : undefined + ); + } + tasks.push(() => { logShowProjectCommand(options.name); }); @@ -594,6 +627,7 @@ async function normalizeOptions( 'dist', options.rootProject ? options.name : appProjectRoot ), + isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), }; } diff --git a/packages/node/src/generators/application/schema.d.ts b/packages/node/src/generators/application/schema.d.ts index e5a15e8a949682..d0bae6f1db0cb0 100644 --- a/packages/node/src/generators/application/schema.d.ts +++ b/packages/node/src/generators/application/schema.d.ts @@ -23,6 +23,7 @@ export interface Schema { docker?: boolean; isNest?: boolean; addPlugin?: boolean; + useTsSolution?: boolean; } export type NodeJsFrameWorks = 'express' | 'koa' | 'fastify' | 'nest' | 'none'; diff --git a/packages/node/src/generators/application/schema.json b/packages/node/src/generators/application/schema.json index ed4a8ae5693468..60dd8531ff0799 100644 --- a/packages/node/src/generators/application/schema.json +++ b/packages/node/src/generators/application/schema.json @@ -36,14 +36,18 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint"], - "default": "eslint" + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["jest", "none"], "description": "Test runner to use for unit tests.", - "default": "jest" + "default": "none", + "x-priority": "important", + "x-prompt": "Which unit test runner would you like to use?" }, "tags": { "type": "string", diff --git a/packages/node/src/generators/init/init.ts b/packages/node/src/generators/init/init.ts index 95b34a31344bcf..a7d1dce3122e0d 100644 --- a/packages/node/src/generators/init/init.ts +++ b/packages/node/src/generators/init/init.ts @@ -6,7 +6,6 @@ import { runTasksInSerial, Tree, } from '@nx/devkit'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { nxVersion } from '../../utils/versions'; import { Schema } from './schema'; @@ -27,8 +26,6 @@ function updateDependencies(tree: Tree, options: Schema) { } export async function initGenerator(tree: Tree, options: Schema) { - assertNotUsingTsSolutionSetup(tree, 'node', 'init'); - let installTask: GeneratorCallback = () => {}; if (!options.skipPackageJson) { installTask = updateDependencies(tree, options); diff --git a/packages/node/src/generators/library/files/lib/package.json__tmpl__ b/packages/node/src/generators/library/files/lib/package.json__tmpl__ deleted file mode 100644 index e3a3ad83c46eb5..00000000000000 --- a/packages/node/src/generators/library/files/lib/package.json__tmpl__ +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "<%= importPath %>", - "version": "0.0.1" -} diff --git a/packages/node/src/generators/library/library.spec.ts b/packages/node/src/generators/library/library.spec.ts index ae8b2c5b43a5e6..30b474b847c72c 100644 --- a/packages/node/src/generators/library/library.spec.ts +++ b/packages/node/src/generators/library/library.spec.ts @@ -5,6 +5,8 @@ import { readJson, readProjectConfiguration, Tree, + updateJson, + writeJson, } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; @@ -517,4 +519,114 @@ describe('lib', () => { expect(tree.exists('my-dir/my-lib/src/lib/my-lib.spec.js')).toBeTruthy(); }); }); + describe('TS solution setup', () => { + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + updateJson(tree, 'package.json', (json) => { + json.workspaces = ['packages/*', 'apps/*']; + return json; + }); + writeJson(tree, 'tsconfig.base.json', { + compilerOptions: { + composite: true, + declaration: true, + }, + }); + writeJson(tree, 'tsconfig.json', { + extends: './tsconfig.base.json', + files: [], + references: [], + }); + }); + + it('should add project references when using TS solution', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + unitTestRunner: 'jest', + addPlugin: true, + } as Schema); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./mylib", + }, + ] + `); + expect(readJson(tree, 'mylib/package.json')).toMatchInlineSnapshot(` + { + "dependencies": {}, + "main": "./src/index.ts", + "name": "@proj/mylib", + "nx": { + "name": "mylib", + "projectType": "library", + "sourceRoot": "mylib/src", + }, + "private": true, + "types": "./src/index.ts", + "version": "0.0.1", + } + `); + expect(readJson(tree, 'mylib/tsconfig.json')).toMatchInlineSnapshot(` + { + "extends": "../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json", + }, + { + "path": "./tsconfig.spec.json", + }, + ], + } + `); + expect(readJson(tree, 'mylib/tsconfig.lib.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "declaration": true, + "module": "commonjs", + "outDir": "../dist/out-tsc", + "types": [ + "node", + ], + }, + "exclude": [ + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.test.ts", + ], + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + ], + } + `); + expect(readJson(tree, 'mylib/tsconfig.spec.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "outDir": "./out-tsc/jest", + "types": [ + "jest", + "node", + ], + }, + "extends": "../tsconfig.base.json", + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts", + ], + "references": [ + { + "path": "./tsconfig.lib.json", + }, + ], + } + `); + }); + }); }); diff --git a/packages/node/src/generators/library/library.ts b/packages/node/src/generators/library/library.ts index d5eef8892372c6..1726cfaf61c543 100644 --- a/packages/node/src/generators/library/library.ts +++ b/packages/node/src/generators/library/library.ts @@ -3,6 +3,7 @@ import { formatFiles, generateFiles, GeneratorCallback, + installPackagesTask, joinPathFragments, names, offsetFromRoot, @@ -13,6 +14,7 @@ import { Tree, updateProjectConfiguration, updateTsConfigsToJs, + writeJson, } from '@nx/devkit'; import { determineProjectNameAndRootOptions, @@ -21,12 +23,13 @@ import { import { libraryGenerator as jsLibraryGenerator } from '@nx/js'; import { addSwcConfig } from '@nx/js/src/utils/swc/add-swc-config'; import { addSwcDependencies } from '@nx/js/src/utils/swc/add-swc-dependencies'; -import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { join } from 'path'; import { tslibVersion, typesNodeVersion } from '../../utils/versions'; import { initGenerator } from '../init/init'; import { Schema } from './schema'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; export interface NormalizedSchema extends Schema { fileName: string; @@ -34,6 +37,7 @@ export interface NormalizedSchema extends Schema { projectRoot: string; parsedTags: string[]; compiler: 'swc' | 'tsc'; + isUsingTsSolutionConfig: boolean; } export async function libraryGenerator(tree: Tree, schema: Schema) { @@ -44,15 +48,8 @@ export async function libraryGenerator(tree: Tree, schema: Schema) { } export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { - assertNotUsingTsSolutionSetup(tree, 'node', 'library'); - const options = await normalizeOptions(tree, schema); - const tasks: GeneratorCallback[] = [ - await initGenerator(tree, { - ...options, - skipFormat: true, - }), - ]; + const tasks: GeneratorCallback[] = []; if (options.publishable === true && !schema.importPath) { throw new Error( @@ -60,16 +57,40 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { ); } - const libraryInstall = await jsLibraryGenerator(tree, { - ...options, - bundler: schema.buildable || schema.publishable ? 'tsc' : 'none', - includeBabelRc: schema.babelJest, - importPath: options.importPath, - testEnvironment: 'node', - skipFormat: true, - setParserOptionsProject: options.setParserOptionsProject, - }); - tasks.push(libraryInstall); + // Create `package.json` first because @nx/js:lib generator will update it. + if ( + options.isUsingTsSolutionConfig || + options.publishable || + options.buildable + ) { + writeJson(tree, joinPathFragments(options.projectRoot, 'package.json'), { + name: getImportPath(tree, options.name), + version: '0.0.1', + private: true, + files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, + }); + } + + tasks.push( + await jsLibraryGenerator(tree, { + ...schema, + bundler: schema.buildable || schema.publishable ? 'tsc' : 'none', + includeBabelRc: schema.babelJest, + importPath: schema.importPath, + testEnvironment: 'node', + skipFormat: true, + setParserOptionsProject: schema.setParserOptionsProject, + useProjectJson: !options.isUsingTsSolutionConfig, + }) + ); + + tasks.push( + await initGenerator(tree, { + ...options, + skipFormat: true, + }) + ); + createFiles(tree, options); if (options.js) { @@ -79,6 +100,11 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { tasks.push(ensureDependencies(tree)); + // Always run install to link packages. + if (options.isUsingTsSolutionConfig) { + tasks.push(() => installPackagesTask(tree, true)); + } + if (!schema.skipFormat) { await formatFiles(tree); } @@ -129,6 +155,7 @@ async function normalizeOptions( projectRoot, parsedTags, importPath, + isUsingTsSolutionConfig: isUsingTsSolutionSetup(tree), }; } @@ -149,9 +176,6 @@ function createFiles(tree: Tree, options: NormalizedSchema) { join(options.projectRoot, `./src/lib/${options.fileName}.spec.ts`) ); } - if (!options.publishable && !options.buildable) { - tree.delete(join(options.projectRoot, 'package.json')); - } if (options.js) { toJS(tree); } diff --git a/packages/node/src/generators/library/schema.json b/packages/node/src/generators/library/schema.json index 51124a0a3f973c..876c55165d2684 100644 --- a/packages/node/src/generators/library/schema.json +++ b/packages/node/src/generators/library/schema.json @@ -36,14 +36,18 @@ "linter": { "description": "The tool to use for running lint checks.", "type": "string", - "enum": ["eslint"], - "default": "eslint" + "enum": ["eslint", "none"], + "default": "none", + "x-prompt": "Which linter would you like to use?", + "x-priority": "important" }, "unitTestRunner": { "type": "string", "enum": ["jest", "none"], "description": "Test runner to use for unit tests.", - "default": "jest" + "default": "none", + "x-prompt": "Which unit test runner would you like to use?", + "x-priority": "important" }, "tags": { "type": "string", diff --git a/packages/workspace/src/generators/new/generate-workspace-files.ts b/packages/workspace/src/generators/new/generate-workspace-files.ts index 9698efba5da38b..fdca213300a2b6 100644 --- a/packages/workspace/src/generators/new/generate-workspace-files.ts +++ b/packages/workspace/src/generators/new/generate-workspace-files.ts @@ -423,7 +423,9 @@ function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) { options.preset === Preset.ReactNative || options.preset === Preset.RemixMonorepo || options.preset === Preset.VueMonorepo || - options.preset === Preset.Nuxt) && + options.preset === Preset.Nuxt || + options.preset === Preset.NodeMonorepo || + options.preset === Preset.Express) && options.workspaces) ) { const workspaces = options.workspaceGlobs ?? ['packages/**']; diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index d9d8a278f7490f..f60042302f0bd9 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -236,6 +236,8 @@ async function createPreset(tree: Tree, options: Schema) { linter: options.linter, e2eTestRunner: options.e2eTestRunner ?? 'jest', addPlugin, + useTsSolution: options.workspaces, + formatter: options.formatter, }); } else if (options.preset === Preset.Express) { const { @@ -247,6 +249,8 @@ async function createPreset(tree: Tree, options: Schema) { linter: options.linter, e2eTestRunner: options.e2eTestRunner ?? 'jest', addPlugin, + useTsSolution: options.workspaces, + formatter: options.formatter, }); } else if (options.preset === Preset.ReactNative) { const { reactNativeApplicationGenerator } = require('@nx' + @@ -324,6 +328,8 @@ async function createPreset(tree: Tree, options: Schema) { rootProject: false, e2eTestRunner: options.e2eTestRunner ?? 'jest', addPlugin, + useTsSolution: options.workspaces, + formatter: options.formatter, }); } else { throw new Error(`Invalid preset ${options.preset}`);