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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ jobs:
- name: ct-runner
working-directory: ct-runner
run: deno task test
- name: release bundle (embedder artifact gate)
run: deno test -A tools/release-bundle/bundle_test.ts
- name: conformance (official CM suite, Deno lane)
working-directory: harness
run: deno task conformance
Expand Down
23 changes: 18 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: denoland/setup-deno@v2
with:
deno-version: "2.9.5"

- name: compute tag
id: tag
Expand Down Expand Up @@ -63,18 +66,27 @@ jobs:
--config 'profile.release.strip=true'
cp target/wasm32-unknown-unknown/release/translator_shim.wasm deltic-translator-shim-min.wasm

- name: build embedder bundle
# The consumer-facing platform-neutral ES module (browser pages/
# workers + plain Node): tools/release-bundle/entry.ts, gated by
# tools/release-bundle/bundle_test.ts in the core matrix.
run: deno run -A tools/release-bundle/build.ts --out deltic-embedder.mjs

- name: checksums
run: sha256sum deltic-translator-shim.wasm deltic-translator-shim-min.wasm > SHA256SUMS
run: sha256sum deltic-translator-shim.wasm deltic-translator-shim-min.wasm deltic-embedder.mjs > SHA256SUMS

- name: create prerelease
run: |
{
echo "Prerelease \`${TAG}\` at ${GITHUB_SHA}."
echo
echo "Artifacts: the prebuilt translator shim wasm (standard release"
echo "build — what the test suites run against) and the size-tuned"
echo "variant per crates/translator-shim/README.md. Consumers need no"
echo "Rust toolchain; see README.md for status and usage."
echo "build — what the test suites run against), the size-tuned"
echo "variant per crates/translator-shim/README.md, and the embedder"
echo "bundle deltic-embedder.mjs (one platform-neutral ES module:"
echo "embedder API + Translator + ct-runner + wasi-shims — browsers"
echo "and plain Node, no flags). Consumers need no Rust toolchain;"
echo "see README.md for status and usage."
echo
echo '```'
cat SHA256SUMS
Expand All @@ -86,7 +98,8 @@ jobs:
--prerelease \
--title "deltic $TAG" \
--notes-file notes.md \
deltic-translator-shim.wasm deltic-translator-shim-min.wasm SHA256SUMS
deltic-translator-shim.wasm deltic-translator-shim-min.wasm \
deltic-embedder.mjs SHA256SUMS
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.tag.outputs.tag }}
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ cargo test -p translator-shim -p bindgen -p testgen
(cd harness && deno task conformance) # official CM suite, Deno lane
(cd wasi-shims && deno task test)
(cd ct-runner && deno task test)
deno test -A tools/release-bundle/bundle_test.ts # embedder-bundle release asset
(cd ports/websocket && deno task test) # + deno task conformance (spawns their echod)
deno run --allow-read tools/smoke-tls/run.ts --exec # polymorph-tls suite (issue #18)
(cd ports/webcrypto && deno test --allow-read tests/)
Expand Down
11 changes: 11 additions & 0 deletions ct-runner/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,14 @@ export {
} from "./import-analysis.ts";

export { Context, TEST_CONTEXT_INTERFACE, testContextImportRecord } from "./context.ts";

export {
applies,
collectTagsSections,
firstExcluding,
loadTagsInventory,
parseTagsRecords,
TAGS_SECTION,
type TagsInventory,
tagsOf,
} from "./tags.ts";
45 changes: 45 additions & 0 deletions tools/release-bundle/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Builds the consumer-facing embedder bundle (see ./entry.ts) — the
// `deltic-embedder.mjs` release asset. Same emission mechanism as the
// browser lanes (tools/browser/bundle.ts): `deno bundle --platform browser`,
// which resolves the workspace's `@deltic/*` bare specifiers natively and
// fails on `node:` residues (the runtime is platform-neutral by contract,
// docs/architecture.md §4.3).
//
// Usage: deno run -A tools/release-bundle/build.ts [--out <path>]
// (default: tools/release-bundle/dist/deltic-embedder.mjs, gitignored)

import { dirname, fromFileUrl, join, normalize } from "jsr:@std/path@1";

const repoRoot = normalize(
join(dirname(fromFileUrl(import.meta.url)), "..", ".."),
);

export async function buildBundle(out?: string): Promise<string> {
const outPath = out ??
join(repoRoot, "tools", "release-bundle", "dist", "deltic-embedder.mjs");
await Deno.mkdir(dirname(outPath), { recursive: true });
const cmd = new Deno.Command(Deno.execPath(), {
args: [
"bundle",
"--platform",
"browser",
"--format",
"esm",
"-o",
outPath,
join(repoRoot, "tools", "release-bundle", "entry.ts"),
],
cwd: repoRoot,
stdout: "inherit",
stderr: "inherit",
});
const { code } = await cmd.output();
if (code !== 0) throw new Error(`deno bundle failed with code ${code}`);
return outPath;
}

