fix(cli): preserve unsupported historical-source imports (#795)

This commit is contained in:
N0zoM1z0 authored and GitHub committed 2026-10-07 04:02:10 +08:00
1 parent b62931f833
commit 911d580634
8 files changed
+85 -5

No files matched your search

+2
View File
@@ -368,6 +368,8 @@ jobs:
tests/boundary/filesystem/safeOutputTree.test.ts
tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts
tests/boundary/filesystem/setupUnavailableProvider.test.ts
tests/boundary/cli/referenceSourceImportStatus.test.ts
src/cliReferenceSourceImportStatus.test.ts
tests/boundary/filesystem/asarInventoryFreshness.test.ts
tests/composition/javascript-applications/javascriptArtifactReconstruction.test.ts
tests/conformance/providers/ghidra/windowsCapabilities.test.ts
+5
View File
@@ -250,6 +250,11 @@ Import an older source tree as a reference. REA keeps it separate from observati
rea import-reference-source /absolute/path/to/source
```
Historical-source import requires safe no-follow file opens and currently runs
on Linux and macOS. Native Windows reports `unsupported_host`; run the import
with Linux REA in WSL or on another supported host. Changing permissions or
reinstalling REA does not enable this Windows workflow.
Imports read the path supplied to the command. File names do not cause automatic omissions; files are represented by hashes and metadata. To exclude selected paths, set `REA_REFERENCE_SECRET_PATTERNS_JSON` to a JSON string array of ignore patterns. Exports never replace an existing file unless `--overwrite` is explicit.
Use a snapshot to save successful analysis results and reuse them on later runs. REA reuses a result only when the target bytes, operation, parameters, analysis tool, and settings match. It does not cache changes or cursor-dependent calls. Snapshot files stay local and use owner-only permissions.
+2
View File
@@ -163,6 +163,8 @@ export const importReferenceSource = async (
if (!readResult.ok) {
const error = readResult.error;
if (error.code === "cancelled") return err(cancelled());
if (error.code === "unsupported")
return err(failure("unsupported", error.message));
return err(failure("io", error.message));
}
@@ -3,7 +3,7 @@ import type { ReferenceSourcePolicy } from "../domain/referenceSourcePolicy.js";
/** Typed expected failure returned by historical-source imports. */
export interface ReferenceSourceImportError {
readonly tag: "reference-source-import";
readonly code: "cancelled" | "invalid-root" | "io" | "parse";
readonly code: "cancelled" | "invalid-root" | "unsupported" | "io" | "parse";
readonly message: string;
}
@@ -23,6 +23,12 @@ export const projectReferenceSourceImportError = (
message:
"Reference source directory could not be opened. Check that the path exists, is readable, and points to a directory.",
};
if (error.code === "unsupported")
return {
category: "unsupported_host",
message:
"Safe no-follow file opens are unavailable on this host. Import the source tree with REA on Linux (including WSL) or macOS.",
};
if (error.code === "io")
return {
category: "execution_failure",
+6 -1
View File
@@ -4,7 +4,12 @@ import { isCliOperationFailure } from "../cliLogging.js";
const referenceSourceImportFailureSchema = z.strictObject({
error: z.literal("Import failed"),
category: z.enum(["cancelled", "invalid_input", "execution_failure"]),
category: z.enum([
"cancelled",
"invalid_input",
"unsupported_host",
"execution_failure",
]),
message: z.string().min(1),
});
+2 -1
View File
@@ -60,7 +60,7 @@ const successfulValues: readonly (readonly [string, unknown])[] = [
];
describe("reference-source import CLI failure classification", () => {
it.each(["cancelled", "invalid-root", "io", "parse"] as const)(
it.each(["cancelled", "invalid-root", "unsupported", "io", "parse"] as const)(
"recognizes the projected %s failure only at this command boundary",
(code) => {
const value = importFailure(code);
@@ -82,6 +82,7 @@ describe("reference-source import CLI logging seam", () => {
it.each([
["cancelled", importFailure("cancelled"), true],
["invalid-root", invalidRootFailure, true],
["unsupported", importFailure("unsupported"), true],
["io", importFailure("io"), true],
["parse", importFailure("parse"), true],
["configuration", configurationFailure, true],
@@ -1,4 +1,4 @@
import { writeFile } from "node:fs/promises";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect } from "vitest";
@@ -13,6 +13,62 @@ const invalidRootOutput = {
"Reference source directory could not be opened. Check that the path exists, is readable, and points to a directory.",
};
const unsupportedHostOutput = {
error: "Import failed",
category: "unsupported_host",
message:
"Safe no-follow file opens are unavailable on this host. Import the source tree with REA on Linux (including WSL) or macOS.",
};
describe("compiled Windows reference-source import", () => {
for (const fullOutput of [false, true]) {
for (const logging of [false, true]) {
cliTest.runIf(process.platform === "win32")(
`readable root, ${fullOutput ? "full-output" : "JSON"}, logging ${String(logging)}`,
async ({ cli }) => {
const root = await createTestTempDirectory("rea-reference-host-");
const source = join(root, "source.js");
const bytes = "export const answer = 42;\n";
await writeFile(source, bytes);
const result = await cli.run({
arguments: [
"import-reference-source",
root,
"--json",
...(fullOutput ? ["--full-output"] : []),
],
cwd: root,
environment: {
USERPROFILE: root,
XDG_CONFIG_HOME: root,
XDG_CACHE_HOME: root,
...(logging ? { REA_LOG_LEVEL: "info" } : {}),
},
});
expect(result.json).toEqual(
fullOutput
? expect.objectContaining({
ok: true,
data: unsupportedHostOutput,
})
: unsupportedHostOutput,
);
expect.soft(result.exitCode).toBe(1);
expect(await readFile(source, "utf8")).toBe(bytes);
if (logging) {
expect(JSON.parse(result.stderr.trim())).toMatchObject({
command: "import-reference-source",
status: "error",
level: 50,
msg: "CLI command failed",
});
} else expect(result.stderr).toBe("");
},
);
}
}
});
describe("compiled reference-source import preflight failures", () => {
for (const rootKind of ["missing", "regular-file"] as const) {
for (const fullOutput of [false, true]) {
@@ -92,6 +92,7 @@ describe("reference source import error projection", () => {
const expectedCategories = {
cancelled: "cancelled",
"invalid-root": "invalid_input",
unsupported: "unsupported_host",
io: "execution_failure",
parse: "execution_failure",
} as const;
@@ -104,7 +105,9 @@ describe("reference source import error projection", () => {
expect(projected.category).toBe(category);
expect(projected.message).not.toContain("SECRET");
expect(projected.message).not.toContain("/private/path");
expect(projected.message).toMatch(/try again|when ready|Check that/u);
expect(projected.message).toMatch(
/try again|when ready|Check that|REA on Linux/u,
);
}
});
});