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
22 changes: 8 additions & 14 deletions packages/core/scripts/stage-default-software.mjs
Original file line number Diff line number Diff line change
@@ -1,34 +1,28 @@
import { cpSync, mkdirSync, rmSync, statSync } from "node:fs";
import { cpSync, mkdirSync, readFileSync, rmSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

const DEFAULT_SOFTWARE = [
"coreutils",
"sed",
"grep",
"gawk",
"findutils",
"diffutils",
"tar",
"gzip",
];

const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
const repoRoot = join(packageRoot, "..", "..");
const listPath = join(repoRoot, "software", "default-software.json");
const outputDir = join(packageRoot, "dist", "default-software");
const defaultSoftware = JSON.parse(readFileSync(listPath, "utf8"));

rmSync(outputDir, { recursive: true, force: true });
mkdirSync(outputDir, { recursive: true });

for (const name of DEFAULT_SOFTWARE) {
for (const name of defaultSoftware) {
const source = join(repoRoot, "software", name, "dist", "package.aospkg");
const size = statSync(source).size;
if (size === 0) {
throw new Error(`default software artifact is empty: ${source}`);
}
cpSync(source, join(outputDir, `${name}.aospkg`));
}
// The runtime reads the list from the staged directory, so the published
// package carries it next to the artifacts.
cpSync(listPath, join(outputDir, "default-software.json"));

process.stdout.write(
`staged ${DEFAULT_SOFTWARE.length} default software artifacts -> ${outputDir}\n`,
`staged ${defaultSoftware.length} default software artifacts -> ${outputDir}\n`,
);
26 changes: 10 additions & 16 deletions packages/core/src/default-software.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import { readFileSync } from "node:fs";
import type { SoftwarePackageRef } from "./agentos-package.js";

const DEFAULT_SOFTWARE = [
"coreutils",
"sed",
"grep",
"gawk",
"findutils",
"diffutils",
"tar",
"gzip",
] as const;

/**
* Default software for a bare `AgentOs.create()`. These immutable `.aospkg`
* files are vendored into the Core package at build time; runtime resolution
* never consults npm or scans node_modules. Opt out with
* `defaultSoftware: false`; add more trusted paths via `software`.
* Default software for a bare `AgentOs.create()`. The build stages the list
* from `software/default-software.json` and its immutable `.aospkg` files into
* the Core package; runtime resolution never consults npm or scans
* node_modules. Opt out with `defaultSoftware: false`; add more trusted paths
* via `software`.
*/
export function resolveDefaultSoftware(): SoftwarePackageRef[] {
// Published consumers execute this module from dist/, while Vitest executes
Expand All @@ -25,8 +16,11 @@ export function resolveDefaultSoftware(): SoftwarePackageRef[] {
const artifactDirectory = moduleDirectory.pathname.endsWith("/src/")
? new URL("../dist/default-software/", moduleDirectory)
: new URL("./default-software/", moduleDirectory);
const names: string[] = JSON.parse(
readFileSync(new URL("default-software.json", artifactDirectory), "utf8"),
);

return DEFAULT_SOFTWARE.map((name) => ({
return names.map((name) => ({
packagePath: new URL(`${name}.aospkg`, artifactDirectory).pathname,
}));
}
2 changes: 1 addition & 1 deletion scripts/publish/src/ci/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ program
// ---------------------------------------------------------------------------
program
.command("stage-software")
.description("Stage immutable .aospkg files and their manifest")
.description("Stage immutable .aospkg files, their manifest, and the default software list")
.requiredOption("--output <dir>", "Local artifact directory to replace")
.action((opts) => {
const repoRoot = findRepoRoot();
Expand Down
39 changes: 39 additions & 0 deletions scripts/publish/src/lib/software-artifacts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ test("stages deterministic digest-addressed .aospkg artifacts", () => {
);
const bytes = Buffer.from("aospkg fixture");
writeFileSync(join(packageDir, "dist", "package.aospkg"), bytes);
writeFileSync(
join(root, "software", "default-software.json"),
JSON.stringify(["example"]),
);

const result = stageSoftwareArtifacts(
root,
Expand All @@ -60,6 +64,10 @@ test("stages deterministic digest-addressed .aospkg artifacts", () => {
JSON.parse(readFileSync(result.manifestPath, "utf8")),
result.manifest,
);
assert.deepEqual(
JSON.parse(readFileSync(result.defaultSoftwarePath, "utf8")),
result.manifest,
);
} finally {
rmSync(root, { recursive: true, force: true });
}
Expand Down Expand Up @@ -119,3 +127,34 @@ test("refuses output outside the repository or with an ambiguous name", () => {
rmSync(root, { recursive: true, force: true });
}
});

test("fails when a default package is not in the software catalog", () => {
const root = mkdtempSync(join(tmpdir(), "agentos-software-artifacts-"));
try {
const packageDir = join(root, "software", "example");
mkdirSync(join(packageDir, "dist"), { recursive: true });
writeFileSync(
join(packageDir, "package.json"),
JSON.stringify({ name: "@agentos-software/example", version: "0.0.1" }),
);
writeFileSync(
join(packageDir, "agentos-package.json"),
JSON.stringify({ commands: ["example"] }),
);
writeFileSync(join(packageDir, "dist", "package.aospkg"), "aospkg fixture");
writeFileSync(
join(root, "software", "default-software.json"),
JSON.stringify(["coreutils"]),
);
assert.throws(
() =>
stageSoftwareArtifacts(
root,
join(root, "target", "software-artifacts"),
),
/default software coreutils is not in the software catalog/,
);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
24 changes: 23 additions & 1 deletion scripts/publish/src/lib/software-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export interface SoftwareArtifactManifest {
export interface StageSoftwareArtifactsResult {
manifest: SoftwareArtifactManifest;
manifestPath: string;
/** Manifest entries of the default software, in install order. */
defaultSoftware: SoftwareArtifactManifest;
defaultSoftwarePath: string;
outputDir: string;
}

Expand Down Expand Up @@ -129,5 +132,24 @@ export function stageSoftwareArtifacts(
const manifest: SoftwareArtifactManifest = { schemaVersion: 1, artifacts };
const manifestPath = join(outputDir, "manifest.json");
writeFileSync(manifestPath, `${JSON.stringify(manifest, null, "\t")}\n`);
return { manifest, manifestPath, outputDir };

const defaultSoftwareNames: string[] = JSON.parse(
readFileSync(join(softwareRoot, "default-software.json"), "utf8"),
);
const defaultSoftware: SoftwareArtifactManifest = {
schemaVersion: 1,
artifacts: defaultSoftwareNames.map((name) => {
const artifact = artifacts.find((candidate) => candidate.name === name);
if (!artifact) {
throw new Error(`default software ${name} is not in the software catalog`);
}
return artifact;
}),
};
const defaultSoftwarePath = join(outputDir, "default-software.json");
writeFileSync(
defaultSoftwarePath,
`${JSON.stringify(defaultSoftware, null, "\t")}\n`,
);
return { manifest, manifestPath, defaultSoftware, defaultSoftwarePath, outputDir };
}
10 changes: 10 additions & 0 deletions software/default-software.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
"coreutils",
"sed",
"grep",
"gawk",
"findutils",
"diffutils",
"tar",
"gzip"
]
Loading