diff --git a/packages/turbo-codemod/__tests__/__fixtures__/add-package-manager/has-dev-engines-package-manager/package.json b/packages/turbo-codemod/__tests__/__fixtures__/add-package-manager/has-dev-engines-package-manager/package.json new file mode 100644 index 0000000000000..c1cb0ad725ccc --- /dev/null +++ b/packages/turbo-codemod/__tests__/__fixtures__/add-package-manager/has-dev-engines-package-manager/package.json @@ -0,0 +1,12 @@ +{ + "name": "has-dev-engines-package-manager", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": {}, + "devEngines": { + "packageManager": { + "name": "npm", + "version": "1.2.3" + } + } +} diff --git a/packages/turbo-codemod/__tests__/__fixtures__/add-package-manager/has-dev-engines/package.json b/packages/turbo-codemod/__tests__/__fixtures__/add-package-manager/has-dev-engines/package.json new file mode 100644 index 0000000000000..8474667891ddf --- /dev/null +++ b/packages/turbo-codemod/__tests__/__fixtures__/add-package-manager/has-dev-engines/package.json @@ -0,0 +1,12 @@ +{ + "name": "has-dev-engines", + "version": "1.0.0", + "dependencies": {}, + "devDependencies": {}, + "devEngines": { + "runtime": { + "name": "node", + "version": "22.0.0" + } + } +} diff --git a/packages/turbo-codemod/__tests__/add-package-manager.test.ts b/packages/turbo-codemod/__tests__/add-package-manager.test.ts index 71b3eefa86297..84f954b1ff098 100644 --- a/packages/turbo-codemod/__tests__/add-package-manager.test.ts +++ b/packages/turbo-codemod/__tests__/add-package-manager.test.ts @@ -4,7 +4,6 @@ import * as turboUtils from "@turbo/utils"; import { setupTestFixtures } from "@turbo/test-utils"; import { describe, it, expect, jest } from "@jest/globals"; import { transformer } from "../src/transforms/add-package-manager"; -import type { TransformerResults } from "../src/runner"; import type { TransformerOptions } from "../src/types"; import { getWorkspaceDetailsMockReturnValue } from "./test-utils"; @@ -17,10 +16,20 @@ interface TestCase { name: string; fixture: string; existingPackageManagerString: string | undefined; + existingDevEnginesPackageManager: + | { name: string; version: string } + | undefined; packageManager: turboUtils.PackageManager; packageManagerVersion: string; options: TransformerOptions; - result: TransformerResults; + result: { + changes: Record< + string, + { + action: "modified" | "skipped" | "unchanged" | "error"; + } + >; + }; } const TEST_CASES: Array = [ @@ -28,15 +37,14 @@ const TEST_CASES: Array = [ name: "basic", fixture: "no-package-manager", existingPackageManagerString: undefined, + existingDevEnginesPackageManager: undefined, packageManager: "npm", packageManagerVersion: "7.0.0", options: { force: false, dryRun: false, print: false }, result: { changes: { "package.json": { - action: "modified", - additions: 1, - deletions: 0 + action: "modified" } } } @@ -45,15 +53,14 @@ const TEST_CASES: Array = [ name: "dry", fixture: "no-package-manager", existingPackageManagerString: undefined, + existingDevEnginesPackageManager: undefined, packageManager: "npm", packageManagerVersion: "7.0.0", options: { force: false, dryRun: true, print: false }, result: { changes: { "package.json": { - action: "skipped", - additions: 1, - deletions: 0 + action: "skipped" } } } @@ -62,15 +69,14 @@ const TEST_CASES: Array = [ name: "print", fixture: "no-package-manager", existingPackageManagerString: undefined, + existingDevEnginesPackageManager: undefined, packageManager: "yarn", packageManagerVersion: "1.2.3", options: { force: false, dryRun: false, print: true }, result: { changes: { "package.json": { - action: "modified", - additions: 1, - deletions: 0 + action: "modified" } } } @@ -79,15 +85,14 @@ const TEST_CASES: Array = [ name: "print & dry", fixture: "no-package-manager", existingPackageManagerString: undefined, + existingDevEnginesPackageManager: undefined, packageManager: "pnpm", packageManagerVersion: "1.2.3", options: { force: false, dryRun: true, print: true }, result: { changes: { "package.json": { - action: "skipped", - additions: 1, - deletions: 0 + action: "skipped" } } } @@ -96,6 +101,7 @@ const TEST_CASES: Array = [ name: "basic", fixture: "has-package-manager", existingPackageManagerString: "npm@1.2.3", + existingDevEnginesPackageManager: undefined, packageManager: "npm", packageManagerVersion: "1.2.3", options: { force: false, dryRun: false, print: false }, @@ -107,12 +113,41 @@ const TEST_CASES: Array = [ name: "basic", fixture: "wrong-package-manager", existingPackageManagerString: "turbo@1.7.0", + existingDevEnginesPackageManager: undefined, packageManager: "pnpm", packageManagerVersion: "1.2.3", options: { force: false, dryRun: false, print: false }, result: { changes: {} } + }, + { + name: "basic", + fixture: "has-dev-engines-package-manager", + existingPackageManagerString: undefined, + existingDevEnginesPackageManager: { name: "npm", version: "1.2.3" }, + packageManager: "npm", + packageManagerVersion: "1.2.3", + options: { force: false, dryRun: false, print: false }, + result: { + changes: {} + } + }, + { + name: "merge devEngines", + fixture: "has-dev-engines", + existingPackageManagerString: undefined, + existingDevEnginesPackageManager: undefined, + packageManager: "pnpm", + packageManagerVersion: "9.12.3", + options: { force: false, dryRun: false, print: false }, + result: { + changes: { + "package.json": { + action: "modified" + } + } + } } ]; @@ -127,6 +162,7 @@ describe("add-package-manager-2", () => { async ({ fixture, existingPackageManagerString, + existingDevEnginesPackageManager, packageManager, packageManagerVersion, options, @@ -155,9 +191,13 @@ describe("add-package-manager-2", () => { ); // verify package manager - expect(JSON.parse(read("package.json") || "{}").packageManager).toEqual( + const beforePackageJson = JSON.parse(read("package.json") || "{}"); + expect(beforePackageJson.packageManager).toEqual( existingPackageManagerString ); + expect(beforePackageJson.devEngines?.packageManager).toEqual( + existingDevEnginesPackageManager + ); // run the transformer const transformerResult = await transformer({ @@ -165,17 +205,32 @@ describe("add-package-manager-2", () => { options }); - if (existingPackageManagerString === undefined) { + if ( + existingPackageManagerString === undefined && + existingDevEnginesPackageManager === undefined + ) { expect(mockGetAvailablePackageManagers).toHaveBeenCalled(); expect(mockGetWorkspaceDetails).toHaveBeenCalled(); } - expect(JSON.parse(read("package.json") || "{}").packageManager).toEqual( - options.dryRun - ? undefined - : existingPackageManagerString || - `${packageManager}@${packageManagerVersion}` + const afterPackageJson = JSON.parse(read("package.json") || "{}"); + expect(afterPackageJson.packageManager).toEqual( + existingPackageManagerString ); + expect(afterPackageJson.devEngines?.packageManager).toEqual( + options.dryRun || existingPackageManagerString + ? existingDevEnginesPackageManager + : existingDevEnginesPackageManager || { + name: packageManager, + version: packageManagerVersion + } + ); + if (fixture === "has-dev-engines" && !options.dryRun) { + expect(afterPackageJson.devEngines.runtime).toEqual({ + name: "node", + version: "22.0.0" + }); + } // result should be correct expect(transformerResult.changes).toMatchObject(result.changes); diff --git a/packages/turbo-codemod/__tests__/migrate.test.ts b/packages/turbo-codemod/__tests__/migrate.test.ts index afced30475aef..17d456ed89d62 100644 --- a/packages/turbo-codemod/__tests__/migrate.test.ts +++ b/packages/turbo-codemod/__tests__/migrate.test.ts @@ -15,6 +15,29 @@ jest.mock("@turbo/workspaces", () => ({ ...jest.requireActual("@turbo/workspaces") })); +function expectedPackageJsonWithPackageManager({ + name, + turboVersion +}: { + name: string; + turboVersion: string; +}) { + return { + dependencies: {}, + devDependencies: { + turbo: turboVersion + }, + devEngines: { + packageManager: { + name: "pnpm", + version: "1.2.3" + } + }, + name, + version: "1.0.0" + }; +} + describe("migrate", () => { const mockExit = spyExit(); const { useFixture } = setupTestFixtures({ @@ -67,15 +90,12 @@ describe("migrate", () => { install: false }); - expect(readJson("package.json")).toStrictEqual({ - dependencies: {}, - devDependencies: { - turbo: "1.0.0" - }, - name: "no-turbo-json", - packageManager: "pnpm@1.2.3", - version: "1.0.0" - }); + expect(readJson("package.json")).toStrictEqual( + expectedPackageJsonWithPackageManager({ + name: "no-turbo-json", + turboVersion: "1.0.0" + }) + ); expect(readJson("turbo.json")).toStrictEqual({ $schema: "https://turborepo.dev/schema.json", pipeline: { @@ -224,15 +244,12 @@ describe("migrate", () => { to: "1.7.0" }); - expect(readJson("package.json")).toStrictEqual({ - dependencies: {}, - devDependencies: { - turbo: "1.0.0" - }, - name: "no-turbo-json", - packageManager: "pnpm@1.2.3", - version: "1.0.0" - }); + expect(readJson("package.json")).toStrictEqual( + expectedPackageJsonWithPackageManager({ + name: "no-turbo-json", + turboVersion: "1.0.0" + }) + ); expect(readJson("turbo.json")).toStrictEqual({ $schema: "https://turborepo.dev/schema.json", pipeline: { @@ -309,15 +326,12 @@ describe("migrate", () => { from: "1.0.0" }); - expect(readJson("package.json")).toStrictEqual({ - dependencies: {}, - devDependencies: { - turbo: "1.0.0" - }, - name: "no-turbo-json", - packageManager: "pnpm@1.2.3", - version: "1.0.0" - }); + expect(readJson("package.json")).toStrictEqual( + expectedPackageJsonWithPackageManager({ + name: "no-turbo-json", + turboVersion: "1.0.0" + }) + ); expect(readJson("turbo.json")).toStrictEqual({ $schema: "https://turborepo.dev/schema.json", pipeline: { @@ -515,15 +529,12 @@ describe("migrate", () => { install: true }); - expect(readJson("package.json")).toStrictEqual({ - dependencies: {}, - devDependencies: { - turbo: "1.0.0" - }, - name: "no-turbo-json", - packageManager: "pnpm@1.2.3", - version: "1.0.0" - }); + expect(readJson("package.json")).toStrictEqual( + expectedPackageJsonWithPackageManager({ + name: "no-turbo-json", + turboVersion: "1.0.0" + }) + ); expect(readJson("turbo.json")).toStrictEqual({ $schema: "https://turborepo.dev/schema.json", pipeline: { @@ -623,15 +634,12 @@ describe("migrate", () => { install: true }); - expect(readJson("package.json")).toStrictEqual({ - dependencies: {}, - devDependencies: { - turbo: "1.0.0" - }, - name: "no-turbo-json", - packageManager: "pnpm@1.2.3", - version: "1.0.0" - }); + expect(readJson("package.json")).toStrictEqual( + expectedPackageJsonWithPackageManager({ + name: "no-turbo-json", + turboVersion: "1.0.0" + }) + ); expect(readJson("turbo.json")).toStrictEqual({ $schema: "https://turborepo.dev/schema.json", pipeline: { @@ -971,15 +979,12 @@ describe("migrate", () => { install: false }); - expect(readJson("package.json")).toStrictEqual({ - dependencies: {}, - devDependencies: { - turbo: "1.7.1" - }, - name: "turbo-1", - packageManager: "pnpm@1.2.3", - version: "1.0.0" - }); + expect(readJson("package.json")).toStrictEqual( + expectedPackageJsonWithPackageManager({ + name: "turbo-1", + turboVersion: "1.7.1" + }) + ); expect(readJson("turbo.json")).toStrictEqual({ $schema: "https://v2-9-3.turborepo.dev/schema.json", tasks: { diff --git a/packages/turbo-codemod/__tests__/transform.test.ts b/packages/turbo-codemod/__tests__/transform.test.ts index 832ee09121196..0c68da0899374 100644 --- a/packages/turbo-codemod/__tests__/transform.test.ts +++ b/packages/turbo-codemod/__tests__/transform.test.ts @@ -12,6 +12,23 @@ jest.mock("@turbo/workspaces", () => ({ ...jest.requireActual("@turbo/workspaces") })); +function expectedPackageJsonWithPackageManager() { + return { + dependencies: {}, + devDependencies: { + turbo: "1.0.0" + }, + devEngines: { + packageManager: { + name: "pnpm", + version: "1.2.3" + } + }, + name: "transform-basic", + version: "1.0.0" + }; +} + describe("transform", () => { const mockExit = spyExit(); const { useFixture } = setupTestFixtures({ @@ -56,15 +73,9 @@ describe("transform", () => { print: false }); - expect(readJson("package.json")).toStrictEqual({ - dependencies: {}, - devDependencies: { - turbo: "1.0.0" - }, - name: "transform-basic", - packageManager: "pnpm@1.2.3", - version: "1.0.0" - }); + expect(readJson("package.json")).toStrictEqual( + expectedPackageJsonWithPackageManager() + ); // verify mocks were called expect(mockedCheckGitStatus).toHaveBeenCalled(); diff --git a/packages/turbo-codemod/src/transforms/add-package-manager.ts b/packages/turbo-codemod/src/transforms/add-package-manager.ts index 4baadf88ff89d..788a963e27db3 100644 --- a/packages/turbo-codemod/src/transforms/add-package-manager.ts +++ b/packages/turbo-codemod/src/transforms/add-package-manager.ts @@ -8,9 +8,21 @@ import type { Transformer, TransformerArgs } from "../types"; // transformer details const TRANSFORMER = "add-package-manager"; -const DESCRIPTION = "Set the `packageManager` key in root `package.json` file"; +const DESCRIPTION = + "Set the `devEngines.packageManager` key in root `package.json` file"; const INTRODUCED_IN = "1.1.0"; +interface DevEnginesPackageManager { + name: string; + version: string; +} + +interface PackageJsonWithDevEngines extends PackageJson { + devEngines?: Record & { + packageManager?: DevEnginesPackageManager; + }; +} + export async function transformer({ root, options @@ -22,13 +34,21 @@ export async function transformer({ }); const rootPackageJsonPath = path.join(root, "package.json"); - const rootPackageJson = fs.readJsonSync(rootPackageJsonPath) as PackageJson; + const rootPackageJson = fs.readJsonSync( + rootPackageJsonPath + ) as PackageJsonWithDevEngines; if ("packageManager" in rootPackageJson) { log.info(`"packageManager" already set in root "package.json"`); return runner.finish(); } + if (rootPackageJson.devEngines?.packageManager) { + log.info(`"devEngines.packageManager" already set in root "package.json"`); + return runner.finish(); + } - log.info(`Set "packageManager" key in root "package.json" file...`); + log.info( + `Set "devEngines.packageManager" key in root "package.json" file...` + ); let project: Project; try { project = await getWorkspaceDetails({ root }); @@ -49,7 +69,6 @@ export async function transformer({ }); } - const pkgManagerString = `${packageManager}@${version}`; const allWorkspaces = [ { name: "package.json", @@ -63,7 +82,16 @@ export async function transformer({ for (const workspace of allWorkspaces) { const { packageJsonPath, ...pkgJson } = workspace.packageJson; - const newJson = { ...pkgJson, packageManager: pkgManagerString }; + const newJson = { + ...pkgJson, + devEngines: { + ...pkgJson.devEngines, + packageManager: { + name: packageManager, + version + } + } + }; runner.modifyFile({ filePath: packageJsonPath, after: newJson