mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
Merge pull request #244 from morluto/fix/source-checkout-startup
fix(cli): make clean source checkouts start reliably
This commit is contained in:
+10
-5
@@ -9,10 +9,14 @@ REA development requires Node.js 24.18.x and npm 11.16.x. Real-Hopper verificati
|
||||
```bash
|
||||
npm ci
|
||||
npm run rebuild:native # only when the packaged PTY binary is incompatible
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
`npm ci` installs the exact dependencies, builds the compiled runtime through
|
||||
the package `prepare` lifecycle, and prepares the Husky hook. Run
|
||||
`npm run build` again after source changes when you need the standalone CLI or
|
||||
MCP server without running the test prebuild.
|
||||
|
||||
Keep dependencies flowing inward through the existing domain, contracts, provider, application, server, and adapter layers. Parse unknown values at process and protocol boundaries, model expected failures with `Result`, and preserve the 33 direct, 10 enhanced, 5 native, 2 artifact, 8 browser, 2 Electron, and 18 session tools (78 total) unless a deliberate contract change updates every verifier and snapshot.
|
||||
|
||||
Before submitting a pull request, run:
|
||||
@@ -23,10 +27,11 @@ npm run verify:package
|
||||
npm pack --dry-run
|
||||
```
|
||||
|
||||
`npm install` prepares the Husky pre-commit hook. Commits format and lint staged
|
||||
files with lint-staged, then typecheck the complete project. The normal test and
|
||||
CI coverage runs enforce the thresholds in `vitest.config.ts`; `npm run
|
||||
lint:dead` rejects unused files, exports, and dependencies.
|
||||
`npm install` builds the compiled runtime and prepares the Husky pre-commit
|
||||
hook. Commits format and lint staged files with lint-staged, then typecheck the
|
||||
complete project. The normal test and CI coverage runs enforce the thresholds
|
||||
in `vitest.config.ts`; `npm run lint:dead` rejects unused files, exports, and
|
||||
dependencies.
|
||||
|
||||
Set `REA_LOG_LEVEL` to `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or
|
||||
`silent` to control structured JSON diagnostics. MCP mode defaults to `info` and
|
||||
|
||||
+2
-3
@@ -61,7 +61,7 @@
|
||||
"lint:dead": "knip",
|
||||
"jscpd": "jscpd src/ tests/",
|
||||
"scan:todos": "node scripts/scan-todos.mjs",
|
||||
"start": "node dist/main.js",
|
||||
"start": "node scripts/rea.mjs mcp",
|
||||
"pretest": "npm run build",
|
||||
"postinstall": "node scripts/prepare-node-pty.mjs",
|
||||
"test": "vitest run",
|
||||
@@ -82,8 +82,7 @@
|
||||
"docs:generate": "npm run build && node scripts/generate-product-catalog.mjs && typedoc && node scripts/generate-error-schema.mjs",
|
||||
"docs:check": "node scripts/generate-package-metadata.mjs --check && npm run build && node scripts/generate-product-catalog.mjs --check && node scripts/check-doc-facts.mjs && node scripts/check-typedoc.mjs && node scripts/generate-error-schema.mjs --check",
|
||||
"docs:error-schema": "npm run build && node scripts/generate-error-schema.mjs",
|
||||
"prepack": "npm run build",
|
||||
"prepare": "husky",
|
||||
"prepare": "npm run build && husky",
|
||||
"rebuild:native": "node scripts/rebuild-native.mjs"
|
||||
},
|
||||
"lint-staged": {
|
||||
|
||||
+35
-1
@@ -1,5 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { access } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Route production MCP before importing Incur. Incur owns registration helpers
|
||||
// such as `mcp add`, while only dist/main.js may serve the 78-tool stdio server.
|
||||
const args = process.argv.slice(2);
|
||||
@@ -9,8 +13,17 @@ const { default: packageJson } = await import("../package.json", {
|
||||
process.env.REA_PACKAGE_VERSION = packageJson.version;
|
||||
const isMcpMode =
|
||||
args.length === 1 && (args[0] === "--mcp" || args[0] === "mcp");
|
||||
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const runtimeFiles = isMcpMode
|
||||
? ["dist/main.js"]
|
||||
: ["dist/cli.js", "dist/cliOutput.js"];
|
||||
|
||||
if (isMcpMode) {
|
||||
if (!(await compiledRuntimeExists(runtimeFiles))) {
|
||||
process.stderr.write(
|
||||
`REA's compiled runtime is missing. Run \`npm ci\` in ${packageRoot} to install dependencies and build REA, then restart it. If this is an installed package, reinstall rea-agents.\n`,
|
||||
);
|
||||
process.exitCode = 1;
|
||||
} else if (isMcpMode) {
|
||||
const { runEntrypoint } = await import("../dist/main.js");
|
||||
await runEntrypoint();
|
||||
} else {
|
||||
@@ -20,3 +33,24 @@ if (isMcpMode) {
|
||||
stdout: (output) => process.stdout.write(sanitizeCliOutput(output)),
|
||||
});
|
||||
}
|
||||
|
||||
async function compiledRuntimeExists(paths) {
|
||||
for (const path of paths) {
|
||||
try {
|
||||
await access(resolve(packageRoot, path));
|
||||
} catch (cause) {
|
||||
if (isMissing(cause)) return false;
|
||||
throw cause;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function isMissing(cause) {
|
||||
return (
|
||||
typeof cause === "object" &&
|
||||
cause !== null &&
|
||||
"code" in cause &&
|
||||
cause.code === "ENOENT"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,4 +61,33 @@ describe("executable dispatcher", () => {
|
||||
args: ["--mcp", "extra"],
|
||||
});
|
||||
});
|
||||
|
||||
it("explains how to restore a missing compiled runtime", async () => {
|
||||
fixtureRoot = await mkdtemp(join(tmpdir(), "rea-dispatcher-"));
|
||||
const scripts = join(fixtureRoot, "scripts");
|
||||
await mkdir(scripts);
|
||||
await Promise.all([
|
||||
copyFile("scripts/rea.mjs", join(scripts, "rea.mjs")),
|
||||
writeFile(
|
||||
join(fixtureRoot, "package.json"),
|
||||
JSON.stringify({ type: "module", version: "1.0.0" }),
|
||||
),
|
||||
]);
|
||||
|
||||
for (const args of [["mcp"], ["--help"]]) {
|
||||
const execution = execFileAsync(process.execPath, [
|
||||
join(scripts, "rea.mjs"),
|
||||
...args,
|
||||
]);
|
||||
await expect(execution).rejects.toMatchObject({
|
||||
code: 1,
|
||||
stderr: expect.stringContaining(
|
||||
"REA's compiled runtime is missing. Run `npm ci` in",
|
||||
),
|
||||
});
|
||||
await expect(execution).rejects.toMatchObject({
|
||||
stderr: expect.not.stringContaining("ERR_MODULE_NOT_FOUND"),
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+12
-1
@@ -8,7 +8,15 @@ import { PRODUCT_IDENTITY } from "../src/identity.js";
|
||||
describe("package identity", () => {
|
||||
it("keeps the manifest, executable, and floating MCP command aligned", async () => {
|
||||
const packageJson = z
|
||||
.object({ name: z.string(), bin: z.record(z.string(), z.string()) })
|
||||
.object({
|
||||
name: z.string(),
|
||||
bin: z.record(z.string(), z.string()),
|
||||
scripts: z.object({
|
||||
start: z.string(),
|
||||
prepare: z.string(),
|
||||
prepack: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
.parse(
|
||||
JSON.parse(
|
||||
await readFile(
|
||||
@@ -20,5 +28,8 @@ describe("package identity", () => {
|
||||
expect(packageJson.name).toBe(PRODUCT_IDENTITY.packageName);
|
||||
expect(Object.keys(packageJson.bin)).toEqual([PRODUCT_IDENTITY.cliBinary]);
|
||||
expect(PRODUCT_IDENTITY.mcpCommand).toBe(`npx -y ${packageJson.name} mcp`);
|
||||
expect(packageJson.scripts.start).toBe("node scripts/rea.mjs mcp");
|
||||
expect(packageJson.scripts.prepare).toBe("npm run build && husky");
|
||||
expect(packageJson.scripts.prepack).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user