Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "perfkit",
"version": "0.1.1",
"version": "0.1.2",
"description": "Benchmark harness for Fonoster projects: DigitalOcean runs, one report.json schema, one branded report page",
"license": "MIT",
"author": "Pedro Sanders <psanders@fonoster.com>",
Expand Down
50 changes: 32 additions & 18 deletions src/orchestrate/stageAgentPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,25 @@
* Copyright (C) 2026 by Fonoster Inc (https://fonoster.com). MIT License.
*/
import { access, cp, readFile } from "node:fs/promises";
import { join } from "node:path";
import { dirname, join } from "node:path";

/**
* Where `name` is installed for code in `from`, the way Node resolves it: the
* nearest `node_modules/<name>` in `from` or any parent directory. Under npx
* the dependencies sit beside the package, not inside it.
*/
async function findPackageDir(
name: string,
from: string
): Promise<string | null> {
const candidate = join(from, "node_modules", name);
const found = await access(join(candidate, "package.json"))
.then(() => true)
.catch(() => false);
if (found) return candidate;
const parent = dirname(from);
return parent === from ? null : findPackageDir(name, parent);
}

/**
* Copies the CLI's own package into `dest`: `dist/`, `package.json` and the
Expand All @@ -26,21 +44,23 @@ export async function stageAgentPackage(
);
});

// name -> the directory it is installed in
const closure = async (
names: string[],
seen: Set<string>
): Promise<Set<string>> => {
const fresh = names.filter((name) => !seen.has(name));
seen: Map<string, string>
): Promise<Map<string, string>> => {
const fresh = [...new Set(names)].filter((name) => !seen.has(name));
if (fresh.length === 0) return seen;
fresh.forEach((name) => seen.add(name));
const next = await Promise.all(
fresh.map(async (name) => {
const file = join(packageRoot, "node_modules", name, "package.json");
const text = await readFile(file, "utf8").catch(() => {
const dir = await findPackageDir(name, packageRoot);
if (!dir) {
throw new Error(
`dependency ${name} is not installed in ${packageRoot}`
`dependency ${name} is not installed in ${packageRoot} or any parent node_modules`
);
});
}
seen.set(name, dir);
const text = await readFile(join(dir, "package.json"), "utf8");
return Object.keys(
(JSON.parse(text) as { dependencies?: Record<string, string> })
.dependencies ?? {}
Expand All @@ -49,19 +69,13 @@ export async function stageAgentPackage(
);
return closure(next.flat(), seen);
};
const deps = await closure(Object.keys(pkg.dependencies ?? {}), new Set());
const deps = await closure(Object.keys(pkg.dependencies ?? {}), new Map());

await cp(join(packageRoot, "dist"), join(dest, "dist"), { recursive: true });
await cp(join(packageRoot, "package.json"), join(dest, "package.json"));
await Promise.all(
[...deps].map((name) =>
cp(
join(packageRoot, "node_modules", name),
join(dest, "node_modules", name),
{
recursive: true
}
)
[...deps].map(([name, dir]) =>
cp(dir, join(dest, "node_modules", name), { recursive: true })
)
);
return pkg.version;
Expand Down
104 changes: 104 additions & 0 deletions test/orchestrate/stageAgentPackage.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/**
* Copyright (C) 2026 by Fonoster Inc (https://fonoster.com). MIT License.
*/
import { expect } from "chai";
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { stageAgentPackage } from "../../src/orchestrate/stageAgentPackage.js";

const pkgJson = (name: string, deps: Record<string, string> = {}) =>
JSON.stringify({ name, version: "1.0.0", dependencies: deps });

async function writePackage(
dir: string,
name: string,
deps?: Record<string, string>
): Promise<void> {
await mkdir(dir, { recursive: true });
await writeFile(join(dir, "package.json"), pkgJson(name, deps));
await writeFile(join(dir, "index.js"), `module.exports = "${name}";`);
}

describe("stageAgentPackage", () => {
let root: string;

beforeEach(async () => {
root = await mkdtemp(join(tmpdir(), "perfkit-stage-"));
});

afterEach(async () => {
await rm(root, { recursive: true, force: true });
});

async function writePerfkit(dir: string): Promise<void> {
await mkdir(join(dir, "dist"), { recursive: true });
await writeFile(join(dir, "dist", "cli.js"), "// cli");
await writeFile(
join(dir, "package.json"),
JSON.stringify({
name: "perfkit",
version: "0.1.2",
dependencies: { commander: "^14", yaml: "^2" }
})
);
}

it("should stage dependencies installed inside the package", async () => {
const pkg = join(root, "perfkit");
await writePerfkit(pkg);
await writePackage(join(pkg, "node_modules", "commander"), "commander");
await writePackage(join(pkg, "node_modules", "yaml"), "yaml");
const dest = join(root, "out");

const version = await stageAgentPackage(pkg, dest);

expect(version).to.equal("0.1.2");
expect(
await readFile(join(dest, "node_modules", "yaml", "index.js"), "utf8")
).to.contain("yaml");
});

it("should stage dependencies hoisted beside the package, as npx installs them", async () => {
// npx: <cache>/node_modules/perfkit with commander, yaml and yaml's own
// dependency hoisted to <cache>/node_modules
const cache = join(root, "_npx", "abc");
const pkg = join(cache, "node_modules", "perfkit");
await writePerfkit(pkg);
await writePackage(join(cache, "node_modules", "commander"), "commander");
await writePackage(join(cache, "node_modules", "yaml"), "yaml", {
"yaml-dep": "^1"
});
await writePackage(join(cache, "node_modules", "yaml-dep"), "yaml-dep");
const dest = join(root, "out");

await stageAgentPackage(pkg, dest);

expect(
await readFile(
join(dest, "node_modules", "commander", "index.js"),
"utf8"
)
).to.contain("commander");
expect(
await readFile(join(dest, "node_modules", "yaml-dep", "index.js"), "utf8")
).to.contain("yaml-dep");
expect(await readFile(join(dest, "dist", "cli.js"), "utf8")).to.contain(
"cli"
);
});

it("should name a dependency that is installed nowhere", async () => {
const pkg = join(root, "perfkit");
await writePerfkit(pkg);
await writePackage(join(pkg, "node_modules", "commander"), "commander");

const error = await stageAgentPackage(pkg, join(root, "out")).catch(
(e: Error) => e
);

expect((error as Error).message).to.contain(
"dependency yaml is not installed"
);
});
});
Loading