fix(cli): preserve unsupported historical-source imports (#795)
This commit is contained in:
1 parent
b62931f833
commit
911d580634
8 files changed
+85
-5
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user