mirror of
https://github.com/paperclipai/paperclip
synced 2026-04-25 17:25:15 +02:00
125 lines
4.0 KiB
JavaScript
125 lines
4.0 KiB
JavaScript
#!/usr/bin/env -S node --import tsx
|
|
import { spawn } from "node:child_process";
|
|
import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { repoRoot } from "./dev-service-profile.ts";
|
|
|
|
type WorkspaceLinkMismatch = {
|
|
packageName: string;
|
|
expectedPath: string;
|
|
actualPath: string | null;
|
|
};
|
|
|
|
function readJsonFile(filePath: string): Record<string, unknown> {
|
|
return JSON.parse(readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
|
}
|
|
|
|
function discoverWorkspacePackagePaths(rootDir: string): Map<string, string> {
|
|
const packagePaths = new Map<string, string>();
|
|
const ignoredDirNames = new Set([".git", ".paperclip", "dist", "node_modules"]);
|
|
|
|
function visit(dirPath: string) {
|
|
const packageJsonPath = path.join(dirPath, "package.json");
|
|
if (existsSync(packageJsonPath)) {
|
|
const packageJson = readJsonFile(packageJsonPath);
|
|
if (typeof packageJson.name === "string" && packageJson.name.length > 0) {
|
|
packagePaths.set(packageJson.name, dirPath);
|
|
}
|
|
}
|
|
|
|
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
if (!entry.isDirectory()) continue;
|
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
visit(path.join(dirPath, entry.name));
|
|
}
|
|
}
|
|
|
|
visit(path.join(rootDir, "packages"));
|
|
visit(path.join(rootDir, "server"));
|
|
visit(path.join(rootDir, "ui"));
|
|
visit(path.join(rootDir, "cli"));
|
|
|
|
return packagePaths;
|
|
}
|
|
|
|
const workspacePackagePaths = discoverWorkspacePackagePaths(repoRoot);
|
|
|
|
function findServerWorkspaceLinkMismatches(): WorkspaceLinkMismatch[] {
|
|
const serverPackageJson = readJsonFile(path.join(repoRoot, "server", "package.json"));
|
|
const dependencies = {
|
|
...(serverPackageJson.dependencies as Record<string, unknown> | undefined),
|
|
...(serverPackageJson.devDependencies as Record<string, unknown> | undefined),
|
|
};
|
|
const mismatches: WorkspaceLinkMismatch[] = [];
|
|
|
|
for (const [packageName, version] of Object.entries(dependencies)) {
|
|
if (typeof version !== "string" || !version.startsWith("workspace:")) continue;
|
|
|
|
const expectedPath = workspacePackagePaths.get(packageName);
|
|
if (!expectedPath) continue;
|
|
|
|
const linkPath = path.join(repoRoot, "server", "node_modules", ...packageName.split("/"));
|
|
const actualPath = existsSync(linkPath) ? path.resolve(realpathSync(linkPath)) : null;
|
|
if (actualPath === path.resolve(expectedPath)) continue;
|
|
|
|
mismatches.push({
|
|
packageName,
|
|
expectedPath: path.resolve(expectedPath),
|
|
actualPath,
|
|
});
|
|
}
|
|
|
|
return mismatches;
|
|
}
|
|
|
|
function runCommand(command: string, args: string[], cwd: string) {
|
|
return new Promise<void>((resolve, reject) => {
|
|
const child = spawn(command, args, {
|
|
cwd,
|
|
env: process.env,
|
|
stdio: "inherit",
|
|
});
|
|
|
|
child.on("error", reject);
|
|
child.on("exit", (code, signal) => {
|
|
if (code === 0) {
|
|
resolve();
|
|
return;
|
|
}
|
|
reject(
|
|
new Error(
|
|
`${command} ${args.join(" ")} failed with ${signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`}`,
|
|
),
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function ensureServerWorkspaceLinksCurrent() {
|
|
const mismatches = findServerWorkspaceLinkMismatches();
|
|
if (mismatches.length === 0) return;
|
|
|
|
console.log("[paperclip] detected stale workspace package links for server; relinking dependencies...");
|
|
for (const mismatch of mismatches) {
|
|
console.log(
|
|
`[paperclip] ${mismatch.packageName}: ${mismatch.actualPath ?? "missing"} -> ${mismatch.expectedPath}`,
|
|
);
|
|
}
|
|
|
|
const pnpmBin = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
|
await runCommand(
|
|
pnpmBin,
|
|
["install", "--force", "--config.confirmModulesPurge=false"],
|
|
repoRoot,
|
|
);
|
|
|
|
const remainingMismatches = findServerWorkspaceLinkMismatches();
|
|
if (remainingMismatches.length === 0) return;
|
|
|
|
throw new Error(
|
|
`Workspace relink did not repair all server package links: ${remainingMismatches.map((item) => item.packageName).join(", ")}`,
|
|
);
|
|
}
|
|
|
|
await ensureServerWorkspaceLinksCurrent();
|