naro-cli 0.29.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1749,7 +1749,12 @@ var CAPABILITIES = [
1749
1749
  id: "function-get",
1750
1750
  domain: "functions",
1751
1751
  title: "\uD568\uC218 \uC0C1\uC138",
1752
- routes: ["GET projects/[projectId]/backend/functions/[name]"],
1752
+ // `deploy` is a name the rule allows; the batch deploy's static segment
1753
+ // answers that one path for it (functions/deploy/route.ts).
1754
+ routes: [
1755
+ "GET projects/[projectId]/backend/functions/[name]",
1756
+ "GET projects/[projectId]/backend/functions/deploy"
1757
+ ],
1753
1758
  ui: { kind: "built" },
1754
1759
  cli: { kind: "built", commands: ["naro functions versions"] },
1755
1760
  mcp: { kind: "built", tools: ["get_function"] }
@@ -1758,7 +1763,10 @@ var CAPABILITIES = [
1758
1763
  id: "function-update",
1759
1764
  domain: "functions",
1760
1765
  title: "\uD568\uC218 verify_jwt \uC124\uC815",
1761
- routes: ["PATCH projects/[projectId]/backend/functions/[name]"],
1766
+ routes: [
1767
+ "PATCH projects/[projectId]/backend/functions/[name]",
1768
+ "PATCH projects/[projectId]/backend/functions/deploy"
1769
+ ],
1762
1770
  ui: { kind: "built" },
1763
1771
  cli: { kind: "built", commands: ["naro functions update"] },
1764
1772
  mcp: { kind: "built", tools: ["update_function"] }
@@ -1767,7 +1775,10 @@ var CAPABILITIES = [
1767
1775
  id: "function-delete",
1768
1776
  domain: "functions",
1769
1777
  title: "\uD568\uC218 \uC0AD\uC81C",
1770
- routes: ["DELETE projects/[projectId]/backend/functions/[name]"],
1778
+ routes: [
1779
+ "DELETE projects/[projectId]/backend/functions/[name]",
1780
+ "DELETE projects/[projectId]/backend/functions/deploy"
1781
+ ],
1771
1782
  ui: { kind: "built" },
1772
1783
  cli: { kind: "built", commands: ["naro functions delete"] },
1773
1784
  mcp: { kind: "built", tools: ["delete_function"] }
@@ -1790,10 +1801,14 @@ var CAPABILITIES = [
1790
1801
  domain: "functions",
1791
1802
  title: "\uC800\uC7A5\uB41C \uBC84\uC804 \uBC30\uD3EC",
1792
1803
  routes: [
1793
- "POST projects/[projectId]/backend/functions/[name]/versions/[versionId]/deploy"
1804
+ "POST projects/[projectId]/backend/functions/[name]/versions/[versionId]/deploy",
1805
+ // Many functions' versions in one upload of the project's worker
1806
+ // (design 2026-11-08 G4): naro functions deploy with several names,
1807
+ // deploy_function with `names` (Task 8).
1808
+ "POST projects/[projectId]/backend/functions/deploy"
1794
1809
  ],
1795
1810
  ui: { kind: "built" },
1796
- // naro functions deploy가 올린 버전을 배포한다(client.functions.deploy).
1811
+ // naro functions deploy가 올린 버전을 배포한다(client.functions.deploy, 여럿이면 deployMany).
1797
1812
  cli: { kind: "built" },
1798
1813
  // deploy_function이 naro-cli의 deployFunctions로 올린 버전을 배포한다.
1799
1814
  mcp: { kind: "built" }
@@ -2516,7 +2531,7 @@ var DEPLOYMENT_ERROR_CODES = [
2516
2531
  "USAGE_LIMIT_REACHED",
2517
2532
  /** Hobby: another limit (requests, D1, Storage, Functions, Realtime) is reached, so new builds wait (spec 2026-11-07-hobby-any-limit-blocks-build). */
2518
2533
  "BUILDS_PAUSED",
2519
- /** A hobby team has its 3 projects; no project can be added. */
2534
+ /** A hobby team has its 20 projects; no project can be added. */
2520
2535
  "PROJECT_LIMIT_REACHED",
2521
2536
  /** A hobby team has its 1 member; no member or invite can be added. */
2522
2537
  "MEMBER_LIMIT_REACHED",
@@ -2528,6 +2543,8 @@ var DEPLOYMENT_ERROR_CODES = [
2528
2543
  "PLAN_DOMAINS_NOT_INCLUDED",
2529
2544
  /** A hobby project has its 1 custom domain (with its www form); no other can be added. */
2530
2545
  "PLAN_DOMAIN_LIMIT_REACHED",
2546
+ /** A hobby team has its 5 projects with a database; no other project can turn its database or backend on. */
2547
+ "BACKEND_LIMIT_REACHED",
2531
2548
  /** Named environments (spec 2026-10-18-named-environments §15): the name is not lowercase letters, digits and single hyphens, or is over 20 characters. */
2532
2549
  "ENVIRONMENT_NAME_INVALID",
2533
2550
  /** `production` and `preview` are reserved names. */
@@ -20887,7 +20904,7 @@ var LocalBuildExecutor = class {
20887
20904
  input.rootDirectory
20888
20905
  );
20889
20906
  await assertAppRootInsideCheckout(checkout, appRoot);
20890
- const stacks = await import("./stack-PDV3NPWC.js");
20907
+ const stacks = await import("./stack-DFLT7AVX.js");
20891
20908
  const stack = await (this.dependencies.detectStack ?? stacks.detectStack)(
20892
20909
  appRoot
20893
20910
  ).catch(() => null);
@@ -21332,7 +21349,16 @@ async function errorFromResponse(response, context) {
21332
21349
  }
21333
21350
 
21334
21351
  // ../../packages/sdk/src/session.ts
21335
- import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
21352
+ import { randomBytes } from "crypto";
21353
+ import {
21354
+ chmodSync,
21355
+ mkdirSync,
21356
+ readdirSync,
21357
+ readFileSync,
21358
+ renameSync,
21359
+ rmSync,
21360
+ writeFileSync
21361
+ } from "fs";
21336
21362
  import { homedir } from "os";
21337
21363
  import { join as join18 } from "path";
21338
21364
  function credentialsDir(home = homedir()) {
@@ -21342,10 +21368,18 @@ function credentialsFile(home = homedir()) {
21342
21368
  return join18(credentialsDir(home), "credentials.json");
21343
21369
  }
21344
21370
  function saveSession(session, home = homedir()) {
21345
- mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
21346
- writeFileSync(credentialsFile(home), JSON.stringify(session, null, 2), {
21347
- mode: 384
21348
- });
21371
+ const dir = credentialsDir(home);
21372
+ const file2 = credentialsFile(home);
21373
+ mkdirSync(dir, { recursive: true, mode: 448 });
21374
+ chmodSync(dir, 448);
21375
+ const tmp = `${file2}.${randomBytes(6).toString("hex")}.tmp`;
21376
+ try {
21377
+ writeFileSync(tmp, JSON.stringify(session, null, 2), { mode: 384 });
21378
+ renameSync(tmp, file2);
21379
+ } catch (error51) {
21380
+ rmSync(tmp, { force: true });
21381
+ throw error51;
21382
+ }
21349
21383
  }
21350
21384
  function loadStoredSession(home = homedir()) {
21351
21385
  try {
@@ -21357,7 +21391,19 @@ function loadStoredSession(home = homedir()) {
21357
21391
  }
21358
21392
  }
21359
21393
  function clearSession(home = homedir()) {
21394
+ const dir = credentialsDir(home);
21360
21395
  rmSync(credentialsFile(home), { force: true });
21396
+ let names;
21397
+ try {
21398
+ names = readdirSync(dir);
21399
+ } catch {
21400
+ return;
21401
+ }
21402
+ for (const name of names) {
21403
+ if (/^credentials\.json\.[0-9a-f]+\.tmp$/.test(name)) {
21404
+ rmSync(join18(dir, name), { force: true });
21405
+ }
21406
+ }
21361
21407
  }
21362
21408
  function isExpired(session, skewSeconds = 60) {
21363
21409
  return session.expires_at * 1e3 <= Date.now() + skewSeconds * 1e3;
@@ -21532,7 +21578,20 @@ async function startCallbackServer() {
21532
21578
  resolveCode = resolve16;
21533
21579
  rejectCode = reject;
21534
21580
  });
21581
+ let port = 0;
21582
+ let settled = false;
21535
21583
  const server = createServer2((req, res) => {
21584
+ const host = (req.headers.host ?? "").toLowerCase();
21585
+ if (host !== `localhost:${String(port)}` && host !== `127.0.0.1:${String(port)}`) {
21586
+ res.statusCode = 404;
21587
+ res.end("Not found");
21588
+ return;
21589
+ }
21590
+ if (settled) {
21591
+ res.statusCode = 410;
21592
+ res.end("Gone");
21593
+ return;
21594
+ }
21536
21595
  const url2 = new URL(req.url ?? "/", "http://localhost");
21537
21596
  if (url2.pathname !== "/callback") {
21538
21597
  res.statusCode = 404;
@@ -21543,6 +21602,7 @@ async function startCallbackServer() {
21543
21602
  const code = url2.searchParams.get("code");
21544
21603
  if (error51) {
21545
21604
  const description = url2.searchParams.get("error_description");
21605
+ settled = true;
21546
21606
  sendPage(res, 200, failurePage(description ?? error51));
21547
21607
  rejectCode(new Error(`GitHub authorization failed: ${error51}`));
21548
21608
  return;
@@ -21555,11 +21615,12 @@ async function startCallbackServer() {
21555
21615
  );
21556
21616
  return;
21557
21617
  }
21618
+ settled = true;
21558
21619
  sendPage(res, 200, successPage());
21559
21620
  resolveCode(code);
21560
21621
  });
21561
21622
  await new Promise((resolve16) => server.listen(0, "127.0.0.1", resolve16));
21562
- const port = server.address().port;
21623
+ port = server.address().port;
21563
21624
  return {
21564
21625
  port,
21565
21626
  waitForCode(timeoutMs) {
@@ -21765,10 +21826,15 @@ async function resolveAuth(options = {}) {
21765
21826
 
21766
21827
  // ../../packages/sdk/src/browser.ts
21767
21828
  import { spawn as spawn2 } from "child_process";
21829
+ function browserCommand(url2, platform = process.platform) {
21830
+ if (platform === "darwin") return { command: "open", args: [url2] };
21831
+ if (platform === "win32") {
21832
+ return { command: "rundll32", args: ["url.dll,FileProtocolHandler", url2] };
21833
+ }
21834
+ return { command: "xdg-open", args: [url2] };
21835
+ }
21768
21836
  function openBrowser(url2) {
21769
- const platform = process.platform;
21770
- const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
21771
- const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
21837
+ const { command, args } = browserCommand(url2);
21772
21838
  const child = spawn2(command, args, { stdio: "ignore", detached: true });
21773
21839
  child.on("error", () => {
21774
21840
  });
@@ -24257,6 +24323,28 @@ var FunctionsApi = class {
24257
24323
  );
24258
24324
  return body.function;
24259
24325
  }
24326
+ /**
24327
+ * Puts several functions' versions live in one upload of the project's
24328
+ * worker (design 2026-11-08 G4): 1 to 100 `{ name, versionId }`, each
24329
+ * function once. One refusal refuses them all and nothing goes live —
24330
+ * `deploy`'s refusals, `VALIDATION_FAILED` for the list itself (`Send 1 to
24331
+ * 100 versions.`, a name listed twice), and `FUNCTIONS_TOO_LARGE` (413:
24332
+ * the project's live bundles together past 64 MiB). The answer is each
24333
+ * function's new version, in the order asked. Two minutes, or
24334
+ * `options.timeoutMs`; a timeout here does not say whether they went
24335
+ * live — `list` does.
24336
+ */
24337
+ async deployMany(projectId, versions, options = {}) {
24338
+ return this.client.request(
24339
+ this.path(projectId, "/deploy"),
24340
+ {
24341
+ method: "POST",
24342
+ body: { versions },
24343
+ context: "Deploy functions",
24344
+ timeoutMs: options.timeoutMs ?? PROVISION_TIMEOUT_MS
24345
+ }
24346
+ );
24347
+ }
24260
24348
  /**
24261
24349
  * Puts a stored version live again: the one numbered `version`, which must
24262
24350
  * have been live before (`VERSION_NOT_FOUND` otherwise — put a version that
@@ -24524,7 +24612,7 @@ function serializeDotenv(vars) {
24524
24612
  }
24525
24613
 
24526
24614
  // ../../packages/sdk/src/env-scan.ts
24527
- import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
24615
+ import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync } from "fs";
24528
24616
  import { join as join20 } from "path";
24529
24617
  var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
24530
24618
  ".ts",
@@ -24573,7 +24661,7 @@ function collect(dir, out) {
24573
24661
  if (out.length >= MAX_FILES) return;
24574
24662
  let entries;
24575
24663
  try {
24576
- entries = readdirSync(dir, { withFileTypes: true });
24664
+ entries = readdirSync2(dir, { withFileTypes: true });
24577
24665
  } catch {
24578
24666
  return;
24579
24667
  }
@@ -33570,12 +33658,32 @@ function generateTypes(tables, options = {}) {
33570
33658
  // ../../packages/sdk/src/link.ts
33571
33659
  import {
33572
33660
  existsSync as existsSync4,
33661
+ lstatSync,
33573
33662
  mkdirSync as mkdirSync2,
33574
33663
  readFileSync as readFileSync5,
33575
33664
  rmSync as rmSync2,
33576
33665
  writeFileSync as writeFileSync2
33577
33666
  } from "fs";
33578
33667
  import { join as join22 } from "path";
33668
+ function isSymbolicLink(path) {
33669
+ try {
33670
+ return lstatSync(path).isSymbolicLink();
33671
+ } catch {
33672
+ return false;
33673
+ }
33674
+ }
33675
+ function refuseLink(path, shown, before) {
33676
+ if (isSymbolicLink(path))
33677
+ throw new NaroError(
33678
+ "VALIDATION_FAILED",
33679
+ `${shown} is a symbolic link. Remove it before ${before}.`
33680
+ );
33681
+ }
33682
+ function assertProjectLinkWritable(dir, before = "linking the project") {
33683
+ refuseLink(linkDir(dir), ".naro", before);
33684
+ refuseLink(linkFile(dir), ".naro/project.json", before);
33685
+ refuseLink(join22(linkDir(dir), "README.txt"), ".naro/README.txt", before);
33686
+ }
33579
33687
  function linkDir(dir) {
33580
33688
  return join22(dir, ".naro");
33581
33689
  }
@@ -33598,6 +33706,7 @@ function readProjectLink(dir) {
33598
33706
  }
33599
33707
  }
33600
33708
  function writeProjectLink(dir, link) {
33709
+ assertProjectLinkWritable(dir);
33601
33710
  mkdirSync2(linkDir(dir), { recursive: true });
33602
33711
  writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
33603
33712
  `);
@@ -33613,6 +33722,7 @@ function removeProjectLink(dir) {
33613
33722
  }
33614
33723
  function ensureGitignoreHasNaro(dir) {
33615
33724
  const gitignorePath = join22(dir, ".gitignore");
33725
+ if (isSymbolicLink(gitignorePath)) return false;
33616
33726
  const hasGitignore = existsSync4(gitignorePath);
33617
33727
  if (!hasGitignore && !existsSync4(join22(dir, ".git"))) return false;
33618
33728
  const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
@@ -33953,7 +34063,7 @@ async function repairLedger(client, projectId, key, status, ledgerIsEmpty) {
33953
34063
  }
33954
34064
 
33955
34065
  // ../../packages/sdk/src/migrations.ts
33956
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
34066
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
33957
34067
  import { resolve as resolve9 } from "path";
33958
34068
  function resolveMigrationsDir(input) {
33959
34069
  if (input.dir) return resolve9(input.baseDir, input.dir);
@@ -33973,7 +34083,7 @@ function sortMigrationNames(names) {
33973
34083
  function listMigrationFiles(dir) {
33974
34084
  if (!existsSync5(dir)) return [];
33975
34085
  return sortMigrationNames(
33976
- readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name)
34086
+ readdirSync3(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name)
33977
34087
  );
33978
34088
  }
33979
34089
  function nextMigrationFileName(existing, name) {
@@ -35416,8 +35526,6 @@ var BUNDLE_MESSAGES = {
35416
35526
  wasm: (file2) => `WebAssembly modules are not supported in functions yet: ${file2}.`,
35417
35527
  naroJsMissing: 'Cannot find "naro-js/functions". Install naro-js 0.7.0 or later: npm install naro-js@latest.',
35418
35528
  tooLarge: (name, size, inputs) => `Function ${name} bundles to ${bytes(size)} bytes; the limit is 67,108,864 (64 MiB). The biggest inputs: ${inputs}.`,
35419
- // Rounded up: a size over the limit never reads as the limit (10.0 MB).
35420
- tooLargeCompressed: (name, size) => `Function ${name} is ${(Math.ceil(size / 1048576 * 10) / 10).toFixed(1)} MB after compression; Cloudflare accepts 10 MB.`,
35421
35529
  otherBundle: (name, text3, location2) => `Function ${name} did not bundle: ${text3}${location2 ? ` (${location2})` : ""}`,
35422
35530
  noHandler: (name) => `Function ${name} did not register a handler. Call Deno.serve(handler) or export default { fetch }.`,
35423
35531
  failedToStart: (name, line) => `Function ${name} failed to start: ${line}`,
@@ -35427,12 +35535,11 @@ var BUNDLE_MESSAGES = {
35427
35535
  };
35428
35536
 
35429
35537
  // ../../packages/functions-bundler/src/discover.ts
35430
- import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
35538
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
35431
35539
  import { join as join27, relative as relative8, resolve as resolve12, sep as sep6 } from "path";
35432
35540
 
35433
35541
  // ../../packages/functions-bundler/src/limits.ts
35434
35542
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
35435
- var FUNCTION_COMPRESSED_BUNDLE_MAX_BYTES = 10485760;
35436
35543
  var ENTRY_FILES = [
35437
35544
  "index.ts",
35438
35545
  "index.js",
@@ -35459,7 +35566,7 @@ function functionsFolder(options) {
35459
35566
  function listFunctionNames(folder) {
35460
35567
  let entries;
35461
35568
  try {
35462
- entries = readdirSync3(folder.dir, { withFileTypes: true });
35569
+ entries = readdirSync4(folder.dir, { withFileTypes: true });
35463
35570
  } catch {
35464
35571
  if (!folder.explicit && existsSync7(join27(folder.root, "supabase", "functions")))
35465
35572
  throw new FunctionBundleError(
@@ -36192,25 +36299,18 @@ function denoImportsPlugin(options) {
36192
36299
  // ../../packages/functions-bundler/src/bundle.ts
36193
36300
  import { createHash as createHash4 } from "crypto";
36194
36301
  import { join as join30, relative as relative10, sep as sep9 } from "path";
36195
- import { gzipSync } from "zlib";
36196
36302
 
36197
36303
  // ../../packages/functions-runtime/dist/index.js
36198
36304
  var NARO_SHIM_MODULE = "naro-shim.js";
36199
36305
  var NARO_USER_MODULE = "user.js";
36200
36306
  var NARO_BOOT_MODULE = "naro-boot.js";
36201
- var NARO_ENTRY_SOURCE = '// src/naro-entry.ts\nimport "./naro-shim.js";\nimport * as user from "./user.js";\n\n// src/context.ts\nvar CONTEXT_HEADER = "x-naro-context";\nvar MAX_CONTEXT_LENGTH = 4096;\nvar BASE64URL = /^[A-Za-z0-9_-]+$/;\nvar TRACE_ID_RE = /^[A-Za-z0-9-]{1,64}$/;\nvar MAX_HOP = 1e3;\nvar ROLES = /* @__PURE__ */ new Set([\n "anon",\n "authenticated",\n "service"\n]);\nfunction fieldsOf(value) {\n if (typeof value !== "object" || value === null || Array.isArray(value))\n return null;\n return value;\n}\nfunction own(fields, key) {\n return Object.hasOwn(fields, key) ? fields[key] : void 0;\n}\nfunction nonEmptyText(value) {\n return typeof value === "string" && value !== "";\n}\nvar MAX_USER_ID_LENGTH = 256;\nfunction headerSafeId(value) {\n if (!nonEmptyText(value) || value.length > MAX_USER_ID_LENGTH) return false;\n if (value.trim() !== value) return false;\n for (let index = 0; index < value.length; index += 1) {\n const code = value.charCodeAt(index);\n if (code < 32 || code >= 127 && code <= 159) return false;\n if (code >= 55296 && code <= 57343) {\n const next = value.charCodeAt(index + 1);\n if (code >= 56320 || !(next >= 56320 && next <= 57343)) return false;\n index += 1;\n }\n }\n return true;\n}\nfunction userOf(value) {\n const fields = fieldsOf(value);\n if (fields === null) return null;\n const id = own(fields, "id");\n const email = own(fields, "email");\n const emailVerified = own(fields, "emailVerified");\n if (!headerSafeId(id)) return null;\n if (email !== null && typeof email !== "string") return null;\n if (typeof emailVerified !== "boolean") return null;\n return { id, email, emailVerified };\n}\nfunction contextOf(value, projectId, name) {\n const fields = fieldsOf(value);\n if (fields === null) return null;\n if (own(fields, "v") !== 1) return null;\n if (own(fields, "projectId") !== projectId) return null;\n if (own(fields, "function") !== name) return null;\n const traceId = own(fields, "traceId");\n if (typeof traceId !== "string" || !TRACE_ID_RE.test(traceId)) return null;\n const hop = own(fields, "hop");\n if (typeof hop !== "number" || !Number.isInteger(hop) || hop < 0 || hop > MAX_HOP)\n return null;\n const role = own(fields, "role");\n if (role !== null && !ROLES.has(role)) return null;\n const sentUser = own(fields, "user");\n let user2 = null;\n if (role === "authenticated") {\n user2 = userOf(sentUser);\n if (user2 === null) return null;\n } else if (sentUser !== null) return null;\n return {\n v: 1,\n projectId,\n function: name,\n role,\n user: user2,\n traceId,\n hop\n };\n}\nfunction bytesOf(value) {\n if (!BASE64URL.test(value) || value.length % 4 === 1) return null;\n const base64 = value.replace(/-/g, "+").replace(/_/g, "/");\n let binary;\n try {\n binary = atob(base64 + "=".repeat((4 - base64.length % 4) % 4));\n } catch {\n return null;\n }\n const again = btoa(binary).replace(/\\+/g, "-").replace(/\\//g, "_").replace(/=+$/, "");\n if (again !== value) return null;\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n}\nfunction decodeContext(value, bindings) {\n const projectId = bindings.NARO_PROJECT_ID;\n const name = bindings.NARO_FUNCTION_NAME;\n if (!nonEmptyText(projectId) || !nonEmptyText(name)) return null;\n if (typeof value !== "string" || value.length > MAX_CONTEXT_LENGTH)\n return null;\n const bytes = bytesOf(value);\n if (bytes === null) return null;\n let parsed;\n try {\n parsed = JSON.parse(\n new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes)\n );\n } catch {\n return null;\n }\n return contextOf(parsed, projectId, name);\n}\n\n// src/rejections.ts\nfunction loggedRejection(promise, name) {\n return Promise.resolve(promise).then(\n () => void 0,\n (error) => {\n console.error(`naro functions: ${name} waitUntil rejected`, error);\n }\n );\n}\n\n// src/respond.ts\nvar FUNCTION_EXPOSE_HEADERS = [\n "x-relay-error",\n "x-naro-trace-id",\n "retry-after",\n "x-retry-after"\n];\nvar WRAPPER_ERRORS = {\n FUNCTION_ERROR: "The function threw an error. See `naro functions logs {name}`.",\n FUNCTION_NO_HANDLER: "The function has no handler. Call Deno.serve(handler) or export default { fetch }.",\n FUNCTION_MISROUTED: "This function was reached without the Naro gateway."\n};\nfunction appendExposeHeaders(value) {\n const listed = (value ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "");\n const seen = new Set(listed.map((part) => part.toLowerCase()));\n for (const name of FUNCTION_EXPOSE_HEADERS)\n if (!seen.has(name)) listed.push(name);\n return listed.join(", ");\n}\nfunction wrapperError(code, name, traceId) {\n const headers = new Headers({\n "access-control-allow-origin": "*",\n "access-control-expose-headers": appendExposeHeaders(null),\n "cache-control": "no-store"\n });\n if (traceId !== null) headers.set("x-naro-trace-id", traceId);\n return Response.json(\n {\n data: null,\n error: {\n code,\n // A function, not a string: `$&` in a name stays as it is.\n message: WRAPPER_ERRORS[code].replace("{name}", () => name)\n }\n },\n { status: 500, headers }\n );\n}\nfunction withFunctionHeaders(response, traceId) {\n if (response.status === 101) return response;\n const out = new Response(response.body, response);\n const headers = out.headers;\n headers.delete("x-relay-error");\n if (!headers.has("access-control-allow-origin"))\n headers.set("access-control-allow-origin", "*");\n headers.set(\n "access-control-expose-headers",\n appendExposeHeaders(headers.get("access-control-expose-headers"))\n );\n headers.set("x-naro-trace-id", traceId);\n return out;\n}\n\n// src/runtime-state.ts\nvar RUNTIME_KEY = /* @__PURE__ */ Symbol.for("naro.functions.runtime");\nvar CONTEXT_KEY = /* @__PURE__ */ Symbol.for("naro.functions.context");\nvar SHIM_FIRST = "The Naro functions shim must be imported before the wrapper.";\nfunction globals() {\n return globalThis;\n}\nfunction runtimeState() {\n const state2 = globals()[RUNTIME_KEY];\n if (state2 === void 0) throw new Error(SHIM_FIRST);\n return state2;\n}\nfunction callContexts() {\n const contexts2 = globals()[CONTEXT_KEY];\n if (contexts2 === void 0) throw new Error(SHIM_FIRST);\n return contexts2;\n}\n\n// src/naro-entry.ts\nvar state = runtimeState();\nvar contexts = callContexts();\nasync function serveWithInfo(registered, request) {\n let complete;\n const info = {\n remoteAddr: {\n transport: "tcp",\n hostname: request.headers.get("cf-connecting-ip") ?? "0.0.0.0",\n port: 0\n },\n completed: new Promise((resolve) => {\n complete = resolve;\n })\n };\n try {\n return await registered(request, info);\n } finally {\n complete();\n }\n}\nfunction chooseHandler() {\n const registered = state.handler;\n if (registered !== null)\n return {\n handler: async (request) => serveWithInfo(registered, request),\n onError: state.onError\n };\n const exported = user.default;\n if (typeof exported === "object" && exported !== null) {\n const fetchHandler = exported.fetch;\n if (typeof fetchHandler === "function")\n return {\n handler: async (request, env, ctx) => fetchHandler.call(exported, request, env, ctx),\n onError: null\n };\n }\n if (typeof exported === "function") {\n const prototype = exported.prototype;\n if (typeof prototype === "object" && prototype !== null && typeof prototype.fetch === "function")\n return {\n handler: async (request, env, ctx) => new exported(ctx, env).fetch(request),\n onError: null\n };\n return {\n handler: async (request, env, ctx) => exported(request, env, ctx),\n onError: null\n };\n }\n return null;\n}\nfunction callerRequest(request, context) {\n const headers = new Headers();\n for (const [name, value] of request.headers)\n if (!name.startsWith("x-naro-")) headers.append(name, value);\n if (context.role !== null) headers.set("x-naro-role", context.role);\n if (context.user !== null) headers.set("x-naro-user-id", context.user.id);\n headers.set("x-naro-trace-id", context.traceId);\n return new Request(request, { headers });\n}\nfunction logWaitUntilRejections(ctx, name) {\n const waitUntil = ctx.waitUntil.bind(ctx);\n try {\n Object.defineProperty(ctx, "waitUntil", {\n configurable: true,\n writable: true,\n value: (promise) => waitUntil(loggedRejection(promise, name))\n });\n } catch {\n }\n}\nfunction textOf(value) {\n return typeof value === "string" ? value : "";\n}\nfunction responseOf(value, what) {\n if (value instanceof Response) return value;\n throw new TypeError(`${what} did not return a Response.`);\n}\nasync function answerOf(chosen, name, request, env, ctx) {\n try {\n return responseOf(await chosen.handler(request, env, ctx), "The handler");\n } catch (error) {\n if (chosen.onError === null) {\n console.error(`naro functions: ${name} threw`, error);\n return null;\n }\n try {\n return responseOf(await chosen.onError(error), "onError");\n } catch (failed) {\n console.error(`naro functions: ${name} threw`, error, failed);\n return null;\n }\n }\n}\nasync function answerCall(request, env, ctx, seen) {\n const name = textOf(env.NARO_FUNCTION_NAME);\n const context = decodeContext(request.headers.get(CONTEXT_HEADER), env);\n if (context === null) return wrapperError("FUNCTION_MISROUTED", name, null);\n let forwarded;\n try {\n forwarded = callerRequest(request, context);\n } catch (error) {\n console.error(`naro functions: ${name} misrouted`, error);\n return wrapperError("FUNCTION_MISROUTED", name, null);\n }\n seen.traceId = context.traceId;\n const call = {\n role: context.role,\n user: context.user,\n traceId: context.traceId\n };\n contexts.set(forwarded, call);\n const chosen = chooseHandler();\n if (chosen === null)\n return wrapperError("FUNCTION_NO_HANDLER", name, context.traceId);\n logWaitUntilRejections(ctx, name);\n const answer = await state.requests.run(\n { ctx, hop: context.hop, traceId: context.traceId, call },\n () => answerOf(chosen, name, forwarded, env, ctx)\n );\n if (answer === null)\n return wrapperError("FUNCTION_ERROR", name, context.traceId);\n try {\n return withFunctionHeaders(answer, context.traceId);\n } catch (error) {\n console.error(\n `naro functions: ${name} answered with a response that cannot be sent`,\n error\n );\n return wrapperError("FUNCTION_ERROR", name, context.traceId);\n }\n}\nvar naro_entry_default = {\n async fetch(request, env, ctx) {\n const seen = { traceId: null };\n let answer;\n try {\n answer = await answerCall(request, env, ctx, seen);\n } catch (error) {\n return state.lastResort(error, seen.traceId);\n }\n if (state.isResponse(answer)) return answer;\n return state.lastResort(\n new TypeError("The wrapper\'s answer is not a Response."),\n seen.traceId\n );\n }\n};\nexport {\n naro_entry_default as default\n};\n';
36202
- var NARO_SHIM_SOURCE = '// src/naro-shim.ts\nimport { AsyncLocalStorage } from "node:async_hooks";\nimport { env } from "cloudflare:workers";\n\n// src/hosts.ts\nfunction bareHost(host) {\n return host.toLowerCase().replace(/\\.+$/, "");\n}\nfunction isNaroHost(hostname, rootDomain2, naroUrl2) {\n const host = bareHost(hostname);\n if (host === "") return false;\n const root = rootDomain2.toLowerCase().replace(/^\\.+|\\.+$/g, "");\n if (root !== "" && host.endsWith(`.${root}`)) return true;\n if (naroUrl2 === "") return false;\n try {\n return bareHost(new URL(naroUrl2).hostname) === host;\n } catch {\n return false;\n }\n}\n\n// src/redact.ts\nvar MIN_REDACTED_LENGTH = 8;\nvar REDACTED = "[redacted]";\nfunction isSecretBinding(name) {\n return name === "NARO_SERVICE_KEY" || !name.startsWith("NARO_") && name !== "DB";\n}\nfunction secretValues(bindings2) {\n const values = /* @__PURE__ */ new Set();\n for (const [name, value] of Object.entries(bindings2)) {\n if (typeof value !== "string" || value.length < MIN_REDACTED_LENGTH)\n continue;\n if (!isSecretBinding(name)) continue;\n values.add(value);\n values.add(JSON.stringify(value).slice(1, -1));\n }\n return [...values].toSorted(\n (left, right) => right.length - left.length || (left < right ? -1 : left > right ? 1 : 0)\n );\n}\nfunction redactText(text, secrets2) {\n const spans = [];\n for (const secret of secrets2) {\n if (secret === "") continue;\n let last;\n for (let at = text.indexOf(secret); at !== -1; at = text.indexOf(secret, at + 1)) {\n if (last !== void 0 && at <= last[1]) last[1] = at + secret.length;\n else spans.push(last = [at, at + secret.length]);\n }\n }\n if (spans.length === 0) return text;\n const ordered = spans.toSorted((left, right) => left[0] - right[0]);\n let out = "";\n let kept = 0;\n let [start, end] = ordered[0];\n for (const [nextStart, nextEnd] of ordered) {\n if (nextStart <= end) {\n end = Math.max(end, nextEnd);\n continue;\n }\n out += text.slice(kept, start) + REDACTED;\n kept = end;\n start = nextStart;\n end = nextEnd;\n }\n return out + text.slice(kept, start) + REDACTED + text.slice(end);\n}\nfunction stringOf(value) {\n try {\n return String(value);\n } catch {\n return void 0;\n }\n}\nfunction withHead(stack, head) {\n if (stack === head || stack.startsWith(`${head}\n`)) return stack;\n const frames = stack.indexOf("\\n at ");\n return frames === -1 ? `${head}\n${stack}` : `${head}${stack.slice(frames)}`;\n}\nfunction redactError(error, secrets2) {\n const stack = typeof error.stack === "string" ? error.stack : `${error.name}: ${error.message}`;\n const head = stringOf(error);\n let json;\n try {\n json = JSON.stringify(error);\n } catch {\n json = void 0;\n }\n const cleanStack = redactText(stack, secrets2);\n const cleanHead = head === void 0 ? void 0 : redactText(head, secrets2);\n const cleanJson = json === void 0 ? void 0 : redactText(json, secrets2);\n if (cleanStack === stack && head !== void 0 && cleanHead === head && json !== void 0 && cleanJson === json)\n return error;\n const text = cleanHead === void 0 ? cleanStack : withHead(cleanStack, cleanHead);\n return cleanJson === void 0 || cleanJson === "{}" ? text : `${text}\n${cleanJson}`;\n}\nfunction isPlain(value) {\n if (Array.isArray(value)) return true;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\nfunction redactArg(arg, secrets2) {\n if (typeof arg === "string") return redactText(arg, secrets2);\n if (arg === null || typeof arg !== "object" && typeof arg !== "function")\n return arg;\n try {\n if (arg instanceof Error) return redactError(arg, secrets2);\n let json;\n try {\n json = JSON.stringify(arg);\n } catch {\n return redactText(String(arg), secrets2);\n }\n if (json !== void 0) {\n const clean2 = redactText(json, secrets2);\n if (clean2 !== json) return clean2;\n }\n if (isPlain(arg)) return arg;\n const text = stringOf(arg);\n if (text === void 0) return arg;\n const clean = redactText(text, secrets2);\n return clean === text ? arg : clean;\n } catch {\n return REDACTED;\n }\n}\nfunction redactArgs(args, secrets2) {\n if (secrets2.length === 0) return [...args];\n return args.map((arg) => redactArg(arg, secrets2));\n}\n\n// src/rejections.ts\nfunction loggedRejection(promise, name) {\n return Promise.resolve(promise).then(\n () => void 0,\n (error) => {\n console.error(`naro functions: ${name} waitUntil rejected`, error);\n }\n );\n}\n\n// src/respond.ts\nvar FUNCTION_EXPOSE_HEADERS = [\n "x-relay-error",\n "x-naro-trace-id",\n "retry-after",\n "x-retry-after"\n];\nvar WRAPPER_ERRORS = {\n FUNCTION_ERROR: "The function threw an error. See `naro functions logs {name}`.",\n FUNCTION_NO_HANDLER: "The function has no handler. Call Deno.serve(handler) or export default { fetch }.",\n FUNCTION_MISROUTED: "This function was reached without the Naro gateway."\n};\nfunction appendExposeHeaders(value) {\n const listed = (value ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "");\n const seen = new Set(listed.map((part) => part.toLowerCase()));\n for (const name of FUNCTION_EXPOSE_HEADERS)\n if (!seen.has(name)) listed.push(name);\n return listed.join(", ");\n}\n\n// src/runtime-state.ts\nvar RUNTIME_KEY = /* @__PURE__ */ Symbol.for("naro.functions.runtime");\nvar CONTEXT_KEY = /* @__PURE__ */ Symbol.for("naro.functions.context");\n\n// src/naro-shim.ts\nvar bindings = env;\nvar globals = globalThis;\nvar functionName = typeof bindings.NARO_FUNCTION_NAME === "string" ? bindings.NARO_FUNCTION_NAME : "";\nvar secrets = secretValues(bindings);\nfor (const level of ["log", "info", "warn", "error", "debug"]) {\n const original = console[level].bind(console);\n console[level] = (...args) => {\n original(...redactArgs(args, secrets));\n };\n}\nvar logError = console.error;\nvar NativeResponse = Response;\nvar bareRecord = Object.create;\nvar failedBody = JSON.stringify({\n data: null,\n error: {\n code: "FUNCTION_ERROR",\n message: WRAPPER_ERRORS.FUNCTION_ERROR.replace(\n "{name}",\n () => functionName\n )\n }\n});\nvar exposedHeaders = appendExposeHeaders(null);\nfunction lastResort(error, traceId) {\n try {\n logError(`naro functions: ${functionName} threw`, error);\n } catch {\n }\n const headers = bareRecord(null);\n headers["content-type"] = "application/json";\n headers["access-control-allow-origin"] = "*";\n headers["access-control-expose-headers"] = exposedHeaders;\n headers["cache-control"] = "no-store";\n if (traceId !== null) headers["x-naro-trace-id"] = traceId;\n const init = bareRecord(null);\n init.status = 500;\n init.headers = headers;\n return new NativeResponse(failedBody, init);\n}\nvar reflectApply = Reflect.apply;\nvar responseStatus = Object.getOwnPropertyDescriptor(\n NativeResponse.prototype,\n "status"\n)?.get;\nfunction isResponse(value) {\n if (typeof value !== "object" || value === null) return false;\n try {\n if (responseStatus === void 0) return value instanceof NativeResponse;\n reflectApply(responseStatus, value, []);\n return true;\n } catch {\n return false;\n }\n}\nvar state = {\n handler: null,\n onError: null,\n requests: new AsyncLocalStorage(),\n lastResort,\n isResponse\n};\nglobals[RUNTIME_KEY] = state;\nglobals[CONTEXT_KEY] = /* @__PURE__ */ new WeakMap();\nvar rootDomain = typeof bindings.NARO_ROOT_DOMAIN === "string" ? bindings.NARO_ROOT_DOMAIN : "";\nvar naroUrl = typeof bindings.NARO_URL === "string" ? bindings.NARO_URL : "";\nvar originalFetch = globalThis.fetch.bind(globalThis);\nasync function naroFetch(input, init) {\n const store = state.requests.getStore();\n if (store === void 0) return originalFetch(input, init);\n let host;\n try {\n host = new URL(input instanceof Request ? input.url : String(input)).hostname;\n } catch {\n return originalFetch(input, init);\n }\n if (!isNaroHost(host, rootDomain, naroUrl)) return originalFetch(input, init);\n const request = new Request(input, init);\n const headers = new Headers(request.headers);\n headers.set("x-naro-fn-hop", String(store.hop + 1));\n headers.set("x-naro-trace-id", store.traceId);\n return originalFetch(new Request(request, { headers }));\n}\nglobalThis.fetch = naroFetch;\nvar addListener = EventTarget.prototype.addEventListener;\nvar removeListener = EventTarget.prototype.removeEventListener;\nvar standIns = /* @__PURE__ */ new WeakMap();\nfunction standInFor(socket, listener, store) {\n let listeners = standIns.get(socket);\n if (listeners === void 0)\n standIns.set(socket, listeners = /* @__PURE__ */ new WeakMap());\n let standIn = listeners.get(listener);\n if (standIn === void 0) {\n standIn = function(event) {\n state.requests.run(\n store,\n () => typeof listener === "function" ? Reflect.apply(listener, this, [event]) : listener.handleEvent(event)\n );\n };\n listeners.set(listener, standIn);\n }\n return standIn;\n}\nObject.defineProperties(WebSocket.prototype, {\n addEventListener: {\n configurable: true,\n writable: true,\n value(type, listener, ...rest) {\n const store = state.requests.getStore();\n const runs = store !== void 0 && listener !== null && (typeof listener === "function" || typeof listener === "object");\n return Reflect.apply(addListener, this, [\n type,\n runs ? standInFor(this, listener, store) : listener,\n ...rest\n ]);\n }\n },\n removeEventListener: {\n configurable: true,\n writable: true,\n value(type, listener, ...rest) {\n const standIn = typeof listener === "function" || typeof listener === "object" && listener !== null ? standIns.get(this)?.get(listener) : void 0;\n return Reflect.apply(removeListener, this, [\n type,\n standIn ?? listener,\n ...rest\n ]);\n }\n }\n});\nvar ON_EVENTS = ["onopen", "onmessage", "onclose", "onerror"];\nvar onHandlers = /* @__PURE__ */ new WeakMap();\nfunction nativeAccessor(target, name) {\n for (let at = target; at !== null; at = Object.getPrototypeOf(at)) {\n const descriptor = Object.getOwnPropertyDescriptor(at, name);\n if (descriptor === void 0) continue;\n return descriptor.get !== void 0 && descriptor.set !== void 0 ? { get: descriptor.get, set: descriptor.set } : void 0;\n }\n return void 0;\n}\nfor (const name of ON_EVENTS) {\n const native = nativeAccessor(WebSocket.prototype, name);\n Object.defineProperty(WebSocket.prototype, name, {\n configurable: true,\n enumerable: true,\n get() {\n const set = onHandlers.get(this)?.get(name);\n if (set === void 0)\n return native === void 0 ? void 0 : Reflect.apply(native.get, this, []);\n return state.requests.getStore() === void 0 ? set.standIn : set.handler;\n },\n set(handler) {\n const store = state.requests.getStore();\n const standIn = store !== void 0 && typeof handler === "function" ? standInFor(this, handler, store) : handler;\n let handlers = onHandlers.get(this);\n if (handlers === void 0) onHandlers.set(this, handlers = /* @__PURE__ */ new Map());\n handlers.set(name, { handler, standIn });\n if (native !== void 0) Reflect.apply(native.set, this, [standIn]);\n }\n });\n}\nfunction serve(first, second) {\n const options = typeof first === "function" ? {} : first ?? {};\n const handler = typeof first === "function" ? first : second ?? options.handler;\n if (typeof handler !== "function")\n throw new TypeError("Deno.serve needs a handler.");\n state.handler = handler;\n state.onError = typeof options.onError === "function" ? options.onError : null;\n return {\n finished: new Promise(() => {\n }),\n shutdown: () => Promise.resolve()\n };\n}\nfunction textBindings() {\n const out = {};\n for (const [name, value] of Object.entries(bindings))\n if (typeof value === "string") out[name] = value;\n return out;\n}\nvar build = Object.freeze({\n os: "linux",\n arch: "x86_64",\n target: "x86_64-unknown-linux-gnu",\n vendor: "unknown",\n env: "gnu"\n});\nvar version = Object.freeze({\n deno: "2.1.4",\n v8: "13.0.245.12",\n typescript: "5.6.2"\n});\nvar DENO_ERROR_NAMES = [\n "NotFound",\n "PermissionDenied",\n "ConnectionRefused",\n "ConnectionReset",\n "ConnectionAborted",\n "NotConnected",\n "AddrInUse",\n "AddrNotAvailable",\n "BrokenPipe",\n "AlreadyExists",\n "InvalidData",\n "TimedOut",\n "Interrupted",\n "WriteZero",\n "WouldBlock",\n "UnexpectedEof",\n "BadResource",\n "Http",\n "Busy",\n "NotSupported",\n "FilesystemLoop",\n "IsADirectory",\n "NetworkUnreachable",\n "NotADirectory",\n "NotCapable"\n];\nfunction denoError(name) {\n const made = { [name]: class extends Error {\n } }[name];\n Object.defineProperty(made.prototype, "name", {\n configurable: true,\n writable: true,\n value: name\n });\n return made;\n}\nvar errors = Object.freeze(\n Object.fromEntries(DENO_ERROR_NAMES.map((name) => [name, denoError(name)]))\n);\nvar deno = {\n serve,\n env: {\n get: (name) => {\n const value = bindings[name];\n return typeof value === "string" ? value : void 0;\n },\n has: (name) => typeof bindings[name] === "string",\n toObject: () => textBindings(),\n set: () => {\n throw new Error("Deno.env.set is not available on Naro Functions.");\n },\n delete: () => {\n throw new Error("Deno.env.delete is not available on Naro Functions.");\n }\n },\n build,\n version,\n errors\n};\nvar PROBES = /* @__PURE__ */ new Set(["then", "toJSON"]);\nglobals.Deno = new Proxy(deno, {\n get(target, property) {\n if (typeof property === "symbol" || PROBES.has(property)) return void 0;\n if (Object.hasOwn(target, property)) return target[property];\n return () => {\n throw new Error(`Deno.${property} is not available on Naro Functions.`);\n };\n }\n});\nglobals.EdgeRuntime = {\n waitUntil(promise) {\n const logged = loggedRejection(promise, functionName);\n state.requests.getStore()?.ctx.waitUntil(logged);\n return promise;\n }\n};\naddEventListener("unhandledrejection", (event) => {\n event.preventDefault();\n console.error(\n `naro functions: ${functionName} unhandled rejection`,\n event.reason\n );\n});\n';
36203
- var NARO_BOOT_SOURCE = '// src/naro-boot.ts\nimport "./naro-shim.js";\nimport * as user from "./user.js";\n\n// src/runtime-state.ts\nvar RUNTIME_KEY = /* @__PURE__ */ Symbol.for("naro.functions.runtime");\nvar SHIM_FIRST = "The Naro functions shim must be imported before the wrapper.";\nfunction globals() {\n return globalThis;\n}\nfunction runtimeState() {\n const state2 = globals()[RUNTIME_KEY];\n if (state2 === void 0) throw new Error(SHIM_FIRST);\n return state2;\n}\n\n// src/naro-boot.ts\nvar BOOT_PATH = "/__naro/boot";\nvar state = runtimeState();\nfunction hasHandler(exported) {\n if (state.handler !== null) return true;\n if (typeof exported === "function") return true;\n return typeof exported === "object" && exported !== null && typeof exported.fetch === "function";\n}\nvar naro_boot_default = {\n fetch(request) {\n if (new URL(request.url).pathname !== BOOT_PATH)\n return new Response("Not found", { status: 404 });\n return Response.json({ handler: hasHandler(user.default) });\n }\n};\nexport {\n naro_boot_default as default\n};\n';
36307
+ var NARO_SHIM_SOURCE = '// src/naro-shim.ts\nimport { AsyncLocalStorage } from "node:async_hooks";\nimport { env } from "cloudflare:workers";\n\n// src/hosts.ts\nfunction bareHost(host) {\n return host.toLowerCase().replace(/\\.+$/, "");\n}\nfunction isNaroHost(hostname, rootDomain2, naroUrl2) {\n const host = bareHost(hostname);\n if (host === "") return false;\n const root = rootDomain2.toLowerCase().replace(/^\\.+|\\.+$/g, "");\n if (root !== "" && host.endsWith(`.${root}`)) return true;\n if (naroUrl2 === "") return false;\n try {\n return bareHost(new URL(naroUrl2).hostname) === host;\n } catch {\n return false;\n }\n}\n\n// src/redact.ts\nvar MIN_REDACTED_LENGTH = 8;\nvar REDACTED = "[redacted]";\nfunction isSecretBinding(name) {\n return name === "NARO_SERVICE_KEY" || !name.startsWith("NARO_") && name !== "DB";\n}\nfunction secretValues(bindings2) {\n const values = /* @__PURE__ */ new Set();\n for (const [name, value] of Object.entries(bindings2)) {\n if (typeof value !== "string" || value.length < MIN_REDACTED_LENGTH)\n continue;\n if (!isSecretBinding(name)) continue;\n values.add(value);\n values.add(JSON.stringify(value).slice(1, -1));\n }\n return [...values].toSorted(\n (left, right) => right.length - left.length || (left < right ? -1 : left > right ? 1 : 0)\n );\n}\nfunction redactText(text, secrets2) {\n const spans = [];\n for (const secret of secrets2) {\n if (secret === "") continue;\n let last;\n for (let at = text.indexOf(secret); at !== -1; at = text.indexOf(secret, at + 1)) {\n if (last !== void 0 && at <= last[1]) last[1] = at + secret.length;\n else spans.push(last = [at, at + secret.length]);\n }\n }\n if (spans.length === 0) return text;\n const ordered = spans.toSorted((left, right) => left[0] - right[0]);\n let out = "";\n let kept = 0;\n let [start, end] = ordered[0];\n for (const [nextStart, nextEnd] of ordered) {\n if (nextStart <= end) {\n end = Math.max(end, nextEnd);\n continue;\n }\n out += text.slice(kept, start) + REDACTED;\n kept = end;\n start = nextStart;\n end = nextEnd;\n }\n return out + text.slice(kept, start) + REDACTED + text.slice(end);\n}\nfunction stringOf(value) {\n try {\n return String(value);\n } catch {\n return void 0;\n }\n}\nfunction withHead(stack, head) {\n if (stack === head || stack.startsWith(`${head}\n`)) return stack;\n const frames = stack.indexOf("\\n at ");\n return frames === -1 ? `${head}\n${stack}` : `${head}${stack.slice(frames)}`;\n}\nfunction redactError(error, secrets2) {\n const stack = typeof error.stack === "string" ? error.stack : `${error.name}: ${error.message}`;\n const head = stringOf(error);\n let json;\n try {\n json = JSON.stringify(error);\n } catch {\n json = void 0;\n }\n const cleanStack = redactText(stack, secrets2);\n const cleanHead = head === void 0 ? void 0 : redactText(head, secrets2);\n const cleanJson = json === void 0 ? void 0 : redactText(json, secrets2);\n if (cleanStack === stack && head !== void 0 && cleanHead === head && json !== void 0 && cleanJson === json)\n return error;\n const text = cleanHead === void 0 ? cleanStack : withHead(cleanStack, cleanHead);\n return cleanJson === void 0 || cleanJson === "{}" ? text : `${text}\n${cleanJson}`;\n}\nfunction isPlain(value) {\n if (Array.isArray(value)) return true;\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\nfunction redactArg(arg, secrets2) {\n if (typeof arg === "string") return redactText(arg, secrets2);\n if (arg === null || typeof arg !== "object" && typeof arg !== "function")\n return arg;\n try {\n if (arg instanceof Error) return redactError(arg, secrets2);\n let json;\n try {\n json = JSON.stringify(arg);\n } catch {\n return redactText(String(arg), secrets2);\n }\n if (json !== void 0) {\n const clean2 = redactText(json, secrets2);\n if (clean2 !== json) return clean2;\n }\n if (isPlain(arg)) return arg;\n const text = stringOf(arg);\n if (text === void 0) return arg;\n const clean = redactText(text, secrets2);\n return clean === text ? arg : clean;\n } catch {\n return REDACTED;\n }\n}\nfunction redactArgs(args, secrets2) {\n if (secrets2.length === 0) return [...args];\n return args.map((arg) => redactArg(arg, secrets2));\n}\n\n// src/rejections.ts\nfunction loggedRejection(promise, name) {\n return Promise.resolve(promise).then(\n () => void 0,\n (error) => {\n console.error(`naro functions: ${name} waitUntil rejected`, error);\n }\n );\n}\n\n// src/respond.ts\nvar FUNCTION_EXPOSE_HEADERS = [\n "x-relay-error",\n "x-naro-trace-id",\n "retry-after",\n "x-retry-after"\n];\nvar WRAPPER_ERRORS = {\n FUNCTION_ERROR: "The function threw an error. See `naro functions logs {name}`.",\n FUNCTION_NO_HANDLER: "The function has no handler. Call Deno.serve(handler) or export default { fetch }.",\n FUNCTION_MISROUTED: "This function was reached without the Naro gateway.",\n FUNCTION_NOT_FOUND: "Function {name} was not found."\n};\nfunction appendExposeHeaders(value) {\n const listed = (value ?? "").split(",").map((part) => part.trim()).filter((part) => part !== "");\n const seen = new Set(listed.map((part) => part.toLowerCase()));\n for (const name of FUNCTION_EXPOSE_HEADERS)\n if (!seen.has(name)) listed.push(name);\n return listed.join(", ");\n}\n\n// src/runtime-state.ts\nvar RUNTIME_KEY = /* @__PURE__ */ Symbol.for("naro.functions.runtime");\nvar CONTEXT_KEY = /* @__PURE__ */ Symbol.for("naro.functions.context");\n\n// src/naro-shim.ts\nvar bindings = env;\nvar globals = globalThis;\nfunction callName() {\n return state.requests.getStore()?.name ?? "";\n}\nvar secrets = secretValues(bindings);\nfor (const level of ["log", "info", "warn", "error", "debug"]) {\n const original = console[level].bind(console);\n console[level] = (...args) => {\n original(...redactArgs(args, secrets));\n };\n}\nvar logError = console.error;\nvar NativeResponse = Response;\nvar bareRecord = Object.create;\nvar NAME_SLOT = "\\0";\nvar [failedHead = "", failedTail = ""] = JSON.stringify({\n data: null,\n error: {\n code: "FUNCTION_ERROR",\n message: WRAPPER_ERRORS.FUNCTION_ERROR.replace("{name}", () => NAME_SLOT)\n }\n}).split(JSON.stringify(NAME_SLOT).slice(1, -1));\nvar exposedHeaders = appendExposeHeaders(null);\nfunction lastResort(error, traceId, name) {\n try {\n logError(`naro functions: ${name} threw`, error);\n } catch {\n }\n const headers = bareRecord(null);\n headers["content-type"] = "application/json";\n headers["access-control-allow-origin"] = "*";\n headers["access-control-expose-headers"] = exposedHeaders;\n headers["cache-control"] = "no-store";\n if (traceId !== null) headers["x-naro-trace-id"] = traceId;\n const init = bareRecord(null);\n init.status = 500;\n init.headers = headers;\n return new NativeResponse(\n failedHead + name + failedTail,\n init\n );\n}\nvar reflectApply = Reflect.apply;\nvar responseStatus = Object.getOwnPropertyDescriptor(\n NativeResponse.prototype,\n "status"\n)?.get;\nfunction isResponse(value) {\n if (typeof value !== "object" || value === null) return false;\n try {\n if (responseStatus === void 0) return value instanceof NativeResponse;\n reflectApply(responseStatus, value, []);\n return true;\n } catch {\n return false;\n }\n}\nvar state = {\n router: null,\n registered: null,\n requests: new AsyncLocalStorage(),\n lastResort,\n isResponse\n};\nglobals[RUNTIME_KEY] = state;\nglobals[CONTEXT_KEY] = /* @__PURE__ */ new WeakMap();\nvar rootDomain = typeof bindings.NARO_ROOT_DOMAIN === "string" ? bindings.NARO_ROOT_DOMAIN : "";\nvar naroUrl = typeof bindings.NARO_URL === "string" ? bindings.NARO_URL : "";\nvar originalFetch = globalThis.fetch.bind(globalThis);\nasync function naroFetch(input, init) {\n const store = state.requests.getStore();\n if (store === void 0) return originalFetch(input, init);\n let host;\n try {\n host = new URL(input instanceof Request ? input.url : String(input)).hostname;\n } catch {\n return originalFetch(input, init);\n }\n if (!isNaroHost(host, rootDomain, naroUrl)) return originalFetch(input, init);\n const request = new Request(input, init);\n const headers = new Headers(request.headers);\n headers.set("x-naro-fn-hop", String(store.hop + 1));\n headers.set("x-naro-trace-id", store.traceId);\n return originalFetch(new Request(request, { headers }));\n}\nglobalThis.fetch = naroFetch;\nvar addListener = EventTarget.prototype.addEventListener;\nvar removeListener = EventTarget.prototype.removeEventListener;\nvar standIns = /* @__PURE__ */ new WeakMap();\nfunction standInFor(socket, listener, store) {\n let listeners = standIns.get(socket);\n if (listeners === void 0)\n standIns.set(socket, listeners = /* @__PURE__ */ new WeakMap());\n let standIn = listeners.get(listener);\n if (standIn === void 0) {\n standIn = function(event) {\n state.requests.run(\n store,\n () => typeof listener === "function" ? Reflect.apply(listener, this, [event]) : listener.handleEvent(event)\n );\n };\n listeners.set(listener, standIn);\n }\n return standIn;\n}\nObject.defineProperties(WebSocket.prototype, {\n addEventListener: {\n configurable: true,\n writable: true,\n value(type, listener, ...rest) {\n const store = state.requests.getStore();\n const runs = store !== void 0 && listener !== null && (typeof listener === "function" || typeof listener === "object");\n return Reflect.apply(addListener, this, [\n type,\n runs ? standInFor(this, listener, store) : listener,\n ...rest\n ]);\n }\n },\n removeEventListener: {\n configurable: true,\n writable: true,\n value(type, listener, ...rest) {\n const standIn = typeof listener === "function" || typeof listener === "object" && listener !== null ? standIns.get(this)?.get(listener) : void 0;\n return Reflect.apply(removeListener, this, [\n type,\n standIn ?? listener,\n ...rest\n ]);\n }\n }\n});\nvar ON_EVENTS = ["onopen", "onmessage", "onclose", "onerror"];\nvar onHandlers = /* @__PURE__ */ new WeakMap();\nfunction nativeAccessor(target, name) {\n for (let at = target; at !== null; at = Object.getPrototypeOf(at)) {\n const descriptor = Object.getOwnPropertyDescriptor(at, name);\n if (descriptor === void 0) continue;\n return descriptor.get !== void 0 && descriptor.set !== void 0 ? { get: descriptor.get, set: descriptor.set } : void 0;\n }\n return void 0;\n}\nfor (const name of ON_EVENTS) {\n const native = nativeAccessor(WebSocket.prototype, name);\n Object.defineProperty(WebSocket.prototype, name, {\n configurable: true,\n enumerable: true,\n get() {\n const set = onHandlers.get(this)?.get(name);\n if (set === void 0)\n return native === void 0 ? void 0 : Reflect.apply(native.get, this, []);\n return state.requests.getStore() === void 0 ? set.standIn : set.handler;\n },\n set(handler) {\n const store = state.requests.getStore();\n const standIn = store !== void 0 && typeof handler === "function" ? standInFor(this, handler, store) : handler;\n let handlers = onHandlers.get(this);\n if (handlers === void 0) onHandlers.set(this, handlers = /* @__PURE__ */ new Map());\n handlers.set(name, { handler, standIn });\n if (native !== void 0) Reflect.apply(native.set, this, [standIn]);\n }\n });\n}\nfunction serve(first, second) {\n const options = typeof first === "function" ? {} : first ?? {};\n const handler = typeof first === "function" ? first : second ?? options.handler;\n if (typeof handler !== "function")\n throw new TypeError("Deno.serve needs a handler.");\n const onError = typeof options.onError === "function" ? options.onError : null;\n if (state.requests.getStore() === void 0) {\n if (state.router !== null) state.router.register(handler, onError);\n else state.registered = { handler, onError };\n }\n return {\n finished: new Promise(() => {\n }),\n shutdown: () => Promise.resolve()\n };\n}\nfunction textBindings() {\n const out = {};\n for (const [name, value] of Object.entries(bindings))\n if (typeof value === "string") out[name] = value;\n return out;\n}\nvar build = Object.freeze({\n os: "linux",\n arch: "x86_64",\n target: "x86_64-unknown-linux-gnu",\n vendor: "unknown",\n env: "gnu"\n});\nvar version = Object.freeze({\n deno: "2.1.4",\n v8: "13.0.245.12",\n typescript: "5.6.2"\n});\nvar DENO_ERROR_NAMES = [\n "NotFound",\n "PermissionDenied",\n "ConnectionRefused",\n "ConnectionReset",\n "ConnectionAborted",\n "NotConnected",\n "AddrInUse",\n "AddrNotAvailable",\n "BrokenPipe",\n "AlreadyExists",\n "InvalidData",\n "TimedOut",\n "Interrupted",\n "WriteZero",\n "WouldBlock",\n "UnexpectedEof",\n "BadResource",\n "Http",\n "Busy",\n "NotSupported",\n "FilesystemLoop",\n "IsADirectory",\n "NetworkUnreachable",\n "NotADirectory",\n "NotCapable"\n];\nfunction denoError(name) {\n const made = { [name]: class extends Error {\n } }[name];\n Object.defineProperty(made.prototype, "name", {\n configurable: true,\n writable: true,\n value: name\n });\n return made;\n}\nvar errors = Object.freeze(\n Object.fromEntries(DENO_ERROR_NAMES.map((name) => [name, denoError(name)]))\n);\nvar deno = {\n serve,\n env: {\n get: (name) => {\n const value = bindings[name];\n return typeof value === "string" ? value : void 0;\n },\n has: (name) => typeof bindings[name] === "string",\n toObject: () => textBindings(),\n set: () => {\n throw new Error("Deno.env.set is not available on Naro Functions.");\n },\n delete: () => {\n throw new Error("Deno.env.delete is not available on Naro Functions.");\n }\n },\n build,\n version,\n errors\n};\nvar PROBES = /* @__PURE__ */ new Set(["then", "toJSON"]);\nglobals.Deno = new Proxy(deno, {\n get(target, property) {\n if (typeof property === "symbol" || PROBES.has(property)) return void 0;\n if (Object.hasOwn(target, property)) return target[property];\n return () => {\n throw new Error(`Deno.${property} is not available on Naro Functions.`);\n };\n }\n});\nglobals.EdgeRuntime = {\n waitUntil(promise) {\n const logged = loggedRejection(promise, callName());\n state.requests.getStore()?.ctx.waitUntil(logged);\n return promise;\n }\n};\naddEventListener("unhandledrejection", (event) => {\n event.preventDefault();\n console.error(\n `naro functions: ${callName()} unhandled rejection`,\n event.reason\n );\n});\n';
36308
+ var NARO_BOOT_SOURCE = '// src/naro-boot.ts\nimport "./naro-shim.js";\nimport * as user from "./user.js";\n\n// src/choose.ts\nasync function serveWithInfo(registered, request) {\n let complete;\n const info = {\n remoteAddr: {\n transport: "tcp",\n hostname: request.headers.get("cf-connecting-ip") ?? "0.0.0.0",\n port: 0\n },\n completed: new Promise((resolve) => {\n complete = resolve;\n })\n };\n try {\n return await registered(request, info);\n } finally {\n complete();\n }\n}\nfunction chosenOf(registered, module) {\n if (registered !== null) {\n const handler = registered.handler;\n return {\n handler: async (request) => serveWithInfo(handler, request),\n onError: registered.onError\n };\n }\n const exported = module.default;\n if (typeof exported === "object" && exported !== null) {\n const fetchHandler = exported.fetch;\n if (typeof fetchHandler === "function")\n return {\n handler: async (request, env, ctx) => fetchHandler.call(exported, request, env, ctx),\n onError: null\n };\n }\n if (typeof exported === "function") {\n const prototype = exported.prototype;\n if (typeof prototype === "object" && prototype !== null && typeof prototype.fetch === "function")\n return {\n handler: async (request, env, ctx) => new exported(ctx, env).fetch(request),\n onError: null\n };\n return {\n handler: async (request, env, ctx) => exported(request, env, ctx),\n onError: null\n };\n }\n return null;\n}\n\n// src/runtime-state.ts\nvar RUNTIME_KEY = /* @__PURE__ */ Symbol.for("naro.functions.runtime");\nvar SHIM_FIRST = "The Naro functions shim must be imported before the wrapper.";\nfunction globals() {\n return globalThis;\n}\nfunction runtimeState() {\n const state2 = globals()[RUNTIME_KEY];\n if (state2 === void 0) throw new Error(SHIM_FIRST);\n return state2;\n}\n\n// src/naro-boot.ts\nvar BOOT_PATH = "/__naro/boot";\nvar state = runtimeState();\nvar naro_boot_default = {\n fetch(request) {\n if (new URL(request.url).pathname !== BOOT_PATH)\n return new Response("Not found", { status: 404 });\n const module = user;\n return Response.json({\n handler: chosenOf(state.registered, module) !== null\n });\n }\n};\nexport {\n naro_boot_default as default\n};\n';
36204
36309
  var FUNCTIONS_COMPATIBILITY_DATE = "2026-08-19";
36205
36310
  var FUNCTIONS_COMPATIBILITY_FLAGS = Object.freeze(["nodejs_compat", "global_fetch_strictly_public"]);
36206
36311
 
36207
36312
  // ../../packages/functions-bundler/src/bundle.ts
36208
36313
  var NARO_JS_FUNCTIONS = /Could not resolve "naro-js\/functions"/;
36209
- var runtimeGzip;
36210
- function runtimeCompressedBytes() {
36211
- runtimeGzip ??= gzipSync(NARO_ENTRY_SOURCE).byteLength + gzipSync(NARO_SHIM_SOURCE).byteLength;
36212
- return runtimeGzip;
36213
- }
36214
36314
  function relativeLabel(root, file2) {
36215
36315
  return relative10(root, file2).split(sep9).join("/");
36216
36316
  }
@@ -36317,18 +36417,11 @@ async function bundleFunction(source, folder, options = {}) {
36317
36417
  BUNDLE_MESSAGES.tooLarge(source.name, bytes2, biggest)
36318
36418
  );
36319
36419
  }
36320
- const compressedBytes = gzipSync(output.contents).byteLength + runtimeCompressedBytes();
36321
- if (compressedBytes > (options.maxCompressedBytes ?? FUNCTION_COMPRESSED_BUNDLE_MAX_BYTES))
36322
- throw new FunctionBundleError(
36323
- "FUNCTION_BUNDLE_FAILED",
36324
- BUNDLE_MESSAGES.tooLargeCompressed(source.name, compressedBytes)
36325
- );
36326
36420
  return {
36327
36421
  name: source.name,
36328
36422
  code: output.text,
36329
36423
  contents: output.contents,
36330
36424
  bytes: bytes2,
36331
- compressedBytes,
36332
36425
  sha256: createHash4("sha256").update(output.contents).digest("hex"),
36333
36426
  warnings: [...state.warnings]
36334
36427
  };
@@ -36452,7 +36545,16 @@ function retryAfterSeconds(error51) {
36452
36545
  const value = error51.details?.retryAfter;
36453
36546
  return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.min(Math.ceil(value), FUNCTION_RATE_LIMIT_MAX_SECONDS) : FUNCTION_RATE_LIMIT_DEFAULT_SECONDS;
36454
36547
  }
36548
+ var functionWords = (name) => ({
36549
+ rateLimited: (seconds, attempt, of) => `\u23F3 Cloudflare is rate limiting function uploads; trying ${name} again in ${String(seconds)} seconds (${String(attempt)}/${String(of)}).`,
36550
+ busy: (attempt, of) => `\u23F3 ${name} is being changed right now; trying again in ${String(FUNCTION_BUSY_WAIT_MS / 1e3)} seconds (${String(attempt)}/${String(of)}).`
36551
+ });
36552
+ var batchRetryWords = (count2) => ({
36553
+ rateLimited: (seconds, attempt, of) => `\u23F3 Cloudflare is rate limiting function uploads; trying the ${String(count2)} functions again in ${String(seconds)} seconds (${String(attempt)}/${String(of)}).`,
36554
+ busy: (attempt, of) => `\u23F3 Functions in this project are being changed right now; trying again in ${String(FUNCTION_BUSY_WAIT_MS / 1e3)} seconds (${String(attempt)}/${String(of)}).`
36555
+ });
36455
36556
  async function withFunctionRetries(op, name, io) {
36557
+ const words = typeof name === "string" ? functionWords(name) : name;
36456
36558
  const rateRetries = io.rateLimitRetries ?? FUNCTION_RETRIES;
36457
36559
  const busyRetries = io.busyRetries ?? FUNCTION_RETRIES;
36458
36560
  let rate = 0;
@@ -36465,17 +36567,13 @@ async function withFunctionRetries(op, name, io) {
36465
36567
  if (error51.code === "FUNCTIONS_RATE_LIMITED" && rate < rateRetries) {
36466
36568
  rate += 1;
36467
36569
  const seconds = retryAfterSeconds(error51);
36468
- io.log(
36469
- `\u23F3 Cloudflare is rate limiting function uploads; trying ${name} again in ${String(seconds)} seconds (${String(rate)}/${String(rateRetries)}).`
36470
- );
36570
+ io.log(words.rateLimited(seconds, rate, rateRetries));
36471
36571
  await io.sleep(seconds * 1e3);
36472
36572
  continue;
36473
36573
  }
36474
36574
  if (error51.code === "FUNCTION_BUSY" && busy < busyRetries) {
36475
36575
  busy += 1;
36476
- io.log(
36477
- `\u23F3 ${name} is being changed right now; trying again in ${String(FUNCTION_BUSY_WAIT_MS / 1e3)} seconds (${String(busy)}/${String(busyRetries)}).`
36478
- );
36576
+ io.log(words.busy(busy, busyRetries));
36479
36577
  await io.sleep(FUNCTION_BUSY_WAIT_MS);
36480
36578
  continue;
36481
36579
  }
@@ -36494,7 +36592,6 @@ var PER_FUNCTION_CODES = /* @__PURE__ */ new Set([
36494
36592
  "BUNDLE_MISSING",
36495
36593
  "BUNDLE_MISMATCH",
36496
36594
  "FUNCTION_LIMIT",
36497
- "FUNCTION_BUSY",
36498
36595
  "FUNCTION_NOT_FOUND",
36499
36596
  "VERSION_NOT_FOUND",
36500
36597
  "VERSION_NOT_DEPLOYABLE",
@@ -36524,16 +36621,20 @@ function uploadTimeoutMs(upload, now = Date.now()) {
36524
36621
  var SECRETS_LIST_TIMEOUT_MS = 45e3;
36525
36622
  var FUNCTION_CALL_TIMEOUT_MS = 12e4;
36526
36623
  var deadlineReached = (name, deadlineMs) => `Deploying ${name} reached the ${String(deadlineMs / 1e3)}-second limit before it went live; the live version did not change. Try again.`;
36624
+ var batchDeadlineReached = (count2, deadlineMs) => `Deploying ${String(count2)} functions reached the ${String(deadlineMs / 1e3)}-second limit before they went live; the live versions did not change. Try again.`;
36527
36625
  function budgetOf(options) {
36528
36626
  const { deadlineMs } = options;
36529
36627
  if (deadlineMs === void 0) return null;
36530
36628
  const now = options.now ?? Date.now;
36531
36629
  const end = now() + deadlineMs;
36532
36630
  return {
36533
- left(name, cap) {
36631
+ left(name, cap, message) {
36534
36632
  const left = end - now();
36535
36633
  if (left <= 0)
36536
- throw new NaroError("TIMEOUT", deadlineReached(name, deadlineMs));
36634
+ throw new NaroError(
36635
+ "TIMEOUT",
36636
+ message ?? deadlineReached(name, deadlineMs)
36637
+ );
36537
36638
  return Math.min(cap, left);
36538
36639
  }
36539
36640
  };
@@ -36578,58 +36679,79 @@ async function projectSecretNames(client, projectId, timeoutMs) {
36578
36679
  return [];
36579
36680
  }
36580
36681
  }
36581
- async function deployOne(options, folder, name, importMap, secretNames, io, budget) {
36682
+ function callLimit(budget, name, message) {
36683
+ return budget ? { timeoutMs: budget.left(name, FUNCTION_CALL_TIMEOUT_MS, message) } : null;
36684
+ }
36685
+ async function prepareOne(options, folder, name, importMap, secretNames, io, budget) {
36582
36686
  const steps = options.steps ?? { bundle: bundleFunction, boot: bootCheck };
36583
36687
  const { client, projectId } = options;
36584
- const call = () => budget ? { timeoutMs: budget.left(name, FUNCTION_CALL_TIMEOUT_MS) } : null;
36585
36688
  io.log(`\u23F3 Deploying ${name}\u2026`);
36586
- try {
36587
- const source = readFunctionSource(folder, name);
36588
- const bundle = await steps.bundle(source, folder, { importMap });
36589
- for (const warning of bundle.warnings) io.log(warning);
36590
- if (!options.skipCheck)
36591
- await steps.boot(
36592
- name,
36593
- bundle.code,
36594
- budget ? {
36595
- secretNames,
36596
- timeoutMs: bootSeconds(budget.left(name, BOOT_TIMEOUT_MS))
36597
- } : { secretNames }
36598
- );
36599
- const verifyJwt = options.verifyJwt ?? source.verifyJwt ?? true;
36600
- const request2 = {
36601
- size: bundle.bytes,
36602
- sha256: bundle.sha256,
36603
- verifyJwt,
36604
- entrypoint: source.entrypoint,
36605
- ...options.cliVersion ? { cliVersion: options.cliVersion } : {}
36606
- };
36607
- const created = await withFunctionRetries(
36608
- () => {
36609
- const limit = call();
36610
- return limit ? client.functions.createVersion(projectId, name, request2, limit) : client.functions.createVersion(projectId, name, request2);
36611
- },
36689
+ const source = readFunctionSource(folder, name);
36690
+ const bundle = await steps.bundle(source, folder, { importMap });
36691
+ for (const warning of bundle.warnings) io.log(warning);
36692
+ if (!options.skipCheck)
36693
+ await steps.boot(
36612
36694
  name,
36613
- io
36614
- );
36615
- const urlLife = uploadTimeoutMs(created.upload);
36616
- await putBundle(
36617
- options.fetch ?? fetch,
36695
+ bundle.code,
36696
+ budget ? {
36697
+ secretNames,
36698
+ timeoutMs: bootSeconds(budget.left(name, BOOT_TIMEOUT_MS))
36699
+ } : { secretNames }
36700
+ );
36701
+ const verifyJwt = options.verifyJwt ?? source.verifyJwt ?? true;
36702
+ const request2 = {
36703
+ size: bundle.bytes,
36704
+ sha256: bundle.sha256,
36705
+ verifyJwt,
36706
+ entrypoint: source.entrypoint,
36707
+ ...options.cliVersion ? { cliVersion: options.cliVersion } : {}
36708
+ };
36709
+ const created = await withFunctionRetries(
36710
+ () => {
36711
+ const limit = callLimit(budget, name);
36712
+ return limit ? client.functions.createVersion(projectId, name, request2, limit) : client.functions.createVersion(projectId, name, request2);
36713
+ },
36714
+ name,
36715
+ io
36716
+ );
36717
+ const urlLife = uploadTimeoutMs(created.upload);
36718
+ await putBundle(
36719
+ options.fetch ?? fetch,
36720
+ name,
36721
+ created.upload,
36722
+ bundle.contents,
36723
+ budget ? budget.left(name, urlLife) : urlLife
36724
+ );
36725
+ return {
36726
+ name,
36727
+ versionId: created.version.id,
36728
+ number: created.version.number,
36729
+ url: created.function.url,
36730
+ verifyJwt: created.version.verifyJwt
36731
+ };
36732
+ }
36733
+ async function deployOne(options, folder, name, importMap, secretNames, io, budget) {
36734
+ const { client, projectId } = options;
36735
+ try {
36736
+ const ready = await prepareOne(
36737
+ options,
36738
+ folder,
36618
36739
  name,
36619
- created.upload,
36620
- bundle.contents,
36621
- budget ? budget.left(name, urlLife) : urlLife
36740
+ importMap,
36741
+ secretNames,
36742
+ io,
36743
+ budget
36622
36744
  );
36623
36745
  const live = await withFunctionRetries(
36624
36746
  () => {
36625
- const limit = call();
36626
- return limit ? client.functions.deploy(projectId, name, created.version.id, limit) : client.functions.deploy(projectId, name, created.version.id);
36747
+ const limit = callLimit(budget, name);
36748
+ return limit ? client.functions.deploy(projectId, name, ready.versionId, limit) : client.functions.deploy(projectId, name, ready.versionId);
36627
36749
  },
36628
36750
  name,
36629
36751
  io
36630
36752
  );
36631
36753
  io.log(
36632
- `\u2705 Deployed ${name} (v${String(live.version ?? created.version.number)}) \u2014 ${live.url}`
36754
+ `\u2705 Deployed ${name} (v${String(live.version ?? ready.number)}) \u2014 ${live.url}`
36633
36755
  );
36634
36756
  return {
36635
36757
  name,
@@ -36641,6 +36763,30 @@ async function deployOne(options, folder, name, importMap, secretNames, io, budg
36641
36763
  throw asNaroError(error51);
36642
36764
  }
36643
36765
  }
36766
+ async function deployReady(options, ready, io, budget) {
36767
+ const { client, projectId, deadlineMs } = options;
36768
+ const versions = ready.map(({ name, versionId }) => ({ name, versionId }));
36769
+ const [only] = ready;
36770
+ const one = ready.length === 1 && only !== void 0 ? only.name : null;
36771
+ const late = deadlineMs === void 0 ? void 0 : one === null ? batchDeadlineReached(ready.length, deadlineMs) : deadlineReached(one, deadlineMs);
36772
+ io.log(
36773
+ one === null ? `\u23F3 Putting ${String(ready.length)} functions live in one upload\u2026` : `\u23F3 Putting ${one} live\u2026`
36774
+ );
36775
+ const { functions } = await withFunctionRetries(
36776
+ () => {
36777
+ const limit = callLimit(budget, one ?? "", late);
36778
+ return limit ? client.functions.deployMany(projectId, versions, limit) : client.functions.deployMany(projectId, versions);
36779
+ },
36780
+ one ?? batchRetryWords(ready.length),
36781
+ io
36782
+ );
36783
+ const live = new Map(functions.map((fn) => [fn.name, fn.version]));
36784
+ return ready.map((fn) => {
36785
+ const version3 = live.get(fn.name) ?? fn.number;
36786
+ io.log(`\u2705 Deployed ${fn.name} (v${String(version3)}) \u2014 ${fn.url}`);
36787
+ return { name: fn.name, version: version3, url: fn.url, verifyJwt: fn.verifyJwt };
36788
+ });
36789
+ }
36644
36790
  function readFlagImportMap(options, folder) {
36645
36791
  if (options.importMap === void 0) return void 0;
36646
36792
  const file2 = resolve14(options.cwd, options.importMap);
@@ -36704,12 +36850,12 @@ async function deployFunctions(options) {
36704
36850
  )
36705
36851
  ]
36706
36852
  };
36707
- const deployed = [];
36853
+ const ready = [];
36708
36854
  const failures = [];
36709
36855
  for (const name of names) {
36710
36856
  try {
36711
- deployed.push(
36712
- await deployOne(
36857
+ ready.push(
36858
+ await prepareOne(
36713
36859
  options,
36714
36860
  folder,
36715
36861
  name,
@@ -36719,14 +36865,23 @@ async function deployFunctions(options) {
36719
36865
  budget
36720
36866
  )
36721
36867
  );
36722
- } catch (error51) {
36868
+ } catch (caught) {
36869
+ const error51 = asNaroError(caught);
36723
36870
  if (!isNaroError(error51)) throw error51;
36724
36871
  if (!PER_FUNCTION_CODES.has(error51.code))
36725
- throw withProgress(error51, deployed, failures);
36872
+ throw withProgress(error51, [], failures);
36726
36873
  failures.push({ name, code: error51.code, message: error51.message });
36727
36874
  io.log(`\u2717 ${name}: ${printable(error51.message)}`);
36728
36875
  }
36729
36876
  }
36877
+ let deployed = [];
36878
+ if (ready.length > 0)
36879
+ try {
36880
+ deployed = await deployReady(options, ready, io, budget);
36881
+ } catch (error51) {
36882
+ if (!isNaroError(error51)) throw error51;
36883
+ throw withProgress(error51, [], failures);
36884
+ }
36730
36885
  if (failures.length > 0)
36731
36886
  throw new NaroError(
36732
36887
  "FUNCTIONS_DEPLOY_PARTIAL",
@@ -37248,6 +37403,8 @@ export {
37248
37403
  formatRedirectEntry,
37249
37404
  policiesForTypes,
37250
37405
  generateTypes,
37406
+ isSymbolicLink,
37407
+ assertProjectLinkWritable,
37251
37408
  readProjectLink,
37252
37409
  writeProjectLink,
37253
37410
  removeProjectLink,