naro-cli 0.27.0 → 0.29.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.
@@ -5,10 +5,10 @@ import {
5
5
 
6
6
  // src/deploy.ts
7
7
  import { existsSync as existsSync10 } from "fs";
8
- import { mkdtemp as mkdtemp2, readFile as readFile15, rm as rm5 } from "fs/promises";
8
+ import { mkdtemp as mkdtemp2, readFile as readFile16, rm as rm6 } from "fs/promises";
9
9
  import { tmpdir as tmpdir2 } from "os";
10
- import { basename as basename5, dirname as dirname10, join as join31, resolve as resolve14 } from "path";
11
- import { fileURLToPath as fileURLToPath4 } from "url";
10
+ import { basename as basename4, dirname as dirname9, join as join31, resolve as resolve15 } from "path";
11
+ import { fileURLToPath as fileURLToPath5 } from "url";
12
12
 
13
13
  // ../../packages/build/src/artifact-upload.ts
14
14
  function artifactUploadEntries(artifact) {
@@ -41,7 +41,7 @@ function parseArtifactPutUrls(payload) {
41
41
  }
42
42
  async function eachWithinBudget(items, limit, budget, weigh, work) {
43
43
  if (items.length === 0) return;
44
- await new Promise((resolve15, reject) => {
44
+ await new Promise((resolve16, reject) => {
45
45
  let next = 0;
46
46
  let running = 0;
47
47
  let weight = 0;
@@ -58,7 +58,7 @@ async function eachWithinBudget(items, limit, budget, weigh, work) {
58
58
  running -= 1;
59
59
  weight -= itemWeight;
60
60
  finished += 1;
61
- if (finished === items.length) resolve15();
61
+ if (finished === items.length) resolve16();
62
62
  else pump();
63
63
  }, reject);
64
64
  }
@@ -287,9 +287,9 @@ function hashAsset(data, contentType2) {
287
287
  }
288
288
 
289
289
  // ../../packages/build/src/local-build-executor.ts
290
- import { access as access8, mkdtemp, readFile as readFile13, rm as rm3 } from "fs/promises";
290
+ import { access as access6, mkdtemp, readFile as readFile14, rm as rm4 } from "fs/promises";
291
291
  import { tmpdir } from "os";
292
- import { dirname as dirname4, join as join17, resolve as resolve7 } from "path";
292
+ import { dirname as dirname3, join as join17, resolve as resolve7 } from "path";
293
293
 
294
294
  // ../../packages/core/src/route-patterns.ts
295
295
  var ROUTE_PATTERN = /^\/[A-Za-z0-9\-._~%!$&()*+,;=:@/[\]]*$/;
@@ -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;
@@ -527,7 +530,10 @@ var CAPABILITIES = [
527
530
  id: "team-usage",
528
531
  domain: "teams",
529
532
  title: "\uD300 \uC0AC\uC6A9\uB7C9",
530
- routes: ["GET teams/[teamSlug]/usage"],
533
+ routes: [
534
+ "GET teams/[teamSlug]/usage",
535
+ "GET teams/[teamSlug]/usage/hosting"
536
+ ],
531
537
  ui: { kind: "built" },
532
538
  cli: { kind: "built", commands: ["naro usage"] },
533
539
  mcp: { kind: "built", tools: ["get_team_usage"] }
@@ -1873,6 +1879,19 @@ var CAPABILITIES = [
1873
1879
  cli: { kind: "built", commands: ["naro backend"] },
1874
1880
  mcp: { kind: "built", tools: ["get_backend"] }
1875
1881
  },
1882
+ {
1883
+ id: "backend-usage",
1884
+ domain: "backend",
1885
+ title: "Backend \uAC1C\uC694\uC758 \uC0AC\uC6A9\uB7C9(DB \uC4F0\uAE30\xB7\uD06C\uAE30, Storage \uC6A9\uB7C9\xB7\uC4F0\uAE30, \uD568\uC218 \uD638\uCD9C, Realtime \uBA54\uC2DC\uC9C0)",
1886
+ routes: ["GET projects/[projectId]/backend/usage"],
1887
+ ui: { kind: "built" },
1888
+ // 같은 숫자를 섹션마다 이미 돌려준다: naro db info(오늘 쓴 행·한도), naro
1889
+ // storage(용량·오늘 쓰기), naro functions(함수별 오늘 호출), naro realtime
1890
+ // (이번 달 메시지). 이 라우트는 개요가 한 번에 읽으려고 모은 것이다.
1891
+ cli: { kind: "built" },
1892
+ // get_database·get_storage·list_functions·get_realtime이 같은 숫자를 준다.
1893
+ mcp: { kind: "built" }
1894
+ },
1876
1895
  {
1877
1896
  id: "backend-enable",
1878
1897
  domain: "backend",
@@ -2248,7 +2267,7 @@ function parseDotenvDetailed(text3) {
2248
2267
  var STALL_WINDOW_MS = 10 * 6e4;
2249
2268
 
2250
2269
  // ../../packages/core/src/deployment-strategy.ts
2251
- var MIN_SUPPORTED_NEXT_MAJOR = 15;
2270
+ var MIN_SUPPORTED_NEXT_MAJOR = 16;
2252
2271
 
2253
2272
  // ../../packages/core/src/environment-guidance.ts
2254
2273
  var DATA_COMMANDS = /* @__PURE__ */ new Set([
@@ -2495,12 +2514,20 @@ var DEPLOYMENT_ERROR_CODES = [
2495
2514
  "DATABASE_FULL",
2496
2515
  /** Usage limits spec 2026-10-18 §6: a hobby team created its 100 deployments this UTC month. */
2497
2516
  "USAGE_LIMIT_REACHED",
2517
+ /** Hobby: another limit (requests, D1, Storage, Functions, Realtime) is reached, so new builds wait (spec 2026-11-07-hobby-any-limit-blocks-build). */
2518
+ "BUILDS_PAUSED",
2498
2519
  /** A hobby team has its 3 projects; no project can be added. */
2499
2520
  "PROJECT_LIMIT_REACHED",
2500
2521
  /** A hobby team has its 1 member; no member or invite can be added. */
2501
2522
  "MEMBER_LIMIT_REACHED",
2502
- /** Custom domains are not part of the Hobby plan. */
2523
+ /**
2524
+ * Custom domains were not part of the Hobby plan. No longer answered since
2525
+ * 202611070030 (one domain per hobby project); kept so an older client
2526
+ * still names it.
2527
+ */
2503
2528
  "PLAN_DOMAINS_NOT_INCLUDED",
2529
+ /** A hobby project has its 1 custom domain (with its www form); no other can be added. */
2530
+ "PLAN_DOMAIN_LIMIT_REACHED",
2504
2531
  /** Named environments (spec 2026-10-18-named-environments §15): the name is not lowercase letters, digits and single hyphens, or is over 20 characters. */
2505
2532
  "ENVIRONMENT_NAME_INVALID",
2506
2533
  /** `production` and `preview` are reserved names. */
@@ -17551,7 +17578,7 @@ var REQUESTS = {
17551
17578
  subject100: (team) => `${team} has reached its monthly request limit`,
17552
17579
  usage: (team, used, limit, month) => `${team} has used ${used} of the ${limit} requests the Hobby plan includes for ${month}.`,
17553
17580
  warning: "When it reaches the limit, requests past it may be blocked until the month ends (UTC).",
17554
- blocked: "Sites on this team answer 503 until the month ends (UTC).",
17581
+ blocked: "Sites on this team are blocked (HTTP 403 or 503) until the month ends (UTC).",
17555
17582
  running: "Sites keep running for now.",
17556
17583
  button: "View usage",
17557
17584
  footer: (team) => `You're receiving this because you're an owner or billing member of ${team} on Naro.`
@@ -17561,7 +17588,7 @@ var REQUESTS = {
17561
17588
  subject100: (team) => `${team} \uD300\uC774 \uC774\uBC88 \uB2EC \uC694\uCCAD \uD55C\uB3C4\uC5D0 \uB3C4\uB2EC\uD588\uC2B5\uB2C8\uB2E4`,
17562
17589
  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.`,
17563
17590
  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.",
17564
- 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.",
17591
+ 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).",
17565
17592
  running: "\uC9C0\uAE08\uC740 \uC0AC\uC774\uD2B8\uAC00 \uACC4\uC18D \uB3D9\uC791\uD569\uB2C8\uB2E4.",
17566
17593
  button: "\uC0AC\uC6A9\uB7C9 \uBCF4\uAE30",
17567
17594
  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.`
@@ -17571,7 +17598,7 @@ var REQUESTS = {
17571
17598
  subject100: (team) => `${team} \u30C1\u30FC\u30E0\u304C\u4ECA\u6708\u306E\u30EA\u30AF\u30A8\u30B9\u30C8\u4E0A\u9650\u306B\u9054\u3057\u307E\u3057\u305F`,
17572
17599
  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`,
17573
17600
  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",
17574
- blocked: "\u6708\u672B(UTC)\u307E\u3067\u3001\u3053\u306E\u30C1\u30FC\u30E0\u306E\u30B5\u30A4\u30C8\u306F503\u3092\u8FD4\u3057\u307E\u3059\u3002",
17601
+ 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",
17575
17602
  running: "\u73FE\u5728\u3001\u30B5\u30A4\u30C8\u306F\u5F15\u304D\u7D9A\u304D\u52D5\u4F5C\u3057\u3066\u3044\u307E\u3059\u3002",
17576
17603
  button: "\u4F7F\u7528\u91CF\u3092\u898B\u308B",
17577
17604
  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`
@@ -17610,6 +17637,187 @@ var BUILDS = {
17610
17637
  }
17611
17638
  };
17612
17639
 
17640
+ // ../../packages/next-adapter/src/index.ts
17641
+ import { fileURLToPath } from "url";
17642
+
17643
+ // ../../packages/next-adapter/src/supported.ts
17644
+ var SUPPORTED_NEXT = [
17645
+ { minor: "16.2", next: "16.2.12", routing: "@next/routing-16.2" },
17646
+ { minor: "16.3", next: "16.3.8", routing: "@next/routing-16.3" },
17647
+ { minor: "16.4", next: "16.4.5", routing: "@next/routing-16.4" }
17648
+ ];
17649
+ var VERSION = /^(\d+)\.(\d+)\.(\d+)/;
17650
+ function judgeNextVersion(version3) {
17651
+ const match = VERSION.exec(version3.trim());
17652
+ if (!match) return null;
17653
+ const major = Number(match[1]);
17654
+ const minor = Number(match[2]);
17655
+ if (major < 16 || major === 16 && minor < 2) return { kind: "too-old" };
17656
+ const entry = SUPPORTED_NEXT.find(
17657
+ (candidate) => candidate.minor === `${String(major)}.${String(minor)}`
17658
+ );
17659
+ if (entry) return { kind: "supported", entry };
17660
+ const newest = SUPPORTED_NEXT[SUPPORTED_NEXT.length - 1];
17661
+ if (!newest) throw new Error("SUPPORTED_NEXT is empty.");
17662
+ return { kind: "newer", entry: newest };
17663
+ }
17664
+ function testedRange() {
17665
+ const first = SUPPORTED_NEXT[0];
17666
+ const last = SUPPORTED_NEXT[SUPPORTED_NEXT.length - 1];
17667
+ if (!first || !last) throw new Error("SUPPORTED_NEXT is empty.");
17668
+ return `${first.minor}\u2013${last.minor}`;
17669
+ }
17670
+
17671
+ // ../../packages/next-adapter/src/paths.ts
17672
+ var OUTPUT_DIR = ".naro";
17673
+ var BUILD_RECORD = ".naro/build.json";
17674
+ var WORKER_DIR = ".naro/worker";
17675
+ var ASSETS_DIR = ".naro/assets";
17676
+ var WRANGLER_CONFIG = ".naro/worker/wrangler.jsonc";
17677
+
17678
+ // ../../packages/next-adapter/src/bundle/wrangler.ts
17679
+ import { posix } from "path";
17680
+
17681
+ // ../../packages/next-adapter/src/config-wrapper.ts
17682
+ import { access, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
17683
+ import { join as join2 } from "path";
17684
+ var PROJECT_CONFIG_BASENAME = "next.config.naro-user";
17685
+ var WRAPPABLE = {
17686
+ "next.config.js": {
17687
+ wrapper: "next.config.mjs",
17688
+ specifier: `./${PROJECT_CONFIG_BASENAME}.js`,
17689
+ typescript: false
17690
+ },
17691
+ "next.config.mjs": {
17692
+ wrapper: "next.config.mjs",
17693
+ specifier: `./${PROJECT_CONFIG_BASENAME}.mjs`,
17694
+ typescript: false
17695
+ },
17696
+ "next.config.ts": {
17697
+ wrapper: "next.config.ts",
17698
+ specifier: `./${PROJECT_CONFIG_BASENAME}`,
17699
+ typescript: true
17700
+ },
17701
+ "next.config.mts": {
17702
+ wrapper: "next.config.mts",
17703
+ specifier: `./${PROJECT_CONFIG_BASENAME}.mts`,
17704
+ typescript: true
17705
+ }
17706
+ };
17707
+ function nextConfigWrapperSource(projectSpecifier, typescript) {
17708
+ return `${typescript ? "// @ts-nocheck\n" : ""}// Written by Naro for this build only. The project's own next.config is
17709
+ // ${projectSpecifier}, unchanged; this wrapper swaps its Next.js adapter for
17710
+ // the Naro adapter (project setting "replace").
17711
+ import projectConfig from ${JSON.stringify(projectSpecifier)};
17712
+
17713
+ export default async function naroNextConfig(phase, context) {
17714
+ const config =
17715
+ typeof projectConfig === "function"
17716
+ ? await projectConfig(phase, context)
17717
+ : await projectConfig;
17718
+ const rest = { ...(config ?? {}) };
17719
+ delete rest.adapterPath;
17720
+ if (rest.experimental && typeof rest.experimental === "object") {
17721
+ rest.experimental = { ...rest.experimental };
17722
+ delete rest.experimental.adapterPath;
17723
+ }
17724
+ return { ...rest, adapterPath: process.env.NEXT_ADAPTER_PATH };
17725
+ }
17726
+ `;
17727
+ }
17728
+ async function exists(path) {
17729
+ try {
17730
+ await access(path);
17731
+ return true;
17732
+ } catch {
17733
+ return false;
17734
+ }
17735
+ }
17736
+ async function wrapNextConfig(appRoot, file2) {
17737
+ const plan = WRAPPABLE[file2];
17738
+ if (!plan) return null;
17739
+ const ext = file2.slice("next.config.".length);
17740
+ const project2 = `${PROJECT_CONFIG_BASENAME}.${ext}`;
17741
+ if (await exists(join2(appRoot, project2)))
17742
+ throw new Error(
17743
+ `${project2} already exists in the project; Naro needs that name to replace the project's Next.js adapter.`
17744
+ );
17745
+ await readFile2(join2(appRoot, file2));
17746
+ await rename(join2(appRoot, file2), join2(appRoot, project2));
17747
+ await writeFile2(
17748
+ join2(appRoot, plan.wrapper),
17749
+ nextConfigWrapperSource(plan.specifier, plan.typescript)
17750
+ );
17751
+ return { wrapper: plan.wrapper, project: project2 };
17752
+ }
17753
+ function assetPrefixWrapperSource(projectSpecifier, typescript) {
17754
+ return `${typescript ? "// @ts-nocheck\n" : ""}// Written by Naro for this build only. The project's own next.config is
17755
+ // ${projectSpecifier}, unchanged; this wrapper serves _next/static from
17756
+ // Naro's CDN unless the project sets its own assetPrefix.
17757
+ import projectConfig from ${JSON.stringify(projectSpecifier)};
17758
+
17759
+ export default async function naroNextConfig(phase, context) {
17760
+ const config =
17761
+ typeof projectConfig === "function"
17762
+ ? await projectConfig(phase, context)
17763
+ : await projectConfig;
17764
+ const rest = { ...(config ?? {}) };
17765
+ return {
17766
+ ...rest,
17767
+ assetPrefix: rest.assetPrefix || process.env.NARO_STATIC_ASSET_PREFIX,
17768
+ };
17769
+ }
17770
+ `;
17771
+ }
17772
+ var ASSET_PREFIX_ONLY_CONFIG = `// Written by Naro for this build only: the project has no next.config.
17773
+ export default { assetPrefix: process.env.NARO_STATIC_ASSET_PREFIX };
17774
+ `;
17775
+ var CONFIG_LOOKUP = [
17776
+ "next.config.js",
17777
+ "next.config.mjs",
17778
+ "next.config.ts",
17779
+ "next.config.mts",
17780
+ "next.config.cjs"
17781
+ ];
17782
+ async function wrapNextConfigForAssetPrefix(appRoot, prefix) {
17783
+ if (!prefix) return "skipped";
17784
+ let file2;
17785
+ for (const name of CONFIG_LOOKUP)
17786
+ if (await exists(join2(appRoot, name))) {
17787
+ file2 = name;
17788
+ break;
17789
+ }
17790
+ if (!file2) {
17791
+ await writeFile2(join2(appRoot, "next.config.mjs"), ASSET_PREFIX_ONLY_CONFIG);
17792
+ return "created";
17793
+ }
17794
+ const plan = WRAPPABLE[file2];
17795
+ if (!plan) return "skipped";
17796
+ const project2 = `${PROJECT_CONFIG_BASENAME}.${file2.slice("next.config.".length)}`;
17797
+ if (await exists(join2(appRoot, project2))) return "skipped";
17798
+ await rename(join2(appRoot, file2), join2(appRoot, project2));
17799
+ try {
17800
+ await writeFile2(
17801
+ join2(appRoot, plan.wrapper),
17802
+ assetPrefixWrapperSource(plan.specifier, plan.typescript)
17803
+ );
17804
+ } catch (error51) {
17805
+ await rename(join2(appRoot, project2), join2(appRoot, file2));
17806
+ throw error51;
17807
+ }
17808
+ return "wrapped";
17809
+ }
17810
+
17811
+ // ../../packages/next-adapter/src/index.ts
17812
+ function nextAdapterPath(base2 = import.meta.url) {
17813
+ if (!base2) return void 0;
17814
+ try {
17815
+ return fileURLToPath(new URL("../dist/adapter.cjs", base2));
17816
+ } catch {
17817
+ return void 0;
17818
+ }
17819
+ }
17820
+
17613
17821
  // ../../packages/build/src/next-version.ts
17614
17822
  var NEXT_RELEASE_DATES = {
17615
17823
  16: "2025-10-21",
@@ -17619,7 +17827,6 @@ var NEXT_RELEASE_DATES = {
17619
17827
  12: "2021-10-26"
17620
17828
  };
17621
17829
  var SUPPORT_WINDOW_YEARS = 2;
17622
- var VERIFIED_15_FLOOR = { major: 15, minor: 5, patch: 21 };
17623
17830
  var VERIFIED_16_FLOOR = { major: 16, minor: 2, patch: 11 };
17624
17831
  function parseNextVersion(raw) {
17625
17832
  const match = /^\s*(\d+)\.(\d+)\.(\d+)/.exec(raw);
@@ -17647,7 +17854,6 @@ function compare(a, b) {
17647
17854
  return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
17648
17855
  }
17649
17856
  function withinVerifiedRange(version3) {
17650
- if (version3.major === 15) return compare(version3, VERIFIED_15_FLOOR) >= 0;
17651
17857
  if (version3.major === 16) return compare(version3, VERIFIED_16_FLOOR) >= 0;
17652
17858
  return version3.major > 16;
17653
17859
  }
@@ -17663,34 +17869,34 @@ function classifyNextVersion(raw, now) {
17663
17869
  reason: `Could not read a Next.js version from "${raw}".`
17664
17870
  };
17665
17871
  }
17666
- const windowEnd = supportEndsAt(version3.major);
17667
- if (!windowEnd || version3.major < VERIFIED_15_FLOOR.major) {
17872
+ if (version3.major < MIN_SUPPORTED_NEXT_MAJOR) {
17668
17873
  return {
17669
17874
  status: "too-old",
17670
17875
  version: version3,
17671
- reason: `Next.js ${raw} is too old to deploy. Naro deploys Next.js 15 or newer. Upgrade to Next.js 16.`
17876
+ reason: `Next.js ${raw} is too old to deploy. Naro deploys Next.js ${String(MIN_SUPPORTED_NEXT_MAJOR)} or newer. Upgrade to Next.js 16.`
17672
17877
  };
17673
17878
  }
17674
- if (now > windowEnd) {
17879
+ const windowEnd = supportEndsAt(version3.major);
17880
+ if (windowEnd && now > windowEnd) {
17675
17881
  return {
17676
- status: "needs-unsupported-flag",
17882
+ status: "past-support-window",
17677
17883
  version: version3,
17678
- reason: `Next.js ${version3.major} left its support window on ${formatDate(windowEnd)}. The build continues, but unsupported releases stop receiving security patches \u2014 upgrade to Next.js 16. See https://nextjs.org/support-policy`
17884
+ reason: `Next.js ${version3.major} left its support window on ${formatDate(windowEnd)}. The build continues, but unsupported releases stop receiving security patches \u2014 upgrade Next.js. See https://nextjs.org/support-policy`
17679
17885
  };
17680
17886
  }
17681
17887
  if (!withinVerifiedRange(version3)) {
17682
17888
  return {
17683
- status: "outside-peer-range",
17889
+ status: "outside-verified-range",
17684
17890
  version: version3,
17685
- reason: `Next.js ${raw} is outside the range Naro verifies deploys on (>=15.5.21 <16 || >=16.2.11). The build may fail in ways Naro cannot explain.`
17891
+ reason: `Next.js ${raw} is outside the range Naro verifies deploys on (>=16.2.11). The build may fail in ways Naro cannot explain.`
17686
17892
  };
17687
17893
  }
17688
17894
  return { status: "supported", version: version3, reason: "" };
17689
17895
  }
17690
17896
 
17691
17897
  // ../../packages/build/src/package-manager.ts
17692
- import { access, readFile as readFile2 } from "fs/promises";
17693
- import { join as join2 } from "path";
17898
+ import { access as access2, readFile as readFile3 } from "fs/promises";
17899
+ import { join as join3 } from "path";
17694
17900
  function cacheEnv(dir, manager) {
17695
17901
  return {
17696
17902
  ...manager === "pnpm" ? { npm_config_store_dir: dir } : {},
@@ -17710,9 +17916,9 @@ function withSharedCache(spec, dir) {
17710
17916
  addEnv: { ...spec.addEnv, ...cacheEnv(dir, spec.name) }
17711
17917
  };
17712
17918
  }
17713
- async function exists(path) {
17919
+ async function exists2(path) {
17714
17920
  try {
17715
- await access(path);
17921
+ await access2(path);
17716
17922
  return true;
17717
17923
  } catch {
17718
17924
  return false;
@@ -17720,7 +17926,7 @@ async function exists(path) {
17720
17926
  }
17721
17927
  async function usesModernYarn(root) {
17722
17928
  try {
17723
- const raw = await readFile2(join2(root, "package.json"), "utf8");
17929
+ const raw = await readFile3(join3(root, "package.json"), "utf8");
17724
17930
  const packageJson = JSON.parse(raw);
17725
17931
  if (typeof packageJson.packageManager !== "string") return false;
17726
17932
  const version3 = /^yarn@(\d+)/.exec(packageJson.packageManager)?.[1];
@@ -17730,7 +17936,7 @@ async function usesModernYarn(root) {
17730
17936
  }
17731
17937
  }
17732
17938
  async function detectPackageManager(root) {
17733
- if (await exists(join2(root, "pnpm-lock.yaml"))) {
17939
+ if (await exists2(join3(root, "pnpm-lock.yaml"))) {
17734
17940
  return {
17735
17941
  name: "pnpm",
17736
17942
  install: ["pnpm", "install", "--frozen-lockfile"],
@@ -17741,7 +17947,7 @@ async function detectPackageManager(root) {
17741
17947
  addArgs: (specs) => ["pnpm", "add", ...specs]
17742
17948
  };
17743
17949
  }
17744
- if (await exists(join2(root, "package-lock.json"))) {
17950
+ if (await exists2(join3(root, "package-lock.json"))) {
17745
17951
  return {
17746
17952
  name: "npm",
17747
17953
  install: ["npm", "ci"],
@@ -17767,7 +17973,7 @@ async function detectPackageManager(root) {
17767
17973
  sharedCacheAddArgs: ["--prefer-offline"]
17768
17974
  };
17769
17975
  }
17770
- if (await exists(join2(root, "yarn.lock"))) {
17976
+ if (await exists2(join3(root, "yarn.lock"))) {
17771
17977
  const modern = await usesModernYarn(root);
17772
17978
  return {
17773
17979
  name: "yarn",
@@ -17788,114 +17994,13 @@ async function detectPackageManager(root) {
17788
17994
  );
17789
17995
  }
17790
17996
 
17791
- // ../../packages/build/src/proxy-to-middleware.ts
17792
- import { access as access2, readFile as readFile3, rename, writeFile as writeFile2 } from "fs/promises";
17793
- import { join as join3 } from "path";
17794
- var EXTENSIONS = ["ts", "tsx", "js", "jsx"];
17795
- var DIRECTORIES = ["", "src"];
17796
- async function exists2(path) {
17797
- try {
17798
- await access2(path);
17799
- return true;
17800
- } catch {
17801
- return false;
17802
- }
17803
- }
17804
- function renameExport(source) {
17805
- return source.replace(
17806
- /(export\s+(?:default\s+)?(?:async\s+)?function\s+)proxy\b/g,
17807
- "$1middleware"
17808
- ).replace(/(export\s+const\s+)proxy\b/g, "$1middleware");
17809
- }
17810
- async function convertProxyToMiddleware(appRoot) {
17811
- for (const dir of DIRECTORIES) {
17812
- for (const ext of EXTENSIONS) {
17813
- const rel = join3(dir, `proxy.${ext}`);
17814
- const proxyPath = join3(appRoot, rel);
17815
- if (!await exists2(proxyPath)) continue;
17816
- const middlewarePath = join3(appRoot, dir, `middleware.${ext}`);
17817
- if (await exists2(middlewarePath)) return { converted: false };
17818
- const source = await readFile3(proxyPath, "utf8");
17819
- await writeFile2(middlewarePath, renameExport(source));
17820
- await rename(proxyPath, `${proxyPath}.converted`);
17821
- return { converted: true, file: rel };
17822
- }
17823
- }
17824
- return { converted: false };
17825
- }
17826
-
17827
17997
  // ../../packages/build/src/vinext.ts
17828
- import { access as access5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
17829
- import { join as join6, resolve as resolve2 } from "path";
17998
+ import { access as access3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
17999
+ import { join as join6, resolve } from "path";
17830
18000
 
17831
- // ../../packages/build/src/open-next.ts
17832
- import { access as access3, readdir, readFile as readFile4, realpath } from "fs/promises";
17833
- import {
17834
- basename,
17835
- dirname,
17836
- extname,
17837
- isAbsolute,
17838
- join as join4,
17839
- relative,
17840
- resolve,
17841
- sep
17842
- } from "path";
17843
- function parseJsonc(source) {
17844
- let output = "";
17845
- let index = 0;
17846
- let inString = false;
17847
- let escaped = false;
17848
- while (index < source.length) {
17849
- const current = source[index] ?? "";
17850
- const next = source[index + 1] ?? "";
17851
- if (inString) {
17852
- output += current;
17853
- if (escaped) escaped = false;
17854
- else if (current === "\\") escaped = true;
17855
- else if (current === '"') inString = false;
17856
- index += 1;
17857
- continue;
17858
- }
17859
- if (current === '"') {
17860
- inString = true;
17861
- output += current;
17862
- index += 1;
17863
- continue;
17864
- }
17865
- if (current === "/" && next === "/") {
17866
- index += 2;
17867
- while (index < source.length && source[index] !== "\n") index += 1;
17868
- continue;
17869
- }
17870
- if (current === "/" && next === "*") {
17871
- index += 2;
17872
- while (index < source.length && !(source[index] === "*" && source[index + 1] === "/"))
17873
- index += 1;
17874
- index += 2;
17875
- continue;
17876
- }
17877
- output += current;
17878
- index += 1;
17879
- }
17880
- return JSON.parse(output.replace(/,\s*([}\]])/g, "$1"));
17881
- }
17882
- function confinedPath(root, configuredPath, label2, base2 = root) {
17883
- if (isAbsolute(configuredPath)) {
17884
- throw new DeploymentError(
17885
- "ARTIFACT_INVALID",
17886
- `${label2} must be relative to the application root.`
17887
- );
17888
- }
17889
- const resolved = resolve(base2, configuredPath);
17890
- const relation = relative(root, resolved);
17891
- if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
17892
- throw new DeploymentError(
17893
- "ARTIFACT_INVALID",
17894
- `${label2} escapes the application root.`
17895
- );
17896
- }
17897
- return resolved;
17898
- }
18001
+ // ../../packages/build/src/artifact-files.ts
18002
+ import { readdir, readFile as readFile4, realpath } from "fs/promises";
18003
+ import { extname, isAbsolute, join as join4, relative, sep } from "path";
17899
18004
  function contentType(path) {
17900
18005
  switch (extname(path).toLowerCase()) {
17901
18006
  case ".html":
@@ -17970,7 +18075,7 @@ async function assertInsideOnDisk(appRoot, directory, label2) {
17970
18075
  );
17971
18076
  }
17972
18077
  }
17973
- async function listArtifactFiles(root, label2 = "OpenNext", appRoot) {
18078
+ async function listArtifactFiles(root, label2 = "Build", appRoot) {
17974
18079
  if (appRoot !== void 0) await assertInsideOnDisk(appRoot, root, label2);
17975
18080
  const files = [];
17976
18081
  async function visit(directory) {
@@ -17998,131 +18103,12 @@ async function listArtifactFiles(root, label2 = "OpenNext", appRoot) {
17998
18103
  await visit(root);
17999
18104
  return files;
18000
18105
  }
18001
- async function findWranglerConfig(root, configuredPath) {
18002
- if (configuredPath) {
18003
- const resolved = isAbsolute(configuredPath) ? configuredPath : resolve(root, configuredPath);
18004
- const relation = relative(root, resolved);
18005
- if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
18006
- throw new DeploymentError(
18007
- "ARTIFACT_INVALID",
18008
- "Wrangler config escapes the application root."
18009
- );
18010
- }
18011
- await access3(resolved);
18012
- return resolved;
18013
- }
18014
- for (const name of ["wrangler.jsonc", "wrangler.json"]) {
18015
- const path = join4(root, name);
18016
- try {
18017
- await access3(path);
18018
- return path;
18019
- } catch {
18020
- }
18021
- }
18022
- throw new DeploymentError(
18023
- "ARTIFACT_INVALID",
18024
- "A wrangler.jsonc or wrangler.json file is required."
18025
- );
18026
- }
18027
- var OPEN_NEXT_LAYOUT = {
18028
- outputRoot: ".open-next",
18029
- bundleDir: ".open-next/worker-bundle"
18030
- };
18031
- async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
18032
- const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
18033
- return readWorkerArtifact(appRoot, identity, {
18034
- wranglerConfigPath: configPath,
18035
- ...OPEN_NEXT_LAYOUT
18036
- });
18037
- }
18038
- async function readWorkerArtifact(appRoot, identity, layout) {
18039
- const listFiles = (root) => listArtifactFiles(root, "OpenNext", appRoot);
18040
- try {
18041
- const configPath = await findWranglerConfig(
18042
- appRoot,
18043
- layout.wranglerConfigPath
18044
- );
18045
- const metadata = parseJsonc(
18046
- await readFile4(configPath, "utf8")
18047
- );
18048
- if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string") {
18049
- throw new DeploymentError(
18050
- "ARTIFACT_INVALID",
18051
- "Wrangler metadata must declare main and compatibility_date."
18052
- );
18053
- }
18054
- const configDir = dirname(configPath);
18055
- const outputRoot = confinedPath(appRoot, layout.outputRoot, "Output root");
18056
- const entrypointPath = confinedPath(
18057
- appRoot,
18058
- metadata.main,
18059
- "Worker entrypoint",
18060
- configDir
18061
- );
18062
- const entryRelation = relative(outputRoot, entrypointPath);
18063
- if (entryRelation === ".." || entryRelation.startsWith(`..${sep}`) || isAbsolute(entryRelation)) {
18064
- throw new DeploymentError(
18065
- "ARTIFACT_INVALID",
18066
- `Worker entrypoint must be inside ${layout.outputRoot}.`
18067
- );
18068
- }
18069
- await access3(entrypointPath);
18070
- const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(
18071
- appRoot,
18072
- metadata.assets.directory,
18073
- "Assets directory",
18074
- configDir
18075
- ) : join4(outputRoot, "assets");
18076
- const assetsRelation = relative(outputRoot, configuredAssets);
18077
- if (assetsRelation === ".." || assetsRelation.startsWith(`..${sep}`) || isAbsolute(assetsRelation)) {
18078
- throw new DeploymentError(
18079
- "ARTIFACT_INVALID",
18080
- `Assets directory must be inside ${layout.outputRoot}.`
18081
- );
18082
- }
18083
- const bundleRoot = confinedPath(appRoot, layout.bundleDir, "Bundle dir");
18084
- let bundleExists = true;
18085
- try {
18086
- await access3(bundleRoot);
18087
- } catch {
18088
- bundleExists = false;
18089
- }
18090
- const bundleFiles = bundleExists ? await listFiles(bundleRoot) : null;
18091
- const outputFiles = bundleFiles ?? await listFiles(outputRoot);
18092
- const entrypointName = bundleFiles ? basename(entrypointPath) : relative(outputRoot, entrypointPath).split(sep).join("/");
18093
- const assetsPrefix = `${relative(outputRoot, configuredAssets).split(sep).join("/")}/`;
18094
- const entrypoint = outputFiles.find((file2) => file2.name === entrypointName);
18095
- if (!entrypoint)
18096
- throw new DeploymentError(
18097
- "ARTIFACT_INVALID",
18098
- "Worker entrypoint is missing from the build output."
18099
- );
18100
- return {
18101
- ...identity,
18102
- entrypoint,
18103
- modules: outputFiles.filter(
18104
- (file2) => file2.name !== entrypointName && !file2.name.endsWith(".map") && file2.name !== "README.md" && (bundleFiles !== null || !file2.name.startsWith(assetsPrefix))
18105
- ),
18106
- assets: (await listFiles(configuredAssets)).map((file2) => ({ ...file2 })),
18107
- compatibilityDate: metadata.compatibility_date,
18108
- compatibilityFlags: Array.isArray(metadata.compatibility_flags) ? metadata.compatibility_flags.filter(
18109
- (flag) => typeof flag === "string"
18110
- ) : [],
18111
- assetsBinding: typeof metadata.assets?.binding === "string" ? metadata.assets.binding : "ASSETS"
18112
- };
18113
- } catch (error51) {
18114
- if (error51 instanceof DeploymentError) throw error51;
18115
- throw new DeploymentError(
18116
- "ARTIFACT_INVALID",
18117
- "Could not read the Worker build output.",
18118
- { cause: error51 }
18119
- );
18120
- }
18121
- }
18106
+
18107
+ // ../../packages/build/src/isr-cache-source.generated.ts
18108
+ var NARO_ISR_CACHE_SOURCE = '// Written by naro: vinext\'s data cache, shared across isolates and colos\n// through the dispatcher (spec 2026-11-06-isr-shared-cache \xA75).\n//\n// Layer 1 is this isolate\'s memory, with vinext\'s MemoryCacheHandler\n// semantics. Layer 2 is the store the dispatcher hands this request as\n// `ctx.props.NARO_CACHE` (an RPC stub, valid for this request only \u2014 never\n// kept). With no store (a build-time prerender, `vinext dev`, an older\n// dispatcher) this is a memory cache, as before.\n\nconst MEMORY_MAX_BYTES = 52_428_800;\nconst TAG_MEMO_TTL_MS = 5_000;\nconst TAG_MEMO_MAX = 10_000;\nconst ENTRY_MAX_CHARS = 2_097_152;\nconst ENTRY_MAX_TAGS = 256;\nconst TAG_MAX_CHARS = 1_024;\nconst TAG_LOOKUP_CHUNK = 256;\nconst SERIAL_VERSION = 1;\nconst WARN_INTERVAL_MS = 60_000;\nconst CONTEXT = Symbol.for("__cloudflare-context__");\n\n/** The store for the current request, or null. */\nexport function currentNaroCacheStore() {\n const context = globalThis[CONTEXT];\n return context?.ctx?.props?.NARO_CACHE ?? null;\n}\n\nfunction currentWaitUntil() {\n const ctx = globalThis[CONTEXT]?.ctx;\n return typeof ctx?.waitUntil === "function"\n ? (promise) => ctx.waitUntil(promise)\n : null;\n}\n\nconst isString = (value) => typeof value === "string";\n\nfunction unique(values) {\n return [...new Set(values)];\n}\n\nfunction readStringArray(ctx, field) {\n const value = ctx?.[field];\n return Array.isArray(value) ? value.filter(isString) : [];\n}\n\nfunction readPositiveNumber(ctx, field) {\n const value = ctx?.[field];\n return typeof value === "number" && value > 0 ? value : undefined;\n}\n\nfunction cacheControlOf(ctx) {\n const value = ctx?.cacheControl;\n return value !== null && typeof value === "object" ? value : undefined;\n}\n\nfunction readCacheControlNumber(ctx, field) {\n const value = cacheControlOf(ctx)?.[field] ?? ctx?.[field];\n return typeof value === "number" ? value : undefined;\n}\n\nfunction readCacheControlRevalidate(ctx) {\n const value = cacheControlOf(ctx)?.revalidate ?? ctx?.revalidate;\n return typeof value === "number" || value === false ? value : undefined;\n}\n\nfunction stringMapSize(map) {\n if (!map) return 0;\n let size = 0;\n for (const [key, value] of Object.entries(map)) {\n size += key.length;\n if (Array.isArray(value)) for (const item of value) size += item.length;\n else size += String(value).length;\n }\n return size;\n}\n\n/** vinext\'s estimate (shims/cache-handler.js), so the 50 MiB means the same. */\nfunction valueSize(value) {\n if (value === null || value === undefined) return 25;\n switch (value.kind) {\n case "FETCH":\n return JSON.stringify(value.data ?? "").length;\n case "PAGES":\n return (\n value.html.length +\n JSON.stringify(value.pageData ?? {}).length +\n stringMapSize(value.headers)\n );\n case "APP_PAGE":\n return (\n value.html.length +\n (value.rscData?.byteLength ?? 0) +\n (value.postponed?.length ?? 0) +\n stringMapSize(value.headers)\n );\n case "APP_ROUTE":\n return value.body.byteLength + stringMapSize(value.headers);\n case "REDIRECT":\n return JSON.stringify(value.props ?? {}).length;\n case "IMAGE":\n return (\n value.buffer.byteLength + value.extension.length + value.etag.length\n );\n default:\n return JSON.stringify(value).length;\n }\n}\n\nfunction entrySize(entry) {\n return (\n valueSize(entry.value) +\n entry.tags.reduce((sum, tag) => sum + tag.length, 0) +\n 64\n );\n}\n\nfunction toBase64(bytes) {\n let binary = "";\n for (let i = 0; i < bytes.length; i += 0x8000)\n binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));\n return btoa(binary);\n}\n\nfunction fromBase64(text) {\n const binary = atob(text);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return bytes;\n}\n\n/** One entry as the text layer 2 keeps. ArrayBuffers go as base64. */\nexport function serializeEntry(entry) {\n return JSON.stringify({ v: SERIAL_VERSION, ...entry }, (_key, value) => {\n if (value instanceof ArrayBuffer)\n return { __naroBytes: toBase64(new Uint8Array(value)) };\n if (ArrayBuffer.isView(value))\n return {\n __naroU8: toBase64(\n new Uint8Array(value.buffer, value.byteOffset, value.byteLength),\n ),\n };\n return value;\n });\n}\n\n/** The entry back, or null for another serial version. Throws on bad JSON. */\nexport function deserializeEntry(text) {\n const parsed = JSON.parse(text, (_key, value) => {\n if (value !== null && typeof value === "object") {\n if (isString(value.__naroBytes))\n return fromBase64(value.__naroBytes).buffer;\n if (isString(value.__naroU8)) return fromBase64(value.__naroU8);\n }\n return value;\n });\n if (\n parsed === null ||\n typeof parsed !== "object" ||\n parsed.v !== SERIAL_VERSION ||\n typeof parsed.lastModified !== "number" ||\n !Array.isArray(parsed.tags)\n )\n return null;\n return {\n value: parsed.value ?? null,\n tags: parsed.tags.filter(isString),\n lastModified: parsed.lastModified,\n revalidateAt:\n typeof parsed.revalidateAt === "number" ? parsed.revalidateAt : null,\n expireAt: typeof parsed.expireAt === "number" ? parsed.expireAt : null,\n cacheControl: parsed.cacheControl,\n };\n}\n\nfunction errorMessage(error) {\n return error instanceof Error && error.message\n ? error.message\n : String(error);\n}\n\nexport class NaroIsrCacheHandler {\n constructor({\n storeFor = currentNaroCacheStore,\n now = Date.now,\n waitUntilFor = currentWaitUntil,\n warn = (message) => console.warn(message),\n maxMemoryBytes = MEMORY_MAX_BYTES,\n tagMemoTtlMs = TAG_MEMO_TTL_MS,\n } = {}) {\n this.storeFor = storeFor;\n this.now = now;\n this.waitUntilFor = waitUntilFor;\n this.warn = warn;\n this.maxMemoryBytes = maxMemoryBytes;\n this.tagMemoTtlMs = tagMemoTtlMs;\n this.entries = new Map();\n this.memoryBytes = 0;\n this.tagMemo = new Map();\n this.lastWarnAt = -Infinity;\n }\n\n warnOnce(error) {\n const now = this.now();\n if (now - this.lastWarnAt < WARN_INTERVAL_MS) return;\n this.lastWarnAt = now;\n this.warn(`[naro-isr] shared cache unavailable: ${errorMessage(error)}`);\n }\n\n forget(key) {\n const existing = this.entries.get(key);\n if (!existing) return;\n this.memoryBytes -= existing.size;\n this.entries.delete(key);\n }\n\n remember(key, entry) {\n const size = entrySize(entry);\n this.forget(key);\n if (this.maxMemoryBytes <= 0 || size > this.maxMemoryBytes) return;\n this.entries.set(key, { ...entry, size });\n this.memoryBytes += size;\n while (this.memoryBytes > this.maxMemoryBytes) {\n const oldest = this.entries.keys().next().value;\n if (oldest === undefined) break;\n this.forget(oldest);\n }\n }\n\n setMemo(tag, time, fetchedAt) {\n this.tagMemo.delete(tag);\n this.tagMemo.set(tag, { time, fetchedAt });\n while (this.tagMemo.size > TAG_MEMO_MAX) {\n const oldest = this.tagMemo.keys().next().value;\n if (oldest === undefined) break;\n this.tagMemo.delete(oldest);\n }\n }\n\n /** Each tag\'s last revalidation as far as this isolate knows (ms, 0 = never). */\n async tagTimes(tags, store) {\n const now = this.now();\n const times = new Map();\n const missing = [];\n for (const tag of tags) {\n const memo = this.tagMemo.get(tag);\n // A tag over the cap never reaches layer 2 (set() does not write such\n // entries, the store ignores such tags): this isolate\'s own word only.\n if (\n memo &&\n (!store ||\n tag.length > TAG_MAX_CHARS ||\n now - memo.fetchedAt < this.tagMemoTtlMs)\n )\n times.set(tag, memo.time);\n else if (!store || tag.length > TAG_MAX_CHARS) times.set(tag, 0);\n else missing.push(tag);\n }\n if (missing.length === 0) return times;\n const chunks = [];\n for (let i = 0; i < missing.length; i += TAG_LOOKUP_CHUNK)\n chunks.push(missing.slice(i, i + TAG_LOOKUP_CHUNK));\n await Promise.all(\n chunks.map(async (chunk) => {\n let remote = null;\n try {\n remote = (await store.tagTimes(chunk)) ?? {};\n } catch (error) {\n this.warnOnce(error);\n }\n for (const tag of chunk) {\n const known = this.tagMemo.get(tag)?.time ?? 0;\n // A tag the store did not answer (failed read, or cut off by its\n // cap) keeps its old memo and fetch time, so the next get asks again.\n if (!remote || typeof remote[tag] !== "number") {\n times.set(tag, known);\n continue;\n }\n // Never forget a revalidation this isolate made: KV can answer an\n // older value for up to a minute after the write.\n const time = Math.max(known, remote[tag]);\n this.setMemo(tag, time, now);\n times.set(tag, time);\n }\n }),\n );\n return times;\n }\n\n /**\n * "valid", "own" (one of the entry\'s own tags was revalidated) or "soft"\n * (only a tag of the path asking was). vinext\'s MemoryCacheHandler evicts\n * only for an own tag: fetch entries are shared by several pages.\n */\n async validity(entry, softTags, store) {\n const tags = unique([...entry.tags, ...softTags]);\n if (tags.length === 0) return "valid";\n const times = await this.tagTimes(tags, store);\n const revalidated = (tag) => (times.get(tag) ?? 0) >= entry.lastModified;\n if (entry.tags.some(revalidated)) return "own";\n return softTags.some(revalidated) ? "soft" : "valid";\n }\n\n timing(entry, ctx) {\n const now = this.now();\n if (entry.expireAt !== null && now > entry.expireAt) return "expired";\n const requested = readPositiveNumber(ctx, "revalidate");\n const requestedAt =\n requested === undefined ? null : entry.lastModified + requested * 1000;\n if (\n (entry.revalidateAt !== null && now > entry.revalidateAt) ||\n (requestedAt !== null && now > requestedAt)\n )\n return "stale";\n return undefined;\n }\n\n answer(key, entry, cacheState) {\n // Most recently used goes last.\n const kept = this.entries.get(key);\n if (kept) {\n this.entries.delete(key);\n this.entries.set(key, kept);\n }\n return {\n lastModified: entry.lastModified,\n value: entry.value,\n ...(cacheState === undefined ? {} : { cacheState }),\n cacheControl: entry.cacheControl,\n };\n }\n\n async readRemote(store, key) {\n try {\n const found = await store.getEntry(key);\n if (!found || !isString(found.body)) return null;\n return deserializeEntry(found.body);\n } catch (error) {\n this.warnOnce(error);\n return null;\n }\n }\n\n async get(key, ctx) {\n const store = this.storeFor();\n const softTags = readStringArray(ctx, "softTags");\n const local = this.entries.get(key) ?? null;\n if (local) {\n const localValidity = await this.validity(local, softTags, store);\n if (localValidity === "valid") {\n const cacheState = this.timing(local, ctx);\n if (cacheState === undefined || !store)\n return this.answer(key, local, cacheState);\n // Stale here: another isolate may already have rendered a newer one.\n const remote = await this.readRemote(store, key);\n if (\n remote &&\n remote.lastModified > local.lastModified &&\n (await this.validity(remote, softTags, store)) === "valid"\n ) {\n this.remember(key, remote);\n return this.answer(key, remote, this.timing(remote, ctx));\n }\n return this.answer(key, local, cacheState);\n }\n // Only its own tags evict; a soft tag just makes this get a miss.\n if (localValidity === "own") this.forget(key);\n }\n if (!store) return null;\n const remote = await this.readRemote(store, key);\n if (!remote || (await this.validity(remote, softTags, store)) !== "valid")\n return null;\n this.remember(key, remote);\n return this.answer(key, remote, this.timing(remote, ctx));\n }\n\n async set(key, data, ctx) {\n const tags = unique([\n ...(data && Array.isArray(data.tags) ? data.tags.filter(isString) : []),\n ...readStringArray(ctx, "tags"),\n ]);\n let revalidate = readCacheControlRevalidate(ctx);\n const expire = readCacheControlNumber(ctx, "expire");\n const stale = readCacheControlNumber(ctx, "stale");\n if (data && typeof data.revalidate === "number")\n revalidate = data.revalidate;\n else if (data && data.revalidate === false) revalidate ??= false;\n if (revalidate === 0) return;\n const now = this.now();\n const entry = {\n value: data,\n tags,\n lastModified: now,\n revalidateAt:\n typeof revalidate === "number" && revalidate > 0\n ? now + revalidate * 1000\n : null,\n expireAt:\n typeof expire === "number" && expire > 0 ? now + expire * 1000 : null,\n cacheControl:\n typeof revalidate === "number" || revalidate === false\n ? {\n revalidate,\n ...(expire === undefined ? {} : { expire }),\n ...(stale === undefined ? {} : { stale }),\n }\n : undefined,\n };\n this.remember(key, entry);\n const store = this.storeFor();\n if (!store) return;\n if (\n tags.length > ENTRY_MAX_TAGS ||\n tags.some((tag) => tag.length > TAG_MAX_CHARS)\n )\n return;\n // The serialized text is never shorter than the size estimate (base64 is\n // 4/3 of the bytes, and the field names come on top), so an entry over the\n // limit by estimate is dropped without being serialized, which for a big\n // body would cost several times its size in memory.\n if (entrySize(entry) > ENTRY_MAX_CHARS) return;\n const body = serializeEntry(entry);\n if (body.length > ENTRY_MAX_CHARS) return;\n const write = Promise.resolve()\n .then(() => store.putEntry(key, body, { lastModified: now, tags }))\n .catch((error) => this.warnOnce(error));\n const waitUntil = this.waitUntilFor();\n if (waitUntil) waitUntil(write);\n else await write;\n }\n\n async revalidateTag(tags) {\n const list = unique((Array.isArray(tags) ? tags : [tags]).filter(isString));\n if (list.length === 0) return;\n const now = this.now();\n for (const tag of list)\n this.setMemo(tag, Math.max(this.tagMemo.get(tag)?.time ?? 0, now), now);\n const store = this.storeFor();\n if (!store) return;\n try {\n const recorded = await store.revalidateTags(list);\n if (typeof recorded === "number") {\n const fetchedAt = this.now();\n for (const tag of list)\n this.setMemo(\n tag,\n Math.max(this.tagMemo.get(tag)?.time ?? 0, recorded),\n fetchedAt,\n );\n }\n } catch (error) {\n this.warnOnce(error);\n }\n }\n\n resetRequestCache() {}\n}\n\n/** vinext calls this once per isolate with `{ env, options }`. */\nexport default function createNaroIsrCache({ options } = {}) {\n const ttl = options?.tagMemoTtlMs;\n return new NaroIsrCacheHandler(\n Number.isInteger(ttl) && ttl >= 0 && ttl <= 60_000\n ? { tagMemoTtlMs: ttl }\n : {},\n );\n}\n';
18122
18109
 
18123
18110
  // ../../packages/build/src/platform-config.ts
18124
- import { access as access4, writeFile as writeFile3 } from "fs/promises";
18125
- import { createRequire } from "module";
18111
+ import { writeFile as writeFile3 } from "fs/promises";
18126
18112
  import { join as join5 } from "path";
18127
18113
  var COMPATIBILITY_DATE = "2026-08-19";
18128
18114
  var NEXT_CONFIG_FILES = [
@@ -18131,14 +18117,6 @@ var NEXT_CONFIG_FILES = [
18131
18117
  "next.config.cjs",
18132
18118
  "next.config.ts"
18133
18119
  ];
18134
- async function pathExists(path) {
18135
- try {
18136
- await access4(path);
18137
- return true;
18138
- } catch {
18139
- return false;
18140
- }
18141
- }
18142
18120
  var BUILD_NPMRC = [
18143
18121
  "# Host package-manager credentials are intentionally unavailable.",
18144
18122
  "audit=false",
@@ -18153,61 +18131,40 @@ async function createPackageManagerConfig(appRoot, writer = async (path, content
18153
18131
  await writer(packageManagerConfigPath, BUILD_NPMRC);
18154
18132
  return packageManagerConfigPath;
18155
18133
  }
18156
- async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
18157
- await writeFile3(path, contents, "utf8");
18158
- }, cloudflareModulePath = createRequire(import.meta.url).resolve(
18159
- "@opennextjs/cloudflare"
18160
- ), exists5 = pathExists) {
18161
- const openNextConfigPath = join5(appRoot, ".next-deploy-open-next.config.mjs");
18162
- const wranglerConfigPath = join5(appRoot, ".next-deploy-wrangler.json");
18163
- const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
18164
- let hasNextConfig = false;
18165
- for (const name of NEXT_CONFIG_FILES) {
18166
- if (await exists5(join5(appRoot, name))) {
18167
- hasNextConfig = true;
18168
- break;
18134
+
18135
+ // ../../packages/build/src/wrangler-output.ts
18136
+ function globToRegExp(glob) {
18137
+ let source = "";
18138
+ for (let index = 0; index < glob.length; index += 1) {
18139
+ const char = glob[index];
18140
+ if (char !== "*") {
18141
+ source += char?.replace(/[.+?^${}()|[\]\\]/g, String.raw`\$&`) ?? "";
18142
+ continue;
18143
+ }
18144
+ if (glob[index + 1] !== "*") {
18145
+ source += "[^/]*";
18146
+ continue;
18147
+ }
18148
+ if (glob[index + 2] === "/") {
18149
+ source += "(?:[^/]*/)*";
18150
+ index += 2;
18151
+ } else {
18152
+ source += ".*";
18153
+ index += 1;
18169
18154
  }
18170
18155
  }
18171
- if (!hasNextConfig) {
18172
- await writer(
18173
- join5(appRoot, "next.config.mjs"),
18174
- 'export default { output: "standalone" };\n'
18175
- );
18176
- }
18177
- await writer(
18178
- openNextConfigPath,
18179
- `import { defineCloudflareConfig } from ${JSON.stringify(cloudflareModulePath)};
18180
-
18181
- export default defineCloudflareConfig({});
18182
- `
18183
- );
18184
- await writer(
18185
- wranglerConfigPath,
18186
- `${JSON.stringify(
18187
- {
18188
- name: `next-deploy-${safeDeploymentId}`,
18189
- main: ".open-next/worker.js",
18190
- compatibility_date: COMPATIBILITY_DATE,
18191
- compatibility_flags: ["nodejs_compat"],
18192
- assets: {
18193
- directory: ".open-next/assets",
18194
- binding: "ASSETS"
18195
- }
18196
- },
18197
- null,
18198
- 2
18199
- )}
18200
- `
18201
- );
18202
- const packageManagerConfigPath = await createPackageManagerConfig(
18203
- appRoot,
18204
- writer
18205
- );
18206
- return {
18207
- openNextConfigPath,
18208
- wranglerConfigPath,
18209
- packageManagerConfigPath
18210
- };
18156
+ return new RegExp(`^${source}$`);
18157
+ }
18158
+ function moduleRules(rules) {
18159
+ if (!Array.isArray(rules)) return [];
18160
+ return rules.flatMap((rule) => {
18161
+ const { type, globs } = rule;
18162
+ if (!Array.isArray(globs)) return [];
18163
+ return globs.filter((glob) => typeof glob === "string").map((glob) => ({
18164
+ type: typeof type === "string" ? type : "",
18165
+ test: globToRegExp(glob)
18166
+ }));
18167
+ });
18211
18168
  }
18212
18169
 
18213
18170
  // ../../packages/build/src/vinext.ts
@@ -18230,6 +18187,8 @@ var VITE_CONFIG_NAMES = [
18230
18187
  ];
18231
18188
  var GENERATED_VITE_CONFIG = "vite.config.js";
18232
18189
  var GENERATED_WORKER_ENTRY = "naro-worker.js";
18190
+ var GENERATED_ISR_CACHE = "naro-isr-cache.js";
18191
+ var ISR_SHARED_CACHE_OFF_NOTE = "ISR shared cache is off: the project builds with its own vite.config.";
18233
18192
  var VINEXT_WORKER_ENTRY_SOURCE = `// Written by naro: vinext's Worker, with getCloudflareContext() given its env.
18234
18193
  import { AsyncLocalStorage } from "node:async_hooks";
18235
18194
  import handler from "vinext/server/fetch-handler";
@@ -18264,7 +18223,7 @@ function vinextWranglerConfigPath(appRoot) {
18264
18223
  }
18265
18224
  async function exists3(path) {
18266
18225
  try {
18267
- await access5(path);
18226
+ await access3(path);
18268
18227
  return true;
18269
18228
  } catch {
18270
18229
  return false;
@@ -18288,6 +18247,13 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
18288
18247
  `The project already has a ${GENERATED_WORKER_ENTRY}. Naro writes its vinext Worker entry under that name and will not overwrite a file of the project's.`
18289
18248
  );
18290
18249
  }
18250
+ const isrCachePath = join6(appRoot, GENERATED_ISR_CACHE);
18251
+ if (await exists3(isrCachePath)) {
18252
+ throw new DeploymentError(
18253
+ "ARTIFACT_INVALID",
18254
+ `The project already has a ${GENERATED_ISR_CACHE}. Naro writes its ISR cache handler under that name and will not overwrite a file of the project's.`
18255
+ );
18256
+ }
18291
18257
  const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
18292
18258
  const viteConfigPath = join6(appRoot, GENERATED_VITE_CONFIG);
18293
18259
  const wranglerConfigPath = join6(appRoot, "wrangler.jsonc");
@@ -18300,7 +18266,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
18300
18266
  "",
18301
18267
  "export default defineConfig({",
18302
18268
  " plugins: [",
18303
- " vinext(),",
18269
+ ` vinext({ cache: { data: { adapter: ${JSON.stringify(isrCachePath)} } } }),`,
18304
18270
  ' cloudflare({ viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] } }),',
18305
18271
  " ],",
18306
18272
  "});",
@@ -18308,6 +18274,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
18308
18274
  ].join("\n")
18309
18275
  );
18310
18276
  await writer(workerEntryPath, VINEXT_WORKER_ENTRY_SOURCE);
18277
+ await writer(isrCachePath, NARO_ISR_CACHE_SOURCE);
18311
18278
  await writer(
18312
18279
  wranglerConfigPath,
18313
18280
  `${JSON.stringify(
@@ -18360,8 +18327,8 @@ function parseVinextCheckReport(output) {
18360
18327
  function vinextRouteFromCheck(report) {
18361
18328
  if (!report)
18362
18329
  return {
18363
- eligible: false,
18364
- reason: "vinext check output could not be read; staying on OpenNext. Expected if vinext changed its report format."
18330
+ eligible: true,
18331
+ reason: "vinext check output could not be read; trying the build anyway. Expected if vinext changed its report format."
18365
18332
  };
18366
18333
  const autoFixed = report.issueLines.filter(
18367
18334
  (line) => AUTO_FIXED_ISSUES.some((pattern) => pattern.test(line))
@@ -18373,7 +18340,7 @@ function vinextRouteFromCheck(report) {
18373
18340
  if (blocking > 0)
18374
18341
  return {
18375
18342
  eligible: false,
18376
- reason: `vinext check reported ${blocking} unsupported item(s) (${report.percent}% compatible); staying on OpenNext.`
18343
+ reason: `vinext check reported ${blocking} unsupported item(s) (${report.percent}% compatible).`
18377
18344
  };
18378
18345
  if (autoFixed.length > 0)
18379
18346
  return {
@@ -18385,38 +18352,12 @@ function vinextRouteFromCheck(report) {
18385
18352
  reason: report.partial > 0 ? `vinext check found no blocking issues (${report.partial} partially supported); attempting a vinext build.` : "vinext check found no issues; attempting a vinext build."
18386
18353
  };
18387
18354
  }
18388
- function globToRegExp(glob) {
18389
- let source = "";
18390
- for (let index = 0; index < glob.length; index += 1) {
18391
- const char = glob[index];
18392
- if (char !== "*") {
18393
- source += char?.replace(/[.+?^${}()|[\]\\]/g, String.raw`\$&`) ?? "";
18394
- continue;
18395
- }
18396
- if (glob[index + 1] !== "*") {
18397
- source += "[^/]*";
18398
- continue;
18399
- }
18400
- if (glob[index + 2] === "/") {
18401
- source += "(?:[^/]*/)*";
18402
- index += 2;
18403
- } else {
18404
- source += ".*";
18405
- index += 1;
18406
- }
18407
- }
18408
- return new RegExp(`^${source}$`);
18409
- }
18410
18355
  function moduleMatchers(rules) {
18411
- if (!Array.isArray(rules)) return [];
18412
- return rules.flatMap((rule) => {
18413
- const globs = rule.globs;
18414
- return Array.isArray(globs) ? globs : [];
18415
- }).filter((glob) => typeof glob === "string").map(globToRegExp);
18356
+ return moduleRules(rules).map((rule) => rule.test);
18416
18357
  }
18417
18358
  async function readVinextArtifact(appRoot, identity) {
18418
18359
  try {
18419
- const outputRoot = resolve2(appRoot, VINEXT_OUTPUT_DIR);
18360
+ const outputRoot = resolve(appRoot, VINEXT_OUTPUT_DIR);
18420
18361
  const serverRoot = join6(outputRoot, SERVER_DIR);
18421
18362
  const clientRoot = join6(outputRoot, CLIENT_DIR);
18422
18363
  const configPath = join6(serverRoot, WRANGLER_OUTPUT_NAME);
@@ -18477,7 +18418,7 @@ var FRAMEWORK_PRESETS = {
18477
18418
  id: "next",
18478
18419
  detectDependency: "next",
18479
18420
  defaultBuildScript: "build",
18480
- defaultOutputDirectory: ".open-next",
18421
+ defaultOutputDirectory: "dist",
18481
18422
  needsServerRuntime: true,
18482
18423
  spaFallback: false,
18483
18424
  cachePath: ".next/cache"
@@ -18815,9 +18756,85 @@ async function detectFramework(root, overrides = {}) {
18815
18756
  };
18816
18757
  }
18817
18758
 
18759
+ // ../../packages/build/src/naro-adapter-artifact.ts
18760
+ import { readFile as readFile7 } from "fs/promises";
18761
+ import { join as join8 } from "path";
18762
+ var RULE_CONTENT_TYPES = {
18763
+ ESModule: "application/javascript+module",
18764
+ CommonJS: "application/javascript",
18765
+ Text: "text/plain",
18766
+ Data: "application/octet-stream",
18767
+ CompiledWasm: "application/wasm"
18768
+ };
18769
+ var CONFIG_NAME = "wrangler.jsonc";
18770
+ async function readNaroAdapterArtifact(appRoot, identity) {
18771
+ try {
18772
+ const workerRoot = join8(appRoot, WORKER_DIR);
18773
+ let metadata;
18774
+ try {
18775
+ metadata = JSON.parse(
18776
+ await readFile7(join8(workerRoot, CONFIG_NAME), "utf8")
18777
+ );
18778
+ } catch (error51) {
18779
+ throw new DeploymentError(
18780
+ "ARTIFACT_INVALID",
18781
+ `The Naro adapter produced no ${WORKER_DIR}/${CONFIG_NAME}.`,
18782
+ { cause: error51 }
18783
+ );
18784
+ }
18785
+ if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string")
18786
+ throw new DeploymentError(
18787
+ "ARTIFACT_INVALID",
18788
+ "Naro adapter wrangler output must declare main and compatibility_date."
18789
+ );
18790
+ const rules = moduleRules(metadata.rules);
18791
+ const files = await listArtifactFiles(workerRoot, "naro-adapter", appRoot);
18792
+ const entry = files.find((file2) => file2.name === metadata.main);
18793
+ if (!entry)
18794
+ throw new DeploymentError(
18795
+ "ARTIFACT_INVALID",
18796
+ `Naro adapter worker entrypoint "${metadata.main}" is missing from the build output.`
18797
+ );
18798
+ const modules = files.filter(
18799
+ (file2) => file2.name !== metadata.main && file2.name !== CONFIG_NAME && !file2.name.endsWith(".map")
18800
+ ).map((file2) => {
18801
+ const rule = rules.find((candidate) => candidate.test.test(file2.name));
18802
+ const contentType2 = rule ? RULE_CONTENT_TYPES[rule.type] : void 0;
18803
+ if (!contentType2)
18804
+ throw new DeploymentError(
18805
+ "ARTIFACT_INVALID",
18806
+ `Naro adapter output file "${file2.name}" matches no module rule.`
18807
+ );
18808
+ return { ...file2, contentType: contentType2 };
18809
+ });
18810
+ return {
18811
+ ...identity,
18812
+ entrypoint: { ...entry, contentType: "application/javascript+module" },
18813
+ modules,
18814
+ assets: await listArtifactFiles(
18815
+ join8(appRoot, ASSETS_DIR),
18816
+ "naro-adapter",
18817
+ appRoot
18818
+ ),
18819
+ compatibilityDate: metadata.compatibility_date,
18820
+ compatibilityFlags: Array.isArray(metadata.compatibility_flags) ? metadata.compatibility_flags.filter(
18821
+ (flag) => typeof flag === "string"
18822
+ ) : [],
18823
+ assetsBinding: typeof metadata.assets?.binding === "string" ? metadata.assets.binding : "ASSETS"
18824
+ };
18825
+ } catch (error51) {
18826
+ if (error51 instanceof DeploymentError) throw error51;
18827
+ throw new DeploymentError(
18828
+ "ARTIFACT_INVALID",
18829
+ "Could not read the Naro adapter output.",
18830
+ { cause: error51 }
18831
+ );
18832
+ }
18833
+ }
18834
+
18818
18835
  // ../../packages/build/src/next-routes.ts
18819
18836
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
18820
- import { join as join8 } from "path";
18837
+ import { join as join9 } from "path";
18821
18838
  var APP_PAGE_FILE = /^page\.(?:js|jsx|ts|tsx|md|mdx)$/;
18822
18839
  var PAGES_ROUTER_FILE = /^(.+)\.(?:js|jsx|ts|tsx)$/;
18823
18840
  var DECLARATION_FILE = /\.d\.ts$/;
@@ -18831,7 +18848,7 @@ function errorCode(error51) {
18831
18848
  }
18832
18849
  async function firstDirectory(root, candidates) {
18833
18850
  for (const candidate of candidates) {
18834
- const path = join8(root, candidate);
18851
+ const path = join9(root, candidate);
18835
18852
  try {
18836
18853
  if ((await stat2(path)).isDirectory()) return path;
18837
18854
  } catch (error51) {
@@ -18861,7 +18878,7 @@ async function readAppRouter(dir, segments, routes) {
18861
18878
  const name = entry.name;
18862
18879
  if (name.startsWith("_") || INTERCEPTING.test(name)) continue;
18863
18880
  const next = ROUTE_GROUP.test(name) || name.startsWith("@") ? segments : [...segments, urlSegment(name, "app")];
18864
- await readAppRouter(join8(dir, name), next, routes);
18881
+ await readAppRouter(join9(dir, name), next, routes);
18865
18882
  }
18866
18883
  }
18867
18884
  async function readPagesRouter(dir, segments, routes) {
@@ -18870,7 +18887,7 @@ async function readPagesRouter(dir, segments, routes) {
18870
18887
  if (entry.isDirectory()) {
18871
18888
  if (segments.length === 0 && entry.name === "api") continue;
18872
18889
  await readPagesRouter(
18873
- join8(dir, entry.name),
18890
+ join9(dir, entry.name),
18874
18891
  [...segments, urlSegment(entry.name, "pages")],
18875
18892
  routes
18876
18893
  );
@@ -18888,8 +18905,8 @@ async function readPagesRouter(dir, segments, routes) {
18888
18905
  }
18889
18906
  }
18890
18907
  async function listNextRoutes(appRoot) {
18891
- const app = await firstDirectory(appRoot, ["app", join8("src", "app")]);
18892
- const pages = await firstDirectory(appRoot, ["pages", join8("src", "pages")]);
18908
+ const app = await firstDirectory(appRoot, ["app", join9("src", "app")]);
18909
+ const pages = await firstDirectory(appRoot, ["pages", join9("src", "pages")]);
18893
18910
  if (!app && !pages) return null;
18894
18911
  const routes = /* @__PURE__ */ new Set();
18895
18912
  if (app) await readAppRouter(app, [], routes);
@@ -19126,14 +19143,14 @@ async function runCommand(spec, observer) {
19126
19143
 
19127
19144
  // ../../packages/build/src/repository.ts
19128
19145
  import { realpath as realpath2 } from "fs/promises";
19129
- import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
19146
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
19130
19147
  function resolveAppRoot(checkout, configuredRoot) {
19131
- const checkoutRoot = resolve3(checkout);
19148
+ const checkoutRoot = resolve2(checkout);
19132
19149
  if (!configuredRoot) return checkoutRoot;
19133
19150
  if (isAbsolute2(configuredRoot)) {
19134
19151
  throw new Error("Configured root directory escapes repository");
19135
19152
  }
19136
- const appRoot = resolve3(checkoutRoot, configuredRoot);
19153
+ const appRoot = resolve2(checkoutRoot, configuredRoot);
19137
19154
  const relativeRoot = relative2(checkoutRoot, appRoot);
19138
19155
  if (relativeRoot === ".." || relativeRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(relativeRoot)) {
19139
19156
  throw new Error("Configured root directory escapes repository");
@@ -19164,8 +19181,8 @@ async function assertAppRootInsideCheckout(checkout, appRoot) {
19164
19181
  }
19165
19182
 
19166
19183
  // ../../packages/build/src/static-artifact.ts
19167
- import { readdir as readdir3, readFile as readFile7 } from "fs/promises";
19168
- import { isAbsolute as isAbsolute3, join as join9, relative as relative3, resolve as resolve4, sep as sep3 } from "path";
19184
+ import { readdir as readdir3, readFile as readFile8 } from "fs/promises";
19185
+ import { isAbsolute as isAbsolute3, join as join10, relative as relative3, resolve as resolve3, sep as sep3 } from "path";
19169
19186
  var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
19170
19187
  var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
19171
19188
  var ALLOWED_DOTFILE = ".well-known";
@@ -19179,7 +19196,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19179
19196
  "ARTIFACT_INVALID",
19180
19197
  "Output directory must be relative to the application root."
19181
19198
  );
19182
- const outputRoot = resolve4(appRoot, outputDirectory);
19199
+ const outputRoot = resolve3(appRoot, outputDirectory);
19183
19200
  const relation = relative3(appRoot, outputRoot);
19184
19201
  if (relation === ".." || relation.startsWith(`..${sep3}`) || isAbsolute3(relation))
19185
19202
  throw new DeploymentError(
@@ -19198,7 +19215,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19198
19215
  (left, right) => left.name.localeCompare(right.name)
19199
19216
  )) {
19200
19217
  if (excluded(entry.name)) continue;
19201
- const path = join9(directory, entry.name);
19218
+ const path = join10(directory, entry.name);
19202
19219
  if (entry.isSymbolicLink())
19203
19220
  throw new DeploymentError(
19204
19221
  "ARTIFACT_INVALID",
@@ -19208,7 +19225,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19208
19225
  else if (entry.isFile()) {
19209
19226
  assets.push({
19210
19227
  name: relative3(outputRoot, path).split(sep3).join("/"),
19211
- data: await readFile7(path),
19228
+ data: await readFile8(path),
19212
19229
  contentType: contentType(path)
19213
19230
  });
19214
19231
  }
@@ -19252,10 +19269,58 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19252
19269
  };
19253
19270
  }
19254
19271
 
19272
+ // ../../packages/build/src/static-cdn.ts
19273
+ var STATIC_ASSET_PREFIX_ENV = "NARO_STATIC_ASSET_PREFIX";
19274
+ var STATIC_ASSETS_ON_CDN_NOTE = "Static assets (_next/static) are served from static.naro.sh.";
19275
+ 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).";
19276
+ function buildEnvWithStaticPrefix(env, prefix) {
19277
+ const result = { ...env };
19278
+ delete result[STATIC_ASSET_PREFIX_ENV];
19279
+ if (prefix) result[STATIC_ASSET_PREFIX_ENV] = prefix;
19280
+ return result;
19281
+ }
19282
+ function contains(file2, needle) {
19283
+ return Buffer.from(
19284
+ file2.data.buffer,
19285
+ file2.data.byteOffset,
19286
+ file2.data.byteLength
19287
+ ).includes(needle);
19288
+ }
19289
+ function withStaticAssetPrefix(artifact, prefix) {
19290
+ if (!prefix) return { artifact, note: null };
19291
+ const needle = Buffer.from(prefix, "utf8");
19292
+ const files = [artifact.entrypoint, ...artifact.modules, ...artifact.assets];
19293
+ if (!files.some((file2) => contains(file2, needle)))
19294
+ return { artifact, note: STATIC_ASSETS_ON_WORKER_NOTE };
19295
+ return {
19296
+ artifact: { ...artifact, staticAssetPrefix: prefix },
19297
+ note: STATIC_ASSETS_ON_CDN_NOTE
19298
+ };
19299
+ }
19300
+ var IMMUTABLE_HEADERS_FILE = "_headers";
19301
+ var IMMUTABLE_HEADERS = `/_next/static/*
19302
+ Cache-Control: ${IMMUTABLE_CACHE_CONTROL}
19303
+ `;
19304
+ function withImmutableHeaders(artifact) {
19305
+ if (artifact.assets.some((asset) => asset.name === IMMUTABLE_HEADERS_FILE))
19306
+ return artifact;
19307
+ return {
19308
+ ...artifact,
19309
+ assets: [
19310
+ ...artifact.assets,
19311
+ {
19312
+ name: IMMUTABLE_HEADERS_FILE,
19313
+ data: new TextEncoder().encode(IMMUTABLE_HEADERS),
19314
+ contentType: "text/plain"
19315
+ }
19316
+ ]
19317
+ };
19318
+ }
19319
+
19255
19320
  // ../../packages/build/src/strategy/fingerprint.ts
19256
19321
  import { createHash as createHash3 } from "crypto";
19257
- import { readFile as readFile8 } from "fs/promises";
19258
- import { join as join10, relative as relative4, sep as sep4 } from "path";
19322
+ import { readFile as readFile9 } from "fs/promises";
19323
+ import { join as join11, relative as relative4, sep as sep4 } from "path";
19259
19324
  var APP_ROOT_FILES = [
19260
19325
  "package.json",
19261
19326
  "next.config.js",
@@ -19292,18 +19357,18 @@ function sha256(data) {
19292
19357
  }
19293
19358
  async function hashFile(path) {
19294
19359
  try {
19295
- return sha256(await readFile8(path));
19360
+ return sha256(await readFile9(path));
19296
19361
  } catch {
19297
19362
  return null;
19298
19363
  }
19299
19364
  }
19300
19365
  async function hashStrategyInputs(checkout, appRoot) {
19301
19366
  const candidates = /* @__PURE__ */ new Set();
19302
- for (const name of CHECKOUT_FILES) candidates.add(join10(checkout, name));
19303
- for (const name of APP_ROOT_FILES) candidates.add(join10(appRoot, name));
19367
+ for (const name of CHECKOUT_FILES) candidates.add(join11(checkout, name));
19368
+ for (const name of APP_ROOT_FILES) candidates.add(join11(appRoot, name));
19304
19369
  for (const base2 of SOURCE_FILES) {
19305
19370
  for (const ext of SOURCE_EXTENSIONS) {
19306
- candidates.add(join10(appRoot, `${base2}.${ext}`));
19371
+ candidates.add(join11(appRoot, `${base2}.${ext}`));
19307
19372
  }
19308
19373
  }
19309
19374
  const entries = [];
@@ -19331,12 +19396,12 @@ async function computeStrategyFingerprint(input) {
19331
19396
  }
19332
19397
 
19333
19398
  // ../../packages/build/src/strategy/project-facts.ts
19334
- import { access as access6, readFile as readFile10 } from "fs/promises";
19335
- import { join as join12 } from "path";
19399
+ import { access as access4, readFile as readFile11 } from "fs/promises";
19400
+ import { join as join13 } from "path";
19336
19401
 
19337
19402
  // ../../packages/build/src/strategy/user-adapter.ts
19338
- import { readFile as readFile9 } from "fs/promises";
19339
- import { join as join11 } from "path";
19403
+ import { readFile as readFile10 } from "fs/promises";
19404
+ import { join as join12 } from "path";
19340
19405
  var ADAPTER_PATH = /\badapterPath\b/;
19341
19406
  var EXPERIMENTAL_ADAPTER_PATH = /\bexperimental\s*:\s*\{[^{}]*\badapterPath\b/;
19342
19407
  function adapterKeyFromConfigSource(source) {
@@ -19368,8 +19433,8 @@ async function adapterInBuildOutput(appRoot, nextVersion) {
19368
19433
  let manifest2;
19369
19434
  try {
19370
19435
  const parsed = JSON.parse(
19371
- await readFile9(
19372
- join11(appRoot, ".next", "required-server-files.json"),
19436
+ await readFile10(
19437
+ join12(appRoot, ".next", "required-server-files.json"),
19373
19438
  "utf8"
19374
19439
  )
19375
19440
  );
@@ -19380,10 +19445,14 @@ async function adapterInBuildOutput(appRoot, nextVersion) {
19380
19445
  }
19381
19446
  const topLevel = manifest2.config?.adapterPath;
19382
19447
  if (keys.includes("adapterPath") && typeof topLevel === "string" && topLevel)
19383
- return { source: "build-output", key: "adapterPath" };
19448
+ return { source: "build-output", key: "adapterPath", path: topLevel };
19384
19449
  const experimental = manifest2.config?.experimental?.adapterPath;
19385
19450
  if (keys.includes("experimental.adapterPath") && typeof experimental === "string" && experimental)
19386
- return { source: "build-output", key: "experimental.adapterPath" };
19451
+ return {
19452
+ source: "build-output",
19453
+ key: "experimental.adapterPath",
19454
+ path: experimental
19455
+ };
19387
19456
  return null;
19388
19457
  }
19389
19458
  function describeUserAdapter(adapter) {
@@ -19396,9 +19465,6 @@ function describeUserAdapter(adapter) {
19396
19465
  return `${adapter.key} in the Next.js build output`;
19397
19466
  }
19398
19467
  }
19399
- function openNextAdapterWarning(adapter) {
19400
- return `P2 opennext-cloudflare: The project sets its own Next.js adapter (${describeUserAdapter(adapter)}); its modifyConfig and onBuildComplete also run in the OpenNext build.`;
19401
- }
19402
19468
 
19403
19469
  // ../../packages/build/src/strategy/project-facts.ts
19404
19470
  var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
@@ -19411,17 +19477,17 @@ var VITE_CONFIGS = [
19411
19477
  ];
19412
19478
  async function exists4(path) {
19413
19479
  try {
19414
- await access6(path);
19480
+ await access4(path);
19415
19481
  return true;
19416
19482
  } catch {
19417
19483
  return false;
19418
19484
  }
19419
19485
  }
19420
- async function findSourceFile(appRoot, basename6) {
19486
+ async function findSourceFile(appRoot, basename5) {
19421
19487
  for (const dir of SOURCE_DIRECTORIES) {
19422
19488
  for (const ext of SOURCE_EXTENSIONS2) {
19423
- const rel = dir ? `${dir}/${basename6}.${ext}` : `${basename6}.${ext}`;
19424
- if (await exists4(join12(appRoot, rel))) return rel;
19489
+ const rel = dir ? `${dir}/${basename5}.${ext}` : `${basename5}.${ext}`;
19490
+ if (await exists4(join13(appRoot, rel))) return rel;
19425
19491
  }
19426
19492
  }
19427
19493
  return null;
@@ -19441,14 +19507,15 @@ function matcherRoutesFromSource(source) {
19441
19507
  }
19442
19508
  async function readIfExists(path) {
19443
19509
  try {
19444
- return await readFile10(path, "utf8");
19510
+ return await readFile11(path, "utf8");
19445
19511
  } catch {
19446
19512
  return null;
19447
19513
  }
19448
19514
  }
19449
19515
  async function findAdapterConfig(appRoot) {
19450
19516
  for (const file2 of NEXT_CONFIG_FILES) {
19451
- const source = await readIfExists(join12(appRoot, file2));
19517
+ if (file2 === "next.config.cjs") continue;
19518
+ const source = await readIfExists(join13(appRoot, file2));
19452
19519
  if (source === null) continue;
19453
19520
  const key = adapterKeyFromConfigSource(source);
19454
19521
  return key ? { file: file2, key } : null;
@@ -19459,10 +19526,10 @@ async function collectProjectFacts(appRoot) {
19459
19526
  const proxyFile = await findSourceFile(appRoot, "proxy");
19460
19527
  const middlewareFile = await findSourceFile(appRoot, "middleware");
19461
19528
  const routeSourceFile = middlewareFile ?? proxyFile;
19462
- const routeSource = routeSourceFile ? await readIfExists(join12(appRoot, routeSourceFile)) : null;
19529
+ const routeSource = routeSourceFile ? await readIfExists(join13(appRoot, routeSourceFile)) : null;
19463
19530
  let usesVinext = false;
19464
19531
  let nextSpec = null;
19465
- const packageJsonSource = await readIfExists(join12(appRoot, "package.json"));
19532
+ const packageJsonSource = await readIfExists(join13(appRoot, "package.json"));
19466
19533
  if (packageJsonSource) {
19467
19534
  try {
19468
19535
  const packageJson = JSON.parse(packageJsonSource);
@@ -19473,7 +19540,7 @@ async function collectProjectFacts(appRoot) {
19473
19540
  nextSpec = dependency("next");
19474
19541
  if (dependency("vinext") !== null) {
19475
19542
  for (const name of VITE_CONFIGS) {
19476
- const config2 = await readIfExists(join12(appRoot, name));
19543
+ const config2 = await readIfExists(join13(appRoot, name));
19477
19544
  if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
19478
19545
  usesVinext = true;
19479
19546
  break;
@@ -19742,7 +19809,8 @@ async function fetchStatus(fetchImpl, url2, timeoutMs) {
19742
19809
  clearTimeout(timer);
19743
19810
  }
19744
19811
  }
19745
- function probeOk(status) {
19812
+ function probeOk(status, expect) {
19813
+ if (expect === "found" && status === 404) return false;
19746
19814
  return status < 500;
19747
19815
  }
19748
19816
  function createSmokeTester(options = {}) {
@@ -19829,7 +19897,11 @@ function createSmokeTester(options = {}) {
19829
19897
  `${origin}${probe.path}`,
19830
19898
  probeTimeoutMs
19831
19899
  );
19832
- const result = "status" in answer ? { ...probe, status: answer.status, ok: probeOk(answer.status) } : { ...probe, status: null, ok: false, detail: answer.error };
19900
+ const result = "status" in answer ? {
19901
+ ...probe,
19902
+ status: answer.status,
19903
+ ok: probeOk(answer.status, probe.expect)
19904
+ } : { ...probe, status: null, ok: false, detail: answer.error };
19833
19905
  probes2.push(result);
19834
19906
  await observer.onLog({
19835
19907
  level: result.ok ? "INFO" : "ERROR",
@@ -19874,13 +19946,40 @@ function defaultSmokeProbes(matcherRoutes) {
19874
19946
  return probes2;
19875
19947
  }
19876
19948
 
19877
- // ../../packages/build/src/strategy/strategies/opennext-aws.ts
19878
- import { writeFile as writeFile6 } from "fs/promises";
19879
- import { join as join14 } from "path";
19949
+ // ../../packages/build/src/strategy/strategies/unavailable.ts
19950
+ function createUnavailableStrategy(options) {
19951
+ return {
19952
+ id: options.id,
19953
+ priority: options.priority,
19954
+ target: options.target,
19955
+ supports: () => ({ supported: false, reason: options.reason }),
19956
+ evaluate: async () => ({ status: "skipped", reason: options.reason })
19957
+ };
19958
+ }
19959
+ function createSplitCloudflareStrategy() {
19960
+ return createUnavailableStrategy({
19961
+ id: "split-cloudflare",
19962
+ priority: 3,
19963
+ target: "cloudflare-split",
19964
+ reason: "Split Cloudflare deployment needs the dispatcher to chain a middleware Worker in front of the app Worker; the runtime plane routes one Worker per hostname today."
19965
+ });
19966
+ }
19967
+ function createAwsMiddlewareCloudflareAppStrategy() {
19968
+ return createUnavailableStrategy({
19969
+ id: "aws-middleware-cloudflare-app",
19970
+ priority: 4,
19971
+ target: "aws-hybrid",
19972
+ reason: "Hybrid AWS middleware + Cloudflare app needs an AWS deploy target and cross-cloud request chaining; neither exists on this control plane."
19973
+ });
19974
+ }
19975
+
19976
+ // ../../packages/build/src/strategy/strategies/naro-adapter-cloudflare.ts
19977
+ import { readFile as readFile13, rm as rm2 } from "fs/promises";
19978
+ import { join as join15, resolve as resolve5 } from "path";
19880
19979
 
19881
19980
  // ../../packages/build/src/strategy/strategies/shared.ts
19882
- import { access as access7, readFile as readFile11, rm, writeFile as writeFile5 } from "fs/promises";
19883
- import { dirname as dirname2, join as join13, resolve as resolve5 } from "path";
19981
+ import { access as access5, readFile as readFile12, rm, writeFile as writeFile5 } from "fs/promises";
19982
+ import { dirname, join as join14, resolve as resolve4 } from "path";
19884
19983
  function capturingObserver(observer) {
19885
19984
  const lines = [];
19886
19985
  return {
@@ -19916,31 +20015,28 @@ function describeFailure(step) {
19916
20015
  function failed(stage, reason) {
19917
20016
  return { status: "failed", stage, reason };
19918
20017
  }
19919
- function appBuildFailed(reason) {
19920
- return { status: "failed", stage: "build", reason, appBuild: true };
19921
- }
19922
20018
  async function info(observer, message) {
19923
20019
  await observer.onLog({ level: "INFO", stage: "BUILDING", message });
19924
20020
  }
19925
20021
  async function warn(observer, message) {
19926
20022
  await observer.onLog({ level: "WARN", stage: "BUILDING", message });
19927
20023
  }
19928
- async function pathExists2(path) {
20024
+ async function pathExists(path) {
19929
20025
  try {
19930
- await access7(path);
20026
+ await access5(path);
19931
20027
  return true;
19932
20028
  } catch {
19933
20029
  return false;
19934
20030
  }
19935
20031
  }
19936
20032
  async function findProjectBin(appRoot, checkout, name) {
19937
- let dir = resolve5(appRoot);
19938
- const stop = resolve5(checkout);
20033
+ let dir = resolve4(appRoot);
20034
+ const stop = resolve4(checkout);
19939
20035
  for (; ; ) {
19940
- const candidate = join13(dir, "node_modules", ".bin", name);
19941
- if (await pathExists2(candidate)) return candidate;
20036
+ const candidate = join14(dir, "node_modules", ".bin", name);
20037
+ if (await pathExists(candidate)) return candidate;
19942
20038
  if (dir === stop) return null;
19943
- const parent = dirname2(dir);
20039
+ const parent = dirname(dir);
19944
20040
  if (parent === dir) return null;
19945
20041
  dir = parent;
19946
20042
  }
@@ -19951,7 +20047,7 @@ function dotenvQuote(value) {
19951
20047
  return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
19952
20048
  }
19953
20049
  async function withDevVars(wranglerConfigPath, env, run) {
19954
- const path = join13(dirname2(wranglerConfigPath), ".dev.vars");
20050
+ const path = join14(dirname(wranglerConfigPath), ".dev.vars");
19955
20051
  const keys = Object.keys(env).filter(
19956
20052
  (key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
19957
20053
  );
@@ -19968,36 +20064,7 @@ async function withDevVars(wranglerConfigPath, env, run) {
19968
20064
  await rm(path, { force: true });
19969
20065
  }
19970
20066
  }
19971
- async function bundleWorker(context, layout, observer, errorCode2) {
19972
- return runStep(
19973
- context,
19974
- {
19975
- command: context.toolchain.wrangler,
19976
- args: [
19977
- "deploy",
19978
- "--dry-run",
19979
- "--config",
19980
- layout.wranglerConfigPath,
19981
- "--outdir",
19982
- layout.bundleDir
19983
- ],
19984
- cwd: layout.appRoot,
19985
- env: {
19986
- CI: "1",
19987
- WRANGLER_LOG_PATH: join13(
19988
- layout.appRoot,
19989
- layout.outputRoot,
19990
- "wrangler.log"
19991
- )
19992
- },
19993
- timeoutMs: 5 * 6e4,
19994
- stage: "BUILDING",
19995
- errorCode: errorCode2
19996
- },
19997
- observer
19998
- );
19999
- }
20000
- async function smokeWorker(context, layout, observer) {
20067
+ async function smokeWorker(context, layout, observer, extraProbes = []) {
20001
20068
  return withDevVars(
20002
20069
  layout.wranglerConfigPath,
20003
20070
  context.env,
@@ -20017,19 +20084,22 @@ async function smokeWorker(context, layout, observer) {
20017
20084
  "--show-interactive-dev-session=false",
20018
20085
  "--log-level=warn",
20019
20086
  "--persist-to",
20020
- join13(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
20087
+ join14(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
20021
20088
  ],
20022
20089
  cwd: layout.appRoot,
20023
20090
  env: {
20024
20091
  CI: "1",
20025
- WRANGLER_LOG_PATH: join13(
20092
+ WRANGLER_LOG_PATH: join14(
20026
20093
  layout.appRoot,
20027
20094
  layout.outputRoot,
20028
20095
  "wrangler-dev.log"
20029
20096
  )
20030
20097
  }
20031
20098
  }),
20032
- probes: defaultSmokeProbes(context.facts.matcherRoutes),
20099
+ probes: [
20100
+ ...defaultSmokeProbes(context.facts.matcherRoutes),
20101
+ ...extraProbes
20102
+ ],
20033
20103
  stage: "BUILDING",
20034
20104
  errorCode: "BUILD_FAILED"
20035
20105
  },
@@ -20038,10 +20108,10 @@ async function smokeWorker(context, layout, observer) {
20038
20108
  );
20039
20109
  }
20040
20110
  async function approvePnpmBuilds(checkout, packages) {
20041
- const path = join13(checkout, "pnpm-workspace.yaml");
20111
+ const path = join14(checkout, "pnpm-workspace.yaml");
20042
20112
  let source = "";
20043
20113
  try {
20044
- source = await readFile11(path, "utf8");
20114
+ source = await readFile12(path, "utf8");
20045
20115
  } catch {
20046
20116
  }
20047
20117
  let next = source;
@@ -20088,412 +20158,191 @@ ${listEntries}
20088
20158
  }
20089
20159
  }
20090
20160
 
20091
- // ../../packages/build/src/strategy/strategies/opennext-aws.ts
20092
- var CONFIG_NAME = ".next-deploy-open-next-aws.config.ts";
20093
- var SERVER_FUNCTION = ".open-next/server-functions/default/index.mjs";
20094
- function smokeConfig(buildCommand) {
20095
- return [
20096
- "const config = {",
20097
- ` buildCommand: ${JSON.stringify(buildCommand)},`,
20098
- " default: {",
20099
- ' override: { wrapper: "node", converter: "node", incrementalCache: "dummy", tagCache: "dummy", queue: "dummy" },',
20100
- " },",
20101
- ' imageOptimization: { loader: "dummy" },',
20102
- " dangerous: { disableIncrementalCache: true, disableTagCache: true },",
20103
- "};",
20104
- "export default config;",
20105
- ""
20106
- ].join("\n");
20161
+ // ../../packages/build/src/strategy/strategies/naro-adapter-cloudflare.ts
20162
+ var NARO_ADAPTER_REASONS = {
20163
+ notInstalled: "The Naro adapter is not installed in this build environment.",
20164
+ unknownVersion: "The installed Next.js version could not be read; the Naro adapter needs Next.js 16.2 or newer.",
20165
+ missingOutput: "next build finished without the Naro adapter's output; another Next.js adapter may have run instead.",
20166
+ userAdapterRan: (key) => `next build ran the project's own Next.js adapter (${key} in the Next.js build output) instead of the Naro adapter. Naro replaces an adapter only when next.config sets adapterPath itself; this one is set somewhere Naro does not read before the build, such as an imported file.`
20167
+ };
20168
+ var REPLACE_TAIL = "Your repository is unchanged, and your adapter's modifyConfig and onBuildComplete do not run. To keep your adapter (vinext only), change the project's Next.js adapter setting.";
20169
+ function replaceWarning(adapter) {
20170
+ return adapter.source === "next-config" ? `${adapter.file} sets its own Next.js adapter (${adapter.key}); this build replaces it with the Naro adapter in a temporary copy. ${REPLACE_TAIL}` : `NEXT_ADAPTER_PATH sets its own Next.js adapter; this build replaces it with the Naro adapter. ${REPLACE_TAIL}`;
20107
20171
  }
20108
- function createOpenNextAwsStrategy() {
20172
+ async function readRecord(appRoot) {
20173
+ try {
20174
+ const parsed = JSON.parse(
20175
+ await readFile13(join15(appRoot, BUILD_RECORD), "utf8")
20176
+ );
20177
+ if (typeof parsed.buildId !== "string" || typeof parsed.distDir !== "string")
20178
+ return null;
20179
+ const staticFiles = Array.isArray(parsed.outputs?.staticFiles) ? parsed.outputs.staticFiles : [];
20180
+ return {
20181
+ buildId: parsed.buildId,
20182
+ distDir: parsed.distDir,
20183
+ basePath: typeof parsed.config?.basePath === "string" ? parsed.config.basePath : "",
20184
+ staticPathnames: staticFiles.flatMap((file2) => {
20185
+ const pathname = file2?.pathname;
20186
+ return typeof pathname === "string" ? [pathname] : [];
20187
+ })
20188
+ };
20189
+ } catch {
20190
+ return null;
20191
+ }
20192
+ }
20193
+ function naroAdapterSmokeProbes(record3, assetNames) {
20194
+ const { basePath } = record3;
20195
+ const probes2 = [];
20196
+ const assetPrefix = `${basePath}/_next/static/`;
20197
+ const script = assetNames.find(
20198
+ (name) => `/${name}`.startsWith(assetPrefix) && name.endsWith(".js")
20199
+ );
20200
+ if (script)
20201
+ probes2.push({ path: `/${script}`, label: "static asset", expect: "found" });
20202
+ const notPages = new Set(
20203
+ ["/404", "/500", "/index"].map((path) => `${basePath}${path}`)
20204
+ );
20205
+ const page2 = record3.staticPathnames.find(
20206
+ (pathname) => !pathname.startsWith(assetPrefix) && !pathname.endsWith(".rsc") && !notPages.has(pathname)
20207
+ );
20208
+ if (page2) probes2.push({ path: page2, label: "static page", expect: "found" });
20209
+ return probes2;
20210
+ }
20211
+ async function readBuildId(distDir) {
20212
+ try {
20213
+ return (await readFile13(join15(distDir, "BUILD_ID"), "utf8")).trim();
20214
+ } catch {
20215
+ return null;
20216
+ }
20217
+ }
20218
+ function createNaroAdapterCloudflareStrategy(options) {
20109
20219
  return {
20110
- id: "opennext-aws",
20111
- priority: 5,
20112
- target: "aws",
20220
+ id: "naro-adapter-cloudflare",
20221
+ priority: 2,
20222
+ target: "cloudflare",
20113
20223
  supports(context) {
20114
- if (!context.targets.aws)
20224
+ if (!context.toolchain.nextAdapter)
20225
+ return { supported: false, reason: NARO_ADAPTER_REASONS.notInstalled };
20226
+ const verdict = judgeNextVersion(context.nextVersion);
20227
+ if (!verdict)
20228
+ return {
20229
+ supported: false,
20230
+ reason: NARO_ADAPTER_REASONS.unknownVersion
20231
+ };
20232
+ if (verdict.kind === "too-old")
20115
20233
  return {
20116
20234
  supported: false,
20117
- reason: "No AWS deploy target is configured on this control plane (no provider, credentials, or routing exist for it)."
20235
+ reason: `The Naro adapter needs Next.js 16.2 or newer; this project runs Next.js ${context.nextVersion}.`
20118
20236
  };
20119
- if (!context.toolchain.openNextAws)
20237
+ const adapter = userAdapterOf(context);
20238
+ if (adapter && context.userAdapter === "keep")
20120
20239
  return {
20121
20240
  supported: false,
20122
- reason: "The @opennextjs/aws toolchain is not installed on this build runner."
20241
+ reason: `The project keeps its own Next.js adapter (${describeUserAdapter(adapter)}) by its Next.js adapter setting, so the Naro adapter is skipped.`
20123
20242
  };
20124
20243
  return { supported: true };
20125
20244
  },
20126
20245
  async evaluate(context, observer) {
20127
- await info(
20128
- observer,
20129
- "P5 opennext-aws: building with the OpenNext AWS adapter."
20130
- );
20246
+ const adapterPath = context.toolchain.nextAdapter;
20247
+ if (!adapterPath)
20248
+ return { status: "skipped", reason: NARO_ADAPTER_REASONS.notInstalled };
20249
+ if (judgeNextVersion(context.nextVersion)?.kind === "newer")
20250
+ await warn(
20251
+ observer,
20252
+ `Next.js ${context.nextVersion} is newer than the versions the Naro adapter is tested with (${testedRange()}); building anyway.`
20253
+ );
20131
20254
  let fork;
20132
20255
  try {
20133
- fork = await context.workspace.fork("opennext-aws");
20256
+ fork = await context.workspace.fork("naro-adapter");
20134
20257
  } catch (error51) {
20135
20258
  return failed("prepare", describeError(error51));
20136
20259
  }
20137
- const configPath = join14(fork.appRoot, CONFIG_NAME);
20138
- try {
20139
- await writeFile6(
20140
- configPath,
20141
- smokeConfig(
20142
- `${context.packageManager.name} run ${context.buildScript}`
20143
- ),
20144
- "utf8"
20145
- );
20146
- } catch (error51) {
20147
- return failed("prepare", describeError(error51));
20260
+ await rm2(join15(fork.appRoot, OUTPUT_DIR), {
20261
+ recursive: true,
20262
+ force: true
20263
+ });
20264
+ const userAdapter = userAdapterOf(context);
20265
+ if (userAdapter && userAdapter.source !== "build-output") {
20266
+ let replaced = true;
20267
+ if (userAdapter.source === "next-config") {
20268
+ try {
20269
+ replaced = await wrapNextConfig(fork.appRoot, userAdapter.file) !== null;
20270
+ } catch (error51) {
20271
+ return failed("prepare", describeError(error51));
20272
+ }
20273
+ }
20274
+ if (replaced) await warn(observer, replaceWarning(userAdapter));
20148
20275
  }
20149
20276
  const build = await runStep(
20150
20277
  context,
20151
20278
  {
20152
- command: context.toolchain.openNextAws ?? "open-next",
20153
- args: ["build", "--config-path", configPath],
20279
+ command: context.packageManager.name,
20280
+ args: ["run", context.buildScript],
20154
20281
  cwd: fork.appRoot,
20155
20282
  env: {
20156
20283
  ...context.env,
20157
- NPM_CONFIG_USERCONFIG: context.packageManagerConfigPath
20284
+ NEXT_ADAPTER_PATH: adapterPath,
20285
+ NPM_CONFIG_USERCONFIG: context.packageManagerConfigPath,
20286
+ CI: "1"
20158
20287
  },
20159
- timeoutMs: 20 * 6e4,
20160
- stage: "BUILDING",
20161
- errorCode: "BUILD_FAILED"
20162
- },
20163
- observer
20164
- );
20165
- if (!build.ok) return failed("build", describeFailure(build));
20166
- const serverFunction = join14(fork.appRoot, SERVER_FUNCTION);
20167
- if (!await pathExists2(serverFunction))
20168
- return failed("bundle", `OpenNext AWS produced no ${SERVER_FUNCTION}.`);
20169
- const smoke = await context.ports.smoke.run(
20170
- {
20171
- boot: (port) => ({
20172
- command: process.execPath,
20173
- args: [serverFunction],
20174
- cwd: fork.appRoot,
20175
- env: { ...context.env, PORT: String(port), NODE_ENV: "production" }
20176
- }),
20177
- probes: defaultSmokeProbes(context.facts.matcherRoutes),
20288
+ timeoutMs: 15 * 6e4,
20178
20289
  stage: "BUILDING",
20179
20290
  errorCode: "BUILD_FAILED"
20180
20291
  },
20181
20292
  observer
20182
20293
  );
20183
- if (!smoke.ok) return failed("smoke", smoke.reason);
20184
- return {
20185
- status: "success",
20186
- artifact: {
20187
- kind: "opennext-aws",
20188
- outputRoot: join14(fork.appRoot, ".open-next"),
20189
- serverFunction
20190
- },
20191
- notes: [
20192
- "Verified with the Node wrapper; an AWS deploy needs a Lambda-wrapper build against provisioned resources."
20193
- ]
20194
- };
20195
- }
20196
- };
20197
- }
20198
-
20199
- // ../../packages/build/src/strategy/strategies/opennext-cloudflare.ts
20200
- import { readFile as readFile12 } from "fs/promises";
20201
- import { join as join15 } from "path";
20202
- var PROXY_TO_MIDDLEWARE_VARIANT = "proxy-to-middleware";
20203
- var NODE_MIDDLEWARE_REFUSAL = /Node\.js middleware is not (?:currently )?supported/i;
20204
- var NODE_MIDDLEWARE_EXPERIMENTAL = /Node\.js middleware support is experimental/i;
20205
- var NODE_MIDDLEWARE_NOT_DEPLOYED = 'Node.js-runtime middleware (runtime: "nodejs") is only an experiment in OpenNext Cloudflare, and Naro does not deploy it. Remove runtime: "nodejs" to build it as Edge middleware';
20206
- async function buildDeclaresNodeMiddleware(appRoot) {
20207
- try {
20208
- const manifest2 = JSON.parse(
20209
- await readFile12(
20210
- join15(appRoot, ".next", "server", "functions-config-manifest.json"),
20211
- "utf8"
20212
- )
20213
- );
20214
- return Boolean(manifest2.functions?.["/_middleware"]);
20215
- } catch {
20216
- return false;
20217
- }
20218
- }
20219
- function createOpenNextCloudflareStrategy(options) {
20220
- async function attempt(context, roots, observer, ownBuild, adapterNotice) {
20221
- let platform;
20222
- try {
20223
- platform = await options.createPlatformBuildConfig(
20224
- roots.appRoot,
20225
- context.identity.deploymentId
20226
- );
20227
- } catch (error51) {
20228
- return {
20229
- ok: false,
20230
- nodeMiddleware: null,
20231
- outcome: failed("prepare", describeError(error51))
20232
- };
20233
- }
20234
- const nextBuild = await runStep(
20235
- context,
20236
- {
20237
- command: context.packageManager.name,
20238
- args: ["run", context.buildScript],
20239
- cwd: roots.appRoot,
20240
- env: {
20241
- ...context.env,
20242
- NEXT_PRIVATE_STANDALONE: "true",
20243
- NPM_CONFIG_USERCONFIG: platform.packageManagerConfigPath
20244
- },
20245
- timeoutMs: 15 * 6e4,
20246
- stage: "BUILDING",
20247
- errorCode: "BUILD_FAILED"
20248
- },
20249
- observer
20250
- );
20251
- if (!nextBuild.ok) {
20252
- return {
20253
- ok: false,
20254
- nodeMiddleware: null,
20255
- outcome: ownBuild ? appBuildFailed(describeFailure(nextBuild)) : failed("build", describeFailure(nextBuild))
20256
- };
20257
- }
20258
- if (!adapterNotice.warned) {
20259
- const adapter = userAdapterOf(context) ?? await adapterInBuildOutput(roots.appRoot, context.nextVersion);
20260
- if (adapter) {
20261
- adapterNotice.warned = true;
20262
- await warn(observer, openNextAdapterWarning(adapter));
20263
- }
20264
- }
20265
- if (await buildDeclaresNodeMiddleware(roots.appRoot)) {
20266
- return {
20267
- ok: false,
20268
- nodeMiddleware: "experimental",
20269
- outcome: failed("build", NODE_MIDDLEWARE_NOT_DEPLOYED)
20270
- };
20271
- }
20272
- const openNext = await runStep(
20273
- context,
20274
- {
20275
- command: context.toolchain.openNextCloudflare,
20276
- args: [
20277
- "build",
20278
- "--skip-next-build",
20279
- "--openNextConfigPath",
20280
- platform.openNextConfigPath,
20281
- "--config",
20282
- platform.wranglerConfigPath,
20283
- ...context.nextSupport.status === "needs-unsupported-flag" ? ["--dangerouslyUseUnsupportedNextVersion"] : []
20284
- ],
20285
- cwd: roots.appRoot,
20286
- timeoutMs: 15 * 6e4,
20287
- stage: "BUILDING",
20288
- errorCode: "OPENNEXT_FAILED"
20289
- },
20290
- observer
20291
- );
20292
- if (!openNext.ok) {
20293
- const refused = openNext.output.some(
20294
- (line) => NODE_MIDDLEWARE_REFUSAL.test(line)
20295
- );
20296
- return {
20297
- ok: false,
20298
- nodeMiddleware: refused ? "refused" : null,
20299
- outcome: failed(
20300
- "build",
20301
- refused ? NODE_MIDDLEWARE_NOT_DEPLOYED : describeFailure(openNext)
20302
- )
20303
- };
20304
- }
20305
- if (openNext.output.some((line) => NODE_MIDDLEWARE_EXPERIMENTAL.test(line))) {
20306
- return {
20307
- ok: false,
20308
- nodeMiddleware: "experimental",
20309
- outcome: failed("build", NODE_MIDDLEWARE_NOT_DEPLOYED)
20310
- };
20311
- }
20312
- const layout = {
20313
- appRoot: roots.appRoot,
20314
- wranglerConfigPath: platform.wranglerConfigPath,
20315
- ...OPEN_NEXT_LAYOUT
20316
- };
20317
- const bundle = await bundleWorker(
20318
- context,
20319
- layout,
20320
- observer,
20321
- "OPENNEXT_FAILED"
20322
- );
20323
- if (!bundle.ok) {
20324
- return {
20325
- ok: false,
20326
- nodeMiddleware: null,
20327
- outcome: failed("bundle", describeFailure(bundle))
20328
- };
20329
- }
20330
- let artifact;
20331
- try {
20332
- artifact = await options.readOpenNextArtifact(
20333
- roots.appRoot,
20334
- context.identity,
20335
- platform.wranglerConfigPath
20336
- );
20337
- } catch (error51) {
20338
- return {
20339
- ok: false,
20340
- nodeMiddleware: null,
20341
- outcome: failed("bundle", describeError(error51))
20342
- };
20343
- }
20344
- const smoke = await smokeWorker(context, layout, observer);
20345
- if (!smoke.ok) {
20346
- return {
20347
- ok: false,
20348
- nodeMiddleware: null,
20349
- outcome: failed("smoke", smoke.reason)
20350
- };
20351
- }
20352
- return { ok: true, artifact };
20353
- }
20354
- return {
20355
- id: "opennext-cloudflare",
20356
- priority: 2,
20357
- target: "cloudflare",
20358
- supports: () => ({ supported: true }),
20359
- async evaluate(context, observer) {
20360
- const adapterNotice = { warned: false };
20361
- const rememberedRewrite = context.pinnedVariant === PROXY_TO_MIDDLEWARE_VARIANT && context.facts.proxyFile !== null;
20362
- if (rememberedRewrite) {
20363
- await info(
20364
- observer,
20365
- `P2 opennext-cloudflare: the stored strategy already needed ${context.facts.proxyFile ?? "proxy"} rewritten to Edge middleware; building that way directly.`
20366
- );
20367
- } else {
20368
- await info(
20369
- observer,
20370
- "P2 opennext-cloudflare: building the project as written."
20371
- );
20372
- const first = await attempt(
20373
- context,
20374
- context.workspace,
20375
- observer,
20376
- true,
20377
- adapterNotice
20378
- );
20379
- if (first.ok) {
20294
+ if (!build.ok) {
20295
+ if (!await pathExists(join15(fork.appRoot, BUILD_RECORD)))
20380
20296
  return {
20381
- status: "success",
20382
- artifact: { kind: "cloudflare-worker", artifact: first.artifact },
20383
- notes: []
20297
+ status: "failed",
20298
+ stage: "build",
20299
+ reason: describeFailure(build),
20300
+ appBuild: true
20384
20301
  };
20385
- }
20386
- if (!first.nodeMiddleware || !context.facts.proxyFile)
20387
- return first.outcome;
20388
- await warn(
20389
- observer,
20390
- first.nodeMiddleware === "experimental" ? `P2 opennext-cloudflare: OpenNext supports the Node.js middleware compiled from ${context.facts.proxyFile} only as an experiment it does not maintain. Retrying in a temporary workspace with it rewritten to Edge middleware.` : `P2 opennext-cloudflare: OpenNext refused the Node.js middleware compiled from ${context.facts.proxyFile}. Retrying in a temporary workspace with it rewritten to Edge middleware.`
20302
+ return failed("bundle", describeFailure(build));
20303
+ }
20304
+ const record3 = await readRecord(fork.appRoot);
20305
+ if (!record3 || await readBuildId(record3.distDir) !== record3.buildId) {
20306
+ const ran = await adapterInBuildOutput(
20307
+ fork.appRoot,
20308
+ context.nextVersion
20309
+ );
20310
+ return failed(
20311
+ "build",
20312
+ ran?.source === "build-output" && resolve5(fork.appRoot, ran.path) !== resolve5(adapterPath) ? NARO_ADAPTER_REASONS.userAdapterRan(ran.key) : NARO_ADAPTER_REASONS.missingOutput
20391
20313
  );
20392
20314
  }
20393
- let fork;
20315
+ let artifact;
20394
20316
  try {
20395
- fork = await context.workspace.fork("opennext-proxy-to-middleware");
20317
+ artifact = await options.readArtifact(fork.appRoot, context.identity);
20396
20318
  } catch (error51) {
20397
- return failed("prepare", describeError(error51));
20398
- }
20399
- const conversion = await options.convertProxyToMiddleware(fork.appRoot);
20400
- if (!conversion.converted) {
20401
- return failed(
20402
- "prepare",
20403
- `Could not rewrite ${context.facts.proxyFile} to middleware (a middleware file already exists).`
20404
- );
20319
+ return failed("bundle", describeError(error51));
20405
20320
  }
20406
- await warn(
20407
- observer,
20408
- `Converted ${conversion.file ?? context.facts.proxyFile} to edge middleware for this build only. Next.js 16 proxy runs on the Node.js runtime, which Cloudflare does not support; middleware runs on the Edge runtime, so Node.js-only APIs are unavailable. Your repository is unchanged.`
20409
- );
20410
- const second = await attempt(
20321
+ const smoke = await smokeWorker(
20411
20322
  context,
20412
- fork,
20323
+ {
20324
+ appRoot: fork.appRoot,
20325
+ wranglerConfigPath: join15(fork.appRoot, WRANGLER_CONFIG),
20326
+ outputRoot: OUTPUT_DIR
20327
+ },
20413
20328
  observer,
20414
- rememberedRewrite,
20415
- adapterNotice
20329
+ naroAdapterSmokeProbes(
20330
+ record3,
20331
+ artifact.assets.map((asset) => asset.name)
20332
+ )
20416
20333
  );
20417
- if (!second.ok) {
20418
- if (!rememberedRewrite || second.outcome.status !== "failed" || !second.outcome.appBuild)
20419
- return second.outcome;
20420
- await info(
20421
- observer,
20422
- `P2 opennext-cloudflare: the rewritten copy did not build; building the project as written once to tell the app's own error from the rewrite's.`
20423
- );
20424
- const asWritten = await attempt(
20425
- context,
20426
- context.workspace,
20427
- observer,
20428
- true,
20429
- adapterNotice
20430
- );
20431
- if (asWritten.ok) {
20432
- await info(
20433
- observer,
20434
- "P2 opennext-cloudflare: the project as written builds and ships as it is; the rewrite is no longer needed."
20435
- );
20436
- return {
20437
- status: "success",
20438
- artifact: {
20439
- kind: "cloudflare-worker",
20440
- artifact: asWritten.artifact
20441
- },
20442
- notes: []
20443
- };
20444
- }
20445
- if (asWritten.outcome.status === "failed" && asWritten.outcome.appBuild) {
20446
- await warn(
20447
- observer,
20448
- "P2 opennext-cloudflare: the project as written does not build either, so the error is the app's own."
20449
- );
20450
- return asWritten.outcome;
20451
- }
20452
- await warn(
20453
- observer,
20454
- "P2 opennext-cloudflare: the project as written builds, so the rewritten copy's error is the Edge rewrite's (a Node-only API reached from proxy.ts), not the app's code."
20455
- );
20456
- return failed("build", second.outcome.reason);
20457
- }
20334
+ if (!smoke.ok) return failed("smoke", smoke.reason);
20458
20335
  return {
20459
20336
  status: "success",
20460
- artifact: { kind: "cloudflare-worker", artifact: second.artifact },
20461
- variant: PROXY_TO_MIDDLEWARE_VARIANT,
20337
+ artifact: { kind: "cloudflare-worker", artifact },
20462
20338
  notes: [
20463
- `${conversion.file ?? "proxy"} was rewritten to Edge middleware for the build.`
20339
+ "Built with the Naro adapter (Next.js Adapter API) in a temporary workspace."
20464
20340
  ]
20465
20341
  };
20466
20342
  }
20467
20343
  };
20468
20344
  }
20469
20345
 
20470
- // ../../packages/build/src/strategy/strategies/unavailable.ts
20471
- function createUnavailableStrategy(options) {
20472
- return {
20473
- id: options.id,
20474
- priority: options.priority,
20475
- target: options.target,
20476
- supports: () => ({ supported: false, reason: options.reason }),
20477
- evaluate: async () => ({ status: "skipped", reason: options.reason })
20478
- };
20479
- }
20480
- function createSplitCloudflareStrategy() {
20481
- return createUnavailableStrategy({
20482
- id: "split-cloudflare",
20483
- priority: 3,
20484
- target: "cloudflare-split",
20485
- reason: "Split Cloudflare deployment needs the dispatcher to chain a middleware Worker in front of the app Worker; the runtime plane routes one Worker per hostname today."
20486
- });
20487
- }
20488
- function createAwsMiddlewareCloudflareAppStrategy() {
20489
- return createUnavailableStrategy({
20490
- id: "aws-middleware-cloudflare-app",
20491
- priority: 4,
20492
- target: "aws-hybrid",
20493
- reason: "Hybrid AWS middleware + Cloudflare app needs an AWS deploy target and cross-cloud request chaining; neither exists on this control plane."
20494
- });
20495
- }
20496
-
20497
20346
  // ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
20498
20347
  var VINEXT_MIN_NEXT_MAJOR = 16;
20499
20348
  var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
@@ -20568,6 +20417,7 @@ function createVinextCloudflareStrategy(options) {
20568
20417
  observer,
20569
20418
  "P1 vinext-cloudflare: the project already builds with vinext; using its own configuration."
20570
20419
  );
20420
+ await info(observer, ISR_SHARED_CACHE_OFF_NOTE);
20571
20421
  }
20572
20422
  let fork;
20573
20423
  try {
@@ -20618,6 +20468,17 @@ function createVinextCloudflareStrategy(options) {
20618
20468
  );
20619
20469
  if (!added.ok) return failed("prepare", describeFailure(added));
20620
20470
  }
20471
+ try {
20472
+ await wrapNextConfigForAssetPrefix(
20473
+ fork.appRoot,
20474
+ context.env[STATIC_ASSET_PREFIX_ENV]
20475
+ );
20476
+ } catch (error51) {
20477
+ await warn(
20478
+ observer,
20479
+ `Could not point static assets at static.naro.sh: ${describeError(error51)}`
20480
+ );
20481
+ }
20621
20482
  const projectBin = adopt ? null : await findProjectBin(fork.appRoot, fork.checkout, "vinext");
20622
20483
  const build = await runStep(
20623
20484
  context,
@@ -20665,13 +20526,13 @@ function createVinextCloudflareStrategy(options) {
20665
20526
 
20666
20527
  // ../../packages/build/src/strategy/workspace.ts
20667
20528
  import { execFile } from "child_process";
20668
- import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm2 } from "fs/promises";
20669
- import { basename as basename2, dirname as dirname3, join as join16, relative as relative5, resolve as resolve6 } from "path";
20529
+ import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm3 } from "fs/promises";
20530
+ import { basename, dirname as dirname2, join as join16, relative as relative5, resolve as resolve6 } from "path";
20670
20531
  import { promisify } from "util";
20671
20532
  var execFileAsync = promisify(execFile);
20672
20533
  var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
20673
20534
  function shouldCopy(path) {
20674
- const name = basename2(path);
20535
+ const name = basename(path);
20675
20536
  if (SKIPPED_ANYWHERE.has(name)) return false;
20676
20537
  return !name.startsWith(".next-deploy-");
20677
20538
  }
@@ -20699,13 +20560,13 @@ async function copyInstalledPackages(source, target2) {
20699
20560
  await copyTree(install, join16(target2, relative5(origin, install)));
20700
20561
  }
20701
20562
  async function copyTree(from, to) {
20702
- await mkdir2(dirname3(to), { recursive: true });
20563
+ await mkdir2(dirname2(to), { recursive: true });
20703
20564
  if (process.platform !== "win32") {
20704
20565
  try {
20705
20566
  await execFileAsync("cp", ["-a", from, to]);
20706
20567
  return;
20707
20568
  } catch {
20708
- await rm2(to, { recursive: true, force: true });
20569
+ await rm3(to, { recursive: true, force: true });
20709
20570
  }
20710
20571
  }
20711
20572
  await cp(from, to, { recursive: true, verbatimSymlinks: true });
@@ -20733,27 +20594,26 @@ async function withRouteList(artifact, list, appRoot, observer) {
20733
20594
  return artifact;
20734
20595
  }
20735
20596
  }
20597
+ var NEXT_ADAPTER_PATH = nextAdapterPath();
20736
20598
  var defaultLocalBuildDependencies = {
20737
20599
  createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
20738
- removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
20600
+ removeWorkspace: (path) => rm4(path, { recursive: true, force: true }),
20739
20601
  runCommand,
20740
20602
  detectPackageManager,
20741
20603
  resolveAppRoot,
20742
20604
  detectFramework,
20743
20605
  readStaticArtifact,
20744
20606
  createPackageManagerConfig,
20745
- createPlatformBuildConfig,
20746
- readOpenNextArtifact,
20747
- openNextCommand: "opennextjs-cloudflare",
20748
20607
  wranglerCommand: "wrangler",
20749
20608
  cacheFetch: (input, init) => fetch(input, init),
20750
- convertProxyToMiddleware,
20751
20609
  commandAvailable: hasCommand,
20752
20610
  readInstalledNextVersion,
20753
20611
  now: () => /* @__PURE__ */ new Date(),
20754
20612
  createVinextBuildConfig,
20755
20613
  readVinextArtifact,
20756
20614
  vinextCommand: "vinext",
20615
+ ...NEXT_ADAPTER_PATH ? { nextAdapterPath: NEXT_ADAPTER_PATH } : {},
20616
+ readNaroAdapterArtifact,
20757
20617
  forkWorkspace: copyCheckout,
20758
20618
  copyInstalledPackages,
20759
20619
  collectProjectFacts,
@@ -20768,7 +20628,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
20768
20628
  const stop = resolve7(checkout);
20769
20629
  for (; ; ) {
20770
20630
  try {
20771
- const source = await readFile13(
20631
+ const source = await readFile14(
20772
20632
  join17(dir, "node_modules", "next", "package.json"),
20773
20633
  "utf8"
20774
20634
  );
@@ -20777,7 +20637,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
20777
20637
  } catch {
20778
20638
  }
20779
20639
  if (dir === stop) return null;
20780
- const parent = dirname4(dir);
20640
+ const parent = dirname3(dir);
20781
20641
  if (parent === dir) return null;
20782
20642
  dir = parent;
20783
20643
  }
@@ -20800,9 +20660,9 @@ async function hasCommand(command) {
20800
20660
  return false;
20801
20661
  }
20802
20662
  }
20803
- async function pathExists3(path) {
20663
+ async function pathExists2(path) {
20804
20664
  try {
20805
- await access8(path);
20665
+ await access6(path);
20806
20666
  return true;
20807
20667
  } catch {
20808
20668
  return false;
@@ -20846,14 +20706,11 @@ function defaultStrategies(dependencies) {
20846
20706
  createVinextBuildConfig: dependencies.createVinextBuildConfig ?? createVinextBuildConfig,
20847
20707
  readVinextArtifact: dependencies.readVinextArtifact ?? readVinextArtifact
20848
20708
  }),
20849
- createOpenNextCloudflareStrategy({
20850
- createPlatformBuildConfig: dependencies.createPlatformBuildConfig,
20851
- convertProxyToMiddleware: dependencies.convertProxyToMiddleware ?? convertProxyToMiddleware,
20852
- readOpenNextArtifact: dependencies.readOpenNextArtifact
20709
+ createNaroAdapterCloudflareStrategy({
20710
+ readArtifact: dependencies.readNaroAdapterArtifact ?? readNaroAdapterArtifact
20853
20711
  }),
20854
20712
  createSplitCloudflareStrategy(),
20855
- createAwsMiddlewareCloudflareAppStrategy(),
20856
- createOpenNextAwsStrategy()
20713
+ createAwsMiddlewareCloudflareAppStrategy()
20857
20714
  ];
20858
20715
  }
20859
20716
  var LocalBuildExecutor = class {
@@ -21132,7 +20989,7 @@ var LocalBuildExecutor = class {
21132
20989
  "NEXT_VERSION_UNSUPPORTED",
21133
20990
  nextSupport.reason
21134
20991
  );
21135
- if (nextSupport.status === "needs-unsupported-flag" || nextSupport.status === "outside-peer-range") {
20992
+ if (nextSupport.status === "past-support-window" || nextSupport.status === "outside-verified-range") {
21136
20993
  await observer.onLog({
21137
20994
  level: "WARN",
21138
20995
  stage: "BUILDING",
@@ -21187,11 +21044,10 @@ var LocalBuildExecutor = class {
21187
21044
  };
21188
21045
  };
21189
21046
  const vinextCommand = this.dependencies.vinextCommand;
21190
- const openNextAwsCommand = this.dependencies.openNextAwsCommand;
21191
21047
  const context = {
21192
21048
  identity,
21193
21049
  branch: input.branch,
21194
- env: input.env ?? {},
21050
+ env: buildEnvWithStaticPrefix(input.env, input.staticAssetPrefix),
21195
21051
  // The toolchain add has to use the store the install linked
21196
21052
  // node_modules from (withSharedCache).
21197
21053
  packageManager: cachePorts ? withSharedCache(packageManager, pmCacheDir) : packageManager,
@@ -21199,15 +21055,14 @@ var LocalBuildExecutor = class {
21199
21055
  buildScript: resolved.buildScript,
21200
21056
  nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown",
21201
21057
  nextMajor,
21202
- nextSupport,
21203
21058
  vinextEnabled: input.vinextEnabled !== false,
21059
+ userAdapter: input.userAdapter === "keep" ? "keep" : "replace",
21204
21060
  facts,
21205
21061
  workspace: { checkout, appRoot, fork: forkWorkspace },
21206
21062
  toolchain: {
21207
- openNextCloudflare: this.dependencies.openNextCommand,
21208
21063
  wrangler: this.dependencies.wranglerCommand,
21209
21064
  ...vinextCommand && await commandAvailable(vinextCommand) ? { vinext: vinextCommand } : {},
21210
- ...openNextAwsCommand && await commandAvailable(openNextAwsCommand) ? { openNextAws: openNextAwsCommand } : {}
21065
+ ...this.dependencies.nextAdapterPath && await pathExists2(this.dependencies.nextAdapterPath) ? { nextAdapter: this.dependencies.nextAdapterPath } : {}
21211
21066
  },
21212
21067
  targets: this.dependencies.targets ?? { cloudflare: true },
21213
21068
  ports: {
@@ -21248,7 +21103,10 @@ var LocalBuildExecutor = class {
21248
21103
  args: ["run", resolved.buildScript],
21249
21104
  cwd: appRoot,
21250
21105
  env: {
21251
- ...input.env,
21106
+ // Static presets never get the asset prefix (spec
21107
+ // 2026-11-07-static-asset-cdn §3): a Vite base or CRA
21108
+ // PUBLIC_URL also moves the router and public/ files.
21109
+ ...buildEnvWithStaticPrefix(input.env, void 0),
21252
21110
  NPM_CONFIG_USERCONFIG: packageManagerConfigPath
21253
21111
  },
21254
21112
  timeoutMs: 15 * 6e4,
@@ -21262,7 +21120,7 @@ var LocalBuildExecutor = class {
21262
21120
  if (depsKey && !depsRestored) {
21263
21121
  await trySave("deps", depsKey, pmCacheDir, cachePorts, observer);
21264
21122
  }
21265
- if (buildCacheDir && await pathExists3(buildCacheDir)) {
21123
+ if (buildCacheDir && await pathExists2(buildCacheDir)) {
21266
21124
  await trySave(
21267
21125
  "next",
21268
21126
  buildCacheKey,
@@ -21282,6 +21140,14 @@ var LocalBuildExecutor = class {
21282
21140
  appRoot,
21283
21141
  observer
21284
21142
  );
21143
+ const cdn = withStaticAssetPrefix(artifact, input.staticAssetPrefix);
21144
+ if (cdn.note)
21145
+ await observer.onLog({
21146
+ level: "INFO",
21147
+ stage: "DEPLOYING",
21148
+ message: cdn.note
21149
+ });
21150
+ artifact = withImmutableHeaders(cdn.artifact);
21285
21151
  return {
21286
21152
  gitCommitSha,
21287
21153
  artifact,
@@ -21662,8 +21528,8 @@ function sendPage(res, status, html) {
21662
21528
  async function startCallbackServer() {
21663
21529
  let resolveCode;
21664
21530
  let rejectCode;
21665
- const codePromise = new Promise((resolve15, reject) => {
21666
- resolveCode = resolve15;
21531
+ const codePromise = new Promise((resolve16, reject) => {
21532
+ resolveCode = resolve16;
21667
21533
  rejectCode = reject;
21668
21534
  });
21669
21535
  const server = createServer2((req, res) => {
@@ -21692,7 +21558,7 @@ async function startCallbackServer() {
21692
21558
  sendPage(res, 200, successPage());
21693
21559
  resolveCode(code);
21694
21560
  });
21695
- await new Promise((resolve15) => server.listen(0, "127.0.0.1", resolve15));
21561
+ await new Promise((resolve16) => server.listen(0, "127.0.0.1", resolve16));
21696
21562
  const port = server.address().port;
21697
21563
  return {
21698
21564
  port,
@@ -22190,7 +22056,8 @@ function projectInfo(row) {
22190
22056
  repositoryUrl: row.repository_url,
22191
22057
  repositoryFullName: row.repository_full_name ?? null,
22192
22058
  createdAt: row.created_at,
22193
- updatedAt: row.updated_at
22059
+ updatedAt: row.updated_at,
22060
+ userAdapter: row.user_adapter ?? null
22194
22061
  };
22195
22062
  }
22196
22063
  function deploymentInfo(row) {
@@ -22361,6 +22228,19 @@ var TeamsApi = class {
22361
22228
  { context: "Read team usage" }
22362
22229
  );
22363
22230
  }
22231
+ /**
22232
+ * The team's hosting compute (CPU) per project and environment for a UTC
22233
+ * month (spec 2026-11-06-hosting-cpu-usage §7). Observed, never billed.
22234
+ */
22235
+ async usageHosting(teamSlug, query = {}) {
22236
+ const params = new URLSearchParams();
22237
+ if (query.month !== void 0) params.set("month", query.month);
22238
+ const text3 = params.toString();
22239
+ return this.client.request(
22240
+ `/api/teams/${encodeURIComponent(teamSlug)}/usage/hosting${text3 ? `?${text3}` : ""}`,
22241
+ { context: "Read team hosting usage" }
22242
+ );
22243
+ }
22364
22244
  path(teamSlug, suffix = "") {
22365
22245
  return `/api/teams/${encodeURIComponent(teamSlug)}${suffix}`;
22366
22246
  }
@@ -22736,7 +22616,7 @@ var DeploymentsApi = class {
22736
22616
  async wait(deploymentId, options = {}) {
22737
22617
  const intervalMs = options.intervalMs ?? 2500;
22738
22618
  const timeoutMs = options.timeoutMs ?? 15 * 6e4;
22739
- const sleep2 = options.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
22619
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve16) => setTimeout(resolve16, ms)));
22740
22620
  const startedAt = Date.now();
22741
22621
  let lastStatus = null;
22742
22622
  let lastSequence = -1;
@@ -23337,7 +23217,7 @@ var DbApi = class {
23337
23217
  */
23338
23218
  async export(projectId, options = {}, wait = {}) {
23339
23219
  const timeoutMs = wait.timeoutMs ?? EXPORT_TIMEOUT_MS;
23340
- const sleep2 = wait.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
23220
+ const sleep2 = wait.sleep ?? ((ms) => new Promise((resolve16) => setTimeout(resolve16, ms)));
23341
23221
  const given = wait.pollIntervalMs;
23342
23222
  const firstPollMs = given !== void 0 && Number.isFinite(given) && given > 0 ? given : EXPORT_FIRST_POLL_MS;
23343
23223
  const maxPollMs = Math.max(EXPORT_MAX_POLL_MS, firstPollMs);
@@ -26800,8 +26680,8 @@ function readAuth(state, start, allowBareJwt) {
26800
26680
  stream.expectOperator("(");
26801
26681
  stream.expectOperator(")");
26802
26682
  if (fn !== "jwt") return { kind: "auth", fn };
26803
- const access9 = readJwtAccess(stream);
26804
- if (access9) return access9;
26683
+ const access7 = readJwtAccess(stream);
26684
+ if (access7) return access7;
26805
26685
  if (allowBareJwt) return "jwt";
26806
26686
  throw new PolicySyntaxError(
26807
26687
  "auth.jwt() is read one claim at a time: auth.jwt() ->> 'email'",
@@ -26869,13 +26749,13 @@ function readPrimary(state) {
26869
26749
  const inner2 = readAuth(state, auth, true);
26870
26750
  stream.expectOperator(")");
26871
26751
  if (inner2 !== "jwt") return inner2;
26872
- const access9 = readJwtAccess(stream);
26873
- if (!access9)
26752
+ const access7 = readJwtAccess(stream);
26753
+ if (!access7)
26874
26754
  throw new PolicySyntaxError(
26875
26755
  "auth.jwt() is read one claim at a time: (select auth.jwt()) ->> 'email'",
26876
26756
  auth.position
26877
26757
  );
26878
- return access9;
26758
+ return access7;
26879
26759
  }
26880
26760
  const inner = readOr(state);
26881
26761
  stream.expectOperator(")");
@@ -34494,11 +34374,13 @@ function summarizeTeamUsage(report, currentMonth) {
34494
34374
  };
34495
34375
  const meters = USAGE_METRICS.map((metric) => {
34496
34376
  const limit = report.limits?.[metric] ?? null;
34377
+ const perProject = metric === "domains" ? report.limits?.domainsPerProject ?? null : null;
34497
34378
  return {
34498
34379
  metric,
34499
34380
  used: used[metric],
34500
34381
  limit,
34501
- percent: limit ? Math.floor(used[metric] * 100 / limit) : null
34382
+ percent: limit ? Math.floor(used[metric] * 100 / limit) : null,
34383
+ perProject
34502
34384
  };
34503
34385
  });
34504
34386
  return {
@@ -34641,10 +34523,10 @@ var SUPPORTED_REMOTE = [
34641
34523
  /^ssh:\/\/[^\s]+$/,
34642
34524
  /^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
34643
34525
  ];
34644
- var defaultExec = (args, cwd) => new Promise((resolve15, reject) => {
34526
+ var defaultExec = (args, cwd) => new Promise((resolve16, reject) => {
34645
34527
  execFile2("git", args, { cwd }, (error51, stdout) => {
34646
34528
  if (error51) reject(error51);
34647
- else resolve15(stdout);
34529
+ else resolve16(stdout);
34648
34530
  });
34649
34531
  });
34650
34532
  async function tryExec(exec, args, cwd) {
@@ -34793,15 +34675,52 @@ async function uploadArtifact(artifact, opts) {
34793
34675
  };
34794
34676
  }
34795
34677
 
34678
+ // src/user-adapter.ts
34679
+ import { resolve as resolve10 } from "path";
34680
+ async function detectUserAdapter(directory, rootDirectory) {
34681
+ return findAdapterConfig(
34682
+ rootDirectory ? resolve10(directory, rootDirectory) : directory
34683
+ );
34684
+ }
34685
+ function parseUserAdapter(value) {
34686
+ return value === "replace" || value === "keep" ? value : null;
34687
+ }
34688
+ var USER_ADAPTER_OPTIONS = [
34689
+ {
34690
+ value: "replace",
34691
+ label: "Replace it with the Naro adapter on Naro's builders (recommended) \u2014 your repository stays as it is"
34692
+ },
34693
+ { value: "keep", label: "Keep my adapter \u2014 Naro builds with vinext only" }
34694
+ ];
34695
+ function userAdapterQuestion(adapter) {
34696
+ return `${adapter.file} sets its own Next.js adapter (${adapter.key}). How should Naro build this project?`;
34697
+ }
34698
+ function userAdapterOutcome(choice) {
34699
+ return choice === "replace" ? "\u25B8 This local build uses vinext only; if vinext cannot build the app, build it on Naro with the Naro adapter: naro deployments create (needs a connected repository)." : "\u25B8 Naro keeps your adapter: builds use vinext only.";
34700
+ }
34701
+ function userAdapterStored(adapter, choice) {
34702
+ return choice === "replace" ? `\u25B8 ${adapter.file} sets its own Next.js adapter; this project replaces it with the Naro adapter on Naro's builders.` : `\u25B8 ${adapter.file} sets its own Next.js adapter; this project keeps it and builds with vinext only.`;
34703
+ }
34704
+ function userAdapterUnasked(adapter) {
34705
+ return `\u26A0 ${adapter.file} sets its own Next.js adapter (${adapter.key}). Naro's builders replace it with the Naro adapter unless you choose otherwise: naro deploy --user-adapter keep, or naro project set --user-adapter keep.`;
34706
+ }
34707
+ var CHOOSE_USER_ADAPTER = {
34708
+ cli: "naro project set --user-adapter replace|keep",
34709
+ mcp: "update_project with userAdapter replace|keep"
34710
+ };
34711
+ function userAdapterUnreadable(adapter, surface = "cli") {
34712
+ return `\u26A0 ${adapter.file} sets its own Next.js adapter (${adapter.key}), but the project's adapter setting could not be read, so nothing was asked. Choose with ${CHOOSE_USER_ADAPTER[surface]}.`;
34713
+ }
34714
+
34796
34715
  // src/version.ts
34797
34716
  import { readFileSync as readFileSync6 } from "fs";
34798
- import { dirname as dirname5, join as join24 } from "path";
34799
- import { fileURLToPath } from "url";
34717
+ import { dirname as dirname4, join as join24 } from "path";
34718
+ import { fileURLToPath as fileURLToPath2 } from "url";
34800
34719
  function version2() {
34801
34720
  try {
34802
34721
  const pkg = JSON.parse(
34803
34722
  readFileSync6(
34804
- join24(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"),
34723
+ join24(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json"),
34805
34724
  "utf8"
34806
34725
  )
34807
34726
  );
@@ -34817,22 +34736,22 @@ import {
34817
34736
  cp as cp2,
34818
34737
  lstat,
34819
34738
  mkdir as mkdir3,
34820
- readFile as readFile14,
34739
+ readFile as readFile15,
34821
34740
  readlink,
34822
34741
  realpath as realpath3,
34823
- rm as rm4,
34742
+ rm as rm5,
34824
34743
  stat as stat3,
34825
34744
  symlink,
34826
34745
  unlink,
34827
- writeFile as writeFile7
34746
+ writeFile as writeFile6
34828
34747
  } from "fs/promises";
34829
34748
  import {
34830
- basename as basename3,
34831
- dirname as dirname6,
34749
+ basename as basename2,
34750
+ dirname as dirname5,
34832
34751
  isAbsolute as isAbsolute4,
34833
34752
  join as join25,
34834
34753
  relative as relative6,
34835
- resolve as resolve10,
34754
+ resolve as resolve11,
34836
34755
  sep as sep5
34837
34756
  } from "path";
34838
34757
  var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
@@ -34862,7 +34781,7 @@ async function committedEnvFiles(sourceDir) {
34862
34781
  if (tab < 0) return [];
34863
34782
  const [mode] = entry.slice(0, tab).split(" ");
34864
34783
  const path = entry.slice(tab + 1);
34865
- if (!ENV_FILE.test(basename3(path)) || COPY_SKIP.test(path)) return [];
34784
+ if (!ENV_FILE.test(basename2(path)) || COPY_SKIP.test(path)) return [];
34866
34785
  if (mode === "100644" || mode === "100755")
34867
34786
  return [{ path, symlink: false }];
34868
34787
  if (mode === "120000") return [{ path, symlink: true }];
@@ -34894,14 +34813,14 @@ async function keptOnly(items, keep, remove) {
34894
34813
  async function readLocal(path) {
34895
34814
  const found = await stat3(path).catch(() => null);
34896
34815
  if (!found?.isFile()) return null;
34897
- return readFile14(path).catch(() => null);
34816
+ return readFile15(path).catch(() => null);
34898
34817
  }
34899
34818
  async function realAncestor(dir) {
34900
- for (let current = dir; ; current = dirname6(current)) {
34819
+ for (let current = dir; ; current = dirname5(current)) {
34901
34820
  try {
34902
34821
  return await realpath3(current);
34903
34822
  } catch {
34904
- if (dirname6(current) === current)
34823
+ if (dirname5(current) === current)
34905
34824
  throw new Error(`No ancestor of ${dir}`);
34906
34825
  }
34907
34826
  }
@@ -34920,7 +34839,7 @@ async function copyWorkingTree(source, checkout) {
34920
34839
  recursive: true,
34921
34840
  // Relative symlinks must stay relative: resolved, they would point
34922
34841
  // back into the developer's real tree, and a strategy's temporary
34923
- // rewrite (proxy -> middleware, vinext toolchain) would land there.
34842
+ // rewrite (the vinext toolchain install) would land there.
34924
34843
  verbatimSymlinks: true,
34925
34844
  filter: async (src) => {
34926
34845
  if (COPY_SKIP.test(src)) return false;
@@ -34928,7 +34847,7 @@ async function copyWorkingTree(source, checkout) {
34928
34847
  const entry = await lstat(src);
34929
34848
  if (entry.isFIFO() || entry.isSocket() || entry.isCharacterDevice() || entry.isBlockDevice())
34930
34849
  return false;
34931
- if (ENV_FILE.test(basename3(src)) && !entry.isDirectory()) {
34850
+ if (ENV_FILE.test(basename2(src)) && !entry.isDirectory()) {
34932
34851
  inWorkingTree.push(relative6(sourceDir, src).split(sep5).join("/"));
34933
34852
  return false;
34934
34853
  }
@@ -34939,7 +34858,7 @@ async function copyWorkingTree(source, checkout) {
34939
34858
  absoluteLinks.push(path);
34940
34859
  return false;
34941
34860
  }
34942
- if (!inside(resolve10(dirname6(src), target2), sourceDir)) {
34861
+ if (!inside(resolve11(dirname5(src), target2), sourceDir)) {
34943
34862
  linksOutside.push(path);
34944
34863
  return false;
34945
34864
  }
@@ -34977,21 +34896,21 @@ async function copyWorkingTree(source, checkout) {
34977
34896
  continue;
34978
34897
  }
34979
34898
  const target2 = join25(checkout, path);
34980
- if (!inside(await realAncestor(dirname6(target2)), root)) {
34899
+ if (!inside(await realAncestor(dirname5(target2)), root)) {
34981
34900
  outsideProject.push(path);
34982
34901
  continue;
34983
34902
  }
34984
34903
  try {
34985
- await mkdir3(dirname6(target2), { recursive: true });
34904
+ await mkdir3(dirname5(target2), { recursive: true });
34986
34905
  } catch {
34987
34906
  outsideProject.push(path);
34988
34907
  continue;
34989
34908
  }
34990
- await rm4(target2, { recursive: true, force: true });
34909
+ await rm5(target2, { recursive: true, force: true });
34991
34910
  const content = await git(sourceDir, ["show", `HEAD:./${path}`]);
34992
34911
  if (isLink) {
34993
34912
  const linkTarget = content.toString("utf8");
34994
- const pointsAt = resolve10(dirname6(target2), linkTarget);
34913
+ const pointsAt = resolve11(dirname5(target2), linkTarget);
34995
34914
  if (isAbsolute4(linkTarget) || !inside(pointsAt, checkout)) {
34996
34915
  outsideProject.push(path);
34997
34916
  continue;
@@ -35000,7 +34919,7 @@ async function copyWorkingTree(source, checkout) {
35000
34919
  envLinks.push(path);
35001
34920
  continue;
35002
34921
  }
35003
- await writeFile7(target2, content, { flag: "wx" });
34922
+ await writeFile6(target2, content, { flag: "wx" });
35004
34923
  const local = await readLocal(join25(sourceDir, path));
35005
34924
  if (!local || !sameText(local, content)) committedVersion.push(path);
35006
34925
  }
@@ -35095,7 +35014,7 @@ var TtyPrompter = class {
35095
35014
  });
35096
35015
  try {
35097
35016
  return await new Promise(
35098
- (resolve15) => rl.question(question, resolve15)
35017
+ (resolve16) => rl.question(question, resolve16)
35099
35018
  );
35100
35019
  } finally {
35101
35020
  rl.close();
@@ -35116,7 +35035,7 @@ var TtyPrompter = class {
35116
35035
  });
35117
35036
  try {
35118
35037
  const answer = new Promise(
35119
- (resolve15) => rl.question(question, resolve15)
35038
+ (resolve16) => rl.question(question, resolve16)
35120
35039
  );
35121
35040
  muted = true;
35122
35041
  const value = await answer;
@@ -35452,7 +35371,7 @@ async function runDbLint(context) {
35452
35371
  }
35453
35372
 
35454
35373
  // src/functions/deploy.ts
35455
- import { relative as relative11, resolve as resolve13, sep as sep10 } from "path";
35374
+ import { relative as relative11, resolve as resolve14, sep as sep10 } from "path";
35456
35375
 
35457
35376
  // ../../packages/functions-bundler/src/errors.ts
35458
35377
  var FunctionBundleError = class extends Error {
@@ -35509,7 +35428,7 @@ var BUNDLE_MESSAGES = {
35509
35428
 
35510
35429
  // ../../packages/functions-bundler/src/discover.ts
35511
35430
  import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync2 } from "fs";
35512
- import { join as join27, relative as relative8, resolve as resolve11, sep as sep6 } from "path";
35431
+ import { join as join27, relative as relative8, resolve as resolve12, sep as sep6 } from "path";
35513
35432
 
35514
35433
  // ../../packages/functions-bundler/src/limits.ts
35515
35434
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
@@ -35531,9 +35450,9 @@ function isEntrypoint(value) {
35531
35450
  );
35532
35451
  }
35533
35452
  function functionsFolder(options) {
35534
- const root = resolve11(options.cwd, options.root ?? ".");
35453
+ const root = resolve12(options.cwd, options.root ?? ".");
35535
35454
  const explicit = options.dir !== void 0;
35536
- const dir = explicit ? resolve11(options.cwd, options.dir) : join27(root, "naro", "functions");
35455
+ const dir = explicit ? resolve12(options.cwd, options.dir) : join27(root, "naro", "functions");
35537
35456
  const label2 = relative8(options.cwd, dir).split(sep6).join("/") || ".";
35538
35457
  return { dir, label: label2, root, explicit };
35539
35458
  }
@@ -35652,7 +35571,7 @@ function closesNext(text3, from) {
35652
35571
  while (i < text3.length && /\s/.test(text3[i] ?? "")) i += 1;
35653
35572
  return text3[i] === "}" || text3[i] === "]";
35654
35573
  }
35655
- function parseJsonc2(text3) {
35574
+ function parseJsonc(text3) {
35656
35575
  const uncommented = stripComments2(text3);
35657
35576
  let out = "";
35658
35577
  let inString = false;
@@ -35713,11 +35632,11 @@ function stripComments2(text3) {
35713
35632
  }
35714
35633
 
35715
35634
  // ../../packages/functions-bundler/src/node-compat.ts
35716
- import { createRequire as createRequire2 } from "module";
35717
- import { dirname as dirname7, resolve as resolvePath } from "path";
35718
- import { fileURLToPath as fileURLToPath2 } from "url";
35719
- var require2 = createRequire2(import.meta.url);
35720
- var MODULE_DIR = dirname7(fileURLToPath2(import.meta.url));
35635
+ import { createRequire } from "module";
35636
+ import { dirname as dirname6, resolve as resolvePath } from "path";
35637
+ import { fileURLToPath as fileURLToPath3 } from "url";
35638
+ var require2 = createRequire(import.meta.url);
35639
+ var MODULE_DIR = dirname6(fileURLToPath3(import.meta.url));
35721
35640
  var REQUIRED_NODE_BUILT_IN_NAMESPACE = "node-built-in-modules";
35722
35641
  var REQUIRED_UNENV_ALIAS_NAMESPACE = "required-unenv-alias";
35723
35642
  var SKIPPED_ALIASES = /* @__PURE__ */ new Set(["debug"]);
@@ -35847,12 +35766,12 @@ ${injects.map(
35847
35766
 
35848
35767
  // ../../packages/functions-bundler/src/deno-imports.ts
35849
35768
  import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync as realpathSync2 } from "fs";
35850
- import { dirname as dirname9, isAbsolute as isAbsolute5, join as join29, relative as relative9, sep as sep8 } from "path";
35769
+ import { dirname as dirname8, isAbsolute as isAbsolute5, join as join29, relative as relative9, sep as sep8 } from "path";
35851
35770
 
35852
35771
  // ../../packages/functions-bundler/src/import-map.ts
35853
35772
  import { existsSync as existsSync8, readFileSync as readFileSync9, realpathSync } from "fs";
35854
- import { basename as basename4, dirname as dirname8, join as join28, resolve as resolve12, sep as sep7 } from "path";
35855
- import { fileURLToPath as fileURLToPath3 } from "url";
35773
+ import { basename as basename3, dirname as dirname7, join as join28, resolve as resolve13, sep as sep7 } from "path";
35774
+ import { fileURLToPath as fileURLToPath4 } from "url";
35856
35775
 
35857
35776
  // ../../packages/functions-bundler/src/printable.ts
35858
35777
  var UNPRINTABLE2 = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
@@ -35885,7 +35804,7 @@ function expandBareKeys(imports) {
35885
35804
  function load(file2, label2) {
35886
35805
  let parsed;
35887
35806
  try {
35888
- parsed = parseJsonc2(readFileSync9(file2, "utf8"));
35807
+ parsed = parseJsonc(readFileSync9(file2, "utf8"));
35889
35808
  } catch {
35890
35809
  throw new FunctionBundleError(
35891
35810
  "VALIDATION_FAILED",
@@ -35908,8 +35827,8 @@ function load(file2, label2) {
35908
35827
  printable2(BUNDLE_MESSAGES.importMapPrefix(label2, key, value))
35909
35828
  );
35910
35829
  return {
35911
- imports: DENO_CONFIGS.has(basename4(file2)) ? expandBareKeys(written) : written,
35912
- base: dirname8(file2),
35830
+ imports: DENO_CONFIGS.has(basename3(file2)) ? expandBareKeys(written) : written,
35831
+ base: dirname7(file2),
35913
35832
  label: label2
35914
35833
  };
35915
35834
  }
@@ -35942,7 +35861,7 @@ var isPathLike = (text3) => text3.startsWith("./") || text3.startsWith("../") ||
35942
35861
  function filePath(text3) {
35943
35862
  if (!text3.startsWith("file:")) return null;
35944
35863
  try {
35945
- return fileURLToPath3(text3);
35864
+ return fileURLToPath4(text3);
35946
35865
  } catch {
35947
35866
  return null;
35948
35867
  }
@@ -35960,7 +35879,7 @@ function keysOf(map2) {
35960
35879
  for (const [key, target2] of Object.entries(map2.imports)) {
35961
35880
  const trailing = key.endsWith("/");
35962
35881
  const file2 = filePath(key);
35963
- const name = file2 !== null ? realKey(file2, trailing) : isPathLike(key) ? realKey(resolve12(base2, key), trailing) : key;
35882
+ const name = file2 !== null ? realKey(file2, trailing) : isPathLike(key) ? realKey(resolve13(base2, key), trailing) : key;
35964
35883
  if (!keys.has(name)) keys.set(name, target2);
35965
35884
  }
35966
35885
  normalized.set(map2, keys);
@@ -35972,10 +35891,10 @@ function mapSpecifier(map2, specifier, importerDir) {
35972
35891
  const file2 = filePath(specifier);
35973
35892
  if (file2 !== null) lookup = realKey(file2, trailing);
35974
35893
  else if (specifier.startsWith("/"))
35975
- lookup = realKey(resolve12(specifier), trailing);
35894
+ lookup = realKey(resolve13(specifier), trailing);
35976
35895
  else if (isPathLike(specifier)) {
35977
35896
  if (importerDir === void 0) return null;
35978
- lookup = realKey(resolve12(importerDir, specifier), trailing);
35897
+ lookup = realKey(resolve13(importerDir, specifier), trailing);
35979
35898
  }
35980
35899
  const keys = keysOf(map2);
35981
35900
  let target2 = keys.get(lookup);
@@ -35988,7 +35907,7 @@ function mapSpecifier(map2, specifier, importerDir) {
35988
35907
  target2 = `${keys.get(prefix) ?? ""}${lookup.slice(prefix.length)}`;
35989
35908
  }
35990
35909
  if (target2.startsWith("./") || target2.startsWith("../"))
35991
- return resolve12(map2.base, target2);
35910
+ return resolve13(map2.base, target2);
35992
35911
  return filePath(target2) ?? target2;
35993
35912
  }
35994
35913
 
@@ -36068,7 +35987,7 @@ function packageDir(from, projectRoot) {
36068
35987
  const inNodeModules = fromRoot(projectRoot, current).split("/").includes("node_modules");
36069
35988
  if (!inNodeModules && existsSync9(join29(current, "package.json")))
36070
35989
  return fromRoot(projectRoot, current) || ".";
36071
- const up = dirname9(current);
35990
+ const up = dirname8(current);
36072
35991
  if (up === current) break;
36073
35992
  current = up;
36074
35993
  }
@@ -36079,14 +35998,14 @@ function isInstalled(from, pkg) {
36079
35998
  let current = from;
36080
35999
  for (; ; ) {
36081
36000
  if (existsSync9(join29(current, "node_modules", pkg))) return true;
36082
- const up = dirname9(current);
36001
+ const up = dirname8(current);
36083
36002
  if (up === current) return false;
36084
36003
  current = up;
36085
36004
  }
36086
36005
  }
36087
36006
  var NODE_MODULES = /[\\/]node_modules[\\/]/;
36088
36007
  function installedVersion(resolved, pkg) {
36089
- let current = dirname9(resolved);
36008
+ let current = dirname8(resolved);
36090
36009
  for (; ; ) {
36091
36010
  const file2 = join29(current, "package.json");
36092
36011
  if (existsSync9(file2)) {
@@ -36097,7 +36016,7 @@ function installedVersion(resolved, pkg) {
36097
36016
  } catch {
36098
36017
  }
36099
36018
  }
36100
- const up = dirname9(current);
36019
+ const up = dirname8(current);
36101
36020
  if (up === current) return null;
36102
36021
  current = up;
36103
36022
  }
@@ -36527,7 +36446,7 @@ var FUNCTION_BUSY_WAIT_MS = 5e3;
36527
36446
  var FUNCTION_RATE_LIMIT_DEFAULT_SECONDS = 60;
36528
36447
  var FUNCTION_RATE_LIMIT_MAX_SECONDS = 300;
36529
36448
  function defaultSleep(ms) {
36530
- return new Promise((resolve15) => setTimeout(resolve15, ms));
36449
+ return new Promise((resolve16) => setTimeout(resolve16, ms));
36531
36450
  }
36532
36451
  function retryAfterSeconds(error51) {
36533
36452
  const value = error51.details?.retryAfter;
@@ -36724,7 +36643,7 @@ async function deployOne(options, folder, name, importMap, secretNames, io, budg
36724
36643
  }
36725
36644
  function readFlagImportMap(options, folder) {
36726
36645
  if (options.importMap === void 0) return void 0;
36727
- const file2 = resolve13(options.cwd, options.importMap);
36646
+ const file2 = resolve14(options.cwd, options.importMap);
36728
36647
  try {
36729
36648
  return readImportMapFile(
36730
36649
  file2,
@@ -36842,7 +36761,7 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
36842
36761
  } catch (error51) {
36843
36762
  lastError = error51;
36844
36763
  await new Promise(
36845
- (resolve15) => setTimeout(resolve15, 500 * (attempt + 1))
36764
+ (resolve16) => setTimeout(resolve16, 500 * (attempt + 1))
36846
36765
  );
36847
36766
  }
36848
36767
  }
@@ -36918,19 +36837,19 @@ function slugify2(value) {
36918
36837
  async function projectName(sourceDir) {
36919
36838
  try {
36920
36839
  const pkg = JSON.parse(
36921
- await readFile15(join31(sourceDir, "package.json"), "utf8")
36840
+ await readFile16(join31(sourceDir, "package.json"), "utf8")
36922
36841
  );
36923
36842
  if (pkg.name) return pkg.name;
36924
36843
  } catch {
36925
36844
  }
36926
- return basename5(sourceDir);
36845
+ return basename4(sourceDir);
36927
36846
  }
36928
36847
  function resolveBin(name) {
36929
- let dir = dirname10(fileURLToPath4(import.meta.url));
36848
+ let dir = dirname9(fileURLToPath5(import.meta.url));
36930
36849
  for (; ; ) {
36931
36850
  const candidate = join31(dir, "node_modules", ".bin", name);
36932
36851
  if (existsSync10(candidate)) return candidate;
36933
- const parent = dirname10(dir);
36852
+ const parent = dirname9(dir);
36934
36853
  if (parent === dir) return name;
36935
36854
  dir = parent;
36936
36855
  }
@@ -36996,7 +36915,7 @@ function workingTreeNotices(copy) {
36996
36915
  function localDependencies(sourceDir, say = () => void 0) {
36997
36916
  return {
36998
36917
  createWorkspace: () => mkdtemp2(join31(tmpdir2(), "naro-build-")),
36999
- removeWorkspace: (path) => rm5(path, { recursive: true, force: true }),
36918
+ removeWorkspace: (path) => rm6(path, { recursive: true, force: true }),
37000
36919
  async runCommand(spec, observer) {
37001
36920
  if (spec.command === "git" && spec.args[0] === "clone") {
37002
36921
  const checkout = spec.args.at(-1);
@@ -37021,9 +36940,6 @@ function localDependencies(sourceDir, say = () => void 0) {
37021
36940
  detectFramework,
37022
36941
  readStaticArtifact,
37023
36942
  createPackageManagerConfig,
37024
- createPlatformBuildConfig,
37025
- readOpenNextArtifact,
37026
- openNextCommand: resolveBin("opennextjs-cloudflare"),
37027
36943
  wranglerCommand: resolveBin("wrangler"),
37028
36944
  // `vinext check` runs on the bare checkout, before the toolchain is added
37029
36945
  // to the user's tree, so this binary has to come from the CLI's own deps.
@@ -37045,7 +36961,7 @@ async function deployDirectory(sourceDir, options, log2) {
37045
36961
  "Pass production: true for production; preview is the default."
37046
36962
  );
37047
36963
  const surface = production ? "production" : environment ?? "preview";
37048
- const appRoot = options.rootDirectory ? resolve14(sourceDir, options.rootDirectory) : sourceDir;
36964
+ const appRoot = options.rootDirectory ? resolve15(sourceDir, options.rootDirectory) : sourceDir;
37049
36965
  const framework = detectFramework2(appRoot);
37050
36966
  if (!framework.deployable && !options.force) {
37051
36967
  throw new NaroError(
@@ -37133,6 +37049,19 @@ async function deployDirectory(sourceDir, options, log2) {
37133
37049
  });
37134
37050
  if (ctx.envSkipped?.length)
37135
37051
  log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
37052
+ const storedAdapter = ctx.userAdapter ?? null;
37053
+ if (options.userAdapter && options.userAdapter !== storedAdapter) {
37054
+ try {
37055
+ await client.projects.update(ctx.projectId, {
37056
+ userAdapter: options.userAdapter
37057
+ });
37058
+ } catch (error51) {
37059
+ log2(
37060
+ `\u26A0 Could not save the Next.js adapter choice: ${error51 instanceof Error ? error51.message : String(error51)}`
37061
+ );
37062
+ }
37063
+ }
37064
+ if (options.userAdapter) log2(userAdapterOutcome(options.userAdapter));
37136
37065
  const executor = options.executor ?? new LocalBuildExecutor(localDependencies(sourceDir, log2));
37137
37066
  const observer = eventsObserver(
37138
37067
  cpUrl,
@@ -37161,7 +37090,8 @@ async function deployDirectory(sourceDir, options, log2) {
37161
37090
  // one, as before.
37162
37091
  env: ctx.environment ? { ...envToSend, ...ctx.env } : { ...ctx.env, ...envToSend },
37163
37092
  strategy: ctx.strategy ?? null,
37164
- ...ctx.vinextEnabled === void 0 ? {} : { vinextEnabled: ctx.vinextEnabled }
37093
+ ...ctx.vinextEnabled === void 0 ? {} : { vinextEnabled: ctx.vinextEnabled },
37094
+ ...options.userAdapter ?? storedAdapter ? { userAdapter: options.userAdapter ?? storedAdapter } : {}
37165
37095
  },
37166
37096
  observer,
37167
37097
  createCacheTransport({
@@ -37343,6 +37273,13 @@ export {
37343
37273
  promptForMissingEnv,
37344
37274
  pullEnv,
37345
37275
  detectGitInfo,
37276
+ detectUserAdapter,
37277
+ parseUserAdapter,
37278
+ USER_ADAPTER_OPTIONS,
37279
+ userAdapterQuestion,
37280
+ userAdapterStored,
37281
+ userAdapterUnasked,
37282
+ userAdapterUnreadable,
37346
37283
  version2 as version,
37347
37284
  DATA_ENV_MESSAGES,
37348
37285
  dataEnvironment,
@@ -37372,7 +37309,7 @@ export {
37372
37309
  runDbLint,
37373
37310
  BUNDLE_MESSAGES,
37374
37311
  functionsFolder,
37375
- parseJsonc2 as parseJsonc,
37312
+ parseJsonc,
37376
37313
  defaultSleep,
37377
37314
  retryAfterSeconds,
37378
37315
  withFunctionRetries,