naro-cli 0.27.0 → 0.28.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\-._~%!$&()*+,;=:@/[\]]*$/;
@@ -527,7 +527,10 @@ var CAPABILITIES = [
527
527
  id: "team-usage",
528
528
  domain: "teams",
529
529
  title: "\uD300 \uC0AC\uC6A9\uB7C9",
530
- routes: ["GET teams/[teamSlug]/usage"],
530
+ routes: [
531
+ "GET teams/[teamSlug]/usage",
532
+ "GET teams/[teamSlug]/usage/hosting"
533
+ ],
531
534
  ui: { kind: "built" },
532
535
  cli: { kind: "built", commands: ["naro usage"] },
533
536
  mcp: { kind: "built", tools: ["get_team_usage"] }
@@ -1873,6 +1876,19 @@ var CAPABILITIES = [
1873
1876
  cli: { kind: "built", commands: ["naro backend"] },
1874
1877
  mcp: { kind: "built", tools: ["get_backend"] }
1875
1878
  },
1879
+ {
1880
+ id: "backend-usage",
1881
+ domain: "backend",
1882
+ 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)",
1883
+ routes: ["GET projects/[projectId]/backend/usage"],
1884
+ ui: { kind: "built" },
1885
+ // 같은 숫자를 섹션마다 이미 돌려준다: naro db info(오늘 쓴 행·한도), naro
1886
+ // storage(용량·오늘 쓰기), naro functions(함수별 오늘 호출), naro realtime
1887
+ // (이번 달 메시지). 이 라우트는 개요가 한 번에 읽으려고 모은 것이다.
1888
+ cli: { kind: "built" },
1889
+ // get_database·get_storage·list_functions·get_realtime이 같은 숫자를 준다.
1890
+ mcp: { kind: "built" }
1891
+ },
1876
1892
  {
1877
1893
  id: "backend-enable",
1878
1894
  domain: "backend",
@@ -2248,7 +2264,7 @@ function parseDotenvDetailed(text3) {
2248
2264
  var STALL_WINDOW_MS = 10 * 6e4;
2249
2265
 
2250
2266
  // ../../packages/core/src/deployment-strategy.ts
2251
- var MIN_SUPPORTED_NEXT_MAJOR = 15;
2267
+ var MIN_SUPPORTED_NEXT_MAJOR = 16;
2252
2268
 
2253
2269
  // ../../packages/core/src/environment-guidance.ts
2254
2270
  var DATA_COMMANDS = /* @__PURE__ */ new Set([
@@ -17610,6 +17626,130 @@ var BUILDS = {
17610
17626
  }
17611
17627
  };
17612
17628
 
17629
+ // ../../packages/next-adapter/src/index.ts
17630
+ import { fileURLToPath } from "url";
17631
+
17632
+ // ../../packages/next-adapter/src/supported.ts
17633
+ var SUPPORTED_NEXT = [
17634
+ { minor: "16.2", next: "16.2.12", routing: "@next/routing-16.2" },
17635
+ { minor: "16.3", next: "16.3.8", routing: "@next/routing-16.3" },
17636
+ { minor: "16.4", next: "16.4.5", routing: "@next/routing-16.4" }
17637
+ ];
17638
+ var VERSION = /^(\d+)\.(\d+)\.(\d+)/;
17639
+ function judgeNextVersion(version3) {
17640
+ const match = VERSION.exec(version3.trim());
17641
+ if (!match) return null;
17642
+ const major = Number(match[1]);
17643
+ const minor = Number(match[2]);
17644
+ if (major < 16 || major === 16 && minor < 2) return { kind: "too-old" };
17645
+ const entry = SUPPORTED_NEXT.find(
17646
+ (candidate) => candidate.minor === `${String(major)}.${String(minor)}`
17647
+ );
17648
+ if (entry) return { kind: "supported", entry };
17649
+ const newest = SUPPORTED_NEXT[SUPPORTED_NEXT.length - 1];
17650
+ if (!newest) throw new Error("SUPPORTED_NEXT is empty.");
17651
+ return { kind: "newer", entry: newest };
17652
+ }
17653
+ function testedRange() {
17654
+ const first = SUPPORTED_NEXT[0];
17655
+ const last = SUPPORTED_NEXT[SUPPORTED_NEXT.length - 1];
17656
+ if (!first || !last) throw new Error("SUPPORTED_NEXT is empty.");
17657
+ return `${first.minor}\u2013${last.minor}`;
17658
+ }
17659
+
17660
+ // ../../packages/next-adapter/src/paths.ts
17661
+ var OUTPUT_DIR = ".naro";
17662
+ var BUILD_RECORD = ".naro/build.json";
17663
+ var WORKER_DIR = ".naro/worker";
17664
+ var ASSETS_DIR = ".naro/assets";
17665
+ var WRANGLER_CONFIG = ".naro/worker/wrangler.jsonc";
17666
+
17667
+ // ../../packages/next-adapter/src/bundle/wrangler.ts
17668
+ import { posix } from "path";
17669
+
17670
+ // ../../packages/next-adapter/src/config-wrapper.ts
17671
+ import { access, readFile as readFile2, rename, writeFile as writeFile2 } from "fs/promises";
17672
+ import { join as join2 } from "path";
17673
+ var PROJECT_CONFIG_BASENAME = "next.config.naro-user";
17674
+ var WRAPPABLE = {
17675
+ "next.config.js": {
17676
+ wrapper: "next.config.mjs",
17677
+ specifier: `./${PROJECT_CONFIG_BASENAME}.js`,
17678
+ typescript: false
17679
+ },
17680
+ "next.config.mjs": {
17681
+ wrapper: "next.config.mjs",
17682
+ specifier: `./${PROJECT_CONFIG_BASENAME}.mjs`,
17683
+ typescript: false
17684
+ },
17685
+ "next.config.ts": {
17686
+ wrapper: "next.config.ts",
17687
+ specifier: `./${PROJECT_CONFIG_BASENAME}`,
17688
+ typescript: true
17689
+ },
17690
+ "next.config.mts": {
17691
+ wrapper: "next.config.mts",
17692
+ specifier: `./${PROJECT_CONFIG_BASENAME}.mts`,
17693
+ typescript: true
17694
+ }
17695
+ };
17696
+ function nextConfigWrapperSource(projectSpecifier, typescript) {
17697
+ return `${typescript ? "// @ts-nocheck\n" : ""}// Written by Naro for this build only. The project's own next.config is
17698
+ // ${projectSpecifier}, unchanged; this wrapper swaps its Next.js adapter for
17699
+ // the Naro adapter (project setting "replace").
17700
+ import projectConfig from ${JSON.stringify(projectSpecifier)};
17701
+
17702
+ export default async function naroNextConfig(phase, context) {
17703
+ const config =
17704
+ typeof projectConfig === "function"
17705
+ ? await projectConfig(phase, context)
17706
+ : await projectConfig;
17707
+ const rest = { ...(config ?? {}) };
17708
+ delete rest.adapterPath;
17709
+ if (rest.experimental && typeof rest.experimental === "object") {
17710
+ rest.experimental = { ...rest.experimental };
17711
+ delete rest.experimental.adapterPath;
17712
+ }
17713
+ return { ...rest, adapterPath: process.env.NEXT_ADAPTER_PATH };
17714
+ }
17715
+ `;
17716
+ }
17717
+ async function exists(path) {
17718
+ try {
17719
+ await access(path);
17720
+ return true;
17721
+ } catch {
17722
+ return false;
17723
+ }
17724
+ }
17725
+ async function wrapNextConfig(appRoot, file2) {
17726
+ const plan = WRAPPABLE[file2];
17727
+ if (!plan) return null;
17728
+ const ext = file2.slice("next.config.".length);
17729
+ const project2 = `${PROJECT_CONFIG_BASENAME}.${ext}`;
17730
+ if (await exists(join2(appRoot, project2)))
17731
+ throw new Error(
17732
+ `${project2} already exists in the project; Naro needs that name to replace the project's Next.js adapter.`
17733
+ );
17734
+ await readFile2(join2(appRoot, file2));
17735
+ await rename(join2(appRoot, file2), join2(appRoot, project2));
17736
+ await writeFile2(
17737
+ join2(appRoot, plan.wrapper),
17738
+ nextConfigWrapperSource(plan.specifier, plan.typescript)
17739
+ );
17740
+ return { wrapper: plan.wrapper, project: project2 };
17741
+ }
17742
+
17743
+ // ../../packages/next-adapter/src/index.ts
17744
+ function nextAdapterPath(base2 = import.meta.url) {
17745
+ if (!base2) return void 0;
17746
+ try {
17747
+ return fileURLToPath(new URL("../dist/adapter.cjs", base2));
17748
+ } catch {
17749
+ return void 0;
17750
+ }
17751
+ }
17752
+
17613
17753
  // ../../packages/build/src/next-version.ts
17614
17754
  var NEXT_RELEASE_DATES = {
17615
17755
  16: "2025-10-21",
@@ -17619,7 +17759,6 @@ var NEXT_RELEASE_DATES = {
17619
17759
  12: "2021-10-26"
17620
17760
  };
17621
17761
  var SUPPORT_WINDOW_YEARS = 2;
17622
- var VERIFIED_15_FLOOR = { major: 15, minor: 5, patch: 21 };
17623
17762
  var VERIFIED_16_FLOOR = { major: 16, minor: 2, patch: 11 };
17624
17763
  function parseNextVersion(raw) {
17625
17764
  const match = /^\s*(\d+)\.(\d+)\.(\d+)/.exec(raw);
@@ -17647,7 +17786,6 @@ function compare(a, b) {
17647
17786
  return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
17648
17787
  }
17649
17788
  function withinVerifiedRange(version3) {
17650
- if (version3.major === 15) return compare(version3, VERIFIED_15_FLOOR) >= 0;
17651
17789
  if (version3.major === 16) return compare(version3, VERIFIED_16_FLOOR) >= 0;
17652
17790
  return version3.major > 16;
17653
17791
  }
@@ -17663,34 +17801,34 @@ function classifyNextVersion(raw, now) {
17663
17801
  reason: `Could not read a Next.js version from "${raw}".`
17664
17802
  };
17665
17803
  }
17666
- const windowEnd = supportEndsAt(version3.major);
17667
- if (!windowEnd || version3.major < VERIFIED_15_FLOOR.major) {
17804
+ if (version3.major < MIN_SUPPORTED_NEXT_MAJOR) {
17668
17805
  return {
17669
17806
  status: "too-old",
17670
17807
  version: version3,
17671
- reason: `Next.js ${raw} is too old to deploy. Naro deploys Next.js 15 or newer. Upgrade to Next.js 16.`
17808
+ 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
17809
  };
17673
17810
  }
17674
- if (now > windowEnd) {
17811
+ const windowEnd = supportEndsAt(version3.major);
17812
+ if (windowEnd && now > windowEnd) {
17675
17813
  return {
17676
- status: "needs-unsupported-flag",
17814
+ status: "past-support-window",
17677
17815
  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`
17816
+ 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
17817
  };
17680
17818
  }
17681
17819
  if (!withinVerifiedRange(version3)) {
17682
17820
  return {
17683
- status: "outside-peer-range",
17821
+ status: "outside-verified-range",
17684
17822
  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.`
17823
+ 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
17824
  };
17687
17825
  }
17688
17826
  return { status: "supported", version: version3, reason: "" };
17689
17827
  }
17690
17828
 
17691
17829
  // ../../packages/build/src/package-manager.ts
17692
- import { access, readFile as readFile2 } from "fs/promises";
17693
- import { join as join2 } from "path";
17830
+ import { access as access2, readFile as readFile3 } from "fs/promises";
17831
+ import { join as join3 } from "path";
17694
17832
  function cacheEnv(dir, manager) {
17695
17833
  return {
17696
17834
  ...manager === "pnpm" ? { npm_config_store_dir: dir } : {},
@@ -17710,9 +17848,9 @@ function withSharedCache(spec, dir) {
17710
17848
  addEnv: { ...spec.addEnv, ...cacheEnv(dir, spec.name) }
17711
17849
  };
17712
17850
  }
17713
- async function exists(path) {
17851
+ async function exists2(path) {
17714
17852
  try {
17715
- await access(path);
17853
+ await access2(path);
17716
17854
  return true;
17717
17855
  } catch {
17718
17856
  return false;
@@ -17720,7 +17858,7 @@ async function exists(path) {
17720
17858
  }
17721
17859
  async function usesModernYarn(root) {
17722
17860
  try {
17723
- const raw = await readFile2(join2(root, "package.json"), "utf8");
17861
+ const raw = await readFile3(join3(root, "package.json"), "utf8");
17724
17862
  const packageJson = JSON.parse(raw);
17725
17863
  if (typeof packageJson.packageManager !== "string") return false;
17726
17864
  const version3 = /^yarn@(\d+)/.exec(packageJson.packageManager)?.[1];
@@ -17730,7 +17868,7 @@ async function usesModernYarn(root) {
17730
17868
  }
17731
17869
  }
17732
17870
  async function detectPackageManager(root) {
17733
- if (await exists(join2(root, "pnpm-lock.yaml"))) {
17871
+ if (await exists2(join3(root, "pnpm-lock.yaml"))) {
17734
17872
  return {
17735
17873
  name: "pnpm",
17736
17874
  install: ["pnpm", "install", "--frozen-lockfile"],
@@ -17741,7 +17879,7 @@ async function detectPackageManager(root) {
17741
17879
  addArgs: (specs) => ["pnpm", "add", ...specs]
17742
17880
  };
17743
17881
  }
17744
- if (await exists(join2(root, "package-lock.json"))) {
17882
+ if (await exists2(join3(root, "package-lock.json"))) {
17745
17883
  return {
17746
17884
  name: "npm",
17747
17885
  install: ["npm", "ci"],
@@ -17767,7 +17905,7 @@ async function detectPackageManager(root) {
17767
17905
  sharedCacheAddArgs: ["--prefer-offline"]
17768
17906
  };
17769
17907
  }
17770
- if (await exists(join2(root, "yarn.lock"))) {
17908
+ if (await exists2(join3(root, "yarn.lock"))) {
17771
17909
  const modern = await usesModernYarn(root);
17772
17910
  return {
17773
17911
  name: "yarn",
@@ -17788,114 +17926,13 @@ async function detectPackageManager(root) {
17788
17926
  );
17789
17927
  }
17790
17928
 
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
17929
  // ../../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";
17930
+ import { access as access3, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
17931
+ import { join as join6, resolve } from "path";
17830
17932
 
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
- }
17933
+ // ../../packages/build/src/artifact-files.ts
17934
+ import { readdir, readFile as readFile4, realpath } from "fs/promises";
17935
+ import { extname, isAbsolute, join as join4, relative, sep } from "path";
17899
17936
  function contentType(path) {
17900
17937
  switch (extname(path).toLowerCase()) {
17901
17938
  case ".html":
@@ -17970,7 +18007,7 @@ async function assertInsideOnDisk(appRoot, directory, label2) {
17970
18007
  );
17971
18008
  }
17972
18009
  }
17973
- async function listArtifactFiles(root, label2 = "OpenNext", appRoot) {
18010
+ async function listArtifactFiles(root, label2 = "Build", appRoot) {
17974
18011
  if (appRoot !== void 0) await assertInsideOnDisk(appRoot, root, label2);
17975
18012
  const files = [];
17976
18013
  async function visit(directory) {
@@ -17998,131 +18035,12 @@ async function listArtifactFiles(root, label2 = "OpenNext", appRoot) {
17998
18035
  await visit(root);
17999
18036
  return files;
18000
18037
  }
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
- }
18038
+
18039
+ // ../../packages/build/src/isr-cache-source.generated.ts
18040
+ 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
18041
 
18123
18042
  // ../../packages/build/src/platform-config.ts
18124
- import { access as access4, writeFile as writeFile3 } from "fs/promises";
18125
- import { createRequire } from "module";
18043
+ import { writeFile as writeFile3 } from "fs/promises";
18126
18044
  import { join as join5 } from "path";
18127
18045
  var COMPATIBILITY_DATE = "2026-08-19";
18128
18046
  var NEXT_CONFIG_FILES = [
@@ -18131,14 +18049,6 @@ var NEXT_CONFIG_FILES = [
18131
18049
  "next.config.cjs",
18132
18050
  "next.config.ts"
18133
18051
  ];
18134
- async function pathExists(path) {
18135
- try {
18136
- await access4(path);
18137
- return true;
18138
- } catch {
18139
- return false;
18140
- }
18141
- }
18142
18052
  var BUILD_NPMRC = [
18143
18053
  "# Host package-manager credentials are intentionally unavailable.",
18144
18054
  "audit=false",
@@ -18153,61 +18063,40 @@ async function createPackageManagerConfig(appRoot, writer = async (path, content
18153
18063
  await writer(packageManagerConfigPath, BUILD_NPMRC);
18154
18064
  return packageManagerConfigPath;
18155
18065
  }
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;
18066
+
18067
+ // ../../packages/build/src/wrangler-output.ts
18068
+ function globToRegExp(glob) {
18069
+ let source = "";
18070
+ for (let index = 0; index < glob.length; index += 1) {
18071
+ const char = glob[index];
18072
+ if (char !== "*") {
18073
+ source += char?.replace(/[.+?^${}()|[\]\\]/g, String.raw`\$&`) ?? "";
18074
+ continue;
18075
+ }
18076
+ if (glob[index + 1] !== "*") {
18077
+ source += "[^/]*";
18078
+ continue;
18079
+ }
18080
+ if (glob[index + 2] === "/") {
18081
+ source += "(?:[^/]*/)*";
18082
+ index += 2;
18083
+ } else {
18084
+ source += ".*";
18085
+ index += 1;
18169
18086
  }
18170
18087
  }
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
- };
18088
+ return new RegExp(`^${source}$`);
18089
+ }
18090
+ function moduleRules(rules) {
18091
+ if (!Array.isArray(rules)) return [];
18092
+ return rules.flatMap((rule) => {
18093
+ const { type, globs } = rule;
18094
+ if (!Array.isArray(globs)) return [];
18095
+ return globs.filter((glob) => typeof glob === "string").map((glob) => ({
18096
+ type: typeof type === "string" ? type : "",
18097
+ test: globToRegExp(glob)
18098
+ }));
18099
+ });
18211
18100
  }
18212
18101
 
18213
18102
  // ../../packages/build/src/vinext.ts
@@ -18230,6 +18119,8 @@ var VITE_CONFIG_NAMES = [
18230
18119
  ];
18231
18120
  var GENERATED_VITE_CONFIG = "vite.config.js";
18232
18121
  var GENERATED_WORKER_ENTRY = "naro-worker.js";
18122
+ var GENERATED_ISR_CACHE = "naro-isr-cache.js";
18123
+ var ISR_SHARED_CACHE_OFF_NOTE = "ISR shared cache is off: the project builds with its own vite.config.";
18233
18124
  var VINEXT_WORKER_ENTRY_SOURCE = `// Written by naro: vinext's Worker, with getCloudflareContext() given its env.
18234
18125
  import { AsyncLocalStorage } from "node:async_hooks";
18235
18126
  import handler from "vinext/server/fetch-handler";
@@ -18264,7 +18155,7 @@ function vinextWranglerConfigPath(appRoot) {
18264
18155
  }
18265
18156
  async function exists3(path) {
18266
18157
  try {
18267
- await access5(path);
18158
+ await access3(path);
18268
18159
  return true;
18269
18160
  } catch {
18270
18161
  return false;
@@ -18288,6 +18179,13 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
18288
18179
  `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
18180
  );
18290
18181
  }
18182
+ const isrCachePath = join6(appRoot, GENERATED_ISR_CACHE);
18183
+ if (await exists3(isrCachePath)) {
18184
+ throw new DeploymentError(
18185
+ "ARTIFACT_INVALID",
18186
+ `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.`
18187
+ );
18188
+ }
18291
18189
  const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
18292
18190
  const viteConfigPath = join6(appRoot, GENERATED_VITE_CONFIG);
18293
18191
  const wranglerConfigPath = join6(appRoot, "wrangler.jsonc");
@@ -18300,7 +18198,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
18300
18198
  "",
18301
18199
  "export default defineConfig({",
18302
18200
  " plugins: [",
18303
- " vinext(),",
18201
+ ` vinext({ cache: { data: { adapter: ${JSON.stringify(isrCachePath)} } } }),`,
18304
18202
  ' cloudflare({ viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] } }),',
18305
18203
  " ],",
18306
18204
  "});",
@@ -18308,6 +18206,7 @@ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (pa
18308
18206
  ].join("\n")
18309
18207
  );
18310
18208
  await writer(workerEntryPath, VINEXT_WORKER_ENTRY_SOURCE);
18209
+ await writer(isrCachePath, NARO_ISR_CACHE_SOURCE);
18311
18210
  await writer(
18312
18211
  wranglerConfigPath,
18313
18212
  `${JSON.stringify(
@@ -18360,8 +18259,8 @@ function parseVinextCheckReport(output) {
18360
18259
  function vinextRouteFromCheck(report) {
18361
18260
  if (!report)
18362
18261
  return {
18363
- eligible: false,
18364
- reason: "vinext check output could not be read; staying on OpenNext. Expected if vinext changed its report format."
18262
+ eligible: true,
18263
+ reason: "vinext check output could not be read; trying the build anyway. Expected if vinext changed its report format."
18365
18264
  };
18366
18265
  const autoFixed = report.issueLines.filter(
18367
18266
  (line) => AUTO_FIXED_ISSUES.some((pattern) => pattern.test(line))
@@ -18373,7 +18272,7 @@ function vinextRouteFromCheck(report) {
18373
18272
  if (blocking > 0)
18374
18273
  return {
18375
18274
  eligible: false,
18376
- reason: `vinext check reported ${blocking} unsupported item(s) (${report.percent}% compatible); staying on OpenNext.`
18275
+ reason: `vinext check reported ${blocking} unsupported item(s) (${report.percent}% compatible).`
18377
18276
  };
18378
18277
  if (autoFixed.length > 0)
18379
18278
  return {
@@ -18385,38 +18284,12 @@ function vinextRouteFromCheck(report) {
18385
18284
  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
18285
  };
18387
18286
  }
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
18287
  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);
18288
+ return moduleRules(rules).map((rule) => rule.test);
18416
18289
  }
18417
18290
  async function readVinextArtifact(appRoot, identity) {
18418
18291
  try {
18419
- const outputRoot = resolve2(appRoot, VINEXT_OUTPUT_DIR);
18292
+ const outputRoot = resolve(appRoot, VINEXT_OUTPUT_DIR);
18420
18293
  const serverRoot = join6(outputRoot, SERVER_DIR);
18421
18294
  const clientRoot = join6(outputRoot, CLIENT_DIR);
18422
18295
  const configPath = join6(serverRoot, WRANGLER_OUTPUT_NAME);
@@ -18477,7 +18350,7 @@ var FRAMEWORK_PRESETS = {
18477
18350
  id: "next",
18478
18351
  detectDependency: "next",
18479
18352
  defaultBuildScript: "build",
18480
- defaultOutputDirectory: ".open-next",
18353
+ defaultOutputDirectory: "dist",
18481
18354
  needsServerRuntime: true,
18482
18355
  spaFallback: false,
18483
18356
  cachePath: ".next/cache"
@@ -18815,9 +18688,85 @@ async function detectFramework(root, overrides = {}) {
18815
18688
  };
18816
18689
  }
18817
18690
 
18691
+ // ../../packages/build/src/naro-adapter-artifact.ts
18692
+ import { readFile as readFile7 } from "fs/promises";
18693
+ import { join as join8 } from "path";
18694
+ var RULE_CONTENT_TYPES = {
18695
+ ESModule: "application/javascript+module",
18696
+ CommonJS: "application/javascript",
18697
+ Text: "text/plain",
18698
+ Data: "application/octet-stream",
18699
+ CompiledWasm: "application/wasm"
18700
+ };
18701
+ var CONFIG_NAME = "wrangler.jsonc";
18702
+ async function readNaroAdapterArtifact(appRoot, identity) {
18703
+ try {
18704
+ const workerRoot = join8(appRoot, WORKER_DIR);
18705
+ let metadata;
18706
+ try {
18707
+ metadata = JSON.parse(
18708
+ await readFile7(join8(workerRoot, CONFIG_NAME), "utf8")
18709
+ );
18710
+ } catch (error51) {
18711
+ throw new DeploymentError(
18712
+ "ARTIFACT_INVALID",
18713
+ `The Naro adapter produced no ${WORKER_DIR}/${CONFIG_NAME}.`,
18714
+ { cause: error51 }
18715
+ );
18716
+ }
18717
+ if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string")
18718
+ throw new DeploymentError(
18719
+ "ARTIFACT_INVALID",
18720
+ "Naro adapter wrangler output must declare main and compatibility_date."
18721
+ );
18722
+ const rules = moduleRules(metadata.rules);
18723
+ const files = await listArtifactFiles(workerRoot, "naro-adapter", appRoot);
18724
+ const entry = files.find((file2) => file2.name === metadata.main);
18725
+ if (!entry)
18726
+ throw new DeploymentError(
18727
+ "ARTIFACT_INVALID",
18728
+ `Naro adapter worker entrypoint "${metadata.main}" is missing from the build output.`
18729
+ );
18730
+ const modules = files.filter(
18731
+ (file2) => file2.name !== metadata.main && file2.name !== CONFIG_NAME && !file2.name.endsWith(".map")
18732
+ ).map((file2) => {
18733
+ const rule = rules.find((candidate) => candidate.test.test(file2.name));
18734
+ const contentType2 = rule ? RULE_CONTENT_TYPES[rule.type] : void 0;
18735
+ if (!contentType2)
18736
+ throw new DeploymentError(
18737
+ "ARTIFACT_INVALID",
18738
+ `Naro adapter output file "${file2.name}" matches no module rule.`
18739
+ );
18740
+ return { ...file2, contentType: contentType2 };
18741
+ });
18742
+ return {
18743
+ ...identity,
18744
+ entrypoint: { ...entry, contentType: "application/javascript+module" },
18745
+ modules,
18746
+ assets: await listArtifactFiles(
18747
+ join8(appRoot, ASSETS_DIR),
18748
+ "naro-adapter",
18749
+ appRoot
18750
+ ),
18751
+ compatibilityDate: metadata.compatibility_date,
18752
+ compatibilityFlags: Array.isArray(metadata.compatibility_flags) ? metadata.compatibility_flags.filter(
18753
+ (flag) => typeof flag === "string"
18754
+ ) : [],
18755
+ assetsBinding: typeof metadata.assets?.binding === "string" ? metadata.assets.binding : "ASSETS"
18756
+ };
18757
+ } catch (error51) {
18758
+ if (error51 instanceof DeploymentError) throw error51;
18759
+ throw new DeploymentError(
18760
+ "ARTIFACT_INVALID",
18761
+ "Could not read the Naro adapter output.",
18762
+ { cause: error51 }
18763
+ );
18764
+ }
18765
+ }
18766
+
18818
18767
  // ../../packages/build/src/next-routes.ts
18819
18768
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
18820
- import { join as join8 } from "path";
18769
+ import { join as join9 } from "path";
18821
18770
  var APP_PAGE_FILE = /^page\.(?:js|jsx|ts|tsx|md|mdx)$/;
18822
18771
  var PAGES_ROUTER_FILE = /^(.+)\.(?:js|jsx|ts|tsx)$/;
18823
18772
  var DECLARATION_FILE = /\.d\.ts$/;
@@ -18831,7 +18780,7 @@ function errorCode(error51) {
18831
18780
  }
18832
18781
  async function firstDirectory(root, candidates) {
18833
18782
  for (const candidate of candidates) {
18834
- const path = join8(root, candidate);
18783
+ const path = join9(root, candidate);
18835
18784
  try {
18836
18785
  if ((await stat2(path)).isDirectory()) return path;
18837
18786
  } catch (error51) {
@@ -18861,7 +18810,7 @@ async function readAppRouter(dir, segments, routes) {
18861
18810
  const name = entry.name;
18862
18811
  if (name.startsWith("_") || INTERCEPTING.test(name)) continue;
18863
18812
  const next = ROUTE_GROUP.test(name) || name.startsWith("@") ? segments : [...segments, urlSegment(name, "app")];
18864
- await readAppRouter(join8(dir, name), next, routes);
18813
+ await readAppRouter(join9(dir, name), next, routes);
18865
18814
  }
18866
18815
  }
18867
18816
  async function readPagesRouter(dir, segments, routes) {
@@ -18870,7 +18819,7 @@ async function readPagesRouter(dir, segments, routes) {
18870
18819
  if (entry.isDirectory()) {
18871
18820
  if (segments.length === 0 && entry.name === "api") continue;
18872
18821
  await readPagesRouter(
18873
- join8(dir, entry.name),
18822
+ join9(dir, entry.name),
18874
18823
  [...segments, urlSegment(entry.name, "pages")],
18875
18824
  routes
18876
18825
  );
@@ -18888,8 +18837,8 @@ async function readPagesRouter(dir, segments, routes) {
18888
18837
  }
18889
18838
  }
18890
18839
  async function listNextRoutes(appRoot) {
18891
- const app = await firstDirectory(appRoot, ["app", join8("src", "app")]);
18892
- const pages = await firstDirectory(appRoot, ["pages", join8("src", "pages")]);
18840
+ const app = await firstDirectory(appRoot, ["app", join9("src", "app")]);
18841
+ const pages = await firstDirectory(appRoot, ["pages", join9("src", "pages")]);
18893
18842
  if (!app && !pages) return null;
18894
18843
  const routes = /* @__PURE__ */ new Set();
18895
18844
  if (app) await readAppRouter(app, [], routes);
@@ -19126,14 +19075,14 @@ async function runCommand(spec, observer) {
19126
19075
 
19127
19076
  // ../../packages/build/src/repository.ts
19128
19077
  import { realpath as realpath2 } from "fs/promises";
19129
- import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
19078
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "path";
19130
19079
  function resolveAppRoot(checkout, configuredRoot) {
19131
- const checkoutRoot = resolve3(checkout);
19080
+ const checkoutRoot = resolve2(checkout);
19132
19081
  if (!configuredRoot) return checkoutRoot;
19133
19082
  if (isAbsolute2(configuredRoot)) {
19134
19083
  throw new Error("Configured root directory escapes repository");
19135
19084
  }
19136
- const appRoot = resolve3(checkoutRoot, configuredRoot);
19085
+ const appRoot = resolve2(checkoutRoot, configuredRoot);
19137
19086
  const relativeRoot = relative2(checkoutRoot, appRoot);
19138
19087
  if (relativeRoot === ".." || relativeRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(relativeRoot)) {
19139
19088
  throw new Error("Configured root directory escapes repository");
@@ -19164,8 +19113,8 @@ async function assertAppRootInsideCheckout(checkout, appRoot) {
19164
19113
  }
19165
19114
 
19166
19115
  // ../../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";
19116
+ import { readdir as readdir3, readFile as readFile8 } from "fs/promises";
19117
+ import { isAbsolute as isAbsolute3, join as join10, relative as relative3, resolve as resolve3, sep as sep3 } from "path";
19169
19118
  var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
19170
19119
  var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
19171
19120
  var ALLOWED_DOTFILE = ".well-known";
@@ -19179,7 +19128,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19179
19128
  "ARTIFACT_INVALID",
19180
19129
  "Output directory must be relative to the application root."
19181
19130
  );
19182
- const outputRoot = resolve4(appRoot, outputDirectory);
19131
+ const outputRoot = resolve3(appRoot, outputDirectory);
19183
19132
  const relation = relative3(appRoot, outputRoot);
19184
19133
  if (relation === ".." || relation.startsWith(`..${sep3}`) || isAbsolute3(relation))
19185
19134
  throw new DeploymentError(
@@ -19198,7 +19147,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19198
19147
  (left, right) => left.name.localeCompare(right.name)
19199
19148
  )) {
19200
19149
  if (excluded(entry.name)) continue;
19201
- const path = join9(directory, entry.name);
19150
+ const path = join10(directory, entry.name);
19202
19151
  if (entry.isSymbolicLink())
19203
19152
  throw new DeploymentError(
19204
19153
  "ARTIFACT_INVALID",
@@ -19208,7 +19157,7 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19208
19157
  else if (entry.isFile()) {
19209
19158
  assets.push({
19210
19159
  name: relative3(outputRoot, path).split(sep3).join("/"),
19211
- data: await readFile7(path),
19160
+ data: await readFile8(path),
19212
19161
  contentType: contentType(path)
19213
19162
  });
19214
19163
  }
@@ -19254,8 +19203,8 @@ async function readStaticArtifact(appRoot, outputDirectory, identity, options, o
19254
19203
 
19255
19204
  // ../../packages/build/src/strategy/fingerprint.ts
19256
19205
  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";
19206
+ import { readFile as readFile9 } from "fs/promises";
19207
+ import { join as join11, relative as relative4, sep as sep4 } from "path";
19259
19208
  var APP_ROOT_FILES = [
19260
19209
  "package.json",
19261
19210
  "next.config.js",
@@ -19292,18 +19241,18 @@ function sha256(data) {
19292
19241
  }
19293
19242
  async function hashFile(path) {
19294
19243
  try {
19295
- return sha256(await readFile8(path));
19244
+ return sha256(await readFile9(path));
19296
19245
  } catch {
19297
19246
  return null;
19298
19247
  }
19299
19248
  }
19300
19249
  async function hashStrategyInputs(checkout, appRoot) {
19301
19250
  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));
19251
+ for (const name of CHECKOUT_FILES) candidates.add(join11(checkout, name));
19252
+ for (const name of APP_ROOT_FILES) candidates.add(join11(appRoot, name));
19304
19253
  for (const base2 of SOURCE_FILES) {
19305
19254
  for (const ext of SOURCE_EXTENSIONS) {
19306
- candidates.add(join10(appRoot, `${base2}.${ext}`));
19255
+ candidates.add(join11(appRoot, `${base2}.${ext}`));
19307
19256
  }
19308
19257
  }
19309
19258
  const entries = [];
@@ -19331,12 +19280,12 @@ async function computeStrategyFingerprint(input) {
19331
19280
  }
19332
19281
 
19333
19282
  // ../../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";
19283
+ import { access as access4, readFile as readFile11 } from "fs/promises";
19284
+ import { join as join13 } from "path";
19336
19285
 
19337
19286
  // ../../packages/build/src/strategy/user-adapter.ts
19338
- import { readFile as readFile9 } from "fs/promises";
19339
- import { join as join11 } from "path";
19287
+ import { readFile as readFile10 } from "fs/promises";
19288
+ import { join as join12 } from "path";
19340
19289
  var ADAPTER_PATH = /\badapterPath\b/;
19341
19290
  var EXPERIMENTAL_ADAPTER_PATH = /\bexperimental\s*:\s*\{[^{}]*\badapterPath\b/;
19342
19291
  function adapterKeyFromConfigSource(source) {
@@ -19368,8 +19317,8 @@ async function adapterInBuildOutput(appRoot, nextVersion) {
19368
19317
  let manifest2;
19369
19318
  try {
19370
19319
  const parsed = JSON.parse(
19371
- await readFile9(
19372
- join11(appRoot, ".next", "required-server-files.json"),
19320
+ await readFile10(
19321
+ join12(appRoot, ".next", "required-server-files.json"),
19373
19322
  "utf8"
19374
19323
  )
19375
19324
  );
@@ -19380,10 +19329,14 @@ async function adapterInBuildOutput(appRoot, nextVersion) {
19380
19329
  }
19381
19330
  const topLevel = manifest2.config?.adapterPath;
19382
19331
  if (keys.includes("adapterPath") && typeof topLevel === "string" && topLevel)
19383
- return { source: "build-output", key: "adapterPath" };
19332
+ return { source: "build-output", key: "adapterPath", path: topLevel };
19384
19333
  const experimental = manifest2.config?.experimental?.adapterPath;
19385
19334
  if (keys.includes("experimental.adapterPath") && typeof experimental === "string" && experimental)
19386
- return { source: "build-output", key: "experimental.adapterPath" };
19335
+ return {
19336
+ source: "build-output",
19337
+ key: "experimental.adapterPath",
19338
+ path: experimental
19339
+ };
19387
19340
  return null;
19388
19341
  }
19389
19342
  function describeUserAdapter(adapter) {
@@ -19396,9 +19349,6 @@ function describeUserAdapter(adapter) {
19396
19349
  return `${adapter.key} in the Next.js build output`;
19397
19350
  }
19398
19351
  }
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
19352
 
19403
19353
  // ../../packages/build/src/strategy/project-facts.ts
19404
19354
  var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
@@ -19411,17 +19361,17 @@ var VITE_CONFIGS = [
19411
19361
  ];
19412
19362
  async function exists4(path) {
19413
19363
  try {
19414
- await access6(path);
19364
+ await access4(path);
19415
19365
  return true;
19416
19366
  } catch {
19417
19367
  return false;
19418
19368
  }
19419
19369
  }
19420
- async function findSourceFile(appRoot, basename6) {
19370
+ async function findSourceFile(appRoot, basename5) {
19421
19371
  for (const dir of SOURCE_DIRECTORIES) {
19422
19372
  for (const ext of SOURCE_EXTENSIONS2) {
19423
- const rel = dir ? `${dir}/${basename6}.${ext}` : `${basename6}.${ext}`;
19424
- if (await exists4(join12(appRoot, rel))) return rel;
19373
+ const rel = dir ? `${dir}/${basename5}.${ext}` : `${basename5}.${ext}`;
19374
+ if (await exists4(join13(appRoot, rel))) return rel;
19425
19375
  }
19426
19376
  }
19427
19377
  return null;
@@ -19441,14 +19391,15 @@ function matcherRoutesFromSource(source) {
19441
19391
  }
19442
19392
  async function readIfExists(path) {
19443
19393
  try {
19444
- return await readFile10(path, "utf8");
19394
+ return await readFile11(path, "utf8");
19445
19395
  } catch {
19446
19396
  return null;
19447
19397
  }
19448
19398
  }
19449
19399
  async function findAdapterConfig(appRoot) {
19450
19400
  for (const file2 of NEXT_CONFIG_FILES) {
19451
- const source = await readIfExists(join12(appRoot, file2));
19401
+ if (file2 === "next.config.cjs") continue;
19402
+ const source = await readIfExists(join13(appRoot, file2));
19452
19403
  if (source === null) continue;
19453
19404
  const key = adapterKeyFromConfigSource(source);
19454
19405
  return key ? { file: file2, key } : null;
@@ -19459,10 +19410,10 @@ async function collectProjectFacts(appRoot) {
19459
19410
  const proxyFile = await findSourceFile(appRoot, "proxy");
19460
19411
  const middlewareFile = await findSourceFile(appRoot, "middleware");
19461
19412
  const routeSourceFile = middlewareFile ?? proxyFile;
19462
- const routeSource = routeSourceFile ? await readIfExists(join12(appRoot, routeSourceFile)) : null;
19413
+ const routeSource = routeSourceFile ? await readIfExists(join13(appRoot, routeSourceFile)) : null;
19463
19414
  let usesVinext = false;
19464
19415
  let nextSpec = null;
19465
- const packageJsonSource = await readIfExists(join12(appRoot, "package.json"));
19416
+ const packageJsonSource = await readIfExists(join13(appRoot, "package.json"));
19466
19417
  if (packageJsonSource) {
19467
19418
  try {
19468
19419
  const packageJson = JSON.parse(packageJsonSource);
@@ -19473,7 +19424,7 @@ async function collectProjectFacts(appRoot) {
19473
19424
  nextSpec = dependency("next");
19474
19425
  if (dependency("vinext") !== null) {
19475
19426
  for (const name of VITE_CONFIGS) {
19476
- const config2 = await readIfExists(join12(appRoot, name));
19427
+ const config2 = await readIfExists(join13(appRoot, name));
19477
19428
  if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
19478
19429
  usesVinext = true;
19479
19430
  break;
@@ -19742,7 +19693,8 @@ async function fetchStatus(fetchImpl, url2, timeoutMs) {
19742
19693
  clearTimeout(timer);
19743
19694
  }
19744
19695
  }
19745
- function probeOk(status) {
19696
+ function probeOk(status, expect) {
19697
+ if (expect === "found" && status === 404) return false;
19746
19698
  return status < 500;
19747
19699
  }
19748
19700
  function createSmokeTester(options = {}) {
@@ -19829,7 +19781,11 @@ function createSmokeTester(options = {}) {
19829
19781
  `${origin}${probe.path}`,
19830
19782
  probeTimeoutMs
19831
19783
  );
19832
- const result = "status" in answer ? { ...probe, status: answer.status, ok: probeOk(answer.status) } : { ...probe, status: null, ok: false, detail: answer.error };
19784
+ const result = "status" in answer ? {
19785
+ ...probe,
19786
+ status: answer.status,
19787
+ ok: probeOk(answer.status, probe.expect)
19788
+ } : { ...probe, status: null, ok: false, detail: answer.error };
19833
19789
  probes2.push(result);
19834
19790
  await observer.onLog({
19835
19791
  level: result.ok ? "INFO" : "ERROR",
@@ -19874,13 +19830,40 @@ function defaultSmokeProbes(matcherRoutes) {
19874
19830
  return probes2;
19875
19831
  }
19876
19832
 
19877
- // ../../packages/build/src/strategy/strategies/opennext-aws.ts
19878
- import { writeFile as writeFile6 } from "fs/promises";
19879
- import { join as join14 } from "path";
19833
+ // ../../packages/build/src/strategy/strategies/unavailable.ts
19834
+ function createUnavailableStrategy(options) {
19835
+ return {
19836
+ id: options.id,
19837
+ priority: options.priority,
19838
+ target: options.target,
19839
+ supports: () => ({ supported: false, reason: options.reason }),
19840
+ evaluate: async () => ({ status: "skipped", reason: options.reason })
19841
+ };
19842
+ }
19843
+ function createSplitCloudflareStrategy() {
19844
+ return createUnavailableStrategy({
19845
+ id: "split-cloudflare",
19846
+ priority: 3,
19847
+ target: "cloudflare-split",
19848
+ 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."
19849
+ });
19850
+ }
19851
+ function createAwsMiddlewareCloudflareAppStrategy() {
19852
+ return createUnavailableStrategy({
19853
+ id: "aws-middleware-cloudflare-app",
19854
+ priority: 4,
19855
+ target: "aws-hybrid",
19856
+ reason: "Hybrid AWS middleware + Cloudflare app needs an AWS deploy target and cross-cloud request chaining; neither exists on this control plane."
19857
+ });
19858
+ }
19859
+
19860
+ // ../../packages/build/src/strategy/strategies/naro-adapter-cloudflare.ts
19861
+ import { readFile as readFile13, rm as rm2 } from "fs/promises";
19862
+ import { join as join15, resolve as resolve5 } from "path";
19880
19863
 
19881
19864
  // ../../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";
19865
+ import { access as access5, readFile as readFile12, rm, writeFile as writeFile5 } from "fs/promises";
19866
+ import { dirname, join as join14, resolve as resolve4 } from "path";
19884
19867
  function capturingObserver(observer) {
19885
19868
  const lines = [];
19886
19869
  return {
@@ -19916,31 +19899,28 @@ function describeFailure(step) {
19916
19899
  function failed(stage, reason) {
19917
19900
  return { status: "failed", stage, reason };
19918
19901
  }
19919
- function appBuildFailed(reason) {
19920
- return { status: "failed", stage: "build", reason, appBuild: true };
19921
- }
19922
19902
  async function info(observer, message) {
19923
19903
  await observer.onLog({ level: "INFO", stage: "BUILDING", message });
19924
19904
  }
19925
19905
  async function warn(observer, message) {
19926
19906
  await observer.onLog({ level: "WARN", stage: "BUILDING", message });
19927
19907
  }
19928
- async function pathExists2(path) {
19908
+ async function pathExists(path) {
19929
19909
  try {
19930
- await access7(path);
19910
+ await access5(path);
19931
19911
  return true;
19932
19912
  } catch {
19933
19913
  return false;
19934
19914
  }
19935
19915
  }
19936
19916
  async function findProjectBin(appRoot, checkout, name) {
19937
- let dir = resolve5(appRoot);
19938
- const stop = resolve5(checkout);
19917
+ let dir = resolve4(appRoot);
19918
+ const stop = resolve4(checkout);
19939
19919
  for (; ; ) {
19940
- const candidate = join13(dir, "node_modules", ".bin", name);
19941
- if (await pathExists2(candidate)) return candidate;
19920
+ const candidate = join14(dir, "node_modules", ".bin", name);
19921
+ if (await pathExists(candidate)) return candidate;
19942
19922
  if (dir === stop) return null;
19943
- const parent = dirname2(dir);
19923
+ const parent = dirname(dir);
19944
19924
  if (parent === dir) return null;
19945
19925
  dir = parent;
19946
19926
  }
@@ -19951,7 +19931,7 @@ function dotenvQuote(value) {
19951
19931
  return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
19952
19932
  }
19953
19933
  async function withDevVars(wranglerConfigPath, env, run) {
19954
- const path = join13(dirname2(wranglerConfigPath), ".dev.vars");
19934
+ const path = join14(dirname(wranglerConfigPath), ".dev.vars");
19955
19935
  const keys = Object.keys(env).filter(
19956
19936
  (key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
19957
19937
  );
@@ -19968,36 +19948,7 @@ async function withDevVars(wranglerConfigPath, env, run) {
19968
19948
  await rm(path, { force: true });
19969
19949
  }
19970
19950
  }
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) {
19951
+ async function smokeWorker(context, layout, observer, extraProbes = []) {
20001
19952
  return withDevVars(
20002
19953
  layout.wranglerConfigPath,
20003
19954
  context.env,
@@ -20017,19 +19968,22 @@ async function smokeWorker(context, layout, observer) {
20017
19968
  "--show-interactive-dev-session=false",
20018
19969
  "--log-level=warn",
20019
19970
  "--persist-to",
20020
- join13(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
19971
+ join14(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
20021
19972
  ],
20022
19973
  cwd: layout.appRoot,
20023
19974
  env: {
20024
19975
  CI: "1",
20025
- WRANGLER_LOG_PATH: join13(
19976
+ WRANGLER_LOG_PATH: join14(
20026
19977
  layout.appRoot,
20027
19978
  layout.outputRoot,
20028
19979
  "wrangler-dev.log"
20029
19980
  )
20030
19981
  }
20031
19982
  }),
20032
- probes: defaultSmokeProbes(context.facts.matcherRoutes),
19983
+ probes: [
19984
+ ...defaultSmokeProbes(context.facts.matcherRoutes),
19985
+ ...extraProbes
19986
+ ],
20033
19987
  stage: "BUILDING",
20034
19988
  errorCode: "BUILD_FAILED"
20035
19989
  },
@@ -20038,10 +19992,10 @@ async function smokeWorker(context, layout, observer) {
20038
19992
  );
20039
19993
  }
20040
19994
  async function approvePnpmBuilds(checkout, packages) {
20041
- const path = join13(checkout, "pnpm-workspace.yaml");
19995
+ const path = join14(checkout, "pnpm-workspace.yaml");
20042
19996
  let source = "";
20043
19997
  try {
20044
- source = await readFile11(path, "utf8");
19998
+ source = await readFile12(path, "utf8");
20045
19999
  } catch {
20046
20000
  }
20047
20001
  let next = source;
@@ -20088,412 +20042,191 @@ ${listEntries}
20088
20042
  }
20089
20043
  }
20090
20044
 
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");
20045
+ // ../../packages/build/src/strategy/strategies/naro-adapter-cloudflare.ts
20046
+ var NARO_ADAPTER_REASONS = {
20047
+ notInstalled: "The Naro adapter is not installed in this build environment.",
20048
+ unknownVersion: "The installed Next.js version could not be read; the Naro adapter needs Next.js 16.2 or newer.",
20049
+ missingOutput: "next build finished without the Naro adapter's output; another Next.js adapter may have run instead.",
20050
+ 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.`
20051
+ };
20052
+ 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.";
20053
+ function replaceWarning(adapter) {
20054
+ 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}`;
20055
+ }
20056
+ async function readRecord(appRoot) {
20057
+ try {
20058
+ const parsed = JSON.parse(
20059
+ await readFile13(join15(appRoot, BUILD_RECORD), "utf8")
20060
+ );
20061
+ if (typeof parsed.buildId !== "string" || typeof parsed.distDir !== "string")
20062
+ return null;
20063
+ const staticFiles = Array.isArray(parsed.outputs?.staticFiles) ? parsed.outputs.staticFiles : [];
20064
+ return {
20065
+ buildId: parsed.buildId,
20066
+ distDir: parsed.distDir,
20067
+ basePath: typeof parsed.config?.basePath === "string" ? parsed.config.basePath : "",
20068
+ staticPathnames: staticFiles.flatMap((file2) => {
20069
+ const pathname = file2?.pathname;
20070
+ return typeof pathname === "string" ? [pathname] : [];
20071
+ })
20072
+ };
20073
+ } catch {
20074
+ return null;
20075
+ }
20076
+ }
20077
+ function naroAdapterSmokeProbes(record3, assetNames) {
20078
+ const { basePath } = record3;
20079
+ const probes2 = [];
20080
+ const assetPrefix = `${basePath}/_next/static/`;
20081
+ const script = assetNames.find(
20082
+ (name) => `/${name}`.startsWith(assetPrefix) && name.endsWith(".js")
20083
+ );
20084
+ if (script)
20085
+ probes2.push({ path: `/${script}`, label: "static asset", expect: "found" });
20086
+ const notPages = new Set(
20087
+ ["/404", "/500", "/index"].map((path) => `${basePath}${path}`)
20088
+ );
20089
+ const page2 = record3.staticPathnames.find(
20090
+ (pathname) => !pathname.startsWith(assetPrefix) && !pathname.endsWith(".rsc") && !notPages.has(pathname)
20091
+ );
20092
+ if (page2) probes2.push({ path: page2, label: "static page", expect: "found" });
20093
+ return probes2;
20094
+ }
20095
+ async function readBuildId(distDir) {
20096
+ try {
20097
+ return (await readFile13(join15(distDir, "BUILD_ID"), "utf8")).trim();
20098
+ } catch {
20099
+ return null;
20100
+ }
20107
20101
  }
20108
- function createOpenNextAwsStrategy() {
20102
+ function createNaroAdapterCloudflareStrategy(options) {
20109
20103
  return {
20110
- id: "opennext-aws",
20111
- priority: 5,
20112
- target: "aws",
20104
+ id: "naro-adapter-cloudflare",
20105
+ priority: 2,
20106
+ target: "cloudflare",
20113
20107
  supports(context) {
20114
- if (!context.targets.aws)
20108
+ if (!context.toolchain.nextAdapter)
20109
+ return { supported: false, reason: NARO_ADAPTER_REASONS.notInstalled };
20110
+ const verdict = judgeNextVersion(context.nextVersion);
20111
+ if (!verdict)
20115
20112
  return {
20116
20113
  supported: false,
20117
- reason: "No AWS deploy target is configured on this control plane (no provider, credentials, or routing exist for it)."
20114
+ reason: NARO_ADAPTER_REASONS.unknownVersion
20118
20115
  };
20119
- if (!context.toolchain.openNextAws)
20116
+ if (verdict.kind === "too-old")
20120
20117
  return {
20121
20118
  supported: false,
20122
- reason: "The @opennextjs/aws toolchain is not installed on this build runner."
20119
+ reason: `The Naro adapter needs Next.js 16.2 or newer; this project runs Next.js ${context.nextVersion}.`
20120
+ };
20121
+ const adapter = userAdapterOf(context);
20122
+ if (adapter && context.userAdapter === "keep")
20123
+ return {
20124
+ supported: false,
20125
+ reason: `The project keeps its own Next.js adapter (${describeUserAdapter(adapter)}) by its Next.js adapter setting, so the Naro adapter is skipped.`
20123
20126
  };
20124
20127
  return { supported: true };
20125
20128
  },
20126
20129
  async evaluate(context, observer) {
20127
- await info(
20128
- observer,
20129
- "P5 opennext-aws: building with the OpenNext AWS adapter."
20130
- );
20130
+ const adapterPath = context.toolchain.nextAdapter;
20131
+ if (!adapterPath)
20132
+ return { status: "skipped", reason: NARO_ADAPTER_REASONS.notInstalled };
20133
+ if (judgeNextVersion(context.nextVersion)?.kind === "newer")
20134
+ await warn(
20135
+ observer,
20136
+ `Next.js ${context.nextVersion} is newer than the versions the Naro adapter is tested with (${testedRange()}); building anyway.`
20137
+ );
20131
20138
  let fork;
20132
20139
  try {
20133
- fork = await context.workspace.fork("opennext-aws");
20140
+ fork = await context.workspace.fork("naro-adapter");
20134
20141
  } catch (error51) {
20135
20142
  return failed("prepare", describeError(error51));
20136
20143
  }
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));
20144
+ await rm2(join15(fork.appRoot, OUTPUT_DIR), {
20145
+ recursive: true,
20146
+ force: true
20147
+ });
20148
+ const userAdapter = userAdapterOf(context);
20149
+ if (userAdapter && userAdapter.source !== "build-output") {
20150
+ let replaced = true;
20151
+ if (userAdapter.source === "next-config") {
20152
+ try {
20153
+ replaced = await wrapNextConfig(fork.appRoot, userAdapter.file) !== null;
20154
+ } catch (error51) {
20155
+ return failed("prepare", describeError(error51));
20156
+ }
20157
+ }
20158
+ if (replaced) await warn(observer, replaceWarning(userAdapter));
20148
20159
  }
20149
20160
  const build = await runStep(
20150
20161
  context,
20151
20162
  {
20152
- command: context.toolchain.openNextAws ?? "open-next",
20153
- args: ["build", "--config-path", configPath],
20163
+ command: context.packageManager.name,
20164
+ args: ["run", context.buildScript],
20154
20165
  cwd: fork.appRoot,
20155
20166
  env: {
20156
20167
  ...context.env,
20157
- NPM_CONFIG_USERCONFIG: context.packageManagerConfigPath
20168
+ NEXT_ADAPTER_PATH: adapterPath,
20169
+ NPM_CONFIG_USERCONFIG: context.packageManagerConfigPath,
20170
+ CI: "1"
20158
20171
  },
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),
20172
+ timeoutMs: 15 * 6e4,
20178
20173
  stage: "BUILDING",
20179
20174
  errorCode: "BUILD_FAILED"
20180
20175
  },
20181
20176
  observer
20182
20177
  );
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) {
20178
+ if (!build.ok) {
20179
+ if (!await pathExists(join15(fork.appRoot, BUILD_RECORD)))
20380
20180
  return {
20381
- status: "success",
20382
- artifact: { kind: "cloudflare-worker", artifact: first.artifact },
20383
- notes: []
20181
+ status: "failed",
20182
+ stage: "build",
20183
+ reason: describeFailure(build),
20184
+ appBuild: true
20384
20185
  };
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.`
20186
+ return failed("bundle", describeFailure(build));
20187
+ }
20188
+ const record3 = await readRecord(fork.appRoot);
20189
+ if (!record3 || await readBuildId(record3.distDir) !== record3.buildId) {
20190
+ const ran = await adapterInBuildOutput(
20191
+ fork.appRoot,
20192
+ context.nextVersion
20193
+ );
20194
+ return failed(
20195
+ "build",
20196
+ ran?.source === "build-output" && resolve5(fork.appRoot, ran.path) !== resolve5(adapterPath) ? NARO_ADAPTER_REASONS.userAdapterRan(ran.key) : NARO_ADAPTER_REASONS.missingOutput
20391
20197
  );
20392
20198
  }
20393
- let fork;
20199
+ let artifact;
20394
20200
  try {
20395
- fork = await context.workspace.fork("opennext-proxy-to-middleware");
20201
+ artifact = await options.readArtifact(fork.appRoot, context.identity);
20396
20202
  } 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
- );
20203
+ return failed("bundle", describeError(error51));
20405
20204
  }
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(
20205
+ const smoke = await smokeWorker(
20411
20206
  context,
20412
- fork,
20207
+ {
20208
+ appRoot: fork.appRoot,
20209
+ wranglerConfigPath: join15(fork.appRoot, WRANGLER_CONFIG),
20210
+ outputRoot: OUTPUT_DIR
20211
+ },
20413
20212
  observer,
20414
- rememberedRewrite,
20415
- adapterNotice
20213
+ naroAdapterSmokeProbes(
20214
+ record3,
20215
+ artifact.assets.map((asset) => asset.name)
20216
+ )
20416
20217
  );
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
- }
20218
+ if (!smoke.ok) return failed("smoke", smoke.reason);
20458
20219
  return {
20459
20220
  status: "success",
20460
- artifact: { kind: "cloudflare-worker", artifact: second.artifact },
20461
- variant: PROXY_TO_MIDDLEWARE_VARIANT,
20221
+ artifact: { kind: "cloudflare-worker", artifact },
20462
20222
  notes: [
20463
- `${conversion.file ?? "proxy"} was rewritten to Edge middleware for the build.`
20223
+ "Built with the Naro adapter (Next.js Adapter API) in a temporary workspace."
20464
20224
  ]
20465
20225
  };
20466
20226
  }
20467
20227
  };
20468
20228
  }
20469
20229
 
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
20230
  // ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
20498
20231
  var VINEXT_MIN_NEXT_MAJOR = 16;
20499
20232
  var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
@@ -20568,6 +20301,7 @@ function createVinextCloudflareStrategy(options) {
20568
20301
  observer,
20569
20302
  "P1 vinext-cloudflare: the project already builds with vinext; using its own configuration."
20570
20303
  );
20304
+ await info(observer, ISR_SHARED_CACHE_OFF_NOTE);
20571
20305
  }
20572
20306
  let fork;
20573
20307
  try {
@@ -20665,13 +20399,13 @@ function createVinextCloudflareStrategy(options) {
20665
20399
 
20666
20400
  // ../../packages/build/src/strategy/workspace.ts
20667
20401
  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";
20402
+ import { cp, mkdir as mkdir2, readdir as readdir4, rm as rm3 } from "fs/promises";
20403
+ import { basename, dirname as dirname2, join as join16, relative as relative5, resolve as resolve6 } from "path";
20670
20404
  import { promisify } from "util";
20671
20405
  var execFileAsync = promisify(execFile);
20672
20406
  var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
20673
20407
  function shouldCopy(path) {
20674
- const name = basename2(path);
20408
+ const name = basename(path);
20675
20409
  if (SKIPPED_ANYWHERE.has(name)) return false;
20676
20410
  return !name.startsWith(".next-deploy-");
20677
20411
  }
@@ -20699,13 +20433,13 @@ async function copyInstalledPackages(source, target2) {
20699
20433
  await copyTree(install, join16(target2, relative5(origin, install)));
20700
20434
  }
20701
20435
  async function copyTree(from, to) {
20702
- await mkdir2(dirname3(to), { recursive: true });
20436
+ await mkdir2(dirname2(to), { recursive: true });
20703
20437
  if (process.platform !== "win32") {
20704
20438
  try {
20705
20439
  await execFileAsync("cp", ["-a", from, to]);
20706
20440
  return;
20707
20441
  } catch {
20708
- await rm2(to, { recursive: true, force: true });
20442
+ await rm3(to, { recursive: true, force: true });
20709
20443
  }
20710
20444
  }
20711
20445
  await cp(from, to, { recursive: true, verbatimSymlinks: true });
@@ -20733,27 +20467,26 @@ async function withRouteList(artifact, list, appRoot, observer) {
20733
20467
  return artifact;
20734
20468
  }
20735
20469
  }
20470
+ var NEXT_ADAPTER_PATH = nextAdapterPath();
20736
20471
  var defaultLocalBuildDependencies = {
20737
20472
  createWorkspace: () => mkdtemp(join17(tmpdir(), "next-deploy-build-")),
20738
- removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
20473
+ removeWorkspace: (path) => rm4(path, { recursive: true, force: true }),
20739
20474
  runCommand,
20740
20475
  detectPackageManager,
20741
20476
  resolveAppRoot,
20742
20477
  detectFramework,
20743
20478
  readStaticArtifact,
20744
20479
  createPackageManagerConfig,
20745
- createPlatformBuildConfig,
20746
- readOpenNextArtifact,
20747
- openNextCommand: "opennextjs-cloudflare",
20748
20480
  wranglerCommand: "wrangler",
20749
20481
  cacheFetch: (input, init) => fetch(input, init),
20750
- convertProxyToMiddleware,
20751
20482
  commandAvailable: hasCommand,
20752
20483
  readInstalledNextVersion,
20753
20484
  now: () => /* @__PURE__ */ new Date(),
20754
20485
  createVinextBuildConfig,
20755
20486
  readVinextArtifact,
20756
20487
  vinextCommand: "vinext",
20488
+ ...NEXT_ADAPTER_PATH ? { nextAdapterPath: NEXT_ADAPTER_PATH } : {},
20489
+ readNaroAdapterArtifact,
20757
20490
  forkWorkspace: copyCheckout,
20758
20491
  copyInstalledPackages,
20759
20492
  collectProjectFacts,
@@ -20768,7 +20501,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
20768
20501
  const stop = resolve7(checkout);
20769
20502
  for (; ; ) {
20770
20503
  try {
20771
- const source = await readFile13(
20504
+ const source = await readFile14(
20772
20505
  join17(dir, "node_modules", "next", "package.json"),
20773
20506
  "utf8"
20774
20507
  );
@@ -20777,7 +20510,7 @@ async function readInstalledNextVersion(appRoot, checkout) {
20777
20510
  } catch {
20778
20511
  }
20779
20512
  if (dir === stop) return null;
20780
- const parent = dirname4(dir);
20513
+ const parent = dirname3(dir);
20781
20514
  if (parent === dir) return null;
20782
20515
  dir = parent;
20783
20516
  }
@@ -20800,9 +20533,9 @@ async function hasCommand(command) {
20800
20533
  return false;
20801
20534
  }
20802
20535
  }
20803
- async function pathExists3(path) {
20536
+ async function pathExists2(path) {
20804
20537
  try {
20805
- await access8(path);
20538
+ await access6(path);
20806
20539
  return true;
20807
20540
  } catch {
20808
20541
  return false;
@@ -20846,14 +20579,11 @@ function defaultStrategies(dependencies) {
20846
20579
  createVinextBuildConfig: dependencies.createVinextBuildConfig ?? createVinextBuildConfig,
20847
20580
  readVinextArtifact: dependencies.readVinextArtifact ?? readVinextArtifact
20848
20581
  }),
20849
- createOpenNextCloudflareStrategy({
20850
- createPlatformBuildConfig: dependencies.createPlatformBuildConfig,
20851
- convertProxyToMiddleware: dependencies.convertProxyToMiddleware ?? convertProxyToMiddleware,
20852
- readOpenNextArtifact: dependencies.readOpenNextArtifact
20582
+ createNaroAdapterCloudflareStrategy({
20583
+ readArtifact: dependencies.readNaroAdapterArtifact ?? readNaroAdapterArtifact
20853
20584
  }),
20854
20585
  createSplitCloudflareStrategy(),
20855
- createAwsMiddlewareCloudflareAppStrategy(),
20856
- createOpenNextAwsStrategy()
20586
+ createAwsMiddlewareCloudflareAppStrategy()
20857
20587
  ];
20858
20588
  }
20859
20589
  var LocalBuildExecutor = class {
@@ -21132,7 +20862,7 @@ var LocalBuildExecutor = class {
21132
20862
  "NEXT_VERSION_UNSUPPORTED",
21133
20863
  nextSupport.reason
21134
20864
  );
21135
- if (nextSupport.status === "needs-unsupported-flag" || nextSupport.status === "outside-peer-range") {
20865
+ if (nextSupport.status === "past-support-window" || nextSupport.status === "outside-verified-range") {
21136
20866
  await observer.onLog({
21137
20867
  level: "WARN",
21138
20868
  stage: "BUILDING",
@@ -21187,7 +20917,6 @@ var LocalBuildExecutor = class {
21187
20917
  };
21188
20918
  };
21189
20919
  const vinextCommand = this.dependencies.vinextCommand;
21190
- const openNextAwsCommand = this.dependencies.openNextAwsCommand;
21191
20920
  const context = {
21192
20921
  identity,
21193
20922
  branch: input.branch,
@@ -21199,15 +20928,14 @@ var LocalBuildExecutor = class {
21199
20928
  buildScript: resolved.buildScript,
21200
20929
  nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown",
21201
20930
  nextMajor,
21202
- nextSupport,
21203
20931
  vinextEnabled: input.vinextEnabled !== false,
20932
+ userAdapter: input.userAdapter === "keep" ? "keep" : "replace",
21204
20933
  facts,
21205
20934
  workspace: { checkout, appRoot, fork: forkWorkspace },
21206
20935
  toolchain: {
21207
- openNextCloudflare: this.dependencies.openNextCommand,
21208
20936
  wrangler: this.dependencies.wranglerCommand,
21209
20937
  ...vinextCommand && await commandAvailable(vinextCommand) ? { vinext: vinextCommand } : {},
21210
- ...openNextAwsCommand && await commandAvailable(openNextAwsCommand) ? { openNextAws: openNextAwsCommand } : {}
20938
+ ...this.dependencies.nextAdapterPath && await pathExists2(this.dependencies.nextAdapterPath) ? { nextAdapter: this.dependencies.nextAdapterPath } : {}
21211
20939
  },
21212
20940
  targets: this.dependencies.targets ?? { cloudflare: true },
21213
20941
  ports: {
@@ -21262,7 +20990,7 @@ var LocalBuildExecutor = class {
21262
20990
  if (depsKey && !depsRestored) {
21263
20991
  await trySave("deps", depsKey, pmCacheDir, cachePorts, observer);
21264
20992
  }
21265
- if (buildCacheDir && await pathExists3(buildCacheDir)) {
20993
+ if (buildCacheDir && await pathExists2(buildCacheDir)) {
21266
20994
  await trySave(
21267
20995
  "next",
21268
20996
  buildCacheKey,
@@ -21662,8 +21390,8 @@ function sendPage(res, status, html) {
21662
21390
  async function startCallbackServer() {
21663
21391
  let resolveCode;
21664
21392
  let rejectCode;
21665
- const codePromise = new Promise((resolve15, reject) => {
21666
- resolveCode = resolve15;
21393
+ const codePromise = new Promise((resolve16, reject) => {
21394
+ resolveCode = resolve16;
21667
21395
  rejectCode = reject;
21668
21396
  });
21669
21397
  const server = createServer2((req, res) => {
@@ -21692,7 +21420,7 @@ async function startCallbackServer() {
21692
21420
  sendPage(res, 200, successPage());
21693
21421
  resolveCode(code);
21694
21422
  });
21695
- await new Promise((resolve15) => server.listen(0, "127.0.0.1", resolve15));
21423
+ await new Promise((resolve16) => server.listen(0, "127.0.0.1", resolve16));
21696
21424
  const port = server.address().port;
21697
21425
  return {
21698
21426
  port,
@@ -22190,7 +21918,8 @@ function projectInfo(row) {
22190
21918
  repositoryUrl: row.repository_url,
22191
21919
  repositoryFullName: row.repository_full_name ?? null,
22192
21920
  createdAt: row.created_at,
22193
- updatedAt: row.updated_at
21921
+ updatedAt: row.updated_at,
21922
+ userAdapter: row.user_adapter ?? null
22194
21923
  };
22195
21924
  }
22196
21925
  function deploymentInfo(row) {
@@ -22361,6 +22090,19 @@ var TeamsApi = class {
22361
22090
  { context: "Read team usage" }
22362
22091
  );
22363
22092
  }
22093
+ /**
22094
+ * The team's hosting compute (CPU) per project and environment for a UTC
22095
+ * month (spec 2026-11-06-hosting-cpu-usage §7). Observed, never billed.
22096
+ */
22097
+ async usageHosting(teamSlug, query = {}) {
22098
+ const params = new URLSearchParams();
22099
+ if (query.month !== void 0) params.set("month", query.month);
22100
+ const text3 = params.toString();
22101
+ return this.client.request(
22102
+ `/api/teams/${encodeURIComponent(teamSlug)}/usage/hosting${text3 ? `?${text3}` : ""}`,
22103
+ { context: "Read team hosting usage" }
22104
+ );
22105
+ }
22364
22106
  path(teamSlug, suffix = "") {
22365
22107
  return `/api/teams/${encodeURIComponent(teamSlug)}${suffix}`;
22366
22108
  }
@@ -22736,7 +22478,7 @@ var DeploymentsApi = class {
22736
22478
  async wait(deploymentId, options = {}) {
22737
22479
  const intervalMs = options.intervalMs ?? 2500;
22738
22480
  const timeoutMs = options.timeoutMs ?? 15 * 6e4;
22739
- const sleep2 = options.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
22481
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve16) => setTimeout(resolve16, ms)));
22740
22482
  const startedAt = Date.now();
22741
22483
  let lastStatus = null;
22742
22484
  let lastSequence = -1;
@@ -23337,7 +23079,7 @@ var DbApi = class {
23337
23079
  */
23338
23080
  async export(projectId, options = {}, wait = {}) {
23339
23081
  const timeoutMs = wait.timeoutMs ?? EXPORT_TIMEOUT_MS;
23340
- const sleep2 = wait.sleep ?? ((ms) => new Promise((resolve15) => setTimeout(resolve15, ms)));
23082
+ const sleep2 = wait.sleep ?? ((ms) => new Promise((resolve16) => setTimeout(resolve16, ms)));
23341
23083
  const given = wait.pollIntervalMs;
23342
23084
  const firstPollMs = given !== void 0 && Number.isFinite(given) && given > 0 ? given : EXPORT_FIRST_POLL_MS;
23343
23085
  const maxPollMs = Math.max(EXPORT_MAX_POLL_MS, firstPollMs);
@@ -26800,8 +26542,8 @@ function readAuth(state, start, allowBareJwt) {
26800
26542
  stream.expectOperator("(");
26801
26543
  stream.expectOperator(")");
26802
26544
  if (fn !== "jwt") return { kind: "auth", fn };
26803
- const access9 = readJwtAccess(stream);
26804
- if (access9) return access9;
26545
+ const access7 = readJwtAccess(stream);
26546
+ if (access7) return access7;
26805
26547
  if (allowBareJwt) return "jwt";
26806
26548
  throw new PolicySyntaxError(
26807
26549
  "auth.jwt() is read one claim at a time: auth.jwt() ->> 'email'",
@@ -26869,13 +26611,13 @@ function readPrimary(state) {
26869
26611
  const inner2 = readAuth(state, auth, true);
26870
26612
  stream.expectOperator(")");
26871
26613
  if (inner2 !== "jwt") return inner2;
26872
- const access9 = readJwtAccess(stream);
26873
- if (!access9)
26614
+ const access7 = readJwtAccess(stream);
26615
+ if (!access7)
26874
26616
  throw new PolicySyntaxError(
26875
26617
  "auth.jwt() is read one claim at a time: (select auth.jwt()) ->> 'email'",
26876
26618
  auth.position
26877
26619
  );
26878
- return access9;
26620
+ return access7;
26879
26621
  }
26880
26622
  const inner = readOr(state);
26881
26623
  stream.expectOperator(")");
@@ -34641,10 +34383,10 @@ var SUPPORTED_REMOTE = [
34641
34383
  /^ssh:\/\/[^\s]+$/,
34642
34384
  /^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
34643
34385
  ];
34644
- var defaultExec = (args, cwd) => new Promise((resolve15, reject) => {
34386
+ var defaultExec = (args, cwd) => new Promise((resolve16, reject) => {
34645
34387
  execFile2("git", args, { cwd }, (error51, stdout) => {
34646
34388
  if (error51) reject(error51);
34647
- else resolve15(stdout);
34389
+ else resolve16(stdout);
34648
34390
  });
34649
34391
  });
34650
34392
  async function tryExec(exec, args, cwd) {
@@ -34793,15 +34535,52 @@ async function uploadArtifact(artifact, opts) {
34793
34535
  };
34794
34536
  }
34795
34537
 
34538
+ // src/user-adapter.ts
34539
+ import { resolve as resolve10 } from "path";
34540
+ async function detectUserAdapter(directory, rootDirectory) {
34541
+ return findAdapterConfig(
34542
+ rootDirectory ? resolve10(directory, rootDirectory) : directory
34543
+ );
34544
+ }
34545
+ function parseUserAdapter(value) {
34546
+ return value === "replace" || value === "keep" ? value : null;
34547
+ }
34548
+ var USER_ADAPTER_OPTIONS = [
34549
+ {
34550
+ value: "replace",
34551
+ label: "Replace it with the Naro adapter on Naro's builders (recommended) \u2014 your repository stays as it is"
34552
+ },
34553
+ { value: "keep", label: "Keep my adapter \u2014 Naro builds with vinext only" }
34554
+ ];
34555
+ function userAdapterQuestion(adapter) {
34556
+ return `${adapter.file} sets its own Next.js adapter (${adapter.key}). How should Naro build this project?`;
34557
+ }
34558
+ function userAdapterOutcome(choice) {
34559
+ 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.";
34560
+ }
34561
+ function userAdapterStored(adapter, choice) {
34562
+ 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.`;
34563
+ }
34564
+ function userAdapterUnasked(adapter) {
34565
+ 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.`;
34566
+ }
34567
+ var CHOOSE_USER_ADAPTER = {
34568
+ cli: "naro project set --user-adapter replace|keep",
34569
+ mcp: "update_project with userAdapter replace|keep"
34570
+ };
34571
+ function userAdapterUnreadable(adapter, surface = "cli") {
34572
+ 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]}.`;
34573
+ }
34574
+
34796
34575
  // src/version.ts
34797
34576
  import { readFileSync as readFileSync6 } from "fs";
34798
- import { dirname as dirname5, join as join24 } from "path";
34799
- import { fileURLToPath } from "url";
34577
+ import { dirname as dirname4, join as join24 } from "path";
34578
+ import { fileURLToPath as fileURLToPath2 } from "url";
34800
34579
  function version2() {
34801
34580
  try {
34802
34581
  const pkg = JSON.parse(
34803
34582
  readFileSync6(
34804
- join24(dirname5(fileURLToPath(import.meta.url)), "..", "package.json"),
34583
+ join24(dirname4(fileURLToPath2(import.meta.url)), "..", "package.json"),
34805
34584
  "utf8"
34806
34585
  )
34807
34586
  );
@@ -34817,22 +34596,22 @@ import {
34817
34596
  cp as cp2,
34818
34597
  lstat,
34819
34598
  mkdir as mkdir3,
34820
- readFile as readFile14,
34599
+ readFile as readFile15,
34821
34600
  readlink,
34822
34601
  realpath as realpath3,
34823
- rm as rm4,
34602
+ rm as rm5,
34824
34603
  stat as stat3,
34825
34604
  symlink,
34826
34605
  unlink,
34827
- writeFile as writeFile7
34606
+ writeFile as writeFile6
34828
34607
  } from "fs/promises";
34829
34608
  import {
34830
- basename as basename3,
34831
- dirname as dirname6,
34609
+ basename as basename2,
34610
+ dirname as dirname5,
34832
34611
  isAbsolute as isAbsolute4,
34833
34612
  join as join25,
34834
34613
  relative as relative6,
34835
- resolve as resolve10,
34614
+ resolve as resolve11,
34836
34615
  sep as sep5
34837
34616
  } from "path";
34838
34617
  var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
@@ -34862,7 +34641,7 @@ async function committedEnvFiles(sourceDir) {
34862
34641
  if (tab < 0) return [];
34863
34642
  const [mode] = entry.slice(0, tab).split(" ");
34864
34643
  const path = entry.slice(tab + 1);
34865
- if (!ENV_FILE.test(basename3(path)) || COPY_SKIP.test(path)) return [];
34644
+ if (!ENV_FILE.test(basename2(path)) || COPY_SKIP.test(path)) return [];
34866
34645
  if (mode === "100644" || mode === "100755")
34867
34646
  return [{ path, symlink: false }];
34868
34647
  if (mode === "120000") return [{ path, symlink: true }];
@@ -34894,14 +34673,14 @@ async function keptOnly(items, keep, remove) {
34894
34673
  async function readLocal(path) {
34895
34674
  const found = await stat3(path).catch(() => null);
34896
34675
  if (!found?.isFile()) return null;
34897
- return readFile14(path).catch(() => null);
34676
+ return readFile15(path).catch(() => null);
34898
34677
  }
34899
34678
  async function realAncestor(dir) {
34900
- for (let current = dir; ; current = dirname6(current)) {
34679
+ for (let current = dir; ; current = dirname5(current)) {
34901
34680
  try {
34902
34681
  return await realpath3(current);
34903
34682
  } catch {
34904
- if (dirname6(current) === current)
34683
+ if (dirname5(current) === current)
34905
34684
  throw new Error(`No ancestor of ${dir}`);
34906
34685
  }
34907
34686
  }
@@ -34920,7 +34699,7 @@ async function copyWorkingTree(source, checkout) {
34920
34699
  recursive: true,
34921
34700
  // Relative symlinks must stay relative: resolved, they would point
34922
34701
  // back into the developer's real tree, and a strategy's temporary
34923
- // rewrite (proxy -> middleware, vinext toolchain) would land there.
34702
+ // rewrite (the vinext toolchain install) would land there.
34924
34703
  verbatimSymlinks: true,
34925
34704
  filter: async (src) => {
34926
34705
  if (COPY_SKIP.test(src)) return false;
@@ -34928,7 +34707,7 @@ async function copyWorkingTree(source, checkout) {
34928
34707
  const entry = await lstat(src);
34929
34708
  if (entry.isFIFO() || entry.isSocket() || entry.isCharacterDevice() || entry.isBlockDevice())
34930
34709
  return false;
34931
- if (ENV_FILE.test(basename3(src)) && !entry.isDirectory()) {
34710
+ if (ENV_FILE.test(basename2(src)) && !entry.isDirectory()) {
34932
34711
  inWorkingTree.push(relative6(sourceDir, src).split(sep5).join("/"));
34933
34712
  return false;
34934
34713
  }
@@ -34939,7 +34718,7 @@ async function copyWorkingTree(source, checkout) {
34939
34718
  absoluteLinks.push(path);
34940
34719
  return false;
34941
34720
  }
34942
- if (!inside(resolve10(dirname6(src), target2), sourceDir)) {
34721
+ if (!inside(resolve11(dirname5(src), target2), sourceDir)) {
34943
34722
  linksOutside.push(path);
34944
34723
  return false;
34945
34724
  }
@@ -34977,21 +34756,21 @@ async function copyWorkingTree(source, checkout) {
34977
34756
  continue;
34978
34757
  }
34979
34758
  const target2 = join25(checkout, path);
34980
- if (!inside(await realAncestor(dirname6(target2)), root)) {
34759
+ if (!inside(await realAncestor(dirname5(target2)), root)) {
34981
34760
  outsideProject.push(path);
34982
34761
  continue;
34983
34762
  }
34984
34763
  try {
34985
- await mkdir3(dirname6(target2), { recursive: true });
34764
+ await mkdir3(dirname5(target2), { recursive: true });
34986
34765
  } catch {
34987
34766
  outsideProject.push(path);
34988
34767
  continue;
34989
34768
  }
34990
- await rm4(target2, { recursive: true, force: true });
34769
+ await rm5(target2, { recursive: true, force: true });
34991
34770
  const content = await git(sourceDir, ["show", `HEAD:./${path}`]);
34992
34771
  if (isLink) {
34993
34772
  const linkTarget = content.toString("utf8");
34994
- const pointsAt = resolve10(dirname6(target2), linkTarget);
34773
+ const pointsAt = resolve11(dirname5(target2), linkTarget);
34995
34774
  if (isAbsolute4(linkTarget) || !inside(pointsAt, checkout)) {
34996
34775
  outsideProject.push(path);
34997
34776
  continue;
@@ -35000,7 +34779,7 @@ async function copyWorkingTree(source, checkout) {
35000
34779
  envLinks.push(path);
35001
34780
  continue;
35002
34781
  }
35003
- await writeFile7(target2, content, { flag: "wx" });
34782
+ await writeFile6(target2, content, { flag: "wx" });
35004
34783
  const local = await readLocal(join25(sourceDir, path));
35005
34784
  if (!local || !sameText(local, content)) committedVersion.push(path);
35006
34785
  }
@@ -35095,7 +34874,7 @@ var TtyPrompter = class {
35095
34874
  });
35096
34875
  try {
35097
34876
  return await new Promise(
35098
- (resolve15) => rl.question(question, resolve15)
34877
+ (resolve16) => rl.question(question, resolve16)
35099
34878
  );
35100
34879
  } finally {
35101
34880
  rl.close();
@@ -35116,7 +34895,7 @@ var TtyPrompter = class {
35116
34895
  });
35117
34896
  try {
35118
34897
  const answer = new Promise(
35119
- (resolve15) => rl.question(question, resolve15)
34898
+ (resolve16) => rl.question(question, resolve16)
35120
34899
  );
35121
34900
  muted = true;
35122
34901
  const value = await answer;
@@ -35452,7 +35231,7 @@ async function runDbLint(context) {
35452
35231
  }
35453
35232
 
35454
35233
  // src/functions/deploy.ts
35455
- import { relative as relative11, resolve as resolve13, sep as sep10 } from "path";
35234
+ import { relative as relative11, resolve as resolve14, sep as sep10 } from "path";
35456
35235
 
35457
35236
  // ../../packages/functions-bundler/src/errors.ts
35458
35237
  var FunctionBundleError = class extends Error {
@@ -35509,7 +35288,7 @@ var BUNDLE_MESSAGES = {
35509
35288
 
35510
35289
  // ../../packages/functions-bundler/src/discover.ts
35511
35290
  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";
35291
+ import { join as join27, relative as relative8, resolve as resolve12, sep as sep6 } from "path";
35513
35292
 
35514
35293
  // ../../packages/functions-bundler/src/limits.ts
35515
35294
  var FUNCTION_BUNDLE_MAX_BYTES = 67108864;
@@ -35531,9 +35310,9 @@ function isEntrypoint(value) {
35531
35310
  );
35532
35311
  }
35533
35312
  function functionsFolder(options) {
35534
- const root = resolve11(options.cwd, options.root ?? ".");
35313
+ const root = resolve12(options.cwd, options.root ?? ".");
35535
35314
  const explicit = options.dir !== void 0;
35536
- const dir = explicit ? resolve11(options.cwd, options.dir) : join27(root, "naro", "functions");
35315
+ const dir = explicit ? resolve12(options.cwd, options.dir) : join27(root, "naro", "functions");
35537
35316
  const label2 = relative8(options.cwd, dir).split(sep6).join("/") || ".";
35538
35317
  return { dir, label: label2, root, explicit };
35539
35318
  }
@@ -35652,7 +35431,7 @@ function closesNext(text3, from) {
35652
35431
  while (i < text3.length && /\s/.test(text3[i] ?? "")) i += 1;
35653
35432
  return text3[i] === "}" || text3[i] === "]";
35654
35433
  }
35655
- function parseJsonc2(text3) {
35434
+ function parseJsonc(text3) {
35656
35435
  const uncommented = stripComments2(text3);
35657
35436
  let out = "";
35658
35437
  let inString = false;
@@ -35713,11 +35492,11 @@ function stripComments2(text3) {
35713
35492
  }
35714
35493
 
35715
35494
  // ../../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));
35495
+ import { createRequire } from "module";
35496
+ import { dirname as dirname6, resolve as resolvePath } from "path";
35497
+ import { fileURLToPath as fileURLToPath3 } from "url";
35498
+ var require2 = createRequire(import.meta.url);
35499
+ var MODULE_DIR = dirname6(fileURLToPath3(import.meta.url));
35721
35500
  var REQUIRED_NODE_BUILT_IN_NAMESPACE = "node-built-in-modules";
35722
35501
  var REQUIRED_UNENV_ALIAS_NAMESPACE = "required-unenv-alias";
35723
35502
  var SKIPPED_ALIASES = /* @__PURE__ */ new Set(["debug"]);
@@ -35847,12 +35626,12 @@ ${injects.map(
35847
35626
 
35848
35627
  // ../../packages/functions-bundler/src/deno-imports.ts
35849
35628
  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";
35629
+ import { dirname as dirname8, isAbsolute as isAbsolute5, join as join29, relative as relative9, sep as sep8 } from "path";
35851
35630
 
35852
35631
  // ../../packages/functions-bundler/src/import-map.ts
35853
35632
  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";
35633
+ import { basename as basename3, dirname as dirname7, join as join28, resolve as resolve13, sep as sep7 } from "path";
35634
+ import { fileURLToPath as fileURLToPath4 } from "url";
35856
35635
 
35857
35636
  // ../../packages/functions-bundler/src/printable.ts
35858
35637
  var UNPRINTABLE2 = /[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu;
@@ -35885,7 +35664,7 @@ function expandBareKeys(imports) {
35885
35664
  function load(file2, label2) {
35886
35665
  let parsed;
35887
35666
  try {
35888
- parsed = parseJsonc2(readFileSync9(file2, "utf8"));
35667
+ parsed = parseJsonc(readFileSync9(file2, "utf8"));
35889
35668
  } catch {
35890
35669
  throw new FunctionBundleError(
35891
35670
  "VALIDATION_FAILED",
@@ -35908,8 +35687,8 @@ function load(file2, label2) {
35908
35687
  printable2(BUNDLE_MESSAGES.importMapPrefix(label2, key, value))
35909
35688
  );
35910
35689
  return {
35911
- imports: DENO_CONFIGS.has(basename4(file2)) ? expandBareKeys(written) : written,
35912
- base: dirname8(file2),
35690
+ imports: DENO_CONFIGS.has(basename3(file2)) ? expandBareKeys(written) : written,
35691
+ base: dirname7(file2),
35913
35692
  label: label2
35914
35693
  };
35915
35694
  }
@@ -35942,7 +35721,7 @@ var isPathLike = (text3) => text3.startsWith("./") || text3.startsWith("../") ||
35942
35721
  function filePath(text3) {
35943
35722
  if (!text3.startsWith("file:")) return null;
35944
35723
  try {
35945
- return fileURLToPath3(text3);
35724
+ return fileURLToPath4(text3);
35946
35725
  } catch {
35947
35726
  return null;
35948
35727
  }
@@ -35960,7 +35739,7 @@ function keysOf(map2) {
35960
35739
  for (const [key, target2] of Object.entries(map2.imports)) {
35961
35740
  const trailing = key.endsWith("/");
35962
35741
  const file2 = filePath(key);
35963
- const name = file2 !== null ? realKey(file2, trailing) : isPathLike(key) ? realKey(resolve12(base2, key), trailing) : key;
35742
+ const name = file2 !== null ? realKey(file2, trailing) : isPathLike(key) ? realKey(resolve13(base2, key), trailing) : key;
35964
35743
  if (!keys.has(name)) keys.set(name, target2);
35965
35744
  }
35966
35745
  normalized.set(map2, keys);
@@ -35972,10 +35751,10 @@ function mapSpecifier(map2, specifier, importerDir) {
35972
35751
  const file2 = filePath(specifier);
35973
35752
  if (file2 !== null) lookup = realKey(file2, trailing);
35974
35753
  else if (specifier.startsWith("/"))
35975
- lookup = realKey(resolve12(specifier), trailing);
35754
+ lookup = realKey(resolve13(specifier), trailing);
35976
35755
  else if (isPathLike(specifier)) {
35977
35756
  if (importerDir === void 0) return null;
35978
- lookup = realKey(resolve12(importerDir, specifier), trailing);
35757
+ lookup = realKey(resolve13(importerDir, specifier), trailing);
35979
35758
  }
35980
35759
  const keys = keysOf(map2);
35981
35760
  let target2 = keys.get(lookup);
@@ -35988,7 +35767,7 @@ function mapSpecifier(map2, specifier, importerDir) {
35988
35767
  target2 = `${keys.get(prefix) ?? ""}${lookup.slice(prefix.length)}`;
35989
35768
  }
35990
35769
  if (target2.startsWith("./") || target2.startsWith("../"))
35991
- return resolve12(map2.base, target2);
35770
+ return resolve13(map2.base, target2);
35992
35771
  return filePath(target2) ?? target2;
35993
35772
  }
35994
35773
 
@@ -36068,7 +35847,7 @@ function packageDir(from, projectRoot) {
36068
35847
  const inNodeModules = fromRoot(projectRoot, current).split("/").includes("node_modules");
36069
35848
  if (!inNodeModules && existsSync9(join29(current, "package.json")))
36070
35849
  return fromRoot(projectRoot, current) || ".";
36071
- const up = dirname9(current);
35850
+ const up = dirname8(current);
36072
35851
  if (up === current) break;
36073
35852
  current = up;
36074
35853
  }
@@ -36079,14 +35858,14 @@ function isInstalled(from, pkg) {
36079
35858
  let current = from;
36080
35859
  for (; ; ) {
36081
35860
  if (existsSync9(join29(current, "node_modules", pkg))) return true;
36082
- const up = dirname9(current);
35861
+ const up = dirname8(current);
36083
35862
  if (up === current) return false;
36084
35863
  current = up;
36085
35864
  }
36086
35865
  }
36087
35866
  var NODE_MODULES = /[\\/]node_modules[\\/]/;
36088
35867
  function installedVersion(resolved, pkg) {
36089
- let current = dirname9(resolved);
35868
+ let current = dirname8(resolved);
36090
35869
  for (; ; ) {
36091
35870
  const file2 = join29(current, "package.json");
36092
35871
  if (existsSync9(file2)) {
@@ -36097,7 +35876,7 @@ function installedVersion(resolved, pkg) {
36097
35876
  } catch {
36098
35877
  }
36099
35878
  }
36100
- const up = dirname9(current);
35879
+ const up = dirname8(current);
36101
35880
  if (up === current) return null;
36102
35881
  current = up;
36103
35882
  }
@@ -36527,7 +36306,7 @@ var FUNCTION_BUSY_WAIT_MS = 5e3;
36527
36306
  var FUNCTION_RATE_LIMIT_DEFAULT_SECONDS = 60;
36528
36307
  var FUNCTION_RATE_LIMIT_MAX_SECONDS = 300;
36529
36308
  function defaultSleep(ms) {
36530
- return new Promise((resolve15) => setTimeout(resolve15, ms));
36309
+ return new Promise((resolve16) => setTimeout(resolve16, ms));
36531
36310
  }
36532
36311
  function retryAfterSeconds(error51) {
36533
36312
  const value = error51.details?.retryAfter;
@@ -36724,7 +36503,7 @@ async function deployOne(options, folder, name, importMap, secretNames, io, budg
36724
36503
  }
36725
36504
  function readFlagImportMap(options, folder) {
36726
36505
  if (options.importMap === void 0) return void 0;
36727
- const file2 = resolve13(options.cwd, options.importMap);
36506
+ const file2 = resolve14(options.cwd, options.importMap);
36728
36507
  try {
36729
36508
  return readImportMapFile(
36730
36509
  file2,
@@ -36842,7 +36621,7 @@ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUr
36842
36621
  } catch (error51) {
36843
36622
  lastError = error51;
36844
36623
  await new Promise(
36845
- (resolve15) => setTimeout(resolve15, 500 * (attempt + 1))
36624
+ (resolve16) => setTimeout(resolve16, 500 * (attempt + 1))
36846
36625
  );
36847
36626
  }
36848
36627
  }
@@ -36918,19 +36697,19 @@ function slugify2(value) {
36918
36697
  async function projectName(sourceDir) {
36919
36698
  try {
36920
36699
  const pkg = JSON.parse(
36921
- await readFile15(join31(sourceDir, "package.json"), "utf8")
36700
+ await readFile16(join31(sourceDir, "package.json"), "utf8")
36922
36701
  );
36923
36702
  if (pkg.name) return pkg.name;
36924
36703
  } catch {
36925
36704
  }
36926
- return basename5(sourceDir);
36705
+ return basename4(sourceDir);
36927
36706
  }
36928
36707
  function resolveBin(name) {
36929
- let dir = dirname10(fileURLToPath4(import.meta.url));
36708
+ let dir = dirname9(fileURLToPath5(import.meta.url));
36930
36709
  for (; ; ) {
36931
36710
  const candidate = join31(dir, "node_modules", ".bin", name);
36932
36711
  if (existsSync10(candidate)) return candidate;
36933
- const parent = dirname10(dir);
36712
+ const parent = dirname9(dir);
36934
36713
  if (parent === dir) return name;
36935
36714
  dir = parent;
36936
36715
  }
@@ -36996,7 +36775,7 @@ function workingTreeNotices(copy) {
36996
36775
  function localDependencies(sourceDir, say = () => void 0) {
36997
36776
  return {
36998
36777
  createWorkspace: () => mkdtemp2(join31(tmpdir2(), "naro-build-")),
36999
- removeWorkspace: (path) => rm5(path, { recursive: true, force: true }),
36778
+ removeWorkspace: (path) => rm6(path, { recursive: true, force: true }),
37000
36779
  async runCommand(spec, observer) {
37001
36780
  if (spec.command === "git" && spec.args[0] === "clone") {
37002
36781
  const checkout = spec.args.at(-1);
@@ -37021,9 +36800,6 @@ function localDependencies(sourceDir, say = () => void 0) {
37021
36800
  detectFramework,
37022
36801
  readStaticArtifact,
37023
36802
  createPackageManagerConfig,
37024
- createPlatformBuildConfig,
37025
- readOpenNextArtifact,
37026
- openNextCommand: resolveBin("opennextjs-cloudflare"),
37027
36803
  wranglerCommand: resolveBin("wrangler"),
37028
36804
  // `vinext check` runs on the bare checkout, before the toolchain is added
37029
36805
  // to the user's tree, so this binary has to come from the CLI's own deps.
@@ -37045,7 +36821,7 @@ async function deployDirectory(sourceDir, options, log2) {
37045
36821
  "Pass production: true for production; preview is the default."
37046
36822
  );
37047
36823
  const surface = production ? "production" : environment ?? "preview";
37048
- const appRoot = options.rootDirectory ? resolve14(sourceDir, options.rootDirectory) : sourceDir;
36824
+ const appRoot = options.rootDirectory ? resolve15(sourceDir, options.rootDirectory) : sourceDir;
37049
36825
  const framework = detectFramework2(appRoot);
37050
36826
  if (!framework.deployable && !options.force) {
37051
36827
  throw new NaroError(
@@ -37133,6 +36909,19 @@ async function deployDirectory(sourceDir, options, log2) {
37133
36909
  });
37134
36910
  if (ctx.envSkipped?.length)
37135
36911
  log2(`\u26A0 skipped env: ${ctx.envSkipped.join(", ")}`);
36912
+ const storedAdapter = ctx.userAdapter ?? null;
36913
+ if (options.userAdapter && options.userAdapter !== storedAdapter) {
36914
+ try {
36915
+ await client.projects.update(ctx.projectId, {
36916
+ userAdapter: options.userAdapter
36917
+ });
36918
+ } catch (error51) {
36919
+ log2(
36920
+ `\u26A0 Could not save the Next.js adapter choice: ${error51 instanceof Error ? error51.message : String(error51)}`
36921
+ );
36922
+ }
36923
+ }
36924
+ if (options.userAdapter) log2(userAdapterOutcome(options.userAdapter));
37136
36925
  const executor = options.executor ?? new LocalBuildExecutor(localDependencies(sourceDir, log2));
37137
36926
  const observer = eventsObserver(
37138
36927
  cpUrl,
@@ -37161,7 +36950,8 @@ async function deployDirectory(sourceDir, options, log2) {
37161
36950
  // one, as before.
37162
36951
  env: ctx.environment ? { ...envToSend, ...ctx.env } : { ...ctx.env, ...envToSend },
37163
36952
  strategy: ctx.strategy ?? null,
37164
- ...ctx.vinextEnabled === void 0 ? {} : { vinextEnabled: ctx.vinextEnabled }
36953
+ ...ctx.vinextEnabled === void 0 ? {} : { vinextEnabled: ctx.vinextEnabled },
36954
+ ...options.userAdapter ?? storedAdapter ? { userAdapter: options.userAdapter ?? storedAdapter } : {}
37165
36955
  },
37166
36956
  observer,
37167
36957
  createCacheTransport({
@@ -37343,6 +37133,13 @@ export {
37343
37133
  promptForMissingEnv,
37344
37134
  pullEnv,
37345
37135
  detectGitInfo,
37136
+ detectUserAdapter,
37137
+ parseUserAdapter,
37138
+ USER_ADAPTER_OPTIONS,
37139
+ userAdapterQuestion,
37140
+ userAdapterStored,
37141
+ userAdapterUnasked,
37142
+ userAdapterUnreadable,
37346
37143
  version2 as version,
37347
37144
  DATA_ENV_MESSAGES,
37348
37145
  dataEnvironment,
@@ -37372,7 +37169,7 @@ export {
37372
37169
  runDbLint,
37373
37170
  BUNDLE_MESSAGES,
37374
37171
  functionsFolder,
37375
- parseJsonc2 as parseJsonc,
37172
+ parseJsonc,
37376
37173
  defaultSleep,
37377
37174
  retryAfterSeconds,
37378
37175
  withFunctionRetries,