diff --git a/scripts/check-access-clock.mjs b/scripts/check-access-clock.mjs index b0984a95..4ca959e1 100644 --- a/scripts/check-access-clock.mjs +++ b/scripts/check-access-clock.mjs @@ -19,6 +19,7 @@ import assert from "node:assert/strict"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; +import { pathToFileURL } from "node:url"; const ROOT = path.resolve(import.meta.dirname, ".."); @@ -31,7 +32,7 @@ async function loadViewers() { v = v.replace(/^import\s+\{[^}]*\}\s+from\s+["']\.\/types["'];?\s*$/m, 'import { TIER_RANK, normalizeTier } from "./types.ts";'); fs.writeFileSync(path.join(tmp, "viewers.ts"), v); - return import(path.join(tmp, "viewers.ts")); + return import(pathToFileURL(path.join(tmp, "viewers.ts")).href); } const { canRead, resolveTier, FIXED_NOW } = await loadViewers(); diff --git a/scripts/check-mcp-keys.mjs b/scripts/check-mcp-keys.mjs index 9514f838..e28ad9c3 100644 --- a/scripts/check-mcp-keys.mjs +++ b/scripts/check-mcp-keys.mjs @@ -19,6 +19,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { createHash, createHmac } from "node:crypto"; +import { pathToFileURL } from "node:url"; const ROOT = path.resolve(import.meta.dirname, ".."); const sha256Hex = (s) => createHash("sha256").update(s).digest("hex"); @@ -67,7 +68,7 @@ async function loadResolver() { 'import { normalizeTier } from "./types.ts";'); fs.copyFileSync(path.join(ROOT, "lib/auth/mcp-pepper.mjs"), path.join(tmp, "mcp-pepper.mjs")); fs.writeFileSync(path.join(tmp, "mcp-keys.ts"), m); - return import(path.join(tmp, "mcp-keys.ts")); + return import(pathToFileURL(path.join(tmp, "mcp-keys.ts")).href); } const { resolveMcpKeyCap } = await loadResolver(); const now = Date.now(); @@ -81,7 +82,7 @@ console.log('[check:mcp-keys] ✓ with no MCP_API_KEYS store, every key (incl. t // ── Guard 2b: an HMAC-keyed store entry grants its tier; expiry is honoured; unknown tier never escalates; // unminted formats, a missing pepper and a bare-SHA-256 (pre-R2) store never resolve. ── -const { mintMcpKey } = await import(path.join(ROOT, "scripts/mint-mcp-key.mjs")); +const { mintMcpKey } = await import(pathToFileURL(path.join(ROOT, "scripts/mint-mcp-key.mjs")).href); const pepper = "test-only-pepper-0123456789-abcdefghij"; // test-only, >= 8 distinct chars const good = mintMcpKey({ pepper, tier: "academic", sub: "org:academic_partner", expiresAt: now + 30 * 86_400_000 }); const expired = mintMcpKey({ pepper, tier: "academic", sub: "org:stale", expiresAt: now - 1 }); diff --git a/scripts/gen-changelog.mjs b/scripts/gen-changelog.mjs index 407bbb7c..317e0a63 100644 --- a/scripts/gen-changelog.mjs +++ b/scripts/gen-changelog.mjs @@ -8,12 +8,13 @@ // fail-soft: with no MEM_GATEWAY_URL / MEM_CONNECT_SECRET (e.g. a local build) // or an unreachable gateway, it writes a graceful placeholder rather than // failing the build. Protocol mirrors lib/ai/memory.ts. +import { createHmac } from "node:crypto"; import fs from "node:fs"; import path from "node:path"; -import { createHmac } from "node:crypto"; +import { fileURLToPath } from "node:url"; import { changelogRepos } from "./lib/changelog-repos.mjs"; -const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), ".."); +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const OUT_DIR = path.join(ROOT, "content", "start", "_generated"); const OUT = path.join(OUT_DIR, "changelog.md"); diff --git a/test/changelog-tiers.test.ts b/test/changelog-tiers.test.ts index 3df14e24..5564d83d 100644 --- a/test/changelog-tiers.test.ts +++ b/test/changelog-tiers.test.ts @@ -5,6 +5,8 @@ * changelog now selects repos with the SAME policy: only repos that resolve to * "public" for the chat are recalled onto the public page. */ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; import { describe, it, expect } from "vitest"; import { resolveRepoTiers, repoTierFrom } from "@/lib/ai/memory"; // @ts-expect-error -- plain ESM helper shared with scripts/gen-changelog.mjs @@ -13,6 +15,22 @@ import { changelogRepos } from "@/scripts/lib/changelog-repos.mjs"; const CANDIDATES = ["citrate-chain", "citrate-core", "citrate-inference-gateway", "citrate-identity", "citrate-docs", "citrate-sdk-js", "citrate-security"]; describe("changelog repo selection matches the chat tier policy", () => { + it("decodes file URLs without losing Windows drive paths", () => { + const fileUrl = new URL("file:///C:/repo/Github%20Federated/citrate-docs/scripts/gen-changelog.mjs"); + const filePath = fileURLToPath(fileUrl); + + expect(filePath).not.toContain("%20"); + if (process.platform === "win32") { + expect(filePath).toBe("C:\\repo\\Github Federated\\citrate-docs\\scripts\\gen-changelog.mjs"); + } else { + expect(filePath).toBe("/C:/repo/Github Federated/citrate-docs/scripts/gen-changelog.mjs"); + } + + const generator = readFileSync(fileURLToPath(new URL("../scripts/gen-changelog.mjs", import.meta.url)), "utf8"); + expect(generator).toContain("fileURLToPath(import.meta.url)"); + expect(generator).not.toContain("new URL(import.meta.url).pathname"); + }); + it("with default config no federation repo is public, so none is recalled", () => { expect(changelogRepos({})).toEqual([]); });