diff options
Diffstat (limited to 'packages/turbo-codemod/src/commands/migrate/steps')
4 files changed, 283 insertions, 0 deletions
diff --git a/packages/turbo-codemod/src/commands/migrate/steps/getCurrentVersion.ts b/packages/turbo-codemod/src/commands/migrate/steps/getCurrentVersion.ts new file mode 100644 index 0000000..3644f8b --- /dev/null +++ b/packages/turbo-codemod/src/commands/migrate/steps/getCurrentVersion.ts @@ -0,0 +1,45 @@ +import path from "path"; +import { existsSync } from "fs-extra"; + +import getPackageManager from "../../../utils/getPackageManager"; +import { exec } from "../utils"; +import type { MigrateCommandOptions } from "../types"; + +function getCurrentVersion( + directory: string, + opts: MigrateCommandOptions +): string | undefined { + const { from } = opts; + if (from) { + return from; + } + + // try global first + const turboVersionFromGlobal = exec(`turbo --version`, { cwd: directory }); + + if (turboVersionFromGlobal) { + return turboVersionFromGlobal; + } + + // try to use the package manager to find the version + const packageManager = getPackageManager({ directory }); + if (packageManager) { + if (packageManager === "yarn") { + return exec(`yarn turbo --version`, { cwd: directory }); + } + if (packageManager === "pnpm") { + return exec(`pnpm turbo --version`, { cwd: directory }); + } else { + // this doesn't work for npm, so manually build the binary path + const turboBin = path.join(directory, "node_modules", ".bin", "turbo"); + if (existsSync(turboBin)) { + return exec(`${turboBin} --version`, { cwd: directory }); + } + } + } + + // unable to determine local version, + return undefined; +} + +export default getCurrentVersion; diff --git a/packages/turbo-codemod/src/commands/migrate/steps/getLatestVersion.ts b/packages/turbo-codemod/src/commands/migrate/steps/getLatestVersion.ts new file mode 100644 index 0000000..a6ab7e6 --- /dev/null +++ b/packages/turbo-codemod/src/commands/migrate/steps/getLatestVersion.ts @@ -0,0 +1,31 @@ +import axios from "axios"; + +import type { MigrateCommandOptions } from "../types"; + +const REGISTRY = "https://registry.npmjs.org"; + +async function getPackageDetails({ packageName }: { packageName: string }) { + try { + const result = await axios.get(`${REGISTRY}/${packageName}`); + return result.data; + } catch (err) { + throw new Error(`Unable to fetch the latest version of ${packageName}`); + } +} + +export default async function getLatestVersion({ + to, +}: MigrateCommandOptions): Promise<string | undefined> { + const packageDetails = await getPackageDetails({ packageName: "turbo" }); + const { "dist-tags": tags, versions } = packageDetails; + + if (to) { + if (tags[to] || versions[to]) { + return to; + } else { + throw new Error(`turbo@${to} does not exist`); + } + } + + return tags.latest as string; +} diff --git a/packages/turbo-codemod/src/commands/migrate/steps/getTransformsForMigration.ts b/packages/turbo-codemod/src/commands/migrate/steps/getTransformsForMigration.ts new file mode 100644 index 0000000..2224c06 --- /dev/null +++ b/packages/turbo-codemod/src/commands/migrate/steps/getTransformsForMigration.ts @@ -0,0 +1,25 @@ +import { gt, lte } from "semver"; + +import loadTransformers from "../../../utils/loadTransformers"; +import type { Transformer } from "../../../types"; + +/** + Returns all transformers introduced after fromVersion, but before or equal to toVersion +**/ +function getTransformsForMigration({ + fromVersion, + toVersion, +}: { + fromVersion: string; + toVersion: string; +}): Array<Transformer> { + const transforms = loadTransformers(); + return transforms.filter((transformer) => { + return ( + gt(transformer.introducedIn, fromVersion) && + lte(transformer.introducedIn, toVersion) + ); + }); +} + +export default getTransformsForMigration; diff --git a/packages/turbo-codemod/src/commands/migrate/steps/getTurboUpgradeCommand.ts b/packages/turbo-codemod/src/commands/migrate/steps/getTurboUpgradeCommand.ts new file mode 100644 index 0000000..8fd5972 --- /dev/null +++ b/packages/turbo-codemod/src/commands/migrate/steps/getTurboUpgradeCommand.ts @@ -0,0 +1,182 @@ +import os from "os"; +import path from "path"; +import fs from "fs-extra"; +import { gte } from "semver"; + +import { exec } from "../utils"; +import getPackageManager, { + PackageManager, +} from "../../../utils/getPackageManager"; +import getPackageManagerVersion from "../../../utils/getPackageManagerVersion"; + +type InstallType = "dependencies" | "devDependencies"; + +function getGlobalBinaryPaths(): Record<PackageManager, string | undefined> { + return { + // we run these from a tmpdir to avoid corepack interference + yarn: exec(`yarn global bin`, { cwd: os.tmpdir() }), + npm: exec(`npm bin --global`, { cwd: os.tmpdir() }), + pnpm: exec(`pnpm bin --global`, { cwd: os.tmpdir() }), + }; +} + +function getGlobalUpgradeCommand( + packageManager: PackageManager, + to: string = "latest" +) { + switch (packageManager) { + case "yarn": + return `yarn global add turbo@${to}`; + case "npm": + return `npm install turbo@${to} --global`; + case "pnpm": + return `pnpm install turbo@${to} --global`; + } +} + +function getLocalUpgradeCommand({ + packageManager, + packageManagerVersion, + installType, + isUsingWorkspaces, + to = "latest", +}: { + packageManager: PackageManager; + packageManagerVersion: string; + installType: InstallType; + isUsingWorkspaces?: boolean; + to?: string; +}) { + const renderCommand = ( + command: Array<string | boolean | undefined> + ): string => command.filter(Boolean).join(" "); + switch (packageManager) { + // yarn command differs depending on the version + case "yarn": + // yarn 2.x and 3.x (berry) + if (gte(packageManagerVersion, "2.0.0")) { + return renderCommand([ + "yarn", + "add", + `turbo@${to}`, + installType === "devDependencies" && "--dev", + ]); + // yarn 1.x + } else { + return renderCommand([ + "yarn", + "add", + `turbo@${to}`, + installType === "devDependencies" && "--dev", + isUsingWorkspaces && "-W", + ]); + } + case "npm": + return renderCommand([ + "npm", + "install", + `turbo@${to}`, + installType === "devDependencies" && "--save-dev", + ]); + case "pnpm": + return renderCommand([ + "pnpm", + "install", + `turbo@${to}`, + installType === "devDependencies" && "--save-dev", + isUsingWorkspaces && "-w", + ]); + } +} + +function getInstallType({ directory }: { directory: string }): { + installType?: InstallType; + isUsingWorkspaces?: boolean; +} { + // read package.json to make sure we have a reference to turbo + const packageJsonPath = path.join(directory, "package.json"); + const pnpmWorkspaceConfig = path.join(directory, "pnpm-workspace.yaml"); + const isPnpmWorkspaces = fs.existsSync(pnpmWorkspaceConfig); + + if (!fs.existsSync(packageJsonPath)) { + console.error(`Unable to find package.json at ${packageJsonPath}`); + return { installType: undefined, isUsingWorkspaces: undefined }; + } + + const packageJson = fs.readJsonSync(packageJsonPath); + const isDevDependency = + packageJson.devDependencies && "turbo" in packageJson.devDependencies; + const isDependency = + packageJson.dependencies && "turbo" in packageJson.dependencies; + let isUsingWorkspaces = "workspaces" in packageJson || isPnpmWorkspaces; + + if (isDependency || isDevDependency) { + return { + installType: isDependency ? "dependencies" : "devDependencies", + isUsingWorkspaces, + }; + } + + return { + installType: undefined, + isUsingWorkspaces, + }; +} + +/** + Finding the correct command to upgrade depends on two things: + 1. The package manager + 2. The install method (local or global) + + We try global first to let turbo handle the inference, then we try local. +**/ +export default function getTurboUpgradeCommand({ + directory, + to, +}: { + directory: string; + to?: string; +}) { + const turboBinaryPathFromGlobal = exec(`turbo bin`, { + cwd: directory, + stdio: "pipe", + }); + const packageManagerGlobalBinaryPaths = getGlobalBinaryPaths(); + + const globalPackageManager = Object.keys( + packageManagerGlobalBinaryPaths + ).find((packageManager) => { + const packageManagerBinPath = + packageManagerGlobalBinaryPaths[packageManager as PackageManager]; + if (packageManagerBinPath && turboBinaryPathFromGlobal) { + return turboBinaryPathFromGlobal.includes(packageManagerBinPath); + } + + return false; + }) as PackageManager; + + if (turboBinaryPathFromGlobal && globalPackageManager) { + // figure which package manager we need to upgrade + return getGlobalUpgradeCommand(globalPackageManager, to); + } else { + const packageManager = getPackageManager({ directory }); + // we didn't find a global install, so we'll try to find a local one + const { installType, isUsingWorkspaces } = getInstallType({ directory }); + if (packageManager && installType) { + const packageManagerVersion = getPackageManagerVersion( + packageManager, + directory + ); + + return getLocalUpgradeCommand({ + packageManager, + packageManagerVersion, + installType, + isUsingWorkspaces, + to, + }); + } + } + + return undefined; +} |
