mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 08:40:01 +02:00
Merge current master and qualify managed Codex onboarding
Reconcile actual master833769abwith preserved Codex1a0ca662and reviewed private onboarding preparation a758b226. Preserve the merged prerequisite and current native/security changes, saved runner choices, managed-image qualification and current Storybook identities. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
7956056b2f
951 files changed
+243773
-32509
No files matched your search
+119
-198
@@ -62,6 +62,109 @@ function company(overrides: Record<string, unknown> = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function allImportEntities(): CompanyPortabilityPreviewResult["include"] {
|
||||
return {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: true,
|
||||
issues: true,
|
||||
skills: true,
|
||||
};
|
||||
}
|
||||
|
||||
function sourceCompanyManifest(): NonNullable<CompanyPortabilityPreviewResult["manifest"]["company"]> {
|
||||
return {
|
||||
path: "COMPANY.md",
|
||||
name: "Source Co",
|
||||
description: null,
|
||||
logoPath: null,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
feedbackDataSharingEnabled: false,
|
||||
feedbackDataSharingConsentAt: null,
|
||||
feedbackDataSharingConsentByUserId: null,
|
||||
feedbackDataSharingTermsVersion: null,
|
||||
};
|
||||
}
|
||||
|
||||
function ceoManifest(): CompanyPortabilityPreviewResult["manifest"]["agents"][number] {
|
||||
return {
|
||||
slug: "ceo",
|
||||
name: "CEO",
|
||||
path: "agents/ceo/AGENT.md",
|
||||
skills: [],
|
||||
role: "ceo",
|
||||
title: null,
|
||||
icon: null,
|
||||
capabilities: null,
|
||||
reportsToSlug: null,
|
||||
reportsToExistingAgentId: null,
|
||||
reportsToExistingAgentSlug: null,
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
permissionGrants: [],
|
||||
budgetMonthlyCents: 0,
|
||||
metadata: null,
|
||||
};
|
||||
}
|
||||
|
||||
function alphaProjectManifest(): CompanyPortabilityPreviewResult["manifest"]["projects"][number] {
|
||||
return {
|
||||
slug: "alpha",
|
||||
name: "Alpha",
|
||||
path: "projects/alpha/PROJECT.md",
|
||||
description: null,
|
||||
ownerAgentSlug: null,
|
||||
leadAgentSlug: null,
|
||||
targetDate: null,
|
||||
color: null,
|
||||
icon: null,
|
||||
status: null,
|
||||
executionWorkspacePolicy: null,
|
||||
workspaces: [],
|
||||
env: null,
|
||||
metadata: null,
|
||||
};
|
||||
}
|
||||
|
||||
function kickoffIssueManifest(): CompanyPortabilityPreviewResult["manifest"]["issues"][number] {
|
||||
return {
|
||||
slug: "kickoff",
|
||||
identifier: null,
|
||||
title: "Kickoff",
|
||||
path: "projects/alpha/issues/kickoff/TASK.md",
|
||||
projectSlug: "alpha",
|
||||
projectWorkspaceKey: null,
|
||||
assigneeAgentSlug: "ceo",
|
||||
description: null,
|
||||
recurring: false,
|
||||
routine: null,
|
||||
legacyRecurrence: null,
|
||||
status: null,
|
||||
priority: null,
|
||||
labelIds: [],
|
||||
billingCode: null,
|
||||
executionWorkspaceSettings: null,
|
||||
assigneeAdapterOverrides: null,
|
||||
comments: [],
|
||||
metadata: null,
|
||||
};
|
||||
}
|
||||
|
||||
function openAiEnvInput(): CompanyPortabilityPreviewResult["envInputs"][number] {
|
||||
return {
|
||||
key: "OPENAI_API_KEY",
|
||||
description: null,
|
||||
agentSlug: "ceo",
|
||||
projectSlug: null,
|
||||
kind: "secret",
|
||||
requirement: "required",
|
||||
defaultValue: null,
|
||||
portability: "portable",
|
||||
};
|
||||
}
|
||||
|
||||
describe("company CLI commands", () => {
|
||||
let fetchMock: ReturnType<typeof vi.fn>;
|
||||
let logSpy: ReturnType<typeof vi.spyOn>;
|
||||
@@ -298,13 +401,7 @@ describe("buildCompanyDashboardUrl", () => {
|
||||
describe("renderCompanyImportPreview", () => {
|
||||
it("summarizes the preview with counts, selection info, and truncated examples", () => {
|
||||
const preview: CompanyPortabilityPreviewResult = {
|
||||
include: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: true,
|
||||
issues: true,
|
||||
skills: true,
|
||||
},
|
||||
include: allImportEntities(),
|
||||
targetCompanyId: "company-123",
|
||||
targetCompanyName: "Imported Co",
|
||||
collisionStrategy: "rename",
|
||||
@@ -334,49 +431,14 @@ describe("renderCompanyImportPreview", () => {
|
||||
companyId: "company-src",
|
||||
companyName: "Source Co",
|
||||
},
|
||||
includes: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: true,
|
||||
issues: true,
|
||||
skills: true,
|
||||
},
|
||||
company: {
|
||||
path: "COMPANY.md",
|
||||
name: "Source Co",
|
||||
description: null,
|
||||
logoPath: null,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
feedbackDataSharingEnabled: false,
|
||||
feedbackDataSharingConsentAt: null,
|
||||
feedbackDataSharingConsentByUserId: null,
|
||||
feedbackDataSharingTermsVersion: null,
|
||||
},
|
||||
includes: allImportEntities(),
|
||||
company: sourceCompanyManifest(),
|
||||
sidebar: {
|
||||
agents: ["ceo"],
|
||||
projects: ["alpha"],
|
||||
},
|
||||
agents: [
|
||||
{
|
||||
slug: "ceo",
|
||||
name: "CEO",
|
||||
path: "agents/ceo/AGENT.md",
|
||||
skills: [],
|
||||
role: "ceo",
|
||||
title: null,
|
||||
icon: null,
|
||||
capabilities: null,
|
||||
reportsToSlug: null,
|
||||
reportsToExistingAgentId: null,
|
||||
reportsToExistingAgentSlug: null,
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
permissionGrants: [],
|
||||
budgetMonthlyCents: 0,
|
||||
metadata: null,
|
||||
},
|
||||
ceoManifest(),
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
@@ -395,73 +457,20 @@ describe("renderCompanyImportPreview", () => {
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
slug: "alpha",
|
||||
name: "Alpha",
|
||||
path: "projects/alpha/PROJECT.md",
|
||||
description: null,
|
||||
ownerAgentSlug: null,
|
||||
leadAgentSlug: null,
|
||||
targetDate: null,
|
||||
color: null,
|
||||
icon: null,
|
||||
status: null,
|
||||
executionWorkspacePolicy: null,
|
||||
workspaces: [],
|
||||
env: null,
|
||||
metadata: null,
|
||||
},
|
||||
alphaProjectManifest(),
|
||||
],
|
||||
issues: [
|
||||
{
|
||||
slug: "kickoff",
|
||||
identifier: null,
|
||||
title: "Kickoff",
|
||||
path: "projects/alpha/issues/kickoff/TASK.md",
|
||||
projectSlug: "alpha",
|
||||
projectWorkspaceKey: null,
|
||||
assigneeAgentSlug: "ceo",
|
||||
description: null,
|
||||
recurring: false,
|
||||
routine: null,
|
||||
legacyRecurrence: null,
|
||||
status: null,
|
||||
priority: null,
|
||||
labelIds: [],
|
||||
billingCode: null,
|
||||
executionWorkspaceSettings: null,
|
||||
assigneeAdapterOverrides: null,
|
||||
comments: [],
|
||||
metadata: null,
|
||||
},
|
||||
kickoffIssueManifest(),
|
||||
],
|
||||
envInputs: [
|
||||
{
|
||||
key: "OPENAI_API_KEY",
|
||||
description: null,
|
||||
agentSlug: "ceo",
|
||||
projectSlug: null,
|
||||
kind: "secret",
|
||||
requirement: "required",
|
||||
defaultValue: null,
|
||||
portability: "portable",
|
||||
},
|
||||
openAiEnvInput(),
|
||||
],
|
||||
},
|
||||
files: {
|
||||
"COMPANY.md": "# Source Co",
|
||||
},
|
||||
envInputs: [
|
||||
{
|
||||
key: "OPENAI_API_KEY",
|
||||
description: null,
|
||||
agentSlug: "ceo",
|
||||
projectSlug: null,
|
||||
kind: "secret",
|
||||
requirement: "required",
|
||||
defaultValue: null,
|
||||
portability: "portable",
|
||||
},
|
||||
openAiEnvInput(),
|
||||
],
|
||||
warnings: ["One warning"],
|
||||
errors: ["One error"],
|
||||
@@ -545,13 +554,7 @@ describe("renderCompanyImportResult", () => {
|
||||
describe("import selection catalog", () => {
|
||||
it("defaults to everything and keeps project selection separate from task selection", () => {
|
||||
const preview: CompanyPortabilityPreviewResult = {
|
||||
include: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: true,
|
||||
issues: true,
|
||||
skills: true,
|
||||
},
|
||||
include: allImportEntities(),
|
||||
targetCompanyId: "company-123",
|
||||
targetCompanyName: "Imported Co",
|
||||
collisionStrategy: "rename",
|
||||
@@ -569,13 +572,7 @@ describe("import selection catalog", () => {
|
||||
companyId: "company-src",
|
||||
companyName: "Source Co",
|
||||
},
|
||||
includes: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: true,
|
||||
issues: true,
|
||||
skills: true,
|
||||
},
|
||||
includes: allImportEntities(),
|
||||
company: {
|
||||
path: "COMPANY.md",
|
||||
name: "Source Co",
|
||||
@@ -592,26 +589,7 @@ describe("import selection catalog", () => {
|
||||
projects: ["alpha"],
|
||||
},
|
||||
agents: [
|
||||
{
|
||||
slug: "ceo",
|
||||
name: "CEO",
|
||||
path: "agents/ceo/AGENT.md",
|
||||
skills: [],
|
||||
role: "ceo",
|
||||
title: null,
|
||||
icon: null,
|
||||
capabilities: null,
|
||||
reportsToSlug: null,
|
||||
reportsToExistingAgentId: null,
|
||||
reportsToExistingAgentSlug: null,
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: {},
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
permissionGrants: [],
|
||||
budgetMonthlyCents: 0,
|
||||
metadata: null,
|
||||
},
|
||||
ceoManifest(),
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
@@ -630,45 +608,10 @@ describe("import selection catalog", () => {
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
slug: "alpha",
|
||||
name: "Alpha",
|
||||
path: "projects/alpha/PROJECT.md",
|
||||
description: null,
|
||||
ownerAgentSlug: null,
|
||||
leadAgentSlug: null,
|
||||
targetDate: null,
|
||||
color: null,
|
||||
icon: null,
|
||||
status: null,
|
||||
executionWorkspacePolicy: null,
|
||||
workspaces: [],
|
||||
env: null,
|
||||
metadata: null,
|
||||
},
|
||||
alphaProjectManifest(),
|
||||
],
|
||||
issues: [
|
||||
{
|
||||
slug: "kickoff",
|
||||
identifier: null,
|
||||
title: "Kickoff",
|
||||
path: "projects/alpha/issues/kickoff/TASK.md",
|
||||
projectSlug: "alpha",
|
||||
projectWorkspaceKey: null,
|
||||
assigneeAgentSlug: "ceo",
|
||||
description: null,
|
||||
recurring: false,
|
||||
routine: null,
|
||||
legacyRecurrence: null,
|
||||
status: null,
|
||||
priority: null,
|
||||
labelIds: [],
|
||||
billingCode: null,
|
||||
executionWorkspaceSettings: null,
|
||||
assigneeAdapterOverrides: null,
|
||||
comments: [],
|
||||
metadata: null,
|
||||
},
|
||||
kickoffIssueManifest(),
|
||||
],
|
||||
envInputs: [],
|
||||
},
|
||||
@@ -720,13 +663,7 @@ describe("import selection catalog", () => {
|
||||
|
||||
it("includes extension file even when all entities are deselected", () => {
|
||||
const preview: CompanyPortabilityPreviewResult = {
|
||||
include: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: true,
|
||||
issues: true,
|
||||
skills: true,
|
||||
},
|
||||
include: allImportEntities(),
|
||||
targetCompanyId: "company-123",
|
||||
targetCompanyName: "Imported Co",
|
||||
collisionStrategy: "rename",
|
||||
@@ -744,24 +681,8 @@ describe("import selection catalog", () => {
|
||||
companyId: "company-src",
|
||||
companyName: "Source Co",
|
||||
},
|
||||
includes: {
|
||||
company: true,
|
||||
agents: true,
|
||||
projects: true,
|
||||
issues: true,
|
||||
skills: true,
|
||||
},
|
||||
company: {
|
||||
path: "COMPANY.md",
|
||||
name: "Source Co",
|
||||
description: null,
|
||||
logoPath: null,
|
||||
requireBoardApprovalForNewAgents: false,
|
||||
feedbackDataSharingEnabled: false,
|
||||
feedbackDataSharingConsentAt: null,
|
||||
feedbackDataSharingConsentByUserId: null,
|
||||
feedbackDataSharingTermsVersion: null,
|
||||
},
|
||||
includes: allImportEntities(),
|
||||
company: sourceCompanyManifest(),
|
||||
sidebar: {
|
||||
agents: [],
|
||||
projects: [],
|
||||
|
||||
@@ -285,9 +285,37 @@ function buildSourceConfig(): PaperclipConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function writeSeedConfigFiles(
|
||||
sourceConfigPath: string,
|
||||
targetConfigPath: string,
|
||||
targetRoot: string,
|
||||
targetPaths: ReturnType<typeof resolveWorktreeLocalPaths>,
|
||||
sourceConfig: PaperclipConfig,
|
||||
targetConfig: PaperclipConfig,
|
||||
): void {
|
||||
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
|
||||
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
|
||||
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
|
||||
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
|
||||
fs.writeFileSync(
|
||||
path.join(targetRoot, ".paperclip", ".env"),
|
||||
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
function initializeTestRepo(repoRoot: string): void {
|
||||
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
|
||||
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
|
||||
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
|
||||
}
|
||||
|
||||
describe("worktree helpers", () => {
|
||||
it("uses the repo-local config for the current worktree", () => {
|
||||
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-current-worktree-"));
|
||||
const targetRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-current-worktree-"));
|
||||
try {
|
||||
const localConfig = path.join(targetRoot, ".paperclip", "config.json");
|
||||
fs.mkdirSync(path.dirname(localConfig), { recursive: true });
|
||||
@@ -307,7 +335,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("uses the repository config from a nested working directory", () => {
|
||||
const targetRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-current-worktree-nested-"));
|
||||
const targetRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-current-worktree-nested-"));
|
||||
try {
|
||||
execFileSync("git", ["init", "-q"], { cwd: targetRoot });
|
||||
const nestedDirectory = path.join(targetRoot, "packages", "example", "src");
|
||||
@@ -517,7 +545,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("requires the seed process to own the target embedded Postgres lifecycle", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-live-target-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-live-target-"));
|
||||
try {
|
||||
fs.writeFileSync(
|
||||
path.join(tempRoot, "postmaster.pid"),
|
||||
@@ -531,18 +559,6 @@ describe("worktree helpers", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("surfaces a credential-safe diagnostic when the target shuts down during restore", () => {
|
||||
expect(formatWorktreeSeedFailureDiagnostic(
|
||||
"restore",
|
||||
new Error(
|
||||
"Failed to restore seed.sql.gz: FATAL: the database system is shutting down; psql error: write EPIPE",
|
||||
),
|
||||
)).toBe(
|
||||
"Target embedded PostgreSQL shut down during restore. Stop any competing worktree service and retry the seed.",
|
||||
);
|
||||
expect(formatWorktreeSeedFailureDiagnostic("migrations", new Error("secret connection failure")))
|
||||
.toBe("Seed failed during migrations.");
|
||||
});
|
||||
|
||||
it("surfaces the missing credential artifact for authenticated seed validation", () => {
|
||||
expect(formatWorktreeSeedFailureDiagnostic(
|
||||
@@ -610,7 +626,7 @@ describe("worktree helpers", () => {
|
||||
itEmbeddedPostgres("recognizes positive legacy database schema evidence", async () => {
|
||||
const tempDb = await startEmbeddedPostgresTestDatabase("paperclip-worktree-legacy-evidence-");
|
||||
onTestFinished(() => tempDb.cleanup());
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-legacy-config-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-legacy-config-"));
|
||||
try {
|
||||
const configPath = path.join(tempRoot, "config.json");
|
||||
const sourceConfig = buildSourceConfig();
|
||||
@@ -644,7 +660,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("ensure-seeded seeds once and fast-exits on the verified manifest", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-"));
|
||||
try {
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetRoot = path.join(tempRoot, "worktree");
|
||||
@@ -661,15 +677,7 @@ describe("worktree helpers", () => {
|
||||
serverPort: 3199,
|
||||
databasePort: 54999,
|
||||
});
|
||||
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
|
||||
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
|
||||
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
|
||||
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
|
||||
fs.writeFileSync(
|
||||
path.join(targetRoot, ".paperclip", ".env"),
|
||||
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
|
||||
);
|
||||
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
|
||||
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
|
||||
|
||||
const seedDatabase = vi.fn().mockResolvedValue({
|
||||
@@ -715,7 +723,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("treats an unregistered markerless config as a normal non-worktree boot", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-unregistered-markerless-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-unregistered-markerless-"));
|
||||
try {
|
||||
const configPath = path.join(tempRoot, "config.json");
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(buildSourceConfig())}\n`);
|
||||
@@ -740,7 +748,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("honors a legacy complete marker without resolving a seed source", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-complete-marker-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-complete-marker-"));
|
||||
try {
|
||||
const configPath = path.join(tempRoot, "config.json");
|
||||
fs.writeFileSync(configPath, `${JSON.stringify(buildSourceConfig())}\n`);
|
||||
@@ -763,7 +771,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("seeds a configured worktree with no seed markers when no legacy database is present", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-unmarked-empty-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-unmarked-empty-"));
|
||||
try {
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetRoot = path.join(tempRoot, "worktree");
|
||||
@@ -780,15 +788,7 @@ describe("worktree helpers", () => {
|
||||
serverPort: 3194,
|
||||
databasePort: 54994,
|
||||
});
|
||||
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
|
||||
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
|
||||
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
|
||||
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
|
||||
fs.writeFileSync(
|
||||
path.join(targetRoot, ".paperclip", ".env"),
|
||||
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
|
||||
);
|
||||
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
|
||||
const inspectLegacyDatabase = vi.fn().mockResolvedValue(null);
|
||||
const seedDatabase = vi.fn().mockResolvedValue(mockVerifiedSeedResult());
|
||||
|
||||
@@ -810,7 +810,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("adopts a markerless legacy worktree only after validating its database schema", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-unmarked-legacy-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-unmarked-legacy-"));
|
||||
try {
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetRoot = path.join(tempRoot, "worktree");
|
||||
@@ -827,15 +827,7 @@ describe("worktree helpers", () => {
|
||||
serverPort: 3193,
|
||||
databasePort: 54993,
|
||||
});
|
||||
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
|
||||
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
|
||||
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
|
||||
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
|
||||
fs.writeFileSync(
|
||||
path.join(targetRoot, ".paperclip", ".env"),
|
||||
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
|
||||
);
|
||||
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
|
||||
const seedDatabase = vi.fn();
|
||||
|
||||
await expect(ensureWorktreeSeeded(
|
||||
@@ -858,7 +850,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("managed ensure-seeded derives a valid source from the registered base workspace", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-managed-seed-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-managed-seed-"));
|
||||
try {
|
||||
const baseRoot = path.join(tempRoot, "base");
|
||||
const sourceConfigPath = path.join(baseRoot, ".paperclip", "config.json");
|
||||
@@ -907,7 +899,7 @@ describe("worktree helpers", () => {
|
||||
it.each(["sibling", "foreign_instance", "symlink", "instance_mismatch"] as const)(
|
||||
"managed ensure-seeded re-derives a stale %s manifest source from registration",
|
||||
async (variant) => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), `paperclip-worktree-managed-${variant}-`));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), `paperclip-worktree-managed-${variant}-`));
|
||||
try {
|
||||
const baseRoot = path.join(tempRoot, "base");
|
||||
const canonicalSource = path.join(baseRoot, ".paperclip", "config.json");
|
||||
@@ -981,7 +973,9 @@ describe("worktree helpers", () => {
|
||||
);
|
||||
|
||||
it("ensure-seeded records a target shutdown diagnostic when restore fails", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-failure-"));
|
||||
expect(formatWorktreeSeedFailureDiagnostic("migrations", new Error("secret connection failure")))
|
||||
.toBe("Seed failed during migrations.");
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-failure-"));
|
||||
try {
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetRoot = path.join(tempRoot, "worktree");
|
||||
@@ -998,15 +992,7 @@ describe("worktree helpers", () => {
|
||||
serverPort: 3198,
|
||||
databasePort: 54998,
|
||||
});
|
||||
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
|
||||
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
|
||||
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
|
||||
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
|
||||
fs.writeFileSync(
|
||||
path.join(targetRoot, ".paperclip", ".env"),
|
||||
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
|
||||
);
|
||||
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
|
||||
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
|
||||
|
||||
await expect(
|
||||
@@ -1044,7 +1030,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("serializes concurrent ensure-seeded calls across the seed marker lock", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-lock-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-lock-"));
|
||||
try {
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetRoot = path.join(tempRoot, "worktree");
|
||||
@@ -1061,15 +1047,7 @@ describe("worktree helpers", () => {
|
||||
serverPort: 3197,
|
||||
databasePort: 54997,
|
||||
});
|
||||
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
|
||||
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
|
||||
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
|
||||
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
|
||||
fs.writeFileSync(
|
||||
path.join(targetRoot, ".paperclip", ".env"),
|
||||
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
|
||||
);
|
||||
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
|
||||
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
|
||||
|
||||
const seedDatabase = vi.fn(async () => {
|
||||
@@ -1094,7 +1072,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("records an interrupted phase before retrying to a verified terminal state", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-interrupted-seed-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-interrupted-seed-"));
|
||||
try {
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetRoot = path.join(tempRoot, "worktree");
|
||||
@@ -1111,15 +1089,7 @@ describe("worktree helpers", () => {
|
||||
serverPort: 3196,
|
||||
databasePort: 54996,
|
||||
});
|
||||
fs.mkdirSync(path.dirname(sourceConfigPath), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(targetConfigPath), { recursive: true });
|
||||
fs.writeFileSync(sourceConfigPath, `${JSON.stringify(sourceConfig)}\n`);
|
||||
fs.writeFileSync(path.join(path.dirname(sourceConfigPath), ".env"), "PAPERCLIP_INSTANCE_ID=source\n");
|
||||
fs.writeFileSync(targetConfigPath, `${JSON.stringify(targetConfig)}\n`);
|
||||
fs.writeFileSync(
|
||||
path.join(targetRoot, ".paperclip", ".env"),
|
||||
`PAPERCLIP_HOME=${targetPaths.homeDir}\nPAPERCLIP_INSTANCE_ID=${targetPaths.instanceId}\n`,
|
||||
);
|
||||
writeSeedConfigFiles(sourceConfigPath, targetConfigPath, targetRoot, targetPaths, sourceConfig, targetConfig);
|
||||
markWorktreeSeedPending({ configPath: targetConfigPath, sourceConfigPath });
|
||||
const interrupted = readWorktreeSeedManifest(targetConfigPath)!;
|
||||
fs.writeFileSync(
|
||||
@@ -1147,7 +1117,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("fails closed instead of racing to reclaim a stale seed lock", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-ensure-seeded-stale-lock-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-ensure-seeded-stale-lock-"));
|
||||
try {
|
||||
const targetConfigPath = path.join(tempRoot, ".paperclip", "config.json");
|
||||
const lockPath = path.join(tempRoot, ".paperclip", "seed.lock");
|
||||
@@ -1413,7 +1383,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("copies the source local_encrypted secrets key into the seeded worktree instance", () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-secrets-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-secrets-"));
|
||||
const originalInlineMasterKey = process.env.PAPERCLIP_SECRETS_MASTER_KEY;
|
||||
const originalKeyFile = process.env.PAPERCLIP_SECRETS_MASTER_KEY_FILE;
|
||||
try {
|
||||
@@ -1452,7 +1422,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("writes the source inline secrets master key into the seeded worktree instance", () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-secrets-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-secrets-"));
|
||||
try {
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetKeyPath = path.join(tempRoot, "target", "secrets", "master.key");
|
||||
@@ -1473,7 +1443,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("creates an explicitly empty worktree without inherited signing secrets or deferred copying", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-empty-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-empty-"));
|
||||
const originalCwd = process.cwd();
|
||||
const originalJwt = process.env.PAPERCLIP_AGENT_JWT_SECRET;
|
||||
const originalSigning = process.env.PAPERCLIP_TOOL_ACTION_SIGNING_SECRET;
|
||||
@@ -1503,7 +1473,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("persists the current agent jwt secret into the worktree env file", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-jwt-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-jwt-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const originalCwd = process.cwd();
|
||||
const originalJwtSecret = process.env.PAPERCLIP_AGENT_JWT_SECRET;
|
||||
@@ -1544,7 +1514,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("preserves repo-managed worktree checkouts when --force re-runs from the source repo", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-force-preserve-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-force-preserve-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const originalCwd = process.cwd();
|
||||
|
||||
@@ -1585,7 +1555,7 @@ describe("worktree helpers", () => {
|
||||
itEmbeddedPostgres(
|
||||
"seeds a local-trusted implicit board user without a credential account",
|
||||
async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-local-board-seed-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-local-board-seed-"));
|
||||
const originalCwd = process.cwd();
|
||||
onTestFinished(() => {
|
||||
process.chdir(originalCwd);
|
||||
@@ -1687,7 +1657,7 @@ describe("worktree helpers", () => {
|
||||
itEmbeddedPostgres(
|
||||
"seeds a lagging source whose migration application order differs from filename order",
|
||||
async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-auth-seed-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-auth-seed-"));
|
||||
const originalCwd = process.cwd();
|
||||
onTestFinished(() => {
|
||||
process.chdir(originalCwd);
|
||||
@@ -1866,7 +1836,7 @@ describe("worktree helpers", () => {
|
||||
);
|
||||
|
||||
it("avoids ports already claimed by sibling worktree instance configs", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-claimed-ports-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-claimed-ports-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
|
||||
const siblingInstanceRoot = path.join(homeDir, "instances", "existing-worktree");
|
||||
@@ -1947,7 +1917,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("reserves distinct ports for postgres-mode siblings under a custom worktree parent", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-custom-parent-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-custom-parent-"));
|
||||
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
|
||||
const customParentDir = path.join(tempRoot, "custom", "workspace-lanes");
|
||||
const firstWorktreeRoot = path.join(customParentDir, "lane-one");
|
||||
@@ -2002,7 +1972,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("defaults the seed source config to the current repo-local Paperclip config", () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-source-config-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-source-config-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const localConfigPath = path.join(repoRoot, ".paperclip", "config.json");
|
||||
const originalCwd = process.cwd();
|
||||
@@ -2027,7 +1997,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("preserves the source config path across worktree:make cwd changes", () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-source-override-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-source-override-"));
|
||||
const sourceConfigPath = path.join(tempRoot, "source", "config.json");
|
||||
const targetRoot = path.join(tempRoot, "target");
|
||||
const originalCwd = process.cwd();
|
||||
@@ -2070,7 +2040,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("derives worktree reseed target paths from the adjacent env file", () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-target-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-target-"));
|
||||
const worktreeRoot = path.join(tempRoot, "repo");
|
||||
const configPath = path.join(worktreeRoot, ".paperclip", "config.json");
|
||||
const envPath = path.join(worktreeRoot, ".paperclip", ".env");
|
||||
@@ -2102,7 +2072,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("rejects reseed targets without worktree env metadata", () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-target-missing-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-target-missing-"));
|
||||
const worktreeRoot = path.join(tempRoot, "repo");
|
||||
const configPath = path.join(worktreeRoot, ".paperclip", "config.json");
|
||||
|
||||
@@ -2127,7 +2097,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
itEmbeddedPostgres("reseed preserves the current worktree ports, instance id, and branding", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const sourceRoot = path.join(tempRoot, "source");
|
||||
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
|
||||
@@ -2232,7 +2202,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("restores the current worktree config and instance data if reseed fails", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-reseed-rollback-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-reseed-rollback-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const sourceRoot = path.join(tempRoot, "source");
|
||||
const homeDir = path.join(tempRoot, ".paperclip-worktrees");
|
||||
@@ -2341,18 +2311,13 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("copies shared git hooks into a linked worktree git dir", () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-hooks-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-hooks-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const worktreePath = path.join(tempRoot, "repo-feature");
|
||||
|
||||
try {
|
||||
fs.mkdirSync(repoRoot, { recursive: true });
|
||||
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
|
||||
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
|
||||
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
|
||||
initializeTestRepo(repoRoot);
|
||||
|
||||
const sourceHooksDir = path.join(repoRoot, ".git", "hooks");
|
||||
const sourceHookPath = path.join(sourceHooksDir, "pre-commit");
|
||||
@@ -2389,7 +2354,7 @@ describe("worktree helpers", () => {
|
||||
}, 15_000);
|
||||
|
||||
it("creates and initializes a worktree from the top-level worktree:make command", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-make-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-make-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const fakeHome = path.join(tempRoot, "home");
|
||||
const worktreePath = path.join(fakeHome, "paperclip-make-test");
|
||||
@@ -2399,12 +2364,7 @@ describe("worktree helpers", () => {
|
||||
try {
|
||||
fs.mkdirSync(repoRoot, { recursive: true });
|
||||
fs.mkdirSync(fakeHome, { recursive: true });
|
||||
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
|
||||
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
|
||||
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
|
||||
initializeTestRepo(repoRoot);
|
||||
|
||||
process.chdir(repoRoot);
|
||||
|
||||
@@ -2424,18 +2384,13 @@ describe("worktree helpers", () => {
|
||||
}, 20_000);
|
||||
|
||||
it("no-ops on the primary checkout unless --branch is provided", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-repair-primary-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-repair-primary-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const originalCwd = process.cwd();
|
||||
|
||||
try {
|
||||
fs.mkdirSync(repoRoot, { recursive: true });
|
||||
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
|
||||
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
|
||||
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
|
||||
initializeTestRepo(repoRoot);
|
||||
|
||||
process.chdir(repoRoot);
|
||||
await worktreeRepairCommand({});
|
||||
@@ -2449,7 +2404,7 @@ describe("worktree helpers", () => {
|
||||
});
|
||||
|
||||
it("repairs the current linked worktree when Paperclip metadata is missing", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-repair-current-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-repair-current-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const worktreePath = path.join(repoRoot, ".paperclip", "worktrees", "repair-me");
|
||||
const sourceConfigPath = path.join(tempRoot, "source-config.json");
|
||||
@@ -2463,12 +2418,7 @@ describe("worktree helpers", () => {
|
||||
|
||||
try {
|
||||
fs.mkdirSync(repoRoot, { recursive: true });
|
||||
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
|
||||
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
|
||||
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
|
||||
initializeTestRepo(repoRoot);
|
||||
fs.mkdirSync(path.dirname(worktreePath), { recursive: true });
|
||||
execFileSync("git", ["worktree", "add", "-b", "repair-me", worktreePath, "HEAD"], {
|
||||
cwd: repoRoot,
|
||||
@@ -2496,7 +2446,7 @@ describe("worktree helpers", () => {
|
||||
}, 20_000);
|
||||
|
||||
it("creates and repairs a missing branch worktree when --branch is provided", async () => {
|
||||
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-worktree-repair-branch-"));
|
||||
const tempRoot = fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), "paperclip-worktree-repair-branch-"));
|
||||
const repoRoot = path.join(tempRoot, "repo");
|
||||
const sourceConfigPath = path.join(tempRoot, "source-config.json");
|
||||
const worktreeHome = path.join(tempRoot, ".paperclip-worktrees");
|
||||
@@ -2505,12 +2455,7 @@ describe("worktree helpers", () => {
|
||||
|
||||
try {
|
||||
fs.mkdirSync(repoRoot, { recursive: true });
|
||||
execFileSync("git", ["init"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["config", "user.name", "Test User"], { cwd: repoRoot, stdio: "ignore" });
|
||||
fs.writeFileSync(path.join(repoRoot, "README.md"), "# temp\n", "utf8");
|
||||
execFileSync("git", ["add", "README.md"], { cwd: repoRoot, stdio: "ignore" });
|
||||
execFileSync("git", ["commit", "-m", "Initial commit"], { cwd: repoRoot, stdio: "ignore" });
|
||||
initializeTestRepo(repoRoot);
|
||||
fs.writeFileSync(sourceConfigPath, JSON.stringify(buildSourceConfig(), null, 2), "utf8");
|
||||
|
||||
process.chdir(repoRoot);
|
||||
|
||||
Reference in new issue
Block a user