diff --git a/docs/site/content/docs/reference/turbo-codemod.mdx b/docs/site/content/docs/reference/turbo-codemod.mdx index 65913b895c2d3..fc41386b2d7f9 100644 --- a/docs/site/content/docs/reference/turbo-codemod.mdx +++ b/docs/site/content/docs/reference/turbo-codemod.mdx @@ -39,6 +39,26 @@ All the codemods you need to upgrade will be run for you. The codemods below are used for migration paths in the second major version of Turborepo. + + + +Updates a versioned schema.json URL to v2. + +```bash title="Terminal" +npx @turbo/codemod update-schema-json-url +``` + +**Example** + +```diff title="./turbo.json" +{ +- "$schema": "https://turbo.build/schema.v1.json", ++ "$schema": "https://turbo.build/schema.v2.json", +} +``` + + + Adds a name to `package.json` in any packages that don't have one. diff --git a/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/current-schema/package.json b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/current-schema/package.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/current-schema/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/current-schema/turbo.json b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/current-schema/turbo.json new file mode 100644 index 0000000000000..b61b61ca90940 --- /dev/null +++ b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/current-schema/turbo.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/v1-schema/package.json b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/v1-schema/package.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/v1-schema/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/v1-schema/turbo.json b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/v1-schema/turbo.json new file mode 100644 index 0000000000000..0e49cca4cfb08 --- /dev/null +++ b/packages/turbo-codemod/__tests__/__fixtures__/update-schema-url/v1-schema/turbo.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://turbo.build/schema.v1.json", + "tasks": { + "build": { + "outputs": ["dist/**"] + } + } +} diff --git a/packages/turbo-codemod/__tests__/update-schema-json-url.test.ts b/packages/turbo-codemod/__tests__/update-schema-json-url.test.ts new file mode 100644 index 0000000000000..5898e28c411a1 --- /dev/null +++ b/packages/turbo-codemod/__tests__/update-schema-json-url.test.ts @@ -0,0 +1,68 @@ +import { setupTestFixtures } from "@turbo/test-utils"; +import { describe, it, expect } from "@jest/globals"; +import { transformer } from "../src/transforms/update-schema-json-url"; + +describe("update-schema-url", () => { + const { useFixture } = setupTestFixtures({ + directory: __dirname, + test: "update-schema-url", + }); + + it("updates schema URL from v1 to current version", () => { + // load the fixture for the test + const { root, read } = useFixture({ + fixture: "v1-schema", + }); + + // run the transformer + const result = transformer({ + root, + options: { force: false, dryRun: false, print: false }, + }); + + expect(JSON.parse(read("turbo.json") || "{}")).toStrictEqual({ + $schema: "https://turbo.build/schema.v2.json", + tasks: { + build: { + outputs: ["dist/**"], + }, + }, + }); + + expect(result.fatalError).toBeUndefined(); + expect(result.changes).toMatchInlineSnapshot(` + { + "turbo.json": { + "action": "modified", + "additions": 1, + "deletions": 1, + }, + } + `); + }); + + it("does nothing if schema URL is already updated", () => { + // load the fixture for the test + const { root, read } = useFixture({ + fixture: "current-schema", + }); + + // run the transformer + const result = transformer({ + root, + options: { force: false, dryRun: false, print: false }, + }); + + expect(JSON.parse(read("turbo.json") || "{}")).toStrictEqual({ + $schema: "https://turbo.build/schema.json", + tasks: { + build: { + outputs: ["dist/**"], + }, + }, + }); + + expect(result.fatalError).toBeUndefined(); + expect(result.changes).toStrictEqual({}); + }); +}); diff --git a/packages/turbo-codemod/src/transforms/update-schema-json-url.ts b/packages/turbo-codemod/src/transforms/update-schema-json-url.ts new file mode 100644 index 0000000000000..070d83c2944c8 --- /dev/null +++ b/packages/turbo-codemod/src/transforms/update-schema-json-url.ts @@ -0,0 +1,80 @@ +import path from "node:path"; +import fs from "fs-extra"; +import type { TransformerResults } from "../runner"; +import { getTransformerHelpers } from "../utils/getTransformerHelpers"; +import type { Transformer, TransformerArgs } from "../types"; + +// transformer details +const TRANSFORMER = "update-schema-json-url"; +const DESCRIPTION = + 'Update the "$schema" property in turbo.json from "https://turbo.build/schema.v1.json" to "https://turbo.build/schema.v2.json"'; +const INTRODUCED_IN = "2.0.0"; + +/** + * Updates the schema URL in a turbo.json file from v1 to the current version + */ +function updateSchemaUrl(content: string): string { + return content.replace( + "https://turbo.build/schema.v1.json", + "https://turbo.build/schema.v2.json" + ); +} + +export function transformer({ + root, + options, +}: TransformerArgs): TransformerResults { + const { log, runner } = getTransformerHelpers({ + transformer: TRANSFORMER, + rootPath: root, + options, + }); + + log.info('Updating "$schema" property in turbo.json...'); + const turboConfigPath = path.join(root, "turbo.json"); + + if (!fs.existsSync(turboConfigPath)) { + return runner.abortTransform({ + reason: `No turbo.json found at ${root}. Is the path correct?`, + }); + } + + try { + // Read turbo.json as string to preserve formatting + const turboConfigContent = fs.readFileSync(turboConfigPath, "utf8"); + + // Check if it has the v1 schema URL + if (turboConfigContent.includes("https://turbo.build/schema.v1.json")) { + // Replace the v1 schema URL with the current one + const updatedContent = updateSchemaUrl(turboConfigContent); + + // Write the updated content back to the file + runner.modifyFile({ + filePath: turboConfigPath, + before: turboConfigContent, + after: updatedContent, + }); + + log.info('Updated "$schema" property in turbo.json'); + } else { + log.info("No v1 schema URL found in turbo.json. Skipping update."); + } + } catch (err) { + return runner.abortTransform({ + reason: `Error updating schema URL in turbo.json: ${String(err)}`, + }); + } + + return runner.finish(); +} + +const transformerMeta: Transformer = { + name: TRANSFORMER, + description: DESCRIPTION, + introducedIn: INTRODUCED_IN, + transformer, + idempotent: true, +}; + +// eslint-disable-next-line import/no-default-export -- transforms require default export +export default transformerMeta; diff --git a/test-codemod/turbo.json b/test-codemod/turbo.json new file mode 100644 index 0000000000000..816da8ffe415d --- /dev/null +++ b/test-codemod/turbo.json @@ -0,0 +1 @@ +{ "$schema": "https://turbo.build/schema.json", "tasks": {} }