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..6aa298ea4903dc 100644 --- a/docs/generated/packages/node/generators/application.json +++ b/docs/generated/packages/node/generators/application.json @@ -36,14 +36,26 @@ "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?" + }, + "e2eTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for end-to-end tests", + "default": "none", + "x-priority": "important", + "x-prompt": "Which end-to-end test runner would you like to use?" }, "tags": { "type": "string", @@ -109,12 +121,6 @@ "hidden": true, "x-priority": "internal" }, - "e2eTestRunner": { - "type": "string", - "enum": ["jest", "none"], - "description": "Test runner to use for end to end (e2e) tests", - "default": "jest" - }, "docker": { "type": "boolean", "description": "Add a docker build target" diff --git a/docs/generated/packages/node/generators/library.json b/docs/generated/packages/node/generators/library.json index fada543625113c..f6b9ab4d739a03 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", @@ -69,7 +73,7 @@ }, "buildable": { "type": "boolean", - "default": false, + "default": true, "description": "Generate a buildable library.", "x-priority": "important" }, 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/package.json b/packages/express/package.json index 44108d69c08d5c..1e777406eddcfe 100644 --- a/packages/express/package.json +++ b/packages/express/package.json @@ -32,7 +32,6 @@ }, "dependencies": { "@nx/devkit": "file:../devkit", - "@nx/js": "file:../js", "@nx/node": "file:../node", "tslib": "^2.3.0" }, diff --git a/packages/express/src/generators/application/application.spec.ts b/packages/express/src/generators/application/application.spec.ts index 954fc1ba1d0e3a..67fa145aa223b9 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,179 @@ 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-e2e", + }, + { + "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", + "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": { + "options": { + "passWithNoTests": true, + }, + }, + }, + }, + "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..801c741cf13a20 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'; @@ -66,6 +66,11 @@ describe('application generator', () => { "runBuildTargetDependencies": false, }, }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, }, } `); @@ -162,4 +167,170 @@ 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-e2e", + }, + { + "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, + }, + }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, + }, + }, + "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..7a3049351082e0 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'; @@ -59,6 +61,11 @@ describe('app', () => { "runBuildTargetDependencies": false, }, }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, }, } `); @@ -266,6 +273,11 @@ describe('app', () => { "runBuildTargetDependencies": false, }, }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, }, } `); @@ -548,4 +560,153 @@ 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-e2e", + }, + { + "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, + }, + }, + "test": { + "options": { + "passWithNoTests": true, + }, + }, + }, + }, + "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..2339b53356ba04 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( @@ -83,7 +89,7 @@ function getWebpackBuildConfig( options.appProjectRoot, 'webpack.config.js' ), - generatePackageJson: true, + generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true, }, configurations: { development: {}, @@ -114,7 +120,7 @@ function getEsBuildConfig( ), tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'), assets: [joinPathFragments(project.sourceRoot, 'assets')], - generatePackageJson: true, + generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true, esbuildOptions: { sourcemap: true, // Generate CJS files as .js so imports can be './foo' rather than './foo.cjs'. @@ -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, @@ -501,6 +519,13 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { skipFormat: true, }); tasks.push(jestTask); + // There are no tests by default, so set `--passWithNoTests` to avoid test failure on new project. + const projectConfig = readProjectConfiguration(tree, options.name); + projectConfig.targets ??= {}; + projectConfig.targets.test = { + options: { passWithNoTests: true }, + }; + updateProjectConfiguration(tree, options.name, projectConfig); } else { // No need for default spec file if unit testing is not setup. tree.delete( @@ -544,6 +569,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 +634,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..114ca3d0f09827 100644 --- a/packages/node/src/generators/application/schema.json +++ b/packages/node/src/generators/application/schema.json @@ -36,14 +36,26 @@ "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?" + }, + "e2eTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for end-to-end tests", + "default": "none", + "x-priority": "important", + "x-prompt": "Which end-to-end test runner would you like to use?" }, "tags": { "type": "string", @@ -109,12 +121,6 @@ "hidden": true, "x-priority": "internal" }, - "e2eTestRunner": { - "type": "string", - "enum": ["jest", "none"], - "description": "Test runner to use for end to end (e2e) tests", - "default": "jest" - }, "docker": { "type": "boolean", "description": "Add a docker build target" diff --git a/packages/node/src/generators/e2e-project/e2e-project.spec.ts b/packages/node/src/generators/e2e-project/e2e-project.spec.ts index 2ac6fc5983a169..9a9ac359504945 100644 --- a/packages/node/src/generators/e2e-project/e2e-project.spec.ts +++ b/packages/node/src/generators/e2e-project/e2e-project.spec.ts @@ -1,6 +1,6 @@ import 'nx/src/internal-testing-utils/mock-project-graph'; -import { Tree } from '@nx/devkit'; +import { readJson, Tree, updateJson, writeJson } from '@nx/devkit'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { applicationGenerator } from '../application/application'; import { e2eProjectGenerator } from './e2e-project'; @@ -74,4 +74,92 @@ describe('e2eProjectGenerator', () => { " `); }); + + 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: 'api', + framework: 'none', + e2eTestRunner: 'none', + addPlugin: true, + }); + await e2eProjectGenerator(tree, { + projectType: 'server', + project: 'api', + addPlugin: true, + }); + + expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` + [ + { + "path": "./api", + }, + { + "path": "./api-e2e", + }, + ] + `); + expect(tree.read('api-e2e/jest.config.ts', 'utf-8')) + .toMatchInlineSnapshot(` + "export default { + displayName: 'api-e2e', + preset: '../jest.preset.js', + globalSetup: '/src/support/global-setup.ts', + globalTeardown: '/src/support/global-teardown.ts', + setupFiles: ['/src/support/test-setup.ts'], + testEnvironment: 'node', + transform: { + '^.+\\\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../coverage/api-e2e', + }; + " + `); + expect(readJson(tree, 'api-e2e/tsconfig.json')).toMatchInlineSnapshot(` + { + "compilerOptions": { + "esModuleInterop": true, + "noImplicitAny": false, + "noUnusedLocals": false, + "outDir": "out-tsc/api-e2e", + }, + "extends": "../tsconfig.base.json", + "include": [ + "jest.config.ts", + "src/**/*.ts", + ], + "references": [ + { + "path": "../api", + }, + ], + } + `); + }); + }); }); diff --git a/packages/node/src/generators/e2e-project/e2e-project.ts b/packages/node/src/generators/e2e-project/e2e-project.ts index 44cf8afbc1f205..a69fc31527adeb 100644 --- a/packages/node/src/generators/e2e-project/e2e-project.ts +++ b/packages/node/src/generators/e2e-project/e2e-project.ts @@ -12,6 +12,7 @@ import { runTasksInSerial, Tree, updateJson, + writeJson, } from '@nx/devkit'; import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Linter, lintProjectGenerator } from '@nx/eslint'; @@ -29,6 +30,9 @@ import { } from '@nx/eslint/src/generators/utils/eslint-file'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { findRootJestPreset } from '@nx/jest/src/utils/config/config-file'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { relative } from 'node:path/posix'; export async function e2eProjectGenerator(host: Tree, options: Schema) { return await e2eProjectGeneratorInternal(host, { @@ -44,24 +48,49 @@ export async function e2eProjectGeneratorInternal( const tasks: GeneratorCallback[] = []; const options = await normalizeOptions(host, _options); const appProject = readProjectConfiguration(host, options.project); + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); // TODO(@ndcunningham): This is broken.. the outputs are wrong.. and this isn't using the jest generator - addProjectConfiguration(host, options.e2eProjectName, { - root: options.e2eProjectRoot, - implicitDependencies: [options.project], - projectType: 'application', - targets: { - e2e: { - executor: '@nx/jest:jest', - outputs: ['{workspaceRoot}/coverage/{e2eProjectRoot}'], - options: { - jestConfig: `${options.e2eProjectRoot}/jest.config.ts`, - passWithNoTests: true, + if (isUsingTsSolutionConfig) { + writeJson(host, joinPathFragments(options.e2eProjectRoot, 'package.json'), { + name: getImportPath(host, options.e2eProjectName), + version: '0.0.1', + private: true, + nx: { + name: options.e2eProjectName, + projectType: 'application', + implicitDependencies: [options.project], + targets: { + e2e: { + executor: '@nx/jest:jest', + outputs: ['{workspaceRoot}/coverage/{e2eProjectRoot}'], + options: { + jestConfig: `${options.e2eProjectRoot}/jest.config.ts`, + passWithNoTests: true, + }, + dependsOn: [`${options.project}:build`], + }, }, - dependsOn: [`${options.project}:build`], }, - }, - }); + }); + } else { + addProjectConfiguration(host, options.e2eProjectName, { + root: options.e2eProjectRoot, + implicitDependencies: [options.project], + projectType: 'application', + targets: { + e2e: { + executor: '@nx/jest:jest', + outputs: ['{workspaceRoot}/coverage/{e2eProjectRoot}'], + options: { + jestConfig: `${options.e2eProjectRoot}/jest.config.ts`, + passWithNoTests: true, + }, + dependsOn: [`${options.project}:build`], + }, + }, + }); + } // TODO(@nicholas): Find a better way to get build target // We remove the 'test' target from the e2e project because it is not needed @@ -91,6 +120,9 @@ export async function e2eProjectGeneratorInternal( } const jestPreset = findRootJestPreset(host) ?? 'jest.preset.js'; + const tsConfigFile = isUsingTsSolutionConfig + ? 'tsconfig.json' + : 'tsconfig.spec.json'; if (options.projectType === 'server') { generateFiles( host, @@ -99,6 +131,7 @@ export async function e2eProjectGeneratorInternal( { ...options, ...names(options.rootProject ? 'server' : options.project), + tsConfigFile, offsetFromRoot: offsetFromRoot(options.e2eProjectRoot), jestPreset, tmpl: '', @@ -113,6 +146,7 @@ export async function e2eProjectGeneratorInternal( { ...options, ...names(options.rootProject ? 'server' : options.project), + tsConfigFile, offsetFromRoot: offsetFromRoot(options.e2eProjectRoot), tmpl: '', } @@ -128,6 +162,7 @@ export async function e2eProjectGeneratorInternal( ...options, ...names(options.rootProject ? 'cli' : options.project), mainFile, + tsConfigFile, offsetFromRoot: offsetFromRoot(options.e2eProjectRoot), jestPreset, tmpl: '', @@ -135,6 +170,34 @@ export async function e2eProjectGeneratorInternal( ); } + if (isUsingTsSolutionConfig) { + generateFiles( + host, + path.join(__dirname, 'files/ts-solution'), + options.e2eProjectRoot, + { + ...options, + relativeProjectReferencePath: relative( + options.e2eProjectRoot, + appProject.root + ), + offsetFromRoot: offsetFromRoot(options.e2eProjectRoot), + tmpl: '', + } + ); + } else { + generateFiles( + host, + path.join(__dirname, 'files/non-ts-solution'), + options.e2eProjectRoot, + { + ...options, + offsetFromRoot: offsetFromRoot(options.e2eProjectRoot), + tmpl: '', + } + ); + } + // axios is more than likely used in the application code, so install it as a regular dependency. const installTask = addDependenciesToPackageJson( host, @@ -171,6 +234,17 @@ export async function e2eProjectGeneratorInternal( await formatFiles(host); } + if (isUsingTsSolutionConfig) { + updateJson(host, 'tsconfig.json', (json) => { + json.references ??= []; + const e2eRef = `./${options.e2eProjectRoot}`; + if (!json.references.find((ref) => ref.path === e2eRef)) { + json.references.push({ path: e2eRef }); + } + return json; + }); + } + tasks.push(() => { logShowProjectCommand(options.e2eProjectName); }); diff --git a/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ b/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ index 3b0e4fbff3681b..fe96a5081ddf33 100644 --- a/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ +++ b/packages/node/src/generators/e2e-project/files/cli/jest.config.ts__tmpl__ @@ -5,7 +5,7 @@ export default { testEnvironment: 'node', transform: { '^.+\\.[tj]s$': ['ts-jest', { - tsconfig: '/tsconfig.spec.json', + tsconfig: '/<%= tsConfigFile %>', }], }, moduleFileExtensions: ['ts', 'js', 'html'], diff --git a/packages/node/src/generators/e2e-project/files/cli/tsconfig.spec.json__tmpl__ b/packages/node/src/generators/e2e-project/files/cli/tsconfig.spec.json__tmpl__ deleted file mode 100644 index 2c05fb02dcbab9..00000000000000 --- a/packages/node/src/generators/e2e-project/files/cli/tsconfig.spec.json__tmpl__ +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "<%= offsetFromRoot %>/dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"] - }, - "include": [ - "jest.config.ts", - "src/**/*.ts" - ] -} diff --git a/packages/node/src/generators/e2e-project/files/cli/tsconfig.json__tmpl__ b/packages/node/src/generators/e2e-project/files/non-ts-solution/tsconfig.json__tmpl__ similarity index 100% rename from packages/node/src/generators/e2e-project/files/cli/tsconfig.json__tmpl__ rename to packages/node/src/generators/e2e-project/files/non-ts-solution/tsconfig.json__tmpl__ diff --git a/packages/node/src/generators/e2e-project/files/server/common/tsconfig.spec.json__tmpl__ b/packages/node/src/generators/e2e-project/files/non-ts-solution/tsconfig.spec.json__tmpl__ similarity index 100% rename from packages/node/src/generators/e2e-project/files/server/common/tsconfig.spec.json__tmpl__ rename to packages/node/src/generators/e2e-project/files/non-ts-solution/tsconfig.spec.json__tmpl__ diff --git a/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ b/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ index 73b4a8b6ca7ad1..329afa69d90f29 100644 --- a/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ +++ b/packages/node/src/generators/e2e-project/files/server/common/jest.config.ts__tmpl__ @@ -7,7 +7,7 @@ export default { testEnvironment: 'node', transform: { '^.+\\.[tj]s$': ['ts-jest', { - tsconfig: '/tsconfig.spec.json', + tsconfig: '/<%= tsConfigFile %>', }], }, moduleFileExtensions: ['ts', 'js', 'html'], diff --git a/packages/node/src/generators/e2e-project/files/server/common/tsconfig.json__tmpl__ b/packages/node/src/generators/e2e-project/files/server/common/tsconfig.json__tmpl__ deleted file mode 100644 index a038446e2badfc..00000000000000 --- a/packages/node/src/generators/e2e-project/files/server/common/tsconfig.json__tmpl__ +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "<%= offsetFromRoot %><% if (rootProject) { %>tsconfig.json<% } else { %>tsconfig.base.json<% } %>", - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.spec.json" - } - ], - "compilerOptions": { - "esModuleInterop": true - } -} diff --git a/packages/node/src/generators/e2e-project/files/ts-solution/tsconfig.json__tmpl__ b/packages/node/src/generators/e2e-project/files/ts-solution/tsconfig.json__tmpl__ new file mode 100644 index 00000000000000..e115f52e896de1 --- /dev/null +++ b/packages/node/src/generators/e2e-project/files/ts-solution/tsconfig.json__tmpl__ @@ -0,0 +1,16 @@ +{ + "extends": "<%= offsetFromRoot %><% if (rootProject) { %>tsconfig.json<% } else { %>tsconfig.base.json<% } %>", + "compilerOptions": { + "outDir": "out-tsc/<%= e2eProjectName %>", + "esModuleInterop": true, + "noUnusedLocals": false, + "noImplicitAny": false + }, + "include": [ + "jest.config.ts", + "src/**/*.ts" + ], + "references": [ + { "path": "<%= relativeProjectReferencePath %>" } + ] +} 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/files/lib/tsconfig.lib.json b/packages/node/src/generators/library/files/non-ts-solution/tsconfig.lib.json similarity index 100% rename from packages/node/src/generators/library/files/lib/tsconfig.lib.json rename to packages/node/src/generators/library/files/non-ts-solution/tsconfig.lib.json diff --git a/packages/node/src/generators/library/files/ts-solution/tsconfig.lib.json b/packages/node/src/generators/library/files/ts-solution/tsconfig.lib.json new file mode 100644 index 00000000000000..ce0ad3f82463d6 --- /dev/null +++ b/packages/node/src/generators/library/files/ts-solution/tsconfig.lib.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "module": "nodenext", + "moduleResolution": "nodenext",, + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "types": ["node"] + }, + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts"] +} diff --git a/packages/node/src/generators/library/library.spec.ts b/packages/node/src/generators/library/library.spec.ts index ae8b2c5b43a5e6..9bbbb1b346511b 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,119 @@ 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": { + "baseUrl": ".", + "emitDeclarationOnly": false, + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "dist", + "rootDir": "src", + "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/node/src/generators/library/library.ts b/packages/node/src/generators/library/library.ts index d5eef8892372c6..9eafb677ed9bea 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); } @@ -167,20 +191,24 @@ function updateProject(tree: Tree, options: NormalizedSchema) { project.targets = project.targets || {}; addBuildTargetDefaults(tree, `@nx/js:${options.compiler}`); - project.targets.build = { - executor: `@nx/js:${options.compiler}`, - outputs: ['{options.outputPath}'], - options: { - outputPath: joinPathFragments( - 'dist', - rootProject ? options.projectName : options.projectRoot - ), - tsConfig: `${options.projectRoot}/tsconfig.lib.json`, - packageJson: `${options.projectRoot}/package.json`, - main: `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'), - assets: [`${options.projectRoot}/*.md`], - }, - }; + + // For TS solution, we want tsc build to be inferred by `@nx/js/typescript` plugin. + if (!options.isUsingTsSolutionConfig || options.compiler === 'swc') { + project.targets.build = { + executor: `@nx/js:${options.compiler}`, + outputs: ['{options.outputPath}'], + options: { + outputPath: joinPathFragments( + 'dist', + rootProject ? options.projectName : options.projectRoot + ), + tsConfig: `${options.projectRoot}/tsconfig.lib.json`, + packageJson: `${options.projectRoot}/package.json`, + main: `${options.projectRoot}/src/index` + (options.js ? '.js' : '.ts'), + assets: [`${options.projectRoot}/*.md`], + }, + }; + } if (options.compiler === 'swc') { addSwcDependencies(tree); diff --git a/packages/node/src/generators/library/schema.json b/packages/node/src/generators/library/schema.json index 51124a0a3f973c..0649384de0bb7f 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", @@ -69,7 +73,7 @@ }, "buildable": { "type": "boolean", - "default": false, + "default": true, "description": "Generate a buildable library.", "x-priority": "important" }, 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}`);