diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba38bb8..5febbc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,11 +29,11 @@ jobs: # Rebuild the default deployment before the normal runtime and package checks. - run: bun run test:cloudflare - run: bun run typecheck:cloudflare - - name: Install celld 0.4.1 + - name: Install celld 0.5.0 env: GH_TOKEN: ${{ github.token }} run: | - gh release download v0.4.1 --repo denoland/celld --pattern celld-x86_64-unknown-linux-gnu.gz --dir "$RUNNER_TEMP" + gh release download v0.5.0 --repo denoland/celld --pattern celld-x86_64-unknown-linux-gnu.gz --dir "$RUNNER_TEMP" gzip -d "$RUNNER_TEMP/celld-x86_64-unknown-linux-gnu.gz" chmod +x "$RUNNER_TEMP/celld-x86_64-unknown-linux-gnu" echo "CELLD_BIN=$RUNNER_TEMP/celld-x86_64-unknown-linux-gnu" >> "$GITHUB_ENV" diff --git a/cloudflare/backend.test.ts b/cloudflare/backend.test.ts index 5b5d454..a61ab04 100644 --- a/cloudflare/backend.test.ts +++ b/cloudflare/backend.test.ts @@ -12,7 +12,7 @@ let directory = "", binary = "", url = "", logs = ""; let child: ReturnType | undefined; async function startNative() { child = Bun.spawn([binary, "dev", directory, "--host", "127.0.0.1", "--port", new URL(url).port, "--no-watch"], { - cwd: directory, env: { ...process.env, CELLD_WORKER_LOADER: "LOADER", CELLD_ESBUILD: join(import.meta.dir, "../node_modules/.bin/esbuild") }, stdout: "pipe", stderr: "pipe", + cwd: directory, env: { ...process.env, CELLD_ESBUILD: join(import.meta.dir, "../node_modules/.bin/esbuild") }, stdout: "pipe", stderr: "pipe", }); for (const stream of [child.stdout, child.stderr]) if (typeof stream !== "number") void (async () => { for await (const chunk of stream) logs += new TextDecoder().decode(chunk); @@ -43,6 +43,7 @@ beforeAll(async () => { await Bun.write(join(directory, "worker.js"), await build.outputs[0]!.text()); await Bun.write(join(directory, "wrangler.jsonc"), JSON.stringify({ name: "backend-test", main: "worker.js", compatibility_date: "2026-09-06", compatibility_flags: ["nodejs_compat"], durable_objects: { bindings: [{ name: "BACKENDS", class_name: "ArtifactBackend" }] }, migrations: [{ tag: "v1", new_sqlite_classes: ["ArtifactBackend"] }], + worker_loaders: [{ binding: "LOADER" }], })); await startNative(); return; diff --git a/cloudflare/celld.test.ts b/cloudflare/celld.test.ts index 542dc08..5dd0ca2 100644 --- a/cloudflare/celld.test.ts +++ b/cloudflare/celld.test.ts @@ -61,7 +61,7 @@ async function startCelld(compiler = esbuild) { processHandle = Bun.spawn({ cmd: [binary, "dev", project, "--host", "127.0.0.1", "--port", String(port), "--no-watch"], cwd: project, - env: { ...process.env, CELLD_ESBUILD: compiler, CELLD_WORKER_LOADER: "LOADER" }, + env: { ...process.env, CELLD_ESBUILD: compiler }, stdout: "pipe", stderr: "pipe", }); diff --git a/cloudflare/scheduling-celld.test.ts b/cloudflare/scheduling-celld.test.ts index d7560e4..8d98e5f 100644 --- a/cloudflare/scheduling-celld.test.ts +++ b/cloudflare/scheduling-celld.test.ts @@ -3,11 +3,12 @@ import {mkdtemp,rm} from "node:fs/promises"; import {join} from "node:path"; import {tmpdir} from "node:os"; import {ensureCelldRuntime} from "../src/local/celld-runtime"; +import {prepareCelldConfig} from "../scripts/prepare-celld"; const enabled=process.env.CELLD_SCHEDULING_INTEGRATION==="1"; let directory="",url="",binary="",logs=""; let child:ReturnType|undefined; async function start(){ - child=Bun.spawn([binary,"dev",directory,"--host","127.0.0.1","--port",new URL(url).port,"--no-watch"],{cwd:directory,env:{...process.env,CELLD_WORKER_LOADER:"LOADER",CELLD_ESBUILD:join(import.meta.dir,"../node_modules/.bin/esbuild")},stdout:"pipe",stderr:"pipe"}); + child=Bun.spawn([binary,"dev",directory,"--host","127.0.0.1","--port",new URL(url).port,"--no-watch","--logs"],{cwd:directory,env:{...process.env,CELLD_ESBUILD:join(import.meta.dir,"../node_modules/.bin/esbuild")},stdout:"pipe",stderr:"pipe"}); for(const stream of [child.stdout,child.stderr]) if(typeof stream!=="number")void(async()=>{for await(const chunk of stream)logs+=new TextDecoder().decode(chunk);})(); const deadline=Date.now()+45000; while(Date.now(){ const bundle=await Bun.build({entrypoints:[join(import.meta.dir,"scripts-test-worker.ts")],target:"browser",format:"esm",external:["cloudflare:workers","node:*","fs","fs/promises"]}); if(!bundle.success)throw new Error(bundle.logs.join("\n")); await Bun.write(join(directory,"worker.js"),await bundle.outputs[0]!.text()); - await Bun.write(join(directory,"wrangler.jsonc"),JSON.stringify({name:"schedule-test",main:"worker.js",compatibility_date:"2026-09-06",compatibility_flags:["nodejs_compat"],durable_objects:{bindings:[{name:"SCRIPTS",class_name:"ScriptLibrary"},{name:"SCRIPT_BACKENDS",class_name:"ScriptBackend"},{name:"LINKS",class_name:"ArtifactLinks"}]},migrations:[{tag:"v1",new_sqlite_classes:["ScriptLibrary","ScriptBackend","ArtifactLinks"]}]})); + await Bun.write(join(directory,"wrangler.jsonc"),JSON.stringify({name:"schedule-test",main:"worker.js",compatibility_date:"2026-09-06",compatibility_flags:["nodejs_compat"],worker_loaders:[{binding:"LOADER"}],durable_objects:{bindings:[{name:"SCRIPTS",class_name:"ScriptLibrary"},{name:"SCRIPT_BACKENDS",class_name:"ScriptBackend"},{name:"LINKS",class_name:"ArtifactLinks"}]},migrations:[{tag:"v1",new_sqlite_classes:["ScriptLibrary","ScriptBackend","ArtifactLinks"]}]})); + await prepareCelldConfig(join(directory,"wrangler.jsonc"),join(directory,"wrangler.jsonc"),{main:"worker.js"}); await start(); },180000); afterAll(async()=>{await stop();if(directory)await rm(directory,{recursive:true,force:true});}); @@ -36,7 +38,7 @@ async function call(path:string,input:unknown={}){const response=await fetch(`${ await call("backend/schedule",{action:"set",identity:{...identity,libraryKey:"scheduled",origin:url},cron:"* * * * *",timezone:"UTC",request:{path:"/tick",method:"POST",headers:[["x-artifact-internal-run","caller-value"]]}}); await call("backend/schedule",{action:"pause"}); await call("backend/schedule",{action:"run_now"}); - expect((await call("backend/runs"))[0]).toMatchObject({status:"succeeded",trigger:"manual"}); + expect((await call("backend/runs"))[0],JSON.stringify(await call("backend/logs"))+logs).toMatchObject({status:"succeeded",trigger:"manual"}); await stop();await start(); expect(await call("backend/schedule")).toMatchObject({paused:true,cron:"* * * * *",timezone:"UTC"}); expect(await call("backend/runs")).toHaveLength(1); diff --git a/cloudflare/script-backend.ts b/cloudflare/script-backend.ts index 962c847..beb68ea 100644 --- a/cloudflare/script-backend.ts +++ b/cloudflare/script-backend.ts @@ -6,14 +6,14 @@ import { nativeRequest } from "./backend"; import type { ArtifactHttpRequest } from "../src/httpTypes"; import { sha256 } from "./library"; import { scriptResponse } from "./script-service-http"; +import { scriptRuntimeConfig } from "./script-runtime"; export type ScriptLog = { timestamp: string; level: string; message: string; run_id?: string }; export type ScriptRequest = {code: string; hash: string; secrets: Record; request: Request; trigger?: "http" | "manual" | "schedule"; run_id?: string}; export type ScheduleIdentity = {libraryKey: string; workspace: string; name: string; origin: string}; export type ScriptSchedule = ScheduleTiming & { paused: boolean; next_run_at: number | null; request: ArtifactHttpRequest}; export type ScriptRun = {id: string; revision: string; trigger: string; started_at: string; finished_at: string | null; duration_ms: number | null; status: string; http_status: number | null}; -type Env = { LOADER: WorkerLoader; SCRIPTS: DurableObjectNamespace; LINKS: DurableObjectNamespace }; -const runtimeConfig = { compatibilityDate: "2026-09-06", compatibilityFlags: ["nodejs_compat"], limits: { cpuMs: 30000, subRequests: 50 } }; +type Env = { LOADER: WorkerLoader; SCRIPTS: DurableObjectNamespace; LINKS: DurableObjectNamespace; ARTIFACTS_RUNTIME?: string }; declare abstract class ScriptFacet extends DurableObject { takeLogs(): ScriptLog[]; } const RUN_HEADER = "x-artifact-internal-run"; export const SCRIPT_HEADER = "x-artifact-internal-script"; @@ -64,10 +64,12 @@ export default { fetch() { return new Response(typeof handler?.fetch === "functi `; export class ScriptBackend extends DurableObject { + private readonly runtimeConfig: ReturnType; private activeKey?: string; private facet?: Fetcher; constructor(state: DurableObjectState, env: Env) { super(state,env); + this.runtimeConfig = scriptRuntimeConfig(env.ARTIFACTS_RUNTIME); state.storage.sql.exec("create table if not exists execution_logs(id integer primary key autoincrement,timestamp text not null,level text not null,message text not null)"); if (!state.storage.sql.exec<{name: string}>("pragma table_info(execution_logs)").toArray().some(column => column.name === "run_id")) state.storage.sql.exec("alter table execution_logs add column run_id text"); state.storage.sql.exec("create table if not exists execution_runs(id text primary key,revision text not null,trigger text not null,started_at text not null,finished_at text,duration_ms integer,status text not null,http_status integer)"); @@ -76,12 +78,12 @@ export class ScriptBackend extends DurableObject { state.storage.sql.exec("update execution_runs set status='interrupted' where status='running'"); } private cacheKey(input: Omit) { - return sha256(JSON.stringify([this.ctx.id.toString(), input.code, input.secrets, runtimeConfig, adapter, logging])); + return sha256(JSON.stringify([this.ctx.id.toString(), input.code, input.secrets, this.runtimeConfig, adapter, logging])); } private worker(input: Omit) { const hash=this.cacheKey(input); return this.env.LOADER.get(hash,async()=>({ - ...runtimeConfig, + ...this.runtimeConfig, mainModule:"adapter.js",modules:{"adapter.js":adapter,"logging.js":logging.replace("__ARTIFACTS_SCRIPT_SECRETS__", () => JSON.stringify(input.secrets)),"user.js":input.code}, env:{secrets:input.secrets}, })); diff --git a/cloudflare/script-runtime.test.ts b/cloudflare/script-runtime.test.ts new file mode 100644 index 0000000..aa1136d --- /dev/null +++ b/cloudflare/script-runtime.test.ts @@ -0,0 +1,10 @@ +import { expect, test } from "bun:test"; +import { scriptRuntimeConfig } from "./script-runtime"; + +test("only an explicit celld deployment omits unsupported script limits", () => { + for (const runtime of [undefined, "cloudflare", "", "unknown"]) { + expect(scriptRuntimeConfig(runtime).limits).toEqual({ cpuMs: 30000, subRequests: 50 }); + } + expect(scriptRuntimeConfig("celld")).not.toHaveProperty("limits"); + expect(scriptRuntimeConfig("celld").compatibilityFlags).toContain("nodejs_compat"); +}); diff --git a/cloudflare/script-runtime.ts b/cloudflare/script-runtime.ts new file mode 100644 index 0000000..d4a64f9 --- /dev/null +++ b/cloudflare/script-runtime.ts @@ -0,0 +1,9 @@ +export function scriptRuntimeConfig(runtime?: string) { + return { + compatibilityDate: "2026-09-06", + compatibilityFlags: ["nodejs_compat"], + // celld 0.5.0 rejects limits and cannot enforce these per-script budgets. + // Keep the Cloudflare limits unless the deployment explicitly selects celld. + ...(runtime === "celld" ? {} : { limits: { cpuMs: 30000, subRequests: 50 } }), + }; +} diff --git a/docs/celld-deployment.md b/docs/celld-deployment.md index 8b2f940..d841535 100644 --- a/docs/celld-deployment.md +++ b/docs/celld-deployment.md @@ -1,8 +1,8 @@ # Deploying Artifacts with celld -The bundled runtime is pinned to celld 0.4.1. This guidance follows the -[upstream deployment documentation](https://github.com/denoland/celld/blob/v0.4.1/docs/README.md) -and [security model](https://github.com/denoland/celld/blob/v0.4.1/docs/security.md). +The bundled runtime is pinned to celld 0.5.0. This guidance follows the +[upstream deployment documentation](https://github.com/denoland/celld/blob/v0.5.0/docs/README.md) +and [security model](https://github.com/denoland/celld/blob/v0.5.0/docs/security.md). ## Local persistent host @@ -25,7 +25,7 @@ For production or multiple machines, use celld's bucket-backed node mode: 1. Choose a supported object store with conditional writes and consistent reads. Upstream qualifies Amazon S3, Cloudflare R2, Google Cloud Storage, Tigris, and Azure Blob Storage. Not every S3-compatible provider meets the requirements; - consult the [storage guarantees](https://github.com/denoland/celld/blob/v0.4.1/docs/guarantees.md). + consult the [storage guarantees](https://github.com/denoland/celld/blob/v0.5.0/docs/guarantees.md). 2. Prepare the Artifacts Worker with `bun run build:package`, configure application authentication and runtime variables, and deploy the prepared Wrangler project through `celld deploy`. Preserve JavaScript and WASM modules together. Do not @@ -42,9 +42,10 @@ For production or multiple machines, use celld's bucket-backed node mode: the supervisor's stop grace must exceed celld's configured shutdown bound (40 seconds by default), with enough time for the expected drain and handoff. 6. Update app code through `celld deploy`. Running nodes adopt deployments in - place; a failed build leaves the previous deployment serving. Roll runtime - upgrades with readiness checks and spare capacity rather than restarting every - node together. + place; a failed build leaves the previous deployment serving. For runtime + upgrades, follow the release-specific shutdown requirements. Upgrading from + 0.4.1 to 0.5.0 requires stopping the whole fleet before starting upgraded nodes; + see the [0.5.0 release notes](https://github.com/denoland/celld/releases/tag/v0.5.0). Two or more nodes reduce write latency through peer durability; a single node waits for bucket persistence. Monitor memory headroom, cold activation queues, diff --git a/docs/cloudflare.md b/docs/cloudflare.md index b2cc01d..368717c 100644 --- a/docs/cloudflare.md +++ b/docs/cloudflare.md @@ -377,8 +377,14 @@ backend. Their browser views can use ordinary local artifact interactions, but [celld](https://github.com/denoland/celld) is a Cloudflare-compatible runtime. Wrangler/workerd remains the default Cloudflare development path. Artifact also -qualifies the released celld v0.4.1 against the built application, including +targets the released celld v0.5.0 with integration tests for the built application, including generated `ArtifactServer` execution on raw SQL and KV facets. +The generated config preserves the `worker_loaders` binding used by celld 0.5.0; +the removed `CELLD_WORKER_LOADER` environment variable must no longer be set. +The generated config sets `ARTIFACTS_RUNTIME=celld`, which omits the unsupported +script CPU and subrequest limits. celld does not enforce these per-script budgets. +Cloudflare retains the 30,000 ms CPU and 50 subrequest limits by default; do not +set this variable to `celld` on Cloudflare. See the [upstream compatibility notes](https://github.com/denoland/celld/blob/v0.5.0/docs/cloudflare-compat.md#dynamic-workers). ```sh bun run dev:celld @@ -414,7 +420,7 @@ carries that D1 binding. celld v0.4.1 can run the checked-in Better Auth schema local D1 and preserve it across a restart. Its `celld d1 migrations apply` command targets deployed bucket storage rather than the local development database, so the Artifacts celld integration uses a temporary bootstrap Worker for local schema -setup. This fixture qualifies the complete OAuth/session flow on celld v0.4.1; +setup. This fixture exercises the complete OAuth/session flow on celld v0.5.0; ordinary `dev:celld` does not apply auth migrations automatically. Use Wrangler/workerd's local migration command for routine Better Auth development. diff --git a/docs/daemon.md b/docs/daemon.md index 21612ea..75117f8 100644 --- a/docs/daemon.md +++ b/docs/daemon.md @@ -29,7 +29,7 @@ The server keeps its project and native state under the Artifacts data directory `--state-dir PATH` overrides that project directory for a foreground run. Press Ctrl-C to request a graceful stop. SIGTERM uses the same shutdown path. -On first use, Artifacts downloads celld 0.4.1 for a supported platform, verifies +On first use, Artifacts downloads celld 0.5.0 for a supported platform, verifies the pinned archive and executable SHA-256 values, and installs it in the Artifact data directory with user-only permissions. This release supports Apple Silicon macOS and glibc Linux on arm64 or x64. The ordinary Bun commands do not require this native runtime; `artifacts server` diff --git a/docs/releasing.md b/docs/releasing.md index e068717..5891a19 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -77,7 +77,7 @@ the runtime suite, and daemon lifecycle on supported service managers. The local packaged server uses loopback single-user mode; deploying Better Auth remains the separate configuration described in [Cloudflare setup](cloudflare.md). -celld v0.4.1 publishes Apple Silicon macOS and glibc Linux arm64/x64 binaries. +celld v0.5.0 publishes Apple Silicon macOS and glibc Linux arm64/x64 binaries. Intel macOS and other targets cannot use this managed native server version. The ordinary Bun CLI, stdio MCP, and local file gallery do not download celld. diff --git a/docs/scripts.md b/docs/scripts.md index c8316c0..9915445 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -53,7 +53,7 @@ export default { } satisfies ExportedHandler; ``` -Do not assume an in-memory global persists between requests or updates. Each execution is limited to 30 seconds of CPU and 50 subrequests, in addition to the Workers runtime limits. Logs retain the latest 100 entries with messages capped at 2 KiB. Secret strings are redacted from captured console output; handlers still control their own response bodies and outbound requests. +Do not assume an in-memory global persists between requests or updates. On Cloudflare, each execution is limited to 30 seconds of CPU and 50 subrequests, in addition to the Workers runtime limits. The celld configuration omits these per-script budgets because celld 0.5.0 rejects them and does not enforce them. Logs retain the latest 100 entries with messages capped at 2 KiB. Secret strings are redacted from captured console output; handlers still control their own response bodies and outbound requests. ## Access diff --git a/e2e/celld-auth-runtime.ts b/e2e/celld-auth-runtime.ts index 90a3c9b..29d4b02 100644 --- a/e2e/celld-auth-runtime.ts +++ b/e2e/celld-auth-runtime.ts @@ -88,7 +88,7 @@ function spawnCelld(binary: string, esbuild: string, project: string, port: numb const process = Bun.spawn({ cmd: [binary, "dev", project, "--host", "127.0.0.1", "--port", String(port), "--no-watch", "--logs"], cwd: project, - env: { ...globalThis.process.env, CELLD_ESBUILD: esbuild, CELLD_WORKER_LOADER: "LOADER" }, + env: { ...globalThis.process.env, CELLD_ESBUILD: esbuild }, stdout: "pipe", stderr: "pipe", }); diff --git a/examples/runner-status/local.ts b/examples/runner-status/local.ts index 1a1a532..87a19f1 100644 --- a/examples/runner-status/local.ts +++ b/examples/runner-status/local.ts @@ -110,7 +110,7 @@ export async function main() { await writeFile(runtimeConfig, JSON.stringify(runtime), { mode: 0o600 }); if (stopping) throw new Error("Startup interrupted"); child = Bun.spawn([binary, "dev", runtimeConfig, "--host", "127.0.0.1", "--port", String(port), "--no-watch"], { - cwd: directory, env: { ...environment, CELLD_ESBUILD: join(root, "node_modules/.bin/esbuild"), CELLD_WORKER_LOADER: "LOADER" }, + cwd: directory, env: { ...environment, CELLD_ESBUILD: join(root, "node_modules/.bin/esbuild") }, stdout: "inherit", stderr: "inherit", }); const origin = `http://127.0.0.1:${port}`; diff --git a/package.json b/package.json index edeb6b9..31564dc 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "build:cloudflare-compiler": "wrangler deploy --config cloudflare/wrangler.compiler.jsonc --dry-run --outdir ../dist/worker", "typecheck:cloudflare": "tsc -p cloudflare/tsconfig.json --noEmit && tsc -p examples/runner-status/tsconfig.worker.json --noEmit", "test:cloudflare": "bun run build:cloudflare-compiler && bun run build:cloudflare && bun test cloudflare --timeout 60000", - "test:celld": "bun run build:cloudflare && bun run prepare:celld && CELLD_INTEGRATION=1 CELLD_AUTH_INTEGRATION=1 CELLD_BACKEND_INTEGRATION=1 bun test cloudflare/backend.test.ts cloudflare/celld.test.ts cloudflare/auth-flow.test.ts --timeout 180000", + "test:celld": "bun run build:cloudflare && bun run prepare:celld && CELLD_INTEGRATION=1 CELLD_AUTH_INTEGRATION=1 CELLD_BACKEND_INTEGRATION=1 CELLD_SCHEDULING_INTEGRATION=1 bun test cloudflare/backend.test.ts cloudflare/celld.test.ts cloudflare/auth-flow.test.ts cloudflare/scheduling-celld.test.ts --timeout 180000", "deploy:cloudflare": "wrangler deploy", "seed:cloudflare": "bun run scripts/seed-cloudflare.ts", "example:runner-status": "bun run examples/runner-status/local.ts", diff --git a/scripts/dev-celld.ts b/scripts/dev-celld.ts index 7c2a415..840bbac 100644 --- a/scripts/dev-celld.ts +++ b/scripts/dev-celld.ts @@ -10,7 +10,7 @@ if (!existsSync(esbuild)) throw new Error(`esbuild was not found at ${esbuild}; const child = Bun.spawn({ cmd: [binary, "dev", resolve(root, "wrangler.celld.jsonc"), "--host", "127.0.0.1", "--port", process.env.CELLD_PORT ?? "4786"], cwd: root, - env: { ...process.env, CELLD_ESBUILD: esbuild, CELLD_WORKER_LOADER: "LOADER" }, + env: { ...process.env, CELLD_ESBUILD: esbuild }, stdin: "inherit", stdout: "inherit", stderr: "inherit", diff --git a/scripts/package.integration.test.ts b/scripts/package.integration.test.ts index 9bc7891..e7f1a1d 100644 --- a/scripts/package.integration.test.ts +++ b/scripts/package.integration.test.ts @@ -234,7 +234,7 @@ try { : undefined; if (!target) throw new Error(`CELLD_PACKAGE_INTEGRATION is unsupported on ${process.platform}/${process.arch}`); if (process.env.CELLD_BIN) { - const managedBinary = join(isolatedEnv.ARTIFACTS_DATA_HOME, "runtimes", "celld", "0.4.1", target, "celld"); + const managedBinary = join(isolatedEnv.ARTIFACTS_DATA_HOME, "runtimes", "celld", "0.5.0", target, "celld"); mkdirSync(dirname(managedBinary), { recursive: true }); copyFileSync(process.env.CELLD_BIN, managedBinary); chmodSync(managedBinary, 0o700); diff --git a/scripts/prepare-celld.ts b/scripts/prepare-celld.ts index 6e2465e..ff54644 100644 --- a/scripts/prepare-celld.ts +++ b/scripts/prepare-celld.ts @@ -17,6 +17,7 @@ const supportedKeys = new Set([ "kv_namespaces", "queues", "workflows", + "worker_loaders", "r2_buckets", ]); @@ -34,7 +35,7 @@ export async function prepareCelldConfig( directory: paths.assets ?? "./dist/cloudflare/assets", }; } - config.vars = { ...(config.vars as Record | undefined), ENVIRONMENT: "local" }; + config.vars = { ...(config.vars as Record | undefined), ENVIRONMENT: "local", ARTIFACTS_RUNTIME: "celld" }; await Bun.write(output, `${JSON.stringify(config, null, 2)}\n`); return config; } diff --git a/src/local/celld-runtime.test.ts b/src/local/celld-runtime.test.ts index ae57088..5e86ca1 100644 --- a/src/local/celld-runtime.test.ts +++ b/src/local/celld-runtime.test.ts @@ -11,7 +11,7 @@ afterEach(async () => { for (const directory of directories.splice(0)) await rm( async function fixture() { const dataRoot = await mkdtemp(join(tmpdir(), "artifact-runtime-test-")); directories.push(dataRoot); - const binary = Buffer.from("#!/bin/sh\nprintf 'celld 0.4.1\\n'\n"); + const binary = Buffer.from("#!/bin/sh\nprintf 'celld 0.5.0\\n'\n"); const compressed = gzipSync(binary); const hash = (bytes: Uint8Array) => createHash("sha256").update(bytes).digest("hex"); return { dataRoot, binary, compressed, artifact: { target: "fixture", archiveSha256: hash(compressed), binarySha256: hash(binary) } }; @@ -33,12 +33,12 @@ test("selects only released native platforms and keeps app data outside the chec test("downloads a verified executable once, works offline thereafter, and repairs executable mode", async () => { const f = await fixture(); let calls = 0; - const fetcher = (async (url: string | URL | Request) => { calls++; expect(String(url)).toEndWith("/v0.4.1/celld-fixture.gz"); return new Response(f.compressed); }); + const fetcher = (async (url: string | URL | Request) => { calls++; expect(String(url)).toEndWith("/v0.5.0/celld-fixture.gz"); return new Response(f.compressed); }); const executable = await ensureCelldRuntime({ ...f, fetch: fetcher }); expect(await readFile(executable)).toEqual(f.binary); expect((await stat(executable)).mode & 0o777).toBe(0o700); const child = Bun.spawn([executable], { stdout: "pipe" }); - expect(await new Response(child.stdout).text()).toBe("celld 0.4.1\n"); + expect(await new Response(child.stdout).text()).toBe("celld 0.5.0\n"); expect(await child.exited).toBe(0); expect(await ensureCelldRuntime({ ...f, fetch: (async () => { throw new Error("offline"); }) })).toBe(executable); expect(calls).toBe(1); @@ -52,7 +52,7 @@ test("rejects HTTP, archive, and decompressed integrity failures before installi await expect(ensureCelldRuntime({ ...f, fetch: (async () => new Response("unavailable", { status: 503 })) })).rejects.toThrow("HTTP 503"); await expect(ensureCelldRuntime({ ...f, fetch: (async () => new Response("bad bytes")) })).rejects.toThrow("archive checksum mismatch"); await expect(ensureCelldRuntime({ ...f, artifact: { ...f.artifact, binarySha256: "wrong" }, fetch: (async () => new Response(f.compressed)) })).rejects.toThrow("executable checksum mismatch"); - expect(await Bun.file(join(f.dataRoot, "runtimes/celld/0.4.1/fixture/celld")).exists()).toBe(false); + expect(await Bun.file(join(f.dataRoot, "runtimes/celld/0.5.0/fixture/celld")).exists()).toBe(false); }); test("concurrent verified installs publish one complete executable", async () => { diff --git a/src/local/celld-runtime.ts b/src/local/celld-runtime.ts index b3f3d62..3227d14 100644 --- a/src/local/celld-runtime.ts +++ b/src/local/celld-runtime.ts @@ -5,14 +5,14 @@ import { homedir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { gunzipSync } from "node:zlib"; -export const CELLD_VERSION = "0.4.1"; +export const CELLD_VERSION = "0.5.0"; type Artifact = { target: string; archiveSha256: string; binarySha256: string }; -// Archive digests from the GitHub v0.4.1 release API; executable digests computed +// Archive digests from the GitHub v0.5.0 release API; executable digests computed // from those verified archives. Pin both so cached executables are checked too. const artifacts: Record = { - "darwin-arm64": { target: "aarch64-apple-darwin", archiveSha256: "95c689769f66c08fd0d191fbfc731adb4c8340b49678b11979698ecf2e73393d", binarySha256: "12865054bc0438f8e3dcdaf0e38194ff73f5609794fa2352cfa9005f7831f344" }, - "linux-arm64": { target: "aarch64-unknown-linux-gnu", archiveSha256: "5cc2281493a896b2cc7c7b6c46b2188753832d6b028457338c8eea774a1dd9a1", binarySha256: "bf2546c163e925120ab4df2de1bf4b510f84fbb8e51ab85a074eb5b052423148" }, - "linux-x64": { target: "x86_64-unknown-linux-gnu", archiveSha256: "7b42a410e340bca4dbadd08ecb7f8983854aa855aa7467c29ab0e08b8b7f2007", binarySha256: "e499e6d8e1bb04297252bd4df661bc4429293144fe6f20e02a51d12f1e23b161" }, + "darwin-arm64": { target: "aarch64-apple-darwin", archiveSha256: "07f6dbded0a2ffe3d7626842908ea81ed517fe94b0cfae784fd0c053d8952e80", binarySha256: "77e5d6f129c1bf49e8d71ac5b68298fb8c8c247392add28a90c4cca6d9957786" }, + "linux-arm64": { target: "aarch64-unknown-linux-gnu", archiveSha256: "bd3965f78f96c755b64b0280746a7c26116fa9df01a9e57c5930e9752945c993", binarySha256: "9942da9973a0ca15260921295bbbdbb80a2f66f2eccac4984e748aefc252bd7e" }, + "linux-x64": { target: "x86_64-unknown-linux-gnu", archiveSha256: "1039eee3737bb432ca0cd399fc55cc0aab4e653b2beae26009e455fea4e5334c", binarySha256: "ca451f33a58a393ec580a186af4f0ef8e9b9e665d253f1f95a7d556213b33bea" }, }; export function celldArtifact(platform: string = process.platform, arch: string = process.arch): Artifact { diff --git a/src/local/server.ts b/src/local/server.ts index f19a175..49f23f5 100644 --- a/src/local/server.ts +++ b/src/local/server.ts @@ -61,7 +61,7 @@ export async function runCelldServer(options: { port?: number; stateDir?: string const esbuild = esbuildRequire.resolve(`@esbuild/${process.platform}-${process.arch}/bin/esbuild`); child = Bun.spawn([executable, "dev", project, "--host", "127.0.0.1", "--port", String(port), "--no-watch", "--logs"], { cwd: project, - env: { ...process.env, CELLD_ESBUILD: esbuild, CELLD_WORKER_LOADER: "LOADER", CELLD_IDLE_EVICT_S: process.env.CELLD_IDLE_EVICT_S ?? "60", RUST_LOG: process.env.RUST_LOG ?? "warn" }, + env: { ...process.env, CELLD_ESBUILD: esbuild, CELLD_IDLE_EVICT_S: process.env.CELLD_IDLE_EVICT_S ?? "60", RUST_LOG: process.env.RUST_LOG ?? "warn" }, stdin: "ignore", stdout: "pipe", stderr: "pipe", }); const forward = async (stream: ReadableStream | number | undefined) => {