Skip to content

Commit dae18c9

Browse files
authored
fix(add-caching): explicitly set targetDefaults for all scripts (#3929)
1 parent 3d747a1 commit dae18c9

8 files changed

Lines changed: 134 additions & 131 deletions

File tree

packages/lerna/src/commands/add-caching/README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ If you mark `build` as needing topological order, the `nx.json` file will look l
3232
}
3333
```
3434

35+
> **Note:** Before running `add-caching`, Lerna assumes that all scripts need to be run in order.
36+
> After running `add-caching`, Lerna will only run scripts in order if they are marked as needing topological order.
37+
3538
### Which scripts are cacheable?
3639

3740
Each script selected will be cached by Lerna. Only select scripts that do not depend on any external inputs (like network calls). `build` and `test` are usually cacheable. `start` and `serve` are usually not cacheable. Sometimes `e2e` is cacheable.
@@ -40,14 +43,9 @@ If you mark `build` as cacheable, the `nx.json` file will look like this:
4043

4144
```jsonc
4245
{
43-
"tasksRunnerOptions": {
44-
"default": {
45-
"runner": "nx/tasks-runners/default",
46-
"options": {
47-
"cacheableOperations": [
48-
"build" // cache the build script
49-
]
50-
}
46+
"targetDefaults": {
47+
"build": {
48+
"cache": true
5149
}
5250
}
5351
}

packages/lerna/src/commands/add-caching/index.ts

Lines changed: 47 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import {
66
workspaceRoot,
77
writeJsonFile,
88
} from "@nx/devkit";
9+
import execa from "execa";
10+
import { appendFile } from "fs-extra";
911
import inquirer from "inquirer";
1012
import log from "npmlog";
1113

@@ -47,6 +49,13 @@ class AddCachingCommand extends Command {
4749
}
4850

4951
override async execute() {
52+
const nxJsonPath = joinPathFragments(workspaceRoot, "nx.json");
53+
let nxJson: NxJsonConfiguration = {};
54+
try {
55+
nxJson = readJsonFile(nxJsonPath);
56+
// eslint-disable-next-line no-empty
57+
} catch {}
58+
5059
this.logger.info(
5160
"add-caching",
5261
"Please answer the following questions about the scripts found in your workspace in order to generate task runner configuration"
@@ -55,16 +64,23 @@ class AddCachingCommand extends Command {
5564

5665
log.pause();
5766

67+
const existingTargetDefaults = this.uniqueScriptNames.filter(
68+
(scriptName) => nxJson.targetDefaults?.[scriptName]?.dependsOn?.length
69+
);
5870
const { targetDefaults } = await inquirer.prompt<{ targetDefaults: UserAnswers["targetDefaults"] }>([
5971
{
6072
type: "checkbox",
6173
name: "targetDefaults",
6274
message:
6375
"Which scripts need to be run in order? (e.g. before building a project, dependent projects must be built.)\n",
6476
choices: this.uniqueScriptNames,
77+
default: existingTargetDefaults,
6578
},
6679
]);
6780

81+
const existingCacheableOperations = this.uniqueScriptNames.filter(
82+
(scriptName) => nxJson.targetDefaults?.[scriptName]?.cache
83+
);
6884
const { cacheableOperations } = await inquirer.prompt<{
6985
cacheableOperations: UserAnswers["cacheableOperations"];
7086
}>([
@@ -74,13 +90,13 @@ class AddCachingCommand extends Command {
7490
message:
7591
"Which scripts are cacheable? (Produce the same output given the same input, e.g. build, test and lint usually are, serve and start are not.)\n",
7692
choices: this.uniqueScriptNames,
93+
default: existingCacheableOperations,
7794
},
7895
]);
7996

8097
const scriptOutputs: UserAnswers["scriptOutputs"] = {};
8198

8299
for (const scriptName of cacheableOperations) {
83-
// eslint-disable-next-line no-await-in-loop
84100
scriptOutputs[scriptName] = await inquirer.prompt<Record<string, string>>([
85101
{
86102
type: "input",
@@ -94,7 +110,9 @@ class AddCachingCommand extends Command {
94110

95111
process.stdout.write("\n");
96112

97-
this.convertAnswersToNxConfig({ cacheableOperations, targetDefaults, scriptOutputs });
113+
this.convertAnswersToNxConfig({ cacheableOperations, targetDefaults, scriptOutputs }, nxJsonPath, nxJson);
114+
115+
await this.configureGitIgnore();
98116

99117
this.logger["success"]("add-caching", "Successfully updated task runner configuration in `nx.json`");
100118

@@ -108,29 +126,7 @@ class AddCachingCommand extends Command {
108126
);
109127
}
110128

111-
convertAnswersToNxConfig(answers: UserAnswers) {
112-
const nxJsonPath = joinPathFragments(workspaceRoot, "nx.json");
113-
let nxJson: NxJsonConfiguration = {};
114-
try {
115-
nxJson = readJsonFile(nxJsonPath);
116-
// eslint-disable-next-line no-empty
117-
} catch {}
118-
119-
nxJson.tasksRunnerOptions = nxJson.tasksRunnerOptions || {};
120-
nxJson.tasksRunnerOptions["default"] = nxJson.tasksRunnerOptions["default"] || {};
121-
nxJson.tasksRunnerOptions["default"].runner =
122-
nxJson.tasksRunnerOptions["default"].runner || "nx/tasks-runners/default";
123-
nxJson.tasksRunnerOptions["default"].options = nxJson.tasksRunnerOptions["default"].options || {};
124-
125-
if (nxJson.tasksRunnerOptions["default"].options.cacheableOperations) {
126-
this.logger.warn(
127-
"add-caching",
128-
"The `tasksRunnerOptions.default.cacheableOperations` property already exists in `nx.json` and will be overwritten by your answers"
129-
);
130-
}
131-
132-
nxJson.tasksRunnerOptions["default"].options.cacheableOperations = answers.cacheableOperations;
133-
129+
private convertAnswersToNxConfig(answers: UserAnswers, nxJsonPath: string, nxJson: NxJsonConfiguration) {
134130
if (nxJson.targetDefaults) {
135131
this.logger.warn(
136132
"add-caching",
@@ -140,14 +136,21 @@ class AddCachingCommand extends Command {
140136

141137
nxJson.targetDefaults = nxJson.targetDefaults || {};
142138

143-
for (const scriptName of answers.targetDefaults) {
139+
for (const scriptName of this.uniqueScriptNames) {
144140
nxJson.targetDefaults[scriptName] = nxJson.targetDefaults[scriptName] || {};
145-
nxJson.targetDefaults[scriptName] = { dependsOn: [`^${scriptName}`] };
141+
if (answers.cacheableOperations.includes(scriptName)) {
142+
nxJson.targetDefaults[scriptName].cache = true;
143+
} else {
144+
delete nxJson.targetDefaults[scriptName].cache;
145+
}
146+
// always set dependsOn, even if empty array, so that `lerna run` knows not to assume any dependencies
147+
nxJson.targetDefaults[scriptName].dependsOn = answers.targetDefaults.includes(scriptName)
148+
? [`^${scriptName}`]
149+
: [];
146150
}
147151

148152
for (const [scriptName, scriptAnswerData] of Object.entries(answers.scriptOutputs)) {
149153
if (!scriptAnswerData[scriptName]) {
150-
// eslint-disable-next-line no-continue
151154
continue;
152155
}
153156
nxJson.targetDefaults[scriptName] = nxJson.targetDefaults[scriptName] || {};
@@ -156,6 +159,22 @@ class AddCachingCommand extends Command {
156159

157160
writeJsonFile(nxJsonPath, nxJson);
158161
}
162+
163+
private async configureGitIgnore(): Promise<void> {
164+
try {
165+
await execa("git", ["check-ignore", ".nx/cache"]);
166+
// .nx/cache is already ignored - no need to update .gitignore
167+
} catch (e) {
168+
try {
169+
await appendFile(joinPathFragments(workspaceRoot, ".gitignore"), "\n.nx/cache\n");
170+
} catch (e) {
171+
this.logger.warn(
172+
"add-caching",
173+
"Failed to update `.gitignore` with `.nx/cache`. Please update manually."
174+
);
175+
}
176+
}
177+
}
159178
}
160179

161180
module.exports.AddCachingCommand = AddCachingCommand;

website/docs/api-reference/configuration.md

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,6 @@ Find the available options in [the API docs](/docs/api-reference/commands).
6767
6868
```json title="nx.json"
6969
{
70-
"tasksRunnerOptions": {
71-
"default": {
72-
"runner": "nx/tasks-runners/default",
73-
"options": {
74-
"cacheableOperations": ["build", "test"]
75-
}
76-
}
77-
},
7870
"namedInputs": {
7971
"default": ["{projectRoot}/**/*"],
8072
"prod": ["!{projectRoot}/**/*.spec.tsx"]
@@ -83,32 +75,27 @@ Find the available options in [the API docs](/docs/api-reference/commands).
8375
"build": {
8476
"dependsOn": ["prebuild", "^build"],
8577
"inputs": ["prod", "^prod"],
86-
"outputs": ["{projectRoot}/dist"]
78+
"outputs": ["{projectRoot}/dist"],
79+
"cache": true
8780
},
8881
"test": {
89-
"inputs": ["default", "^prod", "{workspaceRoot}/jest.config.ts"]
82+
"inputs": ["default", "^prod", "{workspaceRoot}/jest.config.ts"],
83+
"cache": true
9084
}
9185
}
9286
}
9387
```
9488

95-
## taskRunnerOptions
96-
97-
### runner
98-
99-
Everything in Nx is customizable, including running npm scripts. Most of the time you will either use the default runner
100-
or the `@nrwl/nx-cloud` runner.
101-
102-
### cacheableOperations
103-
104-
The `cacheableOperations` array defines the list of npm scripts/operations that are cached by Nx. In most repos all
105-
non-long running tasks (i.e., not `serve`) should be cacheable.
106-
10789
## Target Defaults
10890

10991
Targets are npm script names. You can add metadata associated with say the build script of each project in the repo in
11092
the `targetDefaults` section.
11193

94+
### cache
95+
96+
When set to `true`, tells Nx to cache the results of running the script. In most repos all
97+
non-long running tasks (i.e., not `serve`) should be cacheable.
98+
11299
### dependsOn
113100

114101
Targets can depend on other targets. A common scenario is having to build dependencies of a project first before
@@ -288,3 +275,7 @@ Using pseudocode `outputs = packageJson.targets.build.outputs || nxJson.targetDe
288275
The `"implicitDependencies": ["projecta", "!projectb"]` line tells Nx that the parent project depends on `projecta` even
289276
though there is no dependency in its `package.json`. Nx will treat such a dependency in the same way it treats explicit
290277
dependencies. It also tells Nx that even though there is an explicit dependency on `projectb`, it should be ignored.
278+
279+
## Additional Configuration
280+
281+
For additional ways to configure tasks and caching, see the relevant [Nx documentation](https://nx.dev/recipes/running-tasks).

website/docs/concepts/how-caching-works.md

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -189,20 +189,6 @@ npx lerna run build --skip-nx-cache
189189
npx lerna run test --skip-nx-cache
190190
```
191191

192-
## Customizing the Cache Location
192+
## Additional Configuration
193193

194-
The cache is stored in `node_modules/.cache/nx` by default. To change the cache location, update the `cacheDirectory`
195-
option for the task runner in `nx.json`:
196-
197-
```json
198-
{
199-
"tasksRunnerOptions": {
200-
"default": {
201-
"options": {
202-
"cacheableOperations": ["build", "test"],
203-
"cacheDirectory": "/tmp/mycache"
204-
}
205-
}
206-
}
207-
}
208-
```
194+
For additional ways to configure tasks and caching, see the relevant [Nx documentation](https://nx.dev/recipes/running-tasks).

website/docs/concepts/task-pipeline-configuration.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,6 @@ following:
2424
npx lerna run build --concurrency=5
2525
```
2626

27-
Note, you can also change the default in `nx.json`, like this:
28-
29-
```json title="nx.json"
30-
{
31-
"tasksRunnerOptions": {
32-
"default": {
33-
"runner": "nx/tasks-runners/default",
34-
"options": {
35-
"cacheableOperations": [],
36-
"parallel": 5
37-
}
38-
}
39-
}
40-
}
41-
```
42-
4327
## Define Task Dependencies (aka Task Pipelines)
4428

4529
Without our help Lerna cannot know what targets (scripts) have prerequisites and which ones don't. You can define task dependencies in the `nx.json` file:

website/docs/features/cache-tasks.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,24 @@ If you don't have `nx.json`, run `npx lerna add-caching`.
2323

2424
:::
2525

26-
To enable caching for `build` and `test`, edit the `cacheableOperations` property in `nx.json` to include the `build` and `test` tasks:
26+
To enable caching for `build` and `test`, edit the `targetDefaults` property in `nx.json` to include the `build` and `test` tasks:
2727

2828
```json title="nx.json"
2929
{
30-
"tasksRunnerOptions": {
31-
"default": {
32-
"runner": "nx/tasks-runners/default",
33-
"options": {
34-
"cacheableOperations": ["build", "test"]
35-
}
30+
"targetDefaults": {
31+
"build": {
32+
"cache": true
33+
},
34+
"test": {
35+
"cache": true
3636
}
3737
}
3838
}
3939
```
4040

4141
:::info
4242

43-
Note, `cacheableOperations` need to be side effect free, meaning that given the same input they should always result in
43+
Note, cacheable operations need to be side effect free, meaning that given the same input they should always result in
4444
the same output. As an example, e2e test runs that hit the backend API cannot be cached as the backend might influence
4545
the result of the test run.
4646

0 commit comments

Comments
 (0)