diff options
| author | 2023-04-28 01:36:44 +0800 | |
|---|---|---|
| committer | 2023-04-28 01:36:44 +0800 | |
| commit | dd84b9d64fb98746a230cd24233ff50a562c39c9 (patch) | |
| tree | b583261ef00b3afe72ec4d6dacb31e57779a6faf /packages/turbo-workspaces/src/managers/pnpm.ts | |
| parent | 0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff) | |
| download | HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip | |
Diffstat (limited to 'packages/turbo-workspaces/src/managers/pnpm.ts')
| -rw-r--r-- | packages/turbo-workspaces/src/managers/pnpm.ts | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/packages/turbo-workspaces/src/managers/pnpm.ts b/packages/turbo-workspaces/src/managers/pnpm.ts new file mode 100644 index 0000000..747e578 --- /dev/null +++ b/packages/turbo-workspaces/src/managers/pnpm.ts @@ -0,0 +1,238 @@ +import fs from "fs-extra"; +import path from "path"; +import execa from "execa"; +import { ConvertError } from "../errors"; +import updateDependencies from "../updateDependencies"; +import { + DetectArgs, + ReadArgs, + CreateArgs, + RemoveArgs, + ConvertArgs, + CleanArgs, + Project, + ManagerHandler, +} from "../types"; +import { + getMainStep, + expandPaths, + getWorkspaceInfo, + expandWorkspaces, + getPnpmWorkspaces, + getPackageJson, + getWorkspacePackageManager, +} from "../utils"; + +/** + * Check if a given project is using pnpm workspaces + * Verify by checking for the existence of: + * 1. pnpm-workspace.yaml + * 2. pnpm-workspace.yaml + */ +async function detect(args: DetectArgs): Promise<boolean> { + const lockFile = path.join(args.workspaceRoot, "pnpm-lock.yaml"); + const workspaceFile = path.join(args.workspaceRoot, "pnpm-workspace.yaml"); + const packageManager = getWorkspacePackageManager({ + workspaceRoot: args.workspaceRoot, + }); + return ( + fs.existsSync(lockFile) || + fs.existsSync(workspaceFile) || + packageManager === "pnpm" + ); +} + +/** + Read workspace data from pnpm workspaces into generic format +*/ +async function read(args: ReadArgs): Promise<Project> { + const isPnpm = await detect(args); + if (!isPnpm) { + throw new ConvertError("Not a pnpm project", { + type: "package_manager-unexpected", + }); + } + + const { name, description } = getWorkspaceInfo(args); + return { + name, + description, + packageManager: "pnpm", + paths: expandPaths({ + root: args.workspaceRoot, + lockFile: "pnpm-lock.yaml", + workspaceConfig: "pnpm-workspace.yaml", + }), + workspaceData: { + globs: getPnpmWorkspaces(args), + workspaces: expandWorkspaces({ + workspaceGlobs: getPnpmWorkspaces(args), + ...args, + }), + }, + }; +} + +/** + * Create pnpm workspaces from generic format + * + * Creating pnpm workspaces involves: + * 1. Create pnpm-workspace.yaml + * 2. Setting the packageManager field in package.json + * 3. Updating all workspace package.json dependencies to ensure correct format + */ +async function create(args: CreateArgs): Promise<void> { + const { project, to, logger, options } = args; + const hasWorkspaces = project.workspaceData.globs.length > 0; + + logger.mainStep( + getMainStep({ action: "create", packageManager: "pnpm", project }) + ); + + const packageJson = getPackageJson({ workspaceRoot: project.paths.root }); + logger.rootHeader(); + packageJson.packageManager = `${to.name}@${to.version}`; + logger.rootStep( + `adding "packageManager" field to ${project.name} root "package.json"` + ); + + // write the changes + if (!options?.dry) { + fs.writeJSONSync(project.paths.packageJson, packageJson, { spaces: 2 }); + + if (hasWorkspaces) { + logger.rootStep(`adding "pnpm-workspace.yaml"`); + fs.writeFileSync( + path.join(project.paths.root, "pnpm-workspace.yaml"), + `packages:\n${project.workspaceData.globs + .map((w) => ` - "${w}"`) + .join("\n")}` + ); + } + } + + if (hasWorkspaces) { + // root dependencies + updateDependencies({ + workspace: { name: "root", paths: project.paths }, + project, + to, + logger, + options, + }); + + // workspace dependencies + logger.workspaceHeader(); + project.workspaceData.workspaces.forEach((workspace) => + updateDependencies({ workspace, project, to, logger, options }) + ); + } +} + +/** + * Remove pnpm workspace data + * + * Cleaning up from pnpm involves: + * 1. Removing the pnpm-workspace.yaml file + * 2. Removing the pnpm-lock.yaml file + * 3. Removing the node_modules directory + */ +async function remove(args: RemoveArgs): Promise<void> { + const { project, logger, options } = args; + const hasWorkspaces = project.workspaceData.globs.length > 0; + + logger.mainStep( + getMainStep({ action: "remove", packageManager: "pnpm", project }) + ); + const packageJson = getPackageJson({ workspaceRoot: project.paths.root }); + + if (project.paths.workspaceConfig && hasWorkspaces) { + logger.subStep(`removing "pnpm-workspace.yaml"`); + if (!options?.dry) { + fs.rmSync(project.paths.workspaceConfig, { force: true }); + } + } + + logger.subStep( + `removing "packageManager" field in ${project.name} root "package.json"` + ); + delete packageJson.packageManager; + + if (!options?.dry) { + fs.writeJSONSync(project.paths.packageJson, packageJson, { spaces: 2 }); + + // collect all workspace node_modules directories + const allModulesDirs = [ + project.paths.nodeModules, + ...project.workspaceData.workspaces.map((w) => w.paths.nodeModules), + ]; + + try { + logger.subStep(`removing "node_modules"`); + await Promise.all( + allModulesDirs.map((dir) => + fs.rm(dir, { recursive: true, force: true }) + ) + ); + } catch (err) { + throw new ConvertError("Failed to remove node_modules", { + type: "error_removing_node_modules", + }); + } + } +} + +/** + * Clean is called post install, and is used to clean up any files + * from this package manager that were needed for install, + * but not required after migration + */ +async function clean(args: CleanArgs): Promise<void> { + const { project, logger, options } = args; + + logger.subStep( + `removing ${path.relative(project.paths.root, project.paths.lockfile)}` + ); + if (!options?.dry) { + fs.rmSync(project.paths.lockfile, { force: true }); + } +} + +/** + * Attempts to convert an existing, non pnpm lockfile to a pnpm lockfile + * + * If this is not possible, the non pnpm lockfile is removed + */ +async function convertLock(args: ConvertArgs): Promise<void> { + const { project, logger, options } = args; + + if (project.packageManager !== "pnpm") { + logger.subStep( + `converting ${path.relative( + project.paths.root, + project.paths.lockfile + )} to pnpm-lock.yaml` + ); + if (!options?.dry && fs.existsSync(project.paths.lockfile)) { + try { + await execa("pnpm", ["import"], { + stdio: "ignore", + cwd: project.paths.root, + }); + } finally { + fs.rmSync(project.paths.lockfile, { force: true }); + } + } + } +} + +const pnpm: ManagerHandler = { + detect, + read, + create, + remove, + clean, + convertLock, +}; + +export default pnpm; |
