98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Waiting to run
CLI Smoke Test / smoke-test-linux (24) (push) Waiting to run
CLI Smoke Test / smoke-test-windows (20) (push) Waiting to run
CLI Smoke Test / smoke-test-windows (24) (push) Waiting to run
Expo App TypeScript typecheck / typecheck (push) Waiting to run
194 lines
5.0 KiB
JavaScript
194 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { spawnSync } = require("child_process");
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
|
|
function readJson(filePath) {
|
|
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
}
|
|
|
|
function getReleaseTargets() {
|
|
const rootPackageJson = readJson(path.join(repoRoot, "package.json"));
|
|
const workspacePaths = rootPackageJson.workspaces?.packages;
|
|
|
|
if (!Array.isArray(workspacePaths)) {
|
|
throw new Error("No workspace packages found in root package.json");
|
|
}
|
|
|
|
const targets = [];
|
|
for (const workspacePath of workspacePaths) {
|
|
if (workspacePath.includes("*")) {
|
|
continue;
|
|
}
|
|
|
|
const packageJsonPath = path.join(repoRoot, workspacePath, "package.json");
|
|
if (!fs.existsSync(packageJsonPath)) {
|
|
continue;
|
|
}
|
|
|
|
const workspacePackageJson = readJson(packageJsonPath);
|
|
if (!workspacePackageJson.scripts || !workspacePackageJson.scripts.release) {
|
|
continue;
|
|
}
|
|
|
|
const releaseScript = workspacePackageJson.scripts.release;
|
|
targets.push({
|
|
id: path.basename(workspacePath),
|
|
workspaceName: workspacePackageJson.name,
|
|
workspacePath,
|
|
releaseScript,
|
|
requiresReleaseIt: /\brelease-it\b/.test(releaseScript),
|
|
});
|
|
}
|
|
|
|
return targets;
|
|
}
|
|
|
|
function findTarget(targets, input) {
|
|
return targets.find(
|
|
(target) =>
|
|
input === target.id ||
|
|
input === target.workspaceName ||
|
|
input === target.workspacePath
|
|
);
|
|
}
|
|
|
|
function getReleaseItPath(baseDir) {
|
|
const binName = process.platform === "win32" ? "release-it.cmd" : "release-it";
|
|
return path.join(baseDir, "node_modules", ".bin", binName);
|
|
}
|
|
|
|
function ensureReleaseToolingInstalled(target) {
|
|
if (!target.requiresReleaseIt) {
|
|
return;
|
|
}
|
|
|
|
const rootReleaseIt = getReleaseItPath(repoRoot);
|
|
const workspaceReleaseIt = getReleaseItPath(path.join(repoRoot, target.workspacePath));
|
|
|
|
if (fs.existsSync(rootReleaseIt) || fs.existsSync(workspaceReleaseIt)) {
|
|
return;
|
|
}
|
|
|
|
console.error(
|
|
"Missing release tooling: `release-it` is not installed in this checkout."
|
|
);
|
|
console.error("Run `pnpm install` from repository root, then run `pnpm release` again.");
|
|
process.exit(1);
|
|
}
|
|
|
|
function runRelease(target, releaseArgs = []) {
|
|
ensureReleaseToolingInstalled(target);
|
|
|
|
console.log(
|
|
`Running release pipeline for ${target.id} (workspace: ${target.workspaceName})`
|
|
);
|
|
|
|
const workspaceBin = path.join(
|
|
repoRoot,
|
|
target.workspacePath,
|
|
"node_modules",
|
|
".bin"
|
|
);
|
|
const rootBin = path.join(repoRoot, "node_modules", ".bin");
|
|
const existingPath = process.env.PATH || "";
|
|
const releasePath = [workspaceBin, rootBin, existingPath]
|
|
.filter(Boolean)
|
|
.join(path.delimiter);
|
|
|
|
const commandArgs = ["--filter", target.workspaceName, "run", "release"];
|
|
if (releaseArgs.length > 0) {
|
|
commandArgs.push(...releaseArgs);
|
|
}
|
|
|
|
const result = spawnSync("pnpm", commandArgs, {
|
|
cwd: repoRoot,
|
|
stdio: "inherit",
|
|
env: {
|
|
...process.env,
|
|
PATH: releasePath,
|
|
},
|
|
});
|
|
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
|
|
async function promptForTarget(targets) {
|
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
console.error("Interactive release selection requires a TTY.");
|
|
console.error("Available targets:");
|
|
for (const target of targets) {
|
|
console.error(`- ${target.id} (workspace: ${target.workspaceName})`);
|
|
}
|
|
console.error("Run `pnpm release -- <target> [args...]` in non-interactive mode.");
|
|
process.exit(1);
|
|
}
|
|
|
|
let select;
|
|
try {
|
|
({ select } = await import("@inquirer/prompts"));
|
|
} catch (error) {
|
|
console.error("Missing interactive prompt dependency: `@inquirer/prompts`.");
|
|
console.error("Run `pnpm install` from repository root, then run `pnpm release` again.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const targetId = await select({
|
|
message: "Select workspace to release:",
|
|
pageSize: 10,
|
|
choices: targets.map((target) => ({
|
|
name: `${target.id} (${target.workspaceName})`,
|
|
value: target.id,
|
|
description: target.workspacePath,
|
|
})),
|
|
});
|
|
|
|
const target = findTarget(targets, targetId);
|
|
if (!target) {
|
|
console.error("Invalid selection.");
|
|
process.exit(1);
|
|
}
|
|
|
|
runRelease(target);
|
|
}
|
|
|
|
async function main() {
|
|
const targets = getReleaseTargets();
|
|
if (targets.length === 0) {
|
|
console.error("No releasable workspace packages found.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const input = process.argv[2];
|
|
const releaseArgs = process.argv.slice(3);
|
|
if (input) {
|
|
const target = findTarget(targets, input);
|
|
if (!target) {
|
|
console.error(`Unknown release target: ${input}`);
|
|
console.error("Available targets:");
|
|
for (const candidate of targets) {
|
|
console.error(
|
|
`- ${candidate.id} (workspace: ${candidate.workspaceName}, path: ${candidate.workspacePath})`
|
|
);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
runRelease(target, releaseArgs);
|
|
return;
|
|
}
|
|
|
|
await promptForTarget(targets);
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error.message || String(error));
|
|
process.exit(1);
|
|
});
|