mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
145 lines
3.8 KiB
JavaScript
145 lines
3.8 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
|
||
|
|
import { execFileSync } from "node:child_process";
|
||
|
|
import { existsSync, readFileSync, rmSync } from "node:fs";
|
||
|
|
import path, { dirname } from "node:path";
|
||
|
|
import process from "node:process";
|
||
|
|
import { fileURLToPath } from "node:url";
|
||
|
|
|
||
|
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||
|
|
const repoRoot = path.resolve(scriptDir, "..");
|
||
|
|
const workspacePath = path.join(repoRoot, "pnpm-workspace.yaml");
|
||
|
|
const releasePackageMapPath = path.join(repoRoot, "scripts", "release-package-map.mjs");
|
||
|
|
|
||
|
|
function parseWorkspaceEntries(workspaceText) {
|
||
|
|
// Keep this aligned with the repo's block-sequence `packages:` format in
|
||
|
|
// pnpm-workspace.yaml. If that file moves to a more complex YAML shape,
|
||
|
|
// switch this parser to a real YAML parser instead of line matching.
|
||
|
|
return workspaceText
|
||
|
|
.split("\n")
|
||
|
|
.map((line) => line.match(/^\s*-\s+(.+)\s*$/)?.[1]?.trim() ?? null)
|
||
|
|
.map((entry) => {
|
||
|
|
if (!entry) return entry;
|
||
|
|
return entry.replace(/^(['"])(.*)\1$/, "$2");
|
||
|
|
})
|
||
|
|
.filter(Boolean)
|
||
|
|
.map((entry) => ({
|
||
|
|
pattern: entry.startsWith("!") ? entry.slice(1) : entry,
|
||
|
|
negated: entry.startsWith("!"),
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
|
||
|
|
function globToRegExp(pattern) {
|
||
|
|
let regex = "";
|
||
|
|
|
||
|
|
for (let index = 0; index < pattern.length; index += 1) {
|
||
|
|
const char = pattern[index];
|
||
|
|
const next = pattern[index + 1];
|
||
|
|
|
||
|
|
if (char === "*" && next === "*") {
|
||
|
|
regex += ".*";
|
||
|
|
index += 1;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (char === "*") {
|
||
|
|
regex += "[^/]*";
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (char === "?") {
|
||
|
|
regex += "[^/]";
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
regex += /[|\\{}()[\]^$+?.]/.test(char) ? `\\${char}` : char;
|
||
|
|
}
|
||
|
|
|
||
|
|
return new RegExp(`^${regex}$`);
|
||
|
|
}
|
||
|
|
|
||
|
|
function isWorkspacePackage(pkgDir, workspaceEntries) {
|
||
|
|
let included = false;
|
||
|
|
|
||
|
|
for (const entry of workspaceEntries) {
|
||
|
|
if (globToRegExp(entry.pattern).test(pkgDir)) {
|
||
|
|
included = !entry.negated;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return included;
|
||
|
|
}
|
||
|
|
|
||
|
|
function listPublicPackages() {
|
||
|
|
const output = execFileSync(
|
||
|
|
process.execPath,
|
||
|
|
[releasePackageMapPath, "list"],
|
||
|
|
{ cwd: repoRoot, encoding: "utf8" },
|
||
|
|
);
|
||
|
|
|
||
|
|
return output
|
||
|
|
.trim()
|
||
|
|
.split("\n")
|
||
|
|
.filter(Boolean)
|
||
|
|
.map((line) => {
|
||
|
|
const [dir, name] = line.split("\t");
|
||
|
|
return { dir, name };
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function readPackageJson(pkgDir) {
|
||
|
|
return JSON.parse(
|
||
|
|
readFileSync(path.join(repoRoot, pkgDir, "package.json"), "utf8"),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
function run(command, args, cwd) {
|
||
|
|
execFileSync(command, args, {
|
||
|
|
cwd,
|
||
|
|
env: {
|
||
|
|
...process.env,
|
||
|
|
CI: "true",
|
||
|
|
},
|
||
|
|
stdio: "inherit",
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function main() {
|
||
|
|
const workspaceEntries = parseWorkspaceEntries(readFileSync(workspacePath, "utf8"));
|
||
|
|
const standalonePackages = listPublicPackages()
|
||
|
|
.filter(({ dir }) => !isWorkspacePackage(dir, workspaceEntries));
|
||
|
|
|
||
|
|
if (standalonePackages.length === 0) {
|
||
|
|
console.log(" i No standalone public packages detected outside the pnpm workspace");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const pkg of standalonePackages) {
|
||
|
|
const pkgDir = path.join(repoRoot, pkg.dir);
|
||
|
|
const pkgJson = readPackageJson(pkg.dir);
|
||
|
|
const nodeModulesDir = path.join(pkgDir, "node_modules");
|
||
|
|
const packageLockfilePath = path.join(pkgDir, "pnpm-lock.yaml");
|
||
|
|
|
||
|
|
console.log(` Preparing standalone package ${pkg.name} (${pkg.dir})`);
|
||
|
|
if (existsSync(nodeModulesDir)) {
|
||
|
|
rmSync(nodeModulesDir, { force: true, recursive: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
const installArgs = existsSync(packageLockfilePath)
|
||
|
|
? ["install", "--ignore-workspace", "--frozen-lockfile"]
|
||
|
|
: [
|
||
|
|
"install",
|
||
|
|
"--ignore-workspace",
|
||
|
|
"--no-lockfile",
|
||
|
|
// Standalone packages intentionally avoid committed lockfile churn in the repo.
|
||
|
|
];
|
||
|
|
|
||
|
|
run("pnpm", installArgs, pkgDir);
|
||
|
|
|
||
|
|
if (pkgJson.scripts?.build) {
|
||
|
|
run("pnpm", ["run", "build"], pkgDir);
|
||
|
|
} else {
|
||
|
|
console.log(" i No build script; skipped build");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main();
|