Scope workspace link preflight to linked worktrees
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env -S node --import tsx
|
||||
import fs from "node:fs/promises";
|
||||
import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
||||
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { repoRoot } from "./dev-service-profile.ts";
|
||||
|
||||
@@ -43,6 +43,20 @@ function discoverWorkspacePackagePaths(rootDir: string): Map<string, string> {
|
||||
return packagePaths;
|
||||
}
|
||||
|
||||
function isLinkedGitWorktreeCheckout(rootDir: string) {
|
||||
const gitMetadataPath = path.join(rootDir, ".git");
|
||||
if (!existsSync(gitMetadataPath)) return false;
|
||||
|
||||
const stat = lstatSync(gitMetadataPath);
|
||||
if (!stat.isFile()) return false;
|
||||
|
||||
return readFileSync(gitMetadataPath, "utf8").trimStart().startsWith("gitdir:");
|
||||
}
|
||||
|
||||
if (!isLinkedGitWorktreeCheckout(repoRoot)) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const workspacePackagePaths = discoverWorkspacePackagePaths(repoRoot);
|
||||
const workspaceDirs = Array.from(
|
||||
new Set(
|
||||
|
||||
@@ -200,6 +200,7 @@ describe("ensureServerWorkspaceLinksCurrent", () => {
|
||||
await fs.mkdir(expectedPackageDir, { recursive: true });
|
||||
await fs.mkdir(stalePackageDir, { recursive: true });
|
||||
await fs.mkdir(serverNodeModulesScopeDir, { recursive: true });
|
||||
await fs.writeFile(path.join(repoRoot, ".git"), "gitdir: /tmp/paperclip-main/.git/worktrees/runtime-links\n", "utf8");
|
||||
await fs.writeFile(path.join(repoRoot, "pnpm-workspace.yaml"), "packages:\n - packages/*\n - server\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "server", "package.json"),
|
||||
@@ -235,6 +236,7 @@ describe("ensureServerWorkspaceLinksCurrent", () => {
|
||||
await fs.mkdir(path.join(repoRoot, "server"), { recursive: true });
|
||||
await fs.mkdir(expectedPackageDir, { recursive: true });
|
||||
await fs.mkdir(serverNodeModulesScopeDir, { recursive: true });
|
||||
await fs.writeFile(path.join(repoRoot, ".git"), "gitdir: /tmp/paperclip-main/.git/worktrees/runtime-links-current\n", "utf8");
|
||||
await fs.writeFile(path.join(repoRoot, "pnpm-workspace.yaml"), "packages:\n - packages/*\n - server\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "server", "package.json"),
|
||||
@@ -255,6 +257,45 @@ describe("ensureServerWorkspaceLinksCurrent", () => {
|
||||
|
||||
await ensureServerWorkspaceLinksCurrent(path.join(repoRoot, "server"));
|
||||
});
|
||||
|
||||
it("skips relinking outside linked git worktrees", async () => {
|
||||
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-runtime-links-non-worktree-"));
|
||||
const staleRoot = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-runtime-links-non-worktree-stale-"));
|
||||
const serverNodeModulesScopeDir = path.join(repoRoot, "server", "node_modules", "@paperclipai");
|
||||
const expectedPackageDir = path.join(repoRoot, "packages", "db");
|
||||
const stalePackageDir = path.join(staleRoot, "db");
|
||||
|
||||
await fs.mkdir(path.join(repoRoot, ".git"), { recursive: true });
|
||||
await fs.mkdir(path.join(repoRoot, "server"), { recursive: true });
|
||||
await fs.mkdir(expectedPackageDir, { recursive: true });
|
||||
await fs.mkdir(stalePackageDir, { recursive: true });
|
||||
await fs.mkdir(serverNodeModulesScopeDir, { recursive: true });
|
||||
await fs.writeFile(path.join(repoRoot, "pnpm-workspace.yaml"), "packages:\n - packages/*\n - server\n", "utf8");
|
||||
await fs.writeFile(
|
||||
path.join(repoRoot, "server", "package.json"),
|
||||
JSON.stringify({
|
||||
name: "@paperclipai/server",
|
||||
dependencies: {
|
||||
"@paperclipai/db": "workspace:*",
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(expectedPackageDir, "package.json"),
|
||||
JSON.stringify({ name: "@paperclipai/db" }),
|
||||
"utf8",
|
||||
);
|
||||
await fs.writeFile(
|
||||
path.join(stalePackageDir, "package.json"),
|
||||
JSON.stringify({ name: "@paperclipai/db" }),
|
||||
"utf8",
|
||||
);
|
||||
await fs.symlink(stalePackageDir, path.join(serverNodeModulesScopeDir, "db"));
|
||||
|
||||
await ensureServerWorkspaceLinksCurrent(path.join(repoRoot, "server"));
|
||||
expect(await fs.realpath(path.join(serverNodeModulesScopeDir, "db"))).toBe(await fs.realpath(stalePackageDir));
|
||||
});
|
||||
});
|
||||
|
||||
describe("realizeExecutionWorkspace", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { spawn, type ChildProcess } from "node:child_process";
|
||||
import { existsSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
||||
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync } from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import net from "node:net";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
@@ -157,6 +157,16 @@ function findWorkspaceRoot(startCwd: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function isLinkedGitWorktreeCheckout(rootDir: string) {
|
||||
const gitMetadataPath = path.join(rootDir, ".git");
|
||||
if (!existsSync(gitMetadataPath)) return false;
|
||||
|
||||
const stat = lstatSync(gitMetadataPath);
|
||||
if (!stat.isFile()) return false;
|
||||
|
||||
return readFileSync(gitMetadataPath, "utf8").trimStart().startsWith("gitdir:");
|
||||
}
|
||||
|
||||
function discoverWorkspacePackagePaths(rootDir: string): Map<string, string> {
|
||||
const packagePaths = new Map<string, string>();
|
||||
const ignoredDirNames = new Set([".git", ".paperclip", "dist", "node_modules"]);
|
||||
@@ -228,6 +238,7 @@ export async function ensureServerWorkspaceLinksCurrent(
|
||||
) {
|
||||
const workspaceRoot = findWorkspaceRoot(startCwd);
|
||||
if (!workspaceRoot) return;
|
||||
if (!isLinkedGitWorktreeCheckout(workspaceRoot)) return;
|
||||
|
||||
const mismatches = findServerWorkspaceLinkMismatches(workspaceRoot);
|
||||
if (mismatches.length === 0) return;
|
||||
|
||||
Reference in New Issue
Block a user