fix(js): do not infer tsc tasks with verbose output by default (#29804)

## Current Behavior

The `@nx/js/typescript` plugin infers tasks with `--verbose`. This can
prevent users from running the same task with `--clean.` It can also
produce a lot of logs that might not be too relevant.

## Expected Behavior

The `@nx/js/typescript` plugin should not infer tasks with `--verbose`.
This is more aligned with other tools.

A new plugin option `verboseOutput` is added to allow inferring all
tasks with `--verbose` if desired.

Note: This revealed that some things were working (e.g., `dependsOn`)
because all the `typecheck` commands inferred by the different plugins
matched. As soon as the command is different, the different inferred
tasks are not merged, which is expected. We shouldn't rely on that, and
each plugin inferring the task should set the right options/metadata.
The different plugins were updated in this PR accordingly (they don't
have the verbose option).

We'll follow up on this later, so only the `@nx/js/typescript` plugin
infers the `typecheck` task. This is a breaking change so it will be for
Nx v21.

## Related Issue(s)

Fixes #28677
This commit is contained in:
Leosvel Pérez Espinosa 2025-01-30 14:19:06 +01:00 committed by GitHub
parent 07f1215411
commit c7ff6d358d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 824 additions and 102 deletions

View File

@ -71,7 +71,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -126,7 +126,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -184,7 +184,180 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should create a node with a typecheck target with "--verbose" flag when the "verboseOutput" plugin option is true', async () => {
// Sibling package.json
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/package.json': `{}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, { verboseOutput: true })
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --verbose",
"dependsOn": [
"^typecheck",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
// Sibling project.json
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/project.json': `{}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, { verboseOutput: true })
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --verbose",
"dependsOn": [
"^typecheck",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
// Other tsconfigs present
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/tsconfig.lib.json': `{}`,
'libs/my-lib/tsconfig.build.json': `{}`,
'libs/my-lib/tsconfig.spec.json': `{}`,
'libs/my-lib/project.json': `{}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, { verboseOutput: true })
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --verbose",
"dependsOn": [
"^typecheck",
],
@ -480,7 +653,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -548,7 +721,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -625,7 +798,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -717,7 +890,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -770,7 +943,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1020,7 +1193,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1095,7 +1268,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1160,7 +1333,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1231,7 +1404,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -1271,7 +1444,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
},
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1332,7 +1505,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1437,7 +1610,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1494,7 +1667,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1559,7 +1732,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1625,7 +1798,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1702,7 +1875,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1768,7 +1941,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"typecheck": {
"cache": true,
"command": "tsc --build --emitDeclarationOnly --pretty --verbose",
"command": "tsc --build --emitDeclarationOnly",
"dependsOn": [
"^typecheck",
],
@ -1947,7 +2120,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2022,7 +2195,162 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
},
},
},
}
`);
});
it('should create a node with a build target with "--verbose" flag when the "verboseOutput" plugin option is true', async () => {
// Sibling package.json
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/tsconfig.lib.json': `{"compilerOptions": {"outDir": "dist"}}`,
'libs/my-lib/tsconfig.build.json': `{}`,
'libs/my-lib/package.json': JSON.stringify({
name: 'my-lib',
main: 'dist/index.js',
types: 'dist/index.d.ts',
exports: {
'.': {
types: './dist/index.d.ts',
import: './dist/index.js',
default: './dist/index.js',
},
'./package.json': './package.json',
},
}),
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
// Reduce noise in build snapshots by disabling default typecheck target
typecheck: false,
build: true, // shorthand for apply with default options
verboseOutput: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --verbose",
"dependsOn": [
"^build",
],
"inputs": [
"production",
"^production",
{
"externalDependencies": [
"typescript",
],
},
],
"metadata": {
"description": "Builds the project with \`tsc\`.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "libs/my-lib",
},
"outputs": [
"{projectRoot}/dist",
],
"syncGenerators": [
"@nx/js:typescript-sync",
],
},
"build-deps": {
"dependsOn": [
"^build",
],
},
"watch-deps": {
"command": "npx nx watch --projects my-lib --includeDependentProjects -- npx nx build-deps my-lib",
"dependsOn": [
"build-deps",
],
},
},
},
},
}
`);
// Sibling project.json
await applyFilesToTempFsAndContext(tempFs, context, {
'libs/my-lib/tsconfig.json': `{}`,
'libs/my-lib/tsconfig.lib.json': `{"compilerOptions": {"outDir": "dist"}}`,
'libs/my-lib/tsconfig.build.json': `{}`,
'libs/my-lib/project.json': `{}`,
});
expect(
await invokeCreateNodesOnMatchingFiles(context, {
// Reduce noise in build snapshots by disabling default typecheck target
typecheck: false,
build: {}, // apply with default options
verboseOutput: true,
})
).toMatchInlineSnapshot(`
{
"projects": {
"libs/my-lib": {
"projectType": "library",
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --verbose",
"dependsOn": [
"^build",
],
@ -2090,7 +2418,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"my-build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^my-build",
],
@ -2158,7 +2486,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.build.json --pretty --verbose",
"command": "tsc --build tsconfig.build.json",
"dependsOn": [
"^build",
],
@ -2245,7 +2573,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2323,7 +2651,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2410,7 +2738,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2494,7 +2822,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2754,7 +3082,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2824,7 +3152,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2896,7 +3224,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -2961,7 +3289,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -3048,7 +3376,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -3126,7 +3454,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -3197,7 +3525,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -3279,7 +3607,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],
@ -3350,7 +3678,7 @@ describe(`Plugin: ${PLUGIN_NAME}`, () => {
"targets": {
"build": {
"cache": true,
"command": "tsc --build tsconfig.lib.json --pretty --verbose",
"command": "tsc --build tsconfig.lib.json",
"dependsOn": [
"^build",
],

View File

@ -51,6 +51,7 @@ export interface TscPluginOptions {
buildDepsName?: string;
watchDepsName?: string;
};
verboseOutput?: boolean;
}
interface NormalizedPluginOptions {
@ -67,6 +68,7 @@ interface NormalizedPluginOptions {
buildDepsName?: string;
watchDepsName?: string;
};
verboseOutput: boolean;
}
type TscProjectResult = Pick<ProjectConfiguration, 'targets'>;
@ -263,12 +265,6 @@ function buildTscTargets(
const namedInputs = getNamedInputs(projectRoot, context);
const tsConfig = readCachedTsConfig(configFilePath);
// TODO: check whether we want to always run with --pretty --verbose, it makes replacing scripts harder
// `--verbose` conflicts with `tsc -b --clean`, might be another reason for not using it, it would
// prevent users from running the task with `--clean` flag.
// Should we consider creating a different optional target for `--clean`?
// Should we consider having a plugin option to disable `--pretty` and `--verbose`?
let internalProjectReferences: Record<string, ParsedCommandLine>;
// Typecheck target
if (basename(configFilePath) === 'tsconfig.json' && options.typecheck) {
@ -284,7 +280,9 @@ function buildTscTargets(
);
const targetName = options.typecheck.targetName;
if (!targets[targetName]) {
let command = `tsc --build --emitDeclarationOnly --pretty --verbose`;
let command = `tsc --build --emitDeclarationOnly${
options.verboseOutput ? ' --verbose' : ''
}`;
if (
tsConfig.options.noEmit ||
Object.values(internalProjectReferences).some(
@ -348,7 +346,9 @@ function buildTscTargets(
targets[targetName] = {
dependsOn: [`^${targetName}`],
command: `tsc --build ${options.build.configName} --pretty --verbose`,
command: `tsc --build ${options.build.configName}${
options.verboseOutput ? ' --verbose' : ''
}`,
options: { cwd: projectRoot },
cache: true,
inputs: getInputs(
@ -1009,6 +1009,7 @@ function normalizePluginOptions(
return {
typecheck,
build,
verboseOutput: pluginOptions.verboseOutput ?? false,
};
}

View File

@ -73,7 +73,7 @@ exports[`@nx/remix/plugin Remix Classic Compiler non-root project should create
},
"tsc": {
"cache": true,
"command": "tsc",
"command": "tsc --noEmit",
"inputs": [
"production",
"^production",
@ -83,6 +83,20 @@ exports[`@nx/remix/plugin Remix Classic Compiler non-root project should create
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "my-app",
},
@ -174,7 +188,7 @@ exports[`@nx/remix/plugin Remix Classic Compiler non-root project should infer w
},
"tsc": {
"cache": true,
"command": "tsc",
"command": "tsc --noEmit",
"inputs": [
"production",
"^production",
@ -184,6 +198,20 @@ exports[`@nx/remix/plugin Remix Classic Compiler non-root project should infer w
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "my-app",
},
@ -270,7 +298,7 @@ exports[`@nx/remix/plugin Remix Classic Compiler root project should create node
},
"typecheck": {
"cache": true,
"command": "tsc",
"command": "tsc --noEmit",
"inputs": [
"production",
"^production",
@ -280,6 +308,20 @@ exports[`@nx/remix/plugin Remix Classic Compiler root project should create node
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": ".",
},
@ -364,7 +406,7 @@ exports[`@nx/remix/plugin Remix Vite Compiler non-root project should create nod
},
"tsc": {
"cache": true,
"command": "tsc",
"command": "tsc --noEmit",
"inputs": [
"production",
"^production",
@ -374,6 +416,20 @@ exports[`@nx/remix/plugin Remix Vite Compiler non-root project should create nod
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": "my-app",
},
@ -459,7 +515,7 @@ exports[`@nx/remix/plugin Remix Vite Compiler root project should create nodes 1
},
"typecheck": {
"cache": true,
"command": "tsc",
"command": "tsc --noEmit",
"inputs": [
"production",
"^production",
@ -469,6 +525,20 @@ exports[`@nx/remix/plugin Remix Vite Compiler root project should create nodes 1
],
},
],
"metadata": {
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": ".",
},

View File

@ -2,6 +2,7 @@ import { type CreateNodesContext, joinPathFragments } from '@nx/devkit';
import { createNodesV2 as createNodes } from './plugin';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { loadViteDynamicImport } from '../utils/executor-utils';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
jest.mock('../utils/executor-utils', () => ({
loadViteDynamicImport: jest.fn().mockResolvedValue({
@ -9,11 +10,20 @@ jest.mock('../utils/executor-utils', () => ({
}),
}));
jest.mock('@nx/js/src/utils/typescript/ts-solution-setup', () => ({
...jest.requireActual('@nx/js/src/utils/typescript/ts-solution-setup'),
isUsingTsSolutionSetup: jest.fn(),
}));
describe('@nx/remix/plugin', () => {
let createNodesFunction = createNodes[1];
let context: CreateNodesContext;
let cwd = process.cwd();
beforeEach(() => {
(isUsingTsSolutionSetup as jest.Mock).mockReturnValue(false);
});
describe('Remix Classic Compiler', () => {
describe('root project', () => {
const tempFs = new TempFs('test');
@ -166,6 +176,87 @@ module.exports = {
expect(nodes).toMatchSnapshot();
});
it('should infer typecheck without --build flag when not using TS solution setup', async () => {
tempFs.createFileSync(
'my-app/package.json',
JSON.stringify('{"name": "my-app"}')
);
const nodes = await createNodesFunction(
['my-app/remix.config.cjs'],
{ typecheckTargetName: 'typecheck' },
context
);
expect(
nodes[0][1].projects['my-app'].targets.typecheck.command
).toEqual(`tsc --noEmit`);
expect(nodes[0][1].projects['my-app'].targets.typecheck.metadata)
.toMatchInlineSnapshot(`
{
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
}
`);
expect(
nodes[0][1].projects['my-app'].targets.typecheck.dependsOn
).toBeUndefined();
expect(
nodes[0][1].projects['my-app'].targets.typecheck.syncGenerators
).toBeUndefined();
});
it('should infer typecheck with --build flag when using TS solution setup', async () => {
(isUsingTsSolutionSetup as jest.Mock).mockReturnValue(true);
tempFs.createFileSync(
'my-app/package.json',
JSON.stringify('{"name": "my-app"}')
);
const nodes = await createNodesFunction(
['my-app/remix.config.cjs'],
{ typecheckTargetName: 'typecheck' },
context
);
expect(
nodes[0][1].projects['my-app'].targets.typecheck.command
).toEqual(`tsc --build --emitDeclarationOnly`);
expect(nodes[0][1].projects['my-app'].targets.typecheck.metadata)
.toMatchInlineSnapshot(`
{
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
}
`);
expect(
nodes[0][1].projects['my-app'].targets.typecheck.dependsOn
).toEqual([`^typecheck`]);
expect(
nodes[0][1].projects['my-app'].targets.typecheck.syncGenerators
).toEqual(['@nx/js:typescript-sync']);
});
});
});

View File

@ -24,6 +24,7 @@ import { dirname, join } from 'path';
import { existsSync, readdirSync, readFileSync } from 'fs';
import { loadViteDynamicImport } from '../utils/executor-utils';
import { addBuildAndWatchDepsTargets } from '@nx/js/src/plugins/typescript/util';
import { isUsingTsSolutionSetup as _isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
export interface RemixPluginOptions {
buildTargetName?: string;
@ -75,7 +76,13 @@ export const createNodesV2: CreateNodesV2<RemixPluginOptions> = [
try {
return await createNodesFromFiles(
(configFile, options, context) =>
createNodesInternal(configFile, options, context, targetsCache),
createNodesInternal(
configFile,
options,
context,
targetsCache,
_isUsingTsSolutionSetup()
),
configFilePaths,
options,
context
@ -92,7 +99,13 @@ export const createNodes: CreateNodes<RemixPluginOptions> = [
logger.warn(
'`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.'
);
return createNodesInternal(configFilePath, options, context, {});
return createNodesInternal(
configFilePath,
options,
context,
{},
_isUsingTsSolutionSetup()
);
},
];
@ -100,7 +113,8 @@ async function createNodesInternal(
configFilePath: string,
options: RemixPluginOptions,
context: CreateNodesContext,
targetsCache: Record<string, RemixTargets>
targetsCache: Record<string, RemixTargets>,
isUsingTsSolutionSetup: boolean
) {
const projectRoot = dirname(configFilePath);
const fullyQualifiedProjectRoot = join(context.workspaceRoot, projectRoot);
@ -125,9 +139,12 @@ async function createNodesInternal(
}
const hash =
(await calculateHashForCreateNodes(projectRoot, options, context, [
getLockFileName(detectPackageManager(context.workspaceRoot)),
])) + configFilePath;
(await calculateHashForCreateNodes(
projectRoot,
{ ...options, isUsingTsSolutionSetup },
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
)) + configFilePath;
targetsCache[hash] ??= await buildRemixTargets(
configFilePath,
@ -135,7 +152,8 @@ async function createNodesInternal(
options,
context,
siblingFiles,
remixCompiler
remixCompiler,
isUsingTsSolutionSetup
);
const { targets, metadata } = targetsCache[hash];
@ -159,7 +177,8 @@ async function buildRemixTargets(
options: RemixPluginOptions,
context: CreateNodesContext,
siblingFiles: string[],
remixCompiler: RemixCompiler
remixCompiler: RemixCompiler,
isUsingTsSolutionSetup: boolean
) {
const namedInputs = getNamedInputs(projectRoot, context);
const { buildDirectory, assetsBuildDirectory, serverBuildPath } =
@ -177,36 +196,43 @@ async function buildRemixTargets(
buildDirectory,
assetsBuildDirectory,
namedInputs,
remixCompiler
remixCompiler,
isUsingTsSolutionSetup
);
targets[options.devTargetName] = devTarget(
serverBuildPath,
projectRoot,
remixCompiler
remixCompiler,
isUsingTsSolutionSetup
);
targets[options.startTargetName] = startTarget(
projectRoot,
serverBuildPath,
options.buildTargetName,
remixCompiler
remixCompiler,
isUsingTsSolutionSetup
);
// TODO(colum): Remove for Nx 21
targets[options.staticServeTargetName] = startTarget(
projectRoot,
serverBuildPath,
options.buildTargetName,
remixCompiler
remixCompiler,
isUsingTsSolutionSetup
);
targets[options.serveStaticTargetName] = startTarget(
projectRoot,
serverBuildPath,
options.buildTargetName,
remixCompiler
remixCompiler,
isUsingTsSolutionSetup
);
targets[options.typecheckTargetName] = typecheckTarget(
options.typecheckTargetName,
projectRoot,
namedInputs,
siblingFiles
siblingFiles,
isUsingTsSolutionSetup
);
addBuildAndWatchDepsTargets(
@ -226,7 +252,8 @@ function buildTarget(
buildDirectory: string,
assetsBuildDirectory: string,
namedInputs: { [inputName: string]: any[] },
remixCompiler: RemixCompiler
remixCompiler: RemixCompiler,
isUsingTsSolutionSetup: boolean
): TargetConfiguration {
const serverBuildOutputPath =
projectRoot === '.'
@ -247,7 +274,7 @@ function buildTarget(
]
: [serverBuildOutputPath, assetsBuildOutputPath];
return {
const buildTarget: TargetConfiguration = {
cache: true,
dependsOn: [`^${buildTargetName}`],
inputs: [
@ -263,27 +290,41 @@ function buildTarget(
: 'remix build',
options: { cwd: projectRoot },
};
if (isUsingTsSolutionSetup) {
buildTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return buildTarget;
}
function devTarget(
serverBuildPath: string,
projectRoot: string,
remixCompiler: RemixCompiler
remixCompiler: RemixCompiler,
isUsingTsSolutionSetup: boolean
): TargetConfiguration {
return {
const devTarget: TargetConfiguration = {
command:
remixCompiler === RemixCompiler.IsVte
? 'remix vite:dev'
: 'remix dev --manual',
options: { cwd: projectRoot },
};
if (isUsingTsSolutionSetup) {
devTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return devTarget;
}
function startTarget(
projectRoot: string,
serverBuildPath: string,
buildTargetName: string,
remixCompiler: RemixCompiler
remixCompiler: RemixCompiler,
isUsingTsSolutionSetup: boolean
): TargetConfiguration {
let serverPath = serverBuildPath;
if (remixCompiler === RemixCompiler.IsVte) {
@ -292,26 +333,30 @@ function startTarget(
}
}
return {
const startTarget: TargetConfiguration = {
dependsOn: [buildTargetName],
command: `remix-serve ${serverPath}`,
options: {
cwd: projectRoot,
},
};
if (isUsingTsSolutionSetup) {
startTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return startTarget;
}
function typecheckTarget(
typecheckTargetName: string,
projectRoot: string,
namedInputs: { [inputName: string]: any[] },
siblingFiles: string[]
siblingFiles: string[],
isUsingTsSolutionSetup: boolean
): TargetConfiguration {
const hasTsConfigAppJson = siblingFiles.includes('tsconfig.app.json');
const command = `tsc${
hasTsConfigAppJson ? ` --project tsconfig.app.json` : ``
}`;
return {
command,
const typecheckTarget: TargetConfiguration = {
cache: true,
inputs: [
...('production' in namedInputs
@ -319,10 +364,34 @@ function typecheckTarget(
: ['default', '^default']),
{ externalDependencies: ['typescript'] },
],
command: isUsingTsSolutionSetup
? `tsc --build --emitDeclarationOnly`
: `tsc${hasTsConfigAppJson ? ` -p tsconfig.app.json` : ``} --noEmit`,
options: {
cwd: projectRoot,
},
metadata: {
description: `Runs type-checking for the project.`,
technologies: ['typescript'],
help: {
command: isUsingTsSolutionSetup
? `${pmc.exec} tsc --build --help`
: `${pmc.exec} tsc${
hasTsConfigAppJson ? ` -p tsconfig.app.json` : ``
} --help`,
example: isUsingTsSolutionSetup
? { args: ['--force'] }
: { options: { noEmit: true } },
},
},
};
if (isUsingTsSolutionSetup) {
typecheckTarget.dependsOn = [`^${typecheckTargetName}`];
typecheckTarget.syncGenerators = ['@nx/js:typescript-sync'];
}
return typecheckTarget;
}
async function getBuildPaths(

View File

@ -1,6 +1,7 @@
import { type CreateNodesContext } from '@nx/devkit';
import { createNodesV2 } from './plugin';
import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { createNodesV2 } from './plugin';
jest.mock('@rsbuild/core', () => ({
...jest.requireActual('@rsbuild/core'),
@ -12,7 +13,7 @@ jest.mock('@rsbuild/core', () => ({
jest.mock('@nx/js/src/utils/typescript/ts-solution-setup', () => ({
...jest.requireActual('@nx/js/src/utils/typescript/ts-solution-setup'),
isUsingTsSolutionSetup: jest.fn().mockReturnValue(false),
isUsingTsSolutionSetup: jest.fn(),
}));
describe('@nx/rsbuild', () => {
@ -21,6 +22,7 @@ describe('@nx/rsbuild', () => {
let tempFs: TempFs;
beforeEach(() => {
(isUsingTsSolutionSetup as jest.Mock).mockReturnValue(false);
tempFs = new TempFs('rsbuild-test');
context = {
configFiles: [],
@ -154,4 +156,79 @@ describe('@nx/rsbuild', () => {
]
`);
});
it('should infer typecheck with -p flag when not using TS solution setup', async () => {
tempFs.createFileSync('my-app/tsconfig.json', `{}`);
const nodes = await createNodesFunction(
['my-app/rsbuild.config.ts'],
{ typecheckTargetName: 'typecheck' },
context
);
expect(nodes[0][1].projects['my-app'].targets.typecheck.command).toEqual(
`tsc -p tsconfig.json --noEmit`
);
expect(nodes[0][1].projects['my-app'].targets.typecheck.metadata)
.toMatchInlineSnapshot(`
{
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc -p tsconfig.json --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
}
`);
expect(
nodes[0][1].projects['my-app'].targets.typecheck.dependsOn
).toBeUndefined();
expect(
nodes[0][1].projects['my-app'].targets.typecheck.syncGenerators
).toBeUndefined();
});
it('should infer typecheck with --build flag when using TS solution setup', async () => {
(isUsingTsSolutionSetup as jest.Mock).mockReturnValue(true);
tempFs.createFileSync('my-app/tsconfig.json', `{}`);
const nodes = await createNodesFunction(
['my-app/rsbuild.config.ts'],
{ typecheckTargetName: 'typecheck' },
context
);
expect(nodes[0][1].projects['my-app'].targets.typecheck.command).toEqual(
`tsc --build --emitDeclarationOnly`
);
expect(nodes[0][1].projects['my-app'].targets.typecheck.metadata)
.toMatchInlineSnapshot(`
{
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
}
`);
expect(nodes[0][1].projects['my-app'].targets.typecheck.dependsOn).toEqual([
`^typecheck`,
]);
expect(
nodes[0][1].projects['my-app'].targets.typecheck.syncGenerators
).toEqual(['@nx/js:typescript-sync']);
});
});

View File

@ -102,7 +102,7 @@ async function createNodesInternal(
const normalizedOptions = normalizeOptions(options);
const hash = await calculateHashForCreateNodes(
projectRoot,
normalizedOptions,
{ ...normalizedOptions, isUsingTsSolutionSetup },
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
);
@ -224,21 +224,31 @@ async function createRsbuildTargets(
{ externalDependencies: ['typescript'] },
],
command: isUsingTsSolutionSetup
? `tsc --build --emitDeclarationOnly --pretty --verbose`
: `tsc --noEmit -p ${tsConfigToUse}`,
? `tsc --build --emitDeclarationOnly`
: `tsc -p ${tsConfigToUse} --noEmit`,
options: { cwd: joinPathFragments(projectRoot) },
metadata: {
description: `Run Typechecking`,
description: `Runs type-checking for the project.`,
technologies: ['typescript'],
help: {
command: `${pmc.exec} tsc --help -p ${tsConfigToUse}`,
example: {
options: {
noEmit: true,
},
},
command: isUsingTsSolutionSetup
? `${pmc.exec} tsc --build --help`
: `${pmc.exec} tsc -p ${tsConfigToUse} --help`,
example: isUsingTsSolutionSetup
? { args: ['--force'] }
: { options: { noEmit: true } },
},
},
};
if (isUsingTsSolutionSetup) {
targets[options.typecheckTargetName].dependsOn = [
`^${options.typecheckTargetName}`,
];
targets[options.typecheckTargetName].syncGenerators = [
'@nx/js:typescript-sync',
];
}
}
addBuildAndWatchDepsTargets(

View File

@ -65,15 +65,18 @@ exports[`@nx/vite/plugin root project should create nodes 1`] = `
},
],
"metadata": {
"description": "Run Typechecking",
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help -p tsconfig.lib.json",
"command": "npx tsc -p tsconfig.lib.json --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": ".",

View File

@ -65,15 +65,18 @@ exports[`@nx/vite/plugin with test node root project should create nodes - with
},
],
"metadata": {
"description": "Run Typechecking",
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --help -p tsconfig.lib.json",
"command": "npx tsc -p tsconfig.lib.json --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
},
"options": {
"cwd": ".",

View File

@ -97,6 +97,49 @@ describe('@nx/vite/plugin', () => {
expect(targets?.['serve-input'].command).toMatch(/vite/);
});
it('should infer typecheck with -p flag when not using TS solution setup', async () => {
tempFs.createFileSync('tsconfig.json', '');
const nodes = await createNodesFunction(
['vite.config.ts'],
{
buildTargetName: 'build',
serveTargetName: 'serve',
previewTargetName: 'preview',
testTargetName: 'test',
serveStaticTargetName: 'serve-static',
},
context
);
expect(nodes[0][1].projects['.'].targets.typecheck.command).toEqual(
`tsc --noEmit -p tsconfig.json`
);
expect(nodes[0][1].projects['.'].targets.typecheck.metadata)
.toMatchInlineSnapshot(`
{
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc -p tsconfig.json --help",
"example": {
"options": {
"noEmit": true,
},
},
},
"technologies": [
"typescript",
],
}
`);
expect(
nodes[0][1].projects['.'].targets.typecheck.dependsOn
).toBeUndefined();
expect(
nodes[0][1].projects['.'].targets.typecheck.syncGenerators
).toBeUndefined();
});
it('should infer typecheck with --build flag when using TS solution setup', async () => {
(isUsingTsSolutionSetup as jest.Mock).mockReturnValue(true);
tempFs.createFileSync('tsconfig.json', '');
@ -114,8 +157,31 @@ describe('@nx/vite/plugin', () => {
);
expect(nodes[0][1].projects['.'].targets.typecheck.command).toEqual(
`tsc --build --emitDeclarationOnly --pretty --verbose`
`tsc --build --emitDeclarationOnly`
);
expect(nodes[0][1].projects['.'].targets.typecheck.metadata)
.toMatchInlineSnapshot(`
{
"description": "Runs type-checking for the project.",
"help": {
"command": "npx tsc --build --help",
"example": {
"args": [
"--force",
],
},
},
"technologies": [
"typescript",
],
}
`);
expect(nodes[0][1].projects['.'].targets.typecheck.dependsOn).toEqual([
`^typecheck`,
]);
expect(
nodes[0][1].projects['.'].targets.typecheck.syncGenerators
).toEqual(['@nx/js:typescript-sync']);
});
it('should infer the sync generator when using TS solution setup', async () => {

View File

@ -133,7 +133,7 @@ async function createNodesInternal(
const hash =
(await calculateHashForCreateNodes(
projectRoot,
normalizedOptions,
{ ...normalizedOptions, isUsingTsSolutionSetup },
context,
[getLockFileName(detectPackageManager(context.workspaceRoot))]
)) + configFilePath;
@ -259,23 +259,27 @@ async function buildViteTargets(
{ externalDependencies: ['typescript'] },
],
command: isUsingTsSolutionSetup
? `tsc --build --emitDeclarationOnly --pretty --verbose`
? `tsc --build --emitDeclarationOnly`
: `tsc --noEmit -p ${tsConfigToUse}`,
options: { cwd: joinPathFragments(projectRoot) },
metadata: {
description: `Run Typechecking`,
description: `Runs type-checking for the project.`,
technologies: ['typescript'],
help: {
command: `${pmc.exec} tsc --help -p ${tsConfigToUse}`,
example: {
options: {
noEmit: true,
},
},
command: isUsingTsSolutionSetup
? `${pmc.exec} tsc --build --help`
: `${pmc.exec} tsc -p ${tsConfigToUse} --help`,
example: isUsingTsSolutionSetup
? { args: ['--force'] }
: { options: { noEmit: true } },
},
},
};
if (isUsingTsSolutionSetup) {
targets[options.typecheckTargetName].dependsOn = [
`^${options.typecheckTargetName}`,
];
targets[options.typecheckTargetName].syncGenerators = [
'@nx/js:typescript-sync',
];