70bf21e064
Handle Changesets / Handle Changesets (push) Waiting to run
Semgrep OSS scan / semgrep-oss (push) Waiting to run
Deploy (to testing) and Test Playground Preview Worker / Deploy Playground Preview Worker (testing) (push) Has been skipped
Deploy Workers Shared Staging / Deploy Workers Shared Staging (push) Failing after 0s
Prerelease / build (push) Has been skipped
188 lines
5.2 KiB
TypeScript
188 lines
5.2 KiB
TypeScript
import assert from "node:assert";
|
|
import { existsSync } from "node:fs";
|
|
import { writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { parsePackageJSON, readFileSync } from "@cloudflare/workers-utils";
|
|
import { brandColor, dim } from "./colors";
|
|
import { runCommand } from "./command";
|
|
|
|
type InstallConfig = {
|
|
startText?: string;
|
|
doneText?: string;
|
|
dev?: boolean;
|
|
force?: boolean;
|
|
isWorkspaceRoot?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Install a list of packages to the local project directory and add it to `package.json`
|
|
*
|
|
* @param packageManager - The package manager to use for installation
|
|
* @param packages - An array of package specifiers to be installed
|
|
* @param config.dev - Add packages as `devDependencies`
|
|
* @param config.startText - Spinner start text
|
|
* @param config.doneText - Spinner done text
|
|
* @param config.force - Whether to install with `--force` or not
|
|
*/
|
|
export const installPackages = async (
|
|
packageManager: "npm" | "pnpm" | "yarn" | "bun",
|
|
packages: string[],
|
|
config: InstallConfig = {}
|
|
) => {
|
|
const { force, dev, startText, doneText } = config;
|
|
const isWorkspaceRoot = config.isWorkspaceRoot ?? false;
|
|
|
|
if (packages.length === 0) {
|
|
let cmd;
|
|
switch (packageManager) {
|
|
case "yarn":
|
|
break;
|
|
case "npm":
|
|
case "pnpm":
|
|
default:
|
|
cmd = "install";
|
|
break;
|
|
}
|
|
|
|
await runCommand(
|
|
[
|
|
packageManager,
|
|
...(cmd ? [cmd] : []),
|
|
...packages,
|
|
...(packageManager === "pnpm" ? ["--no-frozen-lockfile"] : []),
|
|
...(force === true ? ["--force"] : []),
|
|
...getWorkspaceInstallRootFlag(packageManager, isWorkspaceRoot),
|
|
],
|
|
{
|
|
cwd: process.cwd(),
|
|
startText,
|
|
doneText,
|
|
silent: true,
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
|
|
let saveFlag;
|
|
let cmd;
|
|
switch (packageManager) {
|
|
case "yarn":
|
|
cmd = "add";
|
|
saveFlag = dev ? "-D" : "";
|
|
break;
|
|
case "bun":
|
|
cmd = "add";
|
|
saveFlag = dev ? "-d" : "";
|
|
break;
|
|
case "npm":
|
|
case "pnpm":
|
|
default:
|
|
cmd = "install";
|
|
saveFlag = dev ? "--save-dev" : "";
|
|
break;
|
|
}
|
|
await runCommand(
|
|
[
|
|
packageManager,
|
|
cmd,
|
|
...(saveFlag ? [saveFlag] : []),
|
|
...packages,
|
|
...(force === true ? ["--force"] : []),
|
|
...getWorkspaceInstallRootFlag(packageManager, isWorkspaceRoot),
|
|
],
|
|
{
|
|
startText,
|
|
doneText,
|
|
silent: true,
|
|
}
|
|
);
|
|
|
|
if (packageManager === "npm") {
|
|
// Npm install will update the package.json with a caret-range rather than the exact version/range we asked for.
|
|
// We can't use `npm install --save-exact` because that always pins to an exact version, and we want to allow ranges too.
|
|
// So let's just fix that up now by rewriting the package.json.
|
|
const pkgJsonPath = path.join(process.cwd(), "package.json");
|
|
const pkgJson = parsePackageJSON(readFileSync(pkgJsonPath), pkgJsonPath);
|
|
const deps = config.dev ? pkgJson.devDependencies : pkgJson.dependencies;
|
|
assert(deps, "dependencies should be defined");
|
|
for (const pkg of packages) {
|
|
const versionMarker = pkg.lastIndexOf("@");
|
|
if (versionMarker > 0) {
|
|
// (if versionMarker was 0 then this would indicate a scoped package with no version)
|
|
const pkgName = pkg.slice(0, versionMarker);
|
|
const pkgVersion = pkg.slice(versionMarker + 1);
|
|
if (pkgVersion !== "latest") {
|
|
deps[pkgName] = pkgVersion;
|
|
}
|
|
}
|
|
}
|
|
await writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns the potential flag(/s) that need to be added to a package manager's install command when it is
|
|
* run at the root of a workspace.
|
|
*
|
|
* @param packageManager The type of package manager
|
|
* @param isWorkspaceRoot Flag indicating whether the install command is being run at the root of a workspace
|
|
* @returns an array containing the flag(/s) to use, or an empty array if not supported or not running in the workspace root.
|
|
*/
|
|
function getWorkspaceInstallRootFlag(
|
|
packageManager: "npm" | "pnpm" | "yarn" | "bun",
|
|
isWorkspaceRoot: boolean
|
|
): string[] {
|
|
if (!isWorkspaceRoot) {
|
|
return [];
|
|
}
|
|
|
|
switch (packageManager) {
|
|
case "pnpm":
|
|
return ["--workspace-root"];
|
|
case "yarn":
|
|
return ["-W"];
|
|
case "npm":
|
|
case "bun":
|
|
// npm and bun don't have the workspace check
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Installs the latest version of wrangler in the project directory if it isn't already.
|
|
*/
|
|
export async function installWrangler(
|
|
packageManager: "npm" | "pnpm" | "yarn" | "bun",
|
|
isWorkspaceRoot: boolean
|
|
) {
|
|
const packages = [`wrangler@latest`] satisfies string[];
|
|
if (hasDependency("@cloudflare/workers-types")) {
|
|
packages.push("@cloudflare/workers-types@latest");
|
|
}
|
|
|
|
// Even if Wrangler is already installed, make sure we install the latest version, as some framework CLIs are pinned to an older version
|
|
await installPackages(packageManager, packages, {
|
|
dev: true,
|
|
isWorkspaceRoot,
|
|
startText: `Installing wrangler ${dim(
|
|
"A command line tool for building Cloudflare Workers"
|
|
)}`,
|
|
doneText: `${brandColor("installed")} ${dim(
|
|
`via \`${packageManager} install wrangler --save-dev\``
|
|
)}`,
|
|
});
|
|
}
|
|
|
|
function hasDependency(packageName: string): boolean {
|
|
const pkgJsonPath = path.join(process.cwd(), "package.json");
|
|
if (!existsSync(pkgJsonPath)) {
|
|
return false;
|
|
}
|
|
|
|
const pkgJson = parsePackageJSON(readFileSync(pkgJsonPath), pkgJsonPath);
|
|
return Boolean(
|
|
pkgJson.dependencies?.[packageName] ||
|
|
pkgJson.devDependencies?.[packageName]
|
|
);
|
|
}
|