diff --git a/packages/turbo-codemod/__tests__/migrate-env-var-dependencies.test.ts b/packages/turbo-codemod/__tests__/migrate-env-var-dependencies.test.ts index d15031ef7b300..4975e0e95e460 100644 --- a/packages/turbo-codemod/__tests__/migrate-env-var-dependencies.test.ts +++ b/packages/turbo-codemod/__tests__/migrate-env-var-dependencies.test.ts @@ -39,6 +39,34 @@ const getTestTurboConfig = ( }; describe("migrate-env-var-dependencies", () => { + it("skips when no pipeline key", () => { + const config: SchemaV2 = { + $schema: "./docs/public/schema.json", + globalDependencies: ["$GLOBAL_ENV_KEY"], + tasks: { + test: { + outputs: ["coverage/**/*"], + dependsOn: ["^build"], + }, + lint: { + outputs: [], + }, + dev: { + cache: false, + }, + build: { + outputs: ["dist/**/*", ".next/**/*", "!.next/cache/**"], + dependsOn: ["^build", "$TASK_ENV_KEY", "$ANOTHER_ENV_KEY"], + }, + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- Testing a situation outside of types that users can get themselves into at runtime + const doneConfig = migrateConfig(config as any); + + expect(doneConfig).toEqual(config); + }); + describe("hasLegacyEnvVarDependencies - utility", () => { it("finds env keys in legacy turbo.json - has keys", () => { const config = getTestTurboConfig(); diff --git a/packages/turbo-codemod/__tests__/set-default-outputs.test.ts b/packages/turbo-codemod/__tests__/set-default-outputs.test.ts index 60460ec21ea1a..a409a6cedf84e 100644 --- a/packages/turbo-codemod/__tests__/set-default-outputs.test.ts +++ b/packages/turbo-codemod/__tests__/set-default-outputs.test.ts @@ -1,13 +1,45 @@ import { setupTestFixtures } from "@turbo/test-utils"; -import { type Schema } from "@turbo/types"; +import { SchemaV2, type Schema } from "@turbo/types"; import { describe, it, expect } from "@jest/globals"; -import { transformer } from "../src/transforms/set-default-outputs"; +import { + transformer, + migrateConfig, +} from "../src/transforms/set-default-outputs"; describe("set-default-outputs", () => { const { useFixture } = setupTestFixtures({ directory: __dirname, test: "set-default-outputs", }); + + it("skips when no pipeline key", () => { + const config: SchemaV2 = { + $schema: "./docs/public/schema.json", + globalDependencies: ["$GLOBAL_ENV_KEY"], + tasks: { + test: { + outputs: ["coverage/**/*"], + dependsOn: ["^build"], + }, + lint: { + outputs: [], + }, + dev: { + cache: false, + }, + build: { + outputs: ["dist/**/*", ".next/**/*", "!.next/cache/**"], + dependsOn: ["^build", "$TASK_ENV_KEY", "$ANOTHER_ENV_KEY"], + }, + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any -- Testing a situation outside of types that users can get themselves into at runtime + const doneConfig = migrateConfig(config as any); + + expect(doneConfig).toEqual(config); + }); + it("migrates turbo.json outputs - basic", () => { // load the fixture for the test const { root, read } = useFixture({ diff --git a/packages/turbo-codemod/__tests__/stabilize-env-mode.test.ts b/packages/turbo-codemod/__tests__/stabilize-env-mode.test.ts index d60af37412524..1e6dbff81061b 100644 --- a/packages/turbo-codemod/__tests__/stabilize-env-mode.test.ts +++ b/packages/turbo-codemod/__tests__/stabilize-env-mode.test.ts @@ -1,6 +1,11 @@ import { setupTestFixtures } from "@turbo/test-utils"; import { describe, it, expect } from "@jest/globals"; -import { transformer } from "../src/transforms/stabilize-env-mode"; +import type { SchemaV2 } from "@turbo/types"; +import { + transformer, + migrateRootConfig, + migrateTaskConfigs, +} from "../src/transforms/stabilize-env-mode"; describe("stabilize-env-mode", () => { const { useFixture } = setupTestFixtures({ @@ -8,6 +13,62 @@ describe("stabilize-env-mode", () => { test: "stabilize-env-mode", }); + it("skips migrateRootConfig when no pipeline key", () => { + const config: SchemaV2 = { + $schema: "./docs/public/schema.json", + globalDependencies: ["$GLOBAL_ENV_KEY"], + tasks: { + test: { + outputs: ["coverage/**/*"], + dependsOn: ["^build"], + }, + lint: { + outputs: [], + }, + dev: { + cache: false, + }, + build: { + outputs: ["dist/**/*", ".next/**/*", "!.next/cache/**"], + dependsOn: ["^build", "$TASK_ENV_KEY", "$ANOTHER_ENV_KEY"], + }, + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument -- Testing a situation outside of types that users can get themselves into at runtime + const doneConfig = migrateRootConfig(config as any); + + expect(doneConfig).toEqual(config); + }); + + it("skips migrateTaskConfigs when no pipeline key", () => { + const config: SchemaV2 = { + $schema: "./docs/public/schema.json", + globalDependencies: ["$GLOBAL_ENV_KEY"], + tasks: { + test: { + outputs: ["coverage/**/*"], + dependsOn: ["^build"], + }, + lint: { + outputs: [], + }, + dev: { + cache: false, + }, + build: { + outputs: ["dist/**/*", ".next/**/*", "!.next/cache/**"], + dependsOn: ["^build", "$TASK_ENV_KEY", "$ANOTHER_ENV_KEY"], + }, + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument -- Testing a situation outside of types that users can get themselves into at runtime + const doneConfig = migrateTaskConfigs(config as any); + + expect(doneConfig).toEqual(config); + }); + it("migrates env-mode has-both", () => { // load the fixture for the test const { root, read } = useFixture({ diff --git a/packages/turbo-codemod/__tests__/transform-env-literals-to-wildcards.test.ts b/packages/turbo-codemod/__tests__/transform-env-literals-to-wildcards.test.ts index 8e2e2b03b3dda..5cd9eab7cd528 100644 --- a/packages/turbo-codemod/__tests__/transform-env-literals-to-wildcards.test.ts +++ b/packages/turbo-codemod/__tests__/transform-env-literals-to-wildcards.test.ts @@ -1,6 +1,11 @@ import { setupTestFixtures } from "@turbo/test-utils"; import { describe, it, expect } from "@jest/globals"; -import { transformer } from "../src/transforms/transform-env-literals-to-wildcards"; +import type { SchemaV2 } from "@turbo/types"; +import { + transformer, + migrateTaskConfigs, + migrateRootConfig, +} from "../src/transforms/transform-env-literals-to-wildcards"; describe.only("transform-env-literals-to-wildcards", () => { const { useFixture } = setupTestFixtures({ @@ -8,6 +13,62 @@ describe.only("transform-env-literals-to-wildcards", () => { test: "transform-env-literals-to-wildcards", }); + it("skips migrateTaskConfigs when no pipeline key", () => { + const config: SchemaV2 = { + $schema: "./docs/public/schema.json", + globalDependencies: ["$GLOBAL_ENV_KEY"], + tasks: { + test: { + outputs: ["coverage/**/*"], + dependsOn: ["^build"], + }, + lint: { + outputs: [], + }, + dev: { + cache: false, + }, + build: { + outputs: ["dist/**/*", ".next/**/*", "!.next/cache/**"], + dependsOn: ["^build", "$TASK_ENV_KEY", "$ANOTHER_ENV_KEY"], + }, + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument -- Testing a situation outside of types that users can get themselves into at runtime + const doneConfig = migrateTaskConfigs(config as any); + + expect(doneConfig).toEqual(config); + }); + + it("skips migrateRootConfigs when no pipeline key", () => { + const config: SchemaV2 = { + $schema: "./docs/public/schema.json", + globalDependencies: ["$GLOBAL_ENV_KEY"], + tasks: { + test: { + outputs: ["coverage/**/*"], + dependsOn: ["^build"], + }, + lint: { + outputs: [], + }, + dev: { + cache: false, + }, + build: { + outputs: ["dist/**/*", ".next/**/*", "!.next/cache/**"], + dependsOn: ["^build", "$TASK_ENV_KEY", "$ANOTHER_ENV_KEY"], + }, + }, + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument -- Testing a situation outside of types that users can get themselves into at runtime + const doneConfig = migrateRootConfig(config as any); + + expect(doneConfig).toEqual(config); + }); + it("migrates wildcards has-empty", () => { // load the fixture for the test const { root, read } = useFixture({ diff --git a/packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts b/packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts index 1aa7ce9558b82..0e298af2ad19b 100644 --- a/packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts +++ b/packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts @@ -6,6 +6,7 @@ import { getTransformerHelpers } from "../utils/getTransformerHelpers"; import type { TransformerResults } from "../runner"; import type { Transformer, TransformerArgs } from "../types"; import { loadTurboJson } from "../utils/loadTurboJson"; +import { isPipelineKeyMissing } from "../utils/is-pipeline-key-missing"; // transformer details const TRANSFORMER = "migrate-env-var-dependencies"; @@ -14,6 +15,10 @@ const DESCRIPTION = const INTRODUCED_IN = "1.5.0"; export function hasLegacyEnvVarDependencies(config: SchemaV1) { + if (isPipelineKeyMissing(config)) { + return { hasKeys: false }; + } + const dependsOn = [ "extends" in config ? [] : config.globalDependencies, Object.values(config.pipeline).flatMap( @@ -93,6 +98,10 @@ export function migrateGlobal(config: SchemaV1) { } export function migrateConfig(config: SchemaV1) { + if (isPipelineKeyMissing(config)) { + return config; + } + const migratedConfig = migrateGlobal(config); Object.keys(config.pipeline).forEach((pipelineKey) => { config.pipeline; diff --git a/packages/turbo-codemod/src/transforms/set-default-outputs.ts b/packages/turbo-codemod/src/transforms/set-default-outputs.ts index 92d03600ff260..4bdd0af7cc870 100644 --- a/packages/turbo-codemod/src/transforms/set-default-outputs.ts +++ b/packages/turbo-codemod/src/transforms/set-default-outputs.ts @@ -6,6 +6,7 @@ import type { Transformer, TransformerArgs } from "../types"; import { getTransformerHelpers } from "../utils/getTransformerHelpers"; import type { TransformerResults } from "../runner"; import { loadTurboJson } from "../utils/loadTurboJson"; +import { isPipelineKeyMissing } from "../utils/is-pipeline-key-missing"; const DEFAULT_OUTPUTS = ["dist/**", "build/**"]; @@ -16,7 +17,11 @@ const DESCRIPTION = const INTRODUCED_IN = "1.7.0"; const IDEMPOTENT = false; -function migrateConfig(config: SchemaV1) { +export function migrateConfig(config: SchemaV1) { + if (isPipelineKeyMissing(config)) { + return config; + } + for (const [_, taskDef] of Object.entries(config.pipeline)) { if (taskDef.cache !== false) { if (!taskDef.outputs) { diff --git a/packages/turbo-codemod/src/transforms/stabilize-env-mode.ts b/packages/turbo-codemod/src/transforms/stabilize-env-mode.ts index 55e73a50b58cf..1b05e6582ac8d 100644 --- a/packages/turbo-codemod/src/transforms/stabilize-env-mode.ts +++ b/packages/turbo-codemod/src/transforms/stabilize-env-mode.ts @@ -6,6 +6,7 @@ import type { Transformer, TransformerArgs } from "../types"; import { getTransformerHelpers } from "../utils/getTransformerHelpers"; import type { TransformerResults } from "../runner"; import { loadTurboJson } from "../utils/loadTurboJson"; +import { isPipelineKeyMissing } from "../utils/is-pipeline-key-missing"; // transformer details const TRANSFORMER = "stabilize-env-mode"; @@ -26,7 +27,11 @@ type ExperimentalSchema = Omit & { pipeline: Record; }; -function migrateRootConfig(config: ExperimentalRootSchema) { +export function migrateRootConfig(config: ExperimentalRootSchema) { + if (isPipelineKeyMissing(config)) { + return config; + } + const oldConfig = config.experimentalGlobalPassThroughEnv; const newConfig = config.globalPassThroughEnv; // Set to an empty array is meaningful, so we have undefined as an option here. @@ -58,7 +63,11 @@ function migrateRootConfig(config: ExperimentalRootSchema) { return migrateTaskConfigs(config); } -function migrateTaskConfigs(config: ExperimentalSchema) { +export function migrateTaskConfigs(config: ExperimentalSchema) { + if (isPipelineKeyMissing(config)) { + return config; + } + for (const [_, taskDef] of Object.entries(config.pipeline)) { const oldConfig = taskDef.experimentalPassThroughEnv; const newConfig = taskDef.passThroughEnv; diff --git a/packages/turbo-codemod/src/transforms/transform-env-literals-to-wildcards.ts b/packages/turbo-codemod/src/transforms/transform-env-literals-to-wildcards.ts index 0eff4b07fd098..90e159f6ce34c 100644 --- a/packages/turbo-codemod/src/transforms/transform-env-literals-to-wildcards.ts +++ b/packages/turbo-codemod/src/transforms/transform-env-literals-to-wildcards.ts @@ -6,6 +6,7 @@ import type { Transformer, TransformerArgs } from "../types"; import { getTransformerHelpers } from "../utils/getTransformerHelpers"; import type { TransformerResults } from "../runner"; import { loadTurboJson } from "../utils/loadTurboJson"; +import { isPipelineKeyMissing } from "../utils/is-pipeline-key-missing"; // transformer details const TRANSFORMER = "transform-env-literals-to-wildcards"; @@ -27,7 +28,11 @@ function transformEnvVarName(envVarName: string): EnvWildcard { return output; } -function migrateRootConfig(config: RootSchemaV1) { +export function migrateRootConfig(config: RootSchemaV1) { + if (isPipelineKeyMissing(config)) { + return config; + } + const { globalEnv, globalPassThroughEnv } = config; if (Array.isArray(globalEnv)) { @@ -40,7 +45,11 @@ function migrateRootConfig(config: RootSchemaV1) { return migrateTaskConfigs(config); } -function migrateTaskConfigs(config: SchemaV1) { +export function migrateTaskConfigs(config: SchemaV1) { + if (isPipelineKeyMissing(config)) { + return config; + } + for (const [_, taskDef] of Object.entries(config.pipeline)) { const { env, passThroughEnv } = taskDef; diff --git a/packages/turbo-codemod/src/utils/is-pipeline-key-missing.ts b/packages/turbo-codemod/src/utils/is-pipeline-key-missing.ts new file mode 100644 index 0000000000000..a9e3fd50cfb09 --- /dev/null +++ b/packages/turbo-codemod/src/utils/is-pipeline-key-missing.ts @@ -0,0 +1,14 @@ +import type { PipelineV1, SchemaV1 } from "@turbo/types"; + +/** This utility allows us to check that the "pipeline" key exists, + * and early exit if it does not. + * + * Codemods for v1 assume that the "pipeline" key is present. + * However, this isn't a safe assumption. + * A user could run the codemod that changes the "pipeline" key to "tasks" + * and end up failing on a codemod that comes later. Later attempts to run the codemods would fail. + * + * See https://github.com/vercel/turborepo/issues/8495. */ +export const isPipelineKeyMissing = (config: SchemaV1) => { + return !config.pipeline as unknown as PipelineV1 | undefined; +};