Build composable Camino packages with explicit type and source dependencies

This commit is contained in:
Timothy J. Aveni
2026-09-22 14:42:54 -07:00
parent 7173751f37
commit 82a4542746
30 changed files with 736 additions and 420 deletions
+19
View File
@@ -154,3 +154,22 @@ test("transaction process services do not promise generated TypeScript modules",
throw Error("No adapter claimed");
});
});
test("a remote export group in a mixed package witnesses only its own adapter", async () => {
const { asyncObjectPlan } = await import("./async-fixture.js");
const { expectedArtifactReceipt } = await import("../src/execution-world/receipts.js");
const plan = await asyncObjectPlan();
const fixture = buildArtifactFixture(plan, "async:controller-runtime@1");
const pkg = plan.definition.packages.find((entry) => entry.revision === fixture.manifest.packageRevisionId)!;
fixture.manifest.implementations = fixture.manifest.implementations.filter(
(entry) => entry.target === "server" && entry.compatibility === "quixos-js-module-v1",
);
for (const implementation of fixture.manifest.implementations)
implementation.exports = implementation.exports.filter((id) =>
pkg.exports.some((entry) => entry.id === id && entry.execution.kind === "remote"),
);
assert.deepEqual(
expectedArtifactReceipt(plan, fixture.manifest, "a".repeat(64)).receipt.generated.map((file) => file.path),
["share/quixos/generated/remote.ts"],
);
});
+76
View File
@@ -0,0 +1,76 @@
import assert from "node:assert/strict";
import test from "node:test";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { compileWorkspaceRepository, compileCapabilityResourceRepository } from "../src/capability-language/index.js";
import { bindingSchema } from "../src/bindings/index.js";
import { parseQuixosLockDocument } from "../src/resource-lock/index.js";
import { editStructure } from "../src/capability-language/structural-edits.js";
const commit = "a".repeat(40);
const entry = (kind: string, name: string) =>
`${kind} ${name} source {repository "https://example.test/${name}.git"; commit "${commit}";}`;
const lock = (entries = "") =>
`quixos-lock version 1 {quixos source {repository "https://example.test/quixos.git"; commit "${commit}";} ${entries}}`;
test("build dependency closures are retained without installing their capability descendants", async (t) => {
const directory = await fs.mkdtemp(path.join(os.tmpdir(), "qx-build-deps-"));
t.after(() => fs.rm(directory, { recursive: true, force: true }));
const write = async (name: string, files: Record<string, string>) => {
await fs.mkdir(path.join(directory, name));
for (const [file, text] of Object.entries(files)) await fs.writeFile(path.join(directory, name, file), text);
};
await write("root", {
"quixos.lock": lock(entry("build", "Library")),
"workspace.qx": `workspace Empty id "empty" revision "empty@1" commit "${commit}" {}`,
});
await write("Library", {
"quixos.lock": lock(entry("package", "Hidden")),
"types.ts": "export type Label = string;",
});
await write("Hidden", {
"quixos.lock": lock(entry("interface", "Data")),
"package.qx": 'import interface Data; package Hidden id "hidden" revision "hidden@1" {}',
});
await write("Data", { "quixos.lock": lock(), "interface.qx": 'interface Data id "data" revision "data@1" {}' });
const resolveResource = async (source: { repository: string }) => ({
directory: path.join(directory, new URL(source.repository).pathname.slice(1, -4)),
});
const compiled = await compileWorkspaceRepository({ rootDirectory: path.join(directory, "root"), resolveResource });
assert.equal(compiled.resources.length, 3);
assert.deepEqual(compiled.capabilityResources, []);
assert.deepEqual(compiled.workspace.packageImports, []);
assert.deepEqual(compiled.workspace.interfaceImports, []);
assert.deepEqual(bindingSchema(compiled).packages, []);
const library = await compileCapabilityResourceRepository({
rootDirectory: path.join(directory, "Library"),
kind: "build",
source: { resolver: "git", repository: "https://example.test/Library.git", commit },
resolveResource,
});
assert.deepEqual(library.resource, { kind: "build" });
assert.deepEqual(bindingSchema(library).interfaces, []);
await fs.writeFile(path.join(directory, "Library/quixos.lock"), lock(entry("build", "Library")));
await assert.rejects(
compileWorkspaceRepository({ rootDirectory: path.join(directory, "root"), resolveResource }),
/dependency cycle/,
);
});
test("build pins retain their kind through parsing and structural updates", () => {
const initial = lock(entry("build", "Library"));
const parsed = parseQuixosLockDocument(initial);
assert.ok(parsed.ok);
assert.equal(parsed.document.resources[0]!.kind, "build");
const edited = editStructure(initial, {
operation: "dependency",
kind: "build",
name: "Library",
source: { repository: "https://example.test/Library.git", commit: "b".repeat(40) },
});
const changed = parseQuixosLockDocument(edited);
assert.ok(changed.ok);
assert.equal(changed.document.resources[0]!.kind, "build");
assert.equal(changed.document.resources[0]!.source.commit, "b".repeat(40));
});
+21
View File
@@ -198,3 +198,24 @@ package Owner id "package:owner" revision "package:owner@1" {
}
await import("./emit-controllers.js");
// Standalone public builder roots must work without opting into the other modes.
for (const kind of ["portable", "service"]) {
const pkg = compileCapabilityResourceSource(
`package Owner id "package:owner" revision "package:owner@1" {
function identity id "identity" : int32 -> record {pid:int32;child:int32;}
execution ${kind === "portable" ? "snapshot targets [browser,server]" : "remote"};
}`,
{ source },
);
assert.ok(pkg.ok && pkg.resource.kind === "package", JSON.stringify(pkg.diagnostics));
const workspace = compileCapabilitySource(
`workspace Simple id "workspace:simple" revision "workspace:simple@1" commit "${source.commit}" {import package Owner;}`,
"workspace.qx",
{ packages: new Map([["Owner", pkg.resource.revision]]) },
);
assert.ok(workspace.ok, JSON.stringify(workspace.diagnostics));
const directory = `dist/managed/standalone-${kind}`;
await mkdir(directory, { recursive: true });
await writeFile(`${directory}/execution-plan.json`, JSON.stringify(compileExecutionPlan(workspace.workspace)));
}
+106 -100
View File
@@ -76,106 +76,112 @@ test(
},
);
test("pin upgrades publish children before parent locks and resume without republishing completed nodes", async (context) => {
const workbench = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-test-"));
context.after(() => fs.rm(workbench, { recursive: true, force: true }));
const from = "a".repeat(40),
to = "b".repeat(40),
framework = "c".repeat(40);
const sources = [
{
kind: "workspace" as const,
directory: "root",
source: { repository: "https://example.test/workspace.git", commit: from },
},
{
kind: "interface" as const,
directory: "resources/Named",
source: { repository: "https://example.test/named.git", commit: from },
},
];
const lock = `quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit "${framework}"; }`;
for (const node of sources) {
const directory = path.join(workbench, node.directory);
await fs.mkdir(directory, { recursive: true });
await execFile("git", ["-C", directory, "init"]);
await execFile("git", ["-C", directory, "remote", "add", "origin", node.source.repository]);
await fs.writeFile(path.join(directory, ".gitignore"), ".quixos/\n");
for (const dependencyKind of ["interface", "build"] as const)
test(`${dependencyKind} pin upgrades publish children before parent locks and resume without republishing completed nodes`, async (context) => {
const workbench = await fs.mkdtemp(path.join(os.tmpdir(), "qx-upgrade-test-"));
context.after(() => fs.rm(workbench, { recursive: true, force: true }));
const from = "a".repeat(40),
to = "b".repeat(40),
framework = "c".repeat(40);
const sources = [
{
kind: "workspace" as const,
directory: "root",
source: { repository: "https://example.test/workspace.git", commit: from },
},
{
kind: dependencyKind,
directory: "resources/Named",
source: { repository: "https://example.test/named.git", commit: from },
},
];
const lock = `quixos-lock version 1 { quixos source { repository "https://example.test/quixos.git"; commit "${framework}"; }`;
for (const node of sources) {
const directory = path.join(workbench, node.directory);
await fs.mkdir(directory, { recursive: true });
await execFile("git", ["-C", directory, "init"]);
await execFile("git", ["-C", directory, "remote", "add", "origin", node.source.repository]);
await fs.writeFile(path.join(directory, ".gitignore"), ".quixos/\n");
await fs.writeFile(
path.join(directory, "quixos.lock"),
lock +
(node.kind === "workspace"
? ` ${dependencyKind} Named source { repository "${sources[1].source.repository}"; commit "${from}"; }`
: "") +
" }",
);
}
await fs.writeFile(
path.join(directory, "quixos.lock"),
lock +
(node.kind === "workspace"
? ` interface Named source { repository "${sources[1].source.repository}"; commit "${from}"; }`
: "") +
" }",
path.join(
workbench,
dependencyKind === "interface" ? "resources/Named/interface.qx" : "resources/Named/types.ts",
),
dependencyKind === "interface"
? 'interface Named id "interface:named" revision "interface:named@1" { value name id "member:name" : string { get id "op:get"; } }'
: "export type Name = string;",
);
}
await fs.writeFile(
path.join(workbench, "resources/Named/interface.qx"),
'interface Named id "interface:named" revision "interface:named@1" { value name id "member:name" : string { get id "op:get"; } }',
);
await fs.writeFile(
path.join(workbench, "root/workspace.qx"),
`workspace W id "workspace:w" revision "workspace:w@1" commit "${from}" { import interface Named; atom A id "atom:a"; }`,
);
const snapshotMap = path.join(workbench, "snapshots.json");
await fs.writeFile(
snapshotMap,
JSON.stringify({
resources: [
{
kind: "interface",
repository: sources[1].source.repository,
commit: to,
directory: path.join(workbench, "resources/Named"),
},
],
}),
);
const oldMap = process.env.QUIXOS_SNAPSHOT_MAP;
process.env.QUIXOS_SNAPSHOT_MAP = snapshotMap;
context.after(() => {
if (oldMap === undefined) delete process.env.QUIXOS_SNAPSHOT_MAP;
else process.env.QUIXOS_SNAPSHOT_MAP = oldMap;
await fs.writeFile(
path.join(workbench, "root/workspace.qx"),
`workspace W id "workspace:w" revision "workspace:w@1" commit "${from}" { ${dependencyKind === "interface" ? "import interface Named;" : ""} atom A id "atom:a"; }`,
);
const snapshotMap = path.join(workbench, "snapshots.json");
await fs.writeFile(
snapshotMap,
JSON.stringify({
resources: [
{
kind: dependencyKind,
repository: sources[1].source.repository,
commit: to,
directory: path.join(workbench, "resources/Named"),
},
],
}),
);
const oldMap = process.env.QUIXOS_SNAPSHOT_MAP;
process.env.QUIXOS_SNAPSHOT_MAP = snapshotMap;
context.after(() => {
if (oldMap === undefined) delete process.env.QUIXOS_SNAPSHOT_MAP;
else process.env.QUIXOS_SNAPSHOT_MAP = oldMap;
});
const plan = await planPinUpgrades(workbench, { nodes: sources });
await fs.mkdir(path.join(workbench, ".quixos"), { recursive: true });
await fs.writeFile(
path.join(workbench, ".quixos/resource-graph.json"),
JSON.stringify({ resources: [{ ...sources[1], source: { resolver: "git", ...sources[1].source } }] }),
);
assert.deepEqual(
plan.nodes.map((node) => node.directory),
["resources/Named", "root"],
);
let fail = true;
const published: string[] = [];
const effects: UpgradeEffects = {
check: async (node) => {
if (node.kind === "workspace" && fail) throw new Error("refactor required");
},
snapshot: async () => to,
publish: async (root) => {
published.push(path.relative(workbench, root));
},
};
await assert.rejects(() => applyPinUpgrades(plan, undefined, effects), /refactor required/);
assert.deepEqual(published, ["resources/Named"]);
assert.match(await fs.readFile(path.join(workbench, "root/quixos.lock"), "utf8"), new RegExp(to));
const id = (await fs.readdir(path.join(workbench, ".quixos/upgrades")))
.find((name) => name.endsWith(".json"))!
.slice(0, -5);
fail = false;
await fs.appendFile(path.join(workbench, "root/workspace.qx"), "\n// explicit refactor\n");
await assert.rejects(() => applyPinUpgrades(plan, id, effects), /accept-edits/);
const result = await applyPinUpgrades(plan, id, effects, { acceptEdits: true });
assert.equal(result.activated, false);
assert.deepEqual(published, ["resources/Named", "root"]);
const graph = JSON.parse(await fs.readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8"));
assert.equal(graph.resources.length, 1);
assert.equal(graph.resources[0].source.commit, to);
const next = await planPinUpgrades(workbench, {
nodes: [sources[0], { ...sources[1], source: graph.resources[0].source }],
});
assert.deepEqual(next.nodes.find((node) => node.kind === "workspace")!.dependencies, ["resources/Named"]);
});
const plan = await planPinUpgrades(workbench, { nodes: sources });
await fs.mkdir(path.join(workbench, ".quixos"), { recursive: true });
await fs.writeFile(
path.join(workbench, ".quixos/resource-graph.json"),
JSON.stringify({ resources: [{ ...sources[1], source: { resolver: "git", ...sources[1].source } }] }),
);
assert.deepEqual(
plan.nodes.map((node) => node.directory),
["resources/Named", "root"],
);
let fail = true;
const published: string[] = [];
const effects: UpgradeEffects = {
check: async (node) => {
if (node.kind === "workspace" && fail) throw new Error("refactor required");
},
snapshot: async () => to,
publish: async (root) => {
published.push(path.relative(workbench, root));
},
};
await assert.rejects(() => applyPinUpgrades(plan, undefined, effects), /refactor required/);
assert.deepEqual(published, ["resources/Named"]);
assert.match(await fs.readFile(path.join(workbench, "root/quixos.lock"), "utf8"), new RegExp(to));
const id = (await fs.readdir(path.join(workbench, ".quixos/upgrades")))
.find((name) => name.endsWith(".json"))!
.slice(0, -5);
fail = false;
await fs.appendFile(path.join(workbench, "root/workspace.qx"), "\n// explicit refactor\n");
await assert.rejects(() => applyPinUpgrades(plan, id, effects), /accept-edits/);
const result = await applyPinUpgrades(plan, id, effects, { acceptEdits: true });
assert.equal(result.activated, false);
assert.deepEqual(published, ["resources/Named", "root"]);
const graph = JSON.parse(await fs.readFile(path.join(workbench, ".quixos/resource-graph.json"), "utf8"));
assert.equal(graph.resources.length, 1);
assert.equal(graph.resources[0].source.commit, to);
const next = await planPinUpgrades(workbench, {
nodes: [sources[0], { ...sources[1], source: graph.resources[0].source }],
});
assert.deepEqual(next.nodes.find((node) => node.kind === "workspace")!.dependencies, ["resources/Named"]);
});