naro-cli 0.28.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.
@@ -306,6 +306,9 @@ function sanitizeRoutes(value) {
306
306
  return JSON.stringify(routes).length > MAX_ROUTE_LIST_BYTES ? void 0 : routes;
307
307
  }
308
308
 
309
+ // ../../packages/core/src/static-assets.ts
310
+ var IMMUTABLE_CACHE_CONTROL = "public, max-age=31536000, immutable";
311
+
309
312
  // ../../packages/core/src/artifact-manifest.ts
310
313
  var MAX_FILE_BYTES = 25 * 1024 * 1024;
311
314
  var MAX_ARTIFACT_BYTES = 64 * 1024 * 1024;
@@ -1746,7 +1749,12 @@ var CAPABILITIES = [
1746
1749
  id: "function-get",
1747
1750
  domain: "functions",
1748
1751
  title: "\uD568\uC218 \uC0C1\uC138",
1749
- 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
+ ],
1750
1758
  ui: { kind: "built" },
1751
1759
  cli: { kind: "built", commands: ["naro functions versions"] },
1752
1760
  mcp: { kind: "built", tools: ["get_function"] }
@@ -1755,7 +1763,10 @@ var CAPABILITIES = [
1755
1763
  id: "function-update",
1756
1764
  domain: "functions",
1757
1765
  title: "\uD568\uC218 verify_jwt \uC124\uC815",
1758
- routes: ["PATCH projects/[projectId]/backend/functions/[name]"],
1766
+ routes: [
1767
+ "PATCH projects/[projectId]/backend/functions/[name]",
1768
+ "PATCH projects/[projectId]/backend/functions/deploy"
1769
+ ],
1759
1770
  ui: { kind: "built" },
1760
1771
  cli: { kind: "built", commands: ["naro functions update"] },
1761
1772
  mcp: { kind: "built", tools: ["update_function"] }
@@ -1764,7 +1775,10 @@ var CAPABILITIES = [
1764
1775
  id: "function-delete",
1765
1776
  domain: "functions",
1766
1777
  title: "\uD568\uC218 \uC0AD\uC81C",
1767
- routes: ["DELETE projects/[projectId]/backend/functions/[name]"],
1778
+ routes: [
1779
+ "DELETE projects/[projectId]/backend/functions/[name]",
1780
+ "DELETE projects/[projectId]/backend/functions/deploy"
1781
+ ],
1768
1782
  ui: { kind: "built" },
1769
1783
  cli: { kind: "built", commands: ["naro functions delete"] },
1770
1784
  mcp: { kind: "built", tools: ["delete_function"] }
@@ -1787,10 +1801,14 @@ var CAPABILITIES = [
1787
1801
  domain: "functions",
1788
1802
  title: "\uC800\uC7A5\uB41C \uBC84\uC804 \uBC30\uD3EC",
1789
1803
  routes: [
1790
- "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"
1791
1809
  ],
1792
1810
  ui: { kind: "built" },
1793
- // naro functions deploy가 올린 버전을 배포한다(client.functions.deploy).
1811
+ // naro functions deploy가 올린 버전을 배포한다(client.functions.deploy, 여럿이면 deployMany).
1794
1812
  cli: { kind: "built" },
1795
1813
  // deploy_function이 naro-cli의 deployFunctions로 올린 버전을 배포한다.
1796
1814
  mcp: { kind: "built" }
@@ -2511,12 +2529,22 @@ var DEPLOYMENT_ERROR_CODES = [
2511
2529
  "DATABASE_FULL",
2512
2530
  /** Usage limits spec 2026-10-18 §6: a hobby team created its 100 deployments this UTC month. */
2513
2531
  "USAGE_LIMIT_REACHED",
2514
- /** A hobby team has its 3 projects; no project can be added. */
2532
+ /** Hobby: another limit (requests, D1, Storage, Functions, Realtime) is reached, so new builds wait (spec 2026-11-07-hobby-any-limit-blocks-build). */
2533
+ "BUILDS_PAUSED",
2534
+ /** A hobby team has its 20 projects; no project can be added. */
2515
2535
  "PROJECT_LIMIT_REACHED",
2516
2536
  /** A hobby team has its 1 member; no member or invite can be added. */
2517
2537
  "MEMBER_LIMIT_REACHED",
2518
- /** Custom domains are not part of the Hobby plan. */
2538
+ /**
2539
+ * Custom domains were not part of the Hobby plan. No longer answered since
2540
+ * 202611070030 (one domain per hobby project); kept so an older client
2541
+ * still names it.
2542
+ */
2519
2543
  "PLAN_DOMAINS_NOT_INCLUDED",
2544
+ /** A hobby project has its 1 custom domain (with its www form); no other can be added. */
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",
2520
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. */
2521
2549
  "ENVIRONMENT_NAME_INVALID",
2522
2550
  /** `production` and `preview` are reserved names. */
@@ -17567,7 +17595,7 @@ var REQUESTS = {
17567
17595
  subject100: (team) => `${team} has reached its monthly request limit`,
17568
17596
  usage: (team, used, limit, month) => `${team} has used ${used} of the ${limit} requests the Hobby plan includes for ${month}.`,
17569
17597
  warning: "When it reaches the limit, requests past it may be blocked until the month ends (UTC).",
17570
- blocked: "Sites on this team answer 503 until the month ends (UTC).",
17598
+ blocked: "Sites on this team are blocked (HTTP 403 or 503) until the month ends (UTC).",
17571
17599
  running: "Sites keep running for now.",
17572
17600
  button: "View usage",
17573
17601
  footer: (team) => `You're receiving this because you're an owner or billing member of ${team} on Naro.`
@@ -17577,7 +17605,7 @@ var REQUESTS = {
17577
17605
  subject100: (team) => `${team} \uD300\uC774 \uC774\uBC88 \uB2EC \uC694\uCCAD \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4`,
17578
17606
  usage: (team, used, limit, month) => `${team} \uD300\uC774 ${month} Hobby \uD50C\uB79C\uC5D0 \uD3EC\uD568\uB41C \uC694\uCCAD ${limit}\uAC74 \uC911 ${used}\uAC74\uC744 \uC37C\uC2B5\uB2C8\uB2E4.`,
17579
17607
  warning: "\uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD558\uBA74 \uC774\uBC88 \uB2EC\uC774 \uB05D\uB0A0 \uB54C\uAE4C\uC9C0(UTC) \uADF8 \uB4A4 \uC694\uCCAD\uC774 \uB9C9\uD790 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
17580
- blocked: "\uC774\uBC88 \uB2EC\uC774 \uB05D\uB0A0 \uB54C\uAE4C\uC9C0(UTC) \uC774 \uD300\uC758 \uC0AC\uC774\uD2B8\uB294 503\uC73C\uB85C \uC751\uB2F5\uD569\uB2C8\uB2E4.",
17608
+ blocked: "\uC774\uBC88 \uB2EC\uC774 \uB05D\uB0A0 \uB54C\uAE4C\uC9C0(UTC) \uC774 \uD300\uC758 \uC0AC\uC774\uD2B8\uB294 \uC694\uCCAD\uC744 \uB9C9\uC2B5\uB2C8\uB2E4(HTTP 403 \uB610\uB294 503).",
17581
17609
  running: "\uC9C0\uAE08\uC740 \uC0AC\uC774\uD2B8\uAC00 \uACC4\uC18D \uB3D9\uC791\uD569\uB2C8\uB2E4.",
17582
17610
  button: "\uC0AC\uC6A9\uB7C9 \uBCF4\uAE30",
17583
17611
  footer: (team) => `Naro\uC5D0\uC11C ${team} \uD300\uC758 owner \uB610\uB294 billing \uC5ED\uD560\uC774\uB77C \uC774 \uBA54\uC77C\uC744 \uBC1B\uC558\uC2B5\uB2C8\uB2E4.`
@@ -17587,7 +17615,7 @@ var REQUESTS = {
17587
17615
  subject100: (team) => `${team} \u30C1\u30FC\u30E0\u304C\u4ECA\u6708\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u4E0A\u9650\u306B\u9054\u3057\u307E\u3057\u305F`,
17588
17616
  usage: (team, used, limit, month) => `${team} \u30C1\u30FC\u30E0\u306F ${month} \u306E Hobby \u30D7\u30E9\u30F3\u306B\u542B\u307E\u308C\u308B ${limit} \u30EA\u30AF\u30A8\u30B9\u30C8\u306E\u3046\u3061 ${used} \u3092\u4F7F\u7528\u3057\u307E\u3057\u305F\u3002`,
17589
17617
  warning: "\u4E0A\u9650\u306B\u9054\u3059\u308B\u3068\u3001\u6708\u672B(UTC)\u307E\u3067\u4EE5\u964D\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u304C\u30D6\u30ED\u30C3\u30AF\u3055\u308C\u308B\u5834\u5408\u304C\u3042\u308A\u307E\u3059\u3002",
17590
- blocked: "\u6708\u672B(UTC)\u307E\u3067\u3001\u3053\u306E\u30C1\u30FC\u30E0\u306E\u30B5\u30A4\u30C8\u306F503\u3092\u8FD4\u3057\u307E\u3059\u3002",
17618
+ blocked: "\u6708\u672B(UTC)\u307E\u3067\u3001\u3053\u306E\u30C1\u30FC\u30E0\u306E\u30B5\u30A4\u30C8\u306F\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u62D2\u5426\u3057\u307E\u3059(HTTP 403 \u307E\u305F\u306F 503)\u3002",
17591
17619
  running: "\u73FE\u5728\u3001\u30B5\u30A4\u30C8\u306F\u5F15\u304D\u7D9A\u304D\u52D5\u4F5C\u3057\u3066\u3044\u307E\u3059\u3002",
17592
17620
  button: "\u4F7F\u7528\u91CF\u3092\u898B\u308B",
17593
17621
  footer: (team) => `Naro \u3067 ${team} \u30C1\u30FC\u30E0\u306E owner \u307E\u305F\u306F billing \u30ED\u30FC\u30EB\u306E\u305F\u3081\u3001\u3053\u306E\u30E1\u30FC\u30EB\u3092\u304A\u9001\u308A\u3057\u3066\u3044\u307E\u3059\u3002`
@@ -17739,6 +17767,63 @@ async function wrapNextConfig(appRoot, file2) {
17739
17767
  );
17740
17768
  return { wrapper: plan.wrapper, project: project2 };
17741
17769
  }
17770
+ function assetPrefixWrapperSource(projectSpecifier, typescript) {
17771
+ return `${typescript ? "// @ts-nocheck\n" : ""}// Written by Naro for this build only. The project's own next.config is
17772
+ // ${projectSpecifier}, unchanged; this wrapper serves _next/static from
17773
+ // Naro's CDN unless the project sets its own assetPrefix.
17774
+ import projectConfig from ${JSON.stringify(projectSpecifier)};
17775
+
17776
+ export default async function naroNextConfig(phase, context) {
17777
+ const config =
17778
+ typeof projectConfig === "function"
17779
+ ? await projectConfig(phase, context)
17780
+ : await projectConfig;
17781
+ const rest = { ...(config ?? {}) };
17782
+ return {
17783
+ ...rest,
17784
+ assetPrefix: rest.assetPrefix || process.env.NARO_STATIC_ASSET_PREFIX,
17785
+ };
17786
+ }
17787
+ `;
17788
+ }
17789
+ var ASSET_PREFIX_ONLY_CONFIG = `// Written by Naro for this build only: the project has no next.config.
17790
+ export default { assetPrefix: process.env.NARO_STATIC_ASSET_PREFIX };
17791
+ `;
17792
+ var CONFIG_LOOKUP = [
17793
+ "next.config.js",
17794
+ "next.config.mjs",
17795
+ "next.config.ts",
17796
+ "next.config.mts",
17797
+ "next.config.cjs"
17798
+ ];
17799
+ async function wrapNextConfigForAssetPrefix(appRoot, prefix) {
17800
+ if (!prefix) return "skipped";
17801
+ let file2;
17802
+ for (const name of CONFIG_LOOKUP)
17803
+ if (await exists(join2(appRoot, name))) {
17804
+ file2 = name;
17805
+ break;
17806
+ }
17807
+ if (!file2) {
17808
+ await writeFile2(join2(appRoot, "next.config.mjs"), ASSET_PREFIX_ONLY_CONFIG);
17809
+ return "created";
17810
+ }
17811
+ const plan = WRAPPABLE[file2];
17812
+ if (!plan) return "skipped";
17813
+ const project2 = `${PROJECT_CONFIG_BASENAME}.${file2.slice("next.config.".length)}`;
17814
+ if (await exists(join2(appRoot, project2))) return "skipped";
17815
+ await rename(join2(appRoot, file2), join2(appRoot, project2));
17816
+ try {
17817
+ await writeFile2(
17818
+ join2(appRoot, plan.wrapper),
17819
+ assetPrefixWrapperSource(plan.specifier, plan.typescript)
17820
+ );
17821
+ } catch (error51) {
17822
+ await rename(join2(appRoot, project2), join2(appRoot, file2));
17823
+ throw error51;
17824
+ }
17825
+ return "wrapped";
17826
+ }
17742
17827
 
17743
17828
  // ../../packages/next-adapter/src/index.ts
17744
17829
  function nextAdapterPath(base2 = import.meta.url) {
@@ -19201,6 +19286,54 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19201
19286
  };
19202
19287
  }
19203
19288
 
19289
+ // ../../packages/build/src/static-cdn.ts
19290
+ var STATIC_ASSET_PREFIX_ENV = "NARO_STATIC_ASSET_PREFIX";
19291
+ var STATIC_ASSETS_ON_CDN_NOTE = "Static assets (_next/static) are served from static.naro.sh.";
19292
+ var STATIC_ASSETS_ON_WORKER_NOTE = "Static assets stay on the Worker: the build did not use Naro's asset prefix (the project sets its own assetPrefix or a basePath, or it is not a Next.js build).";
19293
+ function buildEnvWithStaticPrefix(env, prefix) {
19294
+ const result = { ...env };
19295
+ delete result[STATIC_ASSET_PREFIX_ENV];
19296
+ if (prefix) result[STATIC_ASSET_PREFIX_ENV] = prefix;
19297
+ return result;
19298
+ }
19299
+ function contains(file2, needle) {
19300
+ return Buffer.from(
19301
+ file2.data.buffer,
19302
+ file2.data.byteOffset,
19303
+ file2.data.byteLength
19304
+ ).includes(needle);
19305
+ }
19306
+ function withStaticAssetPrefix(artifact, prefix) {
19307
+ if (!prefix) return { artifact, note: null };
19308
+ const needle = Buffer.from(prefix, "utf8");
19309
+ const files = [artifact.entrypoint, ...artifact.modules, ...artifact.assets];
19310
+ if (!files.some((file2) => contains(file2, needle)))
19311
+ return { artifact, note: STATIC_ASSETS_ON_WORKER_NOTE };
19312
+ return {
19313
+ artifact: { ...artifact, staticAssetPrefix: prefix },
19314
+ note: STATIC_ASSETS_ON_CDN_NOTE
19315
+ };
19316
+ }
19317
+ var IMMUTABLE_HEADERS_FILE = "_headers";
19318
+ var IMMUTABLE_HEADERS = `/_next/static/*
19319
+ Cache-Control: ${IMMUTABLE_CACHE_CONTROL}
19320
+ `;
19321
+ function withImmutableHeaders(artifact) {
19322
+ if (artifact.assets.some((asset) => asset.name === IMMUTABLE_HEADERS_FILE))
19323
+ return artifact;
19324
+ return {
19325
+ ...artifact,
19326
+ assets: [
19327
+ ...artifact.assets,
19328
+ {
19329
+ name: IMMUTABLE_HEADERS_FILE,
19330
+ data: new TextEncoder().encode(IMMUTABLE_HEADERS),
19331
+ contentType: "text/plain"
19332
+ }
19333
+ ]
19334
+ };
19335
+ }
19336
+
19204
19337
  // ../../packages/build/src/strategy/fingerprint.ts
19205
19338
  import { createHash as createHash3 } from "crypto";
19206
19339
  import { readFile as readFile9 } from "fs/promises";
@@ -20352,6 +20485,17 @@ function createVinextCloudflareStrategy(options) {
20352
20485
  );
20353
20486
  if (!added.ok) return failed("prepare", describeFailure(added));
20354
20487
  }
20488
+ try {
20489
+ await wrapNextConfigForAssetPrefix(
20490
+ fork.appRoot,
20491
+ context.env[STATIC_ASSET_PREFIX_ENV]
20492
+ );
20493
+ } catch (error51) {
20494
+ await warn(
20495
+ observer,
20496
+ `Could not point static assets at static.naro.sh: ${describeError(error51)}`
20497
+ );
20498
+ }
20355
20499
  const projectBin = adopt ? null : await findProjectBin(fork.appRoot, fork.checkout, "vinext");
20356
20500
  const build = await runStep(
20357
20501
  context,
@@ -20760,7 +20904,7 @@ var LocalBuildExecutor = class {
20760
20904
  input.rootDirectory
20761
20905
  );
20762
20906
  await assertAppRootInsideCheckout(checkout, appRoot);
20763
- const stacks = await import("./stack-PDV3NPWC.js");
20907
+ const stacks = await import("./stack-DFLT7AVX.js");
20764
20908
  const stack = await (this.dependencies.detectStack ?? stacks.detectStack)(
20765
20909
  appRoot
20766
20910
  ).catch(() => null);
@@ -20920,7 +21064,7 @@ var LocalBuildExecutor = class {
20920
21064
  const context = {
20921
21065
  identity,
20922
21066
  branch: input.branch,
20923
- env: input.env ?? {},
21067
+ env: buildEnvWithStaticPrefix(input.env, input.staticAssetPrefix),
20924
21068
  // The toolchain add has to use the store the install linked
20925
21069
  // node_modules from (withSharedCache).
20926
21070
  packageManager: cachePorts ? withSharedCache(packageManager, pmCacheDir) : packageManager,
@@ -20976,7 +21120,10 @@ var LocalBuildExecutor = class {
20976
21120
  args: ["run", resolved.buildScript],
20977
21121
  cwd: appRoot,
20978
21122
  env: {
20979
- ...input.env,
21123
+ // Static presets never get the asset prefix (spec
21124
+ // 2026-11-07-static-asset-cdn §3): a Vite base or CRA
21125
+ // PUBLIC_URL also moves the router and public/ files.
21126
+ ...buildEnvWithStaticPrefix(input.env, void 0),
20980
21127
  NPM_CONFIG_USERCONFIG: packageManagerConfigPath
20981
21128
  },
20982
21129
  timeoutMs: 15 * 6e4,
@@ -21010,6 +21157,14 @@ var LocalBuildExecutor = class {
21010
21157
  appRoot,
21011
21158
  observer
21012
21159
  );
21160
+ const cdn = withStaticAssetPrefix(artifact, input.staticAssetPrefix);
21161
+ if (cdn.note)
21162
+ await observer.onLog({
21163
+ level: "INFO",
21164
+ stage: "DEPLOYING",
21165
+ message: cdn.note
21166
+ });
21167
+ artifact = withImmutableHeaders(cdn.artifact);
21013
21168
  return {
21014
21169
  gitCommitSha,
21015
21170
  artifact,
@@ -21194,7 +21349,16 @@ async function errorFromResponse(response, context) {
21194
21349
  }
21195
21350
 
21196
21351
  // ../../packages/sdk/src/session.ts
21197
- 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";
21198
21362
  import { homedir } from "os";
21199
21363
  import { join as join18 } from "path";
21200
21364
  function credentialsDir(home = homedir()) {
@@ -21204,10 +21368,18 @@ function credentialsFile(home = homedir()) {
21204
21368
  return join18(credentialsDir(home), "credentials.json");
21205
21369
  }
21206
21370
  function saveSession(session, home = homedir()) {
21207
- mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
21208
- writeFileSync(credentialsFile(home), JSON.stringify(session, null, 2), {
21209
- mode: 384
21210
- });
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
+ }
21211
21383
  }
21212
21384
  function loadStoredSession(home = homedir()) {
21213
21385
  try {
@@ -21219,7 +21391,19 @@ function loadStoredSession(home = homedir()) {
21219
21391
  }
21220
21392
  }
21221
21393
  function clearSession(home = homedir()) {
21394
+ const dir = credentialsDir(home);
21222
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
+ }
21223
21407
  }
21224
21408
  function isExpired(session, skewSeconds = 60) {
21225
21409
  return session.expires_at * 1e3 <= Date.now() + skewSeconds * 1e3;
@@ -21394,7 +21578,20 @@ async function startCallbackServer() {
21394
21578
  resolveCode = resolve16;
21395
21579
  rejectCode = reject;
21396
21580
  });
21581
+ let port = 0;
21582
+ let settled = false;
21397
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
+ }
21398
21595
  const url2 = new URL(req.url ?? "/", "http://localhost");
21399
21596
  if (url2.pathname !== "/callback") {
21400
21597
  res.statusCode = 404;
@@ -21405,6 +21602,7 @@ async function startCallbackServer() {
21405
21602
  const code = url2.searchParams.get("code");
21406
21603
  if (error51) {
21407
21604
  const description = url2.searchParams.get("error_description");
21605
+ settled = true;
21408
21606
  sendPage(res, 200, failurePage(description ?? error51));
21409
21607
  rejectCode(new Error(`GitHub authorization failed: ${error51}`));
21410
21608
  return;
@@ -21417,11 +21615,12 @@ async function startCallbackServer() {
21417
21615
  );
21418
21616
  return;
21419
21617
  }
21618
+ settled = true;
21420
21619
  sendPage(res, 200, successPage());
21421
21620
  resolveCode(code);
21422
21621
  });
21423
21622
  await new Promise((resolve16) => server.listen(0, "127.0.0.1", resolve16));
21424
- const port = server.address().port;
21623
+ port = server.address().port;
21425
21624
  return {
21426
21625
  port,
21427
21626
  waitForCode(timeoutMs) {
@@ -21627,10 +21826,15 @@ async function resolveAuth(options = {}) {
21627
21826
 
21628
21827
  // ../../packages/sdk/src/browser.ts
21629
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
+ }
21630
21836
  function openBrowser(url2) {
21631
- const platform = process.platform;
21632
- const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
21633
- const args = platform === "win32" ? ["/c", "start", "", url2] : [url2];
21837
+ const { command, args } = browserCommand(url2);
21634
21838
  const child = spawn2(command, args, { stdio: "ignore", detached: true });
21635
21839
  child.on("error", () => {
21636
21840
  });
@@ -24119,6 +24323,28 @@ var FunctionsApi = class {
24119
24323
  );
24120
24324
  return body.function;
24121
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
+ }
24122
24348
  /**
24123
24349
  * Puts a stored version live again: the one numbered `version`, which must
24124
24350
  * have been live before (`VERSION_NOT_FOUND` otherwise — put a version that
@@ -24386,7 +24612,7 @@ function serializeDotenv(vars) {
24386
24612
  }
24387
24613
 
24388
24614
  // ../../packages/sdk/src/env-scan.ts
24389
- 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";
24390
24616
  import { join as join20 } from "path";
24391
24617
  var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
24392
24618
  ".ts",
@@ -24435,7 +24661,7 @@ function collect(dir, out) {
24435
24661
  if (out.length >= MAX_FILES) return;
24436
24662
  let entries;
24437
24663
  try {
24438
- entries = readdirSync(dir, { withFileTypes: true });
24664
+ entries = readdirSync2(dir, { withFileTypes: true });
24439
24665
  } catch {
24440
24666
  return;
24441
24667
  }
@@ -33432,12 +33658,32 @@ function generateTypes(tables, options = {}) {
33432
33658
  // ../../packages/sdk/src/link.ts
33433
33659
  import {
33434
33660
  existsSync as existsSync4,
33661
+ lstatSync,
33435
33662
  mkdirSync as mkdirSync2,
33436
33663
  readFileSync as readFileSync5,
33437
33664
  rmSync as rmSync2,
33438
33665
  writeFileSync as writeFileSync2
33439
33666
  } from "fs";
33440
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
+ }
33441
33687
  function linkDir(dir) {
33442
33688
  return join22(dir, ".naro");
33443
33689
  }
@@ -33460,6 +33706,7 @@ function readProjectLink(dir) {
33460
33706
  }
33461
33707
  }
33462
33708
  function writeProjectLink(dir, link) {
33709
+ assertProjectLinkWritable(dir);
33463
33710
  mkdirSync2(linkDir(dir), { recursive: true });
33464
33711
  writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
33465
33712
  `);
@@ -33475,6 +33722,7 @@ function removeProjectLink(dir) {
33475
33722
  }
33476
33723
  function ensureGitignoreHasNaro(dir) {
33477
33724
  const gitignorePath = join22(dir, ".gitignore");
33725
+ if (isSymbolicLink(gitignorePath)) return false;
33478
33726
  const hasGitignore = existsSync4(gitignorePath);
33479
33727
  if (!hasGitignore && !existsSync4(join22(dir, ".git"))) return false;
33480
33728
  const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
@@ -33815,7 +34063,7 @@ async function repairLedger(client, projectId, key, status, ledgerIsEmpty) {
33815
34063
  }
33816
34064
 
33817
34065
  // ../../packages/sdk/src/migrations.ts
33818
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
34066
+ import { existsSync as existsSync5, readdirSync as readdirSync3 } from "fs";
33819
34067
  import { resolve as resolve9 } from "path";
33820
34068
  function resolveMigrationsDir(input) {
33821
34069
  if (input.dir) return resolve9(input.baseDir, input.dir);
@@ -33835,7 +34083,7 @@ function sortMigrationNames(names) {
33835
34083
  function listMigrationFiles(dir) {
33836
34084
  if (!existsSync5(dir)) return [];
33837
34085
  return sortMigrationNames(
33838
- 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)
33839
34087
  );
33840
34088
  }
33841
34089
  function nextMigrationFileName(existing, name) {
@@ -34236,11 +34484,13 @@ function summarizeTeamUsage(report, currentMonth) {
34236
34484
  };
34237
34485
  const meters = USAGE_METRICS.map((metric) => {
34238
34486
  const limit = report.limits?.[metric] ?? null;
34487
+ const perProject = metric === "domains" ? report.limits?.domainsPerProject ?? null : null;
34239
34488
  return {
34240
34489
  metric,
34241
34490
  used: used[metric],
34242
34491
  limit,
34243
- percent: limit ? Math.floor(used[metric] * 100 / limit) : null
34492
+ percent: limit ? Math.floor(used[metric] * 100 / limit) : null,
34493
+ perProject
34244
34494
  };
34245
34495
  });
34246
34496
  return {
@@ -35276,8 +35526,6 @@ var BUNDLE_MESSAGES = {
35276
35526
  wasm: (file2) => `WebAssembly modules are not supported in functions yet: ${file2}.`,
35277
35527
  naroJsMissing: 'Cannot find "naro-js/functions". Install naro-js 0.7.0 or later: npm install naro-js@latest.',
35278
35528
  tooLarge: (name, size, inputs) => `Function ${name} bundles to ${bytes(size)} bytes; the limit is 67,108,864 (64 MiB). The biggest inputs: ${inputs}.`,
35279
- // Rounded up: a size over the limit never reads as the limit (10.0 MB).
35280
- tooLargeCompressed: (name, size) => `Function ${name} is ${(Math.ceil(size / 1048576 * 10) / 10).toFixed(1)} MB after compression; Cloudflare accepts 10 MB.`,
35281
35529
  otherBundle: (name, text3, location2) => `Function ${name} did not bundle: ${text3}${location2 ? ` (${location2})` : ""}`,
35282
35530
  noHandler: (name) => `Function ${name} did not register a handler. Call Deno.serve(handler) or export default { fetch }.`,
35283
35531
  failedToStart: (name, line) => `Function ${name} failed to start: ${line}`,
@@ -35287,12 +35535,11 @@ var BUNDLE_MESSAGES = {
35287
35535
  };
35288
35536
 
35289
35537
  // ../../packages/functions-bundler/src/discover.ts
35290
- 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";
35291
35539
  import { join as join27, relative as relative8, resolve as resolve12, sep as sep6 } from "path";
35292
35540
 
35293
35541
  // ../../packages/functions-bundler/src/limits.ts
35294
35542
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
35295
- var FUNCTION_COMPRESSED_BUNDLE_MAX_BYTES = 10485760;
35296
35543
  var ENTRY_FILES = [
35297
35544
  "index.ts",
35298
35545
  "index.js",
@@ -35319,7 +35566,7 @@ function functionsFolder(options) {
35319
35566
  function listFunctionNames(folder) {
35320
35567
  let entries;
35321
35568
  try {
35322
- entries = readdirSync3(folder.dir, { withFileTypes: true });
35569
+ entries = readdirSync4(folder.dir, { withFileTypes: true });
35323
35570
  } catch {
35324
35571
  if (!folder.explicit && existsSync7(join27(folder.root, "supabase", "functions")))
35325
35572
  throw new FunctionBundleError(
@@ -36052,25 +36299,18 @@ function denoImportsPlugin(options) {
36052
36299
  // ../../packages/functions-bundler/src/bundle.ts
36053
36300
  import { createHash as createHash4 } from "crypto";
36054
36301
  import { join as join30, relative as relative10, sep as sep9 } from "path";
36055
- import { gzipSync } from "zlib";
36056
36302
 
36057
36303
  // ../../packages/functions-runtime/dist/index.js
36058
36304
  var NARO_SHIM_MODULE = "naro-shim.js";
36059
36305
  var NARO_USER_MODULE = "user.js";
36060
36306
  var NARO_BOOT_MODULE = "naro-boot.js";
36061
- 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';
36062
- 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';
36063
- 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';
36064
36309
  var FUNCTIONS_COMPATIBILITY_DATE = "2026-08-19";
36065
36310
  var FUNCTIONS_COMPATIBILITY_FLAGS = Object.freeze(["nodejs_compat", "global_fetch_strictly_public"]);
36066
36311
 
36067
36312
  // ../../packages/functions-bundler/src/bundle.ts
36068
36313
  var NARO_JS_FUNCTIONS = /Could not resolve "naro-js\/functions"/;
36069
- var runtimeGzip;
36070
- function runtimeCompressedBytes() {
36071
- runtimeGzip ??= gzipSync(NARO_ENTRY_SOURCE).byteLength + gzipSync(NARO_SHIM_SOURCE).byteLength;
36072
- return runtimeGzip;
36073
- }
36074
36314
  function relativeLabel(root, file2) {
36075
36315
  return relative10(root, file2).split(sep9).join("/");
36076
36316
  }
@@ -36177,18 +36417,11 @@ async function bundleFunction(source, folder, options = {}) {
36177
36417
  BUNDLE_MESSAGES.tooLarge(source.name, bytes2, biggest)
36178
36418
  );
36179
36419
  }
36180
- const compressedBytes = gzipSync(output.contents).byteLength + runtimeCompressedBytes();
36181
- if (compressedBytes > (options.maxCompressedBytes ?? FUNCTION_COMPRESSED_BUNDLE_MAX_BYTES))
36182
- throw new FunctionBundleError(
36183
- "FUNCTION_BUNDLE_FAILED",
36184
- BUNDLE_MESSAGES.tooLargeCompressed(source.name, compressedBytes)
36185
- );
36186
36420
  return {
36187
36421
  name: source.name,
36188
36422
  code: output.text,
36189
36423
  contents: output.contents,
36190
36424
  bytes: bytes2,
36191
- compressedBytes,
36192
36425
  sha256: createHash4("sha256").update(output.contents).digest("hex"),
36193
36426
  warnings: [...state.warnings]
36194
36427
  };
@@ -36312,7 +36545,16 @@ function retryAfterSeconds(error51) {
36312
36545
  const value = error51.details?.retryAfter;
36313
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;
36314
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
+ });
36315
36556
  async function withFunctionRetries(op, name, io) {
36557
+ const words = typeof name === "string" ? functionWords(name) : name;
36316
36558
  const rateRetries = io.rateLimitRetries ?? FUNCTION_RETRIES;
36317
36559
  const busyRetries = io.busyRetries ?? FUNCTION_RETRIES;
36318
36560
  let rate = 0;
@@ -36325,17 +36567,13 @@ async function withFunctionRetries(op, name, io) {
36325
36567
  if (error51.code === "FUNCTIONS_RATE_LIMITED" && rate < rateRetries) {
36326
36568
  rate += 1;
36327
36569
  const seconds = retryAfterSeconds(error51);
36328
- io.log(
36329
- `\u23F3 Cloudflare is rate limiting function uploads; trying ${name} again in ${String(seconds)} seconds (${String(rate)}/${String(rateRetries)}).`
36330
- );
36570
+ io.log(words.rateLimited(seconds, rate, rateRetries));
36331
36571
  await io.sleep(seconds * 1e3);
36332
36572
  continue;
36333
36573
  }
36334
36574
  if (error51.code === "FUNCTION_BUSY" && busy < busyRetries) {
36335
36575
  busy += 1;
36336
- io.log(
36337
- `\u23F3 ${name} is being changed right now; trying again in ${String(FUNCTION_BUSY_WAIT_MS / 1e3)} seconds (${String(busy)}/${String(busyRetries)}).`
36338
- );
36576
+ io.log(words.busy(busy, busyRetries));
36339
36577
  await io.sleep(FUNCTION_BUSY_WAIT_MS);
36340
36578
  continue;
36341
36579
  }
@@ -36354,7 +36592,6 @@ var PER_FUNCTION_CODES = /* @__PURE__ */ new Set([
36354
36592
  "BUNDLE_MISSING",
36355
36593
  "BUNDLE_MISMATCH",
36356
36594
  "FUNCTION_LIMIT",
36357
- "FUNCTION_BUSY",
36358
36595
  "FUNCTION_NOT_FOUND",
36359
36596
  "VERSION_NOT_FOUND",
36360
36597
  "VERSION_NOT_DEPLOYABLE",
@@ -36384,16 +36621,20 @@ function uploadTimeoutMs(upload, now = Date.now()) {
36384
36621
  var SECRETS_LIST_TIMEOUT_MS = 45e3;
36385
36622
  var FUNCTION_CALL_TIMEOUT_MS = 12e4;
36386
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.`;
36387
36625
  function budgetOf(options) {
36388
36626
  const { deadlineMs } = options;
36389
36627
  if (deadlineMs === void 0) return null;
36390
36628
  const now = options.now ?? Date.now;
36391
36629
  const end = now() + deadlineMs;
36392
36630
  return {
36393
- left(name, cap) {
36631
+ left(name, cap, message) {
36394
36632
  const left = end - now();
36395
36633
  if (left <= 0)
36396
- throw new NaroError("TIMEOUT", deadlineReached(name, deadlineMs));
36634
+ throw new NaroError(
36635
+ "TIMEOUT",
36636
+ message ?? deadlineReached(name, deadlineMs)
36637
+ );
36397
36638
  return Math.min(cap, left);
36398
36639
  }
36399
36640
  };
@@ -36438,58 +36679,79 @@ async function projectSecretNames(client, projectId, timeoutMs) {
36438
36679
  return [];
36439
36680
  }
36440
36681
  }
36441
- 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) {
36442
36686
  const steps = options.steps ?? { bundle: bundleFunction, boot: bootCheck };
36443
36687
  const { client, projectId } = options;
36444
- const call = () => budget ? { timeoutMs: budget.left(name, FUNCTION_CALL_TIMEOUT_MS) } : null;
36445
36688
  io.log(`\u23F3 Deploying ${name}\u2026`);
36446
- try {
36447
- const source = readFunctionSource(folder, name);
36448
- const bundle = await steps.bundle(source, folder, { importMap });
36449
- for (const warning of bundle.warnings) io.log(warning);
36450
- if (!options.skipCheck)
36451
- await steps.boot(
36452
- name,
36453
- bundle.code,
36454
- budget ? {
36455
- secretNames,
36456
- timeoutMs: bootSeconds(budget.left(name, BOOT_TIMEOUT_MS))
36457
- } : { secretNames }
36458
- );
36459
- const verifyJwt = options.verifyJwt ?? source.verifyJwt ?? true;
36460
- const request2 = {
36461
- size: bundle.bytes,
36462
- sha256: bundle.sha256,
36463
- verifyJwt,
36464
- entrypoint: source.entrypoint,
36465
- ...options.cliVersion ? { cliVersion: options.cliVersion } : {}
36466
- };
36467
- const created = await withFunctionRetries(
36468
- () => {
36469
- const limit = call();
36470
- return limit ? client.functions.createVersion(projectId, name, request2, limit) : client.functions.createVersion(projectId, name, request2);
36471
- },
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(
36472
36694
  name,
36473
- io
36474
- );
36475
- const urlLife = uploadTimeoutMs(created.upload);
36476
- await putBundle(
36477
- 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,
36478
36739
  name,
36479
- created.upload,
36480
- bundle.contents,
36481
- budget ? budget.left(name, urlLife) : urlLife
36740
+ importMap,
36741
+ secretNames,
36742
+ io,
36743
+ budget
36482
36744
  );
36483
36745
  const live = await withFunctionRetries(
36484
36746
  () => {
36485
- const limit = call();
36486
- 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);
36487
36749
  },
36488
36750
  name,
36489
36751
  io
36490
36752
  );
36491
36753
  io.log(
36492
- `\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}`
36493
36755
  );
36494
36756
  return {
36495
36757
  name,
@@ -36501,6 +36763,30 @@ async function deployOne(options, folder, name, importMap, secretNames, io, budg
36501
36763
  throw asNaroError(error51);
36502
36764
  }
36503
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
+ }
36504
36790
  function readFlagImportMap(options, folder) {
36505
36791
  if (options.importMap === void 0) return void 0;
36506
36792
  const file2 = resolve14(options.cwd, options.importMap);
@@ -36564,12 +36850,12 @@ async function deployFunctions(options) {
36564
36850
  )
36565
36851
  ]
36566
36852
  };
36567
- const deployed = [];
36853
+ const ready = [];
36568
36854
  const failures = [];
36569
36855
  for (const name of names) {
36570
36856
  try {
36571
- deployed.push(
36572
- await deployOne(
36857
+ ready.push(
36858
+ await prepareOne(
36573
36859
  options,
36574
36860
  folder,
36575
36861
  name,
@@ -36579,14 +36865,23 @@ async function deployFunctions(options) {
36579
36865
  budget
36580
36866
  )
36581
36867
  );
36582
- } catch (error51) {
36868
+ } catch (caught) {
36869
+ const error51 = asNaroError(caught);
36583
36870
  if (!isNaroError(error51)) throw error51;
36584
36871
  if (!PER_FUNCTION_CODES.has(error51.code))
36585
- throw withProgress(error51, deployed, failures);
36872
+ throw withProgress(error51, [], failures);
36586
36873
  failures.push({ name, code: error51.code, message: error51.message });
36587
36874
  io.log(`\u2717 ${name}: ${printable(error51.message)}`);
36588
36875
  }
36589
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
+ }
36590
36885
  if (failures.length > 0)
36591
36886
  throw new NaroError(
36592
36887
  "FUNCTIONS_DEPLOY_PARTIAL",
@@ -37108,6 +37403,8 @@ export {
37108
37403
  formatRedirectEntry,
37109
37404
  policiesForTypes,
37110
37405
  generateTypes,
37406
+ isSymbolicLink,
37407
+ assertProjectLinkWritable,
37111
37408
  readProjectLink,
37112
37409
  writeProjectLink,
37113
37410
  removeProjectLink,