From fe6341acbfd4eaf09b9925824e546c24e5737175 Mon Sep 17 00:00:00 2001 From: KimlikDAO-bot Date: Mon, 6 Jan 2025 20:17:07 -0800 Subject: [PATCH] =?UTF-8?q?=E2=9B=85=EF=B8=8F=20Add=20improved=20CF=20inst?= =?UTF-8?q?rumentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 18 +++++- .gitignore | 1 + kastro/cloudflare/api.d.js | 18 ++++++ kastro/cloudflare/api.js | 18 ++++++ kastro/cloudflare/cloudflare.d.js | 6 ++ kastro/cloudflare/crate.js | 65 ------------------- kastro/cloudflare/kvs.js | 0 kastro/cloudflare/test/workers.test.js | 58 +++++++++++++++++ kastro/cloudflare/workers.js | 89 ++++++++++++++++++++++++++ kastro/compiler/worker.js | 30 --------- kdjs/compile.js | 1 + kdjs/externs/node/process.d.js | 8 +++ package.json | 4 +- testing/runner.js | 5 +- 14 files changed, 222 insertions(+), 99 deletions(-) create mode 100644 kastro/cloudflare/api.d.js create mode 100644 kastro/cloudflare/api.js create mode 100644 kastro/cloudflare/cloudflare.d.js create mode 100644 kastro/cloudflare/kvs.js create mode 100644 kastro/cloudflare/test/workers.test.js create mode 100644 kastro/cloudflare/workers.js delete mode 100644 kastro/compiler/worker.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ee1668..436ba52 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,6 +10,9 @@ jobs: bun_unit_tests: name: bun unit tests runs-on: ubuntu-latest + env: + CF_TESTING_ACCOUNT_ID: ${{ secrets.CF_TESTING_ACCOUNT_ID }} + CF_TESTING_API_TOKEN: ${{ secrets.CF_TESTING_API_TOKEN }} steps: - uses: actions/checkout@v3 - uses: oven-sh/setup-bun@v2 @@ -37,6 +40,19 @@ jobs: bun i bun run test did + kdjs_unit_tests_kastro: + name: kdjs unit tests, kastro + runs-on: ubuntu-latest + env: + CF_TESTING_ACCOUNT_ID: ${{ secrets.CF_TESTING_ACCOUNT_ID }} + CF_TESTING_API_TOKEN: ${{ secrets.CF_TESTING_API_TOKEN }} + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v2 + - run: | + bun i + bun run test kastro + kdjs_unit_tests_others: name: kdjs unit tests, others runs-on: ubuntu-latest @@ -45,7 +61,7 @@ jobs: - uses: oven-sh/setup-bun@v2 - run: | bun i - bun run test --filter did crypto + bun run test --filter did crypto kastro compile_kdjs_with_kdjs: name: Compile `kdjs` with `kdjs` diff --git a/.gitignore b/.gitignore index 9ea4aff..364076e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_Store .kdjs_isolate +.secrets.js .vscode *.out.js build diff --git a/kastro/cloudflare/api.d.js b/kastro/cloudflare/api.d.js new file mode 100644 index 0000000..9857c5d --- /dev/null +++ b/kastro/cloudflare/api.d.js @@ -0,0 +1,18 @@ +/** @externs */ + +import cloudflare from "./cloudflare.d"; + +/** + * @typedef {{ + * accountId: string, + * token: string + * }} + */ +cloudflare.Auth; + +/** + * @typedef {{ + * success: boolean + * }} + */ +cloudflare.Response; diff --git a/kastro/cloudflare/api.js b/kastro/cloudflare/api.js new file mode 100644 index 0000000..c5d33e7 --- /dev/null +++ b/kastro/cloudflare/api.js @@ -0,0 +1,18 @@ +import "./api.d"; + +/** + * The keys here are deliberately different from `cloudflare.Auth` + * to prevent kdjs from defensively treating Auth like an extern. + * + * @struct + * @typedef {{ + * account: string, + * apiToken: string + * }} + */ +const Auth = {}; + +/** @const {string} */ +const ApiV4 = "https://api.cloudflare.com/client/v4"; + +export { Auth, ApiV4 }; diff --git a/kastro/cloudflare/cloudflare.d.js b/kastro/cloudflare/cloudflare.d.js new file mode 100644 index 0000000..89ed069 --- /dev/null +++ b/kastro/cloudflare/cloudflare.d.js @@ -0,0 +1,6 @@ +/** @externs */ + +/** @const */ +const cloudflare = {} + +export default cloudflare; diff --git a/kastro/cloudflare/crate.js b/kastro/cloudflare/crate.js index 474bd3a..9502ab7 100644 --- a/kastro/cloudflare/crate.js +++ b/kastro/cloudflare/crate.js @@ -1,19 +1,8 @@ import { CompressedMimes } from "../workers/mimes"; -/** @const {string} */ -const CloudflareV4 = "https://api.cloudflare.com/client/v4"; - /** @const {!Array} */ const Extensions = ['', '.br', '.gz']; -/** - * @typedef {{ - * accountId: string, - * token: string - * }} - */ -const Auth = {}; - /** * @param {!Array} assets * @return {!Set} @@ -57,58 +46,6 @@ const uploadAssets = async (auth, namespaceId, namedAssets) => { console.log("🌀 Uploading static files:", staticFiles); } -/** - * @param {Auth} auth - * @param {string} name - * @param {string} code - * @param {!Array<{ - * name: string, - * namespace_id: string - * }>=} kvBindings - * @return {!Promise<*>} - */ -const uploadWorker = (auth, name, code, kvBindings) => { - /** @const {string} */ - const yesterday = new Date(Date.now() - 86400000) - .toISOString().split('T')[0]; - /** @dict */ - const metadata = { - "main_module": "a.js", - "compatibility_date": yesterday - }; - if (kvBindings && kvBindings.length) { - for (const kv of kvBindings) - kv["type"] = "kv_namespace"; - metadata["bindings"] = kvBindings; - } - /** @const {!FormData} */ - const form = new FormData(); - form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" })); - form.append("a.js", new File([code], "a.js", { type: "application/javascript+module;charset=utf-8" })); - return fetch(`${CloudflareV4}/accounts/${auth.accountId}/workers/scripts/${name}`, { - method: "PUT", - headers: { "authorization": `Bearer ${auth.token}` }, - body: form - }).then((res) => res.json()); -}; - -/** - * @param {Auth} auth - * @param {string} name - * @param {string} url - */ -const bindWorker = (auth, name, url) => fetch(`${CloudflareV4}/accounts/${auth.accountId}/workers/domains`, { - method: "PUT", - headers: { - "authorization": `Bearer ${auth.token}`, - "content-type": "application/json" - }, - body: JSON.stringify({ - "environment": "production", - "hostname": url, - "service": name - }) -}).then((res) => res.json()); const deployCrate = (crateName) => import(crateName) .then((crate) => { @@ -117,6 +54,4 @@ const deployCrate = (crateName) => import(crateName) export { Auth, - bindWorker, - uploadWorker }; diff --git a/kastro/cloudflare/kvs.js b/kastro/cloudflare/kvs.js new file mode 100644 index 0000000..e69de29 diff --git a/kastro/cloudflare/test/workers.test.js b/kastro/cloudflare/test/workers.test.js new file mode 100644 index 0000000..98672e8 --- /dev/null +++ b/kastro/cloudflare/test/workers.test.js @@ -0,0 +1,58 @@ +import { expect, test } from "bun:test"; +import process from "node:process"; +import { bekle as wait } from "../../../util/promises"; +import { Auth } from "../api"; +import workers from "../workers"; + +/** + * @return {!Promise} + */ +const getAuth = () => { + /** @const {Auth} */ + const auth = { + account: process.env["CF_TESTING_ACCOUNT_ID"], + apiToken: process.env["CF_TESTING_API_TOKEN"], + }; + + const secrets = process.cwd() + "/.secrets.js"; + return auth.account + ? Promise.resolve(auth) + : import(secrets) + .then((mod) => /** @type {Auth} */({ + account: mod["CloudflareAuth"].accountId, + apiToken: mod["CloudflareAuth"].token, + })); +} + +test("upload, fetch and delete worker", async () => { + /** @const {Auth} */ + const auth = await getAuth(); + /** @const {string} */ + const name = `test-worker-${Math.floor(1000 + Math.random() * 9000)}`; + /** @const {string} */ + const code = `export default {fetch(){return new Response("${name}",{headers:{"content-type":"text/plain"}})}}`; + + const uploadResult = await workers.upload(auth, name, code); + expect(uploadResult.success).toBeTrue(); + + const workersDevResult = await workers.enableWorkersDev(auth, name); + expect(workersDevResult.success).toBeTrue(); + + const maxAttempts = 5; + let attempt = 0; + for (; attempt < maxAttempts; ++attempt) { + await wait(5000); + try { + const fetchResult = await fetch(`https://${name}.kimlikdao-testing.workers.dev`); + if (fetchResult.status == 200) { + const text = await fetchResult.text(); + expect(text).toBe(name); + break; + } + } catch (_) { } + } + await workers.delete(auth, name); + expect(attempt).toBeLessThan(maxAttempts); +}, { + timeout: 15_000 +}); diff --git a/kastro/cloudflare/workers.js b/kastro/cloudflare/workers.js new file mode 100644 index 0000000..2d9ffd8 --- /dev/null +++ b/kastro/cloudflare/workers.js @@ -0,0 +1,89 @@ +import { ApiV4, Auth } from "./api"; + +/** + * @param {Auth} auth + * @param {string} name + * @param {string} code + * @param {!Array<{ +* name: string, +* namespace_id: string +* }>=} kvBindings +* @return {!Promise} +*/ +const upload = (auth, name, code, kvBindings) => { + /** @const {string} */ + const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0]; + /** @dict */ + const metadata = { + "main_module": "a.js", + "compatibility_date": yesterday + }; + if (kvBindings && kvBindings.length) { + for (const kv of kvBindings) + kv["type"] = "kv_namespace"; + metadata["bindings"] = kvBindings; + } + /** @const {!FormData} */ + const form = new FormData(); + form.append("metadata", new Blob([JSON.stringify(metadata)], { type: "application/json" })); + form.append("a.js", new File([code], "a.js", { type: "application/javascript+module;charset=utf-8" })); + return fetch(`${ApiV4}/accounts/${auth.account}/workers/scripts/${name}`, { + method: "PUT", + headers: { "authorization": `Bearer ${auth.apiToken}` }, + body: form + }).then((res) => res.json()); +}; + +/** +* @param {Auth} auth +* @param {string} name +* @param {string} url +* @return {!Promise} +*/ +const bind = (auth, name, url) => fetch(`${ApiV4}/accounts/${auth.account}/workers/domains`, { + method: "PUT", + headers: { + "authorization": `Bearer ${auth.apiToken}`, + "content-type": "application/json" + }, + body: JSON.stringify({ + "environment": "production", + "hostname": url, + "service": name + }) +}).then((res) => res.json()); + +/** + * @param {Auth} auth + * @param {string} name + * @return {!Promise} + */ +const enableWorkersDev = (auth, name) => fetch(`${ApiV4}/accounts/${auth.account}/workers/services/${name}/environments/production/subdomain`, { + method: "POST", + headers: { + "authorization": `Bearer ${auth.apiToken}`, + "content-type": "application/json" + }, + body: JSON.stringify({ enabled: true }) +}).then(res => res.json()); + +const workers = { + upload, + bind, + /** + * Deletes a worker by name. + * + * @param {Auth} auth + * @param {string} name + * @return {!Promise} + */ + delete(auth, name) { + return fetch(`${ApiV4}/accounts/${auth.account}/workers/scripts/${name}`, { + method: "DELETE", + headers: { "authorization": `Bearer ${auth.apiToken}` } + }).then((res) => res.json()); + }, + enableWorkersDev, +}; + +export default workers; diff --git a/kastro/compiler/worker.js b/kastro/compiler/worker.js deleted file mode 100644 index 53ee974..0000000 --- a/kastro/compiler/worker.js +++ /dev/null @@ -1,30 +0,0 @@ -import { compile } from "../../kdjs/compile"; -import { ensureDotJs, selectDefines } from "./defines"; - -/** - * @param {string} dirName - * @param {{ - * entry: string, - * defines: !Array - * }} config - * @param {!Object=} env - * @return {!Promise} - */ -const compileWorker = (dirName, config, env) => { - /** @const {string} */ - const entry = ensureDotJs(`${dirName}/${config.entry}`); - const compileParams = { - entry, - output: `build/${entry}`, - }; - if (config.defines) - compileParams.define = selectDefines(entry.slice(0, -3), config.defines, env); - return compile(compileParams); -} - -export { - compileWorker -}; diff --git a/kdjs/compile.js b/kdjs/compile.js index be767a4..7f99de0 100644 --- a/kdjs/compile.js +++ b/kdjs/compile.js @@ -70,6 +70,7 @@ const compile = async (params, checkFreshFn) => { "module_resolution": "NODE", "dependency_mode": "PRUNE", "entry_point": /** @type {string} */(params["entry"]), + "chunk_output_type": "ES_MODULES" }; if (params["define"]) options["define"] = /** @type {(!Array|boolean|string)} */(params["define"]); diff --git a/kdjs/externs/node/process.d.js b/kdjs/externs/node/process.d.js index f497198..0e620c9 100644 --- a/kdjs/externs/node/process.d.js +++ b/kdjs/externs/node/process.d.js @@ -8,6 +8,11 @@ const process = {}; */ process.exit = (exitCode) => { } +/** + * @return {string} + */ +process.cwd = () => { }; + /** * @param {string} event * @param {function(*)} handler @@ -16,3 +21,6 @@ process.on = (event, handler) => { } /** @const {!Array} */ process.argv; + +/** @const {!Object} */ +process.env; diff --git a/package.json b/package.json index fc959e1..7793b6d 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ }, "devDependencies": { "@ethereumjs/evm": "^3.1.1", - "@noble/secp256k1": "^2.1.0", - "ethers": "^6.13.4", + "@noble/secp256k1": "^2.2.2", + "ethers": "^6.13.5", "mina-signer": "^3.0.7" }, "dependencies": { diff --git a/testing/runner.js b/testing/runner.js index eaddf8b..5f23894 100644 --- a/testing/runner.js +++ b/testing/runner.js @@ -43,7 +43,10 @@ const compileAndRunMatching = async (pattern, command, args) => { entry: f, output }).then(() => - runBN(() => spawn(command.split(" ").concat(output)).exited + runBN(() => spawn(command.split(" ").concat(output), { + stdout: "inherit", + stderr: "inherit" + }).exited .then((exitCode) => { const marker = exitCode == 0 ? `${Green}[OK]` : `${Red}[Fail]`; console.log(`${marker}${Clear}, ${exitCode}: ${output}`);