import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; const scriptPath = path.join( path.dirname(fileURLToPath(import.meta.url)), "update-port-registry.mjs", ); function runSync({ stateDir, projectPath, payload }) { return spawnSync( process.execPath, [scriptPath, "--state-dir", stateDir, "--project-path", projectPath], { input: JSON.stringify(payload), encoding: "utf8", }, ); } function readJson(filePath) { return JSON.parse(fs.readFileSync(filePath, "utf8")); } test("creates local snapshot and machine registry", () => { const tempDir = fs.mkdtempSync( path.join(os.tmpdir(), "copilot-port-registry-"), ); const stateDir = path.join(tempDir, "state"); const projectPath = path.join(tempDir, "workspace-a"); fs.mkdirSync(projectPath, { recursive: true }); const result = runSync({ stateDir, projectPath, payload: { timestamp: "2026-05-19T12:00:00.000Z" }, }); assert.equal(result.status, 0, result.stderr); const localSnapshotPath = path.join( projectPath, ".local", "project-ports.json", ); assert.equal(fs.existsSync(localSnapshotPath), true); const localSnapshot = readJson(localSnapshotPath); assert.equal(Array.isArray(localSnapshot.ports), true); const registryPath = path.join(stateDir, "project-ports-registry.json"); const registry = readJson(registryPath); assert.equal(Object.keys(registry.projects).length, 1); assert.equal(Object.keys(registry.conflicts).length, 0); }); test("reports conflict and recommends changing newest project", () => { const tempDir = fs.mkdtempSync( path.join(os.tmpdir(), "copilot-port-registry-"), ); const stateDir = path.join(tempDir, "state"); const projectA = path.join(tempDir, "workspace-a"); const projectB = path.join(tempDir, "workspace-b"); fs.mkdirSync(path.join(projectA, ".local"), { recursive: true }); fs.mkdirSync(path.join(projectB, ".local"), { recursive: true }); fs.writeFileSync( path.join(projectA, ".local", "project-ports.json"), JSON.stringify( { version: 1, projectName: "workspace-a", projectPath: projectA, ports: [{ service: "web", port: 3000 }], }, null, 2, ), ); fs.writeFileSync( path.join(projectB, ".local", "project-ports.json"), JSON.stringify( { version: 1, projectName: "workspace-b", projectPath: projectB, ports: [{ service: "web", port: 3000 }], }, null, 2, ), ); let result = runSync({ stateDir, projectPath: projectA, payload: { timestamp: "2026-05-19T12:00:00.000Z" }, }); assert.equal(result.status, 0, result.stderr); result = runSync({ stateDir, projectPath: projectB, payload: { timestamp: "2026-05-19T13:00:00.000Z" }, }); assert.equal(result.status, 0, result.stderr); const report = spawnSync( process.execPath, [scriptPath, "--state-dir", stateDir, "--report"], { encoding: "utf8" }, ); assert.equal(report.status, 0, report.stderr); const parsedReport = JSON.parse(report.stdout); assert.equal(parsedReport.conflictCount, 1); assert.equal( parsedReport.conflicts["3000"].recommendedProjectToChangeName, "workspace-b", ); }); test("keeps firstSeenAt stable across re-sync", () => { const tempDir = fs.mkdtempSync( path.join(os.tmpdir(), "copilot-port-registry-"), ); const stateDir = path.join(tempDir, "state"); const projectPath = path.join(tempDir, "workspace-a"); fs.mkdirSync(path.join(projectPath, ".local"), { recursive: true }); fs.writeFileSync( path.join(projectPath, ".local", "project-ports.json"), JSON.stringify( { version: 1, projectName: "workspace-a", projectPath, ports: [{ service: "api", port: 4100 }], }, null, 2, ), ); let result = runSync({ stateDir, projectPath, payload: { timestamp: "2026-05-19T10:00:00.000Z" }, }); assert.equal(result.status, 0, result.stderr); result = runSync({ stateDir, projectPath, payload: { timestamp: "2026-05-19T14:00:00.000Z" }, }); assert.equal(result.status, 0, result.stderr); const registry = readJson(path.join(stateDir, "project-ports-registry.json")); const project = registry.projects[projectPath]; assert.equal(project.firstSeenAt, "2026-05-19T10:00:00.000Z"); assert.equal(project.lastSeenAt, "2026-05-19T14:00:00.000Z"); });