Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c04ed41e7 | |||
| 4105745f95 | |||
| 9b2b8f311f | |||
| 1bb96b7f44 | |||
| 93a0200dd8 | |||
| d120c3cd61 | |||
| 2201ecace6 | |||
| 9382ed4826 | |||
| 0f3fd9c6d5 | |||
| 6b6038c77d | |||
| bd02995bec | |||
| 81a661cf7f | |||
| 445a527043 | |||
| d4f6c0ba81 | |||
| 95d99e42fc | |||
| 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": {
|
||||
|
||||
@@ -313,13 +313,13 @@ Sometimes, multiple targets might write to the same directory. When possible it
|
||||
}
|
||||
```
|
||||
|
||||
But if the above is not possible, globs (parsed with the [minimatch](https://github.com/isaacs/minimatch) library) can be specified as outputs to only cache a set of files rather than the whole directory.
|
||||
But if the above is not possible, globs (parsed by the [GlobSet](https://docs.rs/globset/0.4.5/globset/#syntax) Rust library) can be specified as outputs to only cache a set of files rather than the whole directory.
|
||||
|
||||
```json
|
||||
{
|
||||
"targets": {
|
||||
"build-js": {
|
||||
"outputs": ["{workspaceRoot}/dist/libs/mylib/**/*.js"]
|
||||
"outputs": ["{workspaceRoot}/dist/libs/mylib/**/*.{js,map}"]
|
||||
},
|
||||
"build-css": {
|
||||
"outputs": ["{workspaceRoot}/dist/libs/mylib/**/*.css"]
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
checkFilesExist,
|
||||
cleanupProject,
|
||||
expectTestsPass,
|
||||
getPackageManagerCommand,
|
||||
killPorts,
|
||||
newProject,
|
||||
promisifiedTreeKill,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
readResolvedConfiguration,
|
||||
runCLI,
|
||||
runCLIAsync,
|
||||
runCommand,
|
||||
runCommandUntil,
|
||||
uniq,
|
||||
updateFile,
|
||||
@@ -129,4 +131,19 @@ describe('expo', () => {
|
||||
checkFilesExist(`dist/libs/${libName}/src/index.d.ts`);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should tsc app', async () => {
|
||||
expect(() => {
|
||||
const pmc = getPackageManagerCommand();
|
||||
runCommand(
|
||||
`${pmc.runUninstalledPackage} tsc -p apps/${appName}/tsconfig.app.json`
|
||||
);
|
||||
checkFilesExist(
|
||||
`dist/out-tsc/apps/${appName}/src/app/App.js`,
|
||||
`dist/out-tsc/apps/${appName}/src/app/App.d.ts`,
|
||||
`dist/out-tsc/libs/${libName}/src/index.js`,
|
||||
`dist/out-tsc/libs/${libName}/src/index.d.ts`
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@ describe('cache', () => {
|
||||
updateProjectConfig(mylib, (c) => {
|
||||
c.targets.build = {
|
||||
executor: 'nx:run-commands',
|
||||
outputs: ['{workspaceRoot}/dist/*.txt'],
|
||||
outputs: ['{workspaceRoot}/dist/*.{txt,md}'],
|
||||
options: {
|
||||
commands: [
|
||||
'rm -rf dist',
|
||||
@@ -167,7 +167,8 @@ describe('cache', () => {
|
||||
'echo c > dist/c.txt',
|
||||
'echo d > dist/d.txt',
|
||||
'echo e > dist/e.txt',
|
||||
'echo f > dist/f.txt',
|
||||
'echo f > dist/f.md',
|
||||
'echo g > dist/g.html',
|
||||
],
|
||||
parallel: false,
|
||||
},
|
||||
@@ -188,7 +189,8 @@ describe('cache', () => {
|
||||
expect(outputsWithUntouchedOutputs).toContain('c.txt');
|
||||
expect(outputsWithUntouchedOutputs).toContain('d.txt');
|
||||
expect(outputsWithUntouchedOutputs).toContain('e.txt');
|
||||
expect(outputsWithUntouchedOutputs).toContain('f.txt');
|
||||
expect(outputsWithUntouchedOutputs).toContain('f.md');
|
||||
expect(outputsWithUntouchedOutputs).toContain('g.html');
|
||||
|
||||
// Create a file in the dist that does not match output glob
|
||||
updateFile('dist/c.ts', '');
|
||||
@@ -202,7 +204,8 @@ describe('cache', () => {
|
||||
expect(outputsAfterAddingUntouchedFileAndRerunning).toContain('c.txt');
|
||||
expect(outputsAfterAddingUntouchedFileAndRerunning).toContain('d.txt');
|
||||
expect(outputsAfterAddingUntouchedFileAndRerunning).toContain('e.txt');
|
||||
expect(outputsAfterAddingUntouchedFileAndRerunning).toContain('f.txt');
|
||||
expect(outputsAfterAddingUntouchedFileAndRerunning).toContain('f.md');
|
||||
expect(outputsAfterAddingUntouchedFileAndRerunning).toContain('g.html');
|
||||
expect(outputsAfterAddingUntouchedFileAndRerunning).toContain('c.ts');
|
||||
|
||||
// Clear Dist
|
||||
@@ -217,8 +220,9 @@ describe('cache', () => {
|
||||
expect(outputsWithoutOutputs).toContain('c.txt');
|
||||
expect(outputsWithoutOutputs).toContain('d.txt');
|
||||
expect(outputsWithoutOutputs).toContain('e.txt');
|
||||
expect(outputsWithoutOutputs).toContain('f.txt');
|
||||
expect(outputsWithoutOutputs).toContain('f.md');
|
||||
expect(outputsWithoutOutputs).not.toContain('c.ts');
|
||||
expect(outputsWithoutOutputs).not.toContain('g.html');
|
||||
});
|
||||
|
||||
it('should use consider filesets when hashing', async () => {
|
||||
|
||||
@@ -194,4 +194,21 @@ describe('react native', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should tsc app', async () => {
|
||||
expect(() => {
|
||||
const pmc = getPackageManagerCommand();
|
||||
runCommand(
|
||||
`${pmc.runUninstalledPackage} tsc -p apps/${appName}/tsconfig.app.json`
|
||||
);
|
||||
checkFilesExist(
|
||||
`dist/out-tsc/apps/${appName}/src/main.js`,
|
||||
`dist/out-tsc/apps/${appName}/src/main.d.ts`,
|
||||
`dist/out-tsc/apps/${appName}/src/app/App.js`,
|
||||
`dist/out-tsc/apps/${appName}/src/app/App.d.ts`,
|
||||
`dist/out-tsc/libs/${libName}/src/index.js`,
|
||||
`dist/out-tsc/libs/${libName}/src/index.d.ts`
|
||||
);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,5 +36,10 @@ module.exports = {
|
||||
translate: ['group-hover'],
|
||||
},
|
||||
},
|
||||
plugins: [require('@tailwindcss/typography')],
|
||||
plugins: [
|
||||
require('@tailwindcss/typography'),
|
||||
require('@tailwindcss/forms')({
|
||||
strategy: 'class',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ export function Dropdown(props: DropdownProps) {
|
||||
const { className, children, ...rest } = props;
|
||||
return (
|
||||
<select
|
||||
className={`flex items-center rounded-md rounded-md border border-slate-300 bg-white px-4 py-2 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700 ${className}`}
|
||||
className={`form-select flex items-center rounded-md rounded-md border border-slate-300 bg-white pl-4 pr-8 py-2 text-sm font-medium text-slate-700 shadow-sm hover:bg-slate-50 dark:border-slate-600 dark:bg-slate-800 dark:text-slate-300 hover:dark:bg-slate-700 ${className}`}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"packages": ["build/packages/*", "build/packages/nx/native-packages/*"],
|
||||
"version": "16.5.0",
|
||||
"version": "16.5.5",
|
||||
"granularPathspec": false,
|
||||
"command": {
|
||||
"publish": {
|
||||
|
||||
@@ -190,6 +190,14 @@ module.exports = withNx({
|
||||
permanent: true,
|
||||
});
|
||||
}
|
||||
// Nesting 5min tutorials in Getting Started
|
||||
for (let s of Object.keys(redirectRules.nested5minuteTutorialUrls)) {
|
||||
rules.push({
|
||||
source: s,
|
||||
destination: redirectRules.nested5minuteTutorialUrls[s],
|
||||
permanent: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Docs
|
||||
rules.push({
|
||||
|
||||
@@ -410,7 +410,8 @@ const oldNodeTutorialPaths = [
|
||||
];
|
||||
|
||||
const extraNodeRedirects = {
|
||||
'/getting-started/node-tutorial': '/getting-started/node-server-tutorial',
|
||||
'/getting-started/node-tutorial':
|
||||
'/getting-started/tutorials/node-server-tutorial',
|
||||
'/node-tutorial/1-code-generation': '/node-server-tutorial/1-code-generation',
|
||||
'/node-tutorial/2-project-graph': '/node-server-tutorial/2-project-graph',
|
||||
'/node-tutorial/3-task-running': '/node-server-tutorial/3-task-running',
|
||||
|
||||
@@ -100,5 +100,21 @@ describe('Redirect rules configuration', () => {
|
||||
).toEqual(`${newNodePathPrefix}${des}`);
|
||||
});
|
||||
});
|
||||
|
||||
test('old tutorial links', () => {
|
||||
const oldTutorialUrls = [
|
||||
'/tutorials/package-based-repo-tutorial',
|
||||
'/tutorials/integrated-repo-tutorial',
|
||||
'/tutorials/react-standalone-tutorial',
|
||||
'/tutorials/angular-standalone-tutorial',
|
||||
'/tutorials/node-server-tutorial',
|
||||
];
|
||||
|
||||
for (const url of oldTutorialUrls) {
|
||||
expect(redirectRules.nested5minuteTutorialUrls[url]).toEqual(
|
||||
'/getting-started' + url
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,9 @@ export function pointToTutorialAndCourse(preset: Preset) {
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [`https://nx.dev/tutorials/package-based-repo-tutorial`],
|
||||
bodyLines: [
|
||||
`https://nx.dev/getting-started/tutorials/package-based-repo-tutorial`,
|
||||
],
|
||||
});
|
||||
break;
|
||||
|
||||
@@ -19,14 +21,18 @@ export function pointToTutorialAndCourse(preset: Preset) {
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [`https://nx.dev/tutorials/integrated-repo-tutorial`],
|
||||
bodyLines: [
|
||||
`https://nx.dev/getting-started/tutorials/integrated-repo-tutorial`,
|
||||
],
|
||||
});
|
||||
break;
|
||||
case Preset.ReactStandalone:
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [`https://nx.dev/tutorials/react-standalone-tutorial`],
|
||||
bodyLines: [
|
||||
`https://nx.dev/getting-started/tutorials/react-standalone-tutorial`,
|
||||
],
|
||||
});
|
||||
break;
|
||||
case Preset.ReactMonorepo:
|
||||
@@ -42,7 +48,9 @@ export function pointToTutorialAndCourse(preset: Preset) {
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [`https://nx.dev/tutorials/angular-standalone-tutorial`],
|
||||
bodyLines: [
|
||||
`https://nx.dev/getting-started/tutorials/angular-standalone-tutorial`,
|
||||
],
|
||||
});
|
||||
break;
|
||||
case Preset.AngularMonorepo:
|
||||
@@ -57,7 +65,9 @@ export function pointToTutorialAndCourse(preset: Preset) {
|
||||
output.addVerticalSeparator();
|
||||
output.note({
|
||||
title,
|
||||
bodyLines: [`https://nx.dev/tutorials/node-server-tutorial`],
|
||||
bodyLines: [
|
||||
`https://nx.dev/getting-started/tutorials/node-server-tutorial`,
|
||||
],
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import { installPackagesTask } from '../tasks/install-packages-task';
|
||||
import { requireNx } from '../../nx';
|
||||
import { dirSync } from 'tmp';
|
||||
import { join } from 'path';
|
||||
import type { PackageManager } from 'nx/src/utils/package-manager';
|
||||
import { writeFileSync } from 'fs';
|
||||
|
||||
const {
|
||||
readJson,
|
||||
@@ -18,6 +20,7 @@ const {
|
||||
workspaceRoot,
|
||||
detectPackageManager,
|
||||
createTempNpmDirectory,
|
||||
getPackageManagerVersion,
|
||||
} = requireNx();
|
||||
|
||||
const UNIDENTIFIED_VERSION = 'UNIDENTIFIED_VERSION';
|
||||
@@ -464,12 +467,21 @@ export function ensurePackage<T extends any = any>(
|
||||
|
||||
console.log(`Fetching ${pkg}...`);
|
||||
const packageManager = detectPackageManager();
|
||||
const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true';
|
||||
generatePackageManagerFiles(tempDir, packageManager);
|
||||
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',
|
||||
@@ -500,6 +512,27 @@ export function ensurePackage<T extends any = any>(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates necessary files needed for the package manager to work
|
||||
* and for the node_modules to be accessible.
|
||||
*/
|
||||
function generatePackageManagerFiles(
|
||||
root: string,
|
||||
packageManager: PackageManager = detectPackageManager()
|
||||
) {
|
||||
const [pmMajor] = getPackageManagerVersion(packageManager).split('.');
|
||||
switch (packageManager) {
|
||||
case 'yarn':
|
||||
if (+pmMajor >= 2) {
|
||||
writeFileSync(
|
||||
join(root, '.yarnrc.yml'),
|
||||
'nodeLinker: node-modules\nenableScripts: false'
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function addToNodePath(dir: string) {
|
||||
// NODE_PATH is a delimited list of paths.
|
||||
// The delimiter is different for windows.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"outDir": "<%= offsetFromRoot %>dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": ["<%= offsetFromRoot %>node_modules/@nx/expo/typings/svg.d.ts"],
|
||||
"exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.spec.tsx", "test-setup.ts"],
|
||||
"include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"]
|
||||
}
|
||||
|
||||
@@ -8,19 +8,13 @@
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"composite": true,
|
||||
"declaration": true
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nx/expo/typings/svg.d.ts"
|
||||
],
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -37,6 +37,12 @@ export type ChangedFile = {
|
||||
type: 'create' | 'update' | 'delete';
|
||||
};
|
||||
|
||||
enum DaemonStatus {
|
||||
CONNECTING,
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
}
|
||||
|
||||
export class DaemonClient {
|
||||
constructor(private readonly nxJson: NxJsonConfiguration) {
|
||||
this.reset();
|
||||
@@ -50,7 +56,9 @@ export class DaemonClient {
|
||||
private currentReject;
|
||||
|
||||
private _enabled: boolean | undefined;
|
||||
private _connected: boolean;
|
||||
private _daemonStatus: DaemonStatus = DaemonStatus.DISCONNECTED;
|
||||
private _waitForDaemonReady: Promise<void> | null = null;
|
||||
private _daemonReady: () => void | null = null;
|
||||
private _out: FileHandle = null;
|
||||
private _err: FileHandle = null;
|
||||
|
||||
@@ -104,7 +112,10 @@ export class DaemonClient {
|
||||
this._out = null;
|
||||
this._err = null;
|
||||
|
||||
this._connected = false;
|
||||
this._daemonStatus = DaemonStatus.DISCONNECTED;
|
||||
this._waitForDaemonReady = new Promise<void>(
|
||||
(resolve) => (this._daemonReady = resolve)
|
||||
);
|
||||
}
|
||||
|
||||
async requestShutdown(): Promise<void> {
|
||||
@@ -149,27 +160,28 @@ export class DaemonClient {
|
||||
) => void
|
||||
): Promise<UnregisterCallback> {
|
||||
await this.getProjectGraph();
|
||||
const messenger = new SocketMessenger(connect(FULL_OS_SOCKET_PATH)).listen(
|
||||
(message) => {
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
callback(null, parsedMessage);
|
||||
} catch (e) {
|
||||
callback(e, null);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
callback('closed', null);
|
||||
},
|
||||
(err) => callback(err, null)
|
||||
);
|
||||
let messenger: SocketMessenger | undefined;
|
||||
|
||||
await this.queue.sendToQueue(() =>
|
||||
messenger.sendMessage({ type: 'REGISTER_FILE_WATCHER', config })
|
||||
);
|
||||
await this.queue.sendToQueue(() => {
|
||||
messenger = new SocketMessenger(connect(FULL_OS_SOCKET_PATH)).listen(
|
||||
(message) => {
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
callback(null, parsedMessage);
|
||||
} catch (e) {
|
||||
callback(e, null);
|
||||
}
|
||||
},
|
||||
() => {
|
||||
callback('closed', null);
|
||||
},
|
||||
(err) => callback(err, null)
|
||||
);
|
||||
return messenger.sendMessage({ type: 'REGISTER_FILE_WATCHER', config });
|
||||
});
|
||||
|
||||
return () => {
|
||||
messenger.close();
|
||||
messenger?.close();
|
||||
};
|
||||
}
|
||||
|
||||
@@ -232,7 +244,7 @@ export class DaemonClient {
|
||||
// it's ok for the daemon to terminate if the client doesn't wait on
|
||||
// any messages from the daemon
|
||||
if (this.queue.isEmpty()) {
|
||||
this._connected = false;
|
||||
this.reset();
|
||||
} else {
|
||||
output.error({
|
||||
title: 'Daemon process terminated and closed the connection',
|
||||
@@ -280,12 +292,17 @@ export class DaemonClient {
|
||||
}
|
||||
|
||||
private async sendMessageToDaemon(message: Message): Promise<any> {
|
||||
if (!this._connected) {
|
||||
this._connected = true;
|
||||
if (this._daemonStatus == DaemonStatus.DISCONNECTED) {
|
||||
this._daemonStatus = DaemonStatus.CONNECTING;
|
||||
|
||||
if (!(await this.isServerAvailable())) {
|
||||
await this.startInBackground();
|
||||
}
|
||||
this.setUpConnection();
|
||||
this._daemonStatus = DaemonStatus.CONNECTED;
|
||||
this._daemonReady();
|
||||
} else if (this._daemonStatus == DaemonStatus.CONNECTING) {
|
||||
await this._waitForDaemonReady;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -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,152 @@ describe('TaskHasher', () => {
|
||||
expect(hash.value).toContain('|5.0.0|');
|
||||
});
|
||||
|
||||
it('should hash entire subtree in a deterministic way', 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: 'appA',
|
||||
target: 'npm:packageA',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
{
|
||||
source: 'appA',
|
||||
target: 'npm:packageB',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
{
|
||||
source: 'appA',
|
||||
target: 'npm:packageC',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
appB: [
|
||||
{
|
||||
source: 'appB',
|
||||
target: 'npm:packageC',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
'npm:packageC': [
|
||||
{
|
||||
source: 'npm:packageC',
|
||||
target: 'npm:packageA',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
{
|
||||
source: 'npm:packageC',
|
||||
target: 'npm:packageB',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
'npm:packageB': [
|
||||
{
|
||||
source: 'npm:packageB',
|
||||
target: 'npm:packageA',
|
||||
type: DependencyType.static,
|
||||
},
|
||||
],
|
||||
'npm:packageA': [
|
||||
{
|
||||
source: 'npm:packageA',
|
||||
target: 'npm:packageC',
|
||||
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();
|
||||
|
||||
const hasAppA1 = await computeTaskHash(hasher1, 'appA');
|
||||
const hashAppB1 = await computeTaskHash(hasher1, 'appB');
|
||||
|
||||
const hasher2 = createHasher();
|
||||
|
||||
const hashAppB2 = await computeTaskHash(hasher2, 'appB');
|
||||
const hasAppA2 = await computeTaskHash(hasher2, 'appA');
|
||||
|
||||
expect(hashAppB1).toEqual(hashAppB2);
|
||||
expect(hasAppA1).toEqual(hasAppA2);
|
||||
});
|
||||
|
||||
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.target);
|
||||
return {
|
||||
value: hash,
|
||||
details: {
|
||||
@@ -410,15 +410,19 @@ class TaskHasherImpl {
|
||||
|
||||
private hashExternalDependency(
|
||||
projectName: string,
|
||||
visited = new Set<string>()
|
||||
): string {
|
||||
parentProjects = new Set<string>()
|
||||
): { fullyResolved: boolean; hash: string } {
|
||||
// try to retrieve the hash from cache
|
||||
if (this.externalDepsHashCache[projectName]) {
|
||||
return this.externalDepsHashCache[projectName];
|
||||
return {
|
||||
fullyResolved: true,
|
||||
hash: this.externalDepsHashCache[projectName],
|
||||
};
|
||||
}
|
||||
visited.add(projectName);
|
||||
parentProjects.add(projectName);
|
||||
const node = this.projectGraph.externalNodes[projectName];
|
||||
let partialHash: string;
|
||||
let fullyResolved = true;
|
||||
if (node) {
|
||||
const partialHashes: string[] = [];
|
||||
if (node.data.hash) {
|
||||
@@ -431,11 +435,22 @@ class TaskHasherImpl {
|
||||
// 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 (!parentProjects.has(d.target)) {
|
||||
const hashResult = this.hashExternalDependency(
|
||||
d.target,
|
||||
new Set(parentProjects)
|
||||
);
|
||||
partialHashes.push(hashResult.hash);
|
||||
if (!hashResult.fullyResolved) {
|
||||
fullyResolved = false;
|
||||
}
|
||||
} else {
|
||||
// NOTE: do not store hash to cache since it is only a partial hash
|
||||
fullyResolved = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
partialHash = hashArray(partialHashes);
|
||||
} else {
|
||||
// unknown dependency
|
||||
@@ -444,8 +459,12 @@ class TaskHasherImpl {
|
||||
// in this case we have no information about the versioning of the given package
|
||||
partialHash = `__${projectName}__`;
|
||||
}
|
||||
this.externalDepsHashCache[projectName] = partialHash;
|
||||
return partialHash;
|
||||
|
||||
if (fullyResolved) {
|
||||
this.externalDepsHashCache[projectName] = partialHash;
|
||||
}
|
||||
|
||||
return { fullyResolved, hash: partialHash };
|
||||
}
|
||||
|
||||
private hashTarget(
|
||||
@@ -460,7 +479,7 @@ class TaskHasherImpl {
|
||||
return;
|
||||
}
|
||||
|
||||
let hash;
|
||||
let hash: string;
|
||||
// we can only vouch for @nx packages's executor dependencies
|
||||
// if it's "run commands" or third-party we skip traversing since we have no info what this command depends on
|
||||
if (
|
||||
@@ -470,7 +489,7 @@ class TaskHasherImpl {
|
||||
const executorPackage = target.executor.split(':')[0];
|
||||
const executorNodeName =
|
||||
this.findExternalDependencyNodeName(executorPackage);
|
||||
hash = this.hashExternalDependency(executorNodeName);
|
||||
hash = this.hashExternalDependency(executorNodeName).hash;
|
||||
} else {
|
||||
// use command external dependencies if available to construct the hash
|
||||
const partialHashes: string[] = [];
|
||||
@@ -482,7 +501,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(dep).hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -59,6 +59,10 @@ mod test {
|
||||
.child("nx.darwin-arm64.node")
|
||||
.touch()
|
||||
.unwrap();
|
||||
temp.child("multi").child("file.js").touch().unwrap();
|
||||
temp.child("multi").child("src.ts").touch().unwrap();
|
||||
temp.child("multi").child("file.map").touch().unwrap();
|
||||
temp.child("multi").child("file.txt").touch().unwrap();
|
||||
temp
|
||||
}
|
||||
#[test]
|
||||
@@ -80,4 +84,16 @@ mod test {
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_handle_multiple_extensions() {
|
||||
let temp = setup_fs();
|
||||
let entries = vec!["multi/*.{js,map,ts}".to_string()];
|
||||
let mut result = expand_outputs(temp.display().to_string(), entries).unwrap();
|
||||
result.sort();
|
||||
assert_eq!(
|
||||
result,
|
||||
vec!["multi/file.js", "multi/file.map", "multi/src.ts"]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('watcher', () => {
|
||||
});
|
||||
|
||||
it('should trigger the callback for files that are not ignored', (done) => {
|
||||
watcher = new Watcher(realpathSync(temp.tempDir));
|
||||
watcher = new Watcher(temp.tempDir);
|
||||
watcher.watch((error, paths) => {
|
||||
expect(paths).toMatchInlineSnapshot(`
|
||||
[
|
||||
@@ -48,7 +48,7 @@ describe('watcher', () => {
|
||||
});
|
||||
|
||||
it('should trigger the callback when files are updated', (done) => {
|
||||
watcher = new Watcher(realpathSync(temp.tempDir));
|
||||
watcher = new Watcher(temp.tempDir);
|
||||
|
||||
watcher.watch((err, paths) => {
|
||||
expect(paths).toMatchInlineSnapshot(`
|
||||
@@ -62,7 +62,7 @@ describe('watcher', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
wait().then(() => {
|
||||
wait(1000).then(() => {
|
||||
// nxignored file should not trigger a callback
|
||||
temp.appendFile('app2/main.js', 'update');
|
||||
temp.appendFile('app1/main.js', 'update');
|
||||
@@ -70,18 +70,22 @@ describe('watcher', () => {
|
||||
});
|
||||
|
||||
it('should watch file renames', (done) => {
|
||||
watcher = new Watcher(realpathSync(temp.tempDir));
|
||||
watcher = new Watcher(temp.tempDir);
|
||||
|
||||
watcher.watch((err, paths) => {
|
||||
expect(paths.length).toBe(2);
|
||||
expect(paths.find((p) => p.type === 'update')).toMatchObject({
|
||||
path: 'app1/rename.js',
|
||||
type: 'update',
|
||||
});
|
||||
expect(paths.find((p) => p.type === 'delete')).toMatchObject({
|
||||
path: 'app1/main.js',
|
||||
type: 'delete',
|
||||
});
|
||||
expect(paths.find((p) => p.type === 'create')).toMatchInlineSnapshot(`
|
||||
{
|
||||
"path": "app1/rename.js",
|
||||
"type": "create",
|
||||
}
|
||||
`);
|
||||
expect(paths.find((p) => p.type === 'delete')).toMatchInlineSnapshot(`
|
||||
{
|
||||
"path": "app1/main.js",
|
||||
"type": "delete",
|
||||
}
|
||||
`);
|
||||
done();
|
||||
});
|
||||
|
||||
@@ -91,7 +95,7 @@ describe('watcher', () => {
|
||||
});
|
||||
|
||||
it('should trigger on deletes', (done) => {
|
||||
watcher = new Watcher(realpathSync(temp.tempDir));
|
||||
watcher = new Watcher(temp.tempDir);
|
||||
|
||||
watcher.watch((err, paths) => {
|
||||
expect(paths).toMatchInlineSnapshot(`
|
||||
@@ -111,7 +115,7 @@ describe('watcher', () => {
|
||||
});
|
||||
|
||||
it('should ignore nested gitignores', (done) => {
|
||||
watcher = new Watcher(realpathSync(temp.tempDir));
|
||||
watcher = new Watcher(temp.tempDir);
|
||||
|
||||
watcher.watch((err, paths) => {
|
||||
expect(paths).toMatchInlineSnapshot(`
|
||||
@@ -133,10 +137,10 @@ describe('watcher', () => {
|
||||
});
|
||||
});
|
||||
|
||||
function wait() {
|
||||
function wait(timeout = 500) {
|
||||
return new Promise<void>((res) => {
|
||||
setTimeout(() => {
|
||||
res();
|
||||
}, 500);
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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('\\', "/")
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use napi::bindgen_prelude::*;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use tracing::trace;
|
||||
use watchexec_events::filekind::FileEventKind;
|
||||
use watchexec_events::filekind::ModifyKind::Name;
|
||||
use watchexec_events::filekind::RenameMode;
|
||||
use watchexec_events::{Event, Tag};
|
||||
|
||||
#[napi(string_enum)]
|
||||
@@ -24,11 +26,11 @@ pub struct WatchEvent {
|
||||
pub r#type: EventType,
|
||||
}
|
||||
|
||||
impl From<WatchEventInternal> for WatchEvent {
|
||||
fn from(value: WatchEventInternal) -> Self {
|
||||
impl From<&WatchEventInternal> for WatchEvent {
|
||||
fn from(value: &WatchEventInternal) -> Self {
|
||||
let path = value
|
||||
.path
|
||||
.strip_prefix(&value.origin.expect("origin is available"))
|
||||
.strip_prefix(value.origin.as_ref().expect("origin is available"))
|
||||
.unwrap_or(&value.path)
|
||||
.display()
|
||||
.to_string();
|
||||
@@ -67,11 +69,41 @@ impl From<&Event> for WatchEventInternal {
|
||||
let event_type = if matches!(path.1, None) && !path_ref.exists() {
|
||||
EventType::delete
|
||||
} else {
|
||||
match event_kind {
|
||||
FileEventKind::Create(_) => EventType::create,
|
||||
FileEventKind::Modify(_) => EventType::update,
|
||||
FileEventKind::Remove(_) => EventType::delete,
|
||||
_ => EventType::update,
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use std::fs;
|
||||
use std::os::macos::fs::MetadataExt;
|
||||
|
||||
let t = fs::metadata(path_ref);
|
||||
match t {
|
||||
Err(_) => EventType::delete,
|
||||
Ok(t) => {
|
||||
let modified_time = t.st_mtime();
|
||||
let birth_time = t.st_birthtime();
|
||||
|
||||
// if a file is created and updated near the same time, we always get a create event
|
||||
// so we need to check the timestamps to see if it was created or updated
|
||||
// if the modified time is the same as birth_time then it was created
|
||||
if modified_time == birth_time {
|
||||
EventType::create
|
||||
} else {
|
||||
EventType::update
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
use watchexec_events::filekind::FileEventKind;
|
||||
|
||||
match event_kind {
|
||||
FileEventKind::Create(_) => EventType::create,
|
||||
FileEventKind::Modify(Name(RenameMode::To)) => EventType::create,
|
||||
FileEventKind::Modify(Name(RenameMode::From)) => EventType::delete,
|
||||
FileEventKind::Modify(_) => EventType::update,
|
||||
_ => EventType::update,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ use crate::native::watch::utils::get_ignore_files;
|
||||
use crate::native::watch::watch_filterer::WatchFilterer;
|
||||
use ignore_files::IgnoreFilter;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::trace;
|
||||
use watchexec::config::RuntimeConfig;
|
||||
use watchexec_filterer_ignore::IgnoreFilterer;
|
||||
@@ -36,7 +35,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");
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::Infallible;
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::native::watch::types::{WatchEvent, WatchEventInternal};
|
||||
use itertools::Itertools;
|
||||
use crate::native::watch::types::{EventType, WatchEvent, WatchEventInternal};
|
||||
use napi::bindgen_prelude::*;
|
||||
use napi::threadsafe_function::{
|
||||
ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
||||
@@ -68,27 +68,22 @@ impl Watcher {
|
||||
.with_env_filter(EnvFilter::from_env("NX_NATIVE_LOGGING"))
|
||||
.try_init();
|
||||
|
||||
let mut callback_tsfn: ThreadsafeFunction<HashMap<String, Vec<WatchEventInternal>>> =
|
||||
callback.create_threadsafe_function(
|
||||
0,
|
||||
|ctx: ThreadSafeCallContext<HashMap<String, Vec<WatchEventInternal>>>| {
|
||||
let mut watch_events: Vec<WatchEvent> = vec![];
|
||||
trace!(?ctx.value, "Base collection that will be sent");
|
||||
let mut callback_tsfn: ThreadsafeFunction<HashMap<String, WatchEventInternal>> = callback
|
||||
.create_threadsafe_function(
|
||||
0,
|
||||
|ctx: ThreadSafeCallContext<HashMap<String, WatchEventInternal>>| {
|
||||
let mut watch_events: Vec<WatchEvent> = vec![];
|
||||
trace!(?ctx.value, "Base collection that will be sent");
|
||||
|
||||
for (_, value) in ctx.value {
|
||||
let event = value
|
||||
.first()
|
||||
.expect("should always have at least 1 element")
|
||||
.to_owned();
|
||||
for event in ctx.value.values() {
|
||||
watch_events.push(event.into());
|
||||
}
|
||||
|
||||
watch_events.push(event.into());
|
||||
}
|
||||
trace!(?watch_events, "sending to node");
|
||||
|
||||
trace!(?watch_events, "sending to node");
|
||||
|
||||
Ok(vec![watch_events])
|
||||
},
|
||||
)?;
|
||||
Ok(vec![watch_events])
|
||||
},
|
||||
)?;
|
||||
|
||||
callback_tsfn.unref(&env)?;
|
||||
|
||||
@@ -150,10 +145,30 @@ impl Watcher {
|
||||
})
|
||||
.collect::<Vec<WatchEventInternal>>();
|
||||
|
||||
let group_events = events
|
||||
.into_iter()
|
||||
.into_group_map_by(|g| g.path.display().to_string());
|
||||
let mut group_events: HashMap<String, WatchEventInternal> = HashMap::new();
|
||||
for g in events.into_iter() {
|
||||
let path = g.path.display().to_string();
|
||||
|
||||
// Delete > Create > Modify
|
||||
match group_events.entry(path) {
|
||||
// Delete should override anything
|
||||
Entry::Occupied(mut e) if matches!(g.r#type, EventType::delete) => {
|
||||
e.insert(g);
|
||||
}
|
||||
// Create should override update
|
||||
Entry::Occupied(mut e)
|
||||
if matches!(g.r#type, EventType::create)
|
||||
&& matches!(e.get().r#type, EventType::update) =>
|
||||
{
|
||||
e.insert(g);
|
||||
}
|
||||
Entry::Occupied(_) => {}
|
||||
// If its empty, insert
|
||||
Entry::Vacant(e) => {
|
||||
e.insert(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
callback_tsfn.call(Ok(group_events), ThreadsafeFunctionCallMode::NonBlocking);
|
||||
|
||||
action.outcome(Outcome::Start);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { workspaceRoot } from '../../../utils/workspace-root';
|
||||
import { ProjectGraph } from '../../../config/project-graph';
|
||||
import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder';
|
||||
import { PackageJson } from '../../../utils/package-json';
|
||||
import { fileHasher, hashArray } from '../../../hasher/file-hasher';
|
||||
import { hashArray } from '../../../hasher/file-hasher';
|
||||
import { output } from '../../../utils/output';
|
||||
|
||||
import { parseNpmLockfile, stringifyNpmLockfile } from './npm-parser';
|
||||
@@ -22,6 +22,7 @@ import { parsePnpmLockfile, stringifyPnpmLockfile } from './pnpm-parser';
|
||||
import { parseYarnLockfile, stringifyYarnLockfile } from './yarn-parser';
|
||||
import { pruneProjectGraph } from './project-graph-pruning';
|
||||
import { normalizePackageJson } from './utils/package-json';
|
||||
import { readJsonFile } from '../../../utils/fileutils';
|
||||
|
||||
const YARN_LOCK_FILE = 'yarn.lock';
|
||||
const NPM_LOCK_FILE = 'package-lock.json';
|
||||
@@ -86,7 +87,8 @@ export function parseLockFile(
|
||||
try {
|
||||
if (packageManager === 'yarn') {
|
||||
const content = readFileSync(YARN_LOCK_PATH, 'utf8');
|
||||
parseYarnLockfile(content, builder);
|
||||
const packageJson = readJsonFile('package.json');
|
||||
parseYarnLockfile(content, packageJson, builder);
|
||||
return builder.getUpdatedProjectGraph();
|
||||
}
|
||||
if (packageManager === 'pnpm') {
|
||||
@@ -145,19 +147,19 @@ export function createLockFile(
|
||||
): string {
|
||||
const normalizedPackageJson = normalizePackageJson(packageJson);
|
||||
const content = readFileSync(getLockFileName(packageManager), 'utf8');
|
||||
const rootPackageJson = readJsonFile('package.json');
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
|
||||
try {
|
||||
if (packageManager === 'yarn') {
|
||||
parseYarnLockfile(content, builder);
|
||||
parseYarnLockfile(content, rootPackageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, packageJson);
|
||||
return stringifyYarnLockfile(prunedGraph, content, normalizedPackageJson);
|
||||
}
|
||||
if (packageManager === 'pnpm') {
|
||||
parsePnpmLockfile(content, builder);
|
||||
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, packageJson);
|
||||
return stringifyPnpmLockfile(prunedGraph, content, normalizedPackageJson);
|
||||
|
||||
@@ -157,6 +157,7 @@ describe('yarn LockFile utility', () => {
|
||||
});
|
||||
|
||||
let lockFile;
|
||||
let packageJson;
|
||||
|
||||
let graph: ProjectGraph;
|
||||
|
||||
@@ -166,7 +167,11 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/nextjs/yarn.lock'
|
||||
)).default;
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/nextjs/package.json'
|
||||
));
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
graph = builder.getUpdatedProjectGraph();
|
||||
});
|
||||
|
||||
@@ -219,10 +224,141 @@ describe('yarn LockFile utility', () => {
|
||||
describe('auxiliary packages', () => {
|
||||
beforeEach(() => {
|
||||
const fileSys = {
|
||||
'node_modules/@eslint/eslintrc/package.json': '{"version": "1.3.3"}',
|
||||
'node_modules/@humanwhocodes/config-array/package.json':
|
||||
'{"version": "0.11.7"}',
|
||||
'node_modules/@humanwhocodes/module-importer/package.json':
|
||||
'{"version": "1.0.1"}',
|
||||
'node_modules/@humanwhocodes/object-schema/package.json':
|
||||
'{"version": "1.2.1"}',
|
||||
'node_modules/@nodelib/fs.scandir/package.json': '{"version": "2.1.5"}',
|
||||
'node_modules/@nodelib/fs.stat/package.json': '{"version": "2.0.5"}',
|
||||
'node_modules/@nodelib/fs.walk/package.json': '{"version": "1.2.8"}',
|
||||
'node_modules/@nrwl/devkit/package.json': '{"version": "15.0.13"}',
|
||||
'node_modules/@phenomnomnominal/tsquery/package.json':
|
||||
'{"version": "4.1.1"}',
|
||||
'node_modules/acorn/package.json': '{"version": "8.8.1"}',
|
||||
'node_modules/acorn-jsx/package.json': '{"version": "5.3.2"}',
|
||||
'node_modules/ajv/package.json': '{"version": "6.12.6"}',
|
||||
'node_modules/ansi-regex/package.json': '{"version": "5.0.1"}',
|
||||
'node_modules/ansi-styles/package.json': '{"version": "4.3.0"}',
|
||||
'node_modules/app-root-path/package.json': '{"version": "3.1.0"}',
|
||||
'node_modules/argparse/package.json': '{"version": "2.0.1"}',
|
||||
'node_modules/async/package.json': '{"version": "3.2.4"}',
|
||||
'node_modules/balanced-match/package.json': '{"version": "1.0.2"}',
|
||||
'node_modules/brace-expansion/package.json': '{"version": "1.1.11"}',
|
||||
'node_modules/callsites/package.json': '{"version": "3.1.0"}',
|
||||
'node_modules/chalk/package.json': '{"version": "4.1.2"}',
|
||||
'node_modules/cliui/package.json': '{"version": "8.0.1"}',
|
||||
'node_modules/color-convert/package.json': '{"version": "2.0.1"}',
|
||||
'node_modules/color-name/package.json': '{"version": "1.1.4"}',
|
||||
'node_modules/concat-map/package.json': '{"version": "0.0.1"}',
|
||||
'node_modules/cross-spawn/package.json': '{"version": "7.0.3"}',
|
||||
'node_modules/debug/package.json': '{"version": "4.3.4"}',
|
||||
'node_modules/deep-is/package.json': '{"version": "0.1.4"}',
|
||||
'node_modules/doctrine/package.json': '{"version": "3.0.0"}',
|
||||
'node_modules/ejs/package.json': '{"version": "3.1.8"}',
|
||||
'node_modules/emoji-regex/package.json': '{"version": "8.0.0"}',
|
||||
'node_modules/escalade/package.json': '{"version": "3.1.1"}',
|
||||
'node_modules/escape-string-regexp/package.json':
|
||||
'{"version": "4.0.0"}',
|
||||
'node_modules/eslint/package.json': '{"version": "8.29.0"}',
|
||||
'node_modules/eslint-plugin-disable-autofix/package.json':
|
||||
'{"version": "3.0.0"}',
|
||||
'node_modules/eslint-rule-composer/package.json':
|
||||
'{"version": "0.3.0"}',
|
||||
'node_modules/eslint-scope/package.json': '{"version": "7.1.1"}',
|
||||
'node_modules/eslint-utils/package.json': '{"version": "3.0.0"}',
|
||||
'node_modules/eslint-visitor-keys/package.json': '{"version": "3.3.0"}',
|
||||
'node_modules/ignore/package.json': '{"version": "5.2.4"}',
|
||||
'node_modules/espree/package.json': '{"version": "9.4.1"}',
|
||||
'node_modules/esquery/package.json': '{"version": "1.4.0"}',
|
||||
'node_modules/esrecurse/package.json': '{"version": "4.3.0"}',
|
||||
'node_modules/estraverse/package.json': '{"version": "5.3.0"}',
|
||||
'node_modules/esutils/package.json': '{"version": "2.0.3"}',
|
||||
'node_modules/fast-deep-equal/package.json': '{"version": "3.1.3"}',
|
||||
'node_modules/fast-json-stable-stringify/package.json':
|
||||
'{"version": "2.1.0"}',
|
||||
'node_modules/fast-levenshtein/package.json': '{"version": "2.0.6"}',
|
||||
'node_modules/fastq/package.json': '{"version": "1.14.0"}',
|
||||
'node_modules/file-entry-cache/package.json': '{"version": "6.0.1"}',
|
||||
'node_modules/filelist/package.json': '{"version": "1.0.4"}',
|
||||
'node_modules/find-up/package.json': '{"version": "5.0.0"}',
|
||||
'node_modules/flat-cache/package.json': '{"version": "3.0.4"}',
|
||||
'node_modules/flatted/package.json': '{"version": "3.2.7"}',
|
||||
'node_modules/fs.realpath/package.json': '{"version": "1.0.0"}',
|
||||
'node_modules/get-caller-file/package.json': '{"version": "2.0.5"}',
|
||||
'node_modules/glob/package.json': '{"version": "7.2.3"}',
|
||||
'node_modules/glob-parent/package.json': '{"version": "6.0.2"}',
|
||||
'node_modules/globals/package.json': '{"version": "13.18.0"}',
|
||||
'node_modules/grapheme-splitter/package.json': '{"version": "1.0.4"}',
|
||||
'node_modules/has-flag/package.json': '{"version": "4.0.0"}',
|
||||
'node_modules/ignore/package.json': '{"version": "5.2.1"}',
|
||||
'node_modules/import-fresh/package.json': '{"version": "3.3.0"}',
|
||||
'node_modules/imurmurhash/package.json': '{"version": "0.1.4"}',
|
||||
'node_modules/inflight/package.json': '{"version": "1.0.6"}',
|
||||
'node_modules/inherits/package.json': '{"version": "2.0.4"}',
|
||||
'node_modules/is-extglob/package.json': '{"version": "2.1.1"}',
|
||||
'node_modules/is-fullwidth-code-point/package.json':
|
||||
'{"version": "3.0.0"}',
|
||||
'node_modules/is-glob/package.json': '{"version": "4.0.3"}',
|
||||
'node_modules/is-path-inside/package.json': '{"version": "3.0.3"}',
|
||||
'node_modules/isexe/package.json': '{"version": "2.0.0"}',
|
||||
'node_modules/jake/package.json': '{"version": "10.8.5"}',
|
||||
'node_modules/js-sdsl/package.json': '{"version": "4.2.0"}',
|
||||
'node_modules/js-tokens/package.json': '{"version": "4.0.0"}',
|
||||
'node_modules/js-yaml/package.json': '{"version": "4.1.0"}',
|
||||
'node_modules/json-schema-traverse/package.json':
|
||||
'{"version": "0.4.1"}',
|
||||
'node_modules/json-stable-stringify-without-jsonify/package.json':
|
||||
'{"version": "1.0.1"}',
|
||||
'node_modules/levn/package.json': '{"version": "0.4.1"}',
|
||||
'node_modules/locate-path/package.json': '{"version": "6.0.0"}',
|
||||
'node_modules/lodash.merge/package.json': '{"version": "4.6.2"}',
|
||||
'node_modules/loose-envify/package.json': '{"version": "1.4.0"}',
|
||||
'node_modules/lru-cache/package.json': '{"version": "6.0.0"}',
|
||||
'node_modules/minimatch/package.json': '{"version": "3.1.2"}',
|
||||
'node_modules/ms/package.json': '{"version": "2.1.2"}',
|
||||
'node_modules/natural-compare/package.json': '{"version": "1.4.0"}',
|
||||
'node_modules/once/package.json': '{"version": "1.4.0"}',
|
||||
'node_modules/optionator/package.json': '{"version": "0.9.1"}',
|
||||
'node_modules/p-limit/package.json': '{"version": "3.1.0"}',
|
||||
'node_modules/p-locate/package.json': '{"version": "5.0.0"}',
|
||||
'node_modules/parent-module/package.json': '{"version": "1.0.1"}',
|
||||
'node_modules/path-exists/package.json': '{"version": "4.0.0"}',
|
||||
'node_modules/path-is-absolute/package.json': '{"version": "1.0.1"}',
|
||||
'node_modules/path-key/package.json': '{"version": "3.1.1"}',
|
||||
'node_modules/postgres/package.json': '{"version": "3.2.4"}',
|
||||
'node_modules/prelude-ls/package.json': '{"version": "1.2.1"}',
|
||||
'node_modules/punycode/package.json': '{"version": "2.1.1"}',
|
||||
'node_modules/queue-microtask/package.json': '{"version": "1.2.3"}',
|
||||
'node_modules/react/package.json': '{"version": "18.2.0"}',
|
||||
'node_modules/regexpp/package.json': '{"version": "3.2.0"}',
|
||||
'node_modules/require-directory/package.json': '{"version": "2.1.1"}',
|
||||
'node_modules/resolve-from/package.json': '{"version": "4.0.0"}',
|
||||
'node_modules/reusify/package.json': '{"version": "1.0.4"}',
|
||||
'node_modules/rimraf/package.json': '{"version": "3.0.2"}',
|
||||
'node_modules/run-parallel/package.json': '{"version": "1.2.0"}',
|
||||
'node_modules/semver/package.json': '{"version": "7.3.4"}',
|
||||
'node_modules/shebang-command/package.json': '{"version": "2.0.0"}',
|
||||
'node_modules/shebang-regex/package.json': '{"version": "3.0.0"}',
|
||||
'node_modules/string-width/package.json': '{"version": "4.2.3"}',
|
||||
'node_modules/strip-ansi/package.json': '{"version": "6.0.1"}',
|
||||
'node_modules/strip-json-comments/package.json': '{"version": "3.1.1"}',
|
||||
'node_modules/supports-color/package.json': '{"version": "7.2.0"}',
|
||||
'node_modules/text-table/package.json': '{"version": "0.2.0"}',
|
||||
'node_modules/tslib/package.json': '{"version": "2.4.1"}',
|
||||
'node_modules/type-check/package.json': '{"version": "0.4.0"}',
|
||||
'node_modules/type-fest/package.json': '{"version": "0.20.2"}',
|
||||
'node_modules/uri-js/package.json': '{"version": "4.4.1"}',
|
||||
'node_modules/which/package.json': '{"version": "2.0.2"}',
|
||||
'node_modules/word-wrap/package.json': '{"version": "1.2.3"}',
|
||||
'node_modules/wrap-ansi/package.json': '{"version": "7.0.0"}',
|
||||
'node_modules/wrappy/package.json': '{"version": "1.0.2"}',
|
||||
'node_modules/y18n/package.json': '{"version": "5.0.8"}',
|
||||
'node_modules/yallist/package.json': '{"version": "4.0.0"}',
|
||||
'node_modules/yargs/package.json': '{"version": "17.6.2"}',
|
||||
'node_modules/yargs-parser/package.json': '{"version": "21.1.1"}',
|
||||
'node_modules/yocto-queue/package.json': '{"version": "0.1.0"}',
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
});
|
||||
@@ -232,8 +368,12 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/yarn.lock'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(classicLockFile, builder);
|
||||
parseYarnLockfile(classicLockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(Object.keys(graph.externalNodes).length).toEqual(127);
|
||||
|
||||
@@ -289,6 +429,10 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/yarn.lock'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/package.json'
|
||||
));
|
||||
const normalizedPackageJson = {
|
||||
name: 'test',
|
||||
version: '0.0.0',
|
||||
@@ -311,7 +455,7 @@ describe('yarn LockFile utility', () => {
|
||||
)).default;
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, normalizedPackageJson);
|
||||
const result = stringifyYarnLockfile(
|
||||
@@ -349,7 +493,7 @@ describe('yarn LockFile utility', () => {
|
||||
)).default;
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, normalizedPackageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, normalizedPackageJson);
|
||||
const result = stringifyYarnLockfile(
|
||||
@@ -370,8 +514,12 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/yarn-berry.lock'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(berryLockFile, builder);
|
||||
parseYarnLockfile(berryLockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(Object.keys(graph.externalNodes).length).toEqual(129);
|
||||
|
||||
@@ -448,9 +596,13 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/yarn-berry.lock.pruned'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/package.json'
|
||||
));
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, normalizedPackageJson);
|
||||
const result = stringifyYarnLockfile(
|
||||
@@ -464,6 +616,81 @@ describe('yarn LockFile utility', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('auxiliary packages PnP', () => {
|
||||
it('should parse yarn berry pnp', () => {
|
||||
const berryLockFile = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/yarn-berry.lock'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/auxiliary-packages/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(berryLockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(Object.keys(graph.externalNodes).length).toEqual(129);
|
||||
|
||||
expect(graph.externalNodes['npm:react']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"data": {
|
||||
"hash": "88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b",
|
||||
"packageName": "react",
|
||||
"version": "18.2.0",
|
||||
},
|
||||
"name": "npm:react",
|
||||
"type": "npm",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(graph.externalNodes['npm:typescript']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"data": {
|
||||
"hash": "ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db",
|
||||
"packageName": "typescript",
|
||||
"version": "4.8.4",
|
||||
},
|
||||
"name": "npm:typescript",
|
||||
"type": "npm",
|
||||
}
|
||||
`);
|
||||
expect(graph.externalNodes['npm:@nrwl/devkit']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"data": {
|
||||
"hash": "7dcc3600998448c496228e062d7edd8ecf959fa1ddb9721e91bb1f60f1a2284fd0e12e09edc022170988e2fb54acf101c79dc09fe9c54a21c9941e682eb73b92",
|
||||
"packageName": "@nrwl/devkit",
|
||||
"version": "15.0.13",
|
||||
},
|
||||
"name": "npm:@nrwl/devkit",
|
||||
"type": "npm",
|
||||
}
|
||||
`);
|
||||
expect(graph.externalNodes['npm:postgres']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"data": {
|
||||
"hash": "521660853e0c9f1c604cf43d32c75e2b4675e2d912eaec7bb6749716539dd53f1dfaf575a422087f6a53362f5162f9a4b8a88cc1dadf9d7580423fc05137767a",
|
||||
"packageName": "postgres",
|
||||
"version": "https://github.com/charsleysa/postgres.git#commit=3b1a01b2da3e2fafb1a79006f838eff11a8de3cb",
|
||||
},
|
||||
"name": "npm:postgres",
|
||||
"type": "npm",
|
||||
}
|
||||
`);
|
||||
expect(graph.externalNodes['npm:eslint-plugin-disable-autofix'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"data": {
|
||||
"hash": "fb7272c37e5701df14a79d0f8a9d6a0cb521972011ba91d70290eefc33fca589307908a6fb63e2985257b1c7cc3839c076d1c8def0caabddf21a91f13d7c8fc1",
|
||||
"packageName": "eslint-plugin-disable-autofix",
|
||||
"version": "npm:@mattlewis92/eslint-plugin-disable-autofix@3.0.0",
|
||||
},
|
||||
"name": "npm:eslint-plugin-disable-autofix",
|
||||
"type": "npm",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('duplicate packages', () => {
|
||||
beforeEach(() => {
|
||||
const fileSys = {
|
||||
@@ -507,8 +734,12 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/duplicate-package/yarn.lock'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/duplicate-package/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(classicLockFile, builder);
|
||||
parseYarnLockfile(classicLockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(Object.keys(graph.externalNodes).length).toEqual(371);
|
||||
});
|
||||
@@ -538,7 +769,7 @@ describe('yarn LockFile utility', () => {
|
||||
'__fixtures__/optional/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(Object.keys(graph.externalNodes).length).toEqual(103);
|
||||
|
||||
@@ -716,8 +947,12 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/pruning/typescript/package.json'
|
||||
));
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/pruning/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, typescriptPackageJson);
|
||||
const result = stringifyYarnLockfile(
|
||||
@@ -743,8 +978,12 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/pruning/devkit-yargs/package.json'
|
||||
));
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/pruning/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
const prunedGraph = pruneProjectGraph(graph, multiPackageJson);
|
||||
const result = stringifyYarnLockfile(
|
||||
@@ -776,8 +1015,12 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/workspaces/yarn.lock'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/workspaces/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(Object.keys(graph.externalNodes).length).toEqual(5);
|
||||
});
|
||||
@@ -787,8 +1030,12 @@ describe('yarn LockFile utility', () => {
|
||||
__dirname,
|
||||
'__fixtures__/workspaces/yarn.lock.berry'
|
||||
)).default;
|
||||
const packageJson = require(joinPathFragments(
|
||||
__dirname,
|
||||
'__fixtures__/workspaces/package.json'
|
||||
));
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(Object.keys(graph.externalNodes).length).toEqual(5);
|
||||
});
|
||||
@@ -809,6 +1056,17 @@ describe('yarn LockFile utility', () => {
|
||||
types: './src/index.d.ts',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
const fileSys = {
|
||||
'node_modules/@gitlab-examples/semantic-release-npm/package.json':
|
||||
'{"version": "2.0.1"}',
|
||||
'node_modules/tslib/package.json': '{"version": "2.5.0"}',
|
||||
'node_modules/type-fest/package.json': '{"version": "0.20.2"}',
|
||||
'node_modules/@gar/promisify/package.json': '{"version": "1.1.3"}',
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
});
|
||||
|
||||
it('should parse and prune classic', () => {
|
||||
const lockFile = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
@@ -830,8 +1088,21 @@ type-fest@^0.20.2:
|
||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||
`;
|
||||
|
||||
const packageJson: PackageJson = {
|
||||
name: '@my-ns/example',
|
||||
version: '0.0.1',
|
||||
type: 'commonjs',
|
||||
dependencies: {
|
||||
'@gitlab-examples/semantic-release-npm': '^2.0.1',
|
||||
'type-fest': '^0.20.2',
|
||||
},
|
||||
peerDependencies: {
|
||||
tslib: '^2.4.0',
|
||||
},
|
||||
};
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(graph.externalNodes['npm:tslib']).toMatchInlineSnapshot(`
|
||||
{
|
||||
@@ -905,8 +1176,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
`;
|
||||
|
||||
const packageJson: PackageJson = {
|
||||
name: '@my-ns/example',
|
||||
version: '0.0.1',
|
||||
type: 'commonjs',
|
||||
dependencies: {
|
||||
'@gitlab-examples/semantic-release-npm': '^2.0.1',
|
||||
'@gar/promisify': '^1.1.3',
|
||||
},
|
||||
peerDependencies: {
|
||||
tslib: '^2.4.0',
|
||||
},
|
||||
};
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(graph.externalNodes['npm:tslib']).toMatchInlineSnapshot(`
|
||||
{
|
||||
@@ -973,7 +1258,21 @@ __metadata:
|
||||
describe('mixed keys', () => {
|
||||
beforeEach(() => {
|
||||
const fileSys = {
|
||||
'node_modules/wrap-ansi/package.json': '{"version": "7.0.0"}',
|
||||
'node_modules/@isaacs/cliui/package.json': '{"version": "8.0.2"}',
|
||||
'node_modules/ansi-regex/package.json': '{"version": "5.0.1"}',
|
||||
'node_modules/ansi-styles/package.json': '{"version": "4.3.0"}',
|
||||
'node_modules/cliui/package.json': '{"version": "8.0.1"}',
|
||||
'node_modules/color-convert/package.json': '{"version": "2.0.1"}',
|
||||
'node_modules/color-name/package.json': '{"version": "1.1.4"}',
|
||||
'node_modules/eastasianwidth/package.json': '{"version": "0.2.0"}',
|
||||
'node_modules/emoji-regex/package.json': '{"version": "8.0.0"}',
|
||||
'node_modules/is-fullwidth-code-point/package.json':
|
||||
'{"version": "3.0.0"}',
|
||||
'node_modules/string-width/package.json': '{"version": "5.1.2"}',
|
||||
'node_modules/string-width-cjs/package.json': '{"version": "4.2.3"}',
|
||||
'node_modules/strip-ansi/package.json': '{"version": "7.0.1"}',
|
||||
'node_modules/strip-ansi-cjs/package.json': '{"version": "6.0.1"}',
|
||||
'node_modules/wrap-ansi/package.json': '{"version": "8.1.0"}',
|
||||
'node_modules/wrap-ansi-cjs/package.json': '{"version": "7.0.0"}',
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
@@ -990,7 +1289,7 @@ __metadata:
|
||||
));
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(graph.externalNodes).toMatchInlineSnapshot(`
|
||||
{
|
||||
@@ -1003,13 +1302,13 @@ __metadata:
|
||||
"name": "npm:@isaacs/cliui",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-regex@5.0.1": {
|
||||
"npm:ansi-regex": {
|
||||
"data": {
|
||||
"hash": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"packageName": "ansi-regex",
|
||||
"version": "5.0.1",
|
||||
},
|
||||
"name": "npm:ansi-regex@5.0.1",
|
||||
"name": "npm:ansi-regex",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-regex@6.0.1": {
|
||||
@@ -1021,13 +1320,13 @@ __metadata:
|
||||
"name": "npm:ansi-regex@6.0.1",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-styles@4.3.0": {
|
||||
"npm:ansi-styles": {
|
||||
"data": {
|
||||
"hash": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"packageName": "ansi-styles",
|
||||
"version": "4.3.0",
|
||||
},
|
||||
"name": "npm:ansi-styles@4.3.0",
|
||||
"name": "npm:ansi-styles",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-styles@6.2.1": {
|
||||
@@ -1075,13 +1374,13 @@ __metadata:
|
||||
"name": "npm:eastasianwidth",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:emoji-regex@8.0.0": {
|
||||
"npm:emoji-regex": {
|
||||
"data": {
|
||||
"hash": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"packageName": "emoji-regex",
|
||||
"version": "8.0.0",
|
||||
},
|
||||
"name": "npm:emoji-regex@8.0.0",
|
||||
"name": "npm:emoji-regex",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:emoji-regex@9.2.2": {
|
||||
@@ -1102,6 +1401,15 @@ __metadata:
|
||||
"name": "npm:is-fullwidth-code-point",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:string-width": {
|
||||
"data": {
|
||||
"hash": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"packageName": "string-width",
|
||||
"version": "5.1.2",
|
||||
},
|
||||
"name": "npm:string-width",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:string-width-cjs": {
|
||||
"data": {
|
||||
"hash": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
@@ -1120,13 +1428,13 @@ __metadata:
|
||||
"name": "npm:string-width@4.2.3",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:string-width@5.1.2": {
|
||||
"npm:strip-ansi": {
|
||||
"data": {
|
||||
"hash": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"packageName": "string-width",
|
||||
"version": "5.1.2",
|
||||
"hash": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
|
||||
"packageName": "strip-ansi",
|
||||
"version": "7.0.1",
|
||||
},
|
||||
"name": "npm:string-width@5.1.2",
|
||||
"name": "npm:strip-ansi",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:strip-ansi-cjs": {
|
||||
@@ -1147,20 +1455,11 @@ __metadata:
|
||||
"name": "npm:strip-ansi@6.0.1",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:strip-ansi@7.0.1": {
|
||||
"data": {
|
||||
"hash": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==",
|
||||
"packageName": "strip-ansi",
|
||||
"version": "7.0.1",
|
||||
},
|
||||
"name": "npm:strip-ansi@7.0.1",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:wrap-ansi": {
|
||||
"data": {
|
||||
"hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"hash": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"packageName": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"version": "8.1.0",
|
||||
},
|
||||
"name": "npm:wrap-ansi",
|
||||
"type": "npm",
|
||||
@@ -1174,13 +1473,13 @@ __metadata:
|
||||
"name": "npm:wrap-ansi-cjs",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:wrap-ansi@8.1.0": {
|
||||
"npm:wrap-ansi@7.0.0": {
|
||||
"data": {
|
||||
"hash": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"hash": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"packageName": "wrap-ansi",
|
||||
"version": "8.1.0",
|
||||
"version": "7.0.0",
|
||||
},
|
||||
"name": "npm:wrap-ansi@8.1.0",
|
||||
"name": "npm:wrap-ansi@7.0.0",
|
||||
"type": "npm",
|
||||
},
|
||||
}
|
||||
@@ -1202,7 +1501,7 @@ __metadata:
|
||||
));
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(graph.externalNodes).toMatchInlineSnapshot(`
|
||||
{
|
||||
@@ -1215,13 +1514,13 @@ __metadata:
|
||||
"name": "npm:@isaacs/cliui",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-regex@5.0.1": {
|
||||
"npm:ansi-regex": {
|
||||
"data": {
|
||||
"hash": "2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b",
|
||||
"packageName": "ansi-regex",
|
||||
"version": "5.0.1",
|
||||
},
|
||||
"name": "npm:ansi-regex@5.0.1",
|
||||
"name": "npm:ansi-regex",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-regex@6.0.1": {
|
||||
@@ -1233,13 +1532,13 @@ __metadata:
|
||||
"name": "npm:ansi-regex@6.0.1",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-styles@4.3.0": {
|
||||
"npm:ansi-styles": {
|
||||
"data": {
|
||||
"hash": "513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4",
|
||||
"packageName": "ansi-styles",
|
||||
"version": "4.3.0",
|
||||
},
|
||||
"name": "npm:ansi-styles@4.3.0",
|
||||
"name": "npm:ansi-styles",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:ansi-styles@6.2.1": {
|
||||
@@ -1287,13 +1586,13 @@ __metadata:
|
||||
"name": "npm:eastasianwidth",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:emoji-regex@8.0.0": {
|
||||
"npm:emoji-regex": {
|
||||
"data": {
|
||||
"hash": "d4c5c39d5a9868b5fa152f00cada8a936868fd3367f33f71be515ecee4c803132d11b31a6222b2571b1e5f7e13890156a94880345594d0ce7e3c9895f560f192",
|
||||
"packageName": "emoji-regex",
|
||||
"version": "8.0.0",
|
||||
},
|
||||
"name": "npm:emoji-regex@8.0.0",
|
||||
"name": "npm:emoji-regex",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:emoji-regex@9.2.2": {
|
||||
@@ -1314,6 +1613,15 @@ __metadata:
|
||||
"name": "npm:is-fullwidth-code-point",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:string-width": {
|
||||
"data": {
|
||||
"hash": "7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193",
|
||||
"packageName": "string-width",
|
||||
"version": "5.1.2",
|
||||
},
|
||||
"name": "npm:string-width",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:string-width-cjs": {
|
||||
"data": {
|
||||
"hash": "e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb",
|
||||
@@ -1332,15 +1640,6 @@ __metadata:
|
||||
"name": "npm:string-width@4.2.3",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:string-width@5.1.2": {
|
||||
"data": {
|
||||
"hash": "7369deaa29f21dda9a438686154b62c2c5f661f8dda60449088f9f980196f7908fc39fdd1803e3e01541970287cf5deae336798337e9319a7055af89dafa7193",
|
||||
"packageName": "string-width",
|
||||
"version": "5.1.2",
|
||||
},
|
||||
"name": "npm:string-width@5.1.2",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:strip-ansi-cjs": {
|
||||
"data": {
|
||||
"hash": "f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c",
|
||||
@@ -1370,9 +1669,9 @@ __metadata:
|
||||
},
|
||||
"npm:wrap-ansi": {
|
||||
"data": {
|
||||
"hash": "a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b",
|
||||
"hash": "371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238",
|
||||
"packageName": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"version": "8.1.0",
|
||||
},
|
||||
"name": "npm:wrap-ansi",
|
||||
"type": "npm",
|
||||
@@ -1386,13 +1685,13 @@ __metadata:
|
||||
"name": "npm:wrap-ansi-cjs",
|
||||
"type": "npm",
|
||||
},
|
||||
"npm:wrap-ansi@8.1.0": {
|
||||
"npm:wrap-ansi@7.0.0": {
|
||||
"data": {
|
||||
"hash": "371733296dc2d616900ce15a0049dca0ef67597d6394c57347ba334393599e800bab03c41d4d45221b6bc967b8c453ec3ae4749eff3894202d16800fdfe0e238",
|
||||
"hash": "a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b",
|
||||
"packageName": "wrap-ansi",
|
||||
"version": "8.1.0",
|
||||
"version": "7.0.0",
|
||||
},
|
||||
"name": "npm:wrap-ansi@8.1.0",
|
||||
"name": "npm:wrap-ansi@7.0.0",
|
||||
"type": "npm",
|
||||
},
|
||||
}
|
||||
@@ -1405,6 +1704,18 @@ __metadata:
|
||||
});
|
||||
|
||||
describe('invalid resolved', () => {
|
||||
beforeEach(() => {
|
||||
const fileSys = {
|
||||
'node_modules/@octokit/request-error/package.json':
|
||||
'{"version": "3.0.3"}',
|
||||
'node_modules/@octokit/types/package.json': '{"version": "9.2.0"}',
|
||||
'node_modules/@octokit/webhooks-types/package.json':
|
||||
'{"version": "5.8.0"}',
|
||||
'node_modules/@octokit/webhooks/package.json': '{"version": "9.26.0"}',
|
||||
};
|
||||
vol.fromJSON(fileSys, '/root');
|
||||
});
|
||||
|
||||
it('should parse yarn.lock with invalid resolved field', () => {
|
||||
const lockFile = `# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
@@ -1440,8 +1751,21 @@ __metadata:
|
||||
"@octokit/webhooks-types" "5.8.0"
|
||||
aggregate-error "^3.1.0"
|
||||
`;
|
||||
|
||||
const packageJson: PackageJson = {
|
||||
name: '@my-ns/example',
|
||||
version: '0.0.1',
|
||||
type: 'commonjs',
|
||||
dependencies: {
|
||||
'@octokit/request-error': '^3',
|
||||
'@octokit/types': '^9',
|
||||
'@octokit/webhooks-types': '5.8.0',
|
||||
'@octokit/webhooks': '^9.8.4',
|
||||
},
|
||||
};
|
||||
|
||||
const builder = new ProjectGraphBuilder();
|
||||
parseYarnLockfile(lockFile, builder);
|
||||
parseYarnLockfile(lockFile, packageJson, builder);
|
||||
const graph = builder.getUpdatedProjectGraph();
|
||||
expect(graph.externalNodes).toMatchInlineSnapshot(`
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getHoistedPackageVersion } from './utils/package-json';
|
||||
import { ProjectGraphBuilder } from '../../../project-graph/project-graph-builder';
|
||||
import { satisfies, Range } from 'semver';
|
||||
import { satisfies, Range, gt } from 'semver';
|
||||
import { NormalizedPackageJson } from './utils/package-json';
|
||||
import {
|
||||
ProjectGraph,
|
||||
@@ -37,6 +37,7 @@ type YarnDependency = {
|
||||
|
||||
export function parseYarnLockfile(
|
||||
lockFileContent: string,
|
||||
packageJson: NormalizedPackageJson,
|
||||
builder: ProjectGraphBuilder
|
||||
) {
|
||||
const { parseSyml } = require('@yarnpkg/parsers');
|
||||
@@ -44,7 +45,7 @@ export function parseYarnLockfile(
|
||||
|
||||
// we use key => node map to avoid duplicate work when parsing keys
|
||||
const keyMap = new Map<string, ProjectGraphExternalNode>();
|
||||
addNodes(data, builder, keyMap);
|
||||
addNodes(data, packageJson, builder, keyMap);
|
||||
addDependencies(data, builder, keyMap);
|
||||
}
|
||||
|
||||
@@ -59,11 +60,18 @@ function getPackageNames(keys: string): string[] {
|
||||
|
||||
function addNodes(
|
||||
{ __metadata, ...dependencies }: YarnLockFile,
|
||||
packageJson: NormalizedPackageJson,
|
||||
builder: ProjectGraphBuilder,
|
||||
keyMap: Map<string, ProjectGraphExternalNode>
|
||||
) {
|
||||
const isBerry = !!__metadata;
|
||||
const nodes: Map<string, Map<string, ProjectGraphExternalNode>> = new Map();
|
||||
const combinedDeps = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
...packageJson.peerDependencies,
|
||||
...packageJson.optionalDependencies,
|
||||
};
|
||||
|
||||
Object.entries(dependencies).forEach(([keys, snapshot]) => {
|
||||
// ignore workspace projects & patches
|
||||
@@ -103,23 +111,22 @@ function addNodes(
|
||||
};
|
||||
|
||||
keyMap.set(key, node);
|
||||
// use actual version so we can detect it later based on npm package's version
|
||||
const mapKey =
|
||||
snapshot.version && version !== snapshot.version
|
||||
? snapshot.version
|
||||
: version;
|
||||
if (!nodes.has(packageName)) {
|
||||
nodes.set(packageName, new Map([[version, node]]));
|
||||
nodes.set(packageName, new Map([[mapKey, node]]));
|
||||
} else {
|
||||
nodes.get(packageName).set(version, node);
|
||||
nodes.get(packageName).set(mapKey, node);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
for (const [packageName, versionMap] of nodes.entries()) {
|
||||
let hoistedNode: ProjectGraphExternalNode;
|
||||
if (versionMap.size === 1) {
|
||||
hoistedNode = versionMap.values().next().value;
|
||||
} else {
|
||||
const hoistedVersion = getHoistedVersion(packageName);
|
||||
hoistedNode = versionMap.get(hoistedVersion);
|
||||
}
|
||||
const hoistedNode = findHoistedNode(packageName, versionMap, combinedDeps);
|
||||
if (hoistedNode) {
|
||||
hoistedNode.name = `npm:${packageName}`;
|
||||
}
|
||||
@@ -130,6 +137,44 @@ function addNodes(
|
||||
}
|
||||
}
|
||||
|
||||
function findHoistedNode(
|
||||
packageName: string,
|
||||
versionMap: Map<string, ProjectGraphExternalNode>,
|
||||
combinedDeps: Record<string, string>
|
||||
): ProjectGraphExternalNode {
|
||||
const hoistedVersion = getHoistedVersion(packageName);
|
||||
if (hoistedVersion) {
|
||||
return versionMap.get(hoistedVersion);
|
||||
}
|
||||
const rootVersionSpecifier = combinedDeps[packageName];
|
||||
if (!rootVersionSpecifier) {
|
||||
return;
|
||||
}
|
||||
const versions = Array.from(versionMap.keys()).sort((a, b) =>
|
||||
gt(a, b) ? -1 : 1
|
||||
);
|
||||
// take the highest version found
|
||||
if (rootVersionSpecifier === '*') {
|
||||
return versionMap.get(versions[0]);
|
||||
}
|
||||
// take version that satisfies the root version specifier
|
||||
let version = versions.find((v) => satisfies(v, rootVersionSpecifier));
|
||||
if (!version) {
|
||||
// try to find alias version
|
||||
version = versions.find(
|
||||
(v) =>
|
||||
versionMap.get(v).name === `npm:${packageName}@${rootVersionSpecifier}`
|
||||
);
|
||||
}
|
||||
if (!version) {
|
||||
// try to find tarball package
|
||||
version = versions.find((v) => versionMap.get(v).data.version !== v);
|
||||
}
|
||||
if (version) {
|
||||
return versionMap.get(version);
|
||||
}
|
||||
}
|
||||
|
||||
function findVersion(
|
||||
packageName: string,
|
||||
key: string,
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -181,7 +181,10 @@ export function getOutputsForTargetAndConfiguration(
|
||||
options,
|
||||
});
|
||||
})
|
||||
.filter((output) => !!output && !output.match(/{.*}/));
|
||||
.filter(
|
||||
(output) =>
|
||||
!!output && !output.match(/{(projectRoot|workspaceRoot|(options.*))}/)
|
||||
);
|
||||
}
|
||||
|
||||
// Keep backwards compatibility in case `outputs` doesn't exist
|
||||
|
||||
@@ -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,16 +1,17 @@
|
||||
import { join } from 'path';
|
||||
import { dirname, join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
import {
|
||||
mkdtempSync,
|
||||
readFile,
|
||||
outputFile,
|
||||
rmSync,
|
||||
emptyDirSync,
|
||||
outputFileSync,
|
||||
mkdirpSync,
|
||||
mkdtempSync,
|
||||
outputFile,
|
||||
readFile,
|
||||
realpathSync,
|
||||
rmSync,
|
||||
unlinkSync,
|
||||
} from 'fs-extra';
|
||||
import { joinPathFragments } from '../path';
|
||||
import { appendFileSync, writeFileSync, renameSync } from 'fs';
|
||||
import { appendFileSync, existsSync, renameSync, writeFileSync } from 'fs';
|
||||
|
||||
type NestedFiles = {
|
||||
[fileName: string]: string;
|
||||
@@ -19,7 +20,7 @@ type NestedFiles = {
|
||||
export class TempFs {
|
||||
readonly tempDir: string;
|
||||
constructor(private dirname: string, overrideWorkspaceRoot = true) {
|
||||
this.tempDir = mkdtempSync(join(tmpdir(), this.dirname));
|
||||
this.tempDir = realpathSync(mkdtempSync(join(tmpdir(), this.dirname)));
|
||||
if (overrideWorkspaceRoot) {
|
||||
process.env.NX_WORKSPACE_ROOT_PATH = this.tempDir;
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user