if (import.meta.main) {
const outIdx = Deno.args.indexOf("--out");
const out = outIdx >= 0 ? Deno.args[outIdx + 1] : undefined;
console.log(await buildBundle(out));
}
103 changes: 103 additions & 0 deletions tools/release-bundle/bundle_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// The release-asset gate for the embedder bundle: build it exactly as the
// release workflow does, then prove the artifact stands alone — imports as
// one self-contained ES module, carries no platform residues (the runtime
// is platform-neutral by contract, docs/architecture.md §4.3), and drives a
// real suite end to end INCLUDING the tag-gating path (nothing was
// tree-shaken away). Runs in the core CI matrix, which builds the
// translator wasm and the example guests earlier in the job.

import { buildBundle } from "./build.ts";

const root = new URL("../../", import.meta.url);
const TRANSLATOR = new URL(
"target/wasm32-unknown-unknown/release/translator_shim.wasm",
root,
);
const SUITE = new URL(
"examples/guests/build/test-suite.component.wasm",
root,
);

async function present(url: URL): Promise<boolean> {
try {
await Deno.stat(url);
return true;
} catch {
return false;
}
}
const ready = (await present(TRANSLATOR)) && (await present(SUITE));

function assertEq<T>(got: T, want: T, msg?: string): void {
const g = JSON.stringify(got);
const w = JSON.stringify(want);
if (g !== w) throw new Error(`${msg ?? "mismatch"}: got ${g}, want ${w}`);
}

/** Append a `component-test:tags@0.1` custom section (same encoding as
* ct-runner/tests/tags_test.ts — id 0, LEB name + data, legal anywhere). */
function withTags(bytes: Uint8Array, records: string): Uint8Array {
const enc = new TextEncoder();
const leb = (n: number): number[] => {
const out: number[] = [];
do {
let b = n & 0x7f;
n >>>= 7;
if (n !== 0) b |= 0x80;
out.push(b);
} while (n !== 0);
return out;
};
const name = enc.encode("component-test:tags@0.1");
const data = enc.encode(records);
const payload = [...leb(name.length), ...name, ...data];
const section = new Uint8Array([0x00, ...leb(payload.length), ...payload]);
const out = new Uint8Array(bytes.length + section.length);
out.set(bytes, 0);
out.set(section, bytes.length);
return out;
}

Deno.test({
name: "release bundle: self-contained, platform-neutral, runs a suite (tags included)",
ignore: !ready,
fn: async () => {
const out = await buildBundle();

// Platform purity of the ARTIFACT (the lanes pin the sources;
// this pins the emission): no node:/npm: residues, ESM shape.
const text = await Deno.readTextFile(out);
assertEq(/from\s*["']node:/.test(text), false, "node: import residue");
assertEq(/require\(["']node:/.test(text), false, "node: require residue");
assertEq(/from\s*["']npm:/.test(text), false, "npm: specifier residue");

const mod = await import(new URL(`file://${out}`).href);

// The full consumer path through the bundle alone: translate,
// instantiate, enumerate, execute, tag-gate.
const translator = await mod.Translator.create(
await Deno.readFile(TRANSLATOR),
);
const componentBytes = withTags(
await Deno.readFile(SUITE),
"suite/basic/pass\nsuite/basic/fail\nsuite/basic/skip\n" +
"suite/diag/chatty\nsuite/diag/slow hw\nsuite/nested/deep/leaf\n",
);
const { plan, adapters } = translator.translate(componentBytes);

const lines: string[] = [];
const counts = await mod.runSuite({ plan, componentBytes, adapters }, {
target: "deltic/bundle",
suiteName: "test-suite",
missing: ["hw"],
emit: (l: string) => lines.push(l),
});
assertEq(counts, { passed: 3, failed: 1, skipped: 1, na: 1, total: 6 });
assertEq(JSON.parse(lines[0]).run.scheduling, "tags");

// The wasi-shims surface came along too (polymorph consumers wire it).
assertEq(typeof mod.wasiShims, "function");
const shims = mod.wasiShims();
assertEq(typeof shims["wasi:cli/environment@0.2"], "object");
},
});
20 changes: 20 additions & 0 deletions tools/release-bundle/entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// The consumer-facing embedder bundle: one platform-neutral ES module
// carrying the embedder API surface plus the L3 runner glue, for consumers
// that cannot import deltic's TS sources directly — browser pages/workers
// and plain Node (the callback ABI needs no JSPI flag, so stock `node` can
// import this). Built by ./build.ts with `deno bundle --platform browser`
// (the same emission the browser lanes use, tools/browser/bundle.ts) and
// shipped as the `deltic-embedder.mjs` release asset (#16 interim scheme).
//
// Surface discipline: everything here is already public — the embedder API
// (contracts/embedder-api.md), the shim's `Translator`, `@deltic/ct-runner`
// (runSuite + Context + import analysis + the tags inventory), and
// `@deltic/wasi-shims`. The bundle adds no API of its own; per the #8
// rescope there is no runtime code generation anywhere in this graph
// (nothing needs CSP beyond `wasm-unsafe-eval`).

export * from "@deltic/runtime/embedder";
export { Translator } from "@deltic/runtime/shim";
export * from "@deltic/ct-runner";
export { wasiShims } from "@deltic/wasi-shims";
export type { WasiShims, WasiShimsOptions } from "@deltic/wasi-shims";
Loading