UNPKG

upgrade-remix

Version:

Command line utility to upgrade all Remix/React Router dependencies together

280 lines (256 loc) 7.86 kB
#!/usr/bin/env node const fs = require("fs"); const path = require("path"); const { parseArgs } = require("node:util"); const childProcess = require("child_process"); const packageJsonPath = path.join(process.cwd(), "package.json"); if (!fs.existsSync(packageJsonPath)) { throw new Error( "Could not find a package.json file! " + "Please run `npx upgrade-remix` from your root React-Router/Remix app directory.", ); } const packageJson = require(packageJsonPath); const isWindows = process.platform === "win32"; const { args, version, implementation, framework } = setup(); if (args["list-versions"]) { listVersions(); } else { upgradePackages(args); } function setup() { const { values: args, positionals } = parseArgs({ options: { "dry-run": { type: "boolean", short: "d", }, list: { type: "boolean", short: "l", }, "package-manager": { type: "string", short: "p", }, "no-sync": { type: "boolean", short: "s", }, }, allowPositionals: true, }); const version = positionals[0] || "latest"; const implementation = getPackageManagerImplementation( args["package-manager"], version, ); const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; const framework = allDeps["@remix-run/react"] ? "remix" : allDeps["react-router"] ? "react-router" : null; console.log(`Detected ${framework} application`); return { args, version, implementation, framework }; } function getPackageManagerImplementation(packageManagerFlag, version) { let isExact = !/^[\^~]/.test(version); const implementations = { npm: { name: "npm", lockFile: "package-lock.json", install: (packages, isDev) => [ "npm install --force", isDev ? "--save-dev" : "--save", isExact ? "--save-exact" : undefined, packages, ] .filter((a) => a) .join(" "), sync: "npm ci", list: (package) => `npm ls ${package}`, remove: (packages) => `npm uninstall ${packages}`, }, yarn: { name: "yarn", lockFile: "yarn.lock", install: (packages, isDev) => [ "yarn add --force", isDev ? "--dev" : undefined, isExact ? "--exact" : undefined, packages, ] .filter((a) => a) .join(" "), sync: "yarn install --frozen-lockfile", list: (package) => `yarn list --pattern ${package}`, remove: (packages) => `yarn remove ${packages}`, }, pnpm: { name: "pnpm", lockFile: "pnpm-lock.yaml", install: (packages, isDev) => [ "pnpm add --force", isDev ? "--save-dev" : undefined, isExact ? "--save-exact" : undefined, packages, ] .filter((a) => a) .join(" "), sync: "pnpm install --frozen-lockfile", list: (package) => `pnpm list ${package}`, remove: (packages) => `pnpm remove ${packages}`, }, bun: { name: "bun", lockFile: "bun.lockb", install: (packages, isDev) => [ "bun add --force", isDev ? "--dev" : undefined, isExact ? "--exact" : undefined, packages, ] .filter((a) => a) .join(" "), sync: `bun install --frozen-lockfile`, list: (package) => { return `bun pm ls | ${isWindows ? "findstr" : "grep"} ${package}`; }, remove: (packages) => `bun remove ${packages}`, }, }; const implementation = packageManagerFlag ? [packageManagerFlag, implementations[packageManagerFlag]] : Object.entries(implementations).find(([name, impl]) => { if (fs.existsSync(path.join(process.cwd(), impl.lockFile))) { console.log(`Found ${impl.lockFile}, using ${name}`); return true; } }); if (!implementation) { throw new Error("Unsupported Package Manager"); } return implementation[1]; } function getDeps(deps) { if (framework === "remix") { return Object.keys(deps || {}).filter( (k) => (k.startsWith("@remix-run/") || k === "remix") && !k.startsWith("@remix-run/v1-") && k !== "@remix-run/router", ); } else if (framework === "react-router") { return Object.keys(deps || {}).filter( (k) => k.startsWith("@react-router/") || k === "react-router" || k === "react-router-dom", ); } throw new Error("Unable to detect if this is a Remix or a React Router app"); } function listVersions() { console.log(`Listing ${framework}} packages in "${packageJsonPath}"`); const { dependencies, devDependencies } = require(packageJsonPath); let deps = [ ...getDeps(dependencies), ...getDeps(devDependencies), ...(dependencies["react-router"] ? ["react-router"] : []), ...(dependencies["react-router-dom"] ? ["react-router-dom"] : []), ...(dependencies["@remix-run/router"] ? ["@remix-run/router"] : []), ]; deps.forEach((dep) => { let cmd = implementation.list(dep); console.log(`Executing: ${cmd}`); let stdout = childProcess.execSync(cmd).toString(); console.log(stdout); }); } function upgradePackages(args) { if (args.dryRun) { console.log(`⚠️ Skipping package updates due to --dry-run"`); console.log( ` - Would have updated ${framework} packages in "${packageJsonPath}" to version "${version}"`, ); } else { console.log( `Updating ${framework} packages in "${packageJsonPath}" to version "${version}"`, ); } if (version.startsWith("preview/")) { if (implementation.name !== "pnpm") { throw new Error("Preview versions are only supported with pnpm"); } if (framework === "remix") { throw new Error("Preview versions are not supported for Remix"); } } function installUpdates(_deps, isDev) { let deps = getDeps(_deps); if (deps.length === 0) { console.log( `No packages to update in ${isDev ? "devDependencies" : "dependencies"}`, ); return; } const packages = deps .map((k) => { if (version.startsWith("preview/")) { let dir = [ "react-router", "react-router-dom", "create-react-router", ].includes(k) ? k : k.replace("@react-router/", "react-router-"); return `"${k}@github:remix-run/react-router#${version}&path:packages/${dir}"`; } else { return `${k}@${version}`; } }) .join(" "); const cmd = implementation.install(packages, isDev); if (args.dryRun) { console.log(`SKIPPING install command due to --dry-run:`); console.log(` ${cmd}`); } else { console.log(`Executing: ${cmd}`); childProcess.execSync(cmd); } } if ( !args.dryRun && version.startsWith("preview/") && packageJson.dependencies["react-router"].startsWith("github:") ) { console.log( "Installing a preview build overtop of another preview build can run into pnpm issues.", ); console.log( "This will uninstall the current preview builds and then then install the new preview build.", ); let deps = getDeps({ ...packageJson.dependencies, ...packageJson.devDependencies, }); const cmd = implementation.remove(deps.join(" ")); console.log(`Executing: ${cmd}`); childProcess.execSync(cmd); } installUpdates(packageJson.dependencies, false); installUpdates(packageJson.devDependencies, true); const syncCmd = implementation.sync; if (!args["no-sync"] && !args.dryRun) { console.log(`Running '${syncCmd}' to sync up all deps`); childProcess.execSync(syncCmd); } }