Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e886dc76e2 | |||
| e7375813ba | |||
| 2fcf807bb6 | |||
| 9612d9b394 | |||
| 6a839434d0 | |||
| b20b8550e2 | |||
| 371ba7a21c | |||
| bd9e749ffc | |||
| 928273940d | |||
| 12aa582135 | |||
| 2093cccb61 | |||
| d36f0552b1 | |||
| eba235b7c5 | |||
| f8a4eaaafc | |||
| 7b7f1e7f72 | |||
| f525ba37ee | |||
| 5e8f9f4b1e | |||
| 13aa3be1f0 | |||
| f9a615bb04 | |||
| 1a0e65a3d3 | |||
| 25263f72d8 | |||
| 9e448fe341 | |||
| 802243e410 | |||
| 0c480fd94d | |||
| b1a8917b8d | |||
| 0e15beafb2 | |||
| 66090ca3c9 | |||
| 97ce47aa69 | |||
| b39cafaf86 | |||
| ea28f16faf | |||
| 0d04949ffd | |||
| ed22afecef | |||
| a496ef9144 | |||
| 699af690b7 |
+18
-10
@@ -5,7 +5,6 @@ version: 2.1
|
||||
# -------------------------
|
||||
orbs:
|
||||
nx: nrwl/nx@1.6.1
|
||||
node: circleci/node@5.1.0
|
||||
rust: circleci/rust@1.6.0
|
||||
browser-tools: circleci/browser-tools@1.4.0
|
||||
|
||||
@@ -20,14 +19,14 @@ executors:
|
||||
linux:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: cimg/rust:1.66.1-browsers
|
||||
- image: cimg/rust:1.70.0-browsers
|
||||
resource_class: medium+
|
||||
|
||||
macos:
|
||||
<<: *defaults
|
||||
resource_class: macos.x86.medium.gen2
|
||||
macos:
|
||||
xcode: &_XCODE_VERSION '13.0.0'
|
||||
xcode: '14.2.0'
|
||||
|
||||
# -------------------------
|
||||
# COMMANDS
|
||||
@@ -42,10 +41,22 @@ commands:
|
||||
name: Restore pnpm Package Cache
|
||||
keys:
|
||||
- node-deps-{{ arch }}-v3-{{ checksum "pnpm-lock.yaml" }}
|
||||
- run:
|
||||
name: Install pnpm package manager
|
||||
command: |
|
||||
npm install -g @pnpm/exe@8.3.1
|
||||
- when:
|
||||
condition:
|
||||
equal: [<< parameters.os >>, linux]
|
||||
steps:
|
||||
- run:
|
||||
name: Install pnpm package manager (linux)
|
||||
command: |
|
||||
npm install --prefix=$HOME/.local -g @pnpm/exe@8.3.1
|
||||
- when:
|
||||
condition:
|
||||
equal: [<< parameters.os >>, macos]
|
||||
steps:
|
||||
- run:
|
||||
name: Install pnpm package manager (macos)
|
||||
command: |
|
||||
npm install -g @pnpm/exe@8.3.1
|
||||
- run:
|
||||
name: Install Dependencies
|
||||
command: |
|
||||
@@ -95,9 +106,6 @@ commands:
|
||||
sudo apt-get install -y ca-certificates lsof
|
||||
- browser-tools/install-chrome
|
||||
- browser-tools/install-chromedriver
|
||||
- node/install:
|
||||
# Use LTS version
|
||||
node-version: ''
|
||||
- run-pnpm-install:
|
||||
os: << parameters.os >>
|
||||
|
||||
|
||||
Generated
-43
@@ -844,15 +844,6 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "jsonc-parser"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b56a20e76235284255a09fcd1f45cf55d3c524ea657ebd3854735925c57743d"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.7"
|
||||
@@ -1171,13 +1162,10 @@ dependencies = [
|
||||
"ignore",
|
||||
"ignore-files",
|
||||
"itertools",
|
||||
"jsonc-parser",
|
||||
"napi",
|
||||
"napi-build",
|
||||
"napi-derive",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -1447,12 +1435,6 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
@@ -1479,31 +1461,6 @@ name = "serde"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.152"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1_smol"
|
||||
|
||||
@@ -1397,7 +1397,7 @@ execSync(`${getPackageManagerCommand().addDev} my-dev-package`);
|
||||
|
||||
### getPackageManagerVersion
|
||||
|
||||
▸ **getPackageManagerVersion**(`packageManager?`): `string`
|
||||
▸ **getPackageManagerVersion**(`packageManager?`, `cwd?`): `string`
|
||||
|
||||
Returns the version of the package manager used in the workspace.
|
||||
By default, the package manager is derived based on the lock file,
|
||||
@@ -1408,6 +1408,7 @@ but it can also be passed in explicitly.
|
||||
| Name | Type |
|
||||
| :--------------- | :------------------------------------------------------------------ |
|
||||
| `packageManager` | [`PackageManager`](../../devkit/documents/nx_devkit#packagemanager) |
|
||||
| `cwd` | `string` |
|
||||
|
||||
#### Returns
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository."
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode."
|
||||
},
|
||||
"pathToManifestFile": {
|
||||
"type": "string",
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository."
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode."
|
||||
},
|
||||
"verbose": {
|
||||
"type": "boolean",
|
||||
|
||||
@@ -1397,7 +1397,7 @@ execSync(`${getPackageManagerCommand().addDev} my-dev-package`);
|
||||
|
||||
### getPackageManagerVersion
|
||||
|
||||
▸ **getPackageManagerVersion**(`packageManager?`): `string`
|
||||
▸ **getPackageManagerVersion**(`packageManager?`, `cwd?`): `string`
|
||||
|
||||
Returns the version of the package manager used in the workspace.
|
||||
By default, the package manager is derived based on the lock file,
|
||||
@@ -1408,6 +1408,7 @@ but it can also be passed in explicitly.
|
||||
| Name | Type |
|
||||
| :--------------- | :------------------------------------------------------------------ |
|
||||
| `packageManager` | [`PackageManager`](../../devkit/documents/nx_devkit#packagemanager) |
|
||||
| `cwd` | `string` |
|
||||
|
||||
#### Returns
|
||||
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
"host": {
|
||||
"type": "string",
|
||||
"default": "localhost",
|
||||
"description": "The host to inspect the process on."
|
||||
"description": "The host to inspect the process on.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"default": 9229,
|
||||
"description": "The port to inspect the process on. Setting port to 0 will assign random free ports to all forked processes."
|
||||
"description": "The port to inspect the process on. Setting port to 0 will assign random free ports to all forked processes.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"inspect": {
|
||||
"oneOf": [
|
||||
@@ -41,33 +43,44 @@
|
||||
{ "type": "boolean" }
|
||||
],
|
||||
"description": "Ensures the app is starting with debugging.",
|
||||
"default": "inspect"
|
||||
"default": "inspect",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"runtimeArgs": {
|
||||
"type": "array",
|
||||
"description": "Extra args passed to the node process.",
|
||||
"default": [],
|
||||
"items": { "type": "string" }
|
||||
"items": { "type": "string" },
|
||||
"x-priority": "important"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
"description": "Extra args when starting the app.",
|
||||
"default": [],
|
||||
"items": { "type": "string" }
|
||||
"items": { "type": "string" },
|
||||
"x-priority": "important"
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"description": "Enable re-building when files change.",
|
||||
"default": true
|
||||
"default": true,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"debounce": {
|
||||
"type": "number",
|
||||
"description": "Delay in milliseconds to wait before restarting. Useful to batch multiple file changes events together. Set to zero (0) to disable.",
|
||||
"default": 500
|
||||
"default": 500,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"runBuildTargetDependencies": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run dependencies before running the build. Set this to true if the project does not build libraries from source (e.g. 'buildLibsFromSource: false').",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["buildTarget"],
|
||||
"examplesFile": "---\ntitle: JS Node executor examples\ndescription: This page contains examples for the @nx/js:node executor.\n---\n\nThe `@nx/js:node` executor runs the output of a build target. For example, an application uses esbuild ([`@nx/esbuild:esbuild`](/packages/esbuild/executors/esbuild)) to output the bundle to `dist/my-app` folder, which can then be executed by `@nx/js:node`.\n\n`project.json`:\n\n```json\n\"my-app\": {\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/js:node\",\n \"options\": {\n \"buildTarget\": \"my-app:build\"\n }\n },\n \"build\": {\n \"executor\": \"@nx/esbuild:esbuild\",\n \"options\": {\n \"main\": \"my-app/src/main.ts\",\n \"output\": [\"dist/my-app\"],\n //...\n }\n },\n }\n}\n```\n\n```bash\nnpx nx serve my-app\n```\n\n## Examples\n\n{% tabs %}\n{% tab label=\"Pass extra Node CLI arguments\" %}\n\nUsing `runtimeArgs`, you can pass arguments to the underlying `node` command. For example, if you want to set [`--no-warnings`](https://nodejs.org/api/cli.html#--no-warnings) to silence all Node warnings, then add the following to the `project.json` file.\n\n```json\n\"my-app\": {\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/js:node\",\n \"options\": {\n \"runtimeArgs\": [\"--no-warnings\"],\n //...\n },\n },\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"Run all task dependencies\" %}\n\nIf your application build depends on other tasks, and you want those tasks to also be executed, then set the `runBuildTargetDependencies` to `true`. For example, a library may have a task to generate GraphQL schemas, which is consume by the application. In this case, you want to run the generate task before building and running the application.\n\nThis option is also useful when the build consumes a library from its output, not its source. For example, if an executor that supports `buildLibsFromSource` option has it set to `false` (e.g. [`@nx/webpack:webpack`](/packages/webpack/executors/webpack)).\n\nNote that this option will increase the build time, so use it only when necessary.\n\n```json\n\"my-app\": {\n \"targets\": {\n \"serve\": {\n \"executor\": \"@nx/js:node\",\n \"options\": {\n \"runBuildTargetDependencies\": true,\n //...\n },\n },\n }\n}\n```\n\n{% /tab %}\n\n{% /tabs %}\n",
|
||||
"presets": []
|
||||
},
|
||||
"description": "Execute a Node application.",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"buildTarget": {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"skipRemotes": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"host": {
|
||||
|
||||
@@ -189,6 +189,15 @@ describe('js e2e', () => {
|
||||
`dist/libs/${lib}/src/lib/${lib}.js`,
|
||||
`dist/libs/${lib}/src/lib/${lib}.d.ts`
|
||||
);
|
||||
|
||||
// run a second time skipping the nx cache and with the outputs present
|
||||
const secondBatchBuildOutput = runCLI(
|
||||
`build ${parentLib} --skip-nx-cache`,
|
||||
{ env: { NX_BATCH_MODE: 'true' } }
|
||||
);
|
||||
expect(secondBatchBuildOutput).toContain(
|
||||
`Successfully ran target build for project ${parentLib} and 1 task it depends on`
|
||||
);
|
||||
}, 240_000);
|
||||
|
||||
it('should not create a `.babelrc` file when creating libs with js executors (--compiler=tsc)', () => {
|
||||
|
||||
@@ -452,6 +452,12 @@ describe('Linter', () => {
|
||||
];
|
||||
return json;
|
||||
});
|
||||
// Set this to false for now until the `@nx/js:lib` generator is updated to include ts/swc helpers by default.
|
||||
// TODO(jack): Remove this once the above is addressed in another PR.
|
||||
updateJson(`libs/${mylib}/tsconfig.lib.json`, (json) => {
|
||||
json.compilerOptions.importHelpers = false;
|
||||
return json;
|
||||
});
|
||||
updateJson(`libs/${mylib}/project.json`, (json) => {
|
||||
json.targets.lint.options.lintFilePatterns = [
|
||||
`libs/${mylib}/**/*.ts`,
|
||||
@@ -465,8 +471,7 @@ describe('Linter', () => {
|
||||
it('should report dependency check issues', () => {
|
||||
const rootPackageJson = readJson('package.json');
|
||||
const nxVersion = rootPackageJson.devDependencies.nx;
|
||||
const swcCoreVersion = rootPackageJson.devDependencies['@swc/core'];
|
||||
const swcHelpersVersion = rootPackageJson.dependencies['@swc/helpers'];
|
||||
const tslibVersion = rootPackageJson.devDependencies['tslib'];
|
||||
|
||||
let out = runCLI(`lint ${mylib}`, { silenceError: true });
|
||||
expect(out).toContain('All files pass linting');
|
||||
@@ -495,9 +500,6 @@ describe('Linter', () => {
|
||||
{
|
||||
"dependencies": {
|
||||
"@nx/devkit": "${nxVersion}",
|
||||
"@swc/core": "${swcCoreVersion}",
|
||||
"@swc/helpers": "${swcHelpersVersion}",
|
||||
"nx": "${nxVersion}",
|
||||
},
|
||||
"name": "@proj/${mylib}",
|
||||
"type": "commonjs",
|
||||
|
||||
@@ -5,9 +5,12 @@ import {
|
||||
readFile,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runCommandUntil,
|
||||
waitUntil,
|
||||
tmpProjPath,
|
||||
uniq,
|
||||
updateFile,
|
||||
updateProjectConfig,
|
||||
} from '@nx/e2e/utils';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
@@ -47,5 +50,54 @@ describe('Node Applications + webpack', () => {
|
||||
await runCLIAsync(`build ${app} --optimization`);
|
||||
const optimizedContent = readFile(`dist/apps/${app}/main.js`);
|
||||
expect(optimizedContent).toContain('console.log("foo "+"bar")');
|
||||
|
||||
// Test that serve can re-run dependency builds.
|
||||
const lib = uniq('nodelib');
|
||||
runCLI(`generate @nx/js:lib ${lib} --bundler=esbuild --no-interactive`);
|
||||
|
||||
updateProjectConfig(app, (config) => {
|
||||
// Since we read from lib from dist, we should re-build it when lib changes.
|
||||
config.targets.build.options.buildLibsFromSource = false;
|
||||
config.targets.serve.options.runBuildTargetDependencies = true;
|
||||
return config;
|
||||
});
|
||||
|
||||
updateFile(
|
||||
`apps/${app}/src/main.ts`,
|
||||
`
|
||||
import { ${lib} } from '@proj/${lib}';
|
||||
console.log('Hello ' + ${lib}());
|
||||
`
|
||||
);
|
||||
|
||||
const serveProcess = await runCommandUntil(
|
||||
`serve ${app} --watch --runBuildTargetDependencies`,
|
||||
(output) => {
|
||||
return output.includes(`Hello`);
|
||||
}
|
||||
);
|
||||
|
||||
// Update library source and check that it triggers rebuild.
|
||||
const terminalOutputs: string[] = [];
|
||||
serveProcess.stdout.on('data', (chunk) => {
|
||||
const data = chunk.toString();
|
||||
terminalOutputs.push(data);
|
||||
});
|
||||
|
||||
updateFile(
|
||||
`libs/${lib}/src/index.ts`,
|
||||
`export function ${lib}() { return 'should rebuild lib'; }`
|
||||
);
|
||||
|
||||
await waitUntil(
|
||||
() => {
|
||||
return terminalOutputs.some((output) =>
|
||||
output.includes(`should rebuild lib`)
|
||||
);
|
||||
},
|
||||
{ timeout: 30_000, ms: 200 }
|
||||
);
|
||||
|
||||
serveProcess.kill();
|
||||
}, 300_000);
|
||||
});
|
||||
|
||||
@@ -9,10 +9,12 @@ import {
|
||||
getSelectedPackageManager,
|
||||
packageManagerLockFile,
|
||||
readJson,
|
||||
runCommand,
|
||||
runCreateWorkspace,
|
||||
uniq,
|
||||
} from '@nx/e2e/utils';
|
||||
import { existsSync, mkdirSync } from 'fs-extra';
|
||||
import { readFileSync } from 'fs';
|
||||
import { existsSync, mkdirSync, rmSync } from 'fs-extra';
|
||||
|
||||
describe('create-nx-workspace', () => {
|
||||
const packageManager = getSelectedPackageManager() || 'pnpm';
|
||||
@@ -414,7 +416,7 @@ describe('create-nx-workspace', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('create-nx-workspace custom parent folder', () => {
|
||||
describe('create-nx-workspace parent folder', () => {
|
||||
const tmpDir = `${e2eCwd}/${uniq('with space')}`;
|
||||
const wsName = uniq('parent');
|
||||
const packageManager = getSelectedPackageManager() || 'pnpm';
|
||||
@@ -433,3 +435,59 @@ describe('create-nx-workspace custom parent folder', () => {
|
||||
expect(existsSync(`${tmpDir}/${wsName}/package.json`)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('create-nx-workspace yarn berry', () => {
|
||||
const tmpDir = `${e2eCwd}/${uniq('yarn-berry')}`;
|
||||
let wsName: string;
|
||||
|
||||
beforeAll(() => {
|
||||
mkdirSync(tmpDir, { recursive: true });
|
||||
runCommand('corepack prepare yarn@stable --activate', { cwd: tmpDir });
|
||||
runCommand('yarn set version stable', { cwd: tmpDir });
|
||||
// previous command creates a package.json file which we don't want
|
||||
rmSync(`${tmpDir}/package.json`);
|
||||
process.env.YARN_ENABLE_IMMUTABLE_INSTALLS = 'false';
|
||||
});
|
||||
|
||||
afterEach(() => cleanupProject({ cwd: `${tmpDir}/${wsName}` }));
|
||||
|
||||
it('should create a workspace with yarn berry', () => {
|
||||
wsName = uniq('apps');
|
||||
|
||||
runCreateWorkspace(wsName, {
|
||||
preset: 'apps',
|
||||
packageManager: 'yarn',
|
||||
cwd: tmpDir,
|
||||
});
|
||||
|
||||
expect(existsSync(`${tmpDir}/${wsName}/.yarnrc.yml`)).toBeTruthy();
|
||||
expect(
|
||||
readFileSync(`${tmpDir}/${wsName}/.yarnrc.yml`, { encoding: 'utf-8' })
|
||||
).toMatchInlineSnapshot(`
|
||||
"nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.1.cjs
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should create a js workspace with yarn berry', () => {
|
||||
wsName = uniq('ts');
|
||||
|
||||
runCreateWorkspace(wsName, {
|
||||
preset: 'ts',
|
||||
packageManager: 'yarn',
|
||||
cwd: tmpDir,
|
||||
});
|
||||
|
||||
expect(existsSync(`${tmpDir}/${wsName}/.yarnrc.yml`)).toBeTruthy();
|
||||
expect(
|
||||
readFileSync(`${tmpDir}/${wsName}/.yarnrc.yml`, { encoding: 'utf-8' })
|
||||
).toMatchInlineSnapshot(`
|
||||
"nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.1.cjs
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
|
||||
"version": "16.5.0",
|
||||
"version": "16.5.3",
|
||||
"granularPathspec": false,
|
||||
"command": {
|
||||
"publish": {
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
"webpack",
|
||||
"http-server",
|
||||
"magic-string",
|
||||
"enquirer"
|
||||
"enquirer",
|
||||
"find-cache-dir"
|
||||
],
|
||||
"keepLifecycleScripts": true
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
"ESLint",
|
||||
"CLI"
|
||||
],
|
||||
"main": "./src/index.js",
|
||||
"typings": "./src/index.d.ts",
|
||||
"main": "./index.js",
|
||||
"typings": "./index.d.ts",
|
||||
"author": "Victor Savkin",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"Tsc",
|
||||
"CLI"
|
||||
],
|
||||
"main": "src/index.js",
|
||||
"typings": "src/index.d.ts",
|
||||
"main": "./index.js",
|
||||
"typings": "./index.d.ts",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nrwl/nx/issues"
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
"webpack",
|
||||
"http-server",
|
||||
"magic-string",
|
||||
"enquirer"
|
||||
"enquirer",
|
||||
"find-cache-dir"
|
||||
],
|
||||
"keepLifecycleScripts": true
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@typescript-eslint/type-utils": "^5.36.1",
|
||||
"chalk": "^4.1.0",
|
||||
"chokidar": "^3.5.1",
|
||||
"find-cache-dir": "^3.3.2",
|
||||
"http-server": "^14.1.0",
|
||||
"ignore": "^5.0.4",
|
||||
"magic-string": "~0.26.2",
|
||||
|
||||
+12
-1
@@ -1,5 +1,10 @@
|
||||
import type { Schema } from './schema';
|
||||
import { readCachedProjectGraph, workspaceRoot, Workspaces } from '@nx/devkit';
|
||||
import {
|
||||
logger,
|
||||
readCachedProjectGraph,
|
||||
workspaceRoot,
|
||||
Workspaces,
|
||||
} from '@nx/devkit';
|
||||
import { scheduleTarget } from 'nx/src/adapter/ngcli-adapter';
|
||||
import { executeWebpackDevServerBuilder } from '../webpack-dev-server/webpack-dev-server.impl';
|
||||
import { readProjectsConfigurationFromProjectGraph } from 'nx/src/project-graph/project-graph';
|
||||
@@ -51,6 +56,12 @@ export function executeModuleFederationDevServerBuilder(
|
||||
const remotesToSkip = new Set(
|
||||
findMatchingProjects(options.skipRemotes, projectGraph.nodes) ?? []
|
||||
);
|
||||
|
||||
if (remotesToSkip.size > 0) {
|
||||
logger.info(
|
||||
`Remotes not served automatically: ${[...remotesToSkip].join(', ')}`
|
||||
);
|
||||
}
|
||||
const staticRemotes = getStaticRemotes(
|
||||
project,
|
||||
context,
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository."
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode."
|
||||
},
|
||||
"pathToManifestFile": {
|
||||
"type": "string",
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository."
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode."
|
||||
},
|
||||
"verbose": {
|
||||
"type": "boolean",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json';
|
||||
import { join } from 'path';
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { Remotes } from '@nx/devkit';
|
||||
import { logger, Remotes } from '@nx/devkit';
|
||||
|
||||
export function getDynamicRemotes(
|
||||
project: ProjectConfiguration,
|
||||
@@ -45,19 +45,26 @@ export function getDynamicRemotes(
|
||||
return [];
|
||||
}
|
||||
|
||||
const dynamicRemotes = Object.entries(parsedManifest)
|
||||
const allDynamicRemotes = Object.entries(parsedManifest)
|
||||
.map(([remoteName]) => remoteName)
|
||||
.filter((r) => !remotesToSkip.has(r));
|
||||
const invalidDynamicRemotes = dynamicRemotes.filter(
|
||||
(remote) => !workspaceProjects[remote]
|
||||
);
|
||||
if (invalidDynamicRemotes.length) {
|
||||
throw new Error(
|
||||
invalidDynamicRemotes.length === 1
|
||||
? `Invalid dynamic remote configured in "${pathToManifestFile}": ${invalidDynamicRemotes[0]}.`
|
||||
: `Invalid dynamic remotes configured in "${pathToManifestFile}": ${invalidDynamicRemotes.join(
|
||||
', '
|
||||
)}.`
|
||||
|
||||
const remotesNotInWorkspace: string[] = [];
|
||||
|
||||
const dynamicRemotes = allDynamicRemotes.filter((remote) => {
|
||||
if (!workspaceProjects[remote]) {
|
||||
remotesNotInWorkspace.push(remote);
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (remotesNotInWorkspace.length > 0) {
|
||||
logger.warn(
|
||||
`Skipping serving ${remotesNotInWorkspace.join(
|
||||
', '
|
||||
)} as they could not be found in the workspace. Ensure they are served correctly.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -89,22 +96,27 @@ export function getStaticRemotes(
|
||||
Array.isArray(mfeConfig.remotes) && mfeConfig.remotes.length > 0
|
||||
? mfeConfig.remotes
|
||||
: [];
|
||||
const staticRemotes = remotesConfig
|
||||
const allStaticRemotes = remotesConfig
|
||||
.map((remoteDefinition) =>
|
||||
Array.isArray(remoteDefinition) ? remoteDefinition[0] : remoteDefinition
|
||||
)
|
||||
.filter((r) => !remotesToSkip.has(r));
|
||||
const remotesNotInWorkspace: string[] = [];
|
||||
|
||||
const invalidStaticRemotes = staticRemotes.filter(
|
||||
(remote) => !workspaceProjects[remote]
|
||||
);
|
||||
if (invalidStaticRemotes.length) {
|
||||
throw new Error(
|
||||
invalidStaticRemotes.length === 1
|
||||
? `Invalid static remote configured in "${mfConfigPath}": ${invalidStaticRemotes[0]}.`
|
||||
: `Invalid static remotes configured in "${mfConfigPath}": ${invalidStaticRemotes.join(
|
||||
', '
|
||||
)}.`
|
||||
const staticRemotes = allStaticRemotes.filter((remote) => {
|
||||
if (!workspaceProjects[remote]) {
|
||||
remotesNotInWorkspace.push(remote);
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (remotesNotInWorkspace.length > 0) {
|
||||
logger.warn(
|
||||
`Skipping serving ${remotesNotInWorkspace.join(
|
||||
', '
|
||||
)} as they could not be found in the workspace. Ensure they are served correctly.`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ export async function createEmptyWorkspace<T extends CreateWorkspaceOptions>(
|
||||
/\s/.test(nxWorkspaceRoot) &&
|
||||
packageManager === 'npm'
|
||||
) {
|
||||
const pmVersion = +getPackageManagerVersion(packageManager).split('.')[0];
|
||||
const pmVersion = +getPackageManagerVersion(packageManager, tmpDir).split(
|
||||
'.'
|
||||
)[0];
|
||||
if (pmVersion < 7) {
|
||||
nxWorkspaceRoot = `\\"${nxWorkspaceRoot.slice(1, -1)}\\"`;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,10 @@ export async function createPreset<T extends CreateWorkspaceOptions>(
|
||||
/\s/.test(nxWorkspaceRoot) &&
|
||||
packageManager === 'npm'
|
||||
) {
|
||||
const pmVersion = +getPackageManagerVersion(packageManager).split('.')[0];
|
||||
const pmVersion = +getPackageManagerVersion(
|
||||
packageManager,
|
||||
workingDir
|
||||
).split('.')[0];
|
||||
if (pmVersion < 7) {
|
||||
nxWorkspaceRoot = `\\"${nxWorkspaceRoot.slice(1, -1)}\\"`;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function createSandbox(packageManager: PackageManager) {
|
||||
`Installing dependencies with ${packageManager}`
|
||||
).start();
|
||||
|
||||
const { install } = getPackageManagerCommand(packageManager);
|
||||
const { install, preInstall } = getPackageManagerCommand(packageManager);
|
||||
|
||||
const tmpDir = dirSync().name;
|
||||
try {
|
||||
@@ -39,6 +39,10 @@ export async function createSandbox(packageManager: PackageManager) {
|
||||
);
|
||||
generatePackageManagerFiles(tmpDir, packageManager);
|
||||
|
||||
if (preInstall) {
|
||||
await execAndWait(preInstall, tmpDir);
|
||||
}
|
||||
|
||||
await execAndWait(install, tmpDir);
|
||||
|
||||
installSpinner.succeed();
|
||||
|
||||
@@ -60,7 +60,7 @@ export async function recordStat(opts: {
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.log(`Record stat. Major: ${major}`);
|
||||
}
|
||||
if (major < 10 || major > 15) return; // test version, skip it
|
||||
if (major < 10 || major > 16) return; // test version, skip it
|
||||
const axios = require('axios');
|
||||
await (axios['default'] ?? axios)
|
||||
.create({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { execSync } from 'child_process';
|
||||
import { existsSync, writeFileSync } from 'fs';
|
||||
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
/*
|
||||
@@ -36,6 +36,7 @@ export function getPackageManagerCommand(
|
||||
): {
|
||||
install: string;
|
||||
exec: string;
|
||||
preInstall?: string;
|
||||
} {
|
||||
const [pmMajor, pmMinor] =
|
||||
getPackageManagerVersion(packageManager).split('.');
|
||||
@@ -45,10 +46,14 @@ export function getPackageManagerCommand(
|
||||
const useBerry = +pmMajor >= 2;
|
||||
const installCommand = 'yarn install --silent';
|
||||
return {
|
||||
preInstall: useBerry
|
||||
? 'yarn set version stable'
|
||||
: 'yarn set version classic',
|
||||
install: useBerry
|
||||
? installCommand
|
||||
: `${installCommand} --ignore-scripts`,
|
||||
exec: 'yarn',
|
||||
// using npx is necessary to avoid yarn classic manipulating the version detection when using berry
|
||||
exec: useBerry ? 'npx' : 'yarn',
|
||||
};
|
||||
|
||||
case 'pnpm':
|
||||
@@ -81,15 +86,28 @@ export function generatePackageManagerFiles(
|
||||
join(root, '.yarnrc.yml'),
|
||||
'nodeLinker: node-modules\nenableScripts: false'
|
||||
);
|
||||
// avoids errors when using nested yarn projects
|
||||
writeFileSync(join(root, 'yarn.lock'), '');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const pmVersionCache = new Map<PackageManager, string>();
|
||||
|
||||
export function getPackageManagerVersion(
|
||||
packageManager: PackageManager
|
||||
packageManager: PackageManager,
|
||||
cwd = process.cwd()
|
||||
): string {
|
||||
return execSync(`${packageManager} --version`).toString('utf-8').trim();
|
||||
if (pmVersionCache.has(packageManager)) {
|
||||
return pmVersionCache.get(packageManager) as string;
|
||||
}
|
||||
const version = execSync(`${packageManager} --version`, {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
pmVersionCache.set(packageManager, version);
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,5 +5,5 @@ export const cypressViteDevServerVersion = '^2.2.1';
|
||||
export const cypressVersion = '^12.16.0';
|
||||
export const cypressWebpackVersion = '^2.0.0';
|
||||
export const webpackHttpPluginVersion = '^5.5.0';
|
||||
export const viteVersion = '^4.3.4';
|
||||
export const viteVersion = '~4.3.9';
|
||||
export const htmlWebpackPluginVersion = '^5.5.0';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { execSync } from 'child_process';
|
||||
import { type ExecSyncOptions, execSync } from 'child_process';
|
||||
import { join } from 'path';
|
||||
import { requireNx } from '../../nx';
|
||||
|
||||
@@ -38,9 +38,13 @@ export function installPackagesTask(
|
||||
if (storedPackageJsonValue != packageJsonValue || alwaysRun) {
|
||||
global['__packageJsonInstallCache__'] = packageJsonValue;
|
||||
const pmc = getPackageManagerCommand(packageManager);
|
||||
execSync(pmc.install, {
|
||||
const execSyncOptions: ExecSyncOptions = {
|
||||
cwd: join(tree.root, cwd),
|
||||
stdio: process.env.NX_GENERATE_QUIET === 'true' ? 'ignore' : 'inherit',
|
||||
});
|
||||
};
|
||||
if (pmc.preInstall) {
|
||||
execSync(pmc.preInstall, execSyncOptions);
|
||||
}
|
||||
execSync(pmc.install, execSyncOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,12 +464,20 @@ export function ensurePackage<T extends any = any>(
|
||||
|
||||
console.log(`Fetching ${pkg}...`);
|
||||
const packageManager = detectPackageManager();
|
||||
const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true';
|
||||
const preInstallCommand = getPackageManagerCommand(packageManager).preInstall;
|
||||
if (preInstallCommand) {
|
||||
// ensure package.json and repo in tmp folder is set to a proper package manager state
|
||||
execSync(preInstallCommand, {
|
||||
cwd: tempDir,
|
||||
stdio: isVerbose ? 'inherit' : 'ignore',
|
||||
});
|
||||
}
|
||||
let addCommand = getPackageManagerCommand(packageManager).addDev;
|
||||
if (packageManager === 'pnpm') {
|
||||
addCommand = 'pnpm add -D'; // we need to ensure that we are not using workspace command
|
||||
}
|
||||
|
||||
const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true';
|
||||
execSync(`${addCommand} ${pkg}@${requiredVersion}`, {
|
||||
cwd: tempDir,
|
||||
stdio: isVerbose ? 'inherit' : 'ignore',
|
||||
|
||||
@@ -104,7 +104,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -134,6 +134,74 @@ describe('Dependency checks (eslint)', () => {
|
||||
expect(failures.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should exclude files not matching input of the build target', () => {
|
||||
const packageJson = {
|
||||
name: '@mycompany/liba',
|
||||
dependencies: {},
|
||||
};
|
||||
|
||||
const fileSys = {
|
||||
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||
'./libs/liba/src/index.ts': '',
|
||||
'./libs/liba/project.json': JSON.stringify(
|
||||
{
|
||||
name: 'liba',
|
||||
targets: {
|
||||
build: {
|
||||
command: 'tsc -p tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
),
|
||||
'./nx.json': JSON.stringify({
|
||||
targetDefaults: {
|
||||
build: {
|
||||
inputs: [
|
||||
'{projectRoot}/**/*',
|
||||
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
liba: {
|
||||
name: 'liba',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/liba',
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
externalNodes,
|
||||
dependencies: {
|
||||
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
liba: [
|
||||
createFile(`libs/liba/src/main.ts`, []),
|
||||
createFile(`libs/liba/src/main.spec.ts`, ['npm:external1']),
|
||||
createFile(`libs/liba/package.json`, []),
|
||||
],
|
||||
}
|
||||
);
|
||||
expect(failures.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should report missing dependencies section and fix it', () => {
|
||||
const packageJson = {
|
||||
name: '@mycompany/liba',
|
||||
@@ -148,7 +216,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -191,7 +259,6 @@ describe('Dependency checks (eslint)', () => {
|
||||
"{
|
||||
"name": "@mycompany/liba",
|
||||
"dependencies": {
|
||||
"external1": "~16.1.2"
|
||||
}
|
||||
}"
|
||||
`);
|
||||
@@ -211,7 +278,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ ignoredDependencies: ['external1'] },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -255,7 +322,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ ignoredDependencies: ['external1'] },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -299,7 +366,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ ignoredDependencies: ['external1'] },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -344,7 +411,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -416,7 +483,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -473,7 +540,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -534,7 +601,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ buildTargets: ['notbuild'] },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -590,7 +657,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ checkMissingDependencies: false },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -646,7 +713,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ ignoredDependencies: ['external2'] },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -706,7 +773,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -778,7 +845,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -857,7 +924,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -936,7 +1003,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1015,7 +1082,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ checkObsoleteDependencies: false },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1068,7 +1135,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ ignoredDependencies: ['unneeded'] },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1119,7 +1186,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1204,7 +1271,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ checkVersionMismatches: false },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1261,7 +1328,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
|
||||
const failures = runRule(
|
||||
{ ignoredDependencies: ['external1'] },
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1330,7 +1397,7 @@ describe('Dependency checks (eslint)', () => {
|
||||
include: ['**/*.ts'],
|
||||
};
|
||||
|
||||
const tsConfiogBaseJson = {
|
||||
const tsConfigBaseJson = {
|
||||
compilerOptions: {
|
||||
target: 'es2015',
|
||||
importHelpers: true,
|
||||
@@ -1353,15 +1420,15 @@ describe('Dependency checks (eslint)', () => {
|
||||
const fileSys = {
|
||||
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||
'./libs/liba/src/index.ts': '',
|
||||
'./libs/libb/tsconfig.json': JSON.stringify(tsConfigJson, null, 2),
|
||||
'./libs/liba/tsconfig.json': JSON.stringify(tsConfigJson, null, 2),
|
||||
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||
'./tsconfig.base.json': JSON.stringify(tsConfiogBaseJson, null, 2),
|
||||
'./tsconfig.base.json': JSON.stringify(tsConfigBaseJson, null, 2),
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1370,21 +1437,11 @@ describe('Dependency checks (eslint)', () => {
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/liba',
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
libb: {
|
||||
name: 'libb',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/libb',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nx/js:tsc',
|
||||
options: {
|
||||
tsConfig: 'libs/libb/tsconfig.json',
|
||||
tsConfig: 'libs/liba/tsconfig.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1393,26 +1450,20 @@ describe('Dependency checks (eslint)', () => {
|
||||
},
|
||||
externalNodes,
|
||||
dependencies: {
|
||||
liba: [
|
||||
{ source: 'liba', target: 'npm:external1', type: 'static' },
|
||||
{ source: 'liba', target: 'libb', type: 'static' },
|
||||
],
|
||||
libb: [{ source: 'libb', target: 'npm:external2', type: 'static' }],
|
||||
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
liba: [
|
||||
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
||||
createFile(`libs/liba/package.json`, ['npm:external1']),
|
||||
createFile(`libs/libb/src/main.ts`, ['npm:external2']),
|
||||
],
|
||||
}
|
||||
);
|
||||
expect(failures.length).toEqual(1);
|
||||
expect(failures[0].message).toMatchInlineSnapshot(`
|
||||
"The "liba" uses the following packages, but they are missing from the "dependencies":
|
||||
- tslib
|
||||
- external2"
|
||||
- tslib"
|
||||
`);
|
||||
expect(failures[0].line).toEqual(3);
|
||||
});
|
||||
@@ -1435,14 +1486,14 @@ it('should require swc if @nx/js:swc executor', () => {
|
||||
const fileSys = {
|
||||
'./libs/liba/package.json': JSON.stringify(packageJson, null, 2),
|
||||
'./libs/liba/src/index.ts': '',
|
||||
'./libs/libb/.swcrc': JSON.stringify(swcrc, null, 2),
|
||||
'./libs/liba/.swcrc': JSON.stringify(swcrc, null, 2),
|
||||
'./package.json': JSON.stringify(rootPackageJson, null, 2),
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
|
||||
const failures = runRule(
|
||||
{},
|
||||
`${process.cwd()}/proj/libs/liba/package.json`,
|
||||
`/root/libs/liba/package.json`,
|
||||
JSON.stringify(packageJson, null, 2),
|
||||
{
|
||||
nodes: {
|
||||
@@ -1451,22 +1502,10 @@ it('should require swc if @nx/js:swc executor', () => {
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/liba',
|
||||
targets: {
|
||||
build: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
libb: {
|
||||
name: 'libb',
|
||||
type: 'lib',
|
||||
data: {
|
||||
root: 'libs/libb',
|
||||
targets: {
|
||||
build: {
|
||||
executor: '@nx/js:swc',
|
||||
options: {
|
||||
tsConfig: 'libs/libb/tsconfig.json',
|
||||
},
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -1474,26 +1513,21 @@ it('should require swc if @nx/js:swc executor', () => {
|
||||
},
|
||||
externalNodes,
|
||||
dependencies: {
|
||||
liba: [
|
||||
{ source: 'liba', target: 'npm:external1', type: 'static' },
|
||||
{ source: 'liba', target: 'libb', type: 'static' },
|
||||
],
|
||||
libb: [],
|
||||
liba: [{ source: 'liba', target: 'npm:external1', type: 'static' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
liba: [
|
||||
createFile(`libs/liba/src/main.ts`, ['npm:external1']),
|
||||
createFile(`libs/liba/package.json`, ['npm:external1']),
|
||||
createFile(`libs/libb/src/main.ts`),
|
||||
],
|
||||
}
|
||||
);
|
||||
expect(failures.length).toEqual(1);
|
||||
expect(failures[0].message).toMatchInlineSnapshot(`
|
||||
"The "liba" uses the following packages, but they are missing from the "dependencies":
|
||||
- @swc/helpers"
|
||||
`);
|
||||
"The "liba" uses the following packages, but they are missing from the "dependencies":
|
||||
- @swc/helpers"
|
||||
`);
|
||||
expect(failures[0].line).toEqual(3);
|
||||
});
|
||||
|
||||
@@ -1518,7 +1552,6 @@ function runRule(
|
||||
projectGraph: ProjectGraph,
|
||||
projectFileMap: ProjectFileMap
|
||||
): Linter.LintMessage[] {
|
||||
globalThis.projectPath = `${process.cwd()}/proj`;
|
||||
globalThis.projectGraph = projectGraph;
|
||||
globalThis.projectFileMap = projectFileMap;
|
||||
globalThis.projectRootMappings = createProjectRootMappings(
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { join } from 'path';
|
||||
import { satisfies } from 'semver';
|
||||
import { AST } from 'jsonc-eslint-parser';
|
||||
import { normalizePath, workspaceRoot } from '@nx/devkit';
|
||||
import { type JSONLiteral } from 'jsonc-eslint-parser/lib/parser/ast';
|
||||
import {
|
||||
normalizePath,
|
||||
ProjectGraphProjectNode,
|
||||
FileData,
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
import { findNpmDependencies } from '@nx/js/src/utils/find-npm-dependencies';
|
||||
|
||||
import { createESLintRule } from '../utils/create-eslint-rule';
|
||||
import { readProjectGraph } from '../utils/project-graph-utils';
|
||||
import { findProject, getSourceFilePath } from '../utils/runtime-lint-utils';
|
||||
import { join } from 'path';
|
||||
import { findProjectsNpmDependencies } from '@nx/js/src/internal';
|
||||
import { satisfies } from 'semver';
|
||||
import { getHelperDependenciesFromProjectGraph } from '@nx/js';
|
||||
import {
|
||||
getAllDependencies,
|
||||
removePackageJsonFromFileMap,
|
||||
getPackageJson,
|
||||
} from '../utils/package-json-utils';
|
||||
import { JSONLiteral } from 'jsonc-eslint-parser/lib/parser/ast';
|
||||
|
||||
export type Options = [
|
||||
{
|
||||
@@ -21,6 +26,7 @@ export type Options = [
|
||||
checkVersionMismatches?: boolean;
|
||||
checkMissingPackageJson?: boolean;
|
||||
ignoredDependencies?: string[];
|
||||
includeTransitiveDependencies?: boolean;
|
||||
}
|
||||
];
|
||||
|
||||
@@ -50,6 +56,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
checkMissingDependencies: { type: 'boolean' },
|
||||
checkObsoleteDependencies: { type: 'boolean' },
|
||||
checkVersionMismatches: { type: 'boolean' },
|
||||
includeTransitiveDependencies: { type: 'boolean' },
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
@@ -68,6 +75,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
checkObsoleteDependencies: true,
|
||||
checkVersionMismatches: true,
|
||||
ignoredDependencies: [],
|
||||
includeTransitiveDependencies: false,
|
||||
},
|
||||
],
|
||||
create(
|
||||
@@ -79,6 +87,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
checkMissingDependencies,
|
||||
checkObsoleteDependencies,
|
||||
checkVersionMismatches,
|
||||
includeTransitiveDependencies,
|
||||
},
|
||||
]
|
||||
) {
|
||||
@@ -91,8 +100,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
return {};
|
||||
}
|
||||
|
||||
const projectPath = normalizePath(globalThis.projectPath || workspaceRoot);
|
||||
const sourceFilePath = getSourceFilePath(fileName, projectPath);
|
||||
const sourceFilePath = getSourceFilePath(fileName, workspaceRoot);
|
||||
const { projectGraph, projectRootMappings, projectFileMap } =
|
||||
readProjectGraph(RULE_NAME);
|
||||
|
||||
@@ -119,28 +127,19 @@ export default createESLintRule<Options, MessageIds>({
|
||||
return {};
|
||||
}
|
||||
|
||||
// gather helper dependencies for @nx/js executors
|
||||
const helperDependencies = getHelperDependenciesFromProjectGraph(
|
||||
workspaceRoot,
|
||||
sourceProject.name,
|
||||
projectGraph
|
||||
);
|
||||
const rootPackageJson = getPackageJson(join(workspaceRoot, 'package.json'));
|
||||
|
||||
// find all dependencies for the project
|
||||
const npmDeps = findProjectsNpmDependencies(
|
||||
const npmDependencies = findNpmDependencies(
|
||||
workspaceRoot,
|
||||
sourceProject,
|
||||
projectGraph,
|
||||
buildTarget,
|
||||
projectFileMap,
|
||||
buildTarget, // TODO: What if child library has a build target different from the parent?
|
||||
{
|
||||
helperDependencies: helperDependencies.map((dep) => dep.target),
|
||||
},
|
||||
removePackageJsonFromFileMap(projectFileMap)
|
||||
includeTransitiveDependencies,
|
||||
}
|
||||
);
|
||||
const projDependencies = {
|
||||
...npmDeps.dependencies,
|
||||
...npmDeps.peerDependencies,
|
||||
};
|
||||
const expectedDependencyNames = Object.keys(projDependencies);
|
||||
const expectedDependencyNames = Object.keys(npmDependencies);
|
||||
|
||||
const projPackageJsonPath = join(
|
||||
workspaceRoot,
|
||||
@@ -148,12 +147,12 @@ export default createESLintRule<Options, MessageIds>({
|
||||
'package.json'
|
||||
);
|
||||
|
||||
globalThis.projPackageJsonDeps ??= getAllDependencies(projPackageJsonPath);
|
||||
globalThis.projPackageJsonDeps ??= getAllDependencies(
|
||||
getPackageJson(projPackageJsonPath)
|
||||
);
|
||||
const projPackageJsonDeps: Record<string, string> =
|
||||
globalThis.projPackageJsonDeps;
|
||||
const rootPackageJsonDeps = getAllDependencies(
|
||||
join(workspaceRoot, 'package.json')
|
||||
);
|
||||
const rootPackageJsonDeps = getAllDependencies(rootPackageJson);
|
||||
|
||||
function validateMissingDependencies(node: AST.JSONProperty) {
|
||||
if (!checkMissingDependencies) {
|
||||
@@ -175,7 +174,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
fix(fixer) {
|
||||
missingDeps.forEach((d) => {
|
||||
projPackageJsonDeps[d] =
|
||||
rootPackageJsonDeps[d] || projDependencies[d];
|
||||
rootPackageJsonDeps[d] || npmDependencies[d];
|
||||
});
|
||||
|
||||
const deps = (node.value as AST.JSONObjectExpression).properties;
|
||||
@@ -208,8 +207,9 @@ export default createESLintRule<Options, MessageIds>({
|
||||
return;
|
||||
}
|
||||
if (
|
||||
projDependencies[packageName] === '*' ||
|
||||
satisfies(projDependencies[packageName], packageRange)
|
||||
npmDependencies[packageName] === '*' ||
|
||||
packageRange === '*' ||
|
||||
satisfies(npmDependencies[packageName], packageRange)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -219,13 +219,13 @@ export default createESLintRule<Options, MessageIds>({
|
||||
messageId: 'versionMismatch',
|
||||
data: {
|
||||
packageName: packageName,
|
||||
version: projDependencies[packageName],
|
||||
version: npmDependencies[packageName],
|
||||
},
|
||||
fix: (fixer) =>
|
||||
fixer.replaceText(
|
||||
node as any,
|
||||
`"${packageName}": "${
|
||||
rootPackageJsonDeps[packageName] || projDependencies[packageName]
|
||||
rootPackageJsonDeps[packageName] || npmDependencies[packageName]
|
||||
}"`
|
||||
),
|
||||
});
|
||||
@@ -303,15 +303,15 @@ export default createESLintRule<Options, MessageIds>({
|
||||
.join(),
|
||||
},
|
||||
fix: (fixer) => {
|
||||
expectedDependencyNames.sort().reduce((acc, d) => {
|
||||
acc[d] = rootPackageJsonDeps[d] || projDependencies[d];
|
||||
return acc;
|
||||
}, projPackageJsonDeps);
|
||||
|
||||
const dependencies = Object.keys(projPackageJsonDeps)
|
||||
.map((d) => `\n "${d}": "${projPackageJsonDeps[d]}"`)
|
||||
.join(',');
|
||||
|
||||
expectedDependencyNames.sort().reduce((acc, d) => {
|
||||
acc[d] = rootPackageJsonDeps[d] || dependencies[d];
|
||||
return acc;
|
||||
}, projPackageJsonDeps);
|
||||
|
||||
if (!node.properties.length) {
|
||||
return fixer.replaceText(
|
||||
node as any,
|
||||
@@ -332,7 +332,7 @@ export default createESLintRule<Options, MessageIds>({
|
||||
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i]'](
|
||||
node: AST.JSONProperty
|
||||
) {
|
||||
return validateMissingDependencies(node);
|
||||
validateMissingDependencies(node);
|
||||
},
|
||||
['JSONExpressionStatement > JSONObjectExpression > JSONProperty[key.value=/^(dev|peer|optional)?dependencies$/i] > JSONObjectExpression > JSONProperty'](
|
||||
node: AST.JSONProperty
|
||||
@@ -345,19 +345,15 @@ export default createESLintRule<Options, MessageIds>({
|
||||
}
|
||||
|
||||
if (expectedDependencyNames.includes(packageName)) {
|
||||
return validateVersionMatchesInstalled(
|
||||
node,
|
||||
packageName,
|
||||
packageRange
|
||||
);
|
||||
validateVersionMatchesInstalled(node, packageName, packageRange);
|
||||
} else {
|
||||
return reportObsoleteDependency(node, packageName);
|
||||
reportObsoleteDependency(node, packageName);
|
||||
}
|
||||
},
|
||||
['JSONExpressionStatement > JSONObjectExpression'](
|
||||
node: AST.JSONObjectExpression
|
||||
) {
|
||||
return validateDependenciesSectionExistance(node);
|
||||
validateDependenciesSectionExistance(node);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
import { ProjectFileMap, readJsonFile } from '@nx/devkit';
|
||||
import { existsSync } from 'fs';
|
||||
import { PackageJson } from 'nx/src/utils/package-json';
|
||||
|
||||
export function getAllDependencies(path: string): Record<string, string> {
|
||||
export function getAllDependencies(
|
||||
packageJson: PackageJson
|
||||
): Record<string, string> {
|
||||
return {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
...packageJson.peerDependencies,
|
||||
...packageJson.optionalDependencies,
|
||||
};
|
||||
}
|
||||
|
||||
export function getPackageJson(path: string): PackageJson {
|
||||
if (existsSync(path)) {
|
||||
const packageJson = readJsonFile(path);
|
||||
return {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
...packageJson.peerDependencies,
|
||||
};
|
||||
return readJsonFile(path);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
export function removePackageJsonFromFileMap(
|
||||
projectFileMap: ProjectFileMap
|
||||
): ProjectFileMap {
|
||||
const newFileMap = {};
|
||||
Object.keys(projectFileMap).forEach((key) => {
|
||||
newFileMap[key] = projectFileMap[key].filter(
|
||||
(f) => !f.file.endsWith('/package.json')
|
||||
);
|
||||
});
|
||||
return newFileMap;
|
||||
return {} as PackageJson;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
title: JS Node executor examples
|
||||
description: This page contains examples for the @nx/js:node executor.
|
||||
---
|
||||
|
||||
The `@nx/js:node` executor runs the output of a build target. For example, an application uses esbuild ([`@nx/esbuild:esbuild`](/packages/esbuild/executors/esbuild)) to output the bundle to `dist/my-app` folder, which can then be executed by `@nx/js:node`.
|
||||
|
||||
`project.json`:
|
||||
|
||||
```json
|
||||
"my-app": {
|
||||
"targets": {
|
||||
"serve": {
|
||||
"executor": "@nx/js:node",
|
||||
"options": {
|
||||
"buildTarget": "my-app:build"
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"executor": "@nx/esbuild:esbuild",
|
||||
"options": {
|
||||
"main": "my-app/src/main.ts",
|
||||
"output": ["dist/my-app"],
|
||||
//...
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
npx nx serve my-app
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
{% tabs %}
|
||||
{% tab label="Pass extra Node CLI arguments" %}
|
||||
|
||||
Using `runtimeArgs`, you can pass arguments to the underlying `node` command. For example, if you want to set [`--no-warnings`](https://nodejs.org/api/cli.html#--no-warnings) to silence all Node warnings, then add the following to the `project.json` file.
|
||||
|
||||
```json
|
||||
"my-app": {
|
||||
"targets": {
|
||||
"serve": {
|
||||
"executor": "@nx/js:node",
|
||||
"options": {
|
||||
"runtimeArgs": ["--no-warnings"],
|
||||
//...
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Run all task dependencies" %}
|
||||
|
||||
If your application build depends on other tasks, and you want those tasks to also be executed, then set the `runBuildTargetDependencies` to `true`. For example, a library may have a task to generate GraphQL schemas, which is consume by the application. In this case, you want to run the generate task before building and running the application.
|
||||
|
||||
This option is also useful when the build consumes a library from its output, not its source. For example, if an executor that supports `buildLibsFromSource` option has it set to `false` (e.g. [`@nx/webpack:webpack`](/packages/webpack/executors/webpack)).
|
||||
|
||||
Note that this option will increase the build time, so use it only when necessary.
|
||||
|
||||
```json
|
||||
"my-app": {
|
||||
"targets": {
|
||||
"serve": {
|
||||
"executor": "@nx/js:node",
|
||||
"options": {
|
||||
"runBuildTargetDependencies": true,
|
||||
//...
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
@@ -12,7 +12,6 @@
|
||||
"init": {
|
||||
"factory": "./src/generators/init/init#initSchematic",
|
||||
"schema": "./src/generators/init/schema.json",
|
||||
"aliases": ["lib"],
|
||||
"x-type": "init",
|
||||
"description": "Initialize a TS/JS workspace.",
|
||||
"hidden": true
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
ProjectGraphProjectNode,
|
||||
readTargetOptions,
|
||||
runExecutor,
|
||||
Target,
|
||||
} from '@nx/devkit';
|
||||
import { createAsyncIterable } from '@nx/devkit/src/utils/async-iterable';
|
||||
import { daemonClient } from 'nx/src/daemon/client/client';
|
||||
@@ -60,6 +61,14 @@ export async function* nodeExecutor(
|
||||
const buildTargetExecutor =
|
||||
project.data.targets[buildTarget.target]?.executor;
|
||||
|
||||
if (
|
||||
buildTargetExecutor === 'nx:run-commands' ||
|
||||
buildTargetExecutor === '@nrwl/workspace:run-commands'
|
||||
) {
|
||||
// Run commands does not emit build event, so we have to switch to run entire build through Nx CLI.
|
||||
options.runBuildTargetDependencies = true;
|
||||
}
|
||||
|
||||
const buildOptions: Record<string, any> = {
|
||||
...readTargetOptions(buildTarget, context),
|
||||
...options.buildTargetOptions,
|
||||
@@ -85,8 +94,9 @@ export async function* nodeExecutor(
|
||||
buildTargetExecutor
|
||||
);
|
||||
|
||||
const tasks: ActiveTask[] = [];
|
||||
let additionalExitHandler: null | (() => void) = null;
|
||||
let currentTask: ActiveTask = null;
|
||||
const tasks: ActiveTask[] = [];
|
||||
|
||||
yield* createAsyncIterable<{
|
||||
success: boolean;
|
||||
@@ -107,55 +117,34 @@ export async function* nodeExecutor(
|
||||
options.debounce ?? 1_000
|
||||
);
|
||||
|
||||
const addToQueue = async () => {
|
||||
const addToQueue = async (
|
||||
childProcess: null | ChildProcess,
|
||||
buildResult: Promise<{ success: boolean }>
|
||||
) => {
|
||||
const task: ActiveTask = {
|
||||
id: randomUUID(),
|
||||
killed: false,
|
||||
childProcess: null,
|
||||
childProcess,
|
||||
promise: null,
|
||||
start: async () => {
|
||||
let buildFailed = false;
|
||||
// Run the build
|
||||
task.promise = new Promise<void>(async (resolve, reject) => {
|
||||
task.childProcess = exec(
|
||||
`npx nx run ${context.projectName}:${buildTarget.target}${
|
||||
buildTarget.configuration ? `:${buildTarget.configuration}` : ''
|
||||
}`,
|
||||
{
|
||||
cwd: context.root,
|
||||
},
|
||||
(error, stdout, stderr) => {
|
||||
if (
|
||||
// Build succeeded
|
||||
!error ||
|
||||
// If task was killed then another build process has started, ignore errors.
|
||||
task.killed
|
||||
) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
// Wait for build to finish.
|
||||
const result = await buildResult;
|
||||
|
||||
logger.info(stdout);
|
||||
buildFailed = true;
|
||||
if (options.watch) {
|
||||
logger.error(
|
||||
`Build failed, waiting for changes to restart...`
|
||||
);
|
||||
resolve(); // Don't reject because it'll error out and kill the Nx process.
|
||||
} else {
|
||||
logger.error(`Build failed. See above for errors.`);
|
||||
reject();
|
||||
}
|
||||
if (!result.success) {
|
||||
// If in watch-mode, don't throw or else the process exits.
|
||||
if (options.watch) {
|
||||
if (!task.killed) {
|
||||
// Only log build error if task was not killed by a new change.
|
||||
logger.error(`Build failed, waiting for changes to restart...`);
|
||||
}
|
||||
);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
throw new Error(`Build failed. See above for errors.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for build to finish
|
||||
await task.promise;
|
||||
|
||||
// Task may have been stopped due to another running task.
|
||||
// OR build failed, so don't start the process.
|
||||
if (task.killed || buildFailed) return;
|
||||
// Before running the program, check if the task has been killed (by a new change during watch).
|
||||
if (task.killed) return;
|
||||
|
||||
// Run the program
|
||||
task.promise = new Promise<void>((resolve, reject) => {
|
||||
@@ -173,16 +162,17 @@ export async function* nodeExecutor(
|
||||
}
|
||||
);
|
||||
|
||||
task.childProcess.stderr.on('data', (data) => {
|
||||
const handleStdErr = (data) => {
|
||||
// Don't log out error if task is killed and new one has started.
|
||||
// This could happen if a new build is triggered while new process is starting, since the operation is not atomic.
|
||||
// Log the error in normal mode
|
||||
if (!options.watch || !task.killed) {
|
||||
logger.error(data.toString());
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
task.childProcess.stderr.on('data', handleStdErr);
|
||||
task.childProcess.once('exit', (code) => {
|
||||
task.childProcess.off('data', handleStdErr);
|
||||
if (options.watch && !task.killed) {
|
||||
logger.info(
|
||||
`NX Process exited with code ${code}, waiting for changes to restart...`
|
||||
@@ -203,15 +193,46 @@ export async function* nodeExecutor(
|
||||
if (task.childProcess) {
|
||||
await killTree(task.childProcess.pid, signal);
|
||||
}
|
||||
await task.promise;
|
||||
try {
|
||||
await task.promise;
|
||||
} catch {
|
||||
// Doesn't matter if task fails, we just need to wait until it finishes.
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
tasks.push(task);
|
||||
};
|
||||
|
||||
if (options.watch) {
|
||||
const stopWatch = await daemonClient.registerFileWatcher(
|
||||
if (options.runBuildTargetDependencies) {
|
||||
// If a all dependencies need to be rebuild on changes, then register with watcher
|
||||
// and run through CLI, otherwise only the current project will rebuild.
|
||||
const runBuild = async () => {
|
||||
let childProcess: ChildProcess = null;
|
||||
const whenReady = new Promise<{ success: boolean }>(async (resolve) => {
|
||||
childProcess = fork(
|
||||
require.resolve('nx'),
|
||||
[
|
||||
'run',
|
||||
`${context.projectName}:${buildTarget.target}${
|
||||
buildTarget.configuration ? `:${buildTarget.configuration}` : ''
|
||||
}`,
|
||||
],
|
||||
{
|
||||
cwd: context.root,
|
||||
stdio: 'inherit',
|
||||
}
|
||||
);
|
||||
childProcess.once('exit', (code) => {
|
||||
if (code === 0) resolve({ success: true });
|
||||
// If process is killed due to current task being killed, then resolve with success.
|
||||
else resolve({ success: !!currentTask?.killed });
|
||||
});
|
||||
});
|
||||
await addToQueue(childProcess, whenReady);
|
||||
await debouncedProcessQueue();
|
||||
};
|
||||
additionalExitHandler = await daemonClient.registerFileWatcher(
|
||||
{
|
||||
watchProjects: [context.projectName],
|
||||
includeDependentProjects: true,
|
||||
@@ -224,37 +245,52 @@ export async function* nodeExecutor(
|
||||
logger.error(`Watch error: ${err?.message ?? 'Unknown'}`);
|
||||
} else {
|
||||
logger.info(`NX File change detected. Restarting...`);
|
||||
await addToQueue();
|
||||
await debouncedProcessQueue();
|
||||
await runBuild();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const stopAllTasks = (signal: NodeJS.Signals = 'SIGTERM') => {
|
||||
for (const task of tasks) {
|
||||
task.stop(signal);
|
||||
await runBuild(); // run first build
|
||||
} else {
|
||||
// Otherwise, run the build executor, which will not run task dependencies.
|
||||
// This is mostly fine for bundlers like webpack that should already watch for dependency libs.
|
||||
// For tsc/swc or custom build commands, consider using `runBuildTargetDependencies` instead.
|
||||
const output = await runExecutor(
|
||||
buildTarget,
|
||||
{
|
||||
...options.buildTargetOptions,
|
||||
watch: options.watch,
|
||||
},
|
||||
context
|
||||
);
|
||||
while (true) {
|
||||
const event = await output.next();
|
||||
await addToQueue(null, Promise.resolve(event.value));
|
||||
await debouncedProcessQueue();
|
||||
if (event.done || !options.watch) {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
stopWatch();
|
||||
stopAllTasks('SIGTERM');
|
||||
process.exit(128 + 15);
|
||||
});
|
||||
process.on('SIGINT', async () => {
|
||||
stopWatch();
|
||||
stopAllTasks('SIGINT');
|
||||
process.exit(128 + 2);
|
||||
});
|
||||
process.on('SIGHUP', async () => {
|
||||
stopWatch();
|
||||
stopAllTasks('SIGHUP');
|
||||
process.exit(128 + 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await addToQueue();
|
||||
await processQueue();
|
||||
const stopAllTasks = (signal: NodeJS.Signals = 'SIGTERM') => {
|
||||
additionalExitHandler?.();
|
||||
for (const task of tasks) {
|
||||
task.stop(signal);
|
||||
}
|
||||
};
|
||||
|
||||
process.on('SIGTERM', async () => {
|
||||
stopAllTasks('SIGTERM');
|
||||
process.exit(128 + 15);
|
||||
});
|
||||
process.on('SIGINT', async () => {
|
||||
stopAllTasks('SIGINT');
|
||||
process.exit(128 + 2);
|
||||
});
|
||||
process.on('SIGHUP', async () => {
|
||||
stopAllTasks('SIGHUP');
|
||||
process.exit(128 + 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -321,6 +357,18 @@ function getFileToRun(
|
||||
buildOptions: Record<string, any>,
|
||||
buildTargetExecutor: string
|
||||
): string {
|
||||
// If using run-commands or another custom executor, then user should set
|
||||
// outputFileName, but we can try the default value that we use.
|
||||
if (!buildOptions?.outputPath && !buildOptions?.outputFileName) {
|
||||
const fallbackFile = path.join('dist', project.data.root, 'main.js');
|
||||
logger.warn(
|
||||
`Build option ${chalk.bold('outputFileName')} not set for ${chalk.bold(
|
||||
project.name
|
||||
)}. Using fallback value of ${chalk.bold(fallbackFile)}.`
|
||||
);
|
||||
return join(context.root, fallbackFile);
|
||||
}
|
||||
|
||||
let outputFileName = buildOptions.outputFileName;
|
||||
|
||||
if (!outputFileName) {
|
||||
|
||||
+1
@@ -14,4 +14,5 @@ export interface NodeExecutorOptions {
|
||||
port: number;
|
||||
watch?: boolean;
|
||||
debounce?: number;
|
||||
runBuildTargetDependencies?: boolean;
|
||||
}
|
||||
|
||||
@@ -27,12 +27,14 @@
|
||||
"host": {
|
||||
"type": "string",
|
||||
"default": "localhost",
|
||||
"description": "The host to inspect the process on."
|
||||
"description": "The host to inspect the process on.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"port": {
|
||||
"type": "number",
|
||||
"default": 9229,
|
||||
"description": "The port to inspect the process on. Setting port to 0 will assign random free ports to all forked processes."
|
||||
"description": "The port to inspect the process on. Setting port to 0 will assign random free ports to all forked processes.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"inspect": {
|
||||
"oneOf": [
|
||||
@@ -45,7 +47,8 @@
|
||||
}
|
||||
],
|
||||
"description": "Ensures the app is starting with debugging.",
|
||||
"default": "inspect"
|
||||
"default": "inspect",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"runtimeArgs": {
|
||||
"type": "array",
|
||||
@@ -53,7 +56,8 @@
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"x-priority": "important"
|
||||
},
|
||||
"args": {
|
||||
"type": "array",
|
||||
@@ -61,19 +65,28 @@
|
||||
"default": [],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"x-priority": "important"
|
||||
},
|
||||
"watch": {
|
||||
"type": "boolean",
|
||||
"description": "Enable re-building when files change.",
|
||||
"default": true
|
||||
"default": true,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"debounce": {
|
||||
"type": "number",
|
||||
"description": "Delay in milliseconds to wait before restarting. Useful to batch multiple file changes events together. Set to zero (0) to disable.",
|
||||
"default": 500
|
||||
"default": 500,
|
||||
"x-priority": "important"
|
||||
},
|
||||
"runBuildTargetDependencies": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to run dependencies before running the build. Set this to true if the project does not build libraries from source (e.g. 'buildLibsFromSource: false').",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["buildTarget"]
|
||||
"required": ["buildTarget"],
|
||||
"examplesFile": "../../../docs/node-examples.md"
|
||||
}
|
||||
|
||||
@@ -76,6 +76,20 @@ export async function* tscBatchExecutor(
|
||||
},
|
||||
};
|
||||
|
||||
const processTaskPostCompilation = (tsConfig: string) => {
|
||||
if (tsConfigTaskInfoMap[tsConfig]) {
|
||||
const taskInfo = tsConfigTaskInfoMap[tsConfig];
|
||||
taskInfo.assetsHandler.processAllAssetsOnceSync();
|
||||
updatePackageJson(
|
||||
taskInfo.options,
|
||||
taskInfo.context,
|
||||
taskInfo.projectGraphNode,
|
||||
taskInfo.buildableProjectNodeDependencies
|
||||
);
|
||||
taskInfo.endTime = Date.now();
|
||||
}
|
||||
};
|
||||
|
||||
const typescriptCompilation = compileTypescriptSolution(
|
||||
tsCompilationContext,
|
||||
shouldWatch,
|
||||
@@ -86,19 +100,7 @@ export async function* tscBatchExecutor(
|
||||
tsConfigTaskInfoMap[tsConfig].startTime = Date.now();
|
||||
}
|
||||
},
|
||||
afterProjectCompilationCallback: (tsConfig) => {
|
||||
if (tsConfigTaskInfoMap[tsConfig]) {
|
||||
const taskInfo = tsConfigTaskInfoMap[tsConfig];
|
||||
taskInfo.assetsHandler.processAllAssetsOnceSync();
|
||||
updatePackageJson(
|
||||
taskInfo.options,
|
||||
taskInfo.context,
|
||||
taskInfo.projectGraphNode,
|
||||
taskInfo.buildableProjectNodeDependencies
|
||||
);
|
||||
taskInfo.endTime = Date.now();
|
||||
}
|
||||
},
|
||||
afterProjectCompilationCallback: processTaskPostCompilation,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -136,23 +138,68 @@ export async function* tscBatchExecutor(
|
||||
});
|
||||
}
|
||||
|
||||
return yield* mapAsyncIterable(typescriptCompilation, async (iterator) => {
|
||||
const { value, done } = await iterator.next();
|
||||
const toBatchExecutorTaskResult = (
|
||||
tsConfig: string,
|
||||
success: boolean
|
||||
): BatchExecutorTaskResult => ({
|
||||
task: tsConfigTaskInfoMap[tsConfig].task,
|
||||
result: {
|
||||
success: success,
|
||||
terminalOutput: tsConfigTaskInfoMap[tsConfig].terminalOutput,
|
||||
startTime: tsConfigTaskInfoMap[tsConfig].startTime,
|
||||
endTime: tsConfigTaskInfoMap[tsConfig].endTime,
|
||||
},
|
||||
});
|
||||
|
||||
let isCompilationDone = false;
|
||||
const taskTsConfigsToReport = new Set(
|
||||
Object.keys(taskGraph.tasks).map((t) => taskInMemoryTsConfigMap[t].path)
|
||||
);
|
||||
let tasksToReportIterator: IterableIterator<string>;
|
||||
|
||||
const processSkippedTasks = () => {
|
||||
const { value: tsConfig, done } = tasksToReportIterator.next();
|
||||
if (done) {
|
||||
return { value, done: true };
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
|
||||
const taskResult: BatchExecutorTaskResult = {
|
||||
task: tsConfigTaskInfoMap[value.tsConfig].task,
|
||||
result: {
|
||||
success: value.success,
|
||||
terminalOutput: tsConfigTaskInfoMap[value.tsConfig].terminalOutput,
|
||||
startTime: tsConfigTaskInfoMap[value.tsConfig].startTime,
|
||||
endTime: tsConfigTaskInfoMap[value.tsConfig].endTime,
|
||||
},
|
||||
};
|
||||
tsConfigTaskInfoMap[tsConfig].startTime = Date.now();
|
||||
processTaskPostCompilation(tsConfig);
|
||||
|
||||
return { value: taskResult, done: false };
|
||||
return { value: toBatchExecutorTaskResult(tsConfig, true), done: false };
|
||||
};
|
||||
|
||||
return yield* mapAsyncIterable(typescriptCompilation, async (iterator) => {
|
||||
if (isCompilationDone) {
|
||||
return processSkippedTasks();
|
||||
}
|
||||
|
||||
const { value, done } = await iterator.next();
|
||||
if (done) {
|
||||
if (taskTsConfigsToReport.size > 0) {
|
||||
/**
|
||||
* TS compilation is done but we still have tasks to report. This can
|
||||
* happen if, for example, a project is identified as affected, but
|
||||
* no file in the TS project is actually changed or if running a
|
||||
* task with `--skip-nx-cache` and the outputs are already there. There
|
||||
* can still be changes to assets or other files we need to process.
|
||||
*
|
||||
* Switch to handle the iterator for the tasks we still need to report.
|
||||
*/
|
||||
isCompilationDone = true;
|
||||
tasksToReportIterator = taskTsConfigsToReport.values();
|
||||
return processSkippedTasks();
|
||||
}
|
||||
|
||||
return { value: undefined, done: true };
|
||||
}
|
||||
|
||||
taskTsConfigsToReport.delete(value.tsConfig);
|
||||
|
||||
return {
|
||||
value: toBatchExecutorTaskResult(value.tsConfig, value.success),
|
||||
done: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ function setupNpm(options: VerdaccioExecutorSchema) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
let npmRegistryPath;
|
||||
let npmRegistryPath: string;
|
||||
try {
|
||||
npmRegistryPath = execSync(
|
||||
`npm config get registry --location ${options.location}`
|
||||
@@ -154,7 +154,13 @@ function setupNpm(options: VerdaccioExecutorSchema) {
|
||||
|
||||
return () => {
|
||||
try {
|
||||
if (npmRegistryPath) {
|
||||
const currentNpmRegistryPath = execSync(
|
||||
`npm config get registry --location ${options.location}`
|
||||
)
|
||||
?.toString()
|
||||
?.trim()
|
||||
?.replace('\u001b[2K\u001b[1G', ''); // strip out ansi codes
|
||||
if (npmRegistryPath && currentNpmRegistryPath.includes('localhost')) {
|
||||
execSync(
|
||||
`npm config set registry ${npmRegistryPath} --location ${options.location}`
|
||||
);
|
||||
@@ -240,7 +246,13 @@ function setupYarn(options: VerdaccioExecutorSchema) {
|
||||
|
||||
return () => {
|
||||
try {
|
||||
if (yarnRegistryPath) {
|
||||
const currentYarnRegistryPath = execSync(
|
||||
`yarn config get ${registryConfigName}`
|
||||
)
|
||||
?.toString()
|
||||
?.trim()
|
||||
?.replace('\u001b[2K\u001b[1G', ''); // strip out ansi codes
|
||||
if (yarnRegistryPath && currentYarnRegistryPath.includes('localhost')) {
|
||||
execSync(
|
||||
`yarn config set ${registryConfigName} ${yarnRegistryPath}` +
|
||||
(options.location === 'user' ? ' --home' : '')
|
||||
|
||||
@@ -0,0 +1,400 @@
|
||||
import 'nx/src/utils/testing/mock-fs';
|
||||
import { vol } from 'memfs';
|
||||
import { findNpmDependencies } from './find-npm-dependencies';
|
||||
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual<any>('@nx/devkit'),
|
||||
workspaceRoot: '/root',
|
||||
}));
|
||||
|
||||
jest.mock('nx/src/utils/workspace-root', () => ({
|
||||
workspaceRoot: '/root',
|
||||
}));
|
||||
|
||||
describe('findNpmDependencies', () => {
|
||||
const nxJson = {
|
||||
targetDefaults: {
|
||||
build: {
|
||||
inputs: [
|
||||
'{projectRoot}/**/*',
|
||||
'!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)',
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
it('should pick up external npm dependencies and their versions', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
const libWithExternalDeps = {
|
||||
name: 'my-lib',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/my-lib',
|
||||
targets: { build: {} },
|
||||
},
|
||||
};
|
||||
const projectGraph = {
|
||||
nodes: {
|
||||
'my-lib': libWithExternalDeps,
|
||||
},
|
||||
externalNodes: {
|
||||
'npm:foo': {
|
||||
name: 'npm:foo' as const,
|
||||
type: 'npm' as const,
|
||||
data: {
|
||||
packageName: 'foo',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {},
|
||||
};
|
||||
const projectFileMap = {
|
||||
'my-lib': [
|
||||
{
|
||||
file: 'libs/my-lib/index.ts',
|
||||
hash: '123',
|
||||
deps: ['npm:foo'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const results = findNpmDependencies(
|
||||
'/root',
|
||||
libWithExternalDeps,
|
||||
projectGraph,
|
||||
projectFileMap,
|
||||
'build'
|
||||
);
|
||||
|
||||
expect(results).toEqual({
|
||||
foo: '1.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should pick up helper npm dependencies if required', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
'./libs/my-lib/tsconfig.json': JSON.stringify({
|
||||
compilerOptions: {
|
||||
importHelpers: true,
|
||||
},
|
||||
}),
|
||||
'./libs/my-lib/.swcrc': JSON.stringify({
|
||||
jsc: {
|
||||
externalHelpers: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
const libWithHelpers = {
|
||||
name: 'my-lib',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/my-lib',
|
||||
targets: {
|
||||
build1: {
|
||||
executor: '@nx/js:tsc',
|
||||
options: {
|
||||
tsConfig: 'libs/my-lib/tsconfig.json',
|
||||
},
|
||||
},
|
||||
build2: {
|
||||
executor: '@nx/js:swc',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const projectGraph = {
|
||||
nodes: {
|
||||
'my-lib': libWithHelpers,
|
||||
},
|
||||
externalNodes: {
|
||||
'npm:tslib': {
|
||||
name: 'npm:tslib' as const,
|
||||
type: 'npm' as const,
|
||||
data: {
|
||||
packageName: 'tslib',
|
||||
version: '2.6.0',
|
||||
},
|
||||
},
|
||||
'npm:@swc/helpers': {
|
||||
name: 'npm:@swc/helpers' as const,
|
||||
type: 'npm' as const,
|
||||
data: {
|
||||
packageName: '@swc/helpers',
|
||||
version: '0.5.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {},
|
||||
};
|
||||
const projectFileMap = {
|
||||
'my-lib': [],
|
||||
};
|
||||
|
||||
expect(
|
||||
findNpmDependencies(
|
||||
'/root',
|
||||
libWithHelpers,
|
||||
projectGraph,
|
||||
projectFileMap,
|
||||
'build1'
|
||||
)
|
||||
).toEqual({
|
||||
tslib: '2.6.0',
|
||||
});
|
||||
expect(
|
||||
findNpmDependencies(
|
||||
'/root',
|
||||
libWithHelpers,
|
||||
projectGraph,
|
||||
projectFileMap,
|
||||
'build2'
|
||||
)
|
||||
).toEqual({
|
||||
'@swc/helpers': '0.5.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not pick up helper npm dependencies if not required', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./libs/my-lib/tsconfig.json': JSON.stringify({
|
||||
compilerOptions: {
|
||||
importHelpers: false,
|
||||
},
|
||||
}),
|
||||
'./libs/my-lib/.swcrc': JSON.stringify({
|
||||
jsc: {
|
||||
externalHelpers: false,
|
||||
},
|
||||
}),
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
const libWithInlinedHelpers = {
|
||||
name: 'my-lib',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/my-lib',
|
||||
targets: {
|
||||
build1: {
|
||||
executor: '@nx/js:tsc',
|
||||
options: {
|
||||
tsConfig: 'libs/my-lib/tsconfig.json',
|
||||
},
|
||||
},
|
||||
build2: {
|
||||
executor: '@nx/js:swc',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const projectGraph = {
|
||||
nodes: {
|
||||
'my-lib': libWithInlinedHelpers,
|
||||
},
|
||||
externalNodes: {
|
||||
'npm:tslib': {
|
||||
name: 'npm:tslib' as const,
|
||||
type: 'npm' as const,
|
||||
data: {
|
||||
packageName: 'tslib',
|
||||
version: '2.6.0',
|
||||
},
|
||||
},
|
||||
'npm:@swc/helpers': {
|
||||
name: 'npm:@swc/helpers' as const,
|
||||
type: 'npm' as const,
|
||||
data: {
|
||||
packageName: '@swc/helpers',
|
||||
version: '0.5.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {},
|
||||
};
|
||||
const projectFileMap = {
|
||||
'my-lib': [],
|
||||
};
|
||||
|
||||
const results = findNpmDependencies(
|
||||
'/root',
|
||||
libWithInlinedHelpers,
|
||||
projectGraph,
|
||||
projectFileMap,
|
||||
'build'
|
||||
);
|
||||
|
||||
expect(results).toEqual({});
|
||||
});
|
||||
|
||||
it('should support recursive collection of dependencies', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
const parentLib = {
|
||||
name: 'parent',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/parent',
|
||||
targets: { build: {} },
|
||||
},
|
||||
};
|
||||
const projectGraph = {
|
||||
nodes: {
|
||||
parent: parentLib,
|
||||
child1: {
|
||||
name: 'child1',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/child1',
|
||||
targets: { build: {} },
|
||||
},
|
||||
},
|
||||
child2: {
|
||||
name: 'child2',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/child2',
|
||||
targets: { build: {} },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalNodes: {
|
||||
'npm:foo': {
|
||||
name: 'npm:foo' as const,
|
||||
type: 'npm' as const,
|
||||
data: {
|
||||
packageName: 'foo',
|
||||
version: '1.0.0',
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
parent: [
|
||||
{
|
||||
type: 'static',
|
||||
source: 'parent',
|
||||
target: 'child1',
|
||||
},
|
||||
],
|
||||
child1: [
|
||||
{
|
||||
type: 'static',
|
||||
source: 'child1',
|
||||
target: 'child2',
|
||||
},
|
||||
],
|
||||
child2: [
|
||||
{
|
||||
type: 'static',
|
||||
source: 'child2',
|
||||
target: 'npm:foo',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const projectFileMap = {
|
||||
parent: [{ file: 'libs/parent/index.ts', hash: '123', deps: ['child1'] }],
|
||||
child1: [{ file: 'libs/child1/index.ts', hash: '123', deps: ['child2'] }],
|
||||
child2: [
|
||||
{ file: 'libs/child2/index.ts', hash: '123', deps: ['npm:foo'] },
|
||||
],
|
||||
};
|
||||
|
||||
const results = findNpmDependencies(
|
||||
'/root',
|
||||
parentLib,
|
||||
projectGraph,
|
||||
projectFileMap,
|
||||
'build',
|
||||
{
|
||||
includeTransitiveDependencies: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(results).toEqual({
|
||||
foo: '1.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('should find workspace dependencies', () => {
|
||||
vol.fromJSON(
|
||||
{
|
||||
'./libs/lib3/package.json': JSON.stringify({
|
||||
name: '@acme/lib3',
|
||||
version: '0.0.1',
|
||||
}),
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
},
|
||||
'/root'
|
||||
);
|
||||
const lib1 = {
|
||||
name: 'lib1',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/lib1',
|
||||
targets: { build: {} },
|
||||
},
|
||||
};
|
||||
const lib2 = {
|
||||
name: 'lib2',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/lib2',
|
||||
targets: { build: {} },
|
||||
},
|
||||
};
|
||||
const lib3 = {
|
||||
name: 'lib3',
|
||||
type: 'lib' as const,
|
||||
data: {
|
||||
root: 'libs/lib3',
|
||||
targets: { build: {} },
|
||||
},
|
||||
};
|
||||
const projectGraph = {
|
||||
nodes: {
|
||||
lib1: lib1,
|
||||
lib2: lib2,
|
||||
lib3: lib3,
|
||||
},
|
||||
externalNodes: {},
|
||||
dependencies: {},
|
||||
};
|
||||
const projectFileMap = {
|
||||
lib1: [{ file: 'libs/lib1/index.ts', hash: '123', deps: ['lib3'] }],
|
||||
lib2: [{ file: 'libs/lib1/index.ts', hash: '123', deps: ['lib3'] }],
|
||||
lib3: [],
|
||||
};
|
||||
|
||||
expect(
|
||||
findNpmDependencies('/root', lib1, projectGraph, projectFileMap, 'build')
|
||||
).toEqual({
|
||||
'@acme/lib3': '*',
|
||||
});
|
||||
expect(
|
||||
findNpmDependencies('/root', lib2, projectGraph, projectFileMap, 'build')
|
||||
).toEqual({
|
||||
'@acme/lib3': '*',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,191 @@
|
||||
import { join } from 'path';
|
||||
import { readNxJson } from 'nx/src/project-graph/file-utils';
|
||||
import {
|
||||
getTargetInputs,
|
||||
filterUsingGlobPatterns,
|
||||
} from 'nx/src/hasher/task-hasher';
|
||||
import {
|
||||
type ProjectGraph,
|
||||
type ProjectGraphProjectNode,
|
||||
type ProjectFileMap,
|
||||
readJsonFile,
|
||||
FileData,
|
||||
joinPathFragments,
|
||||
} from '@nx/devkit';
|
||||
import { fileExists } from 'nx/src/utils/fileutils';
|
||||
import { fileDataDepTarget } from 'nx/src/config/project-graph';
|
||||
import { readTsConfig } from './typescript/ts-config';
|
||||
|
||||
/**
|
||||
* Finds all npm dependencies and their expected versions for a given project.
|
||||
*/
|
||||
export function findNpmDependencies(
|
||||
workspaceRoot: string,
|
||||
sourceProject: ProjectGraphProjectNode,
|
||||
projectGraph: ProjectGraph,
|
||||
projectFileMap: ProjectFileMap,
|
||||
buildTarget: string,
|
||||
options: {
|
||||
includeTransitiveDependencies?: boolean;
|
||||
} = {}
|
||||
): Record<string, string> {
|
||||
let seen: null | Set<string> = null;
|
||||
if (options.includeTransitiveDependencies) {
|
||||
seen = new Set<string>();
|
||||
}
|
||||
|
||||
const results: Record<string, string> = {};
|
||||
|
||||
function collectAll(
|
||||
currentProject: ProjectGraphProjectNode,
|
||||
collectedDeps: Record<string, string>
|
||||
): void {
|
||||
if (seen?.has(currentProject.name)) return;
|
||||
|
||||
collectDependenciesFromFileMap(
|
||||
workspaceRoot,
|
||||
currentProject,
|
||||
projectGraph,
|
||||
projectFileMap,
|
||||
buildTarget,
|
||||
collectedDeps
|
||||
);
|
||||
|
||||
collectHelperDependencies(
|
||||
workspaceRoot,
|
||||
currentProject,
|
||||
projectGraph,
|
||||
buildTarget,
|
||||
collectedDeps
|
||||
);
|
||||
|
||||
if (options.includeTransitiveDependencies) {
|
||||
const projectDeps = projectGraph.dependencies[currentProject.name];
|
||||
for (const dep of projectDeps) {
|
||||
const projectDep = projectGraph.nodes[dep.target];
|
||||
if (projectDep) collectAll(projectDep, collectedDeps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
collectAll(sourceProject, results);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Keep track of workspace libs we already read package.json for so we don't read from disk again.
|
||||
const seenWorkspaceDeps: Record<string, { name: string; version: string }> = {};
|
||||
|
||||
function collectDependenciesFromFileMap(
|
||||
workspaceRoot: string,
|
||||
sourceProject: ProjectGraphProjectNode,
|
||||
projectGraph: ProjectGraph,
|
||||
projectFileMap: ProjectFileMap,
|
||||
buildTarget: string,
|
||||
npmDeps: Record<string, string>
|
||||
): void {
|
||||
const rawFiles = projectFileMap[sourceProject.name];
|
||||
if (!rawFiles) return;
|
||||
|
||||
// Cannot read inputs if the target does not exist on the project.
|
||||
if (!sourceProject.data.targets[buildTarget]) return;
|
||||
|
||||
const inputs = getTargetInputs(
|
||||
readNxJson(),
|
||||
sourceProject,
|
||||
buildTarget
|
||||
).selfInputs;
|
||||
const files = filterUsingGlobPatterns(
|
||||
sourceProject.data.root,
|
||||
projectFileMap[sourceProject.name] || [],
|
||||
inputs
|
||||
);
|
||||
|
||||
for (const fileData of files) {
|
||||
if (
|
||||
!fileData.deps ||
|
||||
fileData.file ===
|
||||
joinPathFragments(sourceProject.data.root, 'package.json')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const dep of fileData.deps) {
|
||||
const target = fileDataDepTarget(dep);
|
||||
|
||||
// If the node is external, then read package info from `data`.
|
||||
const externalDep = projectGraph.externalNodes[target];
|
||||
if (externalDep?.type === 'npm') {
|
||||
npmDeps[externalDep.data.packageName] = externalDep.data.version;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If node is internal, then try reading package info from `package.json` (which must exist for this to work).
|
||||
const workspaceDep = projectGraph.nodes[target];
|
||||
if (!workspaceDep) continue;
|
||||
const cached = seenWorkspaceDeps[workspaceDep.name];
|
||||
if (cached) {
|
||||
npmDeps[cached.name] = cached.version;
|
||||
} else {
|
||||
const packageJson = readPackageJson(workspaceDep, workspaceRoot);
|
||||
if (packageJson) {
|
||||
// This is a workspace lib so we can't reliably read in a specific version since it depends on how the workspace is set up.
|
||||
// ASSUMPTION: Most users will use '*' for workspace lib versions. Otherwise, they can manually update it.
|
||||
npmDeps[packageJson.name] = '*';
|
||||
seenWorkspaceDeps[workspaceDep.name] = {
|
||||
name: packageJson.name,
|
||||
version: '*',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readPackageJson(
|
||||
project: ProjectGraphProjectNode,
|
||||
workspaceRoot: string
|
||||
): null | {
|
||||
name: string;
|
||||
dependencies?: Record<string, string>;
|
||||
optionalDependencies?: Record<string, string>;
|
||||
peerDependencies?: Record<string, string>;
|
||||
} {
|
||||
const packageJsonPath = join(
|
||||
workspaceRoot,
|
||||
project.data.root,
|
||||
'package.json'
|
||||
);
|
||||
if (fileExists(packageJsonPath)) return readJsonFile(packageJsonPath);
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectHelperDependencies(
|
||||
workspaceRoot: string,
|
||||
sourceProject: ProjectGraphProjectNode,
|
||||
projectGraph: ProjectGraph,
|
||||
buildTarget: string,
|
||||
npmDeps: Record<string, string>
|
||||
): void {
|
||||
const target = sourceProject.data.targets[buildTarget];
|
||||
if (!target) return;
|
||||
|
||||
if (target.executor === '@nx/js:tsc' && target.options?.tsConfig) {
|
||||
const tsConfig = readTsConfig(join(workspaceRoot, target.options.tsConfig));
|
||||
if (tsConfig?.options['importHelpers']) {
|
||||
npmDeps['tslib'] = projectGraph.externalNodes['npm:tslib']?.data.version;
|
||||
}
|
||||
}
|
||||
if (target.executor === '@nx/js:swc') {
|
||||
const swcConfigPath = target.options.swcrc
|
||||
? join(workspaceRoot, target.options.swcrc)
|
||||
: join(workspaceRoot, sourceProject.data.root, '.swcrc');
|
||||
const swcConfig = fileExists(swcConfigPath)
|
||||
? readJsonFile(swcConfigPath)
|
||||
: {};
|
||||
if (swcConfig?.jsc?.externalHelpers) {
|
||||
npmDeps['@swc/helpers'] =
|
||||
projectGraph.externalNodes['npm:@swc/helpers']?.data.version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +290,7 @@ describe('updatePackageJson', () => {
|
||||
const fileMap = {
|
||||
'@org/lib1': [
|
||||
{
|
||||
file: 'test.ts',
|
||||
file: 'libs/lib1/src/test.ts',
|
||||
hash: '',
|
||||
deps: ['npm:external1', 'npm:external2'],
|
||||
},
|
||||
|
||||
@@ -13,12 +13,9 @@ hashbrown = { version = "0.14.0", features = ["rayon"] }
|
||||
ignore = '0.4'
|
||||
ignore-files = "1.3.0"
|
||||
itertools = "0.10.5"
|
||||
jsonc-parser = { version = "0.21.1", features = ["serde"] }
|
||||
napi = { version = '2.12.6', default-features = false, features = ['anyhow', 'napi4', 'tokio_rt'] }
|
||||
napi-derive = '2.9.3'
|
||||
rayon = "1.7.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
thiserror = "1.0.40"
|
||||
tokio = { version = "1.28.2", features = ["fs"] }
|
||||
tracing = "0.1.37"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-darwin-arm64",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-darwin-x64",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-freebsd-x64",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-linux-arm-gnueabihf",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-linux-arm64-gnu",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-linux-arm64-musl",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-linux-x64-gnu",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-linux-x64-musl",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-win32-arm64-msvc",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@nx/nx-win32-x64-msvc",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nrwl/nx.git",
|
||||
|
||||
@@ -25,7 +25,8 @@ export interface InitArgs {
|
||||
}
|
||||
|
||||
export async function initHandler(options: InitArgs) {
|
||||
const args = process.argv.slice(2).join(' ');
|
||||
// strip the 'init' command itself so we don't forward it
|
||||
const args = process.argv.slice(3).join(' ');
|
||||
const flags = parser(args, {
|
||||
boolean: ['useDotNxInstallation'],
|
||||
alias: {
|
||||
|
||||
@@ -95,7 +95,7 @@ export class Workspaces {
|
||||
return this.cachedProjectsConfig;
|
||||
}
|
||||
const nxJson = this.readNxJson();
|
||||
const projectsConfigurations = buildProjectsConfigurationsFromProjectPaths(
|
||||
let projectsConfigurations = buildProjectsConfigurationsFromProjectPaths(
|
||||
nxJson,
|
||||
globForProjectFiles(
|
||||
this.root,
|
||||
@@ -116,15 +116,18 @@ export class Workspaces {
|
||||
opts?._includeProjectsFromAngularJson
|
||||
)
|
||||
) {
|
||||
projectsConfigurations.projects = mergeAngularJsonAndProjects(
|
||||
projectsConfigurations.projects,
|
||||
projectsConfigurations = mergeAngularJsonAndProjects(
|
||||
projectsConfigurations,
|
||||
this.root
|
||||
);
|
||||
}
|
||||
this.cachedProjectsConfig = this.mergeTargetDefaultsIntoProjectDescriptions(
|
||||
projectsConfigurations,
|
||||
nxJson
|
||||
);
|
||||
this.cachedProjectsConfig = {
|
||||
version: 2,
|
||||
projects: this.mergeTargetDefaultsIntoProjectDescriptions(
|
||||
projectsConfigurations,
|
||||
nxJson
|
||||
),
|
||||
};
|
||||
return this.cachedProjectsConfig;
|
||||
}
|
||||
|
||||
@@ -140,10 +143,10 @@ export class Workspaces {
|
||||
}
|
||||
|
||||
private mergeTargetDefaultsIntoProjectDescriptions(
|
||||
config: ProjectsConfigurations,
|
||||
projects: Record<string, ProjectConfiguration>,
|
||||
nxJson: NxJsonConfiguration
|
||||
) {
|
||||
for (const proj of Object.values(config.projects)) {
|
||||
for (const proj of Object.values(projects)) {
|
||||
if (proj.targets) {
|
||||
for (const targetName of Object.keys(proj.targets)) {
|
||||
const projectTargetDefinition = proj.targets[targetName];
|
||||
@@ -163,7 +166,7 @@ export class Workspaces {
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
return projects;
|
||||
}
|
||||
|
||||
isNxExecutor(nodeModule: string, executor: string) {
|
||||
@@ -808,7 +811,7 @@ export function buildProjectsConfigurationsFromProjectPaths(
|
||||
projectFiles: string[], // making this parameter allows devkit to pick up newly created projects
|
||||
readJson: <T extends Object>(string) => T = <T extends Object>(string) =>
|
||||
readJsonFile<T>(string) // making this an arg allows us to reuse in devkit
|
||||
): ProjectsConfigurations {
|
||||
): Record<string, ProjectConfiguration> {
|
||||
const projects: Record<string, ProjectConfiguration> = {};
|
||||
|
||||
for (const file of projectFiles) {
|
||||
@@ -868,10 +871,7 @@ export function buildProjectsConfigurationsFromProjectPaths(
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
version: 2,
|
||||
projects: projects,
|
||||
};
|
||||
return projects;
|
||||
}
|
||||
|
||||
export function mergeTargetConfigurations(
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
retrieveWorkspaceFiles,
|
||||
retrieveProjectConfigurations,
|
||||
} from '../../project-graph/utils/retrieve-workspace-files';
|
||||
import { ProjectConfiguration } from '../../config/workspace-json-project-json';
|
||||
|
||||
let cachedSerializedProjectGraphPromise: Promise<{
|
||||
error: Error | null;
|
||||
@@ -115,8 +116,18 @@ export function addUpdatedAndDeletedFiles(
|
||||
}
|
||||
}
|
||||
|
||||
function computeWorkspaceConfigHash(projectsConfigurations: any) {
|
||||
return hashArray([JSON.stringify(projectsConfigurations)]);
|
||||
function computeWorkspaceConfigHash(
|
||||
projectsConfigurations: Record<string, ProjectConfiguration>
|
||||
) {
|
||||
const projectConfigurationStrings = Object.entries(projectsConfigurations)
|
||||
.sort(([projectNameA], [projectNameB]) =>
|
||||
projectNameA.localeCompare(projectNameB)
|
||||
)
|
||||
.map(
|
||||
([projectName, projectConfig]) =>
|
||||
`${projectName}:${JSON.stringify(projectConfig)}`
|
||||
);
|
||||
return hashArray(projectConfigurationStrings);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -198,7 +198,7 @@ function readAndCombineAllProjectConfigurations(tree: Tree): {
|
||||
nxJson,
|
||||
projectFiles,
|
||||
(file) => readJson(tree, file)
|
||||
).projects;
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1161,6 +1161,144 @@ describe('TaskHasher', () => {
|
||||
expect(hash.value).toContain('|5.0.0|');
|
||||
});
|
||||
|
||||
it('should hash entire subtree of dependencies', async () => {
|
||||
const createHasher = () =>
|
||||
new InProcessTaskHasher(
|
||||
{},
|
||||
allWorkspaceFiles,
|
||||
{
|
||||
nodes: {
|
||||
appA: {
|
||||
name: 'appA',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'apps/appA',
|
||||
targets: { build: { executor: '@nx/webpack:webpack' } },
|
||||
},
|
||||
},
|
||||
appB: {
|
||||
name: 'appB',
|
||||
type: 'app',
|
||||
data: {
|
||||
root: 'apps/appB',
|
||||
targets: { build: { executor: '@nx/webpack:webpack' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
externalNodes: {
|
||||
'npm:packageA': {
|
||||
name: 'npm:packageA',
|
||||
type: 'npm',
|
||||
data: {
|
||||
packageName: 'packageA',
|
||||
version: '0.0.0',
|
||||
hash: '$packageA0.0.0$',
|
||||
},
|
||||
},
|
||||
'npm:packageB': {
|
||||
name: 'npm:packageB',
|
||||
type: 'npm',
|
||||
data: {
|
||||
packageName: 'packageB',
|
||||
version: '0.0.0',
|
||||
hash: '$packageB0.0.0$',
|
||||
},
|
||||
},
|
||||
'npm:packageC': {
|
||||
name: 'npm:packageC',
|
||||
type: 'npm',
|
||||
data: {
|
||||
packageName: 'packageC',
|
||||
version: '0.0.0',
|
||||
hash: '$packageC0.0.0$',
|
||||
},
|
||||
},
|
||||
},
|
||||
dependencies: {
|
||||
appA: [
|
||||
{
|
||||
source: 'app',
|
||||
target: 'npm:packageA',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
{
|
||||
source: 'app',
|
||||
target: 'npm:packageB',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
{
|
||||
source: 'app',
|
||||
target: 'npm:packageC',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
appB: [
|
||||
{
|
||||
source: 'app',
|
||||
target: 'npm:packageC',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
'npm:packageC': [
|
||||
{
|
||||
source: 'app',
|
||||
target: 'npm:packageA',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
{
|
||||
source: 'app',
|
||||
target: 'npm:packageB',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
'npm:packageB': [
|
||||
{
|
||||
source: 'app',
|
||||
target: 'npm:packageA',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
roots: ['app-build'],
|
||||
tasks: {
|
||||
'app-build': {
|
||||
id: 'app-build',
|
||||
target: { project: 'app', target: 'build' },
|
||||
overrides: {},
|
||||
},
|
||||
},
|
||||
dependencies: {},
|
||||
},
|
||||
{} as any,
|
||||
{},
|
||||
fileHasher
|
||||
);
|
||||
|
||||
const computeTaskHash = async (hasher, appName) => {
|
||||
const hashAppA = await hasher.hashTask({
|
||||
target: { project: appName, target: 'build' },
|
||||
id: `${appName}-build`,
|
||||
overrides: { prop: 'prop-value' },
|
||||
});
|
||||
|
||||
return hashAppA.value;
|
||||
};
|
||||
|
||||
const hasher1 = createHasher();
|
||||
|
||||
await computeTaskHash(hasher1, 'appA');
|
||||
const hashAppB1 = await computeTaskHash(hasher1, 'appB');
|
||||
|
||||
const hasher2 = createHasher();
|
||||
|
||||
const hashAppB2 = await computeTaskHash(hasher2, 'appB');
|
||||
await computeTaskHash(hasher2, 'appA');
|
||||
|
||||
expect(hashAppB1).toEqual(hashAppB2);
|
||||
});
|
||||
|
||||
it('should not hash when nx:run-commands executor', async () => {
|
||||
const hasher = new InProcessTaskHasher(
|
||||
{},
|
||||
|
||||
@@ -332,7 +332,7 @@ class TaskHasherImpl {
|
||||
visited
|
||||
);
|
||||
} else {
|
||||
const hash = this.hashExternalDependency(d.target);
|
||||
const hash = this.hashExternalDependency(d.source, d.target);
|
||||
return {
|
||||
value: hash,
|
||||
details: {
|
||||
@@ -408,16 +408,29 @@ class TaskHasherImpl {
|
||||
return partialHashes;
|
||||
}
|
||||
|
||||
private computeExternalDependencyIdentifier(
|
||||
sourceProjectName: string,
|
||||
targetProjectName: string
|
||||
): `${string}->${string}` {
|
||||
return `${sourceProjectName}->${targetProjectName}`;
|
||||
}
|
||||
|
||||
private hashExternalDependency(
|
||||
projectName: string,
|
||||
sourceProjectName: string,
|
||||
targetProjectName: string,
|
||||
visited = new Set<string>()
|
||||
): string {
|
||||
// try to retrieve the hash from cache
|
||||
if (this.externalDepsHashCache[projectName]) {
|
||||
return this.externalDepsHashCache[projectName];
|
||||
if (this.externalDepsHashCache[targetProjectName]) {
|
||||
return this.externalDepsHashCache[targetProjectName];
|
||||
}
|
||||
visited.add(projectName);
|
||||
const node = this.projectGraph.externalNodes[projectName];
|
||||
visited.add(
|
||||
this.computeExternalDependencyIdentifier(
|
||||
sourceProjectName,
|
||||
targetProjectName
|
||||
)
|
||||
);
|
||||
const node = this.projectGraph.externalNodes[targetProjectName];
|
||||
let partialHash: string;
|
||||
if (node) {
|
||||
const partialHashes: string[] = [];
|
||||
@@ -429,22 +442,32 @@ class TaskHasherImpl {
|
||||
partialHashes.push(node.data.version);
|
||||
}
|
||||
// we want to calculate the hash of the entire dependency tree
|
||||
if (this.projectGraph.dependencies[projectName]) {
|
||||
this.projectGraph.dependencies[projectName].forEach((d) => {
|
||||
if (!visited.has(d.target)) {
|
||||
partialHashes.push(this.hashExternalDependency(d.target, visited));
|
||||
if (this.projectGraph.dependencies[targetProjectName]) {
|
||||
this.projectGraph.dependencies[targetProjectName].forEach((d) => {
|
||||
if (
|
||||
!visited.has(
|
||||
this.computeExternalDependencyIdentifier(
|
||||
targetProjectName,
|
||||
d.target
|
||||
)
|
||||
)
|
||||
) {
|
||||
partialHashes.push(
|
||||
this.hashExternalDependency(targetProjectName, d.target, visited)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
partialHash = hashArray(partialHashes);
|
||||
} else {
|
||||
// unknown dependency
|
||||
// this may occur if dependency is not an npm package
|
||||
// but rather symlinked in node_modules or it's pointing to a remote git repo
|
||||
// in this case we have no information about the versioning of the given package
|
||||
partialHash = `__${projectName}__`;
|
||||
partialHash = `__${targetProjectName}__`;
|
||||
}
|
||||
this.externalDepsHashCache[projectName] = partialHash;
|
||||
this.externalDepsHashCache[targetProjectName] = partialHash;
|
||||
return partialHash;
|
||||
}
|
||||
|
||||
@@ -470,7 +493,7 @@ class TaskHasherImpl {
|
||||
const executorPackage = target.executor.split(':')[0];
|
||||
const executorNodeName =
|
||||
this.findExternalDependencyNodeName(executorPackage);
|
||||
hash = this.hashExternalDependency(executorNodeName);
|
||||
hash = this.hashExternalDependency(projectName, executorNodeName);
|
||||
} else {
|
||||
// use command external dependencies if available to construct the hash
|
||||
const partialHashes: string[] = [];
|
||||
@@ -482,7 +505,7 @@ class TaskHasherImpl {
|
||||
const externalDependencies = input['externalDependencies'];
|
||||
for (let dep of externalDependencies) {
|
||||
dep = this.findExternalDependencyNodeName(dep);
|
||||
partialHashes.push(this.hashExternalDependency(dep));
|
||||
partialHashes.push(this.hashExternalDependency(projectName, dep));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+3
-4
@@ -37,14 +37,13 @@ export const enum WorkspaceErrors {
|
||||
Generic = 'Generic'
|
||||
}
|
||||
/** Get workspace config files based on provided globs */
|
||||
export function getConfigFiles(workspaceRoot: string, globs: Array<string>): Array<string>
|
||||
export function getProjectConfigurations(workspaceRoot: string, globs: Array<string>, parseConfigurations: (arg0: Array<string>) => Record<string, object>): Record<string, object>
|
||||
export interface NxWorkspaceFiles {
|
||||
projectFileMap: Record<string, Array<FileData>>
|
||||
globalFiles: Array<FileData>
|
||||
configFiles: Array<string>
|
||||
projectConfigurations: Record<string, object>
|
||||
}
|
||||
/** Throws exceptions */
|
||||
export function getWorkspaceFilesNative(workspaceRoot: string, globs: Array<string>): NxWorkspaceFiles
|
||||
export function getWorkspaceFilesNative(workspaceRoot: string, globs: Array<string>, parseConfigurations: (arg0: Array<string>) => Record<string, object>): NxWorkspaceFiles
|
||||
export class Watcher {
|
||||
origin: string
|
||||
/**
|
||||
|
||||
@@ -246,7 +246,7 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { expandOutputs, remove, copy, hashArray, hashFile, hashFiles, hashFilesMatchingGlobs, EventType, Watcher, WorkspaceErrors, getConfigFiles, getWorkspaceFilesNative } = nativeBinding
|
||||
const { expandOutputs, remove, copy, hashArray, hashFile, hashFiles, hashFilesMatchingGlobs, EventType, Watcher, WorkspaceErrors, getProjectConfigurations, getWorkspaceFilesNative } = nativeBinding
|
||||
|
||||
module.exports.expandOutputs = expandOutputs
|
||||
module.exports.remove = remove
|
||||
@@ -258,5 +258,5 @@ module.exports.hashFilesMatchingGlobs = hashFilesMatchingGlobs
|
||||
module.exports.EventType = EventType
|
||||
module.exports.Watcher = Watcher
|
||||
module.exports.WorkspaceErrors = WorkspaceErrors
|
||||
module.exports.getConfigFiles = getConfigFiles
|
||||
module.exports.getProjectConfigurations = getProjectConfigurations
|
||||
module.exports.getWorkspaceFilesNative = getWorkspaceFilesNative
|
||||
|
||||
@@ -1,12 +1,24 @@
|
||||
import {
|
||||
getConfigFiles,
|
||||
getWorkspaceFilesNative,
|
||||
WorkspaceErrors,
|
||||
} from '../index';
|
||||
import { getProjectConfigurations, getWorkspaceFilesNative } from '../index';
|
||||
import { TempFs } from '../../utils/testing/temp-fs';
|
||||
import { NxJsonConfiguration } from '../../config/nx-json';
|
||||
import { dirname, join } from 'path';
|
||||
import { readJsonFile } from '../../utils/fileutils';
|
||||
|
||||
describe('workspace files', () => {
|
||||
function createParseConfigurationsFunction(tempDir: string) {
|
||||
return (filenames: string[]) => {
|
||||
const res = {};
|
||||
for (const filename of filenames) {
|
||||
const json = readJsonFile(join(tempDir, filename));
|
||||
res[json.name] = {
|
||||
...json,
|
||||
root: dirname(filename),
|
||||
};
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
it('should gather workspace file information', async () => {
|
||||
const fs = new TempFs('workspace-files');
|
||||
const nxJson: NxJsonConfiguration = {};
|
||||
@@ -41,12 +53,16 @@ describe('workspace files', () => {
|
||||
});
|
||||
|
||||
let globs = ['project.json', '**/project.json', 'libs/*/package.json'];
|
||||
let { projectFileMap, configFiles, globalFiles } = getWorkspaceFilesNative(
|
||||
fs.tempDir,
|
||||
globs
|
||||
);
|
||||
let { projectFileMap, projectConfigurations, globalFiles } =
|
||||
getWorkspaceFilesNative(
|
||||
fs.tempDir,
|
||||
globs,
|
||||
createParseConfigurationsFunction(fs.tempDir)
|
||||
);
|
||||
|
||||
let sortedConfigs = configFiles.sort();
|
||||
let sortedConfigs = Object.values(projectConfigurations).sort((a, b) =>
|
||||
a['name'].localeCompare(b['name'])
|
||||
);
|
||||
|
||||
expect(projectFileMap).toMatchInlineSnapshot(`
|
||||
{
|
||||
@@ -104,11 +120,26 @@ describe('workspace files', () => {
|
||||
`);
|
||||
expect(sortedConfigs).toMatchInlineSnapshot(`
|
||||
[
|
||||
"libs/nested/project/project.json",
|
||||
"libs/package-project/package.json",
|
||||
"libs/project1/project.json",
|
||||
"libs/project2/project.json",
|
||||
"libs/project3/project.json",
|
||||
{
|
||||
"name": "nested-project",
|
||||
"root": "libs/nested/project",
|
||||
},
|
||||
{
|
||||
"name": "package-project",
|
||||
"root": "libs/package-project",
|
||||
},
|
||||
{
|
||||
"name": "project1",
|
||||
"root": "libs/project1",
|
||||
},
|
||||
{
|
||||
"name": "project2",
|
||||
"root": "libs/project2",
|
||||
},
|
||||
{
|
||||
"name": "project3",
|
||||
"root": "libs/project3",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(globalFiles).toMatchInlineSnapshot(`
|
||||
@@ -148,7 +179,8 @@ describe('workspace files', () => {
|
||||
const globs = ['project.json', '**/project.json', '**/package.json'];
|
||||
const { globalFiles, projectFileMap } = getWorkspaceFilesNative(
|
||||
fs.tempDir,
|
||||
globs
|
||||
globs,
|
||||
createParseConfigurationsFunction(fs.tempDir)
|
||||
);
|
||||
|
||||
expect(globalFiles).toEqual([]);
|
||||
@@ -201,140 +233,117 @@ describe('workspace files', () => {
|
||||
});
|
||||
|
||||
let globs = ['project.json', '**/project.json', '**/package.json'];
|
||||
let { configFiles } = getWorkspaceFilesNative(fs.tempDir, globs);
|
||||
|
||||
configFiles = configFiles.sort();
|
||||
|
||||
expect(configFiles).toMatchInlineSnapshot(`
|
||||
[
|
||||
"libs/project1/project.json",
|
||||
"project.json",
|
||||
]
|
||||
`);
|
||||
|
||||
let configFiles2 = getConfigFiles(fs.tempDir, globs).sort();
|
||||
expect(configFiles2).toMatchInlineSnapshot(`
|
||||
[
|
||||
"libs/project1/project.json",
|
||||
"project.json",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
describe('errors', () => {
|
||||
it('it should infer names of configuration files without a name', async () => {
|
||||
const fs = new TempFs('workspace-files');
|
||||
const nxJson: NxJsonConfiguration = {};
|
||||
await fs.createFiles({
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
'./package.json': JSON.stringify({
|
||||
name: 'repo-name',
|
||||
version: '0.0.0',
|
||||
dependencies: {},
|
||||
}),
|
||||
'./libs/project1/project.json': JSON.stringify({
|
||||
name: 'project1',
|
||||
}),
|
||||
'./libs/project1/index.js': '',
|
||||
'./libs/project2/project.json': JSON.stringify({}),
|
||||
});
|
||||
|
||||
let globs = ['project.json', '**/project.json', 'libs/*/package.json'];
|
||||
expect(getWorkspaceFilesNative(fs.tempDir, globs).projectFileMap)
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"project1": [
|
||||
{
|
||||
"file": "libs/project1/index.js",
|
||||
"hash": "3244421341483603138",
|
||||
},
|
||||
{
|
||||
"file": "libs/project1/project.json",
|
||||
"hash": "13466615737813422520",
|
||||
},
|
||||
],
|
||||
"project2": [
|
||||
{
|
||||
"file": "libs/project2/project.json",
|
||||
"hash": "1389868326933519382",
|
||||
},
|
||||
],
|
||||
let projectConfigurations = getProjectConfigurations(
|
||||
fs.tempDir,
|
||||
globs,
|
||||
(filenames) => {
|
||||
const res = {};
|
||||
for (const filename of filenames) {
|
||||
const json = readJsonFile(join(fs.tempDir, filename));
|
||||
res[json.name] = {
|
||||
...json,
|
||||
root: dirname(filename),
|
||||
};
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('handles comments', async () => {
|
||||
const fs = new TempFs('workspace-files');
|
||||
const nxJson: NxJsonConfiguration = {};
|
||||
await fs.createFiles({
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
'./package.json': JSON.stringify({
|
||||
name: 'repo-name',
|
||||
version: '0.0.0',
|
||||
dependencies: {},
|
||||
}),
|
||||
'./libs/project1/project.json': `{
|
||||
"name": "temp"
|
||||
// this should not fail
|
||||
}`,
|
||||
'./libs/project1/index.js': '',
|
||||
});
|
||||
|
||||
let globs = ['project.json', '**/project.json', 'libs/*/package.json'];
|
||||
expect(() => getWorkspaceFilesNative(fs.tempDir, globs)).not.toThrow();
|
||||
});
|
||||
|
||||
it('handles extra comma', async () => {
|
||||
const fs = new TempFs('workspace-files');
|
||||
const nxJson: NxJsonConfiguration = {};
|
||||
await fs.createFiles({
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
'./package.json': JSON.stringify({
|
||||
name: 'repo-name',
|
||||
version: '0.0.0',
|
||||
dependencies: {},
|
||||
}),
|
||||
'./libs/project1/project.json': `{
|
||||
"name": "temp",
|
||||
}`,
|
||||
'./libs/project1/index.js': '',
|
||||
});
|
||||
|
||||
let globs = ['**/project.json'];
|
||||
expect(() => getWorkspaceFilesNative(fs.tempDir, globs)).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws parsing errors: missing brackets', async () => {
|
||||
const fs = new TempFs('workspace-files');
|
||||
const nxJson: NxJsonConfiguration = {};
|
||||
await fs.createFiles({
|
||||
'./nx.json': JSON.stringify(nxJson),
|
||||
'./package.json': JSON.stringify({
|
||||
name: 'repo-name',
|
||||
version: '0.0.0',
|
||||
dependencies: {},
|
||||
}),
|
||||
'./libs/project1/project.json': `{
|
||||
"name": "temp", "property": "child": 2 }
|
||||
}`,
|
||||
'./libs/project1/index.js': '',
|
||||
});
|
||||
|
||||
let globs = ['**/project.json'];
|
||||
|
||||
const error = getError(() => getWorkspaceFilesNative(fs.tempDir, globs));
|
||||
expect(error.message).toMatchInlineSnapshot(
|
||||
`"libs/project1/project.json"`
|
||||
);
|
||||
expect(error).toHaveProperty('code', WorkspaceErrors.ParseError);
|
||||
});
|
||||
return res;
|
||||
}
|
||||
);
|
||||
expect(projectConfigurations).toMatchInlineSnapshot(`
|
||||
{
|
||||
"project1": {
|
||||
"name": "project1",
|
||||
"root": "libs/project1",
|
||||
},
|
||||
"repo-name": {
|
||||
"name": "repo-name",
|
||||
"root": ".",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
const getError = (fn: () => unknown): Error => {
|
||||
try {
|
||||
fn();
|
||||
} catch (error: unknown) {
|
||||
return error as Error;
|
||||
}
|
||||
};
|
||||
// describe('errors', () => {
|
||||
// it('it should infer names of configuration files without a name', async () => {
|
||||
// const fs = new TempFs('workspace-files');
|
||||
// const nxJson: NxJsonConfiguration = {};
|
||||
// await fs.createFiles({
|
||||
// './nx.json': JSON.stringify(nxJson),
|
||||
// './package.json': JSON.stringify({
|
||||
// name: 'repo-name',
|
||||
// version: '0.0.0',
|
||||
// dependencies: {},
|
||||
// }),
|
||||
// './libs/project1/project.json': JSON.stringify({
|
||||
// name: 'project1',
|
||||
// }),
|
||||
// './libs/project1/index.js': '',
|
||||
// './libs/project2/project.json': JSON.stringify({}),
|
||||
// });
|
||||
//
|
||||
// let globs = ['project.json', '**/project.json', 'libs/*/package.json'];
|
||||
// expect(getWorkspaceFilesNative(fs.tempDir, globs).projectFileMap)
|
||||
// .toMatchInlineSnapshot(`
|
||||
// {
|
||||
// "libs/project1": [
|
||||
// {
|
||||
// "file": "libs/project1/index.js",
|
||||
// "hash": "3244421341483603138",
|
||||
// },
|
||||
// {
|
||||
// "file": "libs/project1/project.json",
|
||||
// "hash": "13466615737813422520",
|
||||
// },
|
||||
// ],
|
||||
// "libs/project2": [
|
||||
// {
|
||||
// "file": "libs/project2/project.json",
|
||||
// "hash": "1389868326933519382",
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
// `);
|
||||
// });
|
||||
//
|
||||
// it('handles comments', async () => {
|
||||
// const fs = new TempFs('workspace-files');
|
||||
// const nxJson: NxJsonConfiguration = {};
|
||||
// await fs.createFiles({
|
||||
// './nx.json': JSON.stringify(nxJson),
|
||||
// './package.json': JSON.stringify({
|
||||
// name: 'repo-name',
|
||||
// version: '0.0.0',
|
||||
// dependencies: {},
|
||||
// }),
|
||||
// './libs/project1/project.json': `{
|
||||
// "name": "temp"
|
||||
// // this should not fail
|
||||
// }`,
|
||||
// './libs/project1/index.js': '',
|
||||
// });
|
||||
//
|
||||
// let globs = ['project.json', '**/project.json', 'libs/*/package.json'];
|
||||
// expect(() => getWorkspaceFilesNative(fs.tempDir, globs)).not.toThrow();
|
||||
// });
|
||||
//
|
||||
// it('handles extra comma', async () => {
|
||||
// const fs = new TempFs('workspace-files');
|
||||
// const nxJson: NxJsonConfiguration = {};
|
||||
// await fs.createFiles({
|
||||
// './nx.json': JSON.stringify(nxJson),
|
||||
// './package.json': JSON.stringify({
|
||||
// name: 'repo-name',
|
||||
// version: '0.0.0',
|
||||
// dependencies: {},
|
||||
// }),
|
||||
// './libs/project1/project.json': `{
|
||||
// "name": "temp",
|
||||
// }`,
|
||||
// './libs/project1/index.js': '',
|
||||
// });
|
||||
//
|
||||
// let globs = ['**/project.json'];
|
||||
// expect(() => getWorkspaceFilesNative(fs.tempDir, globs)).not.toThrow();
|
||||
// });
|
||||
// });
|
||||
});
|
||||
|
||||
@@ -20,6 +20,10 @@ fn normalize_path<P>(path: P) -> String
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
if path.as_ref() == Path::new("") {
|
||||
return ".".into();
|
||||
}
|
||||
|
||||
// convert back-slashes in Windows paths, since the js expects only forward-slash path separators
|
||||
if cfg!(windows) {
|
||||
path.as_ref().display().to_string().replace('\\', "/")
|
||||
|
||||
@@ -36,7 +36,6 @@ pub(super) async fn create_runtime(
|
||||
runtime.filterer(Arc::new(WatchFilterer {
|
||||
inner: IgnoreFilterer(filter),
|
||||
}));
|
||||
runtime.action_throttle(Duration::from_millis(500));
|
||||
|
||||
// let watch_directories = get_watch_directories(origin);
|
||||
// trace!(directories = ?watch_directories, "watching");
|
||||
|
||||
@@ -2,29 +2,42 @@ use crate::native::utils::glob::build_glob_set;
|
||||
use crate::native::utils::path::Normalize;
|
||||
use crate::native::walker::nx_walker;
|
||||
use globset::GlobSet;
|
||||
|
||||
use napi::JsObject;
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[napi]
|
||||
/// Get workspace config files based on provided globs
|
||||
pub fn get_config_files(workspace_root: String, globs: Vec<String>) -> anyhow::Result<Vec<String>> {
|
||||
pub fn get_project_configurations<ConfigurationParser>(
|
||||
workspace_root: String,
|
||||
globs: Vec<String>,
|
||||
|
||||
parse_configurations: ConfigurationParser,
|
||||
) -> napi::Result<HashMap<String, JsObject>>
|
||||
where
|
||||
ConfigurationParser: Fn(Vec<String>) -> napi::Result<HashMap<String, JsObject>>,
|
||||
{
|
||||
let globs = build_glob_set(globs)?;
|
||||
Ok(nx_walker(workspace_root, move |rec| {
|
||||
let mut config_paths: HashMap<PathBuf, (PathBuf, Vec<u8>)> = HashMap::new();
|
||||
for (path, content) in rec {
|
||||
insert_config_file_into_map((path, content), &mut config_paths, &globs);
|
||||
let config_paths: Vec<String> = nx_walker(workspace_root, move |rec| {
|
||||
let mut config_paths: HashMap<PathBuf, PathBuf> = HashMap::new();
|
||||
for (path, _) in rec {
|
||||
insert_config_file_into_map(path, &mut config_paths, &globs);
|
||||
}
|
||||
|
||||
config_paths
|
||||
.into_iter()
|
||||
.map(|(_, (val, _))| val.to_normalized_string())
|
||||
.into_values()
|
||||
.map(|p| p.to_normalized_string())
|
||||
.collect()
|
||||
}))
|
||||
});
|
||||
|
||||
parse_configurations(config_paths)
|
||||
}
|
||||
|
||||
pub fn insert_config_file_into_map(
|
||||
(path, content): (PathBuf, Vec<u8>),
|
||||
config_paths: &mut HashMap<PathBuf, (PathBuf, Vec<u8>)>,
|
||||
path: PathBuf,
|
||||
config_paths: &mut HashMap<PathBuf, PathBuf>,
|
||||
globs: &GlobSet,
|
||||
) {
|
||||
if globs.is_match(&path) {
|
||||
@@ -34,25 +47,24 @@ pub fn insert_config_file_into_map(
|
||||
.file_name()
|
||||
.expect("Config paths always have file names");
|
||||
if file_name == "project.json" {
|
||||
config_paths.insert(parent, (path, content));
|
||||
config_paths.insert(parent, path);
|
||||
} else if file_name == "package.json" {
|
||||
match config_paths.entry(parent) {
|
||||
Entry::Occupied(mut o) => {
|
||||
if o.get()
|
||||
.0
|
||||
.file_name()
|
||||
.expect("Config paths always have file names")
|
||||
!= "project.json"
|
||||
{
|
||||
o.insert((path, content));
|
||||
o.insert(path);
|
||||
}
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
v.insert((path, content));
|
||||
v.insert(path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
config_paths.entry(parent).or_insert((path, content));
|
||||
config_paths.entry(parent).or_insert(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,34 +77,23 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn should_insert_config_files_properly() {
|
||||
let mut config_paths: HashMap<PathBuf, (PathBuf, Vec<u8>)> = HashMap::new();
|
||||
let mut config_paths: HashMap<PathBuf, PathBuf> = HashMap::new();
|
||||
let globs = build_glob_set(vec!["**/*".into()]).unwrap();
|
||||
|
||||
insert_config_file_into_map(PathBuf::from("project.json"), &mut config_paths, &globs);
|
||||
insert_config_file_into_map(PathBuf::from("package.json"), &mut config_paths, &globs);
|
||||
insert_config_file_into_map(
|
||||
(PathBuf::from("project.json"), vec![]),
|
||||
PathBuf::from("lib1/project.json"),
|
||||
&mut config_paths,
|
||||
&globs,
|
||||
);
|
||||
insert_config_file_into_map(
|
||||
(PathBuf::from("package.json"), vec![]),
|
||||
&mut config_paths,
|
||||
&globs,
|
||||
);
|
||||
insert_config_file_into_map(
|
||||
(PathBuf::from("lib1/project.json"), vec![]),
|
||||
&mut config_paths,
|
||||
&globs,
|
||||
);
|
||||
insert_config_file_into_map(
|
||||
(PathBuf::from("lib2/package.json"), vec![]),
|
||||
PathBuf::from("lib2/package.json"),
|
||||
&mut config_paths,
|
||||
&globs,
|
||||
);
|
||||
|
||||
let config_files: Vec<PathBuf> = config_paths
|
||||
.into_iter()
|
||||
.map(|(_, (path, _))| path)
|
||||
.collect();
|
||||
let config_files: Vec<PathBuf> = config_paths.into_values().collect();
|
||||
|
||||
assert!(config_files.contains(&PathBuf::from("project.json")));
|
||||
assert!(config_files.contains(&PathBuf::from("lib1/project.json")));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use jsonc_parser::ParseOptions;
|
||||
use std::collections::HashMap;
|
||||
use itertools::Itertools;
|
||||
use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode};
|
||||
use napi::{JsFunction, JsObject, JsUnknown, Status};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rayon::prelude::*;
|
||||
@@ -13,21 +15,24 @@ use crate::native::utils::path::Normalize;
|
||||
use crate::native::walker::nx_walker;
|
||||
use crate::native::workspace::errors::{InternalWorkspaceErrors, WorkspaceErrors};
|
||||
use crate::native::workspace::get_config_files::insert_config_file_into_map;
|
||||
use crate::native::workspace::types::{FileLocation, ProjectConfiguration};
|
||||
use crate::native::workspace::types::FileLocation;
|
||||
|
||||
#[napi(object)]
|
||||
pub struct NxWorkspaceFiles {
|
||||
pub project_file_map: HashMap<String, Vec<FileData>>,
|
||||
pub global_files: Vec<FileData>,
|
||||
pub config_files: Vec<String>,
|
||||
pub project_configurations: HashMap<String, JsObject>,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
/// Throws exceptions
|
||||
pub fn get_workspace_files_native(
|
||||
pub fn get_workspace_files_native<ConfigurationParser>(
|
||||
workspace_root: String,
|
||||
globs: Vec<String>,
|
||||
) -> napi::Result<NxWorkspaceFiles, WorkspaceErrors> {
|
||||
parse_configurations: ConfigurationParser,
|
||||
) -> napi::Result<NxWorkspaceFiles, WorkspaceErrors>
|
||||
where
|
||||
ConfigurationParser: Fn(Vec<String>) -> napi::Result<HashMap<String, JsObject>>,
|
||||
{
|
||||
enable_logger();
|
||||
|
||||
trace!("{workspace_root}, {globs:?}");
|
||||
@@ -35,7 +40,12 @@ pub fn get_workspace_files_native(
|
||||
let (projects, mut file_data) = get_file_data(&workspace_root, globs)
|
||||
.map_err(|err| napi::Error::new(WorkspaceErrors::Generic, err.to_string()))?;
|
||||
|
||||
let root_map = create_root_map(&projects)?;
|
||||
let projects_vec: Vec<String> = projects.iter().map(|p| p.to_normalized_string()).collect();
|
||||
|
||||
let project_configurations = parse_configurations(projects_vec)
|
||||
.map_err(|e| napi::Error::new(WorkspaceErrors::ParseError, e.to_string()))?;
|
||||
|
||||
let root_map = create_root_map(&project_configurations);
|
||||
|
||||
trace!(?root_map);
|
||||
|
||||
@@ -46,14 +56,14 @@ pub fn get_workspace_files_native(
|
||||
.into_par_iter()
|
||||
.map(|file_data| {
|
||||
let file_path = Path::new(&file_data.file);
|
||||
let mut parent = file_path.parent().unwrap_or_else(|| Path::new(""));
|
||||
let mut parent = file_path.parent().unwrap_or_else(|| Path::new("."));
|
||||
|
||||
while root_map.get(parent).is_none() && parent != Path::new("") {
|
||||
parent = parent.parent().unwrap_or_else(|| Path::new(""));
|
||||
while root_map.get(parent).is_none() && parent != Path::new(".") {
|
||||
parent = parent.parent().unwrap_or_else(|| Path::new("."));
|
||||
}
|
||||
|
||||
match root_map.get(parent) {
|
||||
Some(project_name) => (FileLocation::Project(project_name.clone()), file_data),
|
||||
Some(project_name) => (FileLocation::Project(project_name.into()), file_data),
|
||||
None => (FileLocation::Global, file_data),
|
||||
}
|
||||
})
|
||||
@@ -76,7 +86,7 @@ pub fn get_workspace_files_native(
|
||||
FileLocation::Global => global_files.push(file_data),
|
||||
FileLocation::Project(project_name) => match project_file_map.get_mut(&project_name) {
|
||||
None => {
|
||||
project_file_map.insert(project_name, vec![file_data]);
|
||||
project_file_map.insert(project_name.clone(), vec![file_data]);
|
||||
}
|
||||
Some(project_files) => project_files.push(file_data),
|
||||
},
|
||||
@@ -86,98 +96,34 @@ pub fn get_workspace_files_native(
|
||||
Ok(NxWorkspaceFiles {
|
||||
project_file_map,
|
||||
global_files,
|
||||
config_files: projects
|
||||
.keys()
|
||||
.map(|path| path.to_normalized_string())
|
||||
.collect(),
|
||||
project_configurations,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_root_map(
|
||||
projects: &HashMap<PathBuf, Vec<u8>>,
|
||||
) -> Result<hashbrown::HashMap<&Path, String>, InternalWorkspaceErrors> {
|
||||
projects
|
||||
.par_iter()
|
||||
.map(|(path, content)| {
|
||||
let file_name = path
|
||||
.file_name()
|
||||
.expect("path should always have a filename");
|
||||
return if file_name == "project.json" || file_name == "package.json" {
|
||||
// use serde_json to do the initial parse, if that fails fall back to jsonc_parser.
|
||||
// If all those fail, expose the error from jsonc_parser
|
||||
let project_configuration: ProjectConfiguration =
|
||||
read_project_configuration(content, path)?;
|
||||
|
||||
let Some(parent_path) = path.parent() else {
|
||||
return Err(InternalWorkspaceErrors::Generic {
|
||||
msg: format!("{path:?} has no parent"),
|
||||
})
|
||||
};
|
||||
|
||||
let name: String = if let Some(name) = project_configuration.name {
|
||||
Ok(name)
|
||||
} else {
|
||||
parent_path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.map_err(|os_string| InternalWorkspaceErrors::Generic {
|
||||
msg: format!("Cannot turn {os_string:?} into String"),
|
||||
})
|
||||
}?;
|
||||
Ok((parent_path, name))
|
||||
} else if let Some(parent_path) = path.parent() {
|
||||
Ok((
|
||||
parent_path,
|
||||
parent_path
|
||||
.file_name()
|
||||
.unwrap_or_default()
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.map_err(|os_string| InternalWorkspaceErrors::Generic {
|
||||
msg: format!("Cannot turn {os_string:?} into String"),
|
||||
})?,
|
||||
))
|
||||
} else {
|
||||
Err(InternalWorkspaceErrors::Generic {
|
||||
msg: format!("{path:?} has no parent"),
|
||||
})
|
||||
};
|
||||
project_configurations: &HashMap<String, JsObject>,
|
||||
) -> hashbrown::HashMap<PathBuf, String> {
|
||||
project_configurations
|
||||
.iter()
|
||||
.map(|(project_name, project_configuration)| {
|
||||
let root: String = project_configuration.get("root").unwrap().unwrap();
|
||||
(PathBuf::from(root), project_name.clone())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_project_configuration(
|
||||
content: &[u8],
|
||||
path: &Path,
|
||||
) -> Result<ProjectConfiguration, InternalWorkspaceErrors> {
|
||||
serde_json::from_slice(content).or_else(|_| {
|
||||
let content_str = std::str::from_utf8(content).expect("content should be valid utf8");
|
||||
let parser_value =
|
||||
jsonc_parser::parse_to_serde_value(content_str, &ParseOptions::default()).map_err(
|
||||
|_| InternalWorkspaceErrors::ParseError {
|
||||
file: PathBuf::from(path),
|
||||
},
|
||||
)?;
|
||||
serde_json::from_value(parser_value.into()).map_err(|_| InternalWorkspaceErrors::Generic {
|
||||
msg: format!("Failed to parse {path:?}"),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type WorkspaceData = (HashMap<PathBuf, Vec<u8>>, Vec<FileData>);
|
||||
type WorkspaceData = (HashSet<PathBuf>, Vec<FileData>);
|
||||
fn get_file_data(workspace_root: &str, globs: Vec<String>) -> anyhow::Result<WorkspaceData> {
|
||||
let globs = build_glob_set(globs)?;
|
||||
let (projects, file_data) = nx_walker(workspace_root, move |rec| {
|
||||
let mut projects: HashMap<PathBuf, (PathBuf, Vec<u8>)> = HashMap::new();
|
||||
let mut projects: HashMap<PathBuf, PathBuf> = HashMap::new();
|
||||
let mut file_hashes: Vec<FileData> = vec![];
|
||||
for (path, content) in rec {
|
||||
file_hashes.push(FileData {
|
||||
file: path.to_normalized_string(),
|
||||
hash: xxh3::xxh3_64(&content).to_string(),
|
||||
});
|
||||
insert_config_file_into_map((path, content), &mut projects, &globs)
|
||||
insert_config_file_into_map(path, &mut projects, &globs)
|
||||
}
|
||||
(projects, file_hashes)
|
||||
});
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct ProjectConfiguration {
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum FileLocation {
|
||||
Global,
|
||||
|
||||
@@ -43,11 +43,19 @@ export function createPackageJson(
|
||||
const projectNode = graph.nodes[projectName];
|
||||
const isLibrary = projectNode.type === 'lib';
|
||||
|
||||
const rootPackageJson = readJsonFile(
|
||||
`${options.root || workspaceRoot}/package.json`
|
||||
);
|
||||
|
||||
const npmDeps = findProjectsNpmDependencies(
|
||||
projectNode,
|
||||
graph,
|
||||
options.target,
|
||||
{ helperDependencies: options.helperDependencies },
|
||||
rootPackageJson,
|
||||
{
|
||||
helperDependencies: options.helperDependencies,
|
||||
isProduction: options.isProduction,
|
||||
},
|
||||
fileMap
|
||||
);
|
||||
|
||||
@@ -92,9 +100,6 @@ export function createPackageJson(
|
||||
);
|
||||
};
|
||||
|
||||
const rootPackageJson = readJsonFile(
|
||||
`${options.root || workspaceRoot}/package.json`
|
||||
);
|
||||
Object.entries(npmDeps.dependencies).forEach(([packageName, version]) => {
|
||||
if (
|
||||
rootPackageJson.devDependencies?.[packageName] &&
|
||||
@@ -179,9 +184,11 @@ export function findProjectsNpmDependencies(
|
||||
projectNode: ProjectGraphProjectNode,
|
||||
graph: ProjectGraph,
|
||||
target: string,
|
||||
rootPackageJson: PackageJson,
|
||||
options: {
|
||||
helperDependencies?: string[];
|
||||
ignoredDependencies?: string[];
|
||||
isProduction?: boolean;
|
||||
},
|
||||
fileMap?: ProjectFileMap
|
||||
): NpmDeps {
|
||||
@@ -208,13 +215,22 @@ export function findProjectsNpmDependencies(
|
||||
recursivelyCollectPeerDependencies(dep, graph, npmDeps, seen);
|
||||
});
|
||||
|
||||
// if it's production, we want to ignore all found devDependencies
|
||||
const ignoredDependencies =
|
||||
options.isProduction && rootPackageJson.devDependencies
|
||||
? [
|
||||
...(options.ignoredDependencies || []),
|
||||
...Object.keys(rootPackageJson.devDependencies),
|
||||
]
|
||||
: options.ignoredDependencies || [];
|
||||
|
||||
findAllNpmDeps(
|
||||
fileMap,
|
||||
projectNode,
|
||||
graph,
|
||||
npmDeps,
|
||||
seen,
|
||||
options.ignoredDependencies || [],
|
||||
ignoredDependencies,
|
||||
dependencyInputs,
|
||||
selfInputs
|
||||
);
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { ProjectType } from '../config/workspace-json-project-json';
|
||||
import { createProjectFileMap, updateProjectFileMap } from './file-map-utils';
|
||||
|
||||
describe('fileMapUtils', () => {
|
||||
describe('createFileMap', () => {
|
||||
it('should map files to projects', () => {
|
||||
const projectsConfigurations = {
|
||||
version: 2,
|
||||
projects: {
|
||||
demo: {
|
||||
root: 'apps/demo',
|
||||
sourceRoot: 'apps/demo/src',
|
||||
projectType: 'application',
|
||||
projectType: 'application' as ProjectType,
|
||||
},
|
||||
'demo-e2e': {
|
||||
root: 'apps/demo-e2e',
|
||||
sourceRoot: 'apps/demo-e2e/src',
|
||||
projectType: 'application',
|
||||
projectType: 'application' as ProjectType,
|
||||
},
|
||||
ui: {
|
||||
root: 'libs/ui',
|
||||
sourceRoot: 'libs/ui/src',
|
||||
projectType: 'library',
|
||||
projectType: 'library' as ProjectType,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -29,7 +31,7 @@ describe('fileMapUtils', () => {
|
||||
{ file: 'tools/myfile.txt', hash: 'some-hash' },
|
||||
];
|
||||
|
||||
const result = createProjectFileMap(projectsConfigurations as any, files);
|
||||
const result = createProjectFileMap(projectsConfigurations, files);
|
||||
|
||||
expect(result).toEqual({
|
||||
projectFileMap: {
|
||||
@@ -52,22 +54,20 @@ describe('fileMapUtils', () => {
|
||||
describe('updateFileMap', () => {
|
||||
it('should map files to projects', () => {
|
||||
const projectsConfigurations = {
|
||||
projects: {
|
||||
demo: {
|
||||
root: 'apps/demo',
|
||||
sourceRoot: 'apps/demo/src',
|
||||
projectType: 'application',
|
||||
},
|
||||
'demo-e2e': {
|
||||
root: 'apps/demo-e2e',
|
||||
sourceRoot: 'apps/demo-e2e/src',
|
||||
projectType: 'application',
|
||||
},
|
||||
ui: {
|
||||
root: 'libs/ui',
|
||||
sourceRoot: 'libs/ui/src',
|
||||
projectType: 'library',
|
||||
},
|
||||
demo: {
|
||||
root: 'apps/demo',
|
||||
sourceRoot: 'apps/demo/src',
|
||||
projectType: 'application' as ProjectType,
|
||||
},
|
||||
'demo-e2e': {
|
||||
root: 'apps/demo-e2e',
|
||||
sourceRoot: 'apps/demo-e2e/src',
|
||||
projectType: 'application' as ProjectType,
|
||||
},
|
||||
ui: {
|
||||
root: 'libs/ui',
|
||||
sourceRoot: 'libs/ui/src',
|
||||
projectType: 'library' as ProjectType,
|
||||
},
|
||||
};
|
||||
const files = [
|
||||
@@ -87,7 +87,7 @@ describe('fileMapUtils', () => {
|
||||
],
|
||||
};
|
||||
const result = updateProjectFileMap(
|
||||
projectsConfigurations as any,
|
||||
projectsConfigurations,
|
||||
projectFileMap,
|
||||
files,
|
||||
new Map([
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
createProjectRootMappingsFromProjectConfigurations,
|
||||
findProjectForPath,
|
||||
} from './utils/find-project-for-path';
|
||||
import { ProjectsConfigurations } from '../config/workspace-json-project-json';
|
||||
import {
|
||||
ProjectConfiguration,
|
||||
ProjectsConfigurations,
|
||||
} from '../config/workspace-json-project-json';
|
||||
import { daemonClient } from '../daemon/client/client';
|
||||
import { readProjectsConfigurationFromProjectGraph } from './project-graph';
|
||||
import { fileHasher } from '../hasher/file-hasher';
|
||||
@@ -52,16 +55,14 @@ export function createProjectFileMap(
|
||||
}
|
||||
|
||||
export function updateProjectFileMap(
|
||||
projectsConfigurations: ProjectsConfigurations,
|
||||
projectsConfigurations: Record<string, ProjectConfiguration>,
|
||||
projectFileMap: ProjectFileMap,
|
||||
allWorkspaceFiles: FileData[],
|
||||
updatedFiles: Map<string, string>,
|
||||
deletedFiles: string[]
|
||||
): { projectFileMap: ProjectFileMap; allWorkspaceFiles: FileData[] } {
|
||||
const projectRootMappings =
|
||||
createProjectRootMappingsFromProjectConfigurations(
|
||||
projectsConfigurations.projects
|
||||
);
|
||||
createProjectRootMappingsFromProjectConfigurations(projectsConfigurations);
|
||||
|
||||
for (const f of updatedFiles.keys()) {
|
||||
const matchingProjectFiles =
|
||||
|
||||
@@ -9,14 +9,17 @@ import {
|
||||
import { getNxRequirePaths } from '../../utils/installation-directory';
|
||||
import { readJsonFile } from '../../utils/fileutils';
|
||||
import { join } from 'path';
|
||||
import { ProjectsConfigurations } from '../../config/workspace-json-project-json';
|
||||
import {
|
||||
ProjectConfiguration,
|
||||
ProjectsConfigurations,
|
||||
} from '../../config/workspace-json-project-json';
|
||||
import {
|
||||
mergeAngularJsonAndProjects,
|
||||
shouldMergeAngularProjects,
|
||||
} from '../../adapter/angular-json';
|
||||
import { NxJsonConfiguration } from '../../config/nx-json';
|
||||
import { FileData, ProjectFileMap } from '../../config/project-graph';
|
||||
import { NxWorkspaceFiles, WorkspaceErrors } from '../../native';
|
||||
import type { NxWorkspaceFiles } from '../../native';
|
||||
|
||||
/**
|
||||
* Walks the workspace directory to create the `projectFileMap`, `ProjectConfigurations` and `allWorkspaceFiles`
|
||||
@@ -40,19 +43,21 @@ export async function retrieveWorkspaceFiles(
|
||||
);
|
||||
|
||||
performance.mark('get-workspace-files:start');
|
||||
let workspaceFiles: NxWorkspaceFiles;
|
||||
try {
|
||||
workspaceFiles = getWorkspaceFilesNative(workspaceRoot, globs);
|
||||
} catch (e) {
|
||||
// If the error is a parse error from Rust, then use the JS readJsonFile function to write a pretty error message
|
||||
if (e.code === WorkspaceErrors.ParseError) {
|
||||
readJsonFile(join(workspaceRoot, e.message));
|
||||
// readJsonFile should always fail, but if it doesn't, then throw the original error
|
||||
throw e;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
const { projectConfigurations, projectFileMap, globalFiles } =
|
||||
getWorkspaceFilesNative(
|
||||
workspaceRoot,
|
||||
globs,
|
||||
(configs: string[]): Record<string, ProjectConfiguration> => {
|
||||
const projectConfigurations = createProjectConfigurations(
|
||||
workspaceRoot,
|
||||
nxJson,
|
||||
configs
|
||||
);
|
||||
|
||||
return projectConfigurations.projects;
|
||||
}
|
||||
) as NxWorkspaceFiles;
|
||||
performance.mark('get-workspace-files:end');
|
||||
performance.measure(
|
||||
'get-workspace-files',
|
||||
@@ -61,16 +66,12 @@ export async function retrieveWorkspaceFiles(
|
||||
);
|
||||
|
||||
return {
|
||||
allWorkspaceFiles: buildAllWorkspaceFiles(
|
||||
workspaceFiles.projectFileMap,
|
||||
workspaceFiles.globalFiles
|
||||
),
|
||||
projectFileMap: workspaceFiles.projectFileMap,
|
||||
projectConfigurations: createProjectConfigurations(
|
||||
workspaceRoot,
|
||||
nxJson,
|
||||
workspaceFiles.configFiles
|
||||
),
|
||||
allWorkspaceFiles: buildAllWorkspaceFiles(projectFileMap, globalFiles),
|
||||
projectFileMap,
|
||||
projectConfigurations: {
|
||||
version: 2,
|
||||
projects: projectConfigurations,
|
||||
} as ProjectsConfigurations,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,11 +84,22 @@ export async function retrieveWorkspaceFiles(
|
||||
export async function retrieveProjectConfigurations(
|
||||
workspaceRoot: string,
|
||||
nxJson: NxJsonConfiguration
|
||||
) {
|
||||
const { getConfigFiles } = require('../../native');
|
||||
): Promise<Record<string, ProjectConfiguration>> {
|
||||
const { getProjectConfigurations } = require('../../native');
|
||||
const globs = await configurationGlobs(workspaceRoot, nxJson);
|
||||
const configPaths = getConfigFiles(workspaceRoot, globs);
|
||||
return createProjectConfigurations(workspaceRoot, nxJson, configPaths);
|
||||
return getProjectConfigurations(
|
||||
workspaceRoot,
|
||||
globs,
|
||||
(configs: string[]): Record<string, ProjectConfiguration> => {
|
||||
const projectConfigurations = createProjectConfigurations(
|
||||
workspaceRoot,
|
||||
nxJson,
|
||||
configs
|
||||
);
|
||||
|
||||
return projectConfigurations.projects;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function buildAllWorkspaceFiles(
|
||||
@@ -97,7 +109,7 @@ function buildAllWorkspaceFiles(
|
||||
performance.mark('get-all-workspace-files:start');
|
||||
let fileData = Object.values(projectFileMap).flat();
|
||||
|
||||
fileData.push(...globalFiles);
|
||||
fileData = fileData.concat(globalFiles);
|
||||
performance.mark('get-all-workspace-files:end');
|
||||
performance.measure(
|
||||
'get-all-workspace-files',
|
||||
@@ -123,8 +135,8 @@ function createProjectConfigurations(
|
||||
);
|
||||
|
||||
if (shouldMergeAngularProjects(workspaceRoot, false)) {
|
||||
projectConfigurations.projects = mergeAngularJsonAndProjects(
|
||||
projectConfigurations.projects,
|
||||
projectConfigurations = mergeAngularJsonAndProjects(
|
||||
projectConfigurations,
|
||||
workspaceRoot
|
||||
);
|
||||
}
|
||||
@@ -135,14 +147,17 @@ function createProjectConfigurations(
|
||||
'build-project-configs:end'
|
||||
);
|
||||
|
||||
return projectConfigurations;
|
||||
return {
|
||||
version: 2,
|
||||
projects: projectConfigurations,
|
||||
};
|
||||
}
|
||||
|
||||
function mergeTargetDefaultsIntoProjectDescriptions(
|
||||
config: ProjectsConfigurations,
|
||||
projects: Record<string, ProjectConfiguration>,
|
||||
nxJson: NxJsonConfiguration
|
||||
) {
|
||||
for (const proj of Object.values(config.projects)) {
|
||||
for (const proj of Object.values(projects)) {
|
||||
if (proj.targets) {
|
||||
for (const targetName of Object.keys(proj.targets)) {
|
||||
const projectTargetDefinition = proj.targets[targetName];
|
||||
@@ -162,7 +177,7 @@ function mergeTargetDefaultsIntoProjectDescriptions(
|
||||
}
|
||||
}
|
||||
}
|
||||
return config;
|
||||
return projects;
|
||||
}
|
||||
|
||||
async function configurationGlobs(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { workspaceRoot } from '../utils/workspace-root';
|
||||
import { mkdir, mkdirSync, pathExists, readFile, writeFile } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { DefaultTasksRunnerOptions } from './default-tasks-runner';
|
||||
import { spawn } from 'child_process';
|
||||
import { cacheDir } from '../utils/cache-directory';
|
||||
|
||||
@@ -388,10 +388,10 @@ export function createTaskGraph(
|
||||
}
|
||||
|
||||
export function mapTargetDefaultsToDependencies(
|
||||
defaults: TargetDefaults
|
||||
defaults: TargetDefaults | undefined
|
||||
): TargetDependencies {
|
||||
const res = {};
|
||||
Object.keys(defaults).forEach((k) => {
|
||||
Object.keys(defaults ?? {}).forEach((k) => {
|
||||
res[k] = defaults[k].dependsOn;
|
||||
});
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ export async function recordStat(opts: {
|
||||
if (process.env.NX_VERBOSE_LOGGING === 'true') {
|
||||
console.log(`Record stat. Major: ${major}`);
|
||||
}
|
||||
if (major < 10 || major > 15) return; // test version, skip it
|
||||
if (major < 10 || major > 16) return; // test version, skip it
|
||||
const axios = require('axios');
|
||||
await (axios['default'] ?? axios)
|
||||
.create({
|
||||
|
||||
@@ -2,8 +2,8 @@ import {
|
||||
findMatchingProjects,
|
||||
getMatchingStringsWithCache,
|
||||
} from './find-matching-projects';
|
||||
import minimatch = require('minimatch');
|
||||
import type { ProjectGraphProjectNode } from '../config/project-graph';
|
||||
import minimatch = require('minimatch');
|
||||
|
||||
describe('findMatchingProjects', () => {
|
||||
let projectGraph: Record<string, ProjectGraphProjectNode> = {
|
||||
|
||||
@@ -15,6 +15,7 @@ const execAsync = promisify(exec);
|
||||
export type PackageManager = 'yarn' | 'pnpm' | 'npm';
|
||||
|
||||
export interface PackageManagerCommands {
|
||||
preInstall?: string;
|
||||
install: string;
|
||||
ciInstall: string;
|
||||
add: string;
|
||||
@@ -60,10 +61,13 @@ export function getPackageManagerCommand(
|
||||
): PackageManagerCommands {
|
||||
const commands: { [pm in PackageManager]: () => PackageManagerCommands } = {
|
||||
yarn: () => {
|
||||
const yarnVersion = getPackageManagerVersion('yarn');
|
||||
const yarnVersion = getPackageManagerVersion('yarn', root);
|
||||
const useBerry = gte(yarnVersion, '2.0.0');
|
||||
|
||||
return {
|
||||
preInstall: useBerry
|
||||
? 'yarn set version stable'
|
||||
: 'yarn set version classic',
|
||||
install: 'yarn',
|
||||
ciInstall: useBerry
|
||||
? 'yarn install --immutable'
|
||||
@@ -77,7 +81,7 @@ export function getPackageManagerCommand(
|
||||
};
|
||||
},
|
||||
pnpm: () => {
|
||||
const pnpmVersion = getPackageManagerVersion('pnpm');
|
||||
const pnpmVersion = getPackageManagerVersion('pnpm', root);
|
||||
const useExec = gte(pnpmVersion, '6.13.0');
|
||||
const includeDoubleDashBeforeArgs = lt(pnpmVersion, '7.0.0');
|
||||
const isPnpmWorkspace = existsSync(join(root, 'pnpm-workspace.yaml'));
|
||||
@@ -122,9 +126,13 @@ export function getPackageManagerCommand(
|
||||
* but it can also be passed in explicitly.
|
||||
*/
|
||||
export function getPackageManagerVersion(
|
||||
packageManager: PackageManager = detectPackageManager()
|
||||
packageManager: PackageManager = detectPackageManager(),
|
||||
cwd = process.cwd()
|
||||
): string {
|
||||
return execSync(`${packageManager} --version`).toString('utf-8').trim();
|
||||
return execSync(`${packageManager} --version`, {
|
||||
cwd,
|
||||
encoding: 'utf-8',
|
||||
}).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { join } from 'path';
|
||||
import { basename, dirname, join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import {
|
||||
mkdtempSync,
|
||||
@@ -8,9 +8,10 @@ import {
|
||||
emptyDirSync,
|
||||
outputFileSync,
|
||||
unlinkSync,
|
||||
mkdirpSync,
|
||||
} from 'fs-extra';
|
||||
import { joinPathFragments } from '../path';
|
||||
import { appendFileSync, writeFileSync, renameSync } from 'fs';
|
||||
import { appendFileSync, writeFileSync, renameSync, existsSync } from 'fs';
|
||||
|
||||
type NestedFiles = {
|
||||
[fileName: string]: string;
|
||||
@@ -44,7 +45,11 @@ export class TempFs {
|
||||
}
|
||||
|
||||
createFileSync(filePath: string, content: string) {
|
||||
outputFileSync(joinPathFragments(this.tempDir, filePath), content);
|
||||
let dir = joinPathFragments(this.tempDir, dirname(filePath));
|
||||
if (!existsSync(dir)) {
|
||||
mkdirpSync(dir);
|
||||
}
|
||||
writeFileSync(joinPathFragments(this.tempDir, filePath), content);
|
||||
}
|
||||
|
||||
async readFile(filePath: string): Promise<string> {
|
||||
|
||||
@@ -950,6 +950,15 @@
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"16.5.0-beta.4": {
|
||||
"version": "16.5.0-beta.4",
|
||||
"packages": {
|
||||
"@types/react": {
|
||||
"version": "18.2.14",
|
||||
"alwaysAddToPackageJson": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-2
@@ -39,12 +39,39 @@ export default async function* moduleFederationDevServer(
|
||||
}
|
||||
|
||||
const remotesToSkip = new Set(
|
||||
findMatchingProjects(options.skipRemotes ?? [], context.projectGraph.nodes)
|
||||
findMatchingProjects(options.skipRemotes, context.projectGraph.nodes) ?? []
|
||||
);
|
||||
|
||||
if (remotesToSkip.size > 0) {
|
||||
logger.info(
|
||||
`Remotes not served automatically: ${[...remotesToSkip.values()].join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
const remotesNotInWorkspace: string[] = [];
|
||||
|
||||
const knownRemotes = (moduleFederationConfig.remotes ?? []).filter((r) => {
|
||||
const validRemote = Array.isArray(r) ? r[0] : r;
|
||||
return !remotesToSkip.has(validRemote);
|
||||
|
||||
if (remotesToSkip.has(validRemote)) {
|
||||
return false;
|
||||
} else if (!context.projectGraph.nodes[validRemote]) {
|
||||
remotesNotInWorkspace.push(validRemote);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (remotesNotInWorkspace.length > 0) {
|
||||
logger.warn(
|
||||
`Skipping serving ${remotesNotInWorkspace.join(
|
||||
', '
|
||||
)} as they could not be found in the workspace. Ensure they are served correctly.`
|
||||
);
|
||||
}
|
||||
|
||||
const remotePorts = knownRemotes.map(
|
||||
(r) => context.projectGraph.nodes[r].data.targets['serve'].options.port
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"buildTarget": {
|
||||
|
||||
+21
-3
@@ -46,9 +46,27 @@ export default async function* moduleFederationSsrDevServer(
|
||||
}
|
||||
|
||||
const remotesToSkip = new Set(options.skipRemotes ?? []);
|
||||
const knownRemotes = (moduleFederationConfig.remotes ?? []).filter(
|
||||
(r) => !remotesToSkip.has(r)
|
||||
);
|
||||
const remotesNotInWorkspace: string[] = [];
|
||||
const knownRemotes = (moduleFederationConfig.remotes ?? []).filter((r) => {
|
||||
const validRemote = Array.isArray(r) ? r[0] : r;
|
||||
|
||||
if (remotesToSkip.has(validRemote)) {
|
||||
return false;
|
||||
} else if (!context.projectGraph.nodes[validRemote]) {
|
||||
remotesNotInWorkspace.push(validRemote);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (remotesNotInWorkspace.length > 0) {
|
||||
logger.warn(
|
||||
`Skipping serving ${remotesNotInWorkspace.join(
|
||||
', '
|
||||
)} as they could not be found in the workspace. Ensure they are served correctly.`
|
||||
);
|
||||
}
|
||||
|
||||
const devServeApps = !options.devRemotes
|
||||
? []
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode. This can be useful for multi-repository module federation setups where the host application uses a remote application from an external repository.",
|
||||
"description": "List of remote applications to not automatically serve, either statically or in development mode.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"host": {
|
||||
|
||||
@@ -8,7 +8,7 @@ export const reactDomVersion = '18.2.0';
|
||||
export const reactIsVersion = '18.2.0';
|
||||
export const swcLoaderVersion = '0.1.15';
|
||||
export const babelLoaderVersion = '^9.1.2';
|
||||
export const typesReactVersion = '18.2.13';
|
||||
export const typesReactVersion = '18.2.14';
|
||||
export const typesReactDomVersion = '18.2.6';
|
||||
export const typesReactIsVersion = '18.2.1';
|
||||
|
||||
|
||||
@@ -564,7 +564,7 @@ export function getTsConfigPath(
|
||||
const { root, projectType } = readProjectConfiguration(tree, projectName);
|
||||
return join(
|
||||
root,
|
||||
path && path.length > 0
|
||||
path?.length > 0
|
||||
? path
|
||||
: projectType === 'application'
|
||||
? 'tsconfig.app.json'
|
||||
@@ -595,7 +595,7 @@ export function addBuildStorybookToCacheableOperations(tree: Tree) {
|
||||
}
|
||||
|
||||
export function projectIsRootProjectInStandaloneWorkspace(projectRoot: string) {
|
||||
return relative(workspaceRoot, projectRoot).length === 0;
|
||||
return relative(workspaceRoot, projectRoot)?.length === 0;
|
||||
}
|
||||
|
||||
export function workspaceHasRootProject(tree: Tree) {
|
||||
@@ -675,21 +675,21 @@ export function renameAndMoveOldTsConfig(
|
||||
pathToStorybookConfigFile: string,
|
||||
tree: Tree
|
||||
) {
|
||||
if (pathToStorybookConfigFile) {
|
||||
if (pathToStorybookConfigFile && tree.exists(pathToStorybookConfigFile)) {
|
||||
updateJson(tree, pathToStorybookConfigFile, (json) => {
|
||||
if (json.extends?.startsWith('../')) {
|
||||
// drop one level of nesting
|
||||
json.extends = json.extends.replace('../', './');
|
||||
}
|
||||
|
||||
for (let i = 0; i < json.files.length; i++) {
|
||||
for (let i = 0; i < json.files?.length; i++) {
|
||||
// drop one level of nesting
|
||||
if (json.files[i].startsWith('../../../')) {
|
||||
json.files[i] = json.files[i].replace('../../../', '../../');
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < json.include.length; i++) {
|
||||
for (let i = 0; i < json.include?.length; i++) {
|
||||
if (json.include[i].startsWith('../')) {
|
||||
json.include[i] = json.include[i].replace('../', '');
|
||||
}
|
||||
@@ -702,7 +702,7 @@ export function renameAndMoveOldTsConfig(
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < json.exclude.length; i++) {
|
||||
for (let i = 0; i < json.exclude?.length; i++) {
|
||||
if (json.exclude[i].startsWith('../')) {
|
||||
json.exclude[i] = json.exclude[i].replace('../', 'src/');
|
||||
}
|
||||
@@ -718,13 +718,30 @@ export function renameAndMoveOldTsConfig(
|
||||
}
|
||||
|
||||
const projectTsConfig = joinPathFragments(projectRoot, 'tsconfig.json');
|
||||
updateJson(tree, projectTsConfig, (json) => {
|
||||
for (let i = 0; i < json.references.length; i++) {
|
||||
if (json.references[i].path === './.storybook/tsconfig.json') {
|
||||
json.references[i].path = './tsconfig.storybook.json';
|
||||
break;
|
||||
|
||||
if (tree.exists(projectTsConfig)) {
|
||||
updateJson(tree, projectTsConfig, (json) => {
|
||||
for (let i = 0; i < json.references?.length; i++) {
|
||||
if (json.references[i].path === './.storybook/tsconfig.json') {
|
||||
json.references[i].path = './tsconfig.storybook.json';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return json;
|
||||
});
|
||||
return json;
|
||||
});
|
||||
}
|
||||
|
||||
const projectEsLintFile = joinPathFragments(projectRoot, '.eslintrc.json');
|
||||
|
||||
if (tree.exists(projectEsLintFile)) {
|
||||
updateJson(tree, projectEsLintFile, (json) => {
|
||||
const jsonString = JSON.stringify(json);
|
||||
const newJsonString = jsonString.replace(
|
||||
/\.storybook\/tsconfig\.json/g,
|
||||
'tsconfig.storybook.json'
|
||||
);
|
||||
json = JSON.parse(newJsonString);
|
||||
return json;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,24 @@ describe('testing utilities', () => {
|
||||
`
|
||||
);
|
||||
|
||||
appTree.write(
|
||||
`test-ui-lib/src/lib/button/button.other.stories.ts`,
|
||||
`
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { Button } from './button';
|
||||
|
||||
const Story: Meta<typeof Button> = {
|
||||
component: Button,
|
||||
title: 'Layout/Texts/Button',
|
||||
};
|
||||
export default Story;
|
||||
|
||||
export const Primary = {
|
||||
args: {},
|
||||
};
|
||||
`
|
||||
);
|
||||
|
||||
appTree.write(
|
||||
`test-ui-lib/src/lib/button/button.component.other.ts`,
|
||||
`
|
||||
@@ -155,6 +173,14 @@ describe('testing utilities', () => {
|
||||
expect(fileIsStory).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should verify it is story when using Meta', () => {
|
||||
const fileIsStory = isTheFileAStory(
|
||||
appTree,
|
||||
'test-ui-lib/src/lib/button/button.other.stories.ts'
|
||||
);
|
||||
expect(fileIsStory).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should verify it is story for ReactNative', () => {
|
||||
const fileIsStory = isTheFileAStory(
|
||||
appTree,
|
||||
|
||||
@@ -221,6 +221,7 @@ export function isTheFileAStory(tree: Tree, path: string): boolean {
|
||||
importSpecifiers.forEach((importSpecifier: ts.ImportSpecifier) => {
|
||||
if (
|
||||
importSpecifier.getText() === 'Story' ||
|
||||
importSpecifier.getText() === 'Meta' ||
|
||||
importSpecifier.getText() === 'storiesOf' ||
|
||||
importSpecifier.getText() === 'ComponentStory'
|
||||
) {
|
||||
|
||||
@@ -7,4 +7,4 @@ export const tsNodeVersion = '10.9.1';
|
||||
|
||||
export const storybookVersion = '^7.0.24';
|
||||
export const reactVersion = '^18.2.0';
|
||||
export const viteVersion = '^4.3.4';
|
||||
export const viteVersion = '~4.3.9';
|
||||
|
||||
@@ -8,14 +8,14 @@ exports[`@nx/vite:init dependencies for package.json should add vite packages an
|
||||
"devDependencies": {
|
||||
"@nx/js": "0.0.1",
|
||||
"@nx/vite": "0.0.1",
|
||||
"@vitejs/plugin-react": "^4.0.0",
|
||||
"@vitest/ui": "^0.32.0",
|
||||
"@vitejs/plugin-react": "~4.0.0",
|
||||
"@vitest/ui": "~0.32.0",
|
||||
"existing": "1.0.0",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "~5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-tsconfig-paths": "^4.2.0",
|
||||
"vitest": "^0.32.0",
|
||||
"vite": "~4.3.9",
|
||||
"vite-tsconfig-paths": "~4.2.0",
|
||||
"vitest": "~0.32.0",
|
||||
},
|
||||
"name": "test-name",
|
||||
}
|
||||
@@ -28,12 +28,12 @@ exports[`@nx/vite:init dependencies for package.json should support --testEnviro
|
||||
"@edge-runtime/vm": "~3.0.2",
|
||||
"@nx/js": "0.0.1",
|
||||
"@nx/vite": "0.0.1",
|
||||
"@vitest/ui": "^0.32.0",
|
||||
"@vitest/ui": "~0.32.0",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "~5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-tsconfig-paths": "^4.2.0",
|
||||
"vitest": "^0.32.0",
|
||||
"vite": "~4.3.9",
|
||||
"vite-tsconfig-paths": "~4.2.0",
|
||||
"vitest": "~0.32.0",
|
||||
},
|
||||
"name": "test-name",
|
||||
}
|
||||
@@ -45,13 +45,13 @@ exports[`@nx/vite:init dependencies for package.json should support --testEnviro
|
||||
"devDependencies": {
|
||||
"@nx/js": "0.0.1",
|
||||
"@nx/vite": "0.0.1",
|
||||
"@vitest/ui": "^0.32.0",
|
||||
"@vitest/ui": "~0.32.0",
|
||||
"happy-dom": "~9.20.3",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "~5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-tsconfig-paths": "^4.2.0",
|
||||
"vitest": "^0.32.0",
|
||||
"vite": "~4.3.9",
|
||||
"vite-tsconfig-paths": "~4.2.0",
|
||||
"vitest": "~0.32.0",
|
||||
},
|
||||
"name": "test-name",
|
||||
}
|
||||
@@ -63,13 +63,13 @@ exports[`@nx/vite:init dependencies for package.json should support --testEnviro
|
||||
"devDependencies": {
|
||||
"@nx/js": "0.0.1",
|
||||
"@nx/vite": "0.0.1",
|
||||
"@vitest/ui": "^0.32.0",
|
||||
"@vitest/ui": "~0.32.0",
|
||||
"jsdom": "~22.1.0",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "~5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-tsconfig-paths": "^4.2.0",
|
||||
"vitest": "^0.32.0",
|
||||
"vite": "~4.3.9",
|
||||
"vite-tsconfig-paths": "~4.2.0",
|
||||
"vitest": "~0.32.0",
|
||||
},
|
||||
"name": "test-name",
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
export const nxVersion = require('../../package.json').version;
|
||||
export const viteVersion = '^4.3.9';
|
||||
export const vitestVersion = '^0.32.0';
|
||||
export const vitestUiVersion = '^0.32.0';
|
||||
export const vitePluginReactVersion = '^4.0.0';
|
||||
export const vitePluginReactSwcVersion = '^3.3.2';
|
||||
export const viteTsConfigPathsVersion = '^4.2.0';
|
||||
export const viteVersion = '~4.3.9';
|
||||
export const vitestVersion = '~0.32.0';
|
||||
export const vitestUiVersion = '~0.32.0';
|
||||
export const vitePluginReactVersion = '~4.0.0';
|
||||
export const vitePluginReactSwcVersion = '~3.3.2';
|
||||
export const viteTsConfigPathsVersion = '~4.2.0';
|
||||
export const jsdomVersion = '~22.1.0';
|
||||
export const vitePluginDtsVersion = '~2.3.0';
|
||||
export const happyDomVersion = '~9.20.3';
|
||||
export const edgeRuntimeVmVersion = '~3.0.2';
|
||||
|
||||
// Coverage providers
|
||||
export const vitestCoverageC8Version = '^0.32.0';
|
||||
export const vitestCoverageIstanbulVersion = '^0.32.0';
|
||||
export const vitestCoverageC8Version = '~0.32.0';
|
||||
export const vitestCoverageIstanbulVersion = '~0.32.0';
|
||||
|
||||
@@ -20,6 +20,18 @@ import browserslist = require('browserslist');
|
||||
|
||||
const VALID_BROWSERSLIST_FILES = ['.browserslistrc', 'browserslist'];
|
||||
|
||||
const ES5_BROWSERS = [
|
||||
'ie 10',
|
||||
'ie 11',
|
||||
'safari 11',
|
||||
'safari 11.1',
|
||||
'safari 12',
|
||||
'safari 12.1',
|
||||
'safari 13',
|
||||
'ios_saf 13.0',
|
||||
'ios_saf 13.3',
|
||||
];
|
||||
|
||||
function getTerserEcmaVersion(projectRoot: string) {
|
||||
let pathToBrowserslistFile = '';
|
||||
for (const browserslistFile of VALID_BROWSERSLIST_FILES) {
|
||||
@@ -36,7 +48,7 @@ function getTerserEcmaVersion(projectRoot: string) {
|
||||
|
||||
const env = browserslist.loadConfig({ path: pathToBrowserslistFile });
|
||||
const browsers = browserslist(env);
|
||||
return browsers.includes('ie 11') ? 5 : 2020;
|
||||
return browsers.some((b) => ES5_BROWSERS.includes(b)) ? 5 : 2020;
|
||||
}
|
||||
|
||||
const IGNORED_WEBPACK_WARNINGS = [
|
||||
|
||||
@@ -11,6 +11,8 @@ describe('@nx/workspace:generateWorkspaceFiles', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTree();
|
||||
// we need an actual path for the package manager version check
|
||||
tree.root = process.cwd();
|
||||
});
|
||||
|
||||
it('should create files', async () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
import { join, join as pathJoin } from 'path';
|
||||
import { join } from 'path';
|
||||
import { Preset } from '../utils/presets';
|
||||
import { deduceDefaultBase } from '../../utilities/default-base';
|
||||
import { NormalizedSchema } from './new';
|
||||
@@ -22,18 +22,26 @@ export async function generateWorkspaceFiles(
|
||||
if (!options.name) {
|
||||
throw new Error(`Invalid options, "name" is required.`);
|
||||
}
|
||||
// we need to check package manager version before the package.json is generated
|
||||
// since it might influence the version report
|
||||
const packageManagerVersion = getPackageManagerVersion(
|
||||
options.packageManager as PackageManager,
|
||||
tree.root
|
||||
);
|
||||
options = normalizeOptions(options);
|
||||
createReadme(tree, options);
|
||||
createFiles(tree, options);
|
||||
createNxJson(tree, options);
|
||||
|
||||
const [packageMajor] = getPackageManagerVersion(
|
||||
options.packageManager as PackageManager
|
||||
).split('.');
|
||||
const [packageMajor] = packageManagerVersion.split('.');
|
||||
if (options.packageManager === 'pnpm' && +packageMajor >= 7) {
|
||||
createNpmrc(tree, options);
|
||||
} else if (options.packageManager === 'yarn' && +packageMajor >= 2) {
|
||||
createYarnrcYml(tree, options);
|
||||
} else if (options.packageManager === 'yarn') {
|
||||
if (+packageMajor >= 2) {
|
||||
createYarnrcYml(tree, options);
|
||||
// avoids errors when using nested yarn projects
|
||||
tree.write(join(options.directory, 'yarn.lock'), '');
|
||||
}
|
||||
}
|
||||
setPresetProperty(tree, options);
|
||||
addNpmScripts(tree, options);
|
||||
@@ -133,7 +141,7 @@ function createFiles(tree: Tree, options: NormalizedSchema) {
|
||||
: options.preset === Preset.NPM || options.preset === Preset.Core
|
||||
? './files-package-based-repo'
|
||||
: './files-integrated-repo';
|
||||
generateFiles(tree, pathJoin(__dirname, filesDirName), options.directory, {
|
||||
generateFiles(tree, join(__dirname, filesDirName), options.directory, {
|
||||
formattedNames,
|
||||
dot: '.',
|
||||
tmpl: '',
|
||||
|
||||
@@ -32,6 +32,8 @@ describe('new', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTree();
|
||||
// we need an actual path for the package manager version check
|
||||
tree.root = process.cwd();
|
||||
});
|
||||
|
||||
it('should generate an empty nx.json', async () => {
|
||||
|
||||
@@ -36,7 +36,6 @@ const IGNORE_MATCHES_IN_PACKAGE = {
|
||||
'injection-js',
|
||||
'browserslist',
|
||||
'cacache',
|
||||
'find-cache-dir',
|
||||
'less',
|
||||
'node-sass',
|
||||
'node-sass-tilde-importer',
|
||||
|
||||
Reference in New Issue
Block a user