diff --git a/tests/commands/adr/import.test.ts b/tests/commands/adr/import.test.ts index d5e068f6..99b54eb0 100644 --- a/tests/commands/adr/import.test.ts +++ b/tests/commands/adr/import.test.ts @@ -23,103 +23,108 @@ import { join } from "node:path"; import { Command } from "@commander-js/extra-typings"; +import { parsePackMetadata } from "../../../src/formats/pack"; + // Module mock — declared before importing so shallowClone never hits the network. +// Provides explicit implementations instead of `require()` + spread of the mocked +// module, which is unreliable on macOS ARM64 (Bun mock.module interop issue). let fakeCloneDir: string = ""; -mock.module("../../../src/helpers/registry", () => { - const real = require("../../../src/helpers/registry"); - return { ...real, shallowClone: () => Promise.resolve(fakeCloneDir) }; -}); +mock.module("../../../src/helpers/registry", () => ({ + resolveSource(input: string) { + const atIdx = input.lastIndexOf("@"); + const base = atIdx <= 0 ? input : input.slice(0, atIdx); + const ref = atIdx <= 0 ? undefined : input.slice(atIdx + 1); + if (base.startsWith("packs/")) { + return { + kind: "official", + repoUrl: "https://github.com/archgate/awesome-adrs.git", + ref, + subpath: base, + }; + } + const segments = base.split("/"); + if (segments.length >= 3) { + const [org, repo, ...rest] = segments; + return { + kind: "github-repo", + repoUrl: `https://github.com/${org}/${repo}.git`, + ref, + subpath: rest.join("/"), + }; + } + throw new Error(`Cannot resolve source "${input}".`); + }, + async detectTarget(cloneDir: string, subpath: string) { + const fullPath = join(cloneDir, subpath); + const packYaml = join(fullPath, "archgate-pack.yaml"); + if (existsSync(packYaml)) { + const raw = await Bun.file(packYaml).text(); + const packMeta = parsePackMetadata(raw); + const adrsDir = join(fullPath, "adrs"); + const entries = existsSync(adrsDir) ? readdirSync(adrsDir) : []; + return { + kind: "pack", + packMeta, + adrFiles: entries + .filter((f: string) => f.endsWith(".md")) + .map((f: string) => join(adrsDir, f)), + rulesFiles: entries + .filter((f: string) => f.endsWith(".rules.ts")) + .map((f: string) => join(adrsDir, f)), + baseDir: adrsDir, + }; + } + const mdPath = fullPath.endsWith(".md") ? fullPath : `${fullPath}.md`; + if (existsSync(mdPath)) { + const rulesPath = mdPath.replace(/\.md$/u, ".rules.ts"); + return { + kind: "single-adr", + adrFile: mdPath, + rulesFile: existsSync(rulesPath) ? rulesPath : null, + baseDir: join(mdPath, ".."), + }; + } + throw new Error( + `Cannot detect import target at "${subpath}". Expected archgate-pack.yaml (pack) or a .md file (single ADR).` + ); + }, + shallowClone: () => Promise.resolve(fakeCloneDir), +})); import { registerAdrImportCommand } from "../../../src/commands/adr/import"; import { safeRmSync } from "../../test-utils"; -const PACK_YAML = [ - "name: test-pack", - "version: 0.1.0", - "description: A test pack for import testing.", - "maintainers:", - " - github: testuser", - "tags: []", - "requires: []", -].join("\n"); - -const ADR_1 = [ - "---", - "id: TP-001", - "title: Test Rule", - "domain: architecture", - "rules: true", - "---", - "", - "## Context", - "Test ADR.", -].join("\n"); - -const ADR_2 = [ - "---", - "id: TP-002", - "title: Another Rule", - "domain: architecture", - "rules: false", - "---", - "", - "## Context", - "Another test ADR.", -].join("\n"); - -const RULES_TS = - "/// \n" + - "export default { rules: {} } satisfies RuleSet;\n"; - -describe("registerAdrImportCommand", () => { - test("registers 'import' as a subcommand", () => { - const parent = new Command("adr"); - registerAdrImportCommand(parent); - const sub = parent.commands.find((c) => c.name() === "import"); - expect(sub).toBeDefined(); - }); - - test("has a description", () => { - const parent = new Command("adr"); - registerAdrImportCommand(parent); - const sub = parent.commands.find((c) => c.name() === "import")!; - expect(sub.description()).toBeTruthy(); - }); +const PACK_YAML = + "name: test-pack\nversion: 0.1.0\ndescription: A test pack for import testing.\nmaintainers:\n - github: testuser\ntags: []\nrequires: []"; - test("accepts --yes option", () => { - const parent = new Command("adr"); - registerAdrImportCommand(parent); - const sub = parent.commands.find((c) => c.name() === "import")!; - expect(sub.options.find((o) => o.long === "--yes")).toBeDefined(); - }); - - test("accepts --json option", () => { - const parent = new Command("adr"); - registerAdrImportCommand(parent); - const sub = parent.commands.find((c) => c.name() === "import")!; - expect(sub.options.find((o) => o.long === "--json")).toBeDefined(); - }); +const ADR_1 = + "---\nid: TP-001\ntitle: Test Rule\ndomain: architecture\nrules: true\n---\n\n## Context\nTest ADR."; - test("accepts --dry-run option", () => { - const parent = new Command("adr"); - registerAdrImportCommand(parent); - const sub = parent.commands.find((c) => c.name() === "import")!; - expect(sub.options.find((o) => o.long === "--dry-run")).toBeDefined(); - }); +const ADR_2 = + "---\nid: TP-002\ntitle: Another Rule\ndomain: architecture\nrules: false\n---\n\n## Context\nAnother test ADR."; - test("accepts --list option", () => { - const parent = new Command("adr"); - registerAdrImportCommand(parent); - const sub = parent.commands.find((c) => c.name() === "import")!; - expect(sub.options.find((o) => o.long === "--list")).toBeDefined(); - }); +const RULES_TS = + "/// \nexport default { rules: {} } satisfies RuleSet;\n"; +describe("registerAdrImportCommand", () => { + const sub = () => { + const p = new Command("adr"); + registerAdrImportCommand(p); + return p.commands.find((c) => c.name() === "import")!; + }; + const hasOpt = (long: string) => sub().options.find((o) => o.long === long); + + test("registers 'import' as a subcommand", () => expect(sub()).toBeDefined()); + test("has a description", () => expect(sub().description()).toBeTruthy()); + test("accepts --yes option", () => expect(hasOpt("--yes")).toBeDefined()); + test("accepts --json option", () => expect(hasOpt("--json")).toBeDefined()); + test("accepts --dry-run option", () => + expect(hasOpt("--dry-run")).toBeDefined()); + test("accepts --list option", () => expect(hasOpt("--list")).toBeDefined()); test("requires argument", () => { - const parent = new Command("adr"); - registerAdrImportCommand(parent); - const sub = parent.commands.find((c) => c.name() === "import")!; - expect(sub.registeredArguments.length).toBeGreaterThanOrEqual(1); - expect(sub.registeredArguments[0].name()).toBe("source"); + const s = sub(); + expect(s.registeredArguments.length).toBeGreaterThanOrEqual(1); + expect(s.registeredArguments[0].name()).toBe("source"); }); });