Files
elizaos--eliza/packages/scripts/cloud/admin/dev/cloud-api-hono-dev.ts
T
wehub-resource-sync 426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

257 lines
7.2 KiB
TypeScript

#!/usr/bin/env bun
/**
* Local Hono server for the mock cloud E2E harness.
*
* The production Cloud API still runs as a Cloudflare Worker. This launcher is
* intentionally scoped to local/mock tests where we need deterministic process
* startup and the same Hono route graph, but not Wrangler's dev proxy/runtime.
*/
import { createApp } from "../../../../cloud/api/src/bootstrap-app";
type StoredObject = {
bytes: Uint8Array;
httpMetadata?: { contentType?: string };
customMetadata?: Record<string, string>;
uploaded: Date;
etag: string;
};
const encoder = new TextEncoder();
const store = new Map<string, StoredObject>();
const multipartUploads = new Map<
string,
{
key: string;
httpMetadata?: { contentType?: string };
customMetadata?: Record<string, string>;
parts: Map<number, Uint8Array>;
}
>();
async function toBytes(
value: string | ArrayBuffer | ArrayBufferView | Blob | null,
): Promise<Uint8Array> {
if (value === null) return new Uint8Array();
if (typeof value === "string") return encoder.encode(value);
if (value instanceof ArrayBuffer) return new Uint8Array(value);
if (ArrayBuffer.isView(value)) {
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
}
return new Uint8Array(await value.arrayBuffer());
}
function createEtag(bytes: Uint8Array): string {
return `"local-${bytes.byteLength}-${Bun.hash(bytes)}"`;
}
function objectHead(key: string, object: StoredObject) {
return {
key,
version: null,
size: object.bytes.byteLength,
etag: object.etag,
httpEtag: object.etag,
uploaded: object.uploaded,
httpMetadata: object.httpMetadata,
customMetadata: object.customMetadata,
checksums: {},
};
}
const blobBinding = {
async get(key: string) {
const object = store.get(key);
if (!object) return null;
return {
...objectHead(key, object),
httpMetadata: object.httpMetadata,
customMetadata: object.customMetadata,
async text() {
return new TextDecoder().decode(object.bytes);
},
async arrayBuffer() {
return object.bytes.buffer.slice(
object.bytes.byteOffset,
object.bytes.byteOffset + object.bytes.byteLength,
);
},
};
},
async put(
key: string,
value: string | ArrayBuffer | ArrayBufferView | Blob | null,
options?: {
httpMetadata?: { contentType?: string };
customMetadata?: Record<string, string>;
},
) {
const bytes = await toBytes(value);
store.set(key, {
bytes,
httpMetadata: options?.httpMetadata,
customMetadata: options?.customMetadata,
uploaded: new Date(),
etag: createEtag(bytes),
});
},
async delete(key: string) {
store.delete(key);
},
async list(options?: { prefix?: string; limit?: number; cursor?: string }) {
const prefix = options?.prefix ?? "";
const start = options?.cursor ? Number.parseInt(options.cursor, 10) : 0;
const limit = Math.max(1, options?.limit ?? 1000);
const matchingKeys = Array.from(store.keys())
.filter((key) => key.startsWith(prefix))
.sort();
const page = matchingKeys.slice(start, start + limit);
const next = start + page.length;
const objects = page.flatMap((key) => {
const object = store.get(key);
return object ? [objectHead(key, object)] : [];
});
return {
objects,
truncated: next < matchingKeys.length,
cursor: next < matchingKeys.length ? String(next) : undefined,
delimitedPrefixes: [],
};
},
async head(key: string) {
const object = store.get(key);
return object ? objectHead(key, object) : null;
},
async createMultipartUpload(
key: string,
options?: {
httpMetadata?: { contentType?: string };
customMetadata?: Record<string, string>;
},
) {
const uploadId = crypto.randomUUID();
multipartUploads.set(uploadId, {
key,
httpMetadata: options?.httpMetadata,
customMetadata: options?.customMetadata,
parts: new Map(),
});
return this.resumeMultipartUpload(key, uploadId);
},
resumeMultipartUpload(key: string, uploadId: string) {
const upload = multipartUploads.get(uploadId);
if (!upload || upload.key !== key) {
throw new Error(
`[cloud-api-hono-dev] multipart upload not found: ${key} ${uploadId}`,
);
}
return {
key,
uploadId,
async uploadPart(
partNumber: number,
value: string | ArrayBuffer | ArrayBufferView | Blob,
) {
const bytes = await toBytes(value);
upload.parts.set(partNumber, bytes);
return {
partNumber,
etag: createEtag(bytes),
};
},
async complete(uploadedParts: Array<{ partNumber: number }>) {
const orderedParts = uploadedParts
.map((part) => upload.parts.get(part.partNumber))
.filter((part): part is Uint8Array => part !== undefined);
const totalBytes = orderedParts.reduce(
(total, part) => total + part.byteLength,
0,
);
const bytes = new Uint8Array(totalBytes);
let offset = 0;
for (const part of orderedParts) {
bytes.set(part, offset);
offset += part.byteLength;
}
store.set(key, {
bytes,
httpMetadata: upload.httpMetadata,
customMetadata: upload.customMetadata,
uploaded: new Date(),
etag: createEtag(bytes),
});
multipartUploads.delete(uploadId);
const completedObject = store.get(key);
if (!completedObject) {
throw new Error(
`[cloud-api-hono-dev] multipart completion failed: ${key}`,
);
}
return objectHead(key, completedObject);
},
async abort() {
multipartUploads.delete(uploadId);
},
};
},
};
function executionContext(): ExecutionContext {
return {
waitUntil(promise) {
Promise.resolve(promise).catch((error) => {
console.error("[cloud-api-hono-dev] waitUntil failed", error);
});
},
passThroughOnException() {},
} as ExecutionContext;
}
const port = Number.parseInt(process.env.API_DEV_PORT || "8787", 10);
const hostname = process.env.API_DEV_HOST || "127.0.0.1";
const app = createApp();
const env = {
...process.env,
BLOB: blobBinding,
};
const server = Bun.serve({
hostname,
port,
async fetch(request) {
try {
const url = new URL(request.url);
if (url.pathname === "/api/health") {
return Response.json(
{
status: "ok",
timestamp: Date.now(),
region: "local-hono",
},
{ headers: { "Cache-Control": "no-store, max-age=0" } },
);
}
return await app.fetch(request, env, executionContext());
} catch (error) {
console.error("[cloud-api-hono-dev] unhandled request error", error);
return Response.json(
{ success: false, error: "internal_error" },
{ status: 500 },
);
}
},
});
console.log(
`[cloud-api-hono-dev] listening on http://${hostname}:${server.port}`,
);
const shutdown = () => {
server.stop(true);
process.exit(0);
};
process.once("SIGINT", shutdown);
process.once("SIGTERM", shutdown);