upgrade-remix
Version:
Command line utility to upgrade all Remix/React Router dependencies together
225 lines (205 loc) • 6.29 kB
JavaScript
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 Remix app directory."
);
}
const packageJson = require(packageJsonPath);
let 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",
},
force: {
type: "boolean",
short: "f",
},
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(
version,
args["package-manager"]
);
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(v, packageManagerFlag) {
const isExact = !/^[\^~]/.test(v);
const implementations = {
npm: {
lockFile: "package-lock.json",
install: (packages, force, isDev) =>
[
"npm install",
force ? "--force" : undefined,
isDev ? "--save-dev" : "--save",
isExact ? "--save-exact" : undefined,
packages,
]
.filter((a) => a)
.join(" "),
sync: "npm ci",
list: (package) => `npm ls ${package}`,
},
yarn: {
lockFile: "yarn.lock",
install: (packages, force, isDev) =>
[
"yarn add",
force ? "--force" : undefined,
isDev ? "--dev" : undefined,
isExact ? "--exact" : undefined,
packages,
]
.filter((a) => a)
.join(" "),
sync: "yarn install --frozen-lockfile",
list: (package) => `yarn list --pattern ${package}`,
},
pnpm: {
lockFile: "pnpm-lock.yaml",
install: (packages, force, isDev) =>
[
"pnpm add",
force ? "--force" : undefined,
isDev ? "--save-dev" : undefined,
isExact ? "--save-exact" : undefined,
packages,
]
.filter((a) => a)
.join(" "),
sync: "pnpm install --frozen-lockfile",
list: (package) => `pnpm list ${package}`,
},
bun: {
lockFile: "bun.lockb",
install: (packages, force, isDev) =>
[
"bun add",
force ? "--force" : undefined,
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}`;
},
},
};
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 remix 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}"`
);
}
function installUpdates(deps, force, isDev) {
const packages = getDeps(deps)
.map((k) => `${k}@${version}`)
.join(" ");
const cmd = implementation.install(packages, force, isDev);
if (args.dryRun) {
console.log(`SKIPPING install command due to --dry-run:`);
console.log(` ${cmd}`);
} else {
console.log(`Executing: ${cmd}`);
}
childProcess.execSync(cmd);
}
installUpdates(packageJson.dependencies, args.force, false);
installUpdates(packageJson.devDependencies, args.force, true);
const syncCmd = implementation.sync;
if (!args["no-sync"] && !args.dryRun) {
console.log(`Running '${syncCmd}' to sync up all deps`);
childProcess.execSync(syncCmd);
}
}