Skip to content

Commit 29aeaf0

Browse files
fix(turbo-ignore): infer correct version of turbo for project (#8363)
### Description This PR allows for `turbo-ignore` to use the appropriate version of `turbo` for the project it is pointed at. The strategy is: - `--turbo-version` if users specifies an override for the detection - `turbo` entry in `package.json`'s `dependencies`/`devDependencies` - `turbo@^1` if `pipeline` is in root `turbo.json`, `turbo@^2` if `tasks` is present ### Testing Instructions Added unit tests for finding correct version of turbo based on `package.json` as well as `turbo.json` for getting the major version if necessary. Some manual testing: ``` [0 olszewski@Chriss-MacBook-Pro] /tmp/foo/apps/web $ node ~/code/vercel/turborepo/packages/turbo-ignore/dist/cli.js ≫ Using Turborepo to determine if this project is affected by the commit... ≫ Inferred "web" as workspace from "package.json" ≫ Inferred turbo version "2" from "package.json" ≫ Using "build" as the task from the arguments ≫ Analyzing results of `npx -y turbo@2 run "build" --filter="web...[HEAD^]" --dry=json` ≫ This commit affects "web" ✓ Proceeding with deployment [1 olszewski@Chriss-MacBook-Pro] /tmp/foo/apps/web $ node ~/code/vercel/turborepo/packages/turbo-ignore/dist/cli.js --turbo-version 2.0.2 ≫ Using Turborepo to determine if this project is affected by the commit... ≫ Inferred "web" as workspace from "package.json" ≫ Using turbo version "2.0.2" from arguments ≫ Using "build" as the task from the arguments ≫ Analyzing results of `npx -y [email protected] run "build" --filter="web...[HEAD^]" --dry=json` ≫ This commit affects "web" ✓ Proceeding with deployment [1 olszewski@Chriss-MacBook-Pro] /tmp/foo/apps/web $ vim ../../package.json [0 olszewski@Chriss-MacBook-Pro] /tmp/foo/apps/web $ node ~/code/vercel/turborepo/packages/turbo-ignore/dist/cli.js ≫ Using Turborepo to determine if this project is affected by the commit... ≫ Inferred "web" as workspace from "package.json" ≫ Inferred turbo version ^2 based on "tasks" in "turbo.json" ≫ Using "build" as the task from the arguments ≫ Analyzing results of `npx -y turbo@^2 run "build" --filter="web...[HEAD^]" --dry=json` ≫ This commit affects "web" ✓ Proceeding with deployment [1 olszewski@Chriss-MacBook-Pro] /tmp/foo/apps/web $ vim ../../turbo.json [0 olszewski@Chriss-MacBook-Pro] /tmp/foo/apps/web $ node ~/code/vercel/turborepo/packages/turbo-ignore/dist/cli.js ≫ Using Turborepo to determine if this project is affected by the commit... ≫ Inferred "web" as workspace from "package.json" ≫ Inferred turbo version ^1 based on "pipeline" in "turbo.json" ≫ Using "build" as the task from the arguments ≫ Analyzing results of `npx -y turbo@^1 run "build" --filter="web...[HEAD^]" --dry=json` ≫ This commit affects "web" ✓ Proceeding with deployment ```
1 parent 6555ef7 commit 29aeaf0

File tree

16 files changed

+263
-18
lines changed

16 files changed

+263
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "test-app",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "",
6+
"main": "index.js",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"author": "vercel"
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
// No pipeline or tasks
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "test-app",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "",
6+
"main": "index.js",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"author": "vercel"
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
// A comment!
3+
"tasks": {
4+
"build": {}
5+
}
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "test-app",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "",
6+
"main": "index.js",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"author": "vercel"
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"pipeline": {
3+
"build": {}
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "test-app",
3+
"private": true,
4+
"version": "1.0.0",
5+
"description": "",
6+
"main": "index.js",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"author": "vercel",
11+
"dependencies": {
12+
"turbo": "^99"
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"tasks": {
3+
"build": {}
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { spyConsole, validateLogs } from "@turbo/test-utils";
2+
import { getTurboVersion } from "../src/getTurboVersion";
3+
4+
describe("getWorkspace()", () => {
5+
const mockConsole = spyConsole();
6+
it("getTurboVersion returns turboVersion from arg", () => {
7+
expect(
8+
getTurboVersion(
9+
{
10+
turboVersion: "1.2.3",
11+
},
12+
"./__fixtures__/app"
13+
)
14+
).toEqual("1.2.3");
15+
validateLogs(
16+
['Using turbo version "1.2.3" from arguments'],
17+
mockConsole.log,
18+
{ prefix: "≫ " }
19+
);
20+
});
21+
22+
it("getTurboVersion returns version from package.json", () => {
23+
expect(getTurboVersion({}, "./__fixtures__/turbo_in_deps")).toEqual("^99");
24+
expect(mockConsole.log).toHaveBeenCalledWith(
25+
"≫ ",
26+
'Inferred turbo version "^99" from "package.json"'
27+
);
28+
});
29+
30+
it("getTurboVersion infers ^2 if tasks in turbo.json", () => {
31+
expect(getTurboVersion({}, "./__fixtures__/no_turbo_deps")).toEqual("^2");
32+
expect(mockConsole.log).toHaveBeenCalledWith(
33+
"≫ ",
34+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"'
35+
);
36+
});
37+
38+
it("getTurboVersion infers ^1 if pipeline in turbo.json", () => {
39+
expect(getTurboVersion({}, "./__fixtures__/no_turbo_deps_v1")).toEqual(
40+
"^1"
41+
);
42+
expect(mockConsole.log).toHaveBeenCalledWith(
43+
"≫ ",
44+
'Inferred turbo version ^1 based on "pipeline" in "turbo.json"'
45+
);
46+
});
47+
48+
it("getTurboVersion return null if no turbo.json", () => {
49+
expect(getTurboVersion({}, "./__fixtures__/app")).toEqual(null);
50+
expect(mockConsole.error).toHaveBeenCalledWith(
51+
"≫ ",
52+
'"__fixtures__/app/turbo.json" could not be read. turbo-ignore turbo version inference failed'
53+
);
54+
});
55+
56+
it("getTurboVersion return null if no package.json", () => {
57+
expect(getTurboVersion({}, "./__fixtures__/no-app")).toEqual(null);
58+
expect(mockConsole.error).toHaveBeenCalledWith(
59+
"≫ ",
60+
'"__fixtures__/no-app/package.json" could not be read. turbo-ignore turbo version inference failed'
61+
);
62+
});
63+
64+
it("getTurboVersion return null if invalid JSON", () => {
65+
expect(getTurboVersion({}, "./__fixtures__/invalid_turbo_json")).toEqual(
66+
null
67+
);
68+
});
69+
});

packages/turbo-ignore/__tests__/ignore.test.ts

+50-17
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe("turboIgnore()", () => {
5959
turboIgnore("test-workspace", { telemetry });
6060

6161
expect(mockExec).toHaveBeenCalledWith(
62-
`npx turbo run build --filter="test-workspace...[HEAD^]" --dry=json`,
62+
`npx -y turbo@^2 run build --filter="test-workspace...[HEAD^]" --dry=json`,
6363
expect.anything(),
6464
expect.anything()
6565
);
@@ -92,7 +92,7 @@ describe("turboIgnore()", () => {
9292
turboIgnore("test-workspace", {});
9393

9494
expect(mockExec).toHaveBeenCalledWith(
95-
`npx turbo run build --filter="test-workspace...[HEAD^]" --dry=json`,
95+
`npx -y turbo@^2 run build --filter="test-workspace...[HEAD^]" --dry=json`,
9696
expect.anything(),
9797
expect.anything()
9898
);
@@ -137,7 +137,7 @@ describe("turboIgnore()", () => {
137137
turboIgnore("test-workspace", { telemetry });
138138

139139
expect(mockExec).toHaveBeenCalledWith(
140-
`npx turbo run build --filter="test-workspace...[too-far-back]" --dry=json`,
140+
`npx -y turbo@^2 run build --filter="test-workspace...[too-far-back]" --dry=json`,
141141
expect.anything(),
142142
expect.anything()
143143
);
@@ -175,7 +175,7 @@ describe("turboIgnore()", () => {
175175
turboIgnore("test-workspace", { fallback: "HEAD^" });
176176

177177
expect(mockExec).toHaveBeenCalledWith(
178-
`npx turbo run build --filter="test-workspace...[HEAD^]" --dry=json`,
178+
`npx -y turbo@^2 run build --filter="test-workspace...[HEAD^]" --dry=json`,
179179
expect.anything(),
180180
expect.anything()
181181
);
@@ -250,7 +250,7 @@ describe("turboIgnore()", () => {
250250
process.env.VERCEL_GIT_COMMIT_REF = "my-branch";
251251
turboIgnore("test-app", { directory: "__fixtures__/app" });
252252
expect(mockConsole.log).toHaveBeenNthCalledWith(
253-
4,
253+
5,
254254
"≫ ",
255255
'No previous deployments found for "test-app" on branch "my-branch"'
256256
);
@@ -283,9 +283,10 @@ describe("turboIgnore()", () => {
283283
[
284284
"Using Turborepo to determine if this project is affected by the commit...\n",
285285
'Inferred "test-app" as workspace from "package.json"',
286+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
286287
'Using "build" as the task as it was unspecified',
287288
`Found previous deployment ("last-deployed-sha") for "test-app" on branch "my-branch"`,
288-
'Analyzing results of `npx turbo run build --filter="test-app...[last-deployed-sha]" --dry=json`',
289+
'Analyzing results of `npx -y turbo@^2 run build --filter="test-app...[last-deployed-sha]" --dry=json`',
289290
"This project and its dependencies are not affected",
290291
() => expect.stringContaining("⏭ Ignoring the change"),
291292
],
@@ -327,9 +328,10 @@ describe("turboIgnore()", () => {
327328
[
328329
"Using Turborepo to determine if this project is affected by the commit...\n",
329330
'Inferred "test-app" as workspace from "package.json"',
331+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
330332
'Using "workspace#build" as the task from the arguments',
331333
'Found previous deployment ("last-deployed-sha") for "test-app" on branch "my-branch"',
332-
'Analyzing results of `npx turbo run "workspace#build" --filter="test-app...[last-deployed-sha]" --dry=json`',
334+
'Analyzing results of `npx -y turbo@^2 run "workspace#build" --filter="test-app...[last-deployed-sha]" --dry=json`',
333335
'This commit affects "test-app"',
334336
() => expect.stringContaining("✓ Proceeding with deployment"),
335337
],
@@ -368,9 +370,10 @@ describe("turboIgnore()", () => {
368370
[
369371
"Using Turborepo to determine if this project is affected by the commit...\n",
370372
'Inferred "test-app" as workspace from "package.json"',
373+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
371374
'Using "build" as the task as it was unspecified',
372375
'Found previous deployment ("last-deployed-sha") for "test-app" on branch "my-branch"',
373-
'Analyzing results of `npx turbo run build --filter="test-app...[last-deployed-sha]" --dry=json`',
376+
'Analyzing results of `npx -y turbo@^2 run build --filter="test-app...[last-deployed-sha]" --dry=json`',
374377
'This commit affects "test-app" and 1 dependency (ui)',
375378
() => expect.stringContaining("✓ Proceeding with deployment"),
376379
],
@@ -409,9 +412,10 @@ describe("turboIgnore()", () => {
409412
[
410413
"Using Turborepo to determine if this project is affected by the commit...\n",
411414
'Inferred "test-app" as workspace from "package.json"',
415+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
412416
'Using "build" as the task as it was unspecified',
413417
'Found previous deployment ("last-deployed-sha") for "test-app" on branch "my-branch"',
414-
'Analyzing results of `npx turbo run build --filter="test-app...[last-deployed-sha]" --dry=json`',
418+
'Analyzing results of `npx -y turbo@^2 run build --filter="test-app...[last-deployed-sha]" --dry=json`',
415419
'This commit affects "test-app" and 2 dependencies (ui, tsconfig)',
416420
() => expect.stringContaining("✓ Proceeding with deployment"),
417421
],
@@ -459,10 +463,11 @@ describe("turboIgnore()", () => {
459463
[
460464
"Using Turborepo to determine if this project is affected by the commit...\n",
461465
'Inferred "test-app" as workspace from "package.json"',
466+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
462467
'Using "workspace#build" as the task from the arguments',
463468
'Previous deployment ("last-deployed-sha") for "test-app" on branch "my-branch" is unreachable.',
464469
"Falling back to ref HEAD^2",
465-
'Analyzing results of `npx turbo run "workspace#build" --filter="test-app...[HEAD^2]" --dry=json`',
470+
'Analyzing results of `npx -y turbo@^2 run "workspace#build" --filter="test-app...[HEAD^2]" --dry=json`',
466471
'This commit affects "test-app"',
467472
() => expect.stringContaining("✓ Proceeding with deployment"),
468473
],
@@ -488,13 +493,13 @@ describe("turboIgnore()", () => {
488493
turboIgnore(undefined, { directory: "__fixtures__/app" });
489494

490495
expect(mockExec).toHaveBeenCalledWith(
491-
`npx turbo run build --filter="test-app...[HEAD^]" --dry=json`,
496+
`npx -y turbo@^2 run build --filter="test-app...[HEAD^]" --dry=json`,
492497
expect.anything(),
493498
expect.anything()
494499
);
495500
validateLogs(
496501
[
497-
'Failed to parse JSON output from `npx turbo run build --filter="test-app...[HEAD^]" --dry=json`.',
502+
'Failed to parse JSON output from `npx -y turbo@^2 run build --filter="test-app...[HEAD^]" --dry=json`.',
498503
],
499504
mockConsole.error,
500505
{ prefix: "≫ " }
@@ -521,13 +526,13 @@ describe("turboIgnore()", () => {
521526
turboIgnore(undefined, { directory: "__fixtures__/app" });
522527

523528
expect(mockExec).toHaveBeenCalledWith(
524-
`npx turbo run build --filter="test-app...[HEAD^]" --dry=json`,
529+
`npx -y turbo@^2 run build --filter="test-app...[HEAD^]" --dry=json`,
525530
expect.anything(),
526531
expect.anything()
527532
);
528533
validateLogs(
529534
[
530-
'Failed to parse JSON output from `npx turbo run build --filter="test-app...[HEAD^]" --dry=json`.',
535+
'Failed to parse JSON output from `npx -y turbo@^2 run build --filter="test-app...[HEAD^]" --dry=json`.',
531536
],
532537
mockConsole.error,
533538
{ prefix: "≫ " }
@@ -547,6 +552,7 @@ describe("turboIgnore()", () => {
547552
[
548553
"Using Turborepo to determine if this project is affected by the commit...\n",
549554
'Inferred "test-app" as workspace from "package.json"',
555+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
550556
'Using "build" as the task as it was unspecified',
551557
"Found commit message: [vercel skip]",
552558
() => expect.stringContaining("⏭ Ignoring the change"),
@@ -568,6 +574,7 @@ describe("turboIgnore()", () => {
568574
[
569575
"Using Turborepo to determine if this project is affected by the commit...\n",
570576
'Inferred "test-app" as workspace from "package.json"',
577+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
571578
'Using "build" as the task as it was unspecified',
572579
"Found commit message: [vercel deploy]",
573580
() => expect.stringContaining("✓ Proceeding with deployment"),
@@ -608,10 +615,11 @@ describe("turboIgnore()", () => {
608615
[
609616
"Using Turborepo to determine if this project is affected by the commit...\n",
610617
'Inferred "test-app" as workspace from "package.json"',
618+
'Inferred turbo version ^2 based on "tasks" in "turbo.json"',
611619
'Using "build" as the task as it was unspecified',
612620
"Conflicting commit messages found: [vercel deploy] and [vercel skip]",
613621
`Found previous deployment ("last-deployed-sha") for "test-app" on branch "my-branch"`,
614-
'Analyzing results of `npx turbo run build --filter="test-app...[last-deployed-sha]" --dry=json`',
622+
'Analyzing results of `npx -y turbo@^2 run build --filter="test-app...[last-deployed-sha]" --dry=json`',
615623
"This project and its dependencies are not affected",
616624
() => expect.stringContaining("⏭ Ignoring the change"),
617625
],
@@ -641,7 +649,7 @@ describe("turboIgnore()", () => {
641649
turboIgnore(undefined, { directory: "__fixtures__/app", maxBuffer: 1024 });
642650

643651
expect(mockExec).toHaveBeenCalledWith(
644-
`npx turbo run build --filter="test-app...[HEAD^]" --dry=json`,
652+
`npx -y turbo@^2 run build --filter="test-app...[HEAD^]" --dry=json`,
645653
expect.objectContaining({ maxBuffer: 1024 }),
646654
expect.anything()
647655
);
@@ -670,7 +678,7 @@ describe("turboIgnore()", () => {
670678
});
671679

672680
expect(mockExec).toHaveBeenCalledWith(
673-
`npx turbo run build --filter="test-app...[HEAD^]" --dry=json`,
681+
`npx -y turbo@^2 run build --filter="test-app...[HEAD^]" --dry=json`,
674682
expect.objectContaining({ maxBuffer: 1024 }),
675683
expect.anything()
676684
);
@@ -699,4 +707,29 @@ describe("turboIgnore()", () => {
699707
expectBuild(mockExit);
700708
mockExec.mockRestore();
701709
});
710+
711+
it("defaults to latest turbo if no hints for version", () => {
712+
const mockExec = jest
713+
.spyOn(child_process, "exec")
714+
.mockImplementation((command, options, callback) => {
715+
if (callback) {
716+
return callback(
717+
null,
718+
'{"packages": [],"tasks":[]}',
719+
"stderr"
720+
) as unknown as ChildProcess;
721+
}
722+
return {} as unknown as ChildProcess;
723+
});
724+
725+
turboIgnore(undefined, { directory: "__fixtures__/invalid_turbo_json" });
726+
727+
expect(mockExec).toHaveBeenCalledWith(
728+
`npx -y turbo run build --filter="test-app...[HEAD^]" --dry=json`,
729+
expect.anything(),
730+
expect.anything()
731+
);
732+
733+
mockExec.mockRestore();
734+
});
702735
});

packages/turbo-ignore/package.json

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
"check-types": "tsc --noEmit",
2626
"lint:prettier": "prettier -c . --cache --ignore-path=../../.prettierignore"
2727
},
28+
"dependencies": {
29+
"json5": "^2.2.3"
30+
},
2831
"devDependencies": {
2932
"@turbo/eslint-config": "workspace:*",
3033
"@turbo/telemetry": "workspace:*",

packages/turbo-ignore/src/cli.ts

+6
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ turboIgnoreCli
5454
"The directory to run in (default: cwd)"
5555
)
5656
)
57+
.addOption(
58+
new Option(
59+
"--turbo-version <version>",
60+
"Explicitly set which version of turbo to invoke"
61+
)
62+
)
5763
.addOption(
5864
new Option(
5965
"-b, --max-buffer <number>",

0 commit comments

Comments
 (0)