naro-cli 0.6.0 → 0.7.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.
@@ -6,10 +6,10 @@ var __export = (target, all) => {
6
6
  };
7
7
 
8
8
  // src/deploy.ts
9
- import { existsSync as existsSync6 } from "fs";
10
- import { cp, mkdtemp as mkdtemp2, readFile as readFile7, rm as rm2 } from "fs/promises";
9
+ import { existsSync as existsSync7 } from "fs";
10
+ import { cp as cp2, mkdtemp as mkdtemp2, readFile as readFile13, rm as rm3 } from "fs/promises";
11
11
  import { tmpdir as tmpdir2 } from "os";
12
- import { basename, dirname, join as join15, resolve as resolve4 } from "path";
12
+ import { basename as basename3, dirname as dirname4, join as join23, resolve as resolve9 } from "path";
13
13
  import { fileURLToPath } from "url";
14
14
 
15
15
  // ../../packages/build/src/artifact-upload.ts
@@ -92,19 +92,43 @@ async function computeDepsCacheKey(checkoutDir, packageManager) {
92
92
  async function log(observer, scope, level, message) {
93
93
  await observer.onLog({ level, stage: STAGE[scope], message });
94
94
  }
95
+ async function reportUnavailable(scope, ports, observer) {
96
+ if (ports.report?.unavailable === true) return;
97
+ if (ports.report) ports.report.unavailable = true;
98
+ await log(
99
+ observer,
100
+ scope,
101
+ "WARN",
102
+ "Build cache is unavailable - this build runs uncached and saves nothing for the next one."
103
+ );
104
+ }
95
105
  async function restoreCache(args) {
96
106
  const { scope, key, targetDir, ports, observer } = args;
97
107
  const urls = await ports.transport.resolveUrls(scope, key);
98
- if (!urls) return false;
108
+ if (!urls) {
109
+ await reportUnavailable(scope, ports, observer);
110
+ return false;
111
+ }
99
112
  const response = await ports.fetch(urls.getUrl);
100
- if (response.status !== 200) return false;
113
+ if (response.status !== 200) {
114
+ await log(
115
+ observer,
116
+ scope,
117
+ "INFO",
118
+ `No ${scope} cache stored for ${key} yet.`
119
+ );
120
+ return false;
121
+ }
101
122
  const archive = ports.tmpFile(".tar.zst");
102
123
  await writeFile(archive, Buffer.from(await response.arrayBuffer()));
103
124
  await mkdir(targetDir, { recursive: true });
104
125
  await ports.runCommand(
105
126
  {
106
127
  command: "tar",
107
- args: ["--use-compress-program=zstd", "-xf", archive, "-C", targetDir],
128
+ // --zstd, not --use-compress-program: BSD tar (macOS, where the CLI
129
+ // builds) feeds the external program wrong on extract and dies with
130
+ // "Broken pipe". Both GNU tar 1.31+ and bsdtar 3.x accept --zstd.
131
+ args: ["--zstd", "-xf", archive, "-C", targetDir],
108
132
  cwd: targetDir,
109
133
  timeoutMs: 12e4,
110
134
  stage: STAGE[scope],
@@ -118,23 +142,27 @@ async function restoreCache(args) {
118
142
  async function saveCache(args) {
119
143
  const { scope, key, sourceDir, ports, observer } = args;
120
144
  try {
121
- const info2 = await stat(sourceDir);
122
- if (!info2.isDirectory()) return;
145
+ const info3 = await stat(sourceDir);
146
+ if (!info3.isDirectory()) throw new Error("not a directory");
123
147
  } catch {
148
+ await log(
149
+ observer,
150
+ scope,
151
+ "WARN",
152
+ `Nothing to save for the ${scope} cache - the build wrote nothing to that directory.`
153
+ );
154
+ return;
155
+ }
156
+ const urls = await ports.transport.resolveUrls(scope, key);
157
+ if (!urls) {
158
+ await reportUnavailable(scope, ports, observer);
124
159
  return;
125
160
  }
126
161
  const archive = ports.tmpFile(".tar.zst");
127
162
  await ports.runCommand(
128
163
  {
129
164
  command: "tar",
130
- args: [
131
- "--use-compress-program=zstd",
132
- "-cf",
133
- archive,
134
- "-C",
135
- sourceDir,
136
- "."
137
- ],
165
+ args: ["--zstd", "-cf", archive, "-C", sourceDir, "."],
138
166
  cwd: sourceDir,
139
167
  timeoutMs: 12e4,
140
168
  stage: STAGE[scope],
@@ -142,8 +170,6 @@ async function saveCache(args) {
142
170
  },
143
171
  observer
144
172
  );
145
- const urls = await ports.transport.resolveUrls(scope, key);
146
- if (!urls) return;
147
173
  const body = await readFile(archive);
148
174
  const response = await ports.fetch(urls.putUrl, { method: "PUT", body });
149
175
  if (!response.ok) {
@@ -158,16 +184,51 @@ async function saveCache(args) {
158
184
  await log(observer, scope, "INFO", `Saved ${scope} cache (${key}).`);
159
185
  }
160
186
 
187
+ // ../../packages/build/src/cache-transport.ts
188
+ function createCacheTransport(options) {
189
+ return {
190
+ async resolveUrls(scope, key) {
191
+ try {
192
+ const url2 = new URL(
193
+ `/api/internal/deployments/${options.deploymentId}/cache/url`,
194
+ options.controlPlaneUrl
195
+ );
196
+ const response = await options.fetch(url2, {
197
+ method: "POST",
198
+ headers: {
199
+ "Content-Type": "application/json",
200
+ Authorization: `Bearer ${options.callbackToken}`
201
+ },
202
+ body: JSON.stringify({ scope, key })
203
+ });
204
+ if (!response.ok) return null;
205
+ const data = await response.json();
206
+ if (data.disabled === true) return null;
207
+ if (typeof data.getUrl !== "string" || typeof data.putUrl !== "string") {
208
+ return null;
209
+ }
210
+ return { getUrl: data.getUrl, putUrl: data.putUrl };
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+ };
216
+ }
217
+
161
218
  // ../../packages/build/src/hash.ts
162
219
  import { createHash as createHash2 } from "crypto";
163
- function hashAsset(data) {
164
- return createHash2("sha256").update(data).digest("hex").slice(0, 32);
220
+ var FALLBACK_CONTENT_TYPE = "application/octet-stream";
221
+ function assetContentType(value) {
222
+ return value || FALLBACK_CONTENT_TYPE;
223
+ }
224
+ function hashAsset(data, contentType2) {
225
+ return createHash2("sha256").update(assetContentType(contentType2)).update("\0").update(data).digest("hex").slice(0, 32);
165
226
  }
166
227
 
167
228
  // ../../packages/build/src/local-build-executor.ts
168
- import { mkdtemp, rm } from "fs/promises";
229
+ import { access as access8, mkdtemp, readFile as readFile12, rm as rm2 } from "fs/promises";
169
230
  import { tmpdir } from "os";
170
- import { join as join8 } from "path";
231
+ import { dirname as dirname3, join as join16, resolve as resolve7 } from "path";
171
232
 
172
233
  // ../../packages/core/src/artifact-manifest.ts
173
234
  var MAX_FILE_BYTES = 25 * 1024 * 1024;
@@ -183,6 +244,9 @@ var PHASES = [
183
244
  ];
184
245
  var ACTIVE_STATUSES = new Set(PHASES);
185
246
 
247
+ // ../../packages/core/src/deployment-strategy.ts
248
+ var MIN_SUPPORTED_NEXT_MAJOR = 15;
249
+
186
250
  // ../../packages/core/src/errors.ts
187
251
  var DeploymentError = class extends Error {
188
252
  code;
@@ -217,6 +281,9 @@ function pathnameOf(url2) {
217
281
  }
218
282
  }
219
283
 
284
+ // ../../packages/core/src/internal-auth.ts
285
+ import { Buffer as Buffer2 } from "buffer";
286
+
220
287
  // ../../packages/core/src/paths.ts
221
288
  function projectPath(teamSlug, projectSlug) {
222
289
  return `/${teamSlug}/projects/${projectSlug}`;
@@ -2407,9 +2474,9 @@ var $ZodCheckEndsWith = /* @__PURE__ */ $constructor("$ZodCheckEndsWith", (inst,
2407
2474
  });
2408
2475
  };
2409
2476
  });
2410
- function handleCheckPropertyResult(result, payload, property) {
2477
+ function handleCheckPropertyResult(result, payload, property2) {
2411
2478
  if (result.issues.length) {
2412
- payload.issues.push(...prefixIssues(property, result.issues));
2479
+ payload.issues.push(...prefixIssues(property2, result.issues));
2413
2480
  }
2414
2481
  }
2415
2482
  var $ZodCheckProperty = /* @__PURE__ */ $constructor("$ZodCheckProperty", (inst, def) => {
@@ -11236,10 +11303,10 @@ function _endsWith(suffix, params) {
11236
11303
  });
11237
11304
  }
11238
11305
  // @__NO_SIDE_EFFECTS__
11239
- function _property(property, schema, params) {
11306
+ function _property(property2, schema, params) {
11240
11307
  return new $ZodCheckProperty({
11241
11308
  check: "property",
11242
- property,
11309
+ property: property2,
11243
11310
  schema,
11244
11311
  ...normalizeParams(params)
11245
11312
  });
@@ -11336,28 +11403,28 @@ function _tuple(Class2, items, _paramsOrRest, _params) {
11336
11403
  });
11337
11404
  }
11338
11405
  // @__NO_SIDE_EFFECTS__
11339
- function _record(Class2, keyType, valueType, params) {
11406
+ function _record(Class2, keyType, valueType2, params) {
11340
11407
  return new Class2({
11341
11408
  type: "record",
11342
11409
  keyType,
11343
- valueType,
11410
+ valueType: valueType2,
11344
11411
  ...normalizeParams(params)
11345
11412
  });
11346
11413
  }
11347
11414
  // @__NO_SIDE_EFFECTS__
11348
- function _map(Class2, keyType, valueType, params) {
11415
+ function _map(Class2, keyType, valueType2, params) {
11349
11416
  return new Class2({
11350
11417
  type: "map",
11351
11418
  keyType,
11352
- valueType,
11419
+ valueType: valueType2,
11353
11420
  ...normalizeParams(params)
11354
11421
  });
11355
11422
  }
11356
11423
  // @__NO_SIDE_EFFECTS__
11357
- function _set(Class2, valueType, params) {
11424
+ function _set(Class2, valueType2, params) {
11358
11425
  return new Class2({
11359
11426
  type: "set",
11360
- valueType,
11427
+ valueType: valueType2,
11361
11428
  ...normalizeParams(params)
11362
11429
  });
11363
11430
  }
@@ -13723,37 +13790,37 @@ var ZodRecord = /* @__PURE__ */ $constructor("ZodRecord", (inst, def) => {
13723
13790
  inst.keyType = def.keyType;
13724
13791
  inst.valueType = def.valueType;
13725
13792
  });
13726
- function record(keyType, valueType, params) {
13727
- if (!valueType || !valueType._zod) {
13793
+ function record(keyType, valueType2, params) {
13794
+ if (!valueType2 || !valueType2._zod) {
13728
13795
  return new ZodRecord({
13729
13796
  type: "record",
13730
13797
  keyType: string2(),
13731
13798
  valueType: keyType,
13732
- ...util_exports.normalizeParams(valueType)
13799
+ ...util_exports.normalizeParams(valueType2)
13733
13800
  });
13734
13801
  }
13735
13802
  return new ZodRecord({
13736
13803
  type: "record",
13737
13804
  keyType,
13738
- valueType,
13805
+ valueType: valueType2,
13739
13806
  ...util_exports.normalizeParams(params)
13740
13807
  });
13741
13808
  }
13742
- function partialRecord(keyType, valueType, params) {
13809
+ function partialRecord(keyType, valueType2, params) {
13743
13810
  const k = clone(keyType);
13744
13811
  k._zod.values = void 0;
13745
13812
  return new ZodRecord({
13746
13813
  type: "record",
13747
13814
  keyType: k,
13748
- valueType,
13815
+ valueType: valueType2,
13749
13816
  ...util_exports.normalizeParams(params)
13750
13817
  });
13751
13818
  }
13752
- function looseRecord(keyType, valueType, params) {
13819
+ function looseRecord(keyType, valueType2, params) {
13753
13820
  return new ZodRecord({
13754
13821
  type: "record",
13755
13822
  keyType,
13756
- valueType,
13823
+ valueType: valueType2,
13757
13824
  mode: "loose",
13758
13825
  ...util_exports.normalizeParams(params)
13759
13826
  });
@@ -13769,11 +13836,11 @@ var ZodMap = /* @__PURE__ */ $constructor("ZodMap", (inst, def) => {
13769
13836
  inst.max = (...args) => inst.check(_maxSize(...args));
13770
13837
  inst.size = (...args) => inst.check(_size(...args));
13771
13838
  });
13772
- function map(keyType, valueType, params) {
13839
+ function map(keyType, valueType2, params) {
13773
13840
  return new ZodMap({
13774
13841
  type: "map",
13775
13842
  keyType,
13776
- valueType,
13843
+ valueType: valueType2,
13777
13844
  ...util_exports.normalizeParams(params)
13778
13845
  });
13779
13846
  }
@@ -13786,10 +13853,10 @@ var ZodSet = /* @__PURE__ */ $constructor("ZodSet", (inst, def) => {
13786
13853
  inst.max = (...args) => inst.check(_maxSize(...args));
13787
13854
  inst.size = (...args) => inst.check(_size(...args));
13788
13855
  });
13789
- function set(valueType, params) {
13856
+ function set(valueType2, params) {
13790
13857
  return new ZodSet({
13791
13858
  type: "set",
13792
- valueType,
13859
+ valueType: valueType2,
13793
13860
  ...util_exports.normalizeParams(params)
13794
13861
  });
13795
13862
  }
@@ -14748,6 +14815,7 @@ var RESERVED_SUBDOMAINS = [
14748
14815
  "docs",
14749
14816
  "help",
14750
14817
  "imap",
14818
+ "ingest",
14751
14819
  "mail",
14752
14820
  "mx",
14753
14821
  "naro",
@@ -14827,6 +14895,87 @@ var connectRepositorySchema = external_exports.object({
14827
14895
  })
14828
14896
  });
14829
14897
 
14898
+ // ../../packages/core/src/sql-classifier.ts
14899
+ var NUL = String.fromCharCode(0);
14900
+
14901
+ // ../../packages/build/src/next-version.ts
14902
+ var NEXT_RELEASE_DATES = {
14903
+ 16: "2025-10-21",
14904
+ 15: "2024-10-21",
14905
+ 14: "2023-10-26",
14906
+ 13: "2022-10-26",
14907
+ 12: "2021-10-26"
14908
+ };
14909
+ var SUPPORT_WINDOW_YEARS = 2;
14910
+ var PEER_LOWER = { major: 15, minor: 5, patch: 21 };
14911
+ var PEER_16_FLOOR = { major: 16, minor: 2, patch: 11 };
14912
+ function parseNextVersion(raw) {
14913
+ const match = /^\s*(\d+)\.(\d+)\.(\d+)/.exec(raw);
14914
+ if (!match) return null;
14915
+ return {
14916
+ major: Number(match[1]),
14917
+ minor: Number(match[2]),
14918
+ patch: Number(match[3])
14919
+ };
14920
+ }
14921
+ function declaredNextMajor(range) {
14922
+ const trimmed = range.trim();
14923
+ if (trimmed === "") return null;
14924
+ const match = /^[\^~]?(\d+)(?:\.\d+)*$/.exec(trimmed);
14925
+ return match ? Number(match[1]) : null;
14926
+ }
14927
+ function supportEndsAt(major) {
14928
+ const released = NEXT_RELEASE_DATES[major];
14929
+ if (!released) return null;
14930
+ const end = new Date(released);
14931
+ end.setUTCFullYear(end.getUTCFullYear() + SUPPORT_WINDOW_YEARS);
14932
+ return end;
14933
+ }
14934
+ function compare(a, b) {
14935
+ return a.major - b.major || a.minor - b.minor || a.patch - b.patch;
14936
+ }
14937
+ function withinPeerRange(version2) {
14938
+ if (version2.major === 15) return compare(version2, PEER_LOWER) >= 0;
14939
+ if (version2.major === 16) return compare(version2, PEER_16_FLOOR) >= 0;
14940
+ return version2.major > 16;
14941
+ }
14942
+ function formatDate(date5) {
14943
+ return date5.toISOString().slice(0, 10);
14944
+ }
14945
+ function classifyNextVersion(raw, now) {
14946
+ const version2 = parseNextVersion(raw);
14947
+ if (!version2) {
14948
+ return {
14949
+ status: "unknown",
14950
+ version: null,
14951
+ reason: `Could not read a Next.js version from "${raw}".`
14952
+ };
14953
+ }
14954
+ const windowEnd = supportEndsAt(version2.major);
14955
+ if (!windowEnd || version2.major < PEER_LOWER.major) {
14956
+ return {
14957
+ status: "too-old",
14958
+ version: version2,
14959
+ reason: `Next.js ${raw} is too old to deploy. @opennextjs/cloudflare supports Next.js 15.5.21 and newer. Upgrade to Next.js 16.`
14960
+ };
14961
+ }
14962
+ if (now > windowEnd) {
14963
+ return {
14964
+ status: "needs-unsupported-flag",
14965
+ version: version2,
14966
+ reason: `Next.js ${version2.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`
14967
+ };
14968
+ }
14969
+ if (!withinPeerRange(version2)) {
14970
+ return {
14971
+ status: "outside-peer-range",
14972
+ version: version2,
14973
+ reason: `Next.js ${raw} is outside the range @opennextjs/cloudflare declares support for (>=15.5.21 <16 || >=16.2.11). The build may fail in ways naro cannot explain.`
14974
+ };
14975
+ }
14976
+ return { status: "supported", version: version2, reason: "" };
14977
+ }
14978
+
14830
14979
  // ../../packages/build/src/package-manager.ts
14831
14980
  import { access, readFile as readFile2 } from "fs/promises";
14832
14981
  import { join as join2 } from "path";
@@ -14853,20 +15002,35 @@ async function detectPackageManager(root) {
14853
15002
  if (await exists(join2(root, "pnpm-lock.yaml"))) {
14854
15003
  return {
14855
15004
  name: "pnpm",
14856
- install: ["pnpm", "install", "--frozen-lockfile"]
15005
+ install: ["pnpm", "install", "--frozen-lockfile"],
15006
+ cacheArgs: (dir) => ["--store-dir", dir],
15007
+ // pnpm only warns about skipped esbuild/workerd build scripts here (it is
15008
+ // `pnpm install` that exits 1 over them), and a vinext build succeeds
15009
+ // without those postinstalls.
15010
+ addArgs: (specs) => ["pnpm", "add", ...specs]
14857
15011
  };
14858
15012
  }
14859
15013
  if (await exists(join2(root, "package-lock.json"))) {
14860
- return { name: "npm", install: ["npm", "ci"] };
15014
+ return {
15015
+ name: "npm",
15016
+ install: ["npm", "ci"],
15017
+ cacheArgs: (dir) => ["--cache", dir],
15018
+ // --no-save keeps package.json and the lockfile untouched.
15019
+ addArgs: (specs) => ["npm", "install", "--no-save", ...specs]
15020
+ };
14861
15021
  }
14862
15022
  if (await exists(join2(root, "yarn.lock"))) {
15023
+ const modern = await usesModernYarn(root);
14863
15024
  return {
14864
15025
  name: "yarn",
14865
15026
  install: [
14866
15027
  "yarn",
14867
15028
  "install",
14868
- await usesModernYarn(root) ? "--immutable" : "--frozen-lockfile"
14869
- ]
15029
+ modern ? "--immutable" : "--frozen-lockfile"
15030
+ ],
15031
+ // Berry takes no install-time cache flag; YARN_CACHE_FOLDER covers it.
15032
+ cacheArgs: (dir) => modern ? [] : ["--cache-folder", dir],
15033
+ addArgs: (specs) => ["yarn", "add", ...specs]
14870
15034
  };
14871
15035
  }
14872
15036
  throw new DeploymentError(
@@ -14911,270 +15075,77 @@ async function convertProxyToMiddleware(appRoot) {
14911
15075
  return { converted: false };
14912
15076
  }
14913
15077
 
14914
- // ../../packages/build/src/platform-config.ts
14915
- import { access as access3, writeFile as writeFile3 } from "fs/promises";
14916
- import { createRequire } from "module";
14917
- import { join as join4 } from "path";
14918
- var COMPATIBILITY_DATE = "2026-08-19";
14919
- var NEXT_CONFIG_FILES = [
14920
- "next.config.js",
14921
- "next.config.mjs",
14922
- "next.config.cjs",
14923
- "next.config.ts"
14924
- ];
14925
- async function pathExists(path) {
14926
- try {
14927
- await access3(path);
14928
- return true;
14929
- } catch {
14930
- return false;
14931
- }
14932
- }
14933
- async function createPackageManagerConfig(appRoot, writer = async (path, contents) => {
14934
- await writeFile3(path, contents, "utf8");
14935
- }) {
14936
- const packageManagerConfigPath = join4(appRoot, ".next-deploy-user.npmrc");
14937
- await writer(
14938
- packageManagerConfigPath,
14939
- "# Host package-manager credentials are intentionally unavailable.\n"
14940
- );
14941
- return packageManagerConfigPath;
14942
- }
14943
- async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
14944
- await writeFile3(path, contents, "utf8");
14945
- }, cloudflareModulePath = createRequire(import.meta.url).resolve(
14946
- "@opennextjs/cloudflare"
14947
- ), exists3 = pathExists) {
14948
- const openNextConfigPath = join4(appRoot, ".next-deploy-open-next.config.mjs");
14949
- const wranglerConfigPath = join4(appRoot, ".next-deploy-wrangler.json");
14950
- const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
14951
- let hasNextConfig = false;
14952
- for (const name of NEXT_CONFIG_FILES) {
14953
- if (await exists3(join4(appRoot, name))) {
14954
- hasNextConfig = true;
14955
- break;
14956
- }
14957
- }
14958
- if (!hasNextConfig) {
14959
- await writer(
14960
- join4(appRoot, "next.config.mjs"),
14961
- 'export default { output: "standalone" };\n'
14962
- );
14963
- }
14964
- await writer(
14965
- openNextConfigPath,
14966
- `import { defineCloudflareConfig } from ${JSON.stringify(cloudflareModulePath)};
14967
-
14968
- export default defineCloudflareConfig({});
14969
- `
14970
- );
14971
- await writer(
14972
- wranglerConfigPath,
14973
- `${JSON.stringify(
14974
- {
14975
- name: `next-deploy-${safeDeploymentId}`,
14976
- main: ".open-next/worker.js",
14977
- compatibility_date: COMPATIBILITY_DATE,
14978
- compatibility_flags: ["nodejs_compat"],
14979
- assets: {
14980
- directory: ".open-next/assets",
14981
- binding: "ASSETS"
14982
- }
14983
- },
14984
- null,
14985
- 2
14986
- )}
14987
- `
14988
- );
14989
- const packageManagerConfigPath = await createPackageManagerConfig(
14990
- appRoot,
14991
- writer
14992
- );
14993
- return {
14994
- openNextConfigPath,
14995
- wranglerConfigPath,
14996
- packageManagerConfigPath
14997
- };
14998
- }
15078
+ // ../../packages/build/src/vinext.ts
15079
+ import { access as access5, readFile as readFile5, writeFile as writeFile4 } from "fs/promises";
15080
+ import { join as join6, resolve as resolve2 } from "path";
14999
15081
 
15000
- // ../../packages/build/src/frameworks.ts
15001
- import { readFile as readFile4 } from "fs/promises";
15002
- import { join as join5 } from "path";
15003
- var FRAMEWORK_PRESETS = {
15004
- next: {
15005
- id: "next",
15006
- detectDependency: "next",
15007
- defaultBuildScript: "build",
15008
- defaultOutputDirectory: ".open-next",
15009
- needsServerRuntime: true,
15010
- spaFallback: false,
15011
- cachePath: ".next/cache"
15012
- },
15013
- vite: {
15014
- id: "vite",
15015
- detectDependency: "vite",
15016
- defaultBuildScript: "build",
15017
- defaultOutputDirectory: "dist",
15018
- needsServerRuntime: false,
15019
- spaFallback: true,
15020
- cachePath: "node_modules/.vite"
15021
- },
15022
- cra: {
15023
- id: "cra",
15024
- detectDependency: "react-scripts",
15025
- defaultBuildScript: "build",
15026
- defaultOutputDirectory: "build",
15027
- needsServerRuntime: false,
15028
- spaFallback: true,
15029
- cachePath: "node_modules/.cache"
15030
- },
15031
- static: {
15032
- id: "static",
15033
- detectDependency: null,
15034
- defaultBuildScript: null,
15035
- defaultOutputDirectory: ".",
15036
- needsServerRuntime: false,
15037
- spaFallback: false,
15038
- cachePath: null
15082
+ // ../../packages/build/src/open-next.ts
15083
+ import { access as access3, readdir, readFile as readFile4 } from "fs/promises";
15084
+ import {
15085
+ basename,
15086
+ dirname,
15087
+ extname,
15088
+ isAbsolute,
15089
+ join as join4,
15090
+ relative,
15091
+ resolve,
15092
+ sep
15093
+ } from "path";
15094
+ function parseJsonc(source) {
15095
+ let output = "";
15096
+ let index = 0;
15097
+ let inString = false;
15098
+ let escaped = false;
15099
+ while (index < source.length) {
15100
+ const current = source[index] ?? "";
15101
+ const next = source[index + 1] ?? "";
15102
+ if (inString) {
15103
+ output += current;
15104
+ if (escaped) escaped = false;
15105
+ else if (current === "\\") escaped = true;
15106
+ else if (current === '"') inString = false;
15107
+ index += 1;
15108
+ continue;
15109
+ }
15110
+ if (current === '"') {
15111
+ inString = true;
15112
+ output += current;
15113
+ index += 1;
15114
+ continue;
15115
+ }
15116
+ if (current === "/" && next === "/") {
15117
+ index += 2;
15118
+ while (index < source.length && source[index] !== "\n") index += 1;
15119
+ continue;
15120
+ }
15121
+ if (current === "/" && next === "*") {
15122
+ index += 2;
15123
+ while (index < source.length && !(source[index] === "*" && source[index + 1] === "/"))
15124
+ index += 1;
15125
+ index += 2;
15126
+ continue;
15127
+ }
15128
+ output += current;
15129
+ index += 1;
15039
15130
  }
15040
- };
15041
- var DETECTION_ORDER = ["next", "vite", "cra"];
15042
- var FRAMEWORK_LABELS = {
15043
- next: "Next.js",
15044
- vite: "Vite",
15045
- cra: "Create React App",
15046
- static: "static files"
15047
- };
15048
- function dependencyVersion(packageJson, name) {
15049
- const version2 = packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];
15050
- return typeof version2 === "string" && version2.trim() !== "" ? version2 : null;
15131
+ return JSON.parse(output.replace(/,\s*([}\]])/g, "$1"));
15051
15132
  }
15052
- async function readPackageJson(root) {
15053
- let source;
15054
- try {
15055
- source = await readFile4(join5(root, "package.json"), "utf8");
15056
- } catch (error51) {
15057
- if (error51.code === "ENOENT") return null;
15133
+ function confinedPath(root, configuredPath, label2, base2 = root) {
15134
+ if (isAbsolute(configuredPath)) {
15058
15135
  throw new DeploymentError(
15059
- "FRAMEWORK_DETECTION_FAILED",
15060
- "Could not read package.json.",
15061
- { cause: error51 }
15136
+ "ARTIFACT_INVALID",
15137
+ `${label2} must be relative to the application root.`
15062
15138
  );
15063
15139
  }
15064
- try {
15065
- return JSON.parse(source);
15066
- } catch (error51) {
15140
+ const resolved = resolve(base2, configuredPath);
15141
+ const relation = relative(root, resolved);
15142
+ if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
15067
15143
  throw new DeploymentError(
15068
- "FRAMEWORK_DETECTION_FAILED",
15069
- "The selected root does not contain a valid package.json.",
15070
- { cause: error51 }
15144
+ "ARTIFACT_INVALID",
15145
+ `${label2} escapes the application root.`
15071
15146
  );
15072
15147
  }
15073
- }
15074
- async function detectFramework(root, overrides = {}) {
15075
- const packageJson = await readPackageJson(root);
15076
- let preset;
15077
- if (overrides.framework) {
15078
- const candidate = FRAMEWORK_PRESETS[overrides.framework] ?? null;
15079
- if (!candidate || candidate.id !== overrides.framework)
15080
- throw new DeploymentError(
15081
- "FRAMEWORK_DETECTION_FAILED",
15082
- `Unknown framework "${overrides.framework}".`
15083
- );
15084
- preset = candidate;
15085
- } else if (!packageJson) {
15086
- preset = FRAMEWORK_PRESETS.static;
15087
- } else {
15088
- const detected = DETECTION_ORDER.map((id) => FRAMEWORK_PRESETS[id]).find(
15089
- (candidate) => candidate.detectDependency !== null && dependencyVersion(packageJson, candidate.detectDependency) !== null
15090
- );
15091
- if (!detected)
15092
- throw new DeploymentError(
15093
- "FRAMEWORK_DETECTION_FAILED",
15094
- "Could not detect the project framework from package.json. Set a framework (and build command / output directory) in the project settings."
15095
- );
15096
- preset = detected;
15097
- }
15098
- const buildScript = overrides.buildCommand ?? preset.defaultBuildScript;
15099
- if (buildScript !== null) {
15100
- if (!packageJson)
15101
- throw new DeploymentError(
15102
- "FRAMEWORK_DETECTION_FAILED",
15103
- "A build command requires a package.json."
15104
- );
15105
- if (typeof packageJson.scripts?.[buildScript] !== "string")
15106
- throw new DeploymentError(
15107
- "BUILD_FAILED",
15108
- `The project must declare a "${buildScript}" script.`
15109
- );
15110
- }
15111
- const version2 = packageJson && preset.detectDependency ? dependencyVersion(packageJson, preset.detectDependency) : null;
15112
- return {
15113
- preset,
15114
- buildScript,
15115
- outputDirectory: overrides.outputDirectory ?? preset.defaultOutputDirectory,
15116
- description: version2 ? `${FRAMEWORK_LABELS[preset.id]} ${version2}` : FRAMEWORK_LABELS[preset.id]
15117
- };
15118
- }
15119
-
15120
- // ../../packages/build/src/open-next.ts
15121
- import { access as access4, readdir, readFile as readFile5 } from "fs/promises";
15122
- import { extname, isAbsolute, join as join6, relative, resolve, sep } from "path";
15123
- function parseJsonc(source) {
15124
- let output = "";
15125
- let index = 0;
15126
- let inString = false;
15127
- let escaped = false;
15128
- while (index < source.length) {
15129
- const current = source[index] ?? "";
15130
- const next = source[index + 1] ?? "";
15131
- if (inString) {
15132
- output += current;
15133
- if (escaped) escaped = false;
15134
- else if (current === "\\") escaped = true;
15135
- else if (current === '"') inString = false;
15136
- index += 1;
15137
- continue;
15138
- }
15139
- if (current === '"') {
15140
- inString = true;
15141
- output += current;
15142
- index += 1;
15143
- continue;
15144
- }
15145
- if (current === "/" && next === "/") {
15146
- index += 2;
15147
- while (index < source.length && source[index] !== "\n") index += 1;
15148
- continue;
15149
- }
15150
- if (current === "/" && next === "*") {
15151
- index += 2;
15152
- while (index < source.length && !(source[index] === "*" && source[index + 1] === "/"))
15153
- index += 1;
15154
- index += 2;
15155
- continue;
15156
- }
15157
- output += current;
15158
- index += 1;
15159
- }
15160
- return JSON.parse(output.replace(/,\s*([}\]])/g, "$1"));
15161
- }
15162
- function confinedPath(root, configuredPath, label) {
15163
- if (isAbsolute(configuredPath)) {
15164
- throw new DeploymentError(
15165
- "ARTIFACT_INVALID",
15166
- `${label} must be relative to the application root.`
15167
- );
15168
- }
15169
- const resolved = resolve(root, configuredPath);
15170
- const relation = relative(root, resolved);
15171
- if (relation === ".." || relation.startsWith(`..${sep}`) || isAbsolute(relation)) {
15172
- throw new DeploymentError(
15173
- "ARTIFACT_INVALID",
15174
- `${label} escapes the application root.`
15175
- );
15176
- }
15177
- return resolved;
15148
+ return resolved;
15178
15149
  }
15179
15150
  function contentType(path) {
15180
15151
  switch (extname(path).toLowerCase()) {
@@ -15225,25 +15196,25 @@ function contentType(path) {
15225
15196
  return "application/octet-stream";
15226
15197
  }
15227
15198
  }
15228
- async function listFiles(root) {
15199
+ async function listArtifactFiles(root, label2 = "OpenNext") {
15229
15200
  const files = [];
15230
15201
  async function visit(directory) {
15231
15202
  const entries = await readdir(directory, { withFileTypes: true });
15232
15203
  for (const entry of entries.sort(
15233
15204
  (left, right) => left.name.localeCompare(right.name)
15234
15205
  )) {
15235
- const path = join6(directory, entry.name);
15206
+ const path = join4(directory, entry.name);
15236
15207
  if (entry.isSymbolicLink()) {
15237
15208
  throw new DeploymentError(
15238
15209
  "ARTIFACT_INVALID",
15239
- "OpenNext output cannot contain symbolic links."
15210
+ `${label2} output cannot contain symbolic links.`
15240
15211
  );
15241
15212
  }
15242
15213
  if (entry.isDirectory()) await visit(path);
15243
15214
  else if (entry.isFile()) {
15244
15215
  files.push({
15245
15216
  name: relative(root, path).split(sep).join("/"),
15246
- data: await readFile5(path),
15217
+ data: await readFile4(path),
15247
15218
  contentType: contentType(path)
15248
15219
  });
15249
15220
  }
@@ -15262,13 +15233,13 @@ async function findWranglerConfig(root, configuredPath) {
15262
15233
  "Wrangler config escapes the application root."
15263
15234
  );
15264
15235
  }
15265
- await access4(resolved);
15236
+ await access3(resolved);
15266
15237
  return resolved;
15267
15238
  }
15268
15239
  for (const name of ["wrangler.jsonc", "wrangler.json"]) {
15269
- const path = join6(root, name);
15240
+ const path = join4(root, name);
15270
15241
  try {
15271
- await access4(path);
15242
+ await access3(path);
15272
15243
  return path;
15273
15244
  } catch {
15274
15245
  }
@@ -15278,11 +15249,26 @@ async function findWranglerConfig(root, configuredPath) {
15278
15249
  "A wrangler.jsonc or wrangler.json file is required."
15279
15250
  );
15280
15251
  }
15252
+ var OPEN_NEXT_LAYOUT = {
15253
+ outputRoot: ".open-next",
15254
+ bundleDir: ".open-next/worker-bundle"
15255
+ };
15256
+ var listFiles = (root) => listArtifactFiles(root);
15281
15257
  async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
15258
+ const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
15259
+ return readWorkerArtifact(appRoot, identity, {
15260
+ wranglerConfigPath: configPath,
15261
+ ...OPEN_NEXT_LAYOUT
15262
+ });
15263
+ }
15264
+ async function readWorkerArtifact(appRoot, identity, layout) {
15282
15265
  try {
15283
- const configPath = await findWranglerConfig(appRoot, wranglerConfigPath);
15266
+ const configPath = await findWranglerConfig(
15267
+ appRoot,
15268
+ layout.wranglerConfigPath
15269
+ );
15284
15270
  const metadata = parseJsonc(
15285
- await readFile5(configPath, "utf8")
15271
+ await readFile4(configPath, "utf8")
15286
15272
  );
15287
15273
  if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string") {
15288
15274
  throw new DeploymentError(
@@ -15290,43 +15276,50 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
15290
15276
  "Wrangler metadata must declare main and compatibility_date."
15291
15277
  );
15292
15278
  }
15293
- const outputRoot = resolve(appRoot, ".open-next");
15279
+ const configDir = dirname(configPath);
15280
+ const outputRoot = confinedPath(appRoot, layout.outputRoot, "Output root");
15294
15281
  const entrypointPath = confinedPath(
15295
15282
  appRoot,
15296
15283
  metadata.main,
15297
- "Worker entrypoint"
15284
+ "Worker entrypoint",
15285
+ configDir
15298
15286
  );
15299
15287
  const entryRelation = relative(outputRoot, entrypointPath);
15300
15288
  if (entryRelation === ".." || entryRelation.startsWith(`..${sep}`) || isAbsolute(entryRelation)) {
15301
15289
  throw new DeploymentError(
15302
15290
  "ARTIFACT_INVALID",
15303
- "Worker entrypoint must be inside .open-next."
15291
+ `Worker entrypoint must be inside ${layout.outputRoot}.`
15304
15292
  );
15305
15293
  }
15306
- await access4(entrypointPath);
15307
- const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(appRoot, metadata.assets.directory, "Assets directory") : join6(outputRoot, "assets");
15294
+ await access3(entrypointPath);
15295
+ const configuredAssets = typeof metadata.assets?.directory === "string" ? confinedPath(
15296
+ appRoot,
15297
+ metadata.assets.directory,
15298
+ "Assets directory",
15299
+ configDir
15300
+ ) : join4(outputRoot, "assets");
15308
15301
  const assetsRelation = relative(outputRoot, configuredAssets);
15309
15302
  if (assetsRelation === ".." || assetsRelation.startsWith(`..${sep}`) || isAbsolute(assetsRelation)) {
15310
15303
  throw new DeploymentError(
15311
15304
  "ARTIFACT_INVALID",
15312
- "Assets directory must be inside .open-next."
15305
+ `Assets directory must be inside ${layout.outputRoot}.`
15313
15306
  );
15314
15307
  }
15315
- const bundleRoot = join6(outputRoot, "worker-bundle");
15308
+ const bundleRoot = confinedPath(appRoot, layout.bundleDir, "Bundle dir");
15316
15309
  let bundleFiles = null;
15317
15310
  try {
15318
- await access4(bundleRoot);
15311
+ await access3(bundleRoot);
15319
15312
  bundleFiles = await listFiles(bundleRoot);
15320
15313
  } catch {
15321
15314
  }
15322
15315
  const outputFiles = bundleFiles ?? await listFiles(outputRoot);
15323
- const entrypointName = bundleFiles ? "worker.js" : relative(outputRoot, entrypointPath).split(sep).join("/");
15316
+ const entrypointName = bundleFiles ? basename(entrypointPath) : relative(outputRoot, entrypointPath).split(sep).join("/");
15324
15317
  const assetsPrefix = `${relative(outputRoot, configuredAssets).split(sep).join("/")}/`;
15325
15318
  const entrypoint = outputFiles.find((file2) => file2.name === entrypointName);
15326
15319
  if (!entrypoint)
15327
15320
  throw new DeploymentError(
15328
15321
  "ARTIFACT_INVALID",
15329
- "OpenNext worker entrypoint is missing."
15322
+ "Worker entrypoint is missing from the build output."
15330
15323
  );
15331
15324
  return {
15332
15325
  ...identity,
@@ -15345,245 +15338,2035 @@ async function readOpenNextArtifact(appRoot, identity, wranglerConfigPath) {
15345
15338
  if (error51 instanceof DeploymentError) throw error51;
15346
15339
  throw new DeploymentError(
15347
15340
  "ARTIFACT_INVALID",
15348
- "Could not read the OpenNext deployment output.",
15341
+ "Could not read the Worker build output.",
15349
15342
  { cause: error51 }
15350
15343
  );
15351
15344
  }
15352
15345
  }
15353
15346
 
15354
- // ../../packages/build/src/process.ts
15355
- import { spawn } from "child_process";
15356
- import { createInterface } from "readline";
15357
-
15358
- // ../../packages/build/src/redact.ts
15359
- var SECRET_ASSIGNMENT = /\b(CLOUDFLARE_API_TOKEN|SUPABASE_SERVICE_ROLE_KEY|BUILD_CALLBACK_SECRET)=([^\s]+)/gi;
15360
- function redactLog(message) {
15361
- return message.replace(/\bBearer\s+[^\s]+/gi, "Bearer [REDACTED]").replace(/(https?:\/\/)[^/@\s]+@/gi, "$1[REDACTED]@").replace(SECRET_ASSIGNMENT, "$1=[REDACTED]");
15362
- }
15363
-
15364
- // ../../packages/build/src/process.ts
15365
- var CommandExecutionError = class extends DeploymentError {
15366
- exitCode;
15367
- timedOut;
15368
- constructor(code, message, options) {
15369
- super(
15370
- code,
15371
- message,
15372
- options.cause === void 0 ? void 0 : { cause: options.cause }
15373
- );
15374
- this.name = "CommandExecutionError";
15375
- this.exitCode = options.exitCode;
15376
- this.timedOut = options.timedOut;
15377
- }
15378
- };
15379
- var PASSTHROUGH_ENVIRONMENT = [
15380
- "PATH",
15381
- "HOME",
15382
- "TMPDIR",
15383
- "TMP",
15384
- "TEMP",
15385
- "LANG",
15386
- "LC_ALL",
15387
- "CI",
15388
- "COREPACK_HOME",
15389
- "XDG_CACHE_HOME"
15347
+ // ../../packages/build/src/platform-config.ts
15348
+ import { access as access4, writeFile as writeFile3 } from "fs/promises";
15349
+ import { createRequire } from "module";
15350
+ import { join as join5 } from "path";
15351
+ var COMPATIBILITY_DATE = "2026-08-19";
15352
+ var NEXT_CONFIG_FILES = [
15353
+ "next.config.js",
15354
+ "next.config.mjs",
15355
+ "next.config.cjs",
15356
+ "next.config.ts"
15390
15357
  ];
15391
- var MAX_CAPTURED_LINES = 2e3;
15392
- function commandEnvironment(overrides) {
15393
- const environment = {
15394
- NODE_ENV: process.env.NODE_ENV ?? "production"
15395
- };
15396
- for (const name of PASSTHROUGH_ENVIRONMENT) {
15397
- const value = process.env[name];
15398
- if (value !== void 0) environment[name] = value;
15358
+ async function pathExists(path) {
15359
+ try {
15360
+ await access4(path);
15361
+ return true;
15362
+ } catch {
15363
+ return false;
15399
15364
  }
15400
- return { ...environment, ...overrides };
15401
15365
  }
15402
- async function runCommand(spec, observer) {
15403
- const detached = process.platform !== "win32";
15404
- const child = spawn(spec.command, [...spec.args], {
15405
- cwd: spec.cwd,
15406
- shell: false,
15407
- env: commandEnvironment(spec.env),
15408
- detached,
15409
- stdio: ["ignore", "pipe", "pipe"]
15410
- });
15411
- let timedOut = false;
15412
- const captured = { stdout: [], stderr: [] };
15413
- const consume = async (input, level) => {
15414
- const lines = createInterface({ input });
15415
- for await (const message of lines) {
15416
- const output = captured[level === "INFO" ? "stdout" : "stderr"];
15417
- if (output.length < MAX_CAPTURED_LINES) output.push(message);
15418
- await observer.onLog({
15419
- level,
15420
- stage: spec.stage,
15421
- message: redactLog(message)
15422
- });
15423
- }
15424
- };
15425
- const outputComplete = Promise.all([
15426
- consume(child.stdout, "INFO"),
15427
- consume(child.stderr, "ERROR")
15428
- ]);
15429
- const kill = (signal) => {
15430
- if (detached && child.pid) {
15431
- try {
15432
- process.kill(-child.pid, signal);
15433
- return;
15434
- } catch {
15435
- }
15366
+ async function createPackageManagerConfig(appRoot, writer = async (path, contents) => {
15367
+ await writeFile3(path, contents, "utf8");
15368
+ }) {
15369
+ const packageManagerConfigPath = join5(appRoot, ".next-deploy-user.npmrc");
15370
+ await writer(
15371
+ packageManagerConfigPath,
15372
+ "# Host package-manager credentials are intentionally unavailable.\n"
15373
+ );
15374
+ return packageManagerConfigPath;
15375
+ }
15376
+ async function createPlatformBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
15377
+ await writeFile3(path, contents, "utf8");
15378
+ }, cloudflareModulePath = createRequire(import.meta.url).resolve(
15379
+ "@opennextjs/cloudflare"
15380
+ ), exists5 = pathExists) {
15381
+ const openNextConfigPath = join5(appRoot, ".next-deploy-open-next.config.mjs");
15382
+ const wranglerConfigPath = join5(appRoot, ".next-deploy-wrangler.json");
15383
+ const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
15384
+ let hasNextConfig = false;
15385
+ for (const name of NEXT_CONFIG_FILES) {
15386
+ if (await exists5(join5(appRoot, name))) {
15387
+ hasNextConfig = true;
15388
+ break;
15436
15389
  }
15437
- child.kill(signal);
15438
- };
15439
- let forceKill;
15440
- const timeout = setTimeout(() => {
15441
- timedOut = true;
15442
- kill("SIGTERM");
15443
- forceKill = setTimeout(() => kill("SIGKILL"), 2e3);
15444
- forceKill.unref();
15445
- }, spec.timeoutMs);
15446
- const outcome = await new Promise((resolveOutcome) => {
15447
- child.once("error", (error51) => resolveOutcome({ type: "error", error: error51 }));
15448
- child.once(
15449
- "close",
15450
- (exitCode, signal) => resolveOutcome({ type: "close", exitCode, signal })
15451
- );
15452
- });
15453
- clearTimeout(timeout);
15454
- if (forceKill) clearTimeout(forceKill);
15455
- await outputComplete;
15456
- if (outcome.type === "error") {
15457
- throw new CommandExecutionError(
15458
- spec.errorCode,
15459
- `Could not start ${spec.command}`,
15460
- {
15461
- exitCode: null,
15462
- timedOut,
15463
- cause: outcome.error
15464
- }
15465
- );
15466
- }
15467
- if (timedOut) {
15468
- throw new CommandExecutionError(
15469
- spec.errorCode,
15470
- `${spec.command} exceeded its ${spec.timeoutMs}ms time limit`,
15471
- { exitCode: outcome.exitCode, timedOut: true }
15472
- );
15473
15390
  }
15474
- if (outcome.exitCode !== 0) {
15475
- throw new CommandExecutionError(
15476
- spec.errorCode,
15477
- `${spec.command} exited with code ${String(outcome.exitCode)}${outcome.signal ? ` (${outcome.signal})` : ""}`,
15478
- { exitCode: outcome.exitCode, timedOut: false }
15391
+ if (!hasNextConfig) {
15392
+ await writer(
15393
+ join5(appRoot, "next.config.mjs"),
15394
+ 'export default { output: "standalone" };\n'
15479
15395
  );
15480
15396
  }
15481
- return captured;
15482
- }
15397
+ await writer(
15398
+ openNextConfigPath,
15399
+ `import { defineCloudflareConfig } from ${JSON.stringify(cloudflareModulePath)};
15483
15400
 
15484
- // ../../packages/build/src/repository.ts
15485
- import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "path";
15486
- function resolveAppRoot(checkout, configuredRoot) {
15487
- const checkoutRoot = resolve2(checkout);
15488
- if (!configuredRoot) return checkoutRoot;
15489
- if (isAbsolute2(configuredRoot)) {
15490
- throw new Error("Configured root directory escapes repository");
15401
+ export default defineCloudflareConfig({});
15402
+ `
15403
+ );
15404
+ await writer(
15405
+ wranglerConfigPath,
15406
+ `${JSON.stringify(
15407
+ {
15408
+ name: `next-deploy-${safeDeploymentId}`,
15409
+ main: ".open-next/worker.js",
15410
+ compatibility_date: COMPATIBILITY_DATE,
15411
+ compatibility_flags: ["nodejs_compat"],
15412
+ assets: {
15413
+ directory: ".open-next/assets",
15414
+ binding: "ASSETS"
15415
+ }
15416
+ },
15417
+ null,
15418
+ 2
15419
+ )}
15420
+ `
15421
+ );
15422
+ const packageManagerConfigPath = await createPackageManagerConfig(
15423
+ appRoot,
15424
+ writer
15425
+ );
15426
+ return {
15427
+ openNextConfigPath,
15428
+ wranglerConfigPath,
15429
+ packageManagerConfigPath
15430
+ };
15431
+ }
15432
+
15433
+ // ../../packages/build/src/vinext.ts
15434
+ var VINEXT_TOOLCHAIN = [
15435
+ "vinext@1.0.0-beta.9",
15436
+ "@cloudflare/vite-plugin@1.54.7",
15437
+ "@vitejs/plugin-react@6.1.1",
15438
+ "@vitejs/plugin-rsc@0.5.34",
15439
+ "react-server-dom-webpack@19.2.8",
15440
+ "vite@8.2.2"
15441
+ ];
15442
+ var VINEXT_COMPATIBILITY_DATE = COMPATIBILITY_DATE;
15443
+ var VITE_CONFIG_NAMES = [
15444
+ "vite.config.js",
15445
+ "vite.config.mjs",
15446
+ "vite.config.cjs",
15447
+ "vite.config.ts",
15448
+ "vite.config.mts",
15449
+ "vite.config.cts"
15450
+ ];
15451
+ var GENERATED_VITE_CONFIG = "vite.config.mjs";
15452
+ var OUTPUT_DIR = "dist";
15453
+ var SERVER_DIR = "server";
15454
+ var CLIENT_DIR = "client";
15455
+ var WRANGLER_OUTPUT_NAME = "wrangler.json";
15456
+ async function exists3(path) {
15457
+ try {
15458
+ await access5(path);
15459
+ return true;
15460
+ } catch {
15461
+ return false;
15491
15462
  }
15492
- const appRoot = resolve2(checkoutRoot, configuredRoot);
15493
- const relativeRoot = relative2(checkoutRoot, appRoot);
15494
- if (relativeRoot === ".." || relativeRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(relativeRoot)) {
15495
- throw new Error("Configured root directory escapes repository");
15463
+ }
15464
+ async function createVinextBuildConfig(appRoot, deploymentId, writer = async (path, contents) => {
15465
+ await writeFile4(path, contents, "utf8");
15466
+ }) {
15467
+ for (const name of VITE_CONFIG_NAMES) {
15468
+ if (await exists3(join6(appRoot, name))) {
15469
+ throw new DeploymentError(
15470
+ "ARTIFACT_INVALID",
15471
+ `The project already has a ${name}. vinext offers no way to point at a different config, and overwriting it would change how the project's own tooling builds.`
15472
+ );
15473
+ }
15496
15474
  }
15497
- return appRoot;
15475
+ const safeDeploymentId = deploymentId.replace(/[^a-z0-9-]/gi, "-");
15476
+ const viteConfigPath = join6(appRoot, GENERATED_VITE_CONFIG);
15477
+ const wranglerConfigPath = join6(appRoot, "wrangler.jsonc");
15478
+ await writer(
15479
+ viteConfigPath,
15480
+ [
15481
+ 'import { defineConfig } from "vite";',
15482
+ 'import vinext from "vinext";',
15483
+ 'import { cloudflare } from "@cloudflare/vite-plugin";',
15484
+ "",
15485
+ "export default defineConfig({",
15486
+ " plugins: [",
15487
+ " vinext(),",
15488
+ ' cloudflare({ viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] } }),',
15489
+ " ],",
15490
+ "});",
15491
+ ""
15492
+ ].join("\n")
15493
+ );
15494
+ await writer(
15495
+ wranglerConfigPath,
15496
+ `${JSON.stringify(
15497
+ {
15498
+ name: `naro-vinext-${safeDeploymentId}`,
15499
+ compatibility_date: VINEXT_COMPATIBILITY_DATE,
15500
+ compatibility_flags: ["nodejs_compat"],
15501
+ main: "vinext/server/fetch-handler",
15502
+ assets: {
15503
+ directory: `${OUTPUT_DIR}/${CLIENT_DIR}`,
15504
+ binding: "ASSETS"
15505
+ }
15506
+ },
15507
+ null,
15508
+ 2
15509
+ )}
15510
+ `
15511
+ );
15512
+ return { viteConfigPath, wranglerConfigPath };
15513
+ }
15514
+ var AUTO_FIXED_ISSUES = [/Missing "type": "module"/i];
15515
+ var ISSUE_LINE = /^\s*✗\s+(.+)$/;
15516
+ var ANSI_ESCAPE = /\u001B\[[0-9;]*m/g;
15517
+ var SUMMARY_LINE = /Overall:\s*(\d+)%\s*compatible\s*\(\s*(\d+)\s+supported,\s*(\d+)\s+partial,\s*(\d+)\s+issues?\s*\)/i;
15518
+ function parseVinextCheckReport(output) {
15519
+ const plain = output.replace(ANSI_ESCAPE, "");
15520
+ const match = SUMMARY_LINE.exec(plain);
15521
+ if (!match) return null;
15522
+ const issueLines = [
15523
+ ...new Set(
15524
+ plain.split("\n").map((line) => ISSUE_LINE.exec(line)?.[1]?.trim()).filter((line) => Boolean(line))
15525
+ )
15526
+ ];
15527
+ return {
15528
+ percent: Number(match[1]),
15529
+ supported: Number(match[2]),
15530
+ partial: Number(match[3]),
15531
+ issues: Number(match[4]),
15532
+ issueLines
15533
+ };
15498
15534
  }
15499
-
15500
- // ../../packages/build/src/static-artifact.ts
15501
- import { readdir as readdir2, readFile as readFile6 } from "fs/promises";
15502
- import { isAbsolute as isAbsolute3, join as join7, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
15503
- var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
15504
- var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
15505
- var ALLOWED_DOTFILE = ".well-known";
15506
- function excluded(name) {
15507
- if (name === ALLOWED_DOTFILE) return false;
15508
- return name.startsWith(".") || EXCLUDED_DIRECTORIES.has(name);
15535
+ function vinextRouteFromCheck(report) {
15536
+ if (!report)
15537
+ return {
15538
+ eligible: false,
15539
+ reason: "vinext check output could not be read; staying on OpenNext. Expected if vinext changed its report format."
15540
+ };
15541
+ const autoFixed = report.issueLines.filter(
15542
+ (line) => AUTO_FIXED_ISSUES.some((pattern) => pattern.test(line))
15543
+ );
15544
+ const blocking = Math.max(0, report.issues - autoFixed.length);
15545
+ if (blocking > 0)
15546
+ return {
15547
+ eligible: false,
15548
+ reason: `vinext check reported ${blocking} unsupported item(s) (${report.percent}% compatible); staying on OpenNext.`
15549
+ };
15550
+ if (autoFixed.length > 0)
15551
+ return {
15552
+ eligible: true,
15553
+ reason: `vinext check found only issues vinext build fixes itself (${autoFixed.join("; ")}); attempting a vinext build.`
15554
+ };
15555
+ return {
15556
+ eligible: true,
15557
+ 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."
15558
+ };
15509
15559
  }
15510
- async function readStaticArtifact(appRoot, outputDirectory, identity, options, onWarn) {
15511
- if (isAbsolute3(outputDirectory))
15512
- throw new DeploymentError(
15513
- "ARTIFACT_INVALID",
15514
- "Output directory must be relative to the application root."
15560
+ function globToRegExp(glob) {
15561
+ let source = "";
15562
+ for (let index = 0; index < glob.length; index += 1) {
15563
+ const char = glob[index];
15564
+ if (char !== "*") {
15565
+ source += char?.replace(/[.+?^${}()|[\]\\]/g, String.raw`\$&`) ?? "";
15566
+ continue;
15567
+ }
15568
+ if (glob[index + 1] !== "*") {
15569
+ source += "[^/]*";
15570
+ continue;
15571
+ }
15572
+ if (glob[index + 2] === "/") {
15573
+ source += "(?:[^/]*/)*";
15574
+ index += 2;
15575
+ } else {
15576
+ source += ".*";
15577
+ index += 1;
15578
+ }
15579
+ }
15580
+ return new RegExp(`^${source}$`);
15581
+ }
15582
+ function moduleMatchers(rules) {
15583
+ if (!Array.isArray(rules)) return [];
15584
+ return rules.flatMap((rule) => {
15585
+ const globs = rule.globs;
15586
+ return Array.isArray(globs) ? globs : [];
15587
+ }).filter((glob) => typeof glob === "string").map(globToRegExp);
15588
+ }
15589
+ async function readVinextArtifact(appRoot, identity) {
15590
+ try {
15591
+ const outputRoot = resolve2(appRoot, OUTPUT_DIR);
15592
+ const serverRoot = join6(outputRoot, SERVER_DIR);
15593
+ const clientRoot = join6(outputRoot, CLIENT_DIR);
15594
+ const configPath = join6(serverRoot, WRANGLER_OUTPUT_NAME);
15595
+ if (!await exists3(configPath)) {
15596
+ throw new DeploymentError(
15597
+ "ARTIFACT_INVALID",
15598
+ `vinext build produced no ${OUTPUT_DIR}/${SERVER_DIR}/${WRANGLER_OUTPUT_NAME}.`
15599
+ );
15600
+ }
15601
+ const metadata = JSON.parse(
15602
+ await readFile5(configPath, "utf8")
15515
15603
  );
15516
- const outputRoot = resolve3(appRoot, outputDirectory);
15517
- const relation = relative3(appRoot, outputRoot);
15518
- if (relation === ".." || relation.startsWith(`..${sep2}`) || isAbsolute3(relation))
15604
+ if (typeof metadata.main !== "string" || typeof metadata.compatibility_date !== "string") {
15605
+ throw new DeploymentError(
15606
+ "ARTIFACT_INVALID",
15607
+ "vinext wrangler output must declare main and compatibility_date."
15608
+ );
15609
+ }
15610
+ const entrypointName = metadata.main.split(/[\\/]/).join("/");
15611
+ const matchers = moduleMatchers(metadata.rules);
15612
+ const isModule = (name) => matchers.length === 0 || matchers.some((matcher) => matcher.test(name));
15613
+ const serverFiles = await listArtifactFiles(serverRoot, "vinext");
15614
+ const entrypoint = serverFiles.find((file2) => file2.name === entrypointName);
15615
+ if (!entrypoint) {
15616
+ throw new DeploymentError(
15617
+ "ARTIFACT_INVALID",
15618
+ `vinext worker entrypoint "${entrypointName}" is missing from the build output.`
15619
+ );
15620
+ }
15621
+ return {
15622
+ ...identity,
15623
+ entrypoint,
15624
+ modules: serverFiles.filter(
15625
+ (file2) => file2.name !== entrypointName && file2.name !== WRANGLER_OUTPUT_NAME && !file2.name.endsWith(".map") && isModule(file2.name)
15626
+ ),
15627
+ assets: await listArtifactFiles(clientRoot, "vinext"),
15628
+ compatibilityDate: metadata.compatibility_date,
15629
+ compatibilityFlags: Array.isArray(metadata.compatibility_flags) ? metadata.compatibility_flags.filter(
15630
+ (flag) => typeof flag === "string"
15631
+ ) : [],
15632
+ assetsBinding: typeof metadata.assets?.binding === "string" ? metadata.assets.binding : "ASSETS"
15633
+ };
15634
+ } catch (error51) {
15635
+ if (error51 instanceof DeploymentError) throw error51;
15519
15636
  throw new DeploymentError(
15520
15637
  "ARTIFACT_INVALID",
15521
- "Output directory escapes the application root."
15638
+ "Could not read the vinext deployment output.",
15639
+ { cause: error51 }
15522
15640
  );
15523
- const assets = [];
15524
- async function visit(directory) {
15525
- const entries = await readdir2(directory, { withFileTypes: true });
15526
- for (const entry of entries.sort(
15527
- (left, right) => left.name.localeCompare(right.name)
15528
- )) {
15529
- if (excluded(entry.name)) continue;
15530
- const path = join7(directory, entry.name);
15531
- if (entry.isSymbolicLink())
15532
- throw new DeploymentError(
15533
- "ARTIFACT_INVALID",
15534
- "Static output cannot contain symbolic links."
15535
- );
15536
- if (entry.isDirectory()) await visit(path);
15537
- else if (entry.isFile()) {
15538
- assets.push({
15539
- name: relative3(outputRoot, path).split(sep2).join("/"),
15540
- data: await readFile6(path),
15541
- contentType: contentType(path)
15542
- });
15543
- }
15641
+ }
15642
+ }
15643
+
15644
+ // ../../packages/build/src/frameworks.ts
15645
+ import { readFile as readFile6 } from "fs/promises";
15646
+ import { join as join7 } from "path";
15647
+ var FRAMEWORK_PRESETS = {
15648
+ next: {
15649
+ id: "next",
15650
+ detectDependency: "next",
15651
+ defaultBuildScript: "build",
15652
+ defaultOutputDirectory: ".open-next",
15653
+ needsServerRuntime: true,
15654
+ spaFallback: false,
15655
+ cachePath: ".next/cache"
15656
+ },
15657
+ "next-export": {
15658
+ id: "next-export",
15659
+ detectDependency: "next",
15660
+ defaultBuildScript: "build",
15661
+ defaultOutputDirectory: "out",
15662
+ needsServerRuntime: false,
15663
+ spaFallback: false,
15664
+ cachePath: ".next/cache"
15665
+ },
15666
+ vite: {
15667
+ id: "vite",
15668
+ detectDependency: "vite",
15669
+ defaultBuildScript: "build",
15670
+ defaultOutputDirectory: "dist",
15671
+ needsServerRuntime: false,
15672
+ spaFallback: true,
15673
+ cachePath: "node_modules/.vite"
15674
+ },
15675
+ cra: {
15676
+ id: "cra",
15677
+ detectDependency: "react-scripts",
15678
+ defaultBuildScript: "build",
15679
+ defaultOutputDirectory: "build",
15680
+ needsServerRuntime: false,
15681
+ spaFallback: true,
15682
+ cachePath: "node_modules/.cache"
15683
+ },
15684
+ static: {
15685
+ id: "static",
15686
+ detectDependency: null,
15687
+ defaultBuildScript: null,
15688
+ defaultOutputDirectory: ".",
15689
+ needsServerRuntime: false,
15690
+ spaFallback: false,
15691
+ cachePath: null
15692
+ }
15693
+ };
15694
+ var DETECTION_ORDER = ["next", "vite", "cra"];
15695
+ var FRAMEWORK_LABELS = {
15696
+ next: "Next.js",
15697
+ "next-export": "Next.js static export",
15698
+ vite: "Vite",
15699
+ cra: "Create React App",
15700
+ static: "static files"
15701
+ };
15702
+ function dependencyVersion(packageJson, name) {
15703
+ const version2 = packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];
15704
+ return typeof version2 === "string" && version2.trim() !== "" ? version2 : null;
15705
+ }
15706
+ var STATIC_EXPORT = /(?:^|[\s,{;])output\s*:\s*(["'`])export\1/;
15707
+ function declaresStaticExport(source) {
15708
+ const code = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/(^|[^:])\/\/.*$/gm, "$1");
15709
+ return STATIC_EXPORT.test(code);
15710
+ }
15711
+ async function usesStaticExport(root) {
15712
+ for (const name of NEXT_CONFIG_FILES) {
15713
+ try {
15714
+ return declaresStaticExport(await readFile6(join7(root, name), "utf8"));
15715
+ } catch {
15716
+ continue;
15544
15717
  }
15545
15718
  }
15719
+ return false;
15720
+ }
15721
+ async function readPackageJson(root) {
15722
+ let source;
15546
15723
  try {
15547
- await visit(outputRoot);
15724
+ source = await readFile6(join7(root, "package.json"), "utf8");
15548
15725
  } catch (error51) {
15549
- if (error51 instanceof DeploymentError) throw error51;
15726
+ if (error51.code === "ENOENT") return null;
15550
15727
  throw new DeploymentError(
15551
- "ARTIFACT_INVALID",
15552
- `Could not read the build output directory "${outputDirectory}".`,
15728
+ "FRAMEWORK_DETECTION_FAILED",
15729
+ "Could not read package.json.",
15553
15730
  { cause: error51 }
15554
15731
  );
15555
15732
  }
15556
- if (assets.length === 0)
15733
+ try {
15734
+ return JSON.parse(source);
15735
+ } catch (error51) {
15557
15736
  throw new DeploymentError(
15558
- "ARTIFACT_INVALID",
15559
- `The build output directory "${outputDirectory}" contains no files.`
15560
- );
15561
- if (!assets.some((file2) => file2.name === "index.html"))
15562
- await onWarn?.(
15563
- `No index.html in "${outputDirectory}" \u2014 the site root will not serve a page.`
15737
+ "FRAMEWORK_DETECTION_FAILED",
15738
+ "The selected root does not contain a valid package.json.",
15739
+ { cause: error51 }
15564
15740
  );
15565
- return {
15566
- ...identity,
15567
- entrypoint: {
15568
- name: "worker.js",
15569
- data: new TextEncoder().encode(SHIM_WORKER_SOURCE),
15570
- contentType: "application/javascript"
15571
- },
15572
- modules: [],
15573
- assets,
15574
- compatibilityDate: COMPATIBILITY_DATE,
15575
- compatibilityFlags: [],
15576
- assetsBinding: "ASSETS",
15741
+ }
15742
+ }
15743
+ async function detectFramework(root, overrides = {}) {
15744
+ const packageJson = await readPackageJson(root);
15745
+ let preset;
15746
+ if (overrides.framework) {
15747
+ const candidate = FRAMEWORK_PRESETS[overrides.framework] ?? null;
15748
+ if (!candidate || candidate.id !== overrides.framework)
15749
+ throw new DeploymentError(
15750
+ "FRAMEWORK_DETECTION_FAILED",
15751
+ `Unknown framework "${overrides.framework}".`
15752
+ );
15753
+ preset = candidate;
15754
+ } else if (!packageJson) {
15755
+ preset = FRAMEWORK_PRESETS.static;
15756
+ } else {
15757
+ const detected = DETECTION_ORDER.map((id) => FRAMEWORK_PRESETS[id]).find(
15758
+ (candidate) => candidate.detectDependency !== null && dependencyVersion(packageJson, candidate.detectDependency) !== null
15759
+ );
15760
+ if (!detected)
15761
+ throw new DeploymentError(
15762
+ "FRAMEWORK_DETECTION_FAILED",
15763
+ "Could not detect the project framework from package.json. Set a framework (and build command / output directory) in the project settings."
15764
+ );
15765
+ preset = detected;
15766
+ }
15767
+ if (preset.id === "next" && await usesStaticExport(root))
15768
+ preset = FRAMEWORK_PRESETS["next-export"];
15769
+ const buildScript = overrides.buildCommand ?? preset.defaultBuildScript;
15770
+ if (buildScript !== null) {
15771
+ if (!packageJson)
15772
+ throw new DeploymentError(
15773
+ "FRAMEWORK_DETECTION_FAILED",
15774
+ "A build command requires a package.json."
15775
+ );
15776
+ if (typeof packageJson.scripts?.[buildScript] !== "string")
15777
+ throw new DeploymentError(
15778
+ "BUILD_FAILED",
15779
+ `The project must declare a "${buildScript}" script.`
15780
+ );
15781
+ }
15782
+ const version2 = packageJson && preset.detectDependency ? dependencyVersion(packageJson, preset.detectDependency) : null;
15783
+ return {
15784
+ preset,
15785
+ buildScript,
15786
+ outputDirectory: overrides.outputDirectory ?? preset.defaultOutputDirectory,
15787
+ description: version2 ? `${FRAMEWORK_LABELS[preset.id]} ${version2}` : FRAMEWORK_LABELS[preset.id],
15788
+ dependencyRange: version2
15789
+ };
15790
+ }
15791
+
15792
+ // ../../packages/build/src/process.ts
15793
+ import { spawn } from "child_process";
15794
+ import { createInterface } from "readline";
15795
+
15796
+ // ../../packages/build/src/log-format.ts
15797
+ var CSI_SEQUENCE = /\u001B\[[0-9;?]*[\u0020-\u002F]*[@-~]/g;
15798
+ var OSC_SEQUENCE = /\u001B\][^\u0007\u001B]*(?:\u0007|\u001B\\)/g;
15799
+ function formatLogLine(raw) {
15800
+ const line = raw.replace(/\r+$/, "");
15801
+ const lastFrame = line.slice(line.lastIndexOf("\r") + 1);
15802
+ const plain = lastFrame.replace(OSC_SEQUENCE, "").replace(CSI_SEQUENCE, "").trimEnd();
15803
+ return plain.trim() === "" ? null : plain;
15804
+ }
15805
+
15806
+ // ../../packages/build/src/redact.ts
15807
+ var SECRET_ASSIGNMENT = /\b(CLOUDFLARE_API_TOKEN|SUPABASE_SERVICE_ROLE_KEY|BUILD_CALLBACK_SECRET)=([^\s]+)/gi;
15808
+ function redactLog(message) {
15809
+ return message.replace(/\bBearer\s+[^\s]+/gi, "Bearer [REDACTED]").replace(/(https?:\/\/)[^/@\s]+@/gi, "$1[REDACTED]@").replace(SECRET_ASSIGNMENT, "$1=[REDACTED]");
15810
+ }
15811
+
15812
+ // ../../packages/build/src/process.ts
15813
+ var CommandExecutionError = class extends DeploymentError {
15814
+ exitCode;
15815
+ timedOut;
15816
+ constructor(code, message, options) {
15817
+ super(
15818
+ code,
15819
+ message,
15820
+ options.cause === void 0 ? void 0 : { cause: options.cause }
15821
+ );
15822
+ this.name = "CommandExecutionError";
15823
+ this.exitCode = options.exitCode;
15824
+ this.timedOut = options.timedOut;
15825
+ }
15826
+ };
15827
+ var PASSTHROUGH_ENVIRONMENT = [
15828
+ "PATH",
15829
+ "HOME",
15830
+ "TMPDIR",
15831
+ "TMP",
15832
+ "TEMP",
15833
+ "LANG",
15834
+ "LC_ALL",
15835
+ "CI",
15836
+ "COREPACK_HOME",
15837
+ "XDG_CACHE_HOME"
15838
+ ];
15839
+ var MAX_CAPTURED_LINES = 2e3;
15840
+ function commandEnvironment(overrides) {
15841
+ const environment = {
15842
+ NODE_ENV: process.env.NODE_ENV ?? "production"
15843
+ };
15844
+ for (const name of PASSTHROUGH_ENVIRONMENT) {
15845
+ const value = process.env[name];
15846
+ if (value !== void 0) environment[name] = value;
15847
+ }
15848
+ return { ...environment, ...overrides };
15849
+ }
15850
+ var startProcess = (spec, observer) => {
15851
+ const detached = process.platform !== "win32";
15852
+ const child = spawn(spec.command, [...spec.args], {
15853
+ cwd: spec.cwd,
15854
+ shell: false,
15855
+ env: commandEnvironment(spec.env),
15856
+ detached,
15857
+ stdio: ["ignore", "pipe", "pipe"]
15858
+ });
15859
+ const consume = async (input, level) => {
15860
+ const lines = createInterface({ input });
15861
+ for await (const message of lines) {
15862
+ const formatted = formatLogLine(message);
15863
+ if (formatted === null) continue;
15864
+ await observer.onLog({
15865
+ level,
15866
+ stage: spec.stage,
15867
+ message: redactLog(formatted)
15868
+ });
15869
+ }
15870
+ };
15871
+ const outputComplete = Promise.all([
15872
+ consume(child.stdout, "INFO"),
15873
+ consume(child.stderr, "WARN")
15874
+ ]);
15875
+ const kill = (signal) => {
15876
+ if (detached && child.pid) {
15877
+ try {
15878
+ process.kill(-child.pid, signal);
15879
+ return;
15880
+ } catch {
15881
+ }
15882
+ }
15883
+ child.kill(signal);
15884
+ };
15885
+ let exitCode = null;
15886
+ let done = false;
15887
+ const exited = new Promise((resolveExit) => {
15888
+ child.once("error", (error51) => {
15889
+ done = true;
15890
+ void observer.onLog({
15891
+ level: "WARN",
15892
+ stage: spec.stage,
15893
+ message: `Could not start ${spec.command}: ${error51.message}`
15894
+ });
15895
+ resolveExit(null);
15896
+ });
15897
+ child.once("close", (code) => {
15898
+ done = true;
15899
+ exitCode = code;
15900
+ resolveExit(code);
15901
+ });
15902
+ });
15903
+ let stopping;
15904
+ return {
15905
+ exited,
15906
+ stop: () => {
15907
+ stopping ??= (async () => {
15908
+ if (!done) {
15909
+ kill("SIGTERM");
15910
+ const forceKill = setTimeout(() => kill("SIGKILL"), 2e3);
15911
+ forceKill.unref();
15912
+ await exited;
15913
+ clearTimeout(forceKill);
15914
+ }
15915
+ await outputComplete;
15916
+ void exitCode;
15917
+ })();
15918
+ return stopping;
15919
+ }
15920
+ };
15921
+ };
15922
+ async function runCommand(spec, observer) {
15923
+ const detached = process.platform !== "win32";
15924
+ const child = spawn(spec.command, [...spec.args], {
15925
+ cwd: spec.cwd,
15926
+ shell: false,
15927
+ env: commandEnvironment(spec.env),
15928
+ detached,
15929
+ stdio: ["ignore", "pipe", "pipe"]
15930
+ });
15931
+ let timedOut = false;
15932
+ const captured = { stdout: [], stderr: [] };
15933
+ const consume = async (input, level) => {
15934
+ const lines = createInterface({ input });
15935
+ for await (const message of lines) {
15936
+ const output = captured[level === "INFO" ? "stdout" : "stderr"];
15937
+ if (output.length < MAX_CAPTURED_LINES) output.push(message);
15938
+ const formatted = formatLogLine(message);
15939
+ if (formatted === null) continue;
15940
+ await observer.onLog({
15941
+ level,
15942
+ stage: spec.stage,
15943
+ message: redactLog(formatted)
15944
+ });
15945
+ }
15946
+ };
15947
+ const outputComplete = Promise.all([
15948
+ consume(child.stdout, "INFO"),
15949
+ consume(child.stderr, "ERROR")
15950
+ ]);
15951
+ const kill = (signal) => {
15952
+ if (detached && child.pid) {
15953
+ try {
15954
+ process.kill(-child.pid, signal);
15955
+ return;
15956
+ } catch {
15957
+ }
15958
+ }
15959
+ child.kill(signal);
15960
+ };
15961
+ let forceKill;
15962
+ const timeout = setTimeout(() => {
15963
+ timedOut = true;
15964
+ kill("SIGTERM");
15965
+ forceKill = setTimeout(() => kill("SIGKILL"), 2e3);
15966
+ forceKill.unref();
15967
+ }, spec.timeoutMs);
15968
+ const outcome = await new Promise((resolveOutcome) => {
15969
+ child.once("error", (error51) => resolveOutcome({ type: "error", error: error51 }));
15970
+ child.once(
15971
+ "close",
15972
+ (exitCode, signal) => resolveOutcome({ type: "close", exitCode, signal })
15973
+ );
15974
+ });
15975
+ clearTimeout(timeout);
15976
+ if (forceKill) clearTimeout(forceKill);
15977
+ await outputComplete;
15978
+ if (outcome.type === "error") {
15979
+ throw new CommandExecutionError(
15980
+ spec.errorCode,
15981
+ `Could not start ${spec.command}`,
15982
+ {
15983
+ exitCode: null,
15984
+ timedOut,
15985
+ cause: outcome.error
15986
+ }
15987
+ );
15988
+ }
15989
+ if (timedOut) {
15990
+ throw new CommandExecutionError(
15991
+ spec.errorCode,
15992
+ `${spec.command} exceeded its ${spec.timeoutMs}ms time limit`,
15993
+ { exitCode: outcome.exitCode, timedOut: true }
15994
+ );
15995
+ }
15996
+ if (outcome.exitCode !== 0) {
15997
+ throw new CommandExecutionError(
15998
+ spec.errorCode,
15999
+ `${spec.command} exited with code ${String(outcome.exitCode)}${outcome.signal ? ` (${outcome.signal})` : ""}`,
16000
+ { exitCode: outcome.exitCode, timedOut: false }
16001
+ );
16002
+ }
16003
+ return captured;
16004
+ }
16005
+
16006
+ // ../../packages/build/src/repository.ts
16007
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "path";
16008
+ function resolveAppRoot(checkout, configuredRoot) {
16009
+ const checkoutRoot = resolve3(checkout);
16010
+ if (!configuredRoot) return checkoutRoot;
16011
+ if (isAbsolute2(configuredRoot)) {
16012
+ throw new Error("Configured root directory escapes repository");
16013
+ }
16014
+ const appRoot = resolve3(checkoutRoot, configuredRoot);
16015
+ const relativeRoot = relative2(checkoutRoot, appRoot);
16016
+ if (relativeRoot === ".." || relativeRoot.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute2(relativeRoot)) {
16017
+ throw new Error("Configured root directory escapes repository");
16018
+ }
16019
+ return appRoot;
16020
+ }
16021
+
16022
+ // ../../packages/build/src/static-artifact.ts
16023
+ import { readdir as readdir2, readFile as readFile7 } from "fs/promises";
16024
+ import { isAbsolute as isAbsolute3, join as join8, relative as relative3, resolve as resolve4, sep as sep2 } from "path";
16025
+ var SHIM_WORKER_SOURCE = "export default { fetch: (request, env) => env.ASSETS.fetch(request) };\n";
16026
+ var EXCLUDED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules"]);
16027
+ var ALLOWED_DOTFILE = ".well-known";
16028
+ function excluded(name) {
16029
+ if (name === ALLOWED_DOTFILE) return false;
16030
+ return name.startsWith(".") || EXCLUDED_DIRECTORIES.has(name);
16031
+ }
16032
+ async function readStaticArtifact(appRoot, outputDirectory, identity, options, onWarn) {
16033
+ if (isAbsolute3(outputDirectory))
16034
+ throw new DeploymentError(
16035
+ "ARTIFACT_INVALID",
16036
+ "Output directory must be relative to the application root."
16037
+ );
16038
+ const outputRoot = resolve4(appRoot, outputDirectory);
16039
+ const relation = relative3(appRoot, outputRoot);
16040
+ if (relation === ".." || relation.startsWith(`..${sep2}`) || isAbsolute3(relation))
16041
+ throw new DeploymentError(
16042
+ "ARTIFACT_INVALID",
16043
+ "Output directory escapes the application root."
16044
+ );
16045
+ const assets = [];
16046
+ async function visit(directory) {
16047
+ const entries = await readdir2(directory, { withFileTypes: true });
16048
+ for (const entry of entries.sort(
16049
+ (left, right) => left.name.localeCompare(right.name)
16050
+ )) {
16051
+ if (excluded(entry.name)) continue;
16052
+ const path = join8(directory, entry.name);
16053
+ if (entry.isSymbolicLink())
16054
+ throw new DeploymentError(
16055
+ "ARTIFACT_INVALID",
16056
+ "Static output cannot contain symbolic links."
16057
+ );
16058
+ if (entry.isDirectory()) await visit(path);
16059
+ else if (entry.isFile()) {
16060
+ assets.push({
16061
+ name: relative3(outputRoot, path).split(sep2).join("/"),
16062
+ data: await readFile7(path),
16063
+ contentType: contentType(path)
16064
+ });
16065
+ }
16066
+ }
16067
+ }
16068
+ try {
16069
+ await visit(outputRoot);
16070
+ } catch (error51) {
16071
+ if (error51 instanceof DeploymentError) throw error51;
16072
+ throw new DeploymentError(
16073
+ "ARTIFACT_INVALID",
16074
+ `Could not read the build output directory "${outputDirectory}".`,
16075
+ { cause: error51 }
16076
+ );
16077
+ }
16078
+ if (assets.length === 0)
16079
+ throw new DeploymentError(
16080
+ "ARTIFACT_INVALID",
16081
+ `The build output directory "${outputDirectory}" contains no files.`
16082
+ );
16083
+ if (!assets.some((file2) => file2.name === "index.html"))
16084
+ await onWarn?.(
16085
+ `No index.html in "${outputDirectory}" \u2014 the site root will not serve a page.`
16086
+ );
16087
+ return {
16088
+ ...identity,
16089
+ entrypoint: {
16090
+ name: "worker.js",
16091
+ data: new TextEncoder().encode(SHIM_WORKER_SOURCE),
16092
+ contentType: "application/javascript"
16093
+ },
16094
+ modules: [],
16095
+ assets,
16096
+ compatibilityDate: COMPATIBILITY_DATE,
16097
+ compatibilityFlags: [],
16098
+ assetsBinding: "ASSETS",
15577
16099
  assetsConfig: {
15578
16100
  notFoundHandling: options.spaFallback ? "single-page-application" : "404-page"
15579
16101
  }
15580
16102
  };
15581
16103
  }
15582
16104
 
16105
+ // ../../packages/build/src/strategy/fingerprint.ts
16106
+ import { createHash as createHash3 } from "crypto";
16107
+ import { readFile as readFile8 } from "fs/promises";
16108
+ import { join as join9, relative as relative4, sep as sep3 } from "path";
16109
+ var APP_ROOT_FILES = [
16110
+ "package.json",
16111
+ "next.config.js",
16112
+ "next.config.mjs",
16113
+ "next.config.cjs",
16114
+ "next.config.ts",
16115
+ "vite.config.ts",
16116
+ "vite.config.mts",
16117
+ "vite.config.js",
16118
+ "vite.config.mjs",
16119
+ "open-next.config.ts",
16120
+ "open-next.config.js",
16121
+ "open-next.config.mjs",
16122
+ "wrangler.jsonc",
16123
+ "wrangler.json",
16124
+ "wrangler.toml",
16125
+ "vinext.config.ts",
16126
+ "vinext.config.js",
16127
+ "vinext.config.mjs",
16128
+ ".vinextrc"
16129
+ ];
16130
+ var SOURCE_EXTENSIONS = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
16131
+ var SOURCE_FILES = ["middleware", "proxy", "src/middleware", "src/proxy"];
16132
+ var CHECKOUT_FILES = [
16133
+ "package.json",
16134
+ "pnpm-lock.yaml",
16135
+ "package-lock.json",
16136
+ "yarn.lock",
16137
+ "pnpm-workspace.yaml",
16138
+ ".npmrc"
16139
+ ];
16140
+ function sha256(data) {
16141
+ return createHash3("sha256").update(data).digest("hex");
16142
+ }
16143
+ async function hashFile(path) {
16144
+ try {
16145
+ return sha256(await readFile8(path));
16146
+ } catch {
16147
+ return null;
16148
+ }
16149
+ }
16150
+ async function hashStrategyInputs(checkout, appRoot) {
16151
+ const candidates = /* @__PURE__ */ new Set();
16152
+ for (const name of CHECKOUT_FILES) candidates.add(join9(checkout, name));
16153
+ for (const name of APP_ROOT_FILES) candidates.add(join9(appRoot, name));
16154
+ for (const base2 of SOURCE_FILES) {
16155
+ for (const ext of SOURCE_EXTENSIONS) {
16156
+ candidates.add(join9(appRoot, `${base2}.${ext}`));
16157
+ }
16158
+ }
16159
+ const entries = [];
16160
+ for (const path of candidates) {
16161
+ const digest = await hashFile(path);
16162
+ if (digest === null) continue;
16163
+ entries.push([relative4(checkout, path).split(sep3).join("/"), digest]);
16164
+ }
16165
+ return entries.toSorted(
16166
+ ([left], [right]) => left < right ? -1 : left > right ? 1 : 0
16167
+ );
16168
+ }
16169
+ async function computeStrategyFingerprint(input) {
16170
+ const entries = input.entries ?? await hashStrategyInputs(input.checkout, input.appRoot);
16171
+ const lines = [
16172
+ `next=${input.nextVersion}`,
16173
+ `root=${input.rootDirectory ?? ""}`,
16174
+ `vinext=${input.vinextEnabled ? "on" : "off"}`,
16175
+ ...entries.map(([name, digest]) => `${name}\0${digest}`)
16176
+ ];
16177
+ return {
16178
+ hash: sha256(lines.join("\n")),
16179
+ inputs: entries.map(([name]) => name)
16180
+ };
16181
+ }
16182
+
16183
+ // ../../packages/build/src/strategy/project-facts.ts
16184
+ import { access as access6, readFile as readFile9 } from "fs/promises";
16185
+ import { join as join10 } from "path";
16186
+ var SOURCE_EXTENSIONS2 = ["ts", "tsx", "js", "jsx", "mjs", "mts"];
16187
+ var SOURCE_DIRECTORIES = ["", "src"];
16188
+ var VITE_CONFIGS = [
16189
+ "vite.config.ts",
16190
+ "vite.config.mts",
16191
+ "vite.config.js",
16192
+ "vite.config.mjs"
16193
+ ];
16194
+ async function exists4(path) {
16195
+ try {
16196
+ await access6(path);
16197
+ return true;
16198
+ } catch {
16199
+ return false;
16200
+ }
16201
+ }
16202
+ async function findSourceFile(appRoot, basename4) {
16203
+ for (const dir of SOURCE_DIRECTORIES) {
16204
+ for (const ext of SOURCE_EXTENSIONS2) {
16205
+ const rel = dir ? `${dir}/${basename4}.${ext}` : `${basename4}.${ext}`;
16206
+ if (await exists4(join10(appRoot, rel))) return rel;
16207
+ }
16208
+ }
16209
+ return null;
16210
+ }
16211
+ function matcherRoutesFromSource(source) {
16212
+ const matcher = /matcher\s*:\s*(\[[\s\S]*?\]|["'`][^"'`]*["'`])/.exec(source);
16213
+ if (!matcher?.[1]) return [];
16214
+ const literals = [...matcher[1].matchAll(/["'`]([^"'`]*)["'`]/g)].map((entry) => entry[1] ?? "").map((entry) => entry.trim());
16215
+ const routes = /* @__PURE__ */ new Set();
16216
+ for (const literal2 of literals) {
16217
+ if (!literal2.startsWith("/")) continue;
16218
+ if (/[:*+?()[\]{}\\|]/.test(literal2)) continue;
16219
+ routes.add(literal2);
16220
+ if (routes.size >= 5) break;
16221
+ }
16222
+ return [...routes];
16223
+ }
16224
+ async function readIfExists(path) {
16225
+ try {
16226
+ return await readFile9(path, "utf8");
16227
+ } catch {
16228
+ return null;
16229
+ }
16230
+ }
16231
+ async function collectProjectFacts(appRoot) {
16232
+ const proxyFile = await findSourceFile(appRoot, "proxy");
16233
+ const middlewareFile = await findSourceFile(appRoot, "middleware");
16234
+ const routeSourceFile = middlewareFile ?? proxyFile;
16235
+ const routeSource = routeSourceFile ? await readIfExists(join10(appRoot, routeSourceFile)) : null;
16236
+ let usesVinext = false;
16237
+ let nextSpec = null;
16238
+ const packageJsonSource = await readIfExists(join10(appRoot, "package.json"));
16239
+ if (packageJsonSource) {
16240
+ try {
16241
+ const packageJson = JSON.parse(packageJsonSource);
16242
+ const dependency = (name) => {
16243
+ const value = packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];
16244
+ return typeof value === "string" ? value : null;
16245
+ };
16246
+ nextSpec = dependency("next");
16247
+ if (dependency("vinext") !== null) {
16248
+ for (const name of VITE_CONFIGS) {
16249
+ const config2 = await readIfExists(join10(appRoot, name));
16250
+ if (config2 && /\bvinext\s*\(/.test(config2) && /@cloudflare\/vite-plugin/.test(config2)) {
16251
+ usesVinext = true;
16252
+ break;
16253
+ }
16254
+ }
16255
+ }
16256
+ } catch {
16257
+ }
16258
+ }
16259
+ return {
16260
+ proxyFile,
16261
+ middlewareFile,
16262
+ matcherRoutes: routeSource ? matcherRoutesFromSource(routeSource) : [],
16263
+ usesVinext,
16264
+ nextSpec
16265
+ };
16266
+ }
16267
+
16268
+ // ../../packages/build/src/strategy/resolver.ts
16269
+ function label(strategy) {
16270
+ return `P${String(strategy.priority)} ${strategy.id}`;
16271
+ }
16272
+ function describeOutcome(outcome) {
16273
+ switch (outcome.status) {
16274
+ case "success":
16275
+ return "succeeded";
16276
+ case "failed":
16277
+ return `failed at ${outcome.stage}: ${outcome.reason}`;
16278
+ case "skipped":
16279
+ return `skipped: ${outcome.reason}`;
16280
+ }
16281
+ }
16282
+ async function evaluateOne(strategy, context, observer) {
16283
+ let outcome;
16284
+ try {
16285
+ outcome = await strategy.evaluate(context, observer);
16286
+ } catch (error51) {
16287
+ outcome = {
16288
+ status: "failed",
16289
+ stage: "build",
16290
+ reason: error51 instanceof Error ? error51.message : String(error51)
16291
+ };
16292
+ }
16293
+ await observer.onLog({
16294
+ level: outcome.status === "success" ? "INFO" : "WARN",
16295
+ stage: "BUILDING",
16296
+ message: `${label(strategy)} ${describeOutcome(outcome)}.`
16297
+ });
16298
+ return { id: strategy.id, priority: strategy.priority, outcome };
16299
+ }
16300
+ async function runPool(tasks, limit) {
16301
+ const results = Array.from({ length: tasks.length });
16302
+ let next = 0;
16303
+ const workers = Array.from(
16304
+ { length: Math.max(1, Math.min(limit, tasks.length)) },
16305
+ async () => {
16306
+ for (; ; ) {
16307
+ const index = next++;
16308
+ const task = tasks[index];
16309
+ if (!task) return;
16310
+ results[index] = await task();
16311
+ }
16312
+ }
16313
+ );
16314
+ await Promise.all(workers);
16315
+ return results;
16316
+ }
16317
+ function pickBest(candidates, attempts) {
16318
+ const successes = attempts.filter(
16319
+ (attempt) => attempt.outcome.status === "success"
16320
+ ).toSorted((left, right) => left.priority - right.priority);
16321
+ const best = successes[0];
16322
+ if (!best) return null;
16323
+ const strategy = candidates.find((candidate) => candidate.id === best.id);
16324
+ return strategy ? { strategy, outcome: best.outcome } : null;
16325
+ }
16326
+ function record2(strategy, outcome, context, options) {
16327
+ return {
16328
+ strategy: strategy.id,
16329
+ priority: strategy.priority,
16330
+ nextMajor: context.nextMajor,
16331
+ ...outcome.variant ? { variant: outcome.variant } : {},
16332
+ fingerprint: options.fingerprint,
16333
+ resolvedAt: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
16334
+ deploymentId: context.identity.deploymentId
16335
+ };
16336
+ }
16337
+ async function resolveDeploymentStrategy(strategies, context, observer, options) {
16338
+ const ordered = strategies.toSorted(
16339
+ (left, right) => left.priority - right.priority
16340
+ );
16341
+ const candidates = [];
16342
+ for (const strategy of ordered) {
16343
+ const support = strategy.supports(context);
16344
+ if (support.supported) {
16345
+ candidates.push(strategy);
16346
+ } else {
16347
+ await observer.onLog({
16348
+ level: "INFO",
16349
+ stage: "BUILDING",
16350
+ message: `${label(strategy)} not a candidate: ${support.reason}`
16351
+ });
16352
+ }
16353
+ }
16354
+ if (candidates.length === 0) {
16355
+ throw new DeploymentError(
16356
+ "NO_DEPLOYMENT_STRATEGY",
16357
+ `No deployment strategy can build Next.js ${context.nextVersion} on this platform.`
16358
+ );
16359
+ }
16360
+ const attempts = [];
16361
+ const pinnedStrategy = options.pinned ? candidates.find((candidate) => candidate.id === options.pinned?.strategy) : void 0;
16362
+ if (options.pinned && pinnedStrategy) {
16363
+ const pinned = options.pinned;
16364
+ const stale = pinned.fingerprint !== options.fingerprint ? "its inputs changed" : pinned.nextMajor !== context.nextMajor ? `Next.js moved from ${String(pinned.nextMajor)} to ${String(context.nextMajor)}` : null;
16365
+ if (stale) {
16366
+ await observer.onLog({
16367
+ level: "INFO",
16368
+ stage: "BUILDING",
16369
+ message: `Stored strategy ${label(pinnedStrategy)} is stale (${stale}); resolving again.`
16370
+ });
16371
+ } else {
16372
+ await observer.onLog({
16373
+ level: "INFO",
16374
+ stage: "BUILDING",
16375
+ message: `Reusing stored strategy ${label(pinnedStrategy)}${pinned.variant ? ` (${pinned.variant})` : ""}.`
16376
+ });
16377
+ const attempt = await evaluateOne(
16378
+ pinnedStrategy,
16379
+ pinned.variant ? { ...context, pinnedVariant: pinned.variant } : context,
16380
+ observer
16381
+ );
16382
+ attempts.push(attempt);
16383
+ if (attempt.outcome.status === "success") {
16384
+ const outcome = attempt.outcome;
16385
+ const unchanged = (outcome.variant ?? null) === (pinned.variant ?? null);
16386
+ return {
16387
+ strategy: pinnedStrategy,
16388
+ outcome,
16389
+ record: unchanged ? pinned : record2(pinnedStrategy, outcome, context, options),
16390
+ pinned: true,
16391
+ attempts
16392
+ };
16393
+ }
16394
+ await observer.onLog({
16395
+ level: "WARN",
16396
+ stage: "BUILDING",
16397
+ message: `Stored strategy ${label(pinnedStrategy)} no longer works; resolving again with the remaining candidates.`
16398
+ });
16399
+ }
16400
+ } else if (options.pinned) {
16401
+ await observer.onLog({
16402
+ level: "INFO",
16403
+ stage: "BUILDING",
16404
+ message: `Stored strategy ${options.pinned.strategy} is not a candidate for this checkout; resolving again.`
16405
+ });
16406
+ }
16407
+ const remaining = candidates.filter(
16408
+ (candidate) => !attempts.some((attempt) => attempt.id === candidate.id)
16409
+ );
16410
+ await observer.onLog({
16411
+ level: "INFO",
16412
+ stage: "BUILDING",
16413
+ message: `Resolving deployment strategy for Next.js ${context.nextVersion}: ${remaining.map(label).join(
16414
+ ", "
16415
+ )}${options.concurrency > 1 && remaining.length > 1 ? " (in parallel)" : ""}.`
16416
+ });
16417
+ if (options.concurrency > 1) {
16418
+ const results = await runPool(
16419
+ remaining.map(
16420
+ (strategy) => () => evaluateOne(strategy, context, observer)
16421
+ ),
16422
+ options.concurrency
16423
+ );
16424
+ attempts.push(...results);
16425
+ } else {
16426
+ for (const strategy of remaining) {
16427
+ const attempt = await evaluateOne(strategy, context, observer);
16428
+ attempts.push(attempt);
16429
+ if (attempt.outcome.status === "success") break;
16430
+ }
16431
+ }
16432
+ const best = pickBest(candidates, attempts);
16433
+ if (!best) {
16434
+ throw new DeploymentError(
16435
+ "NO_DEPLOYMENT_STRATEGY",
16436
+ `No deployment strategy succeeded for Next.js ${context.nextVersion}: ${attempts.map(
16437
+ (attempt) => `P${String(attempt.priority)} ${attempt.id} ${describeOutcome(attempt.outcome)}`
16438
+ ).join("; ")}.`
16439
+ );
16440
+ }
16441
+ await observer.onLog({
16442
+ level: "INFO",
16443
+ stage: "BUILDING",
16444
+ message: `Selected ${label(best.strategy)}${best.outcome.variant ? ` (${best.outcome.variant})` : ""}.`
16445
+ });
16446
+ return {
16447
+ strategy: best.strategy,
16448
+ outcome: best.outcome,
16449
+ record: record2(best.strategy, best.outcome, context, options),
16450
+ pinned: false,
16451
+ attempts
16452
+ };
16453
+ }
16454
+
16455
+ // ../../packages/build/src/strategy/smoke.ts
16456
+ import { createServer } from "net";
16457
+ function allocateLoopbackPort() {
16458
+ return new Promise((resolvePort, rejectPort) => {
16459
+ const server = createServer();
16460
+ server.unref();
16461
+ server.once("error", rejectPort);
16462
+ server.listen(0, "127.0.0.1", () => {
16463
+ const address = server.address();
16464
+ const port = typeof address === "object" && address !== null ? address.port : null;
16465
+ server.close(() => {
16466
+ if (port === null) rejectPort(new Error("Could not allocate a port."));
16467
+ else resolvePort(port);
16468
+ });
16469
+ });
16470
+ });
16471
+ }
16472
+ function sleep(ms) {
16473
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
16474
+ }
16475
+ async function fetchStatus(fetchImpl, url2, timeoutMs) {
16476
+ const controller = new AbortController();
16477
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
16478
+ try {
16479
+ const response = await fetchImpl(url2, {
16480
+ method: "GET",
16481
+ redirect: "manual",
16482
+ signal: controller.signal,
16483
+ headers: { "user-agent": "naro-smoke/1" }
16484
+ });
16485
+ await response.arrayBuffer().catch(() => void 0);
16486
+ return { status: response.status };
16487
+ } catch (error51) {
16488
+ return { error: error51 instanceof Error ? error51.message : String(error51) };
16489
+ } finally {
16490
+ clearTimeout(timer);
16491
+ }
16492
+ }
16493
+ function probeOk(status) {
16494
+ return status < 500;
16495
+ }
16496
+ function createSmokeTester(options = {}) {
16497
+ const start = options.startProcess ?? startProcess;
16498
+ const fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
16499
+ const allocatePort = options.allocatePort ?? allocateLoopbackPort;
16500
+ const pollIntervalMs = options.pollIntervalMs ?? 1e3;
16501
+ async function runOnce(request, observer) {
16502
+ const stage = request.stage ?? "BUILDING";
16503
+ const errorCode = request.errorCode ?? "BUILD_FAILED";
16504
+ const bootTimeoutMs = request.bootTimeoutMs ?? 9e4;
16505
+ const probeTimeoutMs = request.probeTimeoutMs ?? 15e3;
16506
+ const port = await allocatePort();
16507
+ const origin = `http://127.0.0.1:${String(port)}`;
16508
+ const boot = request.boot(port);
16509
+ const recent = [];
16510
+ const server = start(
16511
+ { ...boot, stage, errorCode },
16512
+ // The dev server is chatty; keep its stderr from reading as build
16513
+ // errors in the deployment log.
16514
+ {
16515
+ ...observer,
16516
+ onLog: (log2) => {
16517
+ if (log2.message.trim() !== "") {
16518
+ recent.push(log2.message.trim());
16519
+ if (recent.length > 6) recent.shift();
16520
+ }
16521
+ return observer.onLog({
16522
+ ...log2,
16523
+ level: log2.level === "ERROR" ? "WARN" : log2.level
16524
+ });
16525
+ }
16526
+ }
16527
+ );
16528
+ const probes = [];
16529
+ const withOutput = (reason) => recent.length > 0 ? `${reason} Last output: ${recent.join(" | ")}` : reason;
16530
+ try {
16531
+ const deadline = Date.now() + bootTimeoutMs;
16532
+ let booted = false;
16533
+ let lastError = "no response";
16534
+ while (Date.now() < deadline) {
16535
+ const exit = await Promise.race([
16536
+ server.exited.then((code) => ({ exited: code })),
16537
+ sleep(0).then(() => null)
16538
+ ]);
16539
+ if (exit) {
16540
+ return {
16541
+ ok: false,
16542
+ reason: withOutput(
16543
+ `The server exited with code ${String(exit.exited)} before answering a request.`
16544
+ ),
16545
+ probes,
16546
+ // The port was free when allocated and taken by the time the
16547
+ // server bound it — a race, not a broken build.
16548
+ portTaken: recent.some(
16549
+ (line) => /EADDRINUSE|address already in use/i.test(line)
16550
+ )
16551
+ };
16552
+ }
16553
+ const answer = await fetchStatus(
16554
+ fetchImpl,
16555
+ `${origin}/`,
16556
+ probeTimeoutMs
16557
+ );
16558
+ if ("status" in answer) {
16559
+ booted = true;
16560
+ break;
16561
+ }
16562
+ lastError = answer.error;
16563
+ await sleep(pollIntervalMs);
16564
+ }
16565
+ if (!booted) {
16566
+ return {
16567
+ ok: false,
16568
+ reason: withOutput(
16569
+ `The server did not answer within ${String(bootTimeoutMs)}ms (${lastError}).`
16570
+ ),
16571
+ probes
16572
+ };
16573
+ }
16574
+ for (const probe of request.probes) {
16575
+ const answer = await fetchStatus(
16576
+ fetchImpl,
16577
+ `${origin}${probe.path}`,
16578
+ probeTimeoutMs
16579
+ );
16580
+ const result = "status" in answer ? { ...probe, status: answer.status, ok: probeOk(answer.status) } : { ...probe, status: null, ok: false, detail: answer.error };
16581
+ probes.push(result);
16582
+ await observer.onLog({
16583
+ level: result.ok ? "INFO" : "ERROR",
16584
+ stage,
16585
+ message: `Smoke ${probe.label}: GET ${probe.path} -> ${result.status === null ? result.detail ?? "no response" : String(result.status)}`
16586
+ });
16587
+ }
16588
+ const failed2 = probes.filter((probe) => !probe.ok);
16589
+ if (failed2.length > 0) {
16590
+ return {
16591
+ ok: false,
16592
+ reason: `${String(failed2.length)} of ${String(probes.length)} smoke probes failed: ${failed2.map(
16593
+ (probe) => `${probe.path} (${probe.status === null ? probe.detail ?? "no response" : String(probe.status)})`
16594
+ ).join(", ")}.`,
16595
+ probes
16596
+ };
16597
+ }
16598
+ return { ok: true, probes };
16599
+ } finally {
16600
+ await server.stop();
16601
+ }
16602
+ }
16603
+ return {
16604
+ async run(request, observer) {
16605
+ const first = await runOnce(request, observer);
16606
+ if (first.ok || !first.portTaken) return first;
16607
+ await observer.onLog({
16608
+ level: "WARN",
16609
+ stage: request.stage ?? "BUILDING",
16610
+ message: "Smoke port was taken before the server bound it; retrying on a fresh port."
16611
+ });
16612
+ return runOnce(request, observer);
16613
+ }
16614
+ };
16615
+ }
16616
+ function defaultSmokeProbes(matcherRoutes) {
16617
+ const probes = [{ path: "/", label: "root" }];
16618
+ for (const route of matcherRoutes) {
16619
+ if (route === "/") continue;
16620
+ probes.push({ path: route, label: "middleware matcher" });
16621
+ }
16622
+ return probes;
16623
+ }
16624
+
16625
+ // ../../packages/build/src/strategy/strategies/opennext-aws.ts
16626
+ import { writeFile as writeFile6 } from "fs/promises";
16627
+ import { join as join12 } from "path";
16628
+
16629
+ // ../../packages/build/src/strategy/strategies/shared.ts
16630
+ import { access as access7, readFile as readFile10, rm, writeFile as writeFile5 } from "fs/promises";
16631
+ import { dirname as dirname2, join as join11, resolve as resolve5 } from "path";
16632
+ function capturingObserver(observer) {
16633
+ const lines = [];
16634
+ return {
16635
+ lines,
16636
+ observer: {
16637
+ ...observer,
16638
+ onLog: (log2) => {
16639
+ lines.push(log2.message);
16640
+ return observer.onLog(log2);
16641
+ }
16642
+ }
16643
+ };
16644
+ }
16645
+ async function runStep(context, spec, observer) {
16646
+ const capture = capturingObserver(observer);
16647
+ try {
16648
+ return {
16649
+ ok: true,
16650
+ result: await context.ports.runCommand(spec, capture.observer)
16651
+ };
16652
+ } catch (error51) {
16653
+ return { ok: false, error: error51, output: capture.lines };
16654
+ }
16655
+ }
16656
+ function describeError(error51) {
16657
+ return error51 instanceof Error ? error51.message : String(error51);
16658
+ }
16659
+ function describeFailure(step) {
16660
+ const tail = step.output.map((line) => line.trim()).filter((line) => line !== "").slice(-4);
16661
+ return tail.length > 0 ? `${describeError(step.error)} \u2014 ${tail.join(" | ")}` : describeError(step.error);
16662
+ }
16663
+ function failed(stage, reason) {
16664
+ return { status: "failed", stage, reason };
16665
+ }
16666
+ async function info(observer, message) {
16667
+ await observer.onLog({ level: "INFO", stage: "BUILDING", message });
16668
+ }
16669
+ async function warn(observer, message) {
16670
+ await observer.onLog({ level: "WARN", stage: "BUILDING", message });
16671
+ }
16672
+ async function pathExists2(path) {
16673
+ try {
16674
+ await access7(path);
16675
+ return true;
16676
+ } catch {
16677
+ return false;
16678
+ }
16679
+ }
16680
+ async function findProjectBin(appRoot, checkout, name) {
16681
+ let dir = resolve5(appRoot);
16682
+ const stop = resolve5(checkout);
16683
+ for (; ; ) {
16684
+ const candidate = join11(dir, "node_modules", ".bin", name);
16685
+ if (await pathExists2(candidate)) return candidate;
16686
+ if (dir === stop) return null;
16687
+ const parent = dirname2(dir);
16688
+ if (parent === dir) return null;
16689
+ dir = parent;
16690
+ }
16691
+ }
16692
+ function dotenvQuote(value) {
16693
+ if (!value.includes("'")) return `'${value}'`;
16694
+ if (!value.includes("`")) return `\`${value}\``;
16695
+ return `"${value.replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r")}"`;
16696
+ }
16697
+ async function withDevVars(wranglerConfigPath, env, run) {
16698
+ const path = join11(dirname2(wranglerConfigPath), ".dev.vars");
16699
+ const keys = Object.keys(env).filter(
16700
+ (key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)
16701
+ );
16702
+ if (keys.length === 0) return run();
16703
+ await writeFile5(
16704
+ path,
16705
+ `${keys.map((key) => `${key}=${dotenvQuote(env[key] ?? "")}`).join("\n")}
16706
+ `,
16707
+ { mode: 384 }
16708
+ );
16709
+ try {
16710
+ return await run();
16711
+ } finally {
16712
+ await rm(path, { force: true });
16713
+ }
16714
+ }
16715
+ async function bundleWorker(context, layout, observer, errorCode) {
16716
+ return runStep(
16717
+ context,
16718
+ {
16719
+ command: context.toolchain.wrangler,
16720
+ args: [
16721
+ "deploy",
16722
+ "--dry-run",
16723
+ "--config",
16724
+ layout.wranglerConfigPath,
16725
+ "--outdir",
16726
+ layout.bundleDir
16727
+ ],
16728
+ cwd: layout.appRoot,
16729
+ env: {
16730
+ CI: "1",
16731
+ WRANGLER_LOG_PATH: join11(
16732
+ layout.appRoot,
16733
+ layout.outputRoot,
16734
+ "wrangler.log"
16735
+ )
16736
+ },
16737
+ timeoutMs: 5 * 6e4,
16738
+ stage: "BUILDING",
16739
+ errorCode
16740
+ },
16741
+ observer
16742
+ );
16743
+ }
16744
+ async function smokeWorker(context, layout, observer) {
16745
+ return withDevVars(
16746
+ layout.wranglerConfigPath,
16747
+ context.env,
16748
+ () => context.ports.smoke.run(
16749
+ {
16750
+ boot: (port) => ({
16751
+ command: context.toolchain.wrangler,
16752
+ args: [
16753
+ "dev",
16754
+ "--config",
16755
+ layout.wranglerConfigPath,
16756
+ "--local",
16757
+ "--ip",
16758
+ "127.0.0.1",
16759
+ "--port",
16760
+ String(port),
16761
+ "--show-interactive-dev-session=false",
16762
+ "--log-level=warn",
16763
+ "--persist-to",
16764
+ join11(layout.appRoot, layout.outputRoot, ".naro-smoke-state")
16765
+ ],
16766
+ cwd: layout.appRoot,
16767
+ env: {
16768
+ CI: "1",
16769
+ WRANGLER_LOG_PATH: join11(
16770
+ layout.appRoot,
16771
+ layout.outputRoot,
16772
+ "wrangler-dev.log"
16773
+ )
16774
+ }
16775
+ }),
16776
+ probes: defaultSmokeProbes(context.facts.matcherRoutes),
16777
+ stage: "BUILDING",
16778
+ errorCode: "BUILD_FAILED"
16779
+ },
16780
+ observer
16781
+ )
16782
+ );
16783
+ }
16784
+ async function approvePnpmBuilds(checkout, packages) {
16785
+ const path = join11(checkout, "pnpm-workspace.yaml");
16786
+ let source = "";
16787
+ try {
16788
+ source = await readFile10(path, "utf8");
16789
+ } catch {
16790
+ }
16791
+ let next = source;
16792
+ const mapEntries = packages.map((name) => ` ${name}: true`).join("\n");
16793
+ const flowMap = /^(allowBuilds\s*:\s*\{)([^}]*)\}/m;
16794
+ if (flowMap.test(next)) {
16795
+ next = next.replace(flowMap, (_match, open, body) => {
16796
+ const items = body.trim();
16797
+ const added = packages.map((name) => `${name}: true`).join(", ");
16798
+ return `${open} ${items ? `${items.replace(/,\s*$/, "")}, ` : ""}${added} }`;
16799
+ });
16800
+ } else if (/^allowBuilds\s*:/m.test(next)) {
16801
+ next = next.replace(/^(allowBuilds\s*:[^\n]*\n)/m, `$1${mapEntries}
16802
+ `);
16803
+ } else {
16804
+ next = `${next}${next.endsWith("\n") || next === "" ? "" : "\n"}allowBuilds:
16805
+ ${mapEntries}
16806
+ `;
16807
+ }
16808
+ const listEntries = packages.map((name) => ` - ${name}`).join("\n");
16809
+ const flowList = /^(onlyBuiltDependencies\s*:\s*\[)([^\]]*)\]/m;
16810
+ if (flowList.test(next)) {
16811
+ next = next.replace(flowList, (_match, open, body) => {
16812
+ const items = body.trim();
16813
+ return `${open}${items ? `${items.replace(/,\s*$/, "")}, ` : ""}${packages.join(", ")}]`;
16814
+ });
16815
+ } else if (/^onlyBuiltDependencies\s*:/m.test(next)) {
16816
+ next = next.replace(
16817
+ /^(onlyBuiltDependencies\s*:[^\n]*\n)/m,
16818
+ `$1${listEntries}
16819
+ `
16820
+ );
16821
+ } else {
16822
+ next = `${next}onlyBuiltDependencies:
16823
+ ${listEntries}
16824
+ `;
16825
+ }
16826
+ if (next === source) return true;
16827
+ try {
16828
+ await writeFile5(path, next, "utf8");
16829
+ return true;
16830
+ } catch {
16831
+ return false;
16832
+ }
16833
+ }
16834
+
16835
+ // ../../packages/build/src/strategy/strategies/opennext-aws.ts
16836
+ var CONFIG_NAME = ".next-deploy-open-next-aws.config.ts";
16837
+ var SERVER_FUNCTION = ".open-next/server-functions/default/index.mjs";
16838
+ function smokeConfig(buildCommand) {
16839
+ return [
16840
+ "const config = {",
16841
+ ` buildCommand: ${JSON.stringify(buildCommand)},`,
16842
+ " default: {",
16843
+ ' override: { wrapper: "node", converter: "node", incrementalCache: "dummy", tagCache: "dummy", queue: "dummy" },',
16844
+ " },",
16845
+ ' imageOptimization: { loader: "dummy" },',
16846
+ " dangerous: { disableIncrementalCache: true, disableTagCache: true },",
16847
+ "};",
16848
+ "export default config;",
16849
+ ""
16850
+ ].join("\n");
16851
+ }
16852
+ function createOpenNextAwsStrategy() {
16853
+ return {
16854
+ id: "opennext-aws",
16855
+ priority: 5,
16856
+ target: "aws",
16857
+ supports(context) {
16858
+ if (!context.targets.aws)
16859
+ return {
16860
+ supported: false,
16861
+ reason: "No AWS deploy target is configured on this control plane (no provider, credentials, or routing exist for it)."
16862
+ };
16863
+ if (!context.toolchain.openNextAws)
16864
+ return {
16865
+ supported: false,
16866
+ reason: "The @opennextjs/aws toolchain is not installed on this build runner."
16867
+ };
16868
+ return { supported: true };
16869
+ },
16870
+ async evaluate(context, observer) {
16871
+ await info(
16872
+ observer,
16873
+ "P5 opennext-aws: building with the OpenNext AWS adapter."
16874
+ );
16875
+ let fork;
16876
+ try {
16877
+ fork = await context.workspace.fork("opennext-aws");
16878
+ } catch (error51) {
16879
+ return failed("prepare", describeError(error51));
16880
+ }
16881
+ const configPath = join12(fork.appRoot, CONFIG_NAME);
16882
+ try {
16883
+ await writeFile6(
16884
+ configPath,
16885
+ smokeConfig(
16886
+ `${context.packageManager.name} run ${context.buildScript}`
16887
+ ),
16888
+ "utf8"
16889
+ );
16890
+ } catch (error51) {
16891
+ return failed("prepare", describeError(error51));
16892
+ }
16893
+ const build = await runStep(
16894
+ context,
16895
+ {
16896
+ command: context.toolchain.openNextAws ?? "open-next",
16897
+ args: ["build", "--config-path", configPath],
16898
+ cwd: fork.appRoot,
16899
+ env: {
16900
+ ...context.env,
16901
+ NPM_CONFIG_USERCONFIG: context.packageManagerConfigPath
16902
+ },
16903
+ timeoutMs: 20 * 6e4,
16904
+ stage: "BUILDING",
16905
+ errorCode: "BUILD_FAILED"
16906
+ },
16907
+ observer
16908
+ );
16909
+ if (!build.ok) return failed("build", describeFailure(build));
16910
+ const serverFunction = join12(fork.appRoot, SERVER_FUNCTION);
16911
+ if (!await pathExists2(serverFunction))
16912
+ return failed("bundle", `OpenNext AWS produced no ${SERVER_FUNCTION}.`);
16913
+ const smoke = await context.ports.smoke.run(
16914
+ {
16915
+ boot: (port) => ({
16916
+ command: process.execPath,
16917
+ args: [serverFunction],
16918
+ cwd: fork.appRoot,
16919
+ env: { ...context.env, PORT: String(port), NODE_ENV: "production" }
16920
+ }),
16921
+ probes: defaultSmokeProbes(context.facts.matcherRoutes),
16922
+ stage: "BUILDING",
16923
+ errorCode: "BUILD_FAILED"
16924
+ },
16925
+ observer
16926
+ );
16927
+ if (!smoke.ok) return failed("smoke", smoke.reason);
16928
+ return {
16929
+ status: "success",
16930
+ artifact: {
16931
+ kind: "opennext-aws",
16932
+ outputRoot: join12(fork.appRoot, ".open-next"),
16933
+ serverFunction
16934
+ },
16935
+ notes: [
16936
+ "Verified with the Node wrapper; an AWS deploy needs a Lambda-wrapper build against provisioned resources."
16937
+ ]
16938
+ };
16939
+ }
16940
+ };
16941
+ }
16942
+
16943
+ // ../../packages/build/src/strategy/strategies/opennext-cloudflare.ts
16944
+ import { readFile as readFile11 } from "fs/promises";
16945
+ import { join as join13 } from "path";
16946
+ var PROXY_TO_MIDDLEWARE_VARIANT = "proxy-to-middleware";
16947
+ var NODE_MIDDLEWARE_REFUSAL = /Node\.js middleware is not (?:currently )?supported/i;
16948
+ async function buildDeclaresNodeMiddleware(appRoot) {
16949
+ try {
16950
+ const manifest2 = JSON.parse(
16951
+ await readFile11(
16952
+ join13(appRoot, ".next", "server", "functions-config-manifest.json"),
16953
+ "utf8"
16954
+ )
16955
+ );
16956
+ return Boolean(manifest2.functions?.["/_middleware"]);
16957
+ } catch {
16958
+ return false;
16959
+ }
16960
+ }
16961
+ function createOpenNextCloudflareStrategy(options) {
16962
+ async function attempt(context, roots, observer) {
16963
+ let platform;
16964
+ try {
16965
+ platform = await options.createPlatformBuildConfig(
16966
+ roots.appRoot,
16967
+ context.identity.deploymentId
16968
+ );
16969
+ } catch (error51) {
16970
+ return {
16971
+ ok: false,
16972
+ nodeMiddleware: false,
16973
+ outcome: failed("prepare", describeError(error51))
16974
+ };
16975
+ }
16976
+ const nextBuild = await runStep(
16977
+ context,
16978
+ {
16979
+ command: context.packageManager.name,
16980
+ args: ["run", context.buildScript],
16981
+ cwd: roots.appRoot,
16982
+ env: {
16983
+ ...context.env,
16984
+ NEXT_PRIVATE_STANDALONE: "true",
16985
+ NPM_CONFIG_USERCONFIG: platform.packageManagerConfigPath
16986
+ },
16987
+ timeoutMs: 15 * 6e4,
16988
+ stage: "BUILDING",
16989
+ errorCode: "BUILD_FAILED"
16990
+ },
16991
+ observer
16992
+ );
16993
+ if (!nextBuild.ok) {
16994
+ return {
16995
+ ok: false,
16996
+ nodeMiddleware: false,
16997
+ outcome: failed("build", describeFailure(nextBuild))
16998
+ };
16999
+ }
17000
+ const openNext = await runStep(
17001
+ context,
17002
+ {
17003
+ command: context.toolchain.openNextCloudflare,
17004
+ args: [
17005
+ "build",
17006
+ "--skip-next-build",
17007
+ "--openNextConfigPath",
17008
+ platform.openNextConfigPath,
17009
+ "--config",
17010
+ platform.wranglerConfigPath,
17011
+ ...context.nextSupport.status === "needs-unsupported-flag" ? ["--dangerouslyUseUnsupportedNextVersion"] : []
17012
+ ],
17013
+ cwd: roots.appRoot,
17014
+ timeoutMs: 15 * 6e4,
17015
+ stage: "BUILDING",
17016
+ errorCode: "OPENNEXT_FAILED"
17017
+ },
17018
+ observer
17019
+ );
17020
+ if (!openNext.ok) {
17021
+ const nodeMiddleware = openNext.output.some((line) => NODE_MIDDLEWARE_REFUSAL.test(line)) || await buildDeclaresNodeMiddleware(roots.appRoot);
17022
+ return {
17023
+ ok: false,
17024
+ nodeMiddleware,
17025
+ outcome: failed(
17026
+ "build",
17027
+ nodeMiddleware ? "OpenNext Cloudflare does not support Node.js middleware (Next.js 16 proxy.ts)." : describeFailure(openNext)
17028
+ )
17029
+ };
17030
+ }
17031
+ const layout = {
17032
+ appRoot: roots.appRoot,
17033
+ wranglerConfigPath: platform.wranglerConfigPath,
17034
+ ...OPEN_NEXT_LAYOUT
17035
+ };
17036
+ const bundle = await bundleWorker(
17037
+ context,
17038
+ layout,
17039
+ observer,
17040
+ "OPENNEXT_FAILED"
17041
+ );
17042
+ if (!bundle.ok) {
17043
+ return {
17044
+ ok: false,
17045
+ nodeMiddleware: false,
17046
+ outcome: failed("bundle", describeFailure(bundle))
17047
+ };
17048
+ }
17049
+ let artifact;
17050
+ try {
17051
+ artifact = await options.readOpenNextArtifact(
17052
+ roots.appRoot,
17053
+ context.identity,
17054
+ platform.wranglerConfigPath
17055
+ );
17056
+ } catch (error51) {
17057
+ return {
17058
+ ok: false,
17059
+ nodeMiddleware: false,
17060
+ outcome: failed("bundle", describeError(error51))
17061
+ };
17062
+ }
17063
+ const smoke = await smokeWorker(context, layout, observer);
17064
+ if (!smoke.ok) {
17065
+ return {
17066
+ ok: false,
17067
+ nodeMiddleware: false,
17068
+ outcome: failed("smoke", smoke.reason)
17069
+ };
17070
+ }
17071
+ return { ok: true, artifact };
17072
+ }
17073
+ return {
17074
+ id: "opennext-cloudflare",
17075
+ priority: 2,
17076
+ target: "cloudflare",
17077
+ supports: () => ({ supported: true }),
17078
+ async evaluate(context, observer) {
17079
+ const rememberedRewrite = context.pinnedVariant === PROXY_TO_MIDDLEWARE_VARIANT && context.facts.proxyFile !== null;
17080
+ if (rememberedRewrite) {
17081
+ await info(
17082
+ observer,
17083
+ `P2 opennext-cloudflare: the stored strategy already needed ${context.facts.proxyFile ?? "proxy"} rewritten to Edge middleware; building that way directly.`
17084
+ );
17085
+ } else {
17086
+ await info(
17087
+ observer,
17088
+ "P2 opennext-cloudflare: building the project as written."
17089
+ );
17090
+ const first = await attempt(context, context.workspace, observer);
17091
+ if (first.ok) {
17092
+ return {
17093
+ status: "success",
17094
+ artifact: { kind: "cloudflare-worker", artifact: first.artifact },
17095
+ notes: []
17096
+ };
17097
+ }
17098
+ if (!first.nodeMiddleware || !context.facts.proxyFile)
17099
+ return first.outcome;
17100
+ await warn(
17101
+ observer,
17102
+ `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.`
17103
+ );
17104
+ }
17105
+ let fork;
17106
+ try {
17107
+ fork = await context.workspace.fork("opennext-proxy-to-middleware");
17108
+ } catch (error51) {
17109
+ return failed("prepare", describeError(error51));
17110
+ }
17111
+ const conversion = await options.convertProxyToMiddleware(fork.appRoot);
17112
+ if (!conversion.converted) {
17113
+ return failed(
17114
+ "prepare",
17115
+ `Could not rewrite ${context.facts.proxyFile} to middleware (a middleware file already exists).`
17116
+ );
17117
+ }
17118
+ await warn(
17119
+ observer,
17120
+ `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.`
17121
+ );
17122
+ const second = await attempt(context, fork, observer);
17123
+ if (!second.ok) return second.outcome;
17124
+ return {
17125
+ status: "success",
17126
+ artifact: { kind: "cloudflare-worker", artifact: second.artifact },
17127
+ variant: PROXY_TO_MIDDLEWARE_VARIANT,
17128
+ notes: [
17129
+ `${conversion.file ?? "proxy"} was rewritten to Edge middleware for the build.`
17130
+ ]
17131
+ };
17132
+ }
17133
+ };
17134
+ }
17135
+
17136
+ // ../../packages/build/src/strategy/strategies/unavailable.ts
17137
+ function createUnavailableStrategy(options) {
17138
+ return {
17139
+ id: options.id,
17140
+ priority: options.priority,
17141
+ target: options.target,
17142
+ supports: () => ({ supported: false, reason: options.reason }),
17143
+ evaluate: async () => ({ status: "skipped", reason: options.reason })
17144
+ };
17145
+ }
17146
+ function createSplitCloudflareStrategy() {
17147
+ return createUnavailableStrategy({
17148
+ id: "split-cloudflare",
17149
+ priority: 3,
17150
+ target: "cloudflare-split",
17151
+ 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."
17152
+ });
17153
+ }
17154
+ function createAwsMiddlewareCloudflareAppStrategy() {
17155
+ return createUnavailableStrategy({
17156
+ id: "aws-middleware-cloudflare-app",
17157
+ priority: 4,
17158
+ target: "aws-hybrid",
17159
+ reason: "Hybrid AWS middleware + Cloudflare app needs an AWS deploy target and cross-cloud request chaining; neither exists on this control plane."
17160
+ });
17161
+ }
17162
+
17163
+ // ../../packages/build/src/strategy/strategies/vinext-cloudflare.ts
17164
+ import { join as join14 } from "path";
17165
+ var VINEXT_MIN_NEXT_MAJOR = 16;
17166
+ var VINEXT_BUILD_SCRIPTS = ["esbuild", "workerd"];
17167
+ var VINEXT_OUTPUT = {
17168
+ outputRoot: "dist",
17169
+ bundleDir: "dist/.naro-bundle"
17170
+ };
17171
+ async function runCheck(context, observer) {
17172
+ if (!context.toolchain.vinext) return null;
17173
+ const held = [];
17174
+ try {
17175
+ const result = await context.ports.runCommand(
17176
+ {
17177
+ command: context.toolchain.vinext,
17178
+ args: ["check"],
17179
+ cwd: context.workspace.appRoot,
17180
+ timeoutMs: 2 * 6e4,
17181
+ stage: "BUILDING",
17182
+ errorCode: "BUILD_FAILED"
17183
+ },
17184
+ {
17185
+ onLog: (log2) => {
17186
+ held.push(log2);
17187
+ }
17188
+ }
17189
+ );
17190
+ const report = parseVinextCheckReport(result.stdout.join("\n"));
17191
+ if (!report || report.issues > 0) {
17192
+ for (const log2 of held) await observer.onLog(log2);
17193
+ }
17194
+ return report;
17195
+ } catch (error51) {
17196
+ for (const log2 of held) await observer.onLog(log2);
17197
+ await observer.onLog({
17198
+ level: "WARN",
17199
+ stage: "BUILDING",
17200
+ message: `vinext check could not run: ${describeError(error51)}`
17201
+ });
17202
+ return null;
17203
+ }
17204
+ }
17205
+ function createVinextCloudflareStrategy(options) {
17206
+ const toolchain = options.toolchain ?? VINEXT_TOOLCHAIN;
17207
+ return {
17208
+ id: "vinext-cloudflare",
17209
+ priority: 1,
17210
+ target: "cloudflare",
17211
+ supports(context) {
17212
+ if (!context.vinextEnabled)
17213
+ return {
17214
+ supported: false,
17215
+ reason: "vinext is disabled for this project."
17216
+ };
17217
+ if (context.nextMajor < VINEXT_MIN_NEXT_MAJOR)
17218
+ return {
17219
+ supported: false,
17220
+ reason: `vinext targets Next.js ${String(VINEXT_MIN_NEXT_MAJOR)}; this project runs Next.js ${String(context.nextMajor)}.`
17221
+ };
17222
+ if (!context.toolchain.vinext)
17223
+ return {
17224
+ supported: false,
17225
+ reason: "The vinext toolchain is not installed on this build runner."
17226
+ };
17227
+ return { supported: true };
17228
+ },
17229
+ async evaluate(context, observer) {
17230
+ const adopt = !context.facts.usesVinext;
17231
+ if (adopt) {
17232
+ const decision = vinextRouteFromCheck(
17233
+ await runCheck(context, observer)
17234
+ );
17235
+ await info(observer, `P1 vinext-cloudflare: ${decision.reason}`);
17236
+ if (!decision.eligible) return failed("check", decision.reason);
17237
+ } else {
17238
+ await info(
17239
+ observer,
17240
+ "P1 vinext-cloudflare: the project already builds with vinext; using its own configuration."
17241
+ );
17242
+ }
17243
+ let fork;
17244
+ try {
17245
+ fork = await context.workspace.fork("vinext");
17246
+ } catch (error51) {
17247
+ return failed("prepare", describeError(error51));
17248
+ }
17249
+ if (adopt) {
17250
+ try {
17251
+ await options.createVinextBuildConfig(
17252
+ fork.appRoot,
17253
+ context.identity.deploymentId
17254
+ );
17255
+ } catch (error51) {
17256
+ return failed("prepare", describeError(error51));
17257
+ }
17258
+ if (context.packageManager.name === "pnpm") {
17259
+ await approvePnpmBuilds(fork.checkout, VINEXT_BUILD_SCRIPTS);
17260
+ }
17261
+ const [addCommand, ...addArgs] = context.packageManager.addArgs(toolchain);
17262
+ if (!addCommand) {
17263
+ return failed(
17264
+ "prepare",
17265
+ new DeploymentError(
17266
+ "UNSUPPORTED_PACKAGE_MANAGER",
17267
+ "Package manager cannot add packages."
17268
+ ).message
17269
+ );
17270
+ }
17271
+ const added = await runStep(
17272
+ context,
17273
+ {
17274
+ command: addCommand,
17275
+ args: addArgs,
17276
+ cwd: fork.appRoot,
17277
+ // Not production: npm and classic Yarn read that as omit=dev and
17278
+ // would prune the project's devDependencies while adding ours.
17279
+ env: {
17280
+ NODE_ENV: "development",
17281
+ NPM_CONFIG_USERCONFIG: context.packageManagerConfigPath
17282
+ },
17283
+ timeoutMs: 10 * 6e4,
17284
+ stage: "BUILDING",
17285
+ errorCode: "INSTALL_FAILED"
17286
+ },
17287
+ observer
17288
+ );
17289
+ if (!added.ok) return failed("prepare", describeFailure(added));
17290
+ }
17291
+ const projectBin = adopt ? null : await findProjectBin(fork.appRoot, fork.checkout, "vinext");
17292
+ const build = await runStep(
17293
+ context,
17294
+ {
17295
+ command: projectBin ?? context.toolchain.vinext ?? "vinext",
17296
+ args: ["build"],
17297
+ cwd: fork.appRoot,
17298
+ env: { ...context.env, CI: "1" },
17299
+ timeoutMs: 15 * 6e4,
17300
+ stage: "BUILDING",
17301
+ errorCode: "BUILD_FAILED"
17302
+ },
17303
+ observer
17304
+ );
17305
+ if (!build.ok) return failed("build", describeFailure(build));
17306
+ let artifact;
17307
+ try {
17308
+ artifact = await options.readVinextArtifact(
17309
+ fork.appRoot,
17310
+ context.identity
17311
+ );
17312
+ } catch (error51) {
17313
+ return failed("bundle", describeError(error51));
17314
+ }
17315
+ const smoke = await smokeWorker(
17316
+ context,
17317
+ {
17318
+ appRoot: fork.appRoot,
17319
+ wranglerConfigPath: join14(
17320
+ fork.appRoot,
17321
+ "dist",
17322
+ "server",
17323
+ "wrangler.json"
17324
+ ),
17325
+ ...VINEXT_OUTPUT
17326
+ },
17327
+ observer
17328
+ );
17329
+ if (!smoke.ok) return failed("smoke", smoke.reason);
17330
+ return {
17331
+ status: "success",
17332
+ artifact: { kind: "cloudflare-worker", artifact },
17333
+ notes: adopt ? [
17334
+ "Built with the naro-provided vinext toolchain in a temporary workspace."
17335
+ ] : ["Built with the project's own vinext configuration."]
17336
+ };
17337
+ }
17338
+ };
17339
+ }
17340
+
17341
+ // ../../packages/build/src/strategy/workspace.ts
17342
+ import { cp } from "fs/promises";
17343
+ import { basename as basename2, join as join15, relative as relative5, resolve as resolve6 } from "path";
17344
+ var SKIPPED_ANYWHERE = /* @__PURE__ */ new Set(["node_modules"]);
17345
+ function shouldCopy(path) {
17346
+ const name = basename2(path);
17347
+ if (SKIPPED_ANYWHERE.has(name)) return false;
17348
+ return !name.startsWith(".next-deploy-");
17349
+ }
17350
+ async function copyCheckout(source, target) {
17351
+ const origin = resolve6(source);
17352
+ await cp(origin, target, {
17353
+ recursive: true,
17354
+ verbatimSymlinks: true,
17355
+ filter: (path) => resolve6(path) === origin || shouldCopy(path)
17356
+ });
17357
+ }
17358
+ function forkedAppRoot(checkout, appRoot, forkCheckout) {
17359
+ return join15(forkCheckout, relative5(checkout, appRoot));
17360
+ }
17361
+
15583
17362
  // ../../packages/build/src/local-build-executor.ts
15584
- var defaultDependencies = {
15585
- createWorkspace: () => mkdtemp(join8(tmpdir(), "next-deploy-build-")),
15586
- removeWorkspace: (path) => rm(path, { recursive: true, force: true }),
17363
+ function concurrencyFromEnvironment() {
17364
+ const raw = Number(process.env.NARO_RESOLVER_CONCURRENCY ?? "1");
17365
+ return Number.isInteger(raw) && raw > 0 ? raw : 1;
17366
+ }
17367
+ var defaultLocalBuildDependencies = {
17368
+ createWorkspace: () => mkdtemp(join16(tmpdir(), "next-deploy-build-")),
17369
+ removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
15587
17370
  runCommand,
15588
17371
  detectPackageManager,
15589
17372
  resolveAppRoot,
@@ -15595,8 +17378,65 @@ var defaultDependencies = {
15595
17378
  openNextCommand: "opennextjs-cloudflare",
15596
17379
  wranglerCommand: "wrangler",
15597
17380
  cacheFetch: (input, init) => fetch(input, init),
15598
- convertProxyToMiddleware
17381
+ convertProxyToMiddleware,
17382
+ commandAvailable: hasCommand,
17383
+ readInstalledNextVersion,
17384
+ now: () => /* @__PURE__ */ new Date(),
17385
+ createVinextBuildConfig,
17386
+ readVinextArtifact,
17387
+ vinextCommand: "vinext",
17388
+ forkWorkspace: copyCheckout,
17389
+ collectProjectFacts,
17390
+ hashFingerprintInputs: hashStrategyInputs,
17391
+ computeFingerprint: computeStrategyFingerprint,
17392
+ targets: { cloudflare: true },
17393
+ resolverConcurrency: concurrencyFromEnvironment()
15599
17394
  };
17395
+ async function readInstalledNextVersion(appRoot, checkout) {
17396
+ let dir = resolve7(appRoot);
17397
+ const stop = resolve7(checkout);
17398
+ for (; ; ) {
17399
+ try {
17400
+ const source = await readFile12(
17401
+ join16(dir, "node_modules", "next", "package.json"),
17402
+ "utf8"
17403
+ );
17404
+ const version2 = JSON.parse(source).version;
17405
+ return typeof version2 === "string" ? version2 : null;
17406
+ } catch {
17407
+ }
17408
+ if (dir === stop) return null;
17409
+ const parent = dirname3(dir);
17410
+ if (parent === dir) return null;
17411
+ dir = parent;
17412
+ }
17413
+ }
17414
+ async function hasCommand(command) {
17415
+ try {
17416
+ await runCommand(
17417
+ {
17418
+ command,
17419
+ args: ["--version"],
17420
+ cwd: tmpdir(),
17421
+ timeoutMs: 1e4,
17422
+ stage: "CLONING",
17423
+ errorCode: "CLONE_FAILED"
17424
+ },
17425
+ { onLog: () => void 0 }
17426
+ );
17427
+ return true;
17428
+ } catch {
17429
+ return false;
17430
+ }
17431
+ }
17432
+ async function pathExists3(path) {
17433
+ try {
17434
+ await access8(path);
17435
+ return true;
17436
+ } catch {
17437
+ return false;
17438
+ }
17439
+ }
15600
17440
  function sanitizeCacheKey(value) {
15601
17441
  return value.replace(/[^A-Za-z0-9._-]/g, "-").slice(0, 128);
15602
17442
  }
@@ -15626,26 +17466,54 @@ async function trySave(scope, key, sourceDir, ports, observer) {
15626
17466
  async function notifyStatus(observer, status) {
15627
17467
  await observer.onStatus?.(status);
15628
17468
  }
15629
- async function info(observer, stage, message) {
17469
+ async function info2(observer, stage, message) {
15630
17470
  await observer.onLog({ level: "INFO", stage, message });
15631
17471
  }
17472
+ function defaultStrategies(dependencies) {
17473
+ return [
17474
+ createVinextCloudflareStrategy({
17475
+ createVinextBuildConfig: dependencies.createVinextBuildConfig ?? createVinextBuildConfig,
17476
+ readVinextArtifact: dependencies.readVinextArtifact ?? readVinextArtifact
17477
+ }),
17478
+ createOpenNextCloudflareStrategy({
17479
+ createPlatformBuildConfig: dependencies.createPlatformBuildConfig,
17480
+ convertProxyToMiddleware: dependencies.convertProxyToMiddleware ?? convertProxyToMiddleware,
17481
+ readOpenNextArtifact: dependencies.readOpenNextArtifact
17482
+ }),
17483
+ createSplitCloudflareStrategy(),
17484
+ createAwsMiddlewareCloudflareAppStrategy(),
17485
+ createOpenNextAwsStrategy()
17486
+ ];
17487
+ }
15632
17488
  var LocalBuildExecutor = class {
15633
- constructor(dependencies = defaultDependencies) {
17489
+ constructor(dependencies = defaultLocalBuildDependencies) {
15634
17490
  this.dependencies = dependencies;
15635
17491
  }
15636
17492
  async build(input, observer, cache) {
15637
17493
  const workspace = await this.dependencies.createWorkspace();
15638
- const checkout = join8(workspace, "checkout");
15639
- const pmCacheDir = join8(workspace, ".pm-store");
17494
+ const checkout = join16(workspace, "checkout");
17495
+ const pmCacheDir = join16(workspace, ".pm-store");
15640
17496
  let cacheSeq = 0;
15641
- const cachePorts = cache ? {
17497
+ const commandAvailable = this.dependencies.commandAvailable ?? hasCommand;
17498
+ const compressor = "zstd";
17499
+ const canCompress = cache ? await commandAvailable(compressor) : false;
17500
+ const cachePorts = cache && canCompress ? {
15642
17501
  transport: cache,
15643
17502
  fetch: this.dependencies.cacheFetch ?? ((url2, init) => fetch(url2, init)),
15644
17503
  runCommand: this.dependencies.runCommand,
15645
- tmpFile: (suffix) => join8(workspace, `cache-${String(cacheSeq++)}${suffix}`)
17504
+ tmpFile: (suffix) => join16(workspace, `cache-${String(cacheSeq++)}${suffix}`),
17505
+ // One latch for the whole build so a disabled cache warns once.
17506
+ report: { unavailable: false }
15646
17507
  } : null;
15647
17508
  try {
15648
17509
  await notifyStatus(observer, "CLONING");
17510
+ if (cache && !canCompress) {
17511
+ await observer.onLog({
17512
+ level: "WARN",
17513
+ stage: "CLONING",
17514
+ message: `Build cache disabled: ${compressor} is not installed. Install it to reuse dependencies between deploys.`
17515
+ });
17516
+ }
15649
17517
  await this.dependencies.runCommand(
15650
17518
  {
15651
17519
  command: "git",
@@ -15710,27 +17578,31 @@ var LocalBuildExecutor = class {
15710
17578
  buildCommand: input.buildCommand ?? null,
15711
17579
  outputDirectory: input.outputDirectory ?? null
15712
17580
  });
15713
- await info(observer, "CLONING", `Detected ${resolved.description}`);
17581
+ await info2(observer, "CLONING", `Detected ${resolved.description}`);
15714
17582
  const isNext = resolved.preset.id === "next";
15715
- let platformConfig = null;
15716
- let packageManagerConfigPath = null;
15717
- if (isNext) {
15718
- const conversion = await (this.dependencies.convertProxyToMiddleware ?? convertProxyToMiddleware)(appRoot);
15719
- if (conversion.converted) {
15720
- await observer.onLog({
15721
- level: "WARN",
15722
- stage: "CLONING",
15723
- message: `Converted ${conversion.file ?? "proxy"} to edge middleware. 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.`
15724
- });
17583
+ if (isNext && resolved.dependencyRange) {
17584
+ const declared = declaredNextMajor(resolved.dependencyRange);
17585
+ if (declared !== null && declared < MIN_SUPPORTED_NEXT_MAJOR) {
17586
+ throw new DeploymentError(
17587
+ "NEXT_VERSION_UNSUPPORTED",
17588
+ `Naro deploys Next.js ${String(MIN_SUPPORTED_NEXT_MAJOR)} or newer; this project declares next@${resolved.dependencyRange}. Upgrade Next.js and deploy again.`
17589
+ );
15725
17590
  }
15726
- platformConfig = await this.dependencies.createPlatformBuildConfig(
15727
- appRoot,
15728
- input.deploymentId
15729
- );
15730
- packageManagerConfigPath = platformConfig.packageManagerConfigPath;
15731
- } else if (resolved.buildScript !== null) {
17591
+ }
17592
+ let packageManagerConfigPath = null;
17593
+ if (resolved.buildScript !== null) {
15732
17594
  packageManagerConfigPath = await this.dependencies.createPackageManagerConfig(appRoot);
15733
17595
  }
17596
+ const fingerprintEntries = isNext ? await (this.dependencies.hashFingerprintInputs ?? hashStrategyInputs)(
17597
+ checkout,
17598
+ appRoot
17599
+ ) : [];
17600
+ let artifact = null;
17601
+ let strategyRecord;
17602
+ const identity = {
17603
+ deploymentId: input.deploymentId,
17604
+ projectId: input.projectId
17605
+ };
15734
17606
  if (resolved.buildScript !== null) {
15735
17607
  await notifyStatus(observer, "INSTALLING");
15736
17608
  const packageManager = await this.dependencies.detectPackageManager(checkout);
@@ -15754,28 +17626,57 @@ var LocalBuildExecutor = class {
15754
17626
  );
15755
17627
  }
15756
17628
  }
15757
- await this.dependencies.runCommand(
15758
- {
15759
- command: installCommand,
15760
- args: installArgs,
15761
- cwd: checkout,
15762
- env: {
15763
- NODE_ENV: "development",
15764
- NPM_CONFIG_USERCONFIG: packageManagerConfigPath,
15765
- ...cachePorts ? {
15766
- npm_config_store_dir: pmCacheDir,
15767
- npm_config_cache: pmCacheDir,
15768
- YARN_CACHE_FOLDER: pmCacheDir
15769
- } : {}
17629
+ const install = async (cwd) => {
17630
+ await this.dependencies.runCommand(
17631
+ {
17632
+ command: installCommand,
17633
+ args: cachePorts ? [...installArgs, ...packageManager.cacheArgs(pmCacheDir)] : installArgs,
17634
+ cwd,
17635
+ env: {
17636
+ NODE_ENV: "development",
17637
+ NPM_CONFIG_USERCONFIG: packageManagerConfigPath,
17638
+ ...cachePorts ? {
17639
+ npm_config_store_dir: pmCacheDir,
17640
+ npm_config_cache: pmCacheDir,
17641
+ YARN_CACHE_FOLDER: pmCacheDir
17642
+ } : {}
17643
+ },
17644
+ timeoutMs: 10 * 6e4,
17645
+ stage: "INSTALLING",
17646
+ errorCode: "INSTALL_FAILED"
15770
17647
  },
15771
- timeoutMs: 10 * 6e4,
15772
- stage: "INSTALLING",
15773
- errorCode: "INSTALL_FAILED"
15774
- },
15775
- observer
15776
- );
17648
+ observer
17649
+ );
17650
+ };
17651
+ await install(checkout);
17652
+ let nextSupport = {
17653
+ status: "unknown",
17654
+ version: null,
17655
+ reason: ""
17656
+ };
17657
+ if (isNext) {
17658
+ const installed = await (this.dependencies.readInstalledNextVersion ?? readInstalledNextVersion)(appRoot, checkout);
17659
+ if (installed) {
17660
+ nextSupport = classifyNextVersion(
17661
+ installed,
17662
+ (this.dependencies.now ?? (() => /* @__PURE__ */ new Date()))()
17663
+ );
17664
+ if (nextSupport.status === "too-old")
17665
+ throw new DeploymentError(
17666
+ "NEXT_VERSION_UNSUPPORTED",
17667
+ nextSupport.reason
17668
+ );
17669
+ if (nextSupport.status === "needs-unsupported-flag" || nextSupport.status === "outside-peer-range") {
17670
+ await observer.onLog({
17671
+ level: "WARN",
17672
+ stage: "BUILDING",
17673
+ message: nextSupport.reason
17674
+ });
17675
+ }
17676
+ }
17677
+ }
15777
17678
  await notifyStatus(observer, "BUILDING");
15778
- const buildCacheDir = resolved.preset.cachePath ? join8(appRoot, resolved.preset.cachePath) : null;
17679
+ const buildCacheDir = resolved.preset.cachePath ? join16(appRoot, resolved.preset.cachePath) : null;
15779
17680
  const buildCacheKey = sanitizeCacheKey(input.branch);
15780
17681
  if (cachePorts && buildCacheDir) {
15781
17682
  await tryRestore(
@@ -15786,59 +17687,97 @@ var LocalBuildExecutor = class {
15786
17687
  observer
15787
17688
  );
15788
17689
  }
15789
- await this.dependencies.runCommand(
15790
- {
15791
- command: packageManager.name,
15792
- args: ["run", resolved.buildScript],
15793
- cwd: appRoot,
15794
- env: {
15795
- ...input.env,
15796
- ...isNext ? { NEXT_PRIVATE_STANDALONE: "true" } : {},
15797
- NPM_CONFIG_USERCONFIG: packageManagerConfigPath
15798
- },
15799
- timeoutMs: 15 * 6e4,
15800
- stage: "BUILDING",
15801
- errorCode: "BUILD_FAILED"
15802
- },
15803
- observer
15804
- );
15805
17690
  if (isNext) {
15806
- await this.dependencies.runCommand(
15807
- {
15808
- command: this.dependencies.openNextCommand,
15809
- args: [
15810
- "build",
15811
- "--skip-next-build",
15812
- "--openNextConfigPath",
15813
- platformConfig.openNextConfigPath,
15814
- "--config",
15815
- platformConfig.wranglerConfigPath
15816
- ],
15817
- cwd: appRoot,
15818
- timeoutMs: 15 * 6e4,
15819
- stage: "BUILDING",
15820
- errorCode: "OPENNEXT_FAILED"
17691
+ const nextVersion = nextSupport.version;
17692
+ const declaredMajor = resolved.dependencyRange ? declaredNextMajor(resolved.dependencyRange) : null;
17693
+ const nextMajor = nextVersion?.major ?? declaredMajor ?? 16;
17694
+ const facts = await (this.dependencies.collectProjectFacts ?? collectProjectFacts)(appRoot);
17695
+ const fingerprint = await (this.dependencies.computeFingerprint ?? computeStrategyFingerprint)({
17696
+ checkout,
17697
+ appRoot,
17698
+ rootDirectory: input.rootDirectory,
17699
+ vinextEnabled: input.vinextEnabled !== false,
17700
+ entries: fingerprintEntries,
17701
+ nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown"
17702
+ });
17703
+ const pristine = join16(workspace, "pristine");
17704
+ const fork = this.dependencies.forkWorkspace ?? defaultFork;
17705
+ await fork(checkout, pristine);
17706
+ let forks = 0;
17707
+ const forkWorkspace = async (label2) => {
17708
+ const target = join16(
17709
+ workspace,
17710
+ "forks",
17711
+ `${String(forks++)}-${label2.replace(/[^a-z0-9-]/gi, "-")}`
17712
+ );
17713
+ await fork(pristine, target);
17714
+ await install(target);
17715
+ return {
17716
+ checkout: target,
17717
+ appRoot: forkedAppRoot(checkout, appRoot, target)
17718
+ };
17719
+ };
17720
+ const vinextCommand = this.dependencies.vinextCommand;
17721
+ const openNextAwsCommand = this.dependencies.openNextAwsCommand;
17722
+ const context = {
17723
+ identity,
17724
+ branch: input.branch,
17725
+ env: input.env ?? {},
17726
+ packageManager,
17727
+ packageManagerConfigPath,
17728
+ buildScript: resolved.buildScript,
17729
+ nextVersion: nextVersion ? `${String(nextVersion.major)}.${String(nextVersion.minor)}.${String(nextVersion.patch)}` : "unknown",
17730
+ nextMajor,
17731
+ nextSupport,
17732
+ vinextEnabled: input.vinextEnabled !== false,
17733
+ facts,
17734
+ workspace: { checkout, appRoot, fork: forkWorkspace },
17735
+ toolchain: {
17736
+ openNextCloudflare: this.dependencies.openNextCommand,
17737
+ wrangler: this.dependencies.wranglerCommand,
17738
+ ...vinextCommand && await commandAvailable(vinextCommand) ? { vinext: vinextCommand } : {},
17739
+ ...openNextAwsCommand && await commandAvailable(openNextAwsCommand) ? { openNextAws: openNextAwsCommand } : {}
15821
17740
  },
15822
- observer
17741
+ targets: this.dependencies.targets ?? { cloudflare: true },
17742
+ ports: {
17743
+ runCommand: this.dependencies.runCommand,
17744
+ smoke: this.dependencies.smokeTester ?? createSmokeTester(),
17745
+ commandAvailable
17746
+ }
17747
+ };
17748
+ const resolution = await resolveDeploymentStrategy(
17749
+ this.dependencies.strategies ?? defaultStrategies(this.dependencies),
17750
+ context,
17751
+ observer,
17752
+ {
17753
+ pinned: input.strategy ?? null,
17754
+ fingerprint: fingerprint.hash,
17755
+ concurrency: this.dependencies.resolverConcurrency ?? 1,
17756
+ ...this.dependencies.now ? { now: this.dependencies.now } : {}
17757
+ }
15823
17758
  );
17759
+ if (resolution.outcome.artifact.kind !== "cloudflare-worker") {
17760
+ throw new DeploymentError(
17761
+ "NO_DEPLOYMENT_STRATEGY",
17762
+ `Strategy ${resolution.strategy.id} produced a ${resolution.outcome.artifact.kind} artifact, which this control plane cannot deploy.`
17763
+ );
17764
+ }
17765
+ artifact = resolution.outcome.artifact.artifact;
17766
+ strategyRecord = resolution.record;
17767
+ await observer.onMetadata?.({ strategy: resolution.record });
17768
+ } else {
15824
17769
  await this.dependencies.runCommand(
15825
17770
  {
15826
- command: this.dependencies.wranglerCommand,
15827
- args: [
15828
- "deploy",
15829
- "--dry-run",
15830
- "--config",
15831
- platformConfig.wranglerConfigPath,
15832
- "--outdir",
15833
- ".open-next/worker-bundle"
15834
- ],
17771
+ command: packageManager.name,
17772
+ args: ["run", resolved.buildScript],
15835
17773
  cwd: appRoot,
15836
17774
  env: {
15837
- WRANGLER_LOG_PATH: join8(appRoot, ".open-next", "wrangler.log")
17775
+ ...input.env,
17776
+ NPM_CONFIG_USERCONFIG: packageManagerConfigPath
15838
17777
  },
15839
- timeoutMs: 5 * 6e4,
17778
+ timeoutMs: 15 * 6e4,
15840
17779
  stage: "BUILDING",
15841
- errorCode: "OPENNEXT_FAILED"
17780
+ errorCode: "BUILD_FAILED"
15842
17781
  },
15843
17782
  observer
15844
17783
  );
@@ -15847,7 +17786,7 @@ var LocalBuildExecutor = class {
15847
17786
  if (depsKey && !depsRestored) {
15848
17787
  await trySave("deps", depsKey, pmCacheDir, cachePorts, observer);
15849
17788
  }
15850
- if (buildCacheDir) {
17789
+ if (buildCacheDir && await pathExists3(buildCacheDir)) {
15851
17790
  await trySave(
15852
17791
  "next",
15853
17792
  buildCacheKey,
@@ -15859,27 +17798,101 @@ var LocalBuildExecutor = class {
15859
17798
  }
15860
17799
  }
15861
17800
  await notifyStatus(observer, "DEPLOYING");
15862
- const identity = {
15863
- deploymentId: input.deploymentId,
15864
- projectId: input.projectId
17801
+ artifact ??= await this.readStatic(appRoot, resolved, identity, observer);
17802
+ return {
17803
+ gitCommitSha,
17804
+ artifact,
17805
+ ...strategyRecord ? { strategy: strategyRecord } : {}
15865
17806
  };
15866
- const artifact = isNext ? await this.dependencies.readOpenNextArtifact(
15867
- appRoot,
15868
- identity,
15869
- platformConfig.wranglerConfigPath
15870
- ) : await this.dependencies.readStaticArtifact(
15871
- appRoot,
15872
- resolved.outputDirectory,
15873
- identity,
15874
- { spaFallback: resolved.preset.spaFallback },
15875
- (message) => observer.onLog({ level: "WARN", stage: "DEPLOYING", message })
15876
- );
15877
- return { gitCommitSha, artifact };
15878
17807
  } finally {
15879
17808
  await this.dependencies.removeWorkspace(workspace);
15880
17809
  }
15881
17810
  }
17811
+ readStatic(appRoot, resolved, identity, observer) {
17812
+ return this.dependencies.readStaticArtifact(
17813
+ appRoot,
17814
+ resolved.outputDirectory,
17815
+ identity,
17816
+ { spaFallback: resolved.preset.spaFallback },
17817
+ (message) => observer.onLog({
17818
+ level: "WARN",
17819
+ stage: "DEPLOYING",
17820
+ message
17821
+ })
17822
+ );
17823
+ }
15882
17824
  };
17825
+ var defaultFork = copyCheckout;
17826
+
17827
+ // ../../packages/build/src/log-batch.ts
17828
+ var DEPLOYMENT_LOG_BATCH_LIMIT = 40;
17829
+ var DEPLOYMENT_LOG_FLUSH_MS = 1e3;
17830
+ function createLogBatcher(options) {
17831
+ let buffer = [];
17832
+ let timer = null;
17833
+ let pending = null;
17834
+ function disarm() {
17835
+ if (timer === null) return;
17836
+ clearTimeout(timer);
17837
+ timer = null;
17838
+ }
17839
+ async function flush() {
17840
+ disarm();
17841
+ if (pending !== null) {
17842
+ const { error: error51 } = pending;
17843
+ pending = null;
17844
+ throw error51;
17845
+ }
17846
+ if (buffer.length === 0) return;
17847
+ const batch = buffer;
17848
+ buffer = [];
17849
+ await options.send(batch);
17850
+ }
17851
+ function arm() {
17852
+ if (timer !== null || !options.intervalMs) return;
17853
+ timer = setTimeout(() => {
17854
+ timer = null;
17855
+ flush().catch((error51) => {
17856
+ pending ??= { error: error51 };
17857
+ });
17858
+ }, options.intervalMs);
17859
+ timer.unref?.();
17860
+ }
17861
+ return {
17862
+ add: async (log2) => {
17863
+ buffer.push(log2);
17864
+ if (buffer.length >= options.limit) {
17865
+ await flush();
17866
+ return;
17867
+ }
17868
+ arm();
17869
+ },
17870
+ flush
17871
+ };
17872
+ }
17873
+
17874
+ // ../../packages/build/src/log-sender.ts
17875
+ function createLogSender(ports) {
17876
+ let warned = false;
17877
+ return async (logs) => {
17878
+ if (ports.toIngest !== null) {
17879
+ try {
17880
+ const response = await ports.toIngest(logs);
17881
+ if (response.ok) return;
17882
+ } catch {
17883
+ }
17884
+ }
17885
+ await ports.toControlPlane(logs);
17886
+ if (warned) return;
17887
+ warned = true;
17888
+ try {
17889
+ await ports.warn(
17890
+ ports.toIngest === null ? "Sent build logs through the control plane because no log ingest URL was configured." : `Sent build logs through the control plane because the log ingest worker at ${ports.ingestUrl ?? "the configured URL"} did not accept them.`
17891
+ );
17892
+ } catch {
17893
+ }
17894
+ };
17895
+ }
15883
17896
 
15884
17897
  // ../../packages/sdk/src/auth.ts
15885
17898
  import { homedir as homedir2 } from "os";
@@ -15924,6 +17937,8 @@ async function errorFromResponse(response, context) {
15924
17937
  if (body?.error?.fields !== void 0) details.fields = body.error.fields;
15925
17938
  if (typeof body?.error?.installUrl === "string")
15926
17939
  details.installUrl = body.error.installUrl;
17940
+ if (typeof body?.error?.statementIndex === "number")
17941
+ details.statementIndex = body.error.statementIndex;
15927
17942
  return new NaroError(
15928
17943
  code,
15929
17944
  serverMessage ?? `${context} failed: the control plane returned ${String(response.status)}.`,
@@ -15937,12 +17952,12 @@ async function errorFromResponse(response, context) {
15937
17952
  // ../../packages/sdk/src/session.ts
15938
17953
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
15939
17954
  import { homedir } from "os";
15940
- import { join as join9 } from "path";
17955
+ import { join as join17 } from "path";
15941
17956
  function credentialsDir(home = homedir()) {
15942
- return join9(home, ".naro");
17957
+ return join17(home, ".naro");
15943
17958
  }
15944
17959
  function credentialsFile(home = homedir()) {
15945
- return join9(credentialsDir(home), "credentials.json");
17960
+ return join17(credentialsDir(home), "credentials.json");
15946
17961
  }
15947
17962
  function saveSession(session, home = homedir()) {
15948
17963
  mkdirSync(credentialsDir(home), { recursive: true, mode: 448 });
@@ -15983,17 +17998,158 @@ function controlPlaneUrl(env = process.env) {
15983
17998
  }
15984
17999
 
15985
18000
  // ../../packages/sdk/src/callback-server.ts
15986
- import { createServer } from "http";
15987
- var DONE_HTML = "<html><body><p>\u2705 Logged in. You can close this tab and return to the terminal.</p></body></html>";
15988
- var FAIL_HTML = "<html><body><p>Login failed. You can close this tab and return to the terminal.</p></body></html>";
18001
+ import { createServer as createServer2 } from "http";
18002
+
18003
+ // ../../packages/sdk/src/callback-page.ts
18004
+ function escapeHtml(value) {
18005
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
18006
+ }
18007
+ var FAVICON = `data:image/svg+xml,${encodeURIComponent(
18008
+ '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#ededed"/><text x="16" y="23" text-anchor="middle" fill="#000" font-family="system-ui,sans-serif" font-size="20" font-weight="700">n</text></svg>'
18009
+ )}`;
18010
+ var CHECK_ICON = '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 12.5 10 17.5 19 7.5" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
18011
+ var CROSS_ICON = '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M7 7 17 17M17 7 7 17" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>';
18012
+ var STYLES = `
18013
+ *{box-sizing:border-box}
18014
+ :root{
18015
+ --bg:#000;--surface:#0a0a0a;--surface-2:#111;--border:#232323;
18016
+ --text:#ededed;--muted:#a1a1a1;--faint:#6f6f6f;
18017
+ --sans:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
18018
+ --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
18019
+ }
18020
+ html{color-scheme:dark;-webkit-font-smoothing:antialiased}
18021
+ body{
18022
+ margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;
18023
+ background:var(--bg);color:var(--text);font-family:var(--sans);
18024
+ font-size:14px;line-height:1.5;letter-spacing:-.006em;
18025
+ }
18026
+ body::before{
18027
+ content:"";position:fixed;inset:0;pointer-events:none;
18028
+ background:radial-gradient(62% 46% at 50% 0%,var(--glow),transparent 72%);
18029
+ }
18030
+ .card{
18031
+ position:relative;width:100%;max-width:400px;padding:34px 32px 28px;
18032
+ background:var(--surface);border:1px solid var(--border);border-radius:14px;
18033
+ text-align:center;animation:rise .4s cubic-bezier(.2,.7,.3,1) both;
18034
+ }
18035
+ .brand{
18036
+ display:inline-flex;align-items:center;gap:8px;
18037
+ font-size:14px;font-weight:600;letter-spacing:-.02em;color:var(--muted);
18038
+ }
18039
+ .brand-mark{
18040
+ display:inline-grid;width:22px;height:22px;place-items:center;
18041
+ background:var(--text);color:#000;border-radius:6px;
18042
+ font-size:12px;font-weight:700;line-height:1;
18043
+ }
18044
+ .glyph{
18045
+ width:52px;height:52px;margin:26px auto 20px;display:grid;place-items:center;
18046
+ border-radius:50%;color:var(--tone);
18047
+ background:var(--tone-soft);border:1px solid var(--tone-edge);
18048
+ }
18049
+ .glyph svg{width:26px;height:26px}
18050
+ .glyph path{stroke-dasharray:32;stroke-dashoffset:32;animation:draw .5s .15s cubic-bezier(.2,.7,.3,1) forwards}
18051
+ h1{margin:0 0 8px;font-size:19px;font-weight:600;letter-spacing:-.02em}
18052
+ p{margin:0 auto;max-width:32ch;color:var(--muted)}
18053
+ .detail{
18054
+ margin-top:18px;padding:10px 12px;text-align:left;word-break:break-word;
18055
+ background:var(--surface-2);border:1px solid var(--border);border-radius:8px;
18056
+ font-family:var(--mono);font-size:12.5px;color:var(--muted);
18057
+ }
18058
+ .hint{
18059
+ display:flex;align-items:center;gap:10px;margin-top:22px;padding:10px 12px;
18060
+ background:var(--surface-2);border:1px solid var(--border);border-radius:8px;text-align:left;
18061
+ }
18062
+ .hint-label{
18063
+ flex:none;font-size:10px;font-weight:600;letter-spacing:.09em;color:var(--faint);
18064
+ }
18065
+ .hint code{font-family:var(--mono);font-size:13px;color:var(--text)}
18066
+ .hint code::before{content:"$ ";color:var(--faint)}
18067
+ footer{margin-top:20px;font-size:12.5px;color:var(--faint)}
18068
+ @keyframes rise{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
18069
+ @keyframes draw{to{stroke-dashoffset:0}}
18070
+ @media (prefers-reduced-motion:reduce){
18071
+ .card{animation:none}
18072
+ .glyph path{animation:none;stroke-dashoffset:0}
18073
+ }
18074
+ `;
18075
+ var TONES = {
18076
+ success: {
18077
+ color: "#4ade80",
18078
+ soft: "rgba(74,222,128,.12)",
18079
+ edge: "rgba(74,222,128,.3)",
18080
+ glow: "rgba(74,222,128,.09)",
18081
+ icon: CHECK_ICON
18082
+ },
18083
+ failure: {
18084
+ color: "#ff5c5c",
18085
+ soft: "rgba(255,92,92,.12)",
18086
+ edge: "rgba(255,92,92,.3)",
18087
+ glow: "rgba(255,92,92,.09)",
18088
+ icon: CROSS_ICON
18089
+ }
18090
+ };
18091
+ function page(options) {
18092
+ const tone = TONES[options.tone];
18093
+ const title = escapeHtml(options.title);
18094
+ const detail = options.detail ? `<div class="detail">${escapeHtml(options.detail)}</div>` : "";
18095
+ return `<!doctype html>
18096
+ <html lang="en">
18097
+ <head>
18098
+ <meta charset="utf-8">
18099
+ <meta name="viewport" content="width=device-width,initial-scale=1">
18100
+ <title>${title} \xB7 naro</title>
18101
+ <link rel="icon" href="${FAVICON}">
18102
+ <style>:root{--tone:${tone.color};--tone-soft:${tone.soft};--tone-edge:${tone.edge};--glow:${tone.glow}}${STYLES}</style>
18103
+ </head>
18104
+ <body>
18105
+ <main class="card">
18106
+ <span class="brand"><span class="brand-mark">n</span>naro</span>
18107
+ <div class="glyph">${tone.icon}</div>
18108
+ <h1>${title}</h1>
18109
+ <p>${escapeHtml(options.message)}</p>
18110
+ ${detail}
18111
+ <div class="hint">
18112
+ <span class="hint-label">${escapeHtml(options.hint.label)}</span>
18113
+ <code>${escapeHtml(options.hint.command)}</code>
18114
+ </div>
18115
+ <footer>You can close this tab.</footer>
18116
+ </main>
18117
+ </body>
18118
+ </html>`;
18119
+ }
18120
+ function successPage() {
18121
+ return page({
18122
+ tone: "success",
18123
+ title: "You're signed in",
18124
+ message: "The CLI saved your session. Head back to the terminal to keep going.",
18125
+ hint: { label: "NEXT", command: "naro deploy" }
18126
+ });
18127
+ }
18128
+ function failurePage(reason) {
18129
+ return page({
18130
+ tone: "failure",
18131
+ title: "Sign-in failed",
18132
+ message: "Nothing was saved. Return to the terminal and try again.",
18133
+ detail: reason,
18134
+ hint: { label: "RETRY", command: "naro login" }
18135
+ });
18136
+ }
18137
+
18138
+ // ../../packages/sdk/src/callback-server.ts
18139
+ function sendPage(res, status, html) {
18140
+ res.statusCode = status;
18141
+ res.setHeader("content-type", "text/html; charset=utf-8");
18142
+ res.setHeader("cache-control", "no-store");
18143
+ res.end(html);
18144
+ }
15989
18145
  async function startCallbackServer() {
15990
18146
  let resolveCode;
15991
18147
  let rejectCode;
15992
- const codePromise = new Promise((resolve5, reject) => {
15993
- resolveCode = resolve5;
18148
+ const codePromise = new Promise((resolve10, reject) => {
18149
+ resolveCode = resolve10;
15994
18150
  rejectCode = reject;
15995
18151
  });
15996
- const server = createServer((req, res) => {
18152
+ const server = createServer2((req, res) => {
15997
18153
  const url2 = new URL(req.url ?? "/", "http://localhost");
15998
18154
  if (url2.pathname !== "/callback") {
15999
18155
  res.statusCode = 404;
@@ -16003,21 +18159,23 @@ async function startCallbackServer() {
16003
18159
  const error51 = url2.searchParams.get("error");
16004
18160
  const code = url2.searchParams.get("code");
16005
18161
  if (error51) {
16006
- res.setHeader("content-type", "text/html");
16007
- res.end(FAIL_HTML);
18162
+ const description = url2.searchParams.get("error_description");
18163
+ sendPage(res, 200, failurePage(description ?? error51));
16008
18164
  rejectCode(new Error(`GitHub authorization failed: ${error51}`));
16009
18165
  return;
16010
18166
  }
16011
18167
  if (!code) {
16012
- res.statusCode = 400;
16013
- res.end("Missing code");
18168
+ sendPage(
18169
+ res,
18170
+ 400,
18171
+ failurePage("The callback carried no authorization code.")
18172
+ );
16014
18173
  return;
16015
18174
  }
16016
- res.setHeader("content-type", "text/html");
16017
- res.end(DONE_HTML);
18175
+ sendPage(res, 200, successPage());
16018
18176
  resolveCode(code);
16019
18177
  });
16020
- await new Promise((resolve5) => server.listen(0, "127.0.0.1", resolve5));
18178
+ await new Promise((resolve10) => server.listen(0, "127.0.0.1", resolve10));
16021
18179
  const port = server.address().port;
16022
18180
  return {
16023
18181
  port,
@@ -16242,10 +18400,12 @@ function buildLogEntry(row) {
16242
18400
  }
16243
18401
 
16244
18402
  // ../../packages/sdk/src/client.ts
18403
+ var DEFAULT_REQUEST_TIMEOUT_MS = 45e3;
16245
18404
  var NaroClient = class {
16246
18405
  baseUrl;
16247
18406
  getAccessToken;
16248
18407
  doFetch;
18408
+ requestTimeoutMs;
16249
18409
  teams;
16250
18410
  projects;
16251
18411
  github;
@@ -16253,10 +18413,12 @@ var NaroClient = class {
16253
18413
  env;
16254
18414
  domains;
16255
18415
  logs;
18416
+ db;
16256
18417
  constructor(options) {
16257
18418
  this.baseUrl = (options.baseUrl ?? controlPlaneUrl()).replace(/\/$/, "");
16258
18419
  this.getAccessToken = options.getAccessToken;
16259
18420
  this.doFetch = options.fetch ?? fetch;
18421
+ this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
16260
18422
  this.teams = new TeamsApi(this);
16261
18423
  this.projects = new ProjectsApi(this);
16262
18424
  this.github = new GithubApi(this);
@@ -16264,6 +18426,7 @@ var NaroClient = class {
16264
18426
  this.env = new EnvApi(this);
16265
18427
  this.domains = new DomainsApi(this);
16266
18428
  this.logs = new LogsApi(this);
18429
+ this.db = new DbApi(this);
16267
18430
  }
16268
18431
  async request(path, options) {
16269
18432
  const token = await this.getAccessToken();
@@ -16275,9 +18438,17 @@ var NaroClient = class {
16275
18438
  Authorization: `Bearer ${token}`,
16276
18439
  ...options.body !== void 0 ? { "Content-Type": "application/json" } : {}
16277
18440
  },
16278
- ...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {}
18441
+ ...options.body !== void 0 ? { body: JSON.stringify(options.body) } : {},
18442
+ signal: AbortSignal.timeout(this.requestTimeoutMs)
16279
18443
  });
16280
18444
  } catch (error51) {
18445
+ if (error51 instanceof Error && (error51.name === "TimeoutError" || error51.name === "AbortError")) {
18446
+ throw new NaroError(
18447
+ "TIMEOUT",
18448
+ `${options.context} timed out after ${String(Math.round(this.requestTimeoutMs / 1e3))}s.`,
18449
+ { cause: error51 }
18450
+ );
18451
+ }
16281
18452
  throw new NaroError(
16282
18453
  "NETWORK_ERROR",
16283
18454
  `${options.context} failed: could not reach ${this.baseUrl}.`,
@@ -16445,11 +18616,23 @@ var DeploymentsApi = class {
16445
18616
  );
16446
18617
  return deploymentInfo(body.deployment);
16447
18618
  }
18619
+ /**
18620
+ * Stops an in-flight build (QUEUED through BUILDING) and marks the row
18621
+ * FAILED/CANCELED. A deployment that has reached DEPLOYING is past the
18622
+ * point where stopping is safe and answers DEPLOYMENT_NOT_CANCELABLE.
18623
+ */
18624
+ async cancel(projectId, deploymentId) {
18625
+ const body = await this.client.request(
18626
+ `/api/projects/${encodeURIComponent(projectId)}/deployments/${encodeURIComponent(deploymentId)}/cancel`,
18627
+ { method: "POST", context: "Cancel deployment" }
18628
+ );
18629
+ return deploymentInfo(body.deployment);
18630
+ }
16448
18631
  /** Polls a deployment until READY/FAILED, streaming new build logs. */
16449
18632
  async wait(deploymentId, options = {}) {
16450
18633
  const intervalMs = options.intervalMs ?? 2500;
16451
18634
  const timeoutMs = options.timeoutMs ?? 15 * 6e4;
16452
- const sleep = options.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
18635
+ const sleep2 = options.sleep ?? ((ms) => new Promise((resolve10) => setTimeout(resolve10, ms)));
16453
18636
  const startedAt = Date.now();
16454
18637
  let lastStatus = null;
16455
18638
  let lastSequence = -1;
@@ -16473,7 +18656,7 @@ var DeploymentsApi = class {
16473
18656
  { details: { deploymentId } }
16474
18657
  );
16475
18658
  }
16476
- await sleep(intervalMs);
18659
+ await sleep2(intervalMs);
16477
18660
  }
16478
18661
  }
16479
18662
  };
@@ -16529,6 +18712,9 @@ var EnvApi = class {
16529
18712
  return matches.length;
16530
18713
  }
16531
18714
  };
18715
+ function normalizeHostname(hostname3) {
18716
+ return hostname3.trim().toLowerCase().replace(/\.+$/, "");
18717
+ }
16532
18718
  var DomainsApi = class {
16533
18719
  constructor(client) {
16534
18720
  this.client = client;
@@ -16540,12 +18726,27 @@ var DomainsApi = class {
16540
18726
  );
16541
18727
  return body.domains;
16542
18728
  }
16543
- async add(projectId, hostname3) {
16544
- const body = await this.client.request(
16545
- `/api/projects/${encodeURIComponent(projectId)}/domains`,
16546
- { method: "POST", body: { hostname: hostname3 }, context: "Add domain" }
16547
- );
16548
- return body.domain;
18729
+ /**
18730
+ * Attaches a hostname.
18731
+ *
18732
+ * `withWww` is the apex pair the web panel offers, ticked by default: the
18733
+ * server also registers www.<apex> and turns the apex into a 308 redirect to
18734
+ * it, because most registrars (Gabia, Whois, Cafe24, GoDaddy) cannot put a
18735
+ * CNAME on an apex at all. Leaving it out is not the same as passing false —
18736
+ * it means "don't ask for the pair" — so any caller that wants the panel's
18737
+ * behaviour has to send it. The server ignores it for a hostname that is not
18738
+ * an apex, so it is safe to pass unconditionally.
18739
+ */
18740
+ async add(projectId, hostname3, options = {}) {
18741
+ const body = await this.client.request(`/api/projects/${encodeURIComponent(projectId)}/domains`, {
18742
+ method: "POST",
18743
+ body: {
18744
+ hostname: hostname3,
18745
+ ...options.withWww === void 0 ? {} : { withWww: options.withWww }
18746
+ },
18747
+ context: "Add domain"
18748
+ });
18749
+ return body.companion ? { ...body.domain, companion: body.companion } : body.domain;
16549
18750
  }
16550
18751
  async refresh(projectId, domainId) {
16551
18752
  const body = await this.client.request(
@@ -16554,6 +18755,38 @@ var DomainsApi = class {
16554
18755
  );
16555
18756
  return body.domain;
16556
18757
  }
18758
+ /**
18759
+ * Forces a check instead of reading the last one back: the control plane
18760
+ * asks Cloudflare to revalidate now (skipping its hours-long backoff),
18761
+ * probes DNS and returns the recomputed diagnosis. This is what turns a
18762
+ * stuck "pending" into something the user can act on, so `refresh` is not
18763
+ * a substitute for it.
18764
+ *
18765
+ * `auto: true` marks a check nobody asked for (a polling loop, a page that
18766
+ * refreshes itself). Revalidating rotates Cloudflare's DCV token, so a
18767
+ * background check would invalidate the TXT value the user is halfway
18768
+ * through pasting into their registrar; the server reads the stored state
18769
+ * instead for those. The DNS probe happens either way — it is the cheap half.
18770
+ *
18771
+ * The body is always sent, even for the default: a POST with no body at all
18772
+ * is the shape a JSON-parsing route rejects outright.
18773
+ */
18774
+ async verify(projectId, domainId, options = {}) {
18775
+ const body = await this.client.request(
18776
+ `/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}/verify`,
18777
+ {
18778
+ method: "POST",
18779
+ body: { auto: options.auto === true },
18780
+ context: "Verify domain"
18781
+ }
18782
+ );
18783
+ return body.domain;
18784
+ }
18785
+ /** Verifies by the handle users actually have: the hostname. */
18786
+ async verifyByHostname(projectId, hostname3, options = {}) {
18787
+ const match = await this.findByHostname(projectId, hostname3);
18788
+ return this.verify(projectId, match.id, options);
18789
+ }
16557
18790
  async removeById(projectId, domainId) {
16558
18791
  await this.client.request(
16559
18792
  `/api/projects/${encodeURIComponent(projectId)}/domains/${encodeURIComponent(domainId)}`,
@@ -16562,8 +18795,19 @@ var DomainsApi = class {
16562
18795
  }
16563
18796
  /** Removes a domain by hostname. */
16564
18797
  async remove(projectId, hostname3) {
18798
+ const match = await this.findByHostname(projectId, hostname3);
18799
+ await this.removeById(projectId, match.id);
18800
+ }
18801
+ /**
18802
+ * Domain ids exist only in our database, so every hostname-shaped call has
18803
+ * to translate through the list first.
18804
+ */
18805
+ async findByHostname(projectId, hostname3) {
18806
+ const wanted = normalizeHostname(hostname3);
16565
18807
  const domains = await this.list(projectId);
16566
- const match = domains.find((domain2) => domain2.hostname === hostname3);
18808
+ const match = domains.find(
18809
+ (domain2) => normalizeHostname(domain2.hostname) === wanted
18810
+ );
16567
18811
  if (!match) {
16568
18812
  throw new NaroError(
16569
18813
  "DOMAIN_NOT_FOUND",
@@ -16571,7 +18815,7 @@ var DomainsApi = class {
16571
18815
  { details: { hostname: hostname3 } }
16572
18816
  );
16573
18817
  }
16574
- await this.removeById(projectId, match.id);
18818
+ return match;
16575
18819
  }
16576
18820
  };
16577
18821
  var LogsApi = class {
@@ -16589,10 +18833,113 @@ var LogsApi = class {
16589
18833
  return body.entries;
16590
18834
  }
16591
18835
  };
18836
+ var DbApi = class {
18837
+ constructor(client) {
18838
+ this.client = client;
18839
+ }
18840
+ path(projectId, suffix = "") {
18841
+ return `/api/projects/${encodeURIComponent(projectId)}/database${suffix}`;
18842
+ }
18843
+ async info(projectId) {
18844
+ const body = await this.client.request(
18845
+ this.path(projectId),
18846
+ { context: "Read database" }
18847
+ );
18848
+ return body.database;
18849
+ }
18850
+ /** Idempotent: turns the provider on and provisions the D1 when there is none. */
18851
+ async enable(projectId) {
18852
+ const body = await this.client.request(
18853
+ this.path(projectId),
18854
+ { method: "POST", body: {}, context: "Enable database" }
18855
+ );
18856
+ return body.database;
18857
+ }
18858
+ async query(projectId, input) {
18859
+ const body = await this.client.request(
18860
+ this.path(projectId, "/query"),
18861
+ {
18862
+ method: "POST",
18863
+ // Only the keys given: the server validates each one it sees.
18864
+ body: {
18865
+ sql: input.sql,
18866
+ ...input.params !== void 0 ? { params: input.params } : {},
18867
+ ...input.readOnly !== void 0 ? { readOnly: input.readOnly } : {}
18868
+ },
18869
+ context: "Run SQL"
18870
+ }
18871
+ );
18872
+ return body.result;
18873
+ }
18874
+ async tables(projectId) {
18875
+ const body = await this.client.request(
18876
+ this.path(projectId, "/tables"),
18877
+ { context: "List tables" }
18878
+ );
18879
+ return body.tables;
18880
+ }
18881
+ /** The `d1_migrations` ledger, oldest first; [] when nothing was ever applied. */
18882
+ async migrations(projectId) {
18883
+ const body = await this.client.request(
18884
+ this.path(projectId, "/migrations"),
18885
+ { context: "List migrations" }
18886
+ );
18887
+ return body.migrations;
18888
+ }
18889
+ /**
18890
+ * Runs one migration file and records it. All-or-nothing: when a statement
18891
+ * fails, D1 rolls the whole file back and nothing is recorded. The
18892
+ * request makes three round trips against the 45s client timeout, so a
18893
+ * `TIMEOUT` here leaves the outcome unknown — check `migrations()` before
18894
+ * retrying rather than re-running the file blind.
18895
+ */
18896
+ async applyMigration(projectId, input) {
18897
+ return this.client.request(
18898
+ this.path(projectId, "/migrations"),
18899
+ {
18900
+ method: "POST",
18901
+ body: { name: input.name, sql: input.sql },
18902
+ context: "Apply migration"
18903
+ }
18904
+ );
18905
+ }
18906
+ };
18907
+
18908
+ // ../../packages/sdk/src/database.ts
18909
+ var MAX_CELL_CHARS = 60;
18910
+ function formatCell(value) {
18911
+ if (value === null || value === void 0) return "NULL";
18912
+ const text = typeof value === "string" ? value : typeof value === "object" ? JSON.stringify(value) : String(value);
18913
+ const oneLine = text.replace(/\s+/g, " ");
18914
+ return oneLine.length > MAX_CELL_CHARS ? `${oneLine.slice(0, MAX_CELL_CHARS - 1)}\u2026` : oneLine;
18915
+ }
18916
+ function renderRows(statement) {
18917
+ if (statement.columns.length === 0) return [];
18918
+ const cells = statement.rows.map(
18919
+ (row) => statement.columns.map((_, index) => formatCell(row[index]))
18920
+ );
18921
+ const widths = statement.columns.map(
18922
+ (column, index) => Math.max(column.length, ...cells.map((row) => row[index]?.length ?? 0))
18923
+ );
18924
+ const line = (values) => values.map((value, index) => value.padEnd(widths[index] ?? 0)).join(" ").trimEnd();
18925
+ return [
18926
+ line(statement.columns),
18927
+ widths.map((width) => "-".repeat(width)).join(" "),
18928
+ ...cells.map(line)
18929
+ ];
18930
+ }
18931
+ function formatMs(ms) {
18932
+ return ms >= 100 ? String(Math.round(ms)) : ms.toFixed(1);
18933
+ }
18934
+ function summarizeStatement(statement) {
18935
+ const rows = statement.rows.length;
18936
+ const { meta: meta3 } = statement;
18937
+ return `\u21B3 ${String(rows)} row${rows === 1 ? "" : "s"} \xB7 ${String(meta3.rowsRead)} read \xB7 ${String(meta3.rowsWritten)} written \xB7 ${formatMs(meta3.durationMs)} ms`;
18938
+ }
16592
18939
 
16593
18940
  // ../../packages/sdk/src/dotenv.ts
16594
18941
  import { existsSync, readFileSync as readFileSync2 } from "fs";
16595
- import { join as join10 } from "path";
18942
+ import { join as join18 } from "path";
16596
18943
  function parseDotenv(text) {
16597
18944
  const out = {};
16598
18945
  for (const line of text.split(/\r?\n/)) {
@@ -16610,7 +18957,7 @@ function parseDotenv(text) {
16610
18957
  }
16611
18958
  function readProjectEnv(dir) {
16612
18959
  for (const name of [".env.local", ".env.production", ".env"]) {
16613
- const path = join10(dir, name);
18960
+ const path = join18(dir, name);
16614
18961
  if (existsSync(path)) return parseDotenv(readFileSync2(path, "utf8"));
16615
18962
  }
16616
18963
  return {};
@@ -16625,8 +18972,8 @@ function serializeDotenv(vars) {
16625
18972
 
16626
18973
  // ../../packages/sdk/src/env-scan.ts
16627
18974
  import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
16628
- import { join as join11 } from "path";
16629
- var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
18975
+ import { join as join19 } from "path";
18976
+ var SOURCE_EXTENSIONS3 = /* @__PURE__ */ new Set([
16630
18977
  ".ts",
16631
18978
  ".tsx",
16632
18979
  ".js",
@@ -16680,7 +19027,7 @@ function collect(dir, out) {
16680
19027
  for (const entry of entries) {
16681
19028
  if (out.length >= MAX_FILES) return;
16682
19029
  if (entry.isSymbolicLink()) continue;
16683
- const path = join11(dir, entry.name);
19030
+ const path = join19(dir, entry.name);
16684
19031
  if (entry.isDirectory()) {
16685
19032
  if (entry.name.startsWith(".") || SKIP_DIRECTORIES.has(entry.name))
16686
19033
  continue;
@@ -16689,7 +19036,7 @@ function collect(dir, out) {
16689
19036
  }
16690
19037
  if (!entry.isFile()) continue;
16691
19038
  const dot = entry.name.lastIndexOf(".");
16692
- if (dot < 0 || !SOURCE_EXTENSIONS.has(entry.name.slice(dot))) continue;
19039
+ if (dot < 0 || !SOURCE_EXTENSIONS3.has(entry.name.slice(dot))) continue;
16693
19040
  out.push(path);
16694
19041
  }
16695
19042
  }
@@ -16712,7 +19059,7 @@ function scanEnvReferences(dir) {
16712
19059
  }
16713
19060
  }
16714
19061
  for (const name of EXAMPLE_FILES) {
16715
- const path = join11(dir, name);
19062
+ const path = join19(dir, name);
16716
19063
  if (!existsSync2(path)) continue;
16717
19064
  let parsed;
16718
19065
  try {
@@ -16730,11 +19077,11 @@ function scanEnvReferences(dir) {
16730
19077
 
16731
19078
  // ../../packages/sdk/src/framework.ts
16732
19079
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
16733
- import { join as join12 } from "path";
19080
+ import { join as join20 } from "path";
16734
19081
  function readPackageJson2(dir) {
16735
19082
  try {
16736
19083
  return JSON.parse(
16737
- readFileSync4(join12(dir, "package.json"), "utf8")
19084
+ readFileSync4(join20(dir, "package.json"), "utf8")
16738
19085
  );
16739
19086
  } catch {
16740
19087
  return null;
@@ -16745,7 +19092,7 @@ function hasDependency(pkg, name) {
16745
19092
  }
16746
19093
  function hasConfig(dir, base2, evidence) {
16747
19094
  for (const ext of ["js", "mjs", "cjs", "ts", "mts"]) {
16748
- if (existsSync3(join12(dir, `${base2}.${ext}`))) {
19095
+ if (existsSync3(join20(dir, `${base2}.${ext}`))) {
16749
19096
  evidence.push(`${base2}.${ext}`);
16750
19097
  return true;
16751
19098
  }
@@ -16757,8 +19104,8 @@ function detectFramework2(dir) {
16757
19104
  const pkg = readPackageJson2(dir);
16758
19105
  if (hasDependency(pkg, "next") || hasConfig(dir, "next.config", evidence)) {
16759
19106
  if (hasDependency(pkg, "next")) evidence.push("package.json: next");
16760
- if (existsSync3(join12(dir, "app"))) evidence.push("app/");
16761
- if (existsSync3(join12(dir, "pages"))) evidence.push("pages/");
19107
+ if (existsSync3(join20(dir, "app"))) evidence.push("app/");
19108
+ if (existsSync3(join20(dir, "pages"))) evidence.push("pages/");
16762
19109
  return {
16763
19110
  framework: "nextjs",
16764
19111
  name: "Next.js",
@@ -16812,7 +19159,7 @@ function detectFramework2(dir) {
16812
19159
  deployable: true
16813
19160
  };
16814
19161
  }
16815
- if (!pkg && existsSync3(join12(dir, "index.html"))) {
19162
+ if (!pkg && existsSync3(join20(dir, "index.html"))) {
16816
19163
  evidence.push("index.html (no package.json)");
16817
19164
  return {
16818
19165
  framework: "static",
@@ -16833,6 +19180,71 @@ function detectFramework2(dir) {
16833
19180
  };
16834
19181
  }
16835
19182
 
19183
+ // ../../packages/sdk/src/generate-types.ts
19184
+ var HEADER = [
19185
+ "// Generated by `naro gen types`. Do not edit - re-run the command instead.",
19186
+ "// `D1Database` comes from @cloudflare/workers-types."
19187
+ ];
19188
+ function valueType(declared) {
19189
+ const type = declared.toUpperCase();
19190
+ if (type.trim() === "") return "number | string | ArrayBuffer";
19191
+ if (type.includes("INT")) return "number";
19192
+ if (type.includes("CHAR") || type.includes("CLOB") || type.includes("TEXT"))
19193
+ return "string";
19194
+ if (type.includes("BLOB")) return "ArrayBuffer";
19195
+ if (type.includes("REAL") || type.includes("FLOA") || type.includes("DOUB"))
19196
+ return "number";
19197
+ return "number | string";
19198
+ }
19199
+ function interfaceBase(tableName) {
19200
+ const pascal = tableName.split(/[^A-Za-z0-9$]+/).filter((word) => word.length > 0).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("").replace(/[^A-Za-z0-9_$]/g, "");
19201
+ return pascal === "" || /^[0-9]/.test(pascal) ? `_${pascal}` : pascal;
19202
+ }
19203
+ function rowidAlias(table) {
19204
+ const keys = table.columns.filter((column) => column.primaryKey);
19205
+ const only = keys.length === 1 ? keys[0] : void 0;
19206
+ return only && only.type.trim().toUpperCase() === "INTEGER" ? only.name : null;
19207
+ }
19208
+ var UNICODE_ESCAPE = `${String.fromCharCode(92)}u`;
19209
+ function keyLiteral(name) {
19210
+ return JSON.stringify(name).replace(
19211
+ /[^ -~]/g,
19212
+ (unit) => `${UNICODE_ESCAPE}${unit.charCodeAt(0).toString(16).padStart(4, "0")}`
19213
+ );
19214
+ }
19215
+ function property(column, alias) {
19216
+ const nullable2 = !column.notNull && column.name !== alias;
19217
+ return ` ${keyLiteral(column.name)}: ${valueType(column.type)}${nullable2 ? " | null" : ""};`;
19218
+ }
19219
+ function block(name, lines) {
19220
+ return lines.length === 0 ? [`export interface ${name} {}`] : [`export interface ${name} {`, ...lines, "}"];
19221
+ }
19222
+ function generateTypes(tables) {
19223
+ const used = /* @__PURE__ */ new Set();
19224
+ const out = [...HEADER];
19225
+ const map2 = [];
19226
+ for (const table of tables) {
19227
+ const base2 = `${interfaceBase(table.name)}Row`;
19228
+ let name = base2;
19229
+ for (let suffix = 2; used.has(name); suffix += 1)
19230
+ name = `${base2}_${String(suffix)}`;
19231
+ used.add(name);
19232
+ const alias = rowidAlias(table);
19233
+ out.push(
19234
+ "",
19235
+ ...block(
19236
+ name,
19237
+ table.columns.map((column) => property(column, alias))
19238
+ )
19239
+ );
19240
+ map2.push(` ${keyLiteral(table.name)}: ${name};`);
19241
+ }
19242
+ out.push("", ...block("Database", map2));
19243
+ out.push("", ...block("Env", [" DB: D1Database;"]));
19244
+ return `${out.join("\n")}
19245
+ `;
19246
+ }
19247
+
16836
19248
  // ../../packages/sdk/src/link.ts
16837
19249
  import {
16838
19250
  existsSync as existsSync4,
@@ -16841,12 +19253,12 @@ import {
16841
19253
  rmSync as rmSync2,
16842
19254
  writeFileSync as writeFileSync2
16843
19255
  } from "fs";
16844
- import { join as join13 } from "path";
19256
+ import { join as join21 } from "path";
16845
19257
  function linkDir(dir) {
16846
- return join13(dir, ".naro");
19258
+ return join21(dir, ".naro");
16847
19259
  }
16848
19260
  function linkFile(dir) {
16849
- return join13(linkDir(dir), "project.json");
19261
+ return join21(linkDir(dir), "project.json");
16850
19262
  }
16851
19263
  function readProjectLink(dir) {
16852
19264
  try {
@@ -16868,7 +19280,7 @@ function writeProjectLink(dir, link) {
16868
19280
  writeFileSync2(linkFile(dir), `${JSON.stringify(link, null, 2)}
16869
19281
  `);
16870
19282
  writeFileSync2(
16871
- join13(linkDir(dir), "README.txt"),
19283
+ join21(linkDir(dir), "README.txt"),
16872
19284
  "This directory links the project to Naro and should not be committed.\n"
16873
19285
  );
16874
19286
  }
@@ -16878,9 +19290,9 @@ function removeProjectLink(dir) {
16878
19290
  return true;
16879
19291
  }
16880
19292
  function ensureGitignoreHasNaro(dir) {
16881
- const gitignorePath = join13(dir, ".gitignore");
19293
+ const gitignorePath = join21(dir, ".gitignore");
16882
19294
  const hasGitignore = existsSync4(gitignorePath);
16883
- if (!hasGitignore && !existsSync4(join13(dir, ".git"))) return false;
19295
+ if (!hasGitignore && !existsSync4(join21(dir, ".git"))) return false;
16884
19296
  const current = hasGitignore ? readFileSync5(gitignorePath, "utf8") : "";
16885
19297
  const covered = current.split(/\r?\n/).map((line) => line.trim()).some((line) => line === ".naro" || line === ".naro/" || line === "/.naro");
16886
19298
  if (covered) return false;
@@ -16890,9 +19302,94 @@ function ensureGitignoreHasNaro(dir) {
16890
19302
  return true;
16891
19303
  }
16892
19304
 
19305
+ // ../../packages/sdk/src/migrations.ts
19306
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "fs";
19307
+ import { resolve as resolve8 } from "path";
19308
+ function resolveMigrationsDir(input) {
19309
+ if (input.dir) return resolve8(input.baseDir, input.dir);
19310
+ return resolve8(input.baseDir, input.rootDirectory ?? "", "migrations");
19311
+ }
19312
+ function normalizeMigrationName(name) {
19313
+ return name.trim().toLowerCase().replace(/\.sql$/, "").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
19314
+ }
19315
+ function leadingNumber(name) {
19316
+ const match = /^(\d+)/.exec(name);
19317
+ return match ? Number(match[1]) : null;
19318
+ }
19319
+ function sortMigrationNames(names) {
19320
+ return names.toSorted((a, b) => {
19321
+ const left = leadingNumber(a);
19322
+ const right = leadingNumber(b);
19323
+ if (left !== null && right !== null && left !== right) return left - right;
19324
+ if (left === null && right !== null) return 1;
19325
+ if (left !== null && right === null) return -1;
19326
+ return a < b ? -1 : a > b ? 1 : 0;
19327
+ });
19328
+ }
19329
+ function highestMigrationNumber(names) {
19330
+ return names.reduce(
19331
+ (max, name) => Math.max(max, leadingNumber(name) ?? 0),
19332
+ 0
19333
+ );
19334
+ }
19335
+ function listMigrationFiles(dir) {
19336
+ if (!existsSync5(dir)) return [];
19337
+ return sortMigrationNames(
19338
+ readdirSync2(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".sql")).map((entry) => entry.name)
19339
+ );
19340
+ }
19341
+ function nextMigrationFileName(existing, name) {
19342
+ const normalized = normalizeMigrationName(name);
19343
+ if (normalized === "")
19344
+ throw new NaroError(
19345
+ "VALIDATION_FAILED",
19346
+ "Migration name must contain letters or digits."
19347
+ );
19348
+ const number4 = String(highestMigrationNumber(existing) + 1).padStart(4, "0");
19349
+ return `${number4}_${normalized}.sql`;
19350
+ }
19351
+ function compareMigrations(local, applied) {
19352
+ const ledger = new Map(applied.map((row) => [row.name, row]));
19353
+ const highestApplied = highestMigrationNumber(applied.map((row) => row.name));
19354
+ const rows = [];
19355
+ for (const name of local) {
19356
+ const exact = ledger.get(name);
19357
+ if (exact) {
19358
+ ledger.delete(name);
19359
+ rows.push({ name, status: "applied", appliedAt: exact.appliedAt });
19360
+ continue;
19361
+ }
19362
+ const bare = ledger.get(name.replace(/\.sql$/, ""));
19363
+ if (bare) {
19364
+ ledger.delete(bare.name);
19365
+ rows.push({
19366
+ name,
19367
+ status: "applied",
19368
+ appliedAt: bare.appliedAt,
19369
+ warning: "ledger-name-missing-extension"
19370
+ });
19371
+ continue;
19372
+ }
19373
+ const number4 = leadingNumber(name);
19374
+ rows.push({
19375
+ name,
19376
+ status: "pending",
19377
+ ...number4 !== null && number4 < highestApplied ? { warning: "out-of-order" } : {}
19378
+ });
19379
+ }
19380
+ for (const row of ledger.values())
19381
+ rows.push({
19382
+ name: row.name,
19383
+ status: "remote-only",
19384
+ appliedAt: row.appliedAt
19385
+ });
19386
+ const order = sortMigrationNames(rows.map((row) => row.name));
19387
+ return rows.toSorted((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
19388
+ }
19389
+
16893
19390
  // src/env-consent.ts
16894
- import { existsSync as existsSync5 } from "fs";
16895
- import { join as join14 } from "path";
19391
+ import { existsSync as existsSync6 } from "fs";
19392
+ import { join as join22 } from "path";
16896
19393
  var DOTENV_FILES = [".env.local", ".env.production", ".env"];
16897
19394
  var PUBLIC_PREFIXES = ["NEXT_PUBLIC_", "VITE_", "REACT_APP_", "PUBLIC_"];
16898
19395
  function maskEnvValue(key, value) {
@@ -16901,7 +19398,7 @@ function maskEnvValue(key, value) {
16901
19398
  return value.length <= 8 ? "****" : `${value.slice(0, 4)}****`;
16902
19399
  }
16903
19400
  function findDotenvFile(dir) {
16904
- for (const name of DOTENV_FILES) if (existsSync5(join14(dir, name))) return name;
19401
+ for (const name of DOTENV_FILES) if (existsSync6(join22(dir, name))) return name;
16905
19402
  return null;
16906
19403
  }
16907
19404
  function envAddGuidance(keys) {
@@ -16958,10 +19455,10 @@ var SUPPORTED_REMOTE = [
16958
19455
  /^ssh:\/\/[^\s]+$/,
16959
19456
  /^git@[A-Za-z0-9.-]+:[A-Za-z0-9._/-]+(\.git)?$/
16960
19457
  ];
16961
- var defaultExec = (args, cwd) => new Promise((resolve5, reject) => {
19458
+ var defaultExec = (args, cwd) => new Promise((resolve10, reject) => {
16962
19459
  execFile("git", args, { cwd }, (error51, stdout) => {
16963
19460
  if (error51) reject(error51);
16964
- else resolve5(stdout);
19461
+ else resolve10(stdout);
16965
19462
  });
16966
19463
  });
16967
19464
  async function tryExec(exec, args, cwd) {
@@ -17062,7 +19559,7 @@ function manifest(artifact) {
17062
19559
  modules: [artifact.entrypoint, ...artifact.modules].map(meta3),
17063
19560
  assets: artifact.assets.map((file2) => ({
17064
19561
  ...meta3(file2),
17065
- hash: hashAsset(file2.data)
19562
+ hash: hashAsset(file2.data, file2.contentType)
17066
19563
  })),
17067
19564
  compatibilityDate: artifact.compatibilityDate,
17068
19565
  compatibilityFlags: artifact.compatibilityFlags,
@@ -17093,8 +19590,8 @@ function parseDotenv2(text) {
17093
19590
  function readProjectEnv2(dir) {
17094
19591
  return readProjectEnv(dir);
17095
19592
  }
17096
- function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
17097
- const post = (body) => fetchImpl(`${cp2}/api/internal/deployments/${deploymentId}/events`, {
19593
+ function eventsObserver(cp3, deploymentId, token, fetchImpl = fetch, logIngestUrl) {
19594
+ const post = (body) => fetchImpl(`${cp3}/api/internal/deployments/${deploymentId}/events`, {
17098
19595
  method: "POST",
17099
19596
  headers: {
17100
19597
  "Content-Type": "application/json",
@@ -17111,18 +19608,48 @@ function eventsObserver(cp2, deploymentId, token, fetchImpl = fetch) {
17111
19608
  } catch (error51) {
17112
19609
  lastError = error51;
17113
19610
  await new Promise(
17114
- (resolve5) => setTimeout(resolve5, 500 * (attempt + 1))
19611
+ (resolve10) => setTimeout(resolve10, 500 * (attempt + 1))
17115
19612
  );
17116
19613
  }
17117
19614
  }
17118
19615
  throw lastError;
17119
19616
  };
19617
+ const send = createLogSender({
19618
+ ...logIngestUrl ? { ingestUrl: logIngestUrl } : {},
19619
+ toIngest: logIngestUrl ? (logs) => fetchImpl(`${logIngestUrl}/deployments/${deploymentId}/logs`, {
19620
+ method: "POST",
19621
+ headers: {
19622
+ "Content-Type": "application/json",
19623
+ Authorization: `Bearer ${token}`
19624
+ },
19625
+ body: JSON.stringify({ logs })
19626
+ }) : null,
19627
+ toControlPlane: (logs) => post({ type: "logs", logs }),
19628
+ // The CLI swallows log failures so a deploy never dies over a log line.
19629
+ warn: (message) => post({
19630
+ type: "log",
19631
+ log: { level: "WARN", stage: "BUILDING", message }
19632
+ }).catch(() => void 0)
19633
+ });
19634
+ const batcher = createLogBatcher({
19635
+ limit: DEPLOYMENT_LOG_BATCH_LIMIT,
19636
+ intervalMs: DEPLOYMENT_LOG_FLUSH_MS,
19637
+ send
19638
+ });
19639
+ const flushLogs = () => batcher.flush().catch(() => {
19640
+ });
17120
19641
  return {
17121
- onStatus: (status) => postStatus({ type: "status", status }),
17122
- onLog: (log2) => post({ type: "log", log: log2 }).catch(() => {
19642
+ onStatus: async (status) => {
19643
+ await flushLogs();
19644
+ await postStatus({ type: "status", status });
19645
+ },
19646
+ onLog: (log2) => batcher.add(log2).catch(() => {
17123
19647
  }),
17124
- onMetadata: (metadata) => post({ type: "metadata", metadata }).catch(() => {
17125
- })
19648
+ onMetadata: async (metadata) => {
19649
+ await flushLogs();
19650
+ await post({ type: "metadata", metadata }).catch(() => {
19651
+ });
19652
+ }
17126
19653
  };
17127
19654
  }
17128
19655
  function slugify2(value) {
@@ -17132,20 +19659,20 @@ function slugify2(value) {
17132
19659
  async function projectName(sourceDir) {
17133
19660
  try {
17134
19661
  const pkg = JSON.parse(
17135
- await readFile7(join15(sourceDir, "package.json"), "utf8")
19662
+ await readFile13(join23(sourceDir, "package.json"), "utf8")
17136
19663
  );
17137
19664
  if (pkg.name) return pkg.name;
17138
19665
  } catch {
17139
19666
  }
17140
- return basename(sourceDir);
19667
+ return basename3(sourceDir);
17141
19668
  }
17142
19669
  var COPY_SKIP = /(^|[\\/])(node_modules|\.next|\.open-next|\.git|\.wrangler|\.turbo|\.naro)([\\/]|$)/;
17143
19670
  function resolveBin(name) {
17144
- let dir = dirname(fileURLToPath(import.meta.url));
19671
+ let dir = dirname4(fileURLToPath(import.meta.url));
17145
19672
  for (; ; ) {
17146
- const candidate = join15(dir, "node_modules", ".bin", name);
17147
- if (existsSync6(candidate)) return candidate;
17148
- const parent = dirname(dir);
19673
+ const candidate = join23(dir, "node_modules", ".bin", name);
19674
+ if (existsSync7(candidate)) return candidate;
19675
+ const parent = dirname4(dir);
17149
19676
  if (parent === dir) return name;
17150
19677
  dir = parent;
17151
19678
  }
@@ -17162,20 +19689,26 @@ async function detectPackageManagerLenient(root) {
17162
19689
  "--no-audit",
17163
19690
  "--no-fund",
17164
19691
  "--legacy-peer-deps"
17165
- ]
19692
+ ],
19693
+ addArgs: (specs) => ["npm", "install", "--no-save", ...specs],
19694
+ cacheArgs: (dir) => ["--cache", dir]
17166
19695
  };
17167
19696
  }
17168
19697
  }
17169
19698
  function localDependencies(sourceDir) {
17170
19699
  return {
17171
- createWorkspace: () => mkdtemp2(join15(tmpdir2(), "naro-build-")),
17172
- removeWorkspace: (path) => rm2(path, { recursive: true, force: true }),
19700
+ createWorkspace: () => mkdtemp2(join23(tmpdir2(), "naro-build-")),
19701
+ removeWorkspace: (path) => rm3(path, { recursive: true, force: true }),
17173
19702
  async runCommand(spec, observer) {
17174
19703
  if (spec.command === "git" && spec.args[0] === "clone") {
17175
19704
  const checkout = spec.args.at(-1);
17176
19705
  if (!checkout) throw new Error("Missing checkout path.");
17177
- await cp(sourceDir, checkout, {
19706
+ await cp2(sourceDir, checkout, {
17178
19707
  recursive: true,
19708
+ // Relative symlinks must stay relative: resolved, they would point
19709
+ // back into the developer's real tree, and a strategy's temporary
19710
+ // rewrite (proxy -> middleware, vinext toolchain) would land there.
19711
+ verbatimSymlinks: true,
17179
19712
  filter: (src) => !COPY_SKIP.test(src)
17180
19713
  });
17181
19714
  return { stdout: [], stderr: [] };
@@ -17196,13 +19729,16 @@ function localDependencies(sourceDir) {
17196
19729
  createPlatformBuildConfig,
17197
19730
  readOpenNextArtifact,
17198
19731
  openNextCommand: resolveBin("opennextjs-cloudflare"),
17199
- wranglerCommand: resolveBin("wrangler")
19732
+ wranglerCommand: resolveBin("wrangler"),
19733
+ // `vinext check` runs on the bare checkout, before the toolchain is added
19734
+ // to the user's tree, so this binary has to come from the CLI's own deps.
19735
+ vinextCommand: resolveBin("vinext")
17200
19736
  };
17201
19737
  }
17202
19738
  async function deployDirectory(sourceDir, options, log2) {
17203
19739
  const cpUrl = controlPlaneUrl();
17204
19740
  const production = options.production ?? false;
17205
- const appRoot = options.rootDirectory ? resolve4(sourceDir, options.rootDirectory) : sourceDir;
19741
+ const appRoot = options.rootDirectory ? resolve9(sourceDir, options.rootDirectory) : sourceDir;
17206
19742
  const framework = detectFramework2(appRoot);
17207
19743
  if (!framework.deployable && !options.force) {
17208
19744
  throw new NaroError(
@@ -17275,7 +19811,8 @@ async function deployDirectory(sourceDir, options, log2) {
17275
19811
  cpUrl,
17276
19812
  ctx.deploymentId,
17277
19813
  ctx.callbackToken,
17278
- options.fetch
19814
+ options.fetch,
19815
+ ctx.logIngestUrl
17279
19816
  );
17280
19817
  let artifact;
17281
19818
  try {
@@ -17286,9 +19823,17 @@ async function deployDirectory(sourceDir, options, log2) {
17286
19823
  repositoryUrl: sourceDir,
17287
19824
  branch: ctx.branch,
17288
19825
  rootDirectory: options.rootDirectory ?? null,
17289
- env: { ...ctx.env, ...envToSend }
19826
+ env: { ...ctx.env, ...envToSend },
19827
+ strategy: ctx.strategy ?? null,
19828
+ ...ctx.vinextEnabled === void 0 ? {} : { vinextEnabled: ctx.vinextEnabled }
17290
19829
  },
17291
- observer
19830
+ observer,
19831
+ createCacheTransport({
19832
+ deploymentId: ctx.deploymentId,
19833
+ controlPlaneUrl: cpUrl,
19834
+ callbackToken: ctx.callbackToken,
19835
+ ...options.fetch ? { fetch: options.fetch } : { fetch }
19836
+ })
17292
19837
  );
17293
19838
  artifact = result.artifact;
17294
19839
  } catch (error51) {
@@ -17360,11 +19905,18 @@ export {
17360
19905
  openBrowser,
17361
19906
  isTerminalStatus,
17362
19907
  NaroClient,
19908
+ renderRows,
19909
+ summarizeStatement,
17363
19910
  serializeDotenv,
19911
+ generateTypes,
17364
19912
  readProjectLink,
17365
19913
  writeProjectLink,
17366
19914
  removeProjectLink,
17367
19915
  ensureGitignoreHasNaro,
19916
+ resolveMigrationsDir,
19917
+ listMigrationFiles,
19918
+ nextMigrationFileName,
19919
+ compareMigrations,
17368
19920
  promptForMissingEnv,
17369
19921
  detectGitInfo,
17370
19922
  parseDotenv2 as parseDotenv,