feat(code): Workers 就緒化(裁定 A)—— singlefile variant + 純 JS SHA-256 prelude
- 沙箱改用 quickjs-emscripten singlefile variant(wasm 內嵌 base64、同步載入), CF Workers 相容;sandbox.mjs 成 Node/Worker 共用 runtime-agnostic 核心。 - sha256 curated builtin 改「純 JS SHA-256 prelude 字串注入」,去掉 node:crypto / async Web Crypto host-call,Node/Worker 皆決定性(card content_hash 逐字等價)。 - index.ts 成可部署 Worker host(Hono,POST /→runCode),自足不走 TinyGo 模板流程。 - 補 wrangler.toml(arcrun-code / code.arcrun.dev)、tsconfig、DEPLOY.md。 - contract stability 修為 floating(過 registry zod schema 驗證)。 - 單測 12/12 全綠(含 card→envelope 與原模組 planCard 逐欄全等)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJiLCRUU2o3aSpPEzVCt2o
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# `code` 零件 —— 部署清單(過閘用;本輪不執行)
|
||||
|
||||
> 封裝=A(quickjs-emscripten singlefile variant)。`code` 是**自足 Worker**,不走
|
||||
> `deploy-logic-components.sh`(那條是 TinyGo-wasm 專用)。以下由 leo 過閘後執行。
|
||||
|
||||
## 1. 部署 code Worker(wrangler,禁 acr update)
|
||||
|
||||
```bash
|
||||
cd registry/components/code
|
||||
npm install # 裝 hono + quickjs-emscripten-core + singlefile variant
|
||||
npm test # 12 測試須綠(部署前 gate)
|
||||
npx wrangler deploy # → arcrun-code,route code.arcrun.dev/*
|
||||
```
|
||||
|
||||
- Worker 名:`arcrun-code`;route:`code.arcrun.dev/*`(見 `wrangler.toml`)。
|
||||
- **無需注入任何 secret/env**:`code` 零件不碰網路/env,Worker 本身零 binding。
|
||||
- 冷啟:quickjs wasm 內嵌 base64、同步 instantiate;首個請求 instantiate 一次後 module 快取。
|
||||
|
||||
## 2. 註冊到 registry index
|
||||
|
||||
```bash
|
||||
REGISTRY_URL=https://registry.arcrun.dev bash registry/scripts/register-component.sh code
|
||||
```
|
||||
|
||||
- contract 已過 zod schema 驗證(`stability: floating`、`category: logic`、
|
||||
`io_model: stdin_stdout_json`、`gherkin_tests` ≥2)。
|
||||
- `sandbox_limits` / `config_example` 為 yaml 內文件欄位,registry index 會 strip(不影響)。
|
||||
|
||||
## 3. 冒煙驗證(部署後)
|
||||
|
||||
```bash
|
||||
# 基本
|
||||
curl -s https://code.arcrun.dev/ | jq # {ok:true, component:"code"}
|
||||
curl -s -X POST https://code.arcrun.dev/ -H 'content-type: application/json' \
|
||||
-d '{"code":"return {sum: input.a + input.b};","input":{"a":2,"b":40}}' | jq
|
||||
# → {"success":true,"data":{"sum":42}}
|
||||
# 隔離
|
||||
curl -s -X POST https://code.arcrun.dev/ -H 'content-type: application/json' \
|
||||
-d '{"code":"return typeof fetch;","input":{}}' | jq
|
||||
# → {"success":true,"data":"undefined"}
|
||||
```
|
||||
|
||||
## 4. 依賴的下游(Arcrun#8 workflow —— 另一分支)
|
||||
|
||||
`code` 零件是 `km_wiki_ingest_drain` workflow 的前置。workflow 在
|
||||
`feat/issue-8-mechanical-wiki-ingest` 分支,以 `component: code` 引用本零件(registry 解析),
|
||||
不需本零件檔案同分支。issue-8 的部署與 notes 寫入量見該分支 `DEPLOY.md` /
|
||||
本輪回報的「一次過閘清單」。
|
||||
@@ -3,7 +3,7 @@ display_name: "程式碼(沙箱 inline JS)"
|
||||
category: "logic"
|
||||
version: "v1"
|
||||
wasi_target: "preview1"
|
||||
stability: "experimental"
|
||||
stability: "floating"
|
||||
runtime_compat:
|
||||
- "cf-workers"
|
||||
- "workerd"
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
/**
|
||||
* arcrun `code` 零件 —— Worker host(骨架,尚未部署驗證)
|
||||
* arcrun `code` 零件 —— Worker host(可部署)
|
||||
*
|
||||
* POST / → { code, input, limits? } → QuickJS-wasm 沙箱 → { success, data } | { success:false, error, error_type }
|
||||
* POST / → { code, input?, limits? }
|
||||
* → QuickJS-wasm 沙箱(./sandbox.mjs 的 runCode)
|
||||
* → { success:true, data } | { success:false, error, error_type }
|
||||
*
|
||||
* 與其他 logic 零件的差異:
|
||||
* - 其他零件 = 一顆「arcrun 自建 TinyGo→wasm」,靜態 bundle 進 Worker([[wasm_modules]]),
|
||||
* 跑在 component-worker-template 的 WASI-preview1 shim 上。
|
||||
* - `code` 零件 = 載入「QuickJS(JS 直譯器)編成的 wasm」,把 user 的 inline JS 當「資料」
|
||||
* 餵進去跑。QuickJS 的 wasm 由 quickjs-emscripten 提供(有 Cloudflare Workers 相容 variant)。
|
||||
* 封裝=A(quickjs-emscripten singlefile variant):wasm 內嵌為 base64、同步載入,
|
||||
* bundler 友善、無需 [[wasm_modules]] 綁定、無需 nodejs_compat(sandbox 用 TextEncoder 計 bytes)。
|
||||
*
|
||||
* ⚠️ 此檔為設計骨架。PoC 的可執行 + 受測實作在 ./sandbox.mjs(Node/vitest 綠燈)。
|
||||
* live 化差異見 DESIGN.md「② 生產路徑」與「curated builtins(sha256)」。
|
||||
* 與其他 logic 零件不同:`code` 是自足 Worker(自帶 index.ts + sandbox.mjs + quickjs variant),
|
||||
* 不走 component-worker-template 的 TinyGo-wasm bundling 流程。部署見 DEPLOY.md。
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { getQuickJS } from 'quickjs-emscripten';
|
||||
// @ts-expect-error —— sandbox.mjs 為 runtime-agnostic JS 核心(Node 測試與 Worker 共用同一份)
|
||||
import { runCode } from './sandbox.mjs';
|
||||
|
||||
const app = new Hono();
|
||||
app.use('*', cors());
|
||||
|
||||
app.get('/', (c) => c.json({ ok: true, component: 'code' }));
|
||||
|
||||
app.post('/', async (c) => {
|
||||
@@ -28,20 +29,22 @@ app.post('/', async (c) => {
|
||||
} catch {
|
||||
return c.json({ success: false, error: 'request body must be JSON', error_type: 'ContractError' }, 400);
|
||||
}
|
||||
// runCode 語義與簽章同 ./sandbox.mjs(PoC 已受測)。生產版把 sha256 curated builtin
|
||||
// 換成「純 JS SHA-256 prelude」(無 host call、Node/Worker 皆決定性),見 DESIGN.md。
|
||||
const result = await runCodeInWorker(String(body.code ?? ''), body.input, body.limits);
|
||||
return c.json(result);
|
||||
|
||||
if (typeof body.code !== 'string') {
|
||||
return c.json({ success: false, error: 'code (string) is required', error_type: 'ContractError' }, 400);
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await runCode(body.code, body.input, { limits: body.limits });
|
||||
// sandbox 永遠回結構化 envelope;success=false 仍以 200 帶 error_type 回(零件語義層錯,非 HTTP 錯)
|
||||
return c.json(result);
|
||||
} catch (e) {
|
||||
// 理論上 runCode 自己 try/catch;這層是最後保險,Worker 絕不掛。
|
||||
return c.json(
|
||||
{ success: false, error: e instanceof Error ? e.message : String(e), error_type: 'SandboxError' },
|
||||
500,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
||||
// 生產實作(待補:把 sandbox.mjs 的 runCode 移植成 Worker 版)。
|
||||
declare function runCodeInWorker(
|
||||
code: string,
|
||||
input: unknown,
|
||||
limits?: Record<string, number>,
|
||||
): Promise<unknown>;
|
||||
|
||||
// 保留 import 以標示相依(bundler 不 tree-shake 掉):
|
||||
void getQuickJS;
|
||||
|
||||
+16
@@ -8,6 +8,7 @@
|
||||
"name": "arcrun-component-code",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@jitl/quickjs-singlefile-mjs-release-sync": "^0.32.0",
|
||||
"quickjs-emscripten": "^0.31.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -462,6 +463,21 @@
|
||||
"integrity": "sha512-1yrgvXlmXH2oNj3eFTrkwacGJbmM0crwipA3ohCrjv52gBeDaD7PsTvFYinlAnqU8iPME3LGP437yk05a2oejw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jitl/quickjs-singlefile-mjs-release-sync": {
|
||||
"version": "0.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitl/quickjs-singlefile-mjs-release-sync/-/quickjs-singlefile-mjs-release-sync-0.32.0.tgz",
|
||||
"integrity": "sha512-h9g2Dri6WxKNjX6a1+sFAafOVxj1n5YIA1GAzMoSdIqK1M8cG07tz3GxKVC3Gbdutz14uQ3oc3Q2lll8otInhA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jitl/quickjs-ffi-types": "0.32.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jitl/quickjs-singlefile-mjs-release-sync/node_modules/@jitl/quickjs-ffi-types": {
|
||||
"version": "0.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitl/quickjs-ffi-types/-/quickjs-ffi-types-0.32.0.tgz",
|
||||
"integrity": "sha512-v9T+GQpmk43VDJ7d72sf0Nexhk+ArvtUihW27dy7lqAl0zBObFKtSBBIm5RBjwIhE8VwsPPm9PNuvPvNqLWUEg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jitl/quickjs-wasmfile-debug-asyncify": {
|
||||
"version": "0.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@jitl/quickjs-wasmfile-debug-asyncify/-/quickjs-wasmfile-debug-asyncify-0.31.0.tgz",
|
||||
|
||||
@@ -3,14 +3,18 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "arcrun code 零件 —— sandbox inline JS(QuickJS-wasm)",
|
||||
"description": "arcrun code 零件 —— sandbox inline JS(QuickJS-wasm, Workers-ready)",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"test": "vitest run --config vitest.config.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"quickjs-emscripten": "^0.31.0"
|
||||
"hono": "^4.7.0",
|
||||
"quickjs-emscripten-core": "^0.31.0",
|
||||
"@jitl/quickjs-singlefile-mjs-release-sync": "^0.32.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitest": "^3.1.0"
|
||||
"vitest": "^3.1.0",
|
||||
"wrangler": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// arcrun `code` 零件 —— 沙箱核心(PoC 參考實作)
|
||||
// arcrun `code` 零件 —— 沙箱核心(runtime-agnostic:Node 與 CF Workers 共用同一份)
|
||||
// ---------------------------------------------------------------------------
|
||||
// 語義:n8n Code node 式。config 帶一段 inline user JS,stdin 帶 input JSON。
|
||||
// user code 只能:讀 `input`(已解析的 stdin JSON)、回傳一個 JSON-able 值。
|
||||
@@ -6,49 +6,123 @@
|
||||
//
|
||||
// 隔離機制:user JS 跑在 QuickJS(JS 直譯器)編成的 wasm sandbox 內。QuickJS
|
||||
// context 的 global 只有純 ECMAScript 內建(Object/Array/JSON/Math/Date/String…),
|
||||
// 沒有 fetch / process / require / globalThis.env / WebAssembly / 任何 host binding
|
||||
// —— 「無 ambient 能力(no ambient capability by construction)」。要給的能力,
|
||||
// 只能由 host 明確、逐一注入為 curated builtin(本檔 = 一個純函式 sha256)。
|
||||
// 沒有 fetch / process / require / globalThis.env / WebAssembly / 任何 host binding。
|
||||
// 要給的能力,只能由 host 明確、逐一注入 —— 目前唯一 curated builtin = 純函式 `sha256`,
|
||||
// 且以「純 JS 演算法字串 prelude」注入(不呼叫 host、不用 async Web Crypto,Node/Worker 皆決定性)。
|
||||
//
|
||||
// 對齊 arcrun 契約:io_model=stdin_stdout_json、no_network_syscall、
|
||||
// no_filesystem_syscall。與其他零件唯一差別=直譯器是 QuickJS-wasm 而非 TinyGo-wasm。
|
||||
// 封裝方式:quickjs-emscripten「singlefile」variant(wasm 內嵌為 base64、同步載入),
|
||||
// 這是 Cloudflare Workers 相容的 loading 路徑(不靠 fetch/fs 取 .wasm)。
|
||||
//
|
||||
// 對齊 arcrun 契約:io_model=stdin_stdout_json、no_network_syscall、no_filesystem_syscall。
|
||||
|
||||
import { getQuickJS } from 'quickjs-emscripten';
|
||||
import { createHash } from 'node:crypto';
|
||||
import variant from '@jitl/quickjs-singlefile-mjs-release-sync';
|
||||
import { newQuickJSWASMModuleFromVariant } from 'quickjs-emscripten-core';
|
||||
|
||||
export const DEFAULT_LIMITS = {
|
||||
timeout_ms: 1000, // 執行牆鐘上限(interrupt handler 逐指令檢查 deadline)
|
||||
timeout_ms: 1000, // 執行牆鐘上限(interrupt handler 逐指令檢查 deadline)
|
||||
memory_bytes: 16 * 1024 * 1024, // QuickJS runtime 記憶體硬上限
|
||||
max_stack_bytes: 512 * 1024, // 遞迴/深堆疊上限
|
||||
max_output_bytes: 1024 * 1024, // stdout JSON 大小上限(防跑飛)
|
||||
max_code_bytes: 256 * 1024, // user code 本身大小上限
|
||||
};
|
||||
|
||||
// curated 安全 builtin:純、決定性、零 ambient 能力(不能碰網路/檔案/secret)。
|
||||
// PoC 用 Node crypto 實作 sha256;live(Worker)要換成 Web Crypto 的
|
||||
// crypto.subtle.digest('SHA-256', …)(見 DESIGN.md「curated builtins」)。
|
||||
function hostSha256(s) {
|
||||
return createHash('sha256').update(s, 'utf8').digest('hex');
|
||||
// --- curated builtin:純 JS SHA-256(UTF-8 → hex)。無 host call、無 async。 ---
|
||||
// 以字串 prelude 注入沙箱,成為沙箱內一般函式 `sha256(str)`。與 Node crypto sha256 等價
|
||||
// (測試 ⑤ 對 card 全文比對 content_hash 逐字相同)。
|
||||
const SHA256_PRELUDE = `
|
||||
function sha256(ascii) {
|
||||
function rr(n, x) { return (x >>> n) | (x << (32 - n)); }
|
||||
var mathPow = Math.pow, maxWord = mathPow(2, 32), result = '';
|
||||
var words = [], asciiBitLength;
|
||||
var utf8 = [];
|
||||
for (var ci = 0; ci < ascii.length; ci++) {
|
||||
var code = ascii.charCodeAt(ci);
|
||||
if (code < 0x80) utf8.push(code);
|
||||
else if (code < 0x800) { utf8.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f)); }
|
||||
else if (code < 0xd800 || code >= 0xe000) { utf8.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f)); }
|
||||
else {
|
||||
ci++;
|
||||
code = 0x10000 + (((code & 0x3ff) << 10) | (ascii.charCodeAt(ci) & 0x3ff));
|
||||
utf8.push(0xf0 | (code >> 18), 0x80 | ((code >> 12) & 0x3f), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
|
||||
}
|
||||
}
|
||||
asciiBitLength = utf8.length * 8;
|
||||
var hash = sha256.h = sha256.h || [];
|
||||
var k = sha256.k = sha256.k || [];
|
||||
var primeCounter = k.length;
|
||||
var isComposite = {};
|
||||
for (var candidate = 2; primeCounter < 64; candidate++) {
|
||||
if (!isComposite[candidate]) {
|
||||
for (var i2 = 0; i2 < 313; i2 += candidate) isComposite[i2] = candidate;
|
||||
hash[primeCounter] = (mathPow(candidate, 0.5) * maxWord) | 0;
|
||||
k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0;
|
||||
}
|
||||
}
|
||||
hash = hash.slice(0, 8);
|
||||
var bytes = utf8.slice();
|
||||
bytes.push(0x80);
|
||||
while (bytes.length % 64 - 56) bytes.push(0x00);
|
||||
for (var b = 0; b < bytes.length; b++) {
|
||||
words[b >> 2] |= bytes[b] << ((3 - b) % 4) * 8;
|
||||
}
|
||||
words[words.length] = (asciiBitLength / maxWord) | 0;
|
||||
words[words.length] = asciiBitLength;
|
||||
for (var j = 0; j < words.length;) {
|
||||
var w = words.slice(j, j += 16);
|
||||
var oldHash = hash;
|
||||
hash = hash.slice(0, 8);
|
||||
for (var i = 0; i < 64; i++) {
|
||||
var w15 = w[i - 15], w2 = w[i - 2];
|
||||
var a = hash[0], e = hash[4];
|
||||
var temp1 = hash[7]
|
||||
+ (rr(6, e) ^ rr(11, e) ^ rr(25, e))
|
||||
+ ((e & hash[5]) ^ ((~e) & hash[6]))
|
||||
+ k[i]
|
||||
+ (w[i] = (i < 16) ? w[i] : (
|
||||
w[i - 16]
|
||||
+ (rr(7, w15) ^ rr(18, w15) ^ (w15 >>> 3))
|
||||
+ w[i - 7]
|
||||
+ (rr(17, w2) ^ rr(19, w2) ^ (w2 >>> 10))
|
||||
) | 0);
|
||||
var temp2 = (rr(2, a) ^ rr(13, a) ^ rr(22, a))
|
||||
+ ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2]));
|
||||
hash = [(temp1 + temp2) | 0].concat(hash);
|
||||
hash[4] = (hash[4] + temp1) | 0;
|
||||
}
|
||||
for (var i = 0; i < 8; i++) hash[i] = (hash[i] + oldHash[i]) | 0;
|
||||
}
|
||||
for (var i = 0; i < 8; i++) {
|
||||
for (var j = 3; j + 1; j--) {
|
||||
var b2 = (hash[i] >> (j * 8)) & 255;
|
||||
result += ((b2 < 16) ? 0 : '') + b2.toString(16);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
`;
|
||||
|
||||
let _modulePromise = null;
|
||||
function getModule() {
|
||||
if (!_modulePromise) _modulePromise = newQuickJSWASMModuleFromVariant(variant);
|
||||
return _modulePromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在沙箱內跑一段 user code。
|
||||
* @param {string} code user 的 inline JS(函式體:可含宣告、以 `return` 回值)
|
||||
* @param {*} input 已解析的 stdin JSON(會以 `input` 綁進沙箱)
|
||||
* @param {object} [opts] { limits, builtins }
|
||||
* @param {object} [opts] { limits }
|
||||
* @returns {Promise<{success:true,data:*}|{success:false,error:string,error_type?:string}>}
|
||||
*/
|
||||
export async function runCode(code, input, opts = {}) {
|
||||
const limits = { ...DEFAULT_LIMITS, ...(opts.limits || {}) };
|
||||
|
||||
if (typeof code !== 'string') {
|
||||
return err('code must be a string', 'ContractError');
|
||||
}
|
||||
if (Buffer.byteLength(code, 'utf8') > limits.max_code_bytes) {
|
||||
if (typeof code !== 'string') return err('code must be a string', 'ContractError');
|
||||
if (byteLen(code) > limits.max_code_bytes) {
|
||||
return err(`code exceeds max_code_bytes (${limits.max_code_bytes})`, 'ResourceError');
|
||||
}
|
||||
|
||||
const QuickJS = await getQuickJS();
|
||||
const QuickJS = await getModule();
|
||||
const runtime = QuickJS.newRuntime();
|
||||
runtime.setMemoryLimit(limits.memory_bytes);
|
||||
runtime.setMaxStackSize(limits.max_stack_bytes);
|
||||
@@ -62,21 +136,12 @@ export async function runCode(code, input, opts = {}) {
|
||||
|
||||
const ctx = runtime.newContext();
|
||||
try {
|
||||
// 注入 curated builtin:sha256(單一純函式;其餘一律不給)
|
||||
const shaFn = ctx.newFunction('sha256', (argHandle) => {
|
||||
const s = ctx.getString(argHandle);
|
||||
return ctx.newString(hostSha256(s));
|
||||
});
|
||||
ctx.setProp(ctx.global, 'sha256', shaFn);
|
||||
shaFn.dispose();
|
||||
|
||||
// 綁 input:以 JSON 字串安全穿越邊界,沙箱內 JSON.parse(不共享物件圖)
|
||||
const inputJson = JSON.stringify(input === undefined ? null : input);
|
||||
|
||||
// 包裝:user code 當成函式體跑,回值 JSON.stringify 後交回 host。
|
||||
// `input` 為唯一綁定;`sha256` 為唯一注入 builtin。
|
||||
// 包裝:sha256 prelude + input 綁定 + user code 當函式體。回值 JSON.stringify 交回 host。
|
||||
const wrapped = `(() => {
|
||||
"use strict";
|
||||
${SHA256_PRELUDE}
|
||||
const input = JSON.parse(${JSON.stringify(inputJson)});
|
||||
const __run = (input) => { ${code}
|
||||
};
|
||||
@@ -99,7 +164,7 @@ export async function runCode(code, input, opts = {}) {
|
||||
const outJson = ctx.getString(evalResult.value);
|
||||
evalResult.value.dispose();
|
||||
|
||||
if (Buffer.byteLength(outJson, 'utf8') > limits.max_output_bytes) {
|
||||
if (byteLen(outJson) > limits.max_output_bytes) {
|
||||
return err(`output exceeds max_output_bytes (${limits.max_output_bytes})`, 'ResourceError');
|
||||
}
|
||||
return { success: true, data: JSON.parse(outJson) };
|
||||
@@ -114,6 +179,11 @@ export async function runCode(code, input, opts = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function byteLen(s) {
|
||||
if (typeof Buffer !== 'undefined') return Buffer.byteLength(s, 'utf8');
|
||||
return new TextEncoder().encode(s).length;
|
||||
}
|
||||
|
||||
function err(message, error_type) {
|
||||
return { success: false, error: message, error_type };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ES2022"],
|
||||
"types": ["@cloudflare/workers-types"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["index.ts", "sandbox.mjs"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
name = "arcrun-code"
|
||||
main = "index.ts"
|
||||
compatibility_date = "2025-02-19"
|
||||
workers_dev = true
|
||||
|
||||
[vars]
|
||||
COMPONENT_ID = "code"
|
||||
|
||||
[[routes]]
|
||||
pattern = "code.arcrun.dev/*"
|
||||
zone_name = "arcrun.dev"
|
||||
Reference in New Issue
Block a user