feat(js): update vue/node app and lib generators to support TS solutions (#29299)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #

---------

Co-authored-by: Leosvel Pérez Espinosa <leosvel.perez.espinosa@gmail.com>
This commit is contained in:
Jack Hsu 2024-12-12 15:43:14 -05:00 committed by GitHub
parent b6d41b617e
commit a8de7df0e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
84 changed files with 2512 additions and 325 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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).",

View File

@ -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"

View File

@ -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"
},

View File

@ -22,24 +22,27 @@
"pattern": "^[a-zA-Z][^:]*$",
"x-priority": "important"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint"],
"default": "eslint"
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "none",
"x-prompt": "Which linter would you like to use?",
"x-priority": "important"
},
"unitTestRunner": {
"type": "string",
"enum": ["vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "none"
"default": "none",
"x-priority": "important"
},
"e2eTestRunner": {
"type": "string",

View File

@ -54,12 +54,6 @@
]
}
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "eslint"
},
"routing": {
"type": "boolean",
"description": "Generate application with routes.",
@ -72,12 +66,21 @@
"default": false,
"x-priority": "internal"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "none",
"x-prompt": "Which linter would you like to use?",
"x-priority": "important"
},
"unitTestRunner": {
"type": "string",
"enum": ["vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "vitest"
"default": "none",
"x-priority": "important"
},
"inSourceTests": {
"type": "boolean",

View File

@ -36,13 +36,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": {
"type": "string",
"enum": ["vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "What unit test runner should be used?"
"x-prompt": "What unit test runner should be used?",
"default": "none",
"x-priority": "important"
},
"inSourceTests": {
"type": "boolean",

View File

@ -772,7 +772,7 @@ describe('Linter', () => {
const mylib = uniq('mylib');
runCLI(
`generate @nx/node:app --name=${myapp} --linter eslint --directory="." --no-interactive`
`generate @nx/node:app --name=${myapp} --linter=eslint --directory="." --e2eTestRunner=jest --no-interactive`
);
runCLI('reset', { env: { CI: 'false' } });
verifySuccessfulStandaloneSetup(myapp);

View File

@ -141,7 +141,7 @@ describe('Jest', () => {
it('should be able to test node lib with babel-jest', async () => {
const libName = uniq('babel-test-lib');
runCLI(
`generate @nx/node:lib libs/${libName} --buildable --importPath=@some-org/babel-test --publishable --babelJest`
`generate @nx/node:lib libs/${libName} --buildable --importPath=@some-org/babel-test --publishable --babelJest --unitTestRunner=jest`
);
const cliResults = await runCLIAsync(`test ${libName}`);

View File

@ -24,7 +24,7 @@ describe('Node Applications + esbuild', () => {
const app = uniq('nodeapp');
runCLI(
`generate @nx/node:app apps/${app} --bundler=esbuild --no-interactive`
`generate @nx/node:app apps/${app} --bundler=esbuild --no-interactive --linter=eslint --unitTestRunner=jest`
);
checkFilesDoNotExist(`apps/${app}/webpack.config.js`);

View File

@ -80,19 +80,23 @@ describe('Node Applications + webpack', () => {
const nestApp = uniq('nest');
beforeAll(() => {
runCLI(`generate @nx/node:lib libs/${testLib1}`);
runCLI(`generate @nx/node:lib libs/${testLib2} --importPath=@acme/test2`);
runCLI(
`generate @nx/node:app apps/${expressApp} --framework=express --port=7000 --no-interactive`
`generate @nx/node:lib libs/${testLib1} --linter=eslint --unitTestRunner=jest --buildable=false`
);
runCLI(
`generate @nx/node:app apps/${fastifyApp} --framework=fastify --port=7001 --no-interactive`
`generate @nx/node:lib libs/${testLib2} --importPath=@acme/test2 --linter=eslint --unitTestRunner=jest --buildable=false`
);
runCLI(
`generate @nx/node:app apps/${koaApp} --framework=koa --port=7002 --no-interactive`
`generate @nx/node:app apps/${expressApp} --framework=express --port=7000 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${nestApp} --framework=nest --port=7003 --bundler=webpack --no-interactive`
`generate @nx/node:app apps/${fastifyApp} --framework=fastify --port=7001 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${koaApp} --framework=koa --port=7002 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${nestApp} --framework=nest --port=7003 --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
);
addLibImport(expressApp, testLib1);
@ -165,7 +169,7 @@ describe('Node Applications + webpack', () => {
const expressApp = 'docker-express-app'; // needs to be consistent for the Dockerfile snapshot
runCLI(
`generate @nx/node:app apps/${expressApp} --framework=express --docker --no-interactive`
`generate @nx/node:app apps/${expressApp} --framework=express --docker --no-interactive --linter=eslint --unitTestRunner=jest`
);
checkFilesExist(`apps/${expressApp}/Dockerfile`);
@ -179,10 +183,10 @@ describe('Node Applications + webpack', () => {
// Set ports to avoid conflicts with other tests that might run in parallel
runCLI(
`generate @nx/node:app apps/${nodeApp1} --framework=none --no-interactive --port=4444`
`generate @nx/node:app apps/${nodeApp1} --framework=none --no-interactive --port=4444 --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/node:app apps/${nodeApp2} --framework=none --no-interactive --port=4445`
`generate @nx/node:app apps/${nodeApp2} --framework=none --no-interactive --port=4445 --linter=eslint --unitTestRunner=jest`
);
updateJson(join('apps', nodeApp1, 'project.json'), (config) => {
config.targets.serve.options.waitUntilTargets = [`${nodeApp2}:build`];

View File

@ -0,0 +1,170 @@
import {
checkFilesExist,
cleanupProject,
getPackageManagerCommand,
getSelectedPackageManager,
killPorts,
newProject,
promisifiedTreeKill,
runCLI,
runCommand,
runCommandUntil,
tmpProjPath,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
import { execSync } from 'child_process';
import * as http from 'http';
let originalEnvPort;
describe('Node Applications', () => {
const pm = getSelectedPackageManager();
beforeAll(() => {
originalEnvPort = process.env.PORT;
newProject({
packages: ['@nx/node', '@nx/express', '@nx/nest', '@nx/webpack'],
preset: 'ts',
});
if (pm === 'pnpm') {
updateFile(
'pnpm-workspace.yaml',
`
packages:
- 'apps/**'
- 'packages/**'
`
);
} else {
updateJson('package.json', (json) => {
json.workspaces = ['apps/**', 'packages/**'];
return json;
});
}
});
afterAll(() => {
process.env.PORT = originalEnvPort;
cleanupProject();
});
it('should be able to generate an empty application', async () => {
const nodeapp = uniq('nodeapp');
const port = getRandomPort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${nodeapp} --port=${port} --linter=eslint --unitTestRunner=jest`
);
expect(() => runCLI(`lint ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`test ${nodeapp}`)).not.toThrow();
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
runCLI(`build ${nodeapp}`);
checkFilesExist(`dist/apps/${nodeapp}/main.js`);
const result = execSync(`node dist/apps/${nodeapp}/main.js`, {
cwd: tmpProjPath(),
}).toString();
expect(result).toContain('Hello World!');
await killPorts(port);
}, 300_000);
it('should be able to generate an express application', async () => {
const nodeapp = uniq('nodeapp');
const nodelib = uniq('nodelib');
const port = getRandomPort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/express:app apps/${nodeapp} --port=${port} --linter=eslint --unitTestRunner=jest`
);
runCLI(
`generate @nx/node:lib packages/${nodelib} --linter=eslint --unitTestRunner=jest`
);
// No tests are generated by default, add a stub one.
updateFile(
`apps/${nodeapp}/src/app/test.spec.ts`,
`
describe('test', () => {
it('should work', () => {
expect(true).toEqual(true);
})
})
`
);
updateFile(`apps/${nodeapp}/src/assets/file.txt`, `Test`);
updateFile(`apps/${nodeapp}/src/main.ts`, (content) => {
return `import { ${nodelib} } from '@proj/${nodelib}';\n${content}\nconsole.log(${nodelib}());`;
});
// pnpm does not link packages unless they are deps
// npm, yarn, and bun will link them in the root node_modules regardless
if (pm === 'pnpm') {
updateJson(`apps/${nodeapp}/package.json`, (json) => {
json.dependencies = {
[`@proj/${nodelib}`]: 'workspace:',
};
return json;
});
runCommand(getPackageManagerCommand().install);
}
runCLI(`sync`);
expect(() => runCLI(`lint ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`test ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`build ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`lint ${nodelib}`)).not.toThrow();
expect(() => runCLI(`test ${nodelib}`)).not.toThrow();
expect(() => runCLI(`build ${nodelib}`)).not.toThrow();
const p = await runCommandUntil(
`serve ${nodeapp}`,
(output) => output.includes(`Listening at http://localhost:${port}`),
{
env: {
NX_DAEMON: 'true',
},
}
);
let result = await getData(port);
expect(result.message).toMatch(`Welcome to ${nodeapp}!`);
result = await getData(port, '/assets/file.txt');
expect(result).toMatch(`Test`);
try {
await promisifiedTreeKill(p.pid, 'SIGKILL');
expect(await killPorts(port)).toBeTruthy();
} catch (err) {
expect(err).toBeFalsy();
}
}, 300_000);
});
function getRandomPort() {
return Math.floor(1000 + Math.random() * 9000);
}
function getData(port, path = '/api'): Promise<any> {
return new Promise((resolve) => {
http.get(`http://localhost:${port}${path}`, (res) => {
expect(res.statusCode).toEqual(200);
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.once('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
resolve(data);
}
});
});
});
}

View File

@ -30,7 +30,7 @@ describe('Node Applications + webpack', () => {
// This fails with Crystal enabled because `--optimization` is not a correct flag to pass to `webpack`.
runCLI(
`generate @nx/node:app apps/${app} --bundler=webpack --no-interactive`,
`generate @nx/node:app apps/${app} --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest`,
{
env: { NX_ADD_PLUGINS: 'false' },
}

View File

@ -72,11 +72,11 @@ describe('Node Applications', () => {
const port = getRandomPort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${nodeapp} --port=${port} --linter=eslint`
`generate @nx/node:app apps/${nodeapp} --port=${port} --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nodeapp}`);
expect(lintResults).toContain('Successfully ran target lint');
expect(() => runCLI(`lint ${nodeapp}`)).not.toThrow();
expect(() => runCLI(`test ${nodeapp}`)).not.toThrow();
updateFile(`apps/${nodeapp}/src/main.ts`, `console.log('Hello World!');`);
await runCLIAsync(`build ${nodeapp}`);
@ -92,7 +92,9 @@ describe('Node Applications', () => {
// TODO(crystal, @ndcunningham): This does not work because NxWebpackPlugin({}) outputFilename does not work.
xit('should be able to generate the correct outputFileName in options', async () => {
const nodeapp = uniq('nodeapp');
runCLI(`generate @nx/node:app apps/${nodeapp} --linter=eslint`);
runCLI(
`generate @nx/node:app apps/${nodeapp} --linter=eslint --unitTestRunner=jest`
);
updateJson(join('apps', nodeapp, 'project.json'), (config) => {
config.targets.build.options.outputFileName = 'index.js';
@ -108,7 +110,7 @@ describe('Node Applications', () => {
const port = getRandomPort();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${nodeapp} --port=${port} --linter=eslint --bundler=webpack`
`generate @nx/node:app apps/${nodeapp} --port=${port} --linter=eslint --bundler=webpack --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nodeapp}`);
@ -190,7 +192,7 @@ module.exports = {
const nodeapp = uniq('nodeapp');
runCLI(
`generate @nx/node:app apps/${nodeapp} --linter=eslint --bundler=webpack --framework=none`
`generate @nx/node:app apps/${nodeapp} --linter=eslint --bundler=webpack --framework=none --unitTestRunner=jest`
);
updateFile('.env', `NX_FOOBAR="test foo bar"`);
@ -227,7 +229,9 @@ module.exports = {
it("should exclude 'test' target from e2e project that uses jest", async () => {
const appName = uniq('nodeapp');
runCLI(`generate @nx/node:app ${appName} --no-interactive`);
runCLI(
`generate @nx/node:app ${appName} --no-interactive --unitTestRunner=jest --linter=eslint --e2eTestRunner=jest`
);
const nxJson = JSON.parse(readFile('nx.json'));
expect(nxJson.plugins).toBeDefined();
@ -246,7 +250,7 @@ module.exports = {
process.env.PORT = `${port}`;
runCLI(
`generate @nx/express:app apps/${nodeapp} --port=${port} --linter=eslint`
`generate @nx/express:app apps/${nodeapp} --port=${port} --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nodeapp}`);
@ -296,7 +300,9 @@ module.exports = {
it('should be able to generate a nest application', async () => {
const nestapp = uniq('nestapp');
const port = 3335;
runCLI(`generate @nx/nest:app apps/${nestapp} --linter=eslint`);
runCLI(
`generate @nx/nest:app apps/${nestapp} --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestapp}`);
expect(lintResults).toContain('Successfully ran target lint');
@ -344,7 +350,7 @@ module.exports = {
const nestapp = 'node-nest-docker-test';
runCLI(
`generate @nx/node:app ${nestapp} --bundler=webpack --framework=nest --docker`
`generate @nx/node:app ${nestapp} --bundler=webpack --framework=nest --docker --unitTestRunner=jest`
);
checkFilesExist(`${nestapp}/Dockerfile`);
@ -359,7 +365,7 @@ module.exports = {
const esmapp = uniq('esmapp');
runCLI(
`generate @nx/node:app ${esmapp} --linter=eslint --framework=none --bundler=webpack`
`generate @nx/node:app ${esmapp} --linter=eslint --framework=none --bundler=webpack --unitTestRunner=jest`
);
updateJson(`apps/${esmapp}/tsconfig.app.json`, (config) => {
config.module = 'esnext';
@ -424,7 +430,9 @@ describe('Build Node apps', () => {
xit('should generate a package.json with the `--generatePackageJson` flag', async () => {
const packageManager = detectPackageManager(tmpProjPath());
const nestapp = uniq('nestapp');
runCLI(`generate @nx/nest:app apps/${nestapp} --linter=eslint`);
runCLI(
`generate @nx/nest:app apps/${nestapp} --linter=eslint --unitTestRunner=jest`
);
await runCLIAsync(`build ${nestapp} --generatePackageJson`);
@ -485,7 +493,9 @@ describe('Build Node apps', () => {
});
const nodeapp = uniq('nodeapp');
runCLI(`generate @nx/node:app ${nodeapp} --bundler=webpack`);
runCLI(
`generate @nx/node:app ${nodeapp} --bundler=webpack --unitTestRunner=jest --linter=eslint`
);
const jslib = uniq('jslib');
runCLI(`generate @nx/js:lib ${jslib} --bundler=tsc`);
@ -529,7 +539,7 @@ ${jslib}();
process.env.PORT = `${port}`;
runCLI(
`generate @nx/node:app apps/${appName} --port=${port} --no-interactive`
`generate @nx/node:app apps/${appName} --port=${port} --no-interactive --linter=eslint --unitTestRunner=jest`
);
// deleteOutputPath should default to true
@ -569,7 +579,9 @@ ${jslib}();
const port = getRandomPort();
process.env.PORT = `${port}`;
runCLI(`generate @nx/node:app ${appName} --port=${port} --no-interactive`);
runCLI(
`generate @nx/node:app ${appName} --port=${port} --no-interactive --linter=eslint --unitTestRunner=jest`
);
// check files are generated without the layout directory ("apps/") and
// using the project name as the directory when no directory is provided
@ -584,7 +596,9 @@ ${jslib}();
`Successfully ran target test for project ${appName}`
);
runCLI(`generate @nx/node:lib ${libName} --buildable --no-interactive`);
runCLI(
`generate @nx/node:lib ${libName} --buildable --no-interactive --linter=eslint --unitTestRunner=jest`
);
// check files are generated without the layout directory ("libs/") and
// using the project name as the directory when no directory is provided
@ -605,7 +619,9 @@ ${jslib}();
// TODO(crystal, @ndcunningham): What is the alternative here?
xit('should have plugin output if specified in `tsPlugins`', async () => {
const nestapp = uniq('nestapp');
runCLI(`generate @nx/nest:app ${nestapp} --linter=eslint`);
runCLI(
`generate @nx/nest:app ${nestapp} --linter=eslint --unitTestRunner=jest`
);
packageInstall('@nestjs/swagger', undefined, '^7.0.0');
@ -659,7 +675,9 @@ ${jslib}();
describe('nest libraries', function () {
it('should be able to generate a nest library', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib libs/${nestlib}`);
runCLI(
`generate @nx/nest:lib libs/${nestlib} --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
@ -673,7 +691,9 @@ ${jslib}();
it('should be able to generate a nest library w/ service', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --service`);
runCLI(
`generate @nx/nest:lib ${nestlib} --service --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
@ -687,7 +707,9 @@ ${jslib}();
it('should be able to generate a nest library w/ controller', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --controller`);
runCLI(
`generate @nx/nest:lib ${nestlib} --controller --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
@ -701,7 +723,9 @@ ${jslib}();
it('should be able to generate a nest library w/ controller and service', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib ${nestlib} --controller --service`);
runCLI(
`generate @nx/nest:lib ${nestlib} --controller --service --linter=eslint --unitTestRunner=jest`
);
const lintResults = runCLI(`lint ${nestlib}`);
expect(lintResults).toContain('Successfully ran target lint');
@ -714,7 +738,9 @@ ${jslib}();
it('should have plugin output if specified in `transformers`', async () => {
const nestlib = uniq('nestlib');
runCLI(`generate @nx/nest:lib libs/${nestlib} --buildable`);
runCLI(
`generate @nx/nest:lib libs/${nestlib} --buildable --linter=eslint --unitTestRunner=jest`
);
packageInstall('@nestjs/swagger', undefined, '^7.0.0');
@ -758,9 +784,5 @@ exports.FooModel = FooModel;
`
);
}, 300000);
it('should run default jest tests', async () => {
await expectJestTestsToPass('@nx/node:lib');
}, 100000);
});
});

View File

@ -16,7 +16,7 @@ describe('Nuxt Plugin', () => {
packages: ['@nx/nuxt'],
});
runCLI(
`generate @nx/nuxt:app ${app} --unitTestRunner=vitest --e2eTestRunner=cypress`
`generate @nx/nuxt:app ${app} --unitTestRunner=vitest --e2eTestRunner=cypress --linter=eslint`
);
runCLI(
`generate @nx/nuxt:component ${app}/src/components/one/one --name=one --unitTestRunner=vitest`

View File

@ -589,7 +589,7 @@ describe('Nx Running Tests', () => {
`generate @nx/js:lib ${libC} --bundler=tsc --defaults --tags=ui-b,shared --directory=libs/${libC}`
);
runCLI(
`generate @nx/node:lib ${libD} --defaults --tags=api --directory=libs/${libD}`
`generate @nx/node:lib ${libD} --defaults --tags=api --directory=libs/${libD} --buildable=false`
);
// libA depends on libC

View File

@ -76,10 +76,12 @@ export function newProject({
name = uniq('proj'),
packageManager = getSelectedPackageManager(),
packages,
preset = 'apps',
}: {
name?: string;
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'bun';
readonly packages?: Array<NxPackage>;
preset?: string;
} = {}): string {
const newProjectStart = performance.mark('new-project:start');
try {
@ -93,7 +95,7 @@ export function newProject({
'create-nx-workspace:start'
);
runCreateWorkspace(projScope, {
preset: 'apps',
preset,
packageManager,
});
const createNxWorkspaceEnd = performance.mark('create-nx-workspace:end');

View File

@ -0,0 +1,75 @@
import {
cleanupProject,
getSelectedPackageManager,
newProject,
runCLI,
uniq,
updateFile,
updateJson,
} from '@nx/e2e/utils';
describe('Vue Plugin', () => {
let proj: string;
const pm = getSelectedPackageManager();
beforeAll(() => {
proj = newProject({
packages: ['@nx/vue'],
preset: 'ts',
});
if (pm === 'pnpm') {
updateFile(
'pnpm-workspace.yaml',
`
packages:
- 'apps/**'
- 'packages/**'
`
);
} else {
updateJson('package.json', (json) => {
json.workspaces = ['apps/**', 'packages/**'];
return json;
});
}
});
afterAll(() => cleanupProject());
it('should serve application in dev mode', async () => {
const app = uniq('app');
const lib = uniq('lib');
runCLI(
`generate @nx/vue:app apps/${app} --unitTestRunner=vitest --e2eTestRunner=playwright --linter=eslint`
);
runCLI(
`generate @nx/vue:lib packages/${lib} --bundler=vite --unitTestRunner=vitest --linter=eslint`
);
// app and lib generators don't have specs by default, add some stubs
updateFile(
`apps/${app}/src/foo.spec.ts`,
`
test('it should run', () => {
expect(true).toBeTruthy();
});
`
);
updateFile(
`packages/${lib}/src/foo.spec.ts`,
`
test('it should run', () => {
expect(true).toBeTruthy();
});
`
);
expect(() => runCLI(`lint ${app}`)).not.toThrow();
expect(() => runCLI(`test ${app}`)).not.toThrow();
expect(() => runCLI(`build ${app}`)).not.toThrow();
expect(() => runCLI(`lint ${lib}`)).not.toThrow();
expect(() => runCLI(`test ${lib}`)).not.toThrow();
expect(() => runCLI(`build ${lib}`)).not.toThrow();
}, 300_000);
});

View File

@ -32,6 +32,9 @@ import { printSocialInformation } from '../src/utils/social-information';
interface BaseArguments extends CreateWorkspaceOptions {
preset: Preset;
linter?: 'none' | 'eslint';
formatter?: 'none' | 'prettier';
workspaces?: boolean;
}
interface NoneArguments extends BaseArguments {
@ -39,7 +42,6 @@ interface NoneArguments extends BaseArguments {
workspaceType?: 'package-based' | 'integrated' | 'standalone';
js?: boolean;
appName?: string | undefined;
formatter?: 'none' | 'prettier';
}
interface ReactArguments extends BaseArguments {
@ -52,9 +54,6 @@ interface ReactArguments extends BaseArguments {
nextAppDir: boolean;
nextSrcDir: boolean;
e2eTestRunner: 'none' | 'cypress' | 'playwright';
linter?: 'none' | 'eslint';
formatter?: 'none' | 'prettier';
workspaces?: boolean;
}
interface AngularArguments extends BaseArguments {
@ -730,6 +729,10 @@ async function determineVueOptions(
let style: undefined | string = undefined;
let appName: string;
let e2eTestRunner: undefined | 'none' | 'cypress' | 'playwright' = undefined;
let linter: undefined | 'none' | 'eslint';
let formatter: undefined | 'none' | 'prettier';
const workspaces = parsedArgs.workspaces ?? false;
if (parsedArgs.preset && parsedArgs.preset !== Preset.Vue) {
preset = parsedArgs.preset;
@ -741,7 +744,9 @@ async function determineVueOptions(
} else {
const framework = await determineVueFramework(parsedArgs);
const workspaceType = await determineStandaloneOrMonorepo();
const workspaceType = workspaces
? 'monorepo'
: await determineStandaloneOrMonorepo();
if (workspaceType === 'standalone') {
appName = parsedArgs.appName ?? parsedArgs.name;
} else {
@ -798,7 +803,23 @@ async function determineVueOptions(
style = reply.style;
}
return { preset, style, appName, e2eTestRunner };
if (workspaces) {
linter = await determineLinterOptions(parsedArgs);
formatter = await determineFormatterOptions(parsedArgs);
} else {
linter = 'eslint';
formatter = 'prettier';
}
return {
preset,
style,
appName,
e2eTestRunner,
linter,
formatter,
workspaces,
};
}
async function determineAngularOptions(
@ -967,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;
@ -989,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;
@ -1024,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,
};
}

View File

@ -32,7 +32,6 @@
},
"dependencies": {
"@nx/devkit": "file:../devkit",
"@nx/js": "file:../js",
"@nx/node": "file:../node",
"tslib": "^2.3.0"
},

View File

@ -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,178 @@ 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",
"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",
},
],
}
`);
});
});
});

View File

@ -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[] = [];

View File

@ -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",

View File

@ -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);

View File

@ -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,169 @@ 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",
"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",
},
],
}
`);
});
});
});

View File

@ -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<GeneratorCallback> {
assertNotUsingTsSolutionSetup(tree, 'nest', 'application');
const options = await normalizeOptions(tree, rawOptions);
const tasks: GeneratorCallback[] = [];

View File

@ -15,6 +15,7 @@ export interface ApplicationGeneratorOptions {
rootProject?: boolean;
strict?: boolean;
addPlugin?: boolean;
useTsSolution?: boolean;
}
interface NormalizedOptions extends ApplicationGeneratorOptions {

View File

@ -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",

View File

@ -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<GeneratorCallback> {
assertNotUsingTsSolutionSetup(tree, 'nest', 'init');
let installPackagesTask: GeneratorCallback = () => {};
if (!options.skipPackageJson) {
installPackagesTask = addDependencies(tree, options);

View File

@ -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",
},
],
}
`);
});
});
});

View File

@ -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<GeneratorCallback> {
assertNotUsingTsSolutionSetup(tree, 'nest', 'library');
const options = await normalizeOptions(tree, rawOptions);
await jsLibraryGenerator(tree, toJsLibraryGeneratorOptions(options));
const initTask = await initGenerator(tree, rawOptions);

View File

@ -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).",

View File

@ -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,152 @@ 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",
"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",
},
],
}
`);
});
});
});

View File

@ -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,30 @@ 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,
projectType: 'application',
sourceRoot: project.sourceRoot,
targets: project.targets,
tags: project.tags?.length ? project.tags : undefined,
},
});
} 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 +430,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 +469,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 +522,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 +572,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 +637,7 @@ async function normalizeOptions(
'dist',
options.rootProject ? options.name : appProjectRoot
),
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
};
}

View File

@ -8,6 +8,7 @@ export interface Schema {
unitTestRunner?: 'jest' | 'none';
e2eTestRunner?: 'jest' | 'none';
linter?: Linter | LinterType;
formatter?: 'none' | 'prettier';
tags?: string;
frontendProject?: string;
swcJest?: boolean;
@ -23,6 +24,7 @@ export interface Schema {
docker?: boolean;
isNest?: boolean;
addPlugin?: boolean;
useTsSolution?: boolean;
}
export type NodeJsFrameWorks = 'express' | 'koa' | 'fastify' | 'nest' | 'none';

View File

@ -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"

View File

@ -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: '<rootDir>/src/support/global-setup.ts',
globalTeardown: '<rootDir>/src/support/global-teardown.ts',
setupFiles: ['<rootDir>/src/support/test-setup.ts'],
testEnvironment: 'node',
transform: {
'^.+\\\\.[tj]s$': [
'ts-jest',
{
tsconfig: '<rootDir>/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",
},
],
}
`);
});
});
});

View File

@ -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);
});

View File

@ -5,7 +5,7 @@ export default {
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', {
tsconfig: '<rootDir>/tsconfig.spec.json',
tsconfig: '<rootDir>/<%= tsConfigFile %>',
}],
},
moduleFileExtensions: ['ts', 'js', 'html'],

View File

@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "<%= offsetFromRoot %>/dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.ts"
]
}

View File

@ -7,7 +7,7 @@ export default {
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': ['ts-jest', {
tsconfig: '<rootDir>/tsconfig.spec.json',
tsconfig: '<rootDir>/<%= tsConfigFile %>',
}],
},
moduleFileExtensions: ['ts', 'js', 'html'],

View File

@ -1,13 +0,0 @@
{
"extends": "<%= offsetFromRoot %><% if (rootProject) { %>tsconfig.json<% } else { %>tsconfig.base.json<% } %>",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}

View File

@ -0,0 +1,16 @@
{
"extends": "<%= offsetFromRoot %>tsconfig.base.json",
"compilerOptions": {
"outDir": "out-tsc/<%= e2eProjectName %>",
"esModuleInterop": true,
"noUnusedLocals": false,
"noImplicitAny": false
},
"include": [
"jest.config.ts",
"src/**/*.ts"
],
"references": [
{ "path": "<%= relativeProjectReferencePath %>" }
]
}

View File

@ -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);

View File

@ -1,4 +0,0 @@
{
"name": "<%= importPath %>",
"version": "0.0.1"
}

View File

@ -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"]
}

View File

@ -5,6 +5,8 @@ import {
readJson,
readProjectConfiguration,
Tree,
updateJson,
writeJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
@ -474,7 +476,7 @@ describe('lib', () => {
'src/**/*.js',
]);
expect(readJson(tree, 'my-lib/tsconfig.lib.json').exclude).toEqual([
'jest.config.ts',
'jest.config.js',
'src/**/*.spec.ts',
'src/**/*.test.ts',
'src/**/*.spec.js',
@ -517,4 +519,163 @@ 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",
},
],
}
`);
});
it('should set correct options for swc', async () => {
await libraryGenerator(tree, {
directory: 'mylib',
buildable: true,
compiler: 'swc',
unitTestRunner: 'jest',
addPlugin: true,
} as Schema);
expect(readJson(tree, 'mylib/package.json')).toMatchInlineSnapshot(`
{
"dependencies": {
"tslib": "^2.3.0",
},
"main": "./dist/index.js",
"name": "@proj/mylib",
"nx": {
"name": "mylib",
"projectType": "library",
"sourceRoot": "mylib/src",
"targets": {
"build": {
"executor": "@nx/js:swc",
"options": {
"main": "mylib/src/index.ts",
"outputPath": "mylib/dist",
"packageJson": "mylib/package.json",
"stripLeadingPaths": true,
"tsConfig": "mylib/tsconfig.lib.json",
},
"outputs": [
"{options.outputPath}",
],
},
},
},
"private": true,
"type": "commonjs",
"typings": "./dist/index.d.ts",
"version": "0.0.1",
}
`);
});
});
});

View File

@ -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,29 @@ 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: options.isUsingTsSolutionConfig
? joinPathFragments(options.projectRoot, 'dist')
: 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.isUsingTsSolutionConfig
? undefined
: [`${options.projectRoot}/*.md`],
stripLeadingPaths: options.isUsingTsSolutionConfig ? true : undefined,
},
};
}
if (options.compiler === 'swc') {
addSwcDependencies(tree);

View File

@ -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"
},

View File

@ -108,7 +108,6 @@ exports[`app generated files content - as-provided - my-app general application
exports[`app generated files content - as-provided - my-app general application should configure tsconfig and project.json correctly 2`] = `
"{
"compilerOptions": {},
"files": [],
"include": [".nuxt/nuxt.d.ts"],
"references": [
@ -190,7 +189,6 @@ exports[`app generated files content - as-provided - my-app general application
exports[`app generated files content - as-provided - my-app general application should configure vitest correctly 3`] = `
"{
"compilerOptions": {},
"files": [],
"include": [".nuxt/nuxt.d.ts"],
"references": [
@ -469,7 +467,6 @@ exports[`app generated files content - as-provided - myApp general application s
exports[`app generated files content - as-provided - myApp general application should configure tsconfig and project.json correctly 2`] = `
"{
"compilerOptions": {},
"files": [],
"include": [".nuxt/nuxt.d.ts"],
"references": [
@ -551,7 +548,6 @@ exports[`app generated files content - as-provided - myApp general application s
exports[`app generated files content - as-provided - myApp general application should configure vitest correctly 3`] = `
"{
"compilerOptions": {},
"files": [],
"include": [".nuxt/nuxt.d.ts"],
"references": [

View File

@ -1,7 +1,13 @@
import 'nx/src/internal-testing-utils/mock-project-graph';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree, readJson, readProjectConfiguration } from '@nx/devkit';
import {
Tree,
readJson,
readProjectConfiguration,
updateJson,
writeJson,
} from '@nx/devkit';
import { applicationGenerator } from './application';
describe('app', () => {
@ -194,4 +200,173 @@ 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',
e2eTestRunner: 'playwright',
unitTestRunner: 'vitest',
linter: 'eslint',
});
expect(tree.read('myapp/vite.config.ts', 'utf-8')).toMatchInlineSnapshot(
`null`
);
expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./myapp-e2e",
},
{
"path": "./myapp",
},
]
`);
expect(readJson(tree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(`
{
"extends": "../tsconfig.base.json",
"files": [],
"references": [
{
"path": "./tsconfig.app.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}
`);
expect(readJson(tree, 'myapp/tsconfig.app.json')).toMatchInlineSnapshot(`
{
"compilerOptions": {
"composite": true,
"jsx": "preserve",
"jsxImportSource": "vue",
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "out-tsc/myapp",
"resolveJsonModule": true,
"rootDir": "src",
},
"exclude": [
"dist",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"eslint.config.js",
"eslint.config.cjs",
"eslint.config.mjs",
],
"extends": "../tsconfig.base.json",
"include": [
".nuxt/nuxt.d.ts",
"src/**/*",
],
}
`);
expect(readJson(tree, 'myapp/tsconfig.spec.json')).toMatchInlineSnapshot(`
{
"compilerOptions": {
"composite": true,
"jsx": "preserve",
"jsxImportSource": "vue",
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "./out-tsc/vitest",
"resolveJsonModule": true,
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest",
],
},
"extends": "../tsconfig.base.json",
"include": [
".nuxt/nuxt.d.ts",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts",
],
"references": [
{
"path": "./tsconfig.app.json",
},
],
}
`);
expect(readJson(tree, 'myapp-e2e/tsconfig.json')).toMatchInlineSnapshot(`
{
"compilerOptions": {
"allowJs": true,
"outDir": "dist",
"sourceMap": false,
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo",
},
"exclude": [
"dist",
"eslint.config.js",
"eslint.config.mjs",
"eslint.config.cjs",
],
"extends": "../tsconfig.base.json",
"include": [
"**/*.ts",
"**/*.js",
"playwright.config.ts",
"src/**/*.spec.ts",
"src/**/*.spec.js",
"src/**/*.test.ts",
"src/**/*.test.js",
"src/**/*.d.ts",
],
"references": [
{
"path": "../myapp",
},
],
}
`);
});
});
});

View File

@ -9,6 +9,7 @@ import {
runTasksInSerial,
toJS,
Tree,
writeJson,
} from '@nx/devkit';
import { Schema } from './schema';
import nuxtInitGenerator from '../init/init';
@ -18,7 +19,6 @@ import {
getRelativePathToRootTsConfig,
initGenerator as jsInitGenerator,
} from '@nx/js';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { updateGitIgnore } from '../../utils/update-gitignore';
import { Linter } from '@nx/eslint';
import { addE2e } from './lib/add-e2e';
@ -32,12 +32,20 @@ import {
getNxCloudAppOnBoardingUrl,
createNxCloudOnboardingURLForWelcomeApp,
} from 'nx/src/nx-cloud/utilities/onboarding';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function applicationGenerator(tree: Tree, schema: Schema) {
assertNotUsingTsSolutionSetup(tree, 'nuxt', '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 projectOffsetFromRoot = offsetFromRoot(options.appProjectRoot);
@ -51,20 +59,29 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
onBoardingStatus === 'unclaimed' &&
(await getNxCloudAppOnBoardingUrl(options.nxCloudToken));
const jsInitTask = await jsInitGenerator(tree, {
...schema,
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
skipFormat: true,
});
tasks.push(jsInitTask);
tasks.push(ensureDependencies(tree, options));
addProjectConfiguration(tree, options.projectName, {
root: options.appProjectRoot,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
targets: {},
});
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,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
});
} else {
addProjectConfiguration(tree, options.projectName, {
root: options.appProjectRoot,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
targets: {},
});
}
generateFiles(
tree,
@ -111,6 +128,7 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
projectRoot: options.appProjectRoot,
rootProject: options.rootProject,
unitTestRunner: options.unitTestRunner,
isUsingTsSolutionConfig: options.isUsingTsSolutionConfig,
},
getRelativePathToRootTsConfig(tree, options.appProjectRoot)
);
@ -168,6 +186,24 @@ export async function applicationGenerator(tree: Tree, schema: Schema) {
}
});
if (options.isUsingTsSolutionConfig) {
updateTsconfigFiles(
tree,
options.appProjectRoot,
'tsconfig.app.json',
{
jsx: 'preserve',
jsxImportSource: 'vue',
module: 'esnext',
moduleResolution: 'bundler',
resolveJsonModule: true,
},
options.linter === 'eslint'
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
: undefined
);
}
tasks.push(() => {
logShowProjectCommand(options.projectName);
});

View File

@ -4,6 +4,7 @@ import {
ensureProjectName,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { NormalizedSchema, Schema } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function normalizeOptions(
host: Tree,
@ -35,6 +36,7 @@ export async function normalizeOptions(
e2eProjectRoot,
parsedTags,
style: options.style ?? 'none',
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
} as NormalizedSchema;
normalized.unitTestRunner ??= 'vitest';

View File

@ -4,6 +4,7 @@ export interface Schema {
directory: string;
name?: string;
linter?: Linter | LinterType;
formatter?: 'none' | 'prettier';
skipFormat?: boolean;
unitTestRunner?: 'vitest' | 'none';
e2eTestRunner?: 'cypress' | 'playwright' | 'none';
@ -14,6 +15,7 @@ export interface Schema {
setParserOptionsProject?: boolean;
style?: 'css' | 'scss' | 'less' | 'none';
nxCloudToken?: string;
useTsSolution?: boolean;
}
export interface NormalizedSchema extends Schema {
@ -22,4 +24,5 @@ export interface NormalizedSchema extends Schema {
e2eProjectName: string;
e2eProjectRoot: string;
parsedTags: string[];
isUsingTsSolutionConfig: boolean;
}

View File

@ -22,24 +22,27 @@
"pattern": "^[a-zA-Z][^:]*$",
"x-priority": "important"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint"],
"default": "eslint"
},
"skipFormat": {
"description": "Skip formatting files.",
"type": "boolean",
"default": false,
"x-priority": "internal"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "none",
"x-prompt": "Which linter would you like to use?",
"x-priority": "important"
},
"unitTestRunner": {
"type": "string",
"enum": ["vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "none"
"default": "none",
"x-priority": "important"
},
"e2eTestRunner": {
"type": "string",

View File

@ -1,14 +1,11 @@
import { createProjectGraphAsync, GeneratorCallback, Tree } from '@nx/devkit';
import { addPluginV1 } from '@nx/devkit/src/utils/add-plugin';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { createNodes } from '../../plugins/plugin';
import { InitSchema } from './schema';
import { updateDependencies } from './lib/utils';
export async function nuxtInitGenerator(host: Tree, schema: InitSchema) {
assertNotUsingTsSolutionSetup(host, 'nuxt', 'init');
await addPluginV1(
host,
await createProjectGraphAsync(),

View File

@ -7,14 +7,14 @@ export function createTsConfig(
projectRoot: string;
rootProject?: boolean;
unitTestRunner?: string;
isUsingTsSolutionConfig: boolean;
},
relativePathToRootTsConfig: string
) {
createAppTsConfig(host, options);
const json = {
compilerOptions: {},
files: [],
include: ['.nuxt/nuxt.d.ts'],
include: options.isUsingTsSolutionConfig ? undefined : ['.nuxt/nuxt.d.ts'],
references: [
{
path: './tsconfig.app.json',

View File

@ -97,7 +97,12 @@ export async function configurationGeneratorInternal(
if (isTsSolutionSetup) {
// skip eslint from typechecking since it extends from root file that is outside rootDir
if (options.linter === 'eslint') {
tsconfig.exclude = ['dist', 'eslint.config.js'];
tsconfig.exclude = [
'dist',
'eslint.config.js',
'eslint.config.mjs',
'eslint.config.cjs',
];
}
tsconfig.compilerOptions.outDir = 'dist';

View File

@ -1419,6 +1419,8 @@ describe('app', () => {
"exclude": [
"dist",
"eslint.config.js",
"eslint.config.mjs",
"eslint.config.cjs",
],
"extends": "../tsconfig.base.json",
"include": [

View File

@ -1001,7 +1001,7 @@ module.exports = withNx(
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: '../coverage/mylib',
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8',
}
},

View File

@ -507,6 +507,8 @@ describe('Remix Application', () => {
"exclude": [
"dist",
"eslint.config.js",
"eslint.config.mjs",
"eslint.config.cjs",
],
"extends": "../tsconfig.base.json",
"include": [

View File

@ -460,10 +460,11 @@ export function createOrEditViteConfig(
);
}
const reportsDirectory =
projectRoot === '.'
? `./coverage/${options.project}`
: `${offsetFromRoot(projectRoot)}coverage/${projectRoot}`;
const reportsDirectory = isUsingTsPlugin
? './test-output/vitest/coverage'
: projectRoot === '.'
? `./coverage/${options.project}`
: `${offsetFromRoot(projectRoot)}coverage/${projectRoot}`;
const testOption = options.includeVitest
? ` test: {

View File

@ -361,6 +361,7 @@ exports[`application generator should set up project correctly with given option
"test/src/app/NxWelcome.vue",
"test/src/main.ts",
"test/src/styles.css",
"test/src/vue-shims.d.ts",
"test/tsconfig.app.json",
"test/tsconfig.json",
"test/tsconfig.spec.json",

View File

@ -6,6 +6,9 @@ import {
readProjectConfiguration,
readNxJson,
updateNxJson,
updateJson,
writeJson,
readJson,
} from '@nx/devkit';
import * as devkitExports from 'nx/src/devkit-exports';
@ -96,6 +99,188 @@ describe('application generator', () => {
expect(tree.exists('test/src/style.none')).toBeFalsy();
expect(tree.read('test/src/main.ts', 'utf-8')).not.toContain('styles.none');
});
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, {
...options,
style: 'none',
linter: 'eslint',
});
expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchInlineSnapshot(`
"/// <reference types='vitest' />
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/test',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4300,
host: 'localhost',
},
plugins: [vue()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: './dist',
emptyOutDir: true,
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
},
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8',
},
},
});
"
`);
expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./test-e2e",
},
{
"path": "./test",
},
]
`);
expect(readJson(tree, 'test/tsconfig.json')).toMatchInlineSnapshot(`
{
"extends": "../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}
`);
expect(readJson(tree, 'test/tsconfig.app.json')).toMatchInlineSnapshot(`
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue",
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "out-tsc/test",
"resolveJsonModule": true,
"rootDir": "src",
"types": [
"vite/client",
],
},
"exclude": [
"dist",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.vue",
"src/**/*.test.vue",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"eslint.config.js",
"eslint.config.cjs",
"eslint.config.mjs",
],
"extends": "../tsconfig.base.json",
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.vue",
],
}
`);
expect(readJson(tree, 'test/tsconfig.spec.json')).toMatchInlineSnapshot(`
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue",
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "./out-tsc/vitest",
"resolveJsonModule": true,
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest",
],
},
"extends": "../tsconfig.base.json",
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts",
],
"references": [
{
"path": "./tsconfig.app.json",
},
],
}
`);
});
});
});
function listFiles(tree: Tree): string[] {

View File

@ -2,14 +2,15 @@ import {
addProjectConfiguration,
formatFiles,
GeneratorCallback,
joinPathFragments,
readNxJson,
runTasksInSerial,
toJS,
Tree,
writeJson,
} from '@nx/devkit';
import { Linter } from '@nx/eslint';
import { initGenerator as jsInitGenerator } from '@nx/js';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { Schema } from './schema';
import { normalizeOptions } from './lib/normalize-options';
import { vueInitGenerator } from '../init/init';
@ -20,6 +21,8 @@ import { addVite } from './lib/add-vite';
import { extractTsConfigBase } from '../../utils/create-ts-config';
import { ensureDependencies } from '../../utils/ensure-dependencies';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
export function applicationGenerator(tree: Tree, options: Schema) {
return applicationGeneratorInternal(tree, { addPlugin: false, ...options });
@ -29,7 +32,18 @@ export async function applicationGeneratorInternal(
tree: Tree,
_options: Schema
): Promise<GeneratorCallback> {
assertNotUsingTsSolutionSetup(tree, 'vue', 'application');
const tasks: GeneratorCallback[] = [];
tasks.push(
await jsInitGenerator(tree, {
..._options,
tsConfigName: _options.rootProject
? 'tsconfig.json'
: 'tsconfig.base.json',
skipFormat: true,
addTsPlugin: _options.useTsSolution,
formatter: _options.formatter,
})
);
const options = await normalizeOptions(tree, _options);
const nxJson = readNxJson(tree);
@ -38,24 +52,28 @@ export async function applicationGeneratorInternal(
process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
const tasks: GeneratorCallback[] = [];
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,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
});
} else {
addProjectConfiguration(tree, options.projectName, {
root: options.appProjectRoot,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
targets: {},
});
}
addProjectConfiguration(tree, options.projectName, {
root: options.appProjectRoot,
projectType: 'application',
sourceRoot: `${options.appProjectRoot}/src`,
targets: {},
});
tasks.push(
await jsInitGenerator(tree, {
...options,
tsConfigName: options.rootProject
? 'tsconfig.json'
: 'tsconfig.base.json',
skipFormat: true,
})
);
tasks.push(
await vueInitGenerator(tree, {
...options,
@ -97,6 +115,24 @@ export async function applicationGeneratorInternal(
if (!options.skipFormat) await formatFiles(tree);
if (options.isUsingTsSolutionConfig) {
updateTsconfigFiles(
tree,
options.appProjectRoot,
'tsconfig.app.json',
{
jsx: 'preserve',
jsxImportSource: 'vue',
module: 'esnext',
moduleResolution: 'bundler',
resolveJsonModule: true,
},
options.linter === 'eslint'
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
: undefined
);
}
tasks.push(() => {
logShowProjectCommand(options.projectName);
});

View File

@ -0,0 +1,5 @@
declare module '*.vue' {
import { defineComponent } from 'vue';
const component: ReturnType<typeof defineComponent>;
export default component;
}

View File

@ -4,6 +4,7 @@ import {
ensureProjectName,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { NormalizedSchema, Schema } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function normalizeOptions(
host: Tree,
@ -39,6 +40,7 @@ export async function normalizeOptions(
normalized.routing = normalized.routing ?? false;
normalized.unitTestRunner ??= 'vitest';
normalized.e2eTestRunner = normalized.e2eTestRunner ?? 'playwright';
normalized.isUsingTsSolutionConfig = isUsingTsSolutionSetup(host);
return normalized;
}

View File

@ -10,6 +10,7 @@ export interface Schema {
inSourceTests?: boolean;
e2eTestRunner: 'cypress' | 'playwright' | 'none';
linter: Linter | LinterType;
formatter?: 'none' | 'prettier';
routing?: boolean;
js?: boolean;
strict?: boolean;
@ -18,6 +19,7 @@ export interface Schema {
rootProject?: boolean;
addPlugin?: boolean;
nxCloudToken?: string;
useTsSolution?: boolean;
}
export interface NormalizedSchema extends Schema {
@ -27,4 +29,5 @@ export interface NormalizedSchema extends Schema {
e2eProjectRoot: string;
parsedTags: string[];
devServerPort?: number;
isUsingTsSolutionConfig: boolean;
}

View File

@ -60,12 +60,6 @@
]
}
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "eslint"
},
"routing": {
"type": "boolean",
"description": "Generate application with routes.",
@ -78,12 +72,21 @@
"default": false,
"x-priority": "internal"
},
"linter": {
"description": "The tool to use for running lint checks.",
"type": "string",
"enum": ["eslint", "none"],
"default": "none",
"x-prompt": "Which linter would you like to use?",
"x-priority": "important"
},
"unitTestRunner": {
"type": "string",
"enum": ["vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "Which unit test runner would you like to use?",
"default": "vitest"
"default": "none",
"x-priority": "important"
},
"inSourceTests": {
"type": "boolean",

View File

@ -6,7 +6,6 @@ import {
runTasksInSerial,
Tree,
} from '@nx/devkit';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { nxVersion, vueVersion } from '../../utils/versions';
import { InitSchema } from './schema';
@ -28,8 +27,6 @@ function updateDependencies(host: Tree, schema: InitSchema) {
}
export async function vueInitGenerator(host: Tree, schema: InitSchema) {
assertNotUsingTsSolutionSetup(host, 'vue', 'init');
let installTask: GeneratorCallback = () => {};
if (!schema.skipPackageJson) {
installTask = updateDependencies(host, schema);

View File

@ -1,12 +0,0 @@
{
"name": "<%= name %>",
"version": "0.0.1",
"main": "./index.js",
"types": "./index.d.ts",
"exports": {
".": {
"import": "./index.mjs",
"require": "./index.js"
}
}
}

View File

@ -31,8 +31,23 @@ export function createLibraryFiles(host: Tree, options: NormalizedSchema) {
substitutions
);
if (!options.publishable && options.bundler === 'none') {
host.delete(`${options.projectRoot}/package.json`);
if (
!options.isUsingTsSolutionConfig &&
(options.publishable || options.bundler !== 'none')
) {
writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), {
name: options.name,
version: '0.0.1',
main: './index.js',
types: './index.d.ts',
exports: {
'.': {
import: './index.mjs',
require: './index.js',
types: './index.d.ts',
},
},
});
}
if (options.unitTestRunner !== 'vitest') {

View File

@ -10,6 +10,7 @@ import {
ensureProjectName,
} from '@nx/devkit/src/generators/project-name-and-root-utils';
import { NormalizedSchema, Schema } from '../schema';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export async function normalizeOptions(
host: Tree,
@ -60,6 +61,7 @@ export async function normalizeOptions(
projectRoot,
parsedTags,
importPath,
isUsingTsSolutionConfig: isUsingTsSolutionSetup(host),
} as NormalizedSchema;
// Libraries with a bundler or is publishable must also be buildable.

View File

@ -5,6 +5,7 @@ import {
readProjectConfiguration,
Tree,
updateJson,
writeJson,
} from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/eslint';
@ -438,4 +439,212 @@ module.exports = [
expect(eslintConfig.overrides[0].files).toContain('*.vue');
});
});
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, {
...defaultSchema,
setParserOptionsProject: true,
linter: 'eslint',
});
expect(tree.read('my-lib/vite.config.ts', 'utf-8'))
.toMatchInlineSnapshot(`
"import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/my-lib',
plugins: [vue()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
test: {
watch: false,
globals: true,
environment: 'jsdom',
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
reporters: ['default'],
coverage: {
reportsDirectory: './test-output/vitest/coverage',
provider: 'v8',
},
},
});
"
`);
expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(`
[
{
"path": "./my-lib",
},
]
`);
expect(readJson(tree, 'my-lib/tsconfig.json')).toMatchInlineSnapshot(`
{
"extends": "../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json",
},
{
"path": "./tsconfig.spec.json",
},
],
}
`);
expect(readJson(tree, 'my-lib/tsconfig.lib.json')).toMatchInlineSnapshot(`
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue",
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "out-tsc/my-lib",
"resolveJsonModule": true,
"rootDir": "src",
"types": [
"vite/client",
],
},
"exclude": [
"dist",
"src/**/__tests__/*",
"src/**/*.spec.vue",
"src/**/*.test.vue",
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"eslint.config.js",
"eslint.config.cjs",
"eslint.config.mjs",
],
"extends": "../tsconfig.base.json",
"include": [
"src/**/*.js",
"src/**/*.jsx",
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
],
}
`);
expect(readJson(tree, 'my-lib/tsconfig.spec.json'))
.toMatchInlineSnapshot(`
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue",
"module": "esnext",
"moduleResolution": "bundler",
"outDir": "./out-tsc/vitest",
"resolveJsonModule": true,
"types": [
"vitest/globals",
"vitest/importMeta",
"vite/client",
"node",
"vitest",
],
},
"extends": "../tsconfig.base.json",
"include": [
"vite.config.ts",
"vite.config.mts",
"vitest.config.ts",
"vitest.config.mts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.test.tsx",
"src/**/*.spec.tsx",
"src/**/*.test.js",
"src/**/*.spec.js",
"src/**/*.test.jsx",
"src/**/*.spec.jsx",
"src/**/*.d.ts",
],
"references": [
{
"path": "./tsconfig.lib.json",
},
],
}
`);
});
it('should exclude non-buildable libraries from TS plugin registration', async () => {
updateJson(tree, 'nx.json', (json) => {
json.plugins = ['@nx/js/typescript'];
return json;
});
await libraryGenerator(tree, {
...defaultSchema,
addPlugin: true,
setParserOptionsProject: true,
linter: 'eslint',
bundler: 'none',
});
const nxJson = readJson(tree, 'nx.json');
expect(nxJson.plugins).toMatchInlineSnapshot(`
[
{
"exclude": [
"my-lib/*",
],
"plugin": "@nx/js/typescript",
},
{
"options": {
"targetName": "lint",
},
"plugin": "@nx/eslint/plugin",
},
{
"options": {
"buildTargetName": "build",
"previewTargetName": "preview",
"serveStaticTargetName": "serve-static",
"serveTargetName": "serve",
"testTargetName": "test",
"typecheckTargetName": "typecheck",
},
"plugin": "@nx/vite/plugin",
},
]
`);
});
});
});

View File

@ -2,14 +2,17 @@ import {
addProjectConfiguration,
formatFiles,
GeneratorCallback,
installPackagesTask,
joinPathFragments,
readNxJson,
runTasksInSerial,
toJS,
Tree,
updateJson,
updateNxJson,
writeJson,
} from '@nx/devkit';
import { addTsConfigPath, initGenerator as jsInitGenerator } from '@nx/js';
import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { vueInitGenerator } from '../init/init';
import { Schema } from './schema';
import { normalizeOptions } from './lib/normalize-options';
@ -22,16 +25,19 @@ import { ensureDependencies } from '../../utils/ensure-dependencies';
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
import { getRelativeCwd } from '@nx/devkit/src/generators/artifact-name-and-directory-utils';
import { relative } from 'path';
import { getImportPath } from '@nx/js/src/utils/get-import-path';
import { updateTsconfigFiles } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { ensureProjectIsExcludedFromPluginRegistrations } from '@nx/js/src/utils/typescript/plugin';
export function libraryGenerator(tree: Tree, schema: Schema) {
return libraryGeneratorInternal(tree, { addPlugin: false, ...schema });
}
export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
assertNotUsingTsSolutionSetup(tree, 'vue', 'library');
const tasks: GeneratorCallback[] = [];
tasks.push(await jsInitGenerator(tree, { ...schema, skipFormat: true }));
const options = await normalizeOptions(tree, schema);
if (options.publishable === true && !schema.importPath) {
throw new Error(
@ -39,15 +45,43 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
);
}
addProjectConfiguration(tree, options.name, {
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
projectType: 'library',
tags: options.parsedTags,
targets: {},
});
if (options.isUsingTsSolutionConfig) {
const moduleFile =
options.bundler === 'none'
? options.js
? './src/index.js'
: './src/index.ts'
: './dist/index.mjs';
const typesFile =
options.bundler === 'none'
? options.js
? './src/index.js'
: './src/index.ts'
: './dist/index.d.ts';
writeJson(tree, joinPathFragments(options.projectRoot, 'package.json'), {
name: getImportPath(tree, options.name),
version: '0.0.1',
private: true,
module: moduleFile,
types: typesFile,
files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined,
nx: {
name: options.name,
projectType: 'application',
sourceRoot: `${options.projectRoot}/src`,
tags: options.parsedTags?.length ? options.parsedTags : undefined,
},
});
} else {
addProjectConfiguration(tree, options.name, {
root: options.projectRoot,
sourceRoot: joinPathFragments(options.projectRoot, 'src'),
projectType: 'library',
tags: options.parsedTags,
targets: {},
});
}
tasks.push(await jsInitGenerator(tree, { ...schema, skipFormat: true }));
tasks.push(
await vueInitGenerator(tree, {
...options,
@ -86,14 +120,23 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
});
}
if (options.publishable || options.bundler !== 'none') {
if (
!options.isUsingTsSolutionConfig &&
(options.publishable || options.bundler !== 'none')
) {
updateJson(tree, `${options.projectRoot}/package.json`, (json) => {
json.name = options.importPath;
return json;
});
}
if (!options.skipTsConfig) {
if (options.bundler === 'none') {
const nxJson = readNxJson(tree);
ensureProjectIsExcludedFromPluginRegistrations(nxJson, options.projectRoot);
updateNxJson(tree, nxJson);
}
if (!options.skipTsConfig && !options.isUsingTsSolutionConfig) {
addTsConfigPath(tree, options.importPath, [
joinPathFragments(
options.projectRoot,
@ -107,6 +150,29 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) {
if (!options.skipFormat) await formatFiles(tree);
if (options.isUsingTsSolutionConfig) {
updateTsconfigFiles(
tree,
options.projectRoot,
'tsconfig.lib.json',
{
jsx: 'preserve',
jsxImportSource: 'vue',
module: 'esnext',
moduleResolution: 'bundler',
resolveJsonModule: true,
},
options.linter === 'eslint'
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
: undefined
);
}
// Always run install to link packages.
if (options.isUsingTsSolutionConfig) {
tasks.push(() => installPackagesTask(tree, true));
}
tasks.push(() => {
logShowProjectCommand(options.name);
});

View File

@ -35,4 +35,5 @@ export interface NormalizedSchema extends Schema {
appMain?: string;
appSourceRoot?: string;
unitTestRunner?: 'vitest' | 'none';
isUsingTsSolutionConfig: boolean;
}

View File

@ -36,13 +36,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": {
"type": "string",
"enum": ["vitest", "none"],
"description": "Test runner to use for unit tests.",
"x-prompt": "What unit test runner should be used?"
"x-prompt": "What unit test runner should be used?",
"default": "none",
"x-priority": "important"
},
"inSourceTests": {
"type": "boolean",

View File

@ -1,7 +1,80 @@
import { Tree, updateJson, writeJson } from '@nx/devkit';
import * as shared from '@nx/js/src/utils/typescript/create-ts-config';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export function createTsConfig(
host: Tree,
projectRoot: string,
type: 'app' | 'lib',
options: {
strict?: boolean;
style?: string;
bundler?: string;
rootProject?: boolean;
unitTestRunner?: string;
},
relativePathToRootTsConfig: string
) {
if (isUsingTsSolutionSetup(host)) {
createTsConfigForTsSolution(
host,
projectRoot,
type,
options,
relativePathToRootTsConfig
);
} else {
createTsConfigForNonTsSolution(
host,
projectRoot,
type,
options,
relativePathToRootTsConfig
);
}
}
export function createTsConfigForTsSolution(
host: Tree,
projectRoot: string,
type: 'app' | 'lib',
options: {
strict?: boolean;
style?: string;
rootProject?: boolean;
unitTestRunner?: string;
},
relativePathToRootTsConfig: string
) {
const json = {
extends: relativePathToRootTsConfig,
files: [],
include: [],
references: [
{
path: type === 'app' ? './tsconfig.app.json' : './tsconfig.lib.json',
},
],
} as any;
writeJson(host, `${projectRoot}/tsconfig.json`, json);
const tsconfigProjectPath = `${projectRoot}/tsconfig.${type}.json`;
if (host.exists(tsconfigProjectPath)) {
updateJson(host, tsconfigProjectPath, (json) => {
json.compilerOptions ??= {};
const types = new Set(json.compilerOptions.types ?? []);
types.add('vite/client');
json.compilerOptions.types = Array.from(types);
return json;
});
}
}
export function createTsConfigForNonTsSolution(
host: Tree,
projectRoot: string,
type: 'app' | 'lib',

View File

@ -421,7 +421,11 @@ function setUpWorkspacesInPackageJson(tree: Tree, options: NormalizedSchema) {
options.preset === Preset.NextJs ||
options.preset === Preset.ReactMonorepo ||
options.preset === Preset.ReactNative ||
options.preset === Preset.RemixMonorepo) &&
options.preset === Preset.RemixMonorepo ||
options.preset === Preset.VueMonorepo ||
options.preset === Preset.Nuxt ||
options.preset === Preset.NodeMonorepo ||
options.preset === Preset.Express) &&
options.workspaces)
) {
const workspaces = options.workspaceGlobs ?? ['packages/**'];

View File

@ -135,6 +135,8 @@ async function createPreset(tree: Tree, options: Schema) {
e2eTestRunner: options.e2eTestRunner ?? 'playwright',
addPlugin,
nxCloudToken: options.nxCloudToken,
useTsSolution: options.workspaces,
formatter: options.formatter,
});
} else if (options.preset === Preset.VueStandalone) {
const { applicationGenerator: vueApplicationGenerator } = require('@nx' +
@ -163,6 +165,8 @@ async function createPreset(tree: Tree, options: Schema) {
e2eTestRunner: options.e2eTestRunner ?? 'playwright',
addPlugin,
nxCloudToken: options.nxCloudToken,
useTsSolution: options.workspaces,
formatter: options.formatter,
});
} else if (options.preset === Preset.NuxtStandalone) {
const { applicationGenerator: nuxtApplicationGenerator } = require('@nx' +
@ -234,6 +238,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 {
@ -245,6 +251,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' +
@ -322,6 +330,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}`);