@spotpatch/next 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.
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import { runSpotPatchBridgeCli } from "@spotpatch/bridge";
9
9
  import { spawn } from "child_process";
10
10
  import { randomBytes } from "crypto";
11
11
  import { serializeResolvedSpotPatchOptions } from "@spotpatch/dev-server";
12
+ import { SPOTPATCH_API_BASE as SPOTPATCH_API_BASE2 } from "@spotpatch/shared";
12
13
 
13
14
  // src/cli-args.ts
14
15
  import { isLoopbackHostname } from "@spotpatch/dev-server";
@@ -289,8 +290,12 @@ import {
289
290
  createSession,
290
291
  createSourceRegistrationService,
291
292
  createSourceRegistry,
292
- createSpotPatchMiddleware
293
+ createSpotPatchMiddleware,
294
+ resolveManagedExecutionValidation
293
295
  } from "@spotpatch/dev-server";
296
+ import {
297
+ createExternalAgentSupervisor
298
+ } from "@spotpatch/bridge";
294
299
  import {
295
300
  SPOTPATCH_API_BASE,
296
301
  SPOTPATCH_ENDPOINTS,
@@ -300,7 +305,7 @@ import {
300
305
  // package.json
301
306
  var package_default = {
302
307
  name: "@spotpatch/next",
303
- version: "0.6.0",
308
+ version: "0.7.0",
304
309
  description: "Development-only Next.js adapter and CLI for SpotPatch.",
305
310
  license: "MIT",
306
311
  repository: {
@@ -516,38 +521,102 @@ function validateCredentialEnvironment(options, credentials) {
516
521
  throw new TypeError("The SpotPatch credential environment is inconsistent.");
517
522
  }
518
523
  }
519
- async function selfCheckSidecar(sidecarOrigin, publicOrigin, expectedConfig) {
520
- const response = await fetch(new URL(SPOTPATCH_ENDPOINTS.bootstrap, sidecarOrigin), {
521
- method: "POST",
522
- headers: {
523
- "Content-Type": "application/json",
524
- Origin: publicOrigin,
525
- "Sec-Fetch-Site": "same-origin"
526
- },
527
- body: "{}",
528
- signal: AbortSignal.timeout(SIDECAR_SELF_CHECK_TIMEOUT_MS)
529
- });
524
+ function diagnosticUrl(value, fallbackOrigin) {
525
+ try {
526
+ const url = new URL(value, fallbackOrigin);
527
+ return `${url.origin}${url.pathname}`;
528
+ } catch {
529
+ return fallbackOrigin;
530
+ }
531
+ }
532
+ async function checkBootstrapRoute(routeOrigin, expectedOrigin, expectedConfig) {
533
+ const requestUrl = new URL(SPOTPATCH_ENDPOINTS.bootstrap, routeOrigin);
534
+ let response;
535
+ try {
536
+ response = await fetch(requestUrl, {
537
+ method: "POST",
538
+ headers: {
539
+ "Content-Type": "application/json",
540
+ Origin: expectedOrigin,
541
+ "Sec-Fetch-Site": "same-origin"
542
+ },
543
+ body: "{}",
544
+ redirect: "manual",
545
+ signal: AbortSignal.timeout(SIDECAR_SELF_CHECK_TIMEOUT_MS)
546
+ });
547
+ } catch {
548
+ return Object.freeze({
549
+ finalUrl: diagnosticUrl(requestUrl.href, routeOrigin),
550
+ kind: "unreachable",
551
+ ok: false
552
+ });
553
+ }
554
+ const redirectLocation = response.headers.get("location");
555
+ const finalUrl = diagnosticUrl(
556
+ redirectLocation === null ? response.url || requestUrl.href : new URL(redirectLocation, response.url || requestUrl.href).href,
557
+ routeOrigin
558
+ );
530
559
  const declaredLength = Number(response.headers.get("content-length"));
531
560
  if (Number.isFinite(declaredLength) && declaredLength > SIDECAR_SELF_CHECK_LIMIT_BYTES) {
532
561
  await response.body?.cancel();
533
- throw new Error("SpotPatch Sidecar self-check failed.");
562
+ return Object.freeze({
563
+ finalUrl,
564
+ kind: "response-invalid",
565
+ ok: false,
566
+ status: response.status
567
+ });
534
568
  }
535
569
  const text = await response.text();
536
- if (!response.ok || !response.headers.get("cache-control")?.toLowerCase().includes("no-store") || Buffer.byteLength(text, "utf8") > SIDECAR_SELF_CHECK_LIMIT_BYTES) {
537
- throw new Error("SpotPatch Sidecar self-check failed.");
570
+ if (!response.ok) {
571
+ return Object.freeze({
572
+ finalUrl,
573
+ kind: "http",
574
+ ok: false,
575
+ status: response.status
576
+ });
577
+ }
578
+ if (!response.headers.get("cache-control")?.toLowerCase().includes("no-store") || Buffer.byteLength(text, "utf8") > SIDECAR_SELF_CHECK_LIMIT_BYTES) {
579
+ return Object.freeze({
580
+ finalUrl,
581
+ kind: "response-invalid",
582
+ ok: false,
583
+ status: response.status
584
+ });
538
585
  }
539
586
  let value;
540
587
  try {
541
588
  value = JSON.parse(text);
542
- } catch (error) {
543
- throw new Error("SpotPatch Sidecar self-check failed.", { cause: error });
589
+ } catch {
590
+ return Object.freeze({
591
+ finalUrl,
592
+ kind: "response-invalid",
593
+ ok: false,
594
+ status: response.status
595
+ });
544
596
  }
545
597
  if (typeof value !== "object" || value === null || Array.isArray(value) || Object.keys(value).length !== 2 || !("ok" in value) || value.ok !== true || !("data" in value)) {
546
- throw new Error("SpotPatch Sidecar self-check failed.");
598
+ return Object.freeze({
599
+ finalUrl,
600
+ kind: "response-invalid",
601
+ ok: false,
602
+ status: response.status
603
+ });
547
604
  }
548
605
  const parsed = runtimeConfigSchema.safeParse(value.data);
549
606
  const parsedExpected = runtimeConfigSchema.safeParse(expectedConfig);
550
607
  if (!parsed.success || !parsedExpected.success || JSON.stringify(parsed.data) !== JSON.stringify(parsedExpected.data)) {
608
+ return Object.freeze({
609
+ finalUrl,
610
+ kind: "response-invalid",
611
+ ok: false,
612
+ status: response.status
613
+ });
614
+ }
615
+ return Object.freeze({ ok: true });
616
+ }
617
+ async function selfCheckSidecar(sidecarOrigin, publicOrigin, expectedConfig) {
618
+ const result = await checkBootstrapRoute(sidecarOrigin, publicOrigin, expectedConfig);
619
+ if (!result.ok) {
551
620
  throw new Error("SpotPatch Sidecar self-check failed.");
552
621
  }
553
622
  }
@@ -568,6 +637,9 @@ async function createNextSidecar(sidecarOptions) {
568
637
  let registry;
569
638
  let agentManager;
570
639
  let externalHandoffService;
640
+ let externalAgentSupervisor;
641
+ let spotPatchMiddleware;
642
+ let publicRouteCheck;
571
643
  let handler = (_request, response) => {
572
644
  writeUnavailable(response, 503);
573
645
  };
@@ -652,9 +724,11 @@ async function createNextSidecar(sidecarOptions) {
652
724
  root: input.appRoot,
653
725
  sessionId: session.id
654
726
  }) : void 0;
727
+ let handoffReady = false;
655
728
  if (handoffService !== void 0) {
656
729
  try {
657
730
  await handoffService.start();
731
+ handoffReady = true;
658
732
  } catch {
659
733
  process.stderr.write(
660
734
  "[spotpatch:next] External Agent handoff is unavailable; core tools remain active.\n"
@@ -662,9 +736,32 @@ async function createNextSidecar(sidecarOptions) {
662
736
  }
663
737
  }
664
738
  externalHandoffService = handoffService;
739
+ let supervisor;
740
+ if (handoffReady) {
741
+ try {
742
+ const validation = await resolveManagedExecutionValidation({
743
+ ai: input.options.ai,
744
+ appRoot: input.appRoot
745
+ });
746
+ supervisor = await createExternalAgentSupervisor({
747
+ bridgeAdapter: "next",
748
+ checks: validation.checks,
749
+ limits: validation.limits,
750
+ root: input.appRoot,
751
+ sessionId: session.id,
752
+ projectLabel: input.appRoot.split(/[\\/]/u).at(-1) ?? "project"
753
+ });
754
+ } catch {
755
+ process.stderr.write(
756
+ "[spotpatch:next] Managed Agent control is unavailable; Inbox remains active.\n"
757
+ );
758
+ }
759
+ }
760
+ externalAgentSupervisor = supervisor;
665
761
  const middleware = createSpotPatchMiddleware({
666
762
  ...manager === void 0 ? {} : { agentManager: manager },
667
763
  ...handoffService === void 0 ? {} : { externalHandoffService: handoffService },
764
+ ...supervisor === void 0 ? {} : { externalAgentControl: supervisor },
668
765
  bootstrap: {
669
766
  expectedOrigin: input.publicOrigin,
670
767
  runtimeConfig
@@ -689,6 +786,7 @@ async function createNextSidecar(sidecarOptions) {
689
786
  });
690
787
  registry = sourceRegistry;
691
788
  agentManager = manager;
789
+ spotPatchMiddleware = middleware;
692
790
  handler = (request, response) => {
693
791
  if (requestPath(request.url) === NEXT_INTERNAL_REGISTRATION_PATH) {
694
792
  registration.handler(request, response);
@@ -699,8 +797,26 @@ async function createNextSidecar(sidecarOptions) {
699
797
  });
700
798
  };
701
799
  await selfCheckSidecar(sidecarOrigin, input.publicOrigin, runtimeConfig);
800
+ publicRouteCheck = Object.freeze({
801
+ expectedConfig: runtimeConfig,
802
+ publicOrigin: input.publicOrigin
803
+ });
702
804
  active = true;
703
805
  },
806
+ async checkPublicRoute() {
807
+ if (closed || !active || publicRouteCheck === void 0) {
808
+ return Object.freeze({
809
+ finalUrl: sidecarOrigin,
810
+ kind: "unreachable",
811
+ ok: false
812
+ });
813
+ }
814
+ return await checkBootstrapRoute(
815
+ publicRouteCheck.publicOrigin,
816
+ publicRouteCheck.publicOrigin,
817
+ publicRouteCheck.expectedConfig
818
+ );
819
+ },
704
820
  async close() {
705
821
  if (closed) {
706
822
  return;
@@ -710,6 +826,10 @@ async function createNextSidecar(sidecarOptions) {
710
826
  writeUnavailable(response, 503);
711
827
  };
712
828
  registry?.clear();
829
+ spotPatchMiddleware?.dispose();
830
+ spotPatchMiddleware = void 0;
831
+ await externalAgentSupervisor?.dispose();
832
+ externalAgentSupervisor = void 0;
713
833
  await externalHandoffService?.close();
714
834
  externalHandoffService = void 0;
715
835
  await agentManager?.close();
@@ -724,6 +844,8 @@ async function createNextSidecar(sidecarOptions) {
724
844
  // src/cli-owner.ts
725
845
  var CONFIGURATION_STARTUP_TIMEOUT_MS = 6e4;
726
846
  var FORCED_TERMINATION_TIMEOUT_MS = 5e3;
847
+ var PUBLIC_ROUTE_CANARY_RETRY_MS = 250;
848
+ var PUBLIC_ROUTE_CANARY_TIMEOUT_MS = 6e4;
727
849
  var MAX_CONFIGURATION_REQUESTS = 32;
728
850
  var ID_PATTERN2 = /^[A-Za-z0-9_-]{16,128}$/u;
729
851
  function isRecord2(value) {
@@ -782,6 +904,51 @@ function waitForChild(child) {
782
904
  });
783
905
  });
784
906
  }
907
+ function waitForDelay(milliseconds, signal) {
908
+ return new Promise((resolve, reject) => {
909
+ if (signal.aborted) {
910
+ reject(
911
+ signal.reason instanceof Error ? signal.reason : new Error("SpotPatch public-route wait was aborted.")
912
+ );
913
+ return;
914
+ }
915
+ const timer = setTimeout(resolve, milliseconds);
916
+ timer.unref();
917
+ signal.addEventListener(
918
+ "abort",
919
+ () => {
920
+ clearTimeout(timer);
921
+ reject(
922
+ signal.reason instanceof Error ? signal.reason : new Error("SpotPatch public-route wait was aborted.")
923
+ );
924
+ },
925
+ { once: true }
926
+ );
927
+ });
928
+ }
929
+ function publicRouteCanaryError(result) {
930
+ if (result.ok) {
931
+ throw new TypeError("A successful public route canary has no error.");
932
+ }
933
+ const status = result.status === void 0 ? "unavailable" : String(result.status);
934
+ return new Error(
935
+ `SpotPatch public-route canary failed at ${result.finalUrl} (stage=${result.kind}, status=${status}). Check Proxy/Middleware matcher exclusions for ${SPOTPATCH_API_BASE2}.`
936
+ );
937
+ }
938
+ async function waitForPublicRoute(sidecar, signal) {
939
+ const deadline = Date.now() + PUBLIC_ROUTE_CANARY_TIMEOUT_MS;
940
+ while (!signal.aborted) {
941
+ const result = await sidecar.checkPublicRoute();
942
+ if (result.ok) {
943
+ return;
944
+ }
945
+ if (result.kind !== "unreachable" || Date.now() >= deadline) {
946
+ throw publicRouteCanaryError(result);
947
+ }
948
+ await waitForDelay(PUBLIC_ROUTE_CANARY_RETRY_MS, signal);
949
+ }
950
+ throw signal.reason;
951
+ }
785
952
  async function closeSidecar(sidecar) {
786
953
  let timer;
787
954
  try {
@@ -820,6 +987,8 @@ async function runNextDevelopment(arguments_) {
820
987
  let configured = false;
821
988
  let failureCode;
822
989
  let configurationQueue = Promise.resolve();
990
+ const publicRouteCanaryAbort = new AbortController();
991
+ let publicRouteCanary;
823
992
  const handleConfiguration = async (value) => {
824
993
  const correlation = readCorrelation(value, launchNonce);
825
994
  let message;
@@ -881,10 +1050,30 @@ async function runNextDevelopment(arguments_) {
881
1050
  if (!configured) {
882
1051
  configured = true;
883
1052
  clearTimeout(startupTimer);
884
- process.stdout.write(
885
- `[spotpatch:next] ready for Next.js ${project.nextVersion} (${dev.bundler}) at ${dev.publicOrigin}
1053
+ if (message.options.enabled) {
1054
+ publicRouteCanary = waitForPublicRoute(
1055
+ sidecar,
1056
+ publicRouteCanaryAbort.signal
1057
+ ).then(
1058
+ () => {
1059
+ process.stdout.write(
1060
+ `[spotpatch:next] ready for Next.js ${project.nextVersion} (${dev.bundler}) at ${dev.publicOrigin}
886
1061
  `
887
- );
1062
+ );
1063
+ },
1064
+ (error) => {
1065
+ if (publicRouteCanaryAbort.signal.aborted) {
1066
+ return;
1067
+ }
1068
+ failureCode = "PUBLIC_ROUTE_CANARY_FAILED";
1069
+ process.stderr.write(
1070
+ `[spotpatch:next] ${error instanceof Error ? error.message : "SpotPatch public-route canary failed."}
1071
+ `
1072
+ );
1073
+ lifecycle.child?.kill("SIGTERM");
1074
+ }
1075
+ );
1076
+ }
888
1077
  }
889
1078
  return createAck(message, { ok: true });
890
1079
  };
@@ -961,6 +1150,7 @@ async function runNextDevelopment(arguments_) {
961
1150
  process.once("SIGINT", onSigint);
962
1151
  process.once("SIGTERM", onSigterm);
963
1152
  const result = await waitForChild(child);
1153
+ publicRouteCanaryAbort.abort();
964
1154
  clearTimeout(startupTimer);
965
1155
  process.off("SIGINT", onSigint);
966
1156
  process.off("SIGTERM", onSigterm);
@@ -968,6 +1158,7 @@ async function runNextDevelopment(arguments_) {
968
1158
  clearTimeout(forceTimer);
969
1159
  }
970
1160
  await configurationQueue;
1161
+ await publicRouteCanary;
971
1162
  try {
972
1163
  await closeSidecar(sidecar);
973
1164
  } catch {
@@ -997,7 +1188,7 @@ import {
997
1188
  integrationPathExists,
998
1189
  readIntegrationFile
999
1190
  } from "@spotpatch/dev-server";
1000
- import { DEFAULT_AGENT_LIMITS } from "@spotpatch/shared";
1191
+ import { DEFAULT_AGENT_LIMITS, SPOTPATCH_API_BASE as SPOTPATCH_API_BASE3 } from "@spotpatch/shared";
1001
1192
  import { MagicString } from "magic-string";
1002
1193
  import {
1003
1194
  parseSync,
@@ -1023,6 +1214,8 @@ var INSTRUMENTATION_EXTENSIONS = Object.freeze([
1023
1214
  ".cts",
1024
1215
  ".cjs"
1025
1216
  ]);
1217
+ var PROXY_FILE_BASE_NAMES = Object.freeze(["proxy", "middleware"]);
1218
+ var SPOTPATCH_MATCHER_EXCLUSION = "__spotpatch(?:/|$)";
1026
1219
  var SIMPLE_SCRIPT_ARGUMENT_PATTERN = /^[A-Za-z0-9._:/=@%+,-]+$/u;
1027
1220
  function isParserErrorSeverity(value) {
1028
1221
  return value === "Error";
@@ -1030,6 +1223,10 @@ function isParserErrorSeverity(value) {
1030
1223
  function isRecord3(value) {
1031
1224
  return typeof value === "object" && value !== null && !Array.isArray(value);
1032
1225
  }
1226
+ function staticStringLiteral(value) {
1227
+ if (!isRecord3(value) || value.type !== "Literal") return void 0;
1228
+ return typeof value.value === "string" ? value.value : void 0;
1229
+ }
1033
1230
  function parseModule(absolutePath, source) {
1034
1231
  const result = parseSync(absolutePath, source, {
1035
1232
  sourceType: "module",
@@ -1148,21 +1345,102 @@ function staticPropertyName(property) {
1148
1345
  if (property.key.type === "Identifier") return property.key.name;
1149
1346
  return property.key.type === "Literal" && typeof property.key.value === "string" ? property.key.value : void 0;
1150
1347
  }
1151
- function assertSupportedDataFlowOption(factory) {
1152
- const argument = factory.arguments[0];
1153
- if (argument?.type !== "ObjectExpression") return;
1154
- const properties = argument.properties.filter(
1155
- (property2) => property2.type === "Property" && staticPropertyName(property2) === "dataFlow"
1348
+ function unwrapExpression(expression) {
1349
+ let current = expression;
1350
+ while (current.type === "ParenthesizedExpression" || current.type === "TSAsExpression" || current.type === "TSSatisfiesExpression" || current.type === "TSTypeAssertion" || current.type === "TSNonNullExpression") {
1351
+ current = current.expression;
1352
+ }
1353
+ return current;
1354
+ }
1355
+ function findObjectProperty(object, name) {
1356
+ const matches = object.properties.filter(
1357
+ (property) => property.type === "Property" && staticPropertyName(property) === name
1156
1358
  );
1157
- if (properties.length > 1) {
1158
- throw new Error("SpotPatch init found duplicate dataFlow options.");
1359
+ if (matches.length > 1) {
1360
+ throw new Error(`SpotPatch init found duplicate ${name} options.`);
1361
+ }
1362
+ return matches[0];
1363
+ }
1364
+ function lineIndentAt(source, offset) {
1365
+ const lineStart = source.lastIndexOf("\n", Math.max(0, offset - 1)) + 1;
1366
+ return /^[\t ]*/u.exec(source.slice(lineStart, offset))?.[0] ?? "";
1367
+ }
1368
+ function childIndent(source, object) {
1369
+ const first = object.properties[0];
1370
+ return first === void 0 ? `${lineIndentAt(source, object.start)} ` : lineIndentAt(source, first.start);
1371
+ }
1372
+ function initializedNextOptions(trustedFastModeAvailable) {
1373
+ return `{ externalAgent: true${trustedFastModeAvailable ? ", trustedFastMode: true" : ""} }`;
1374
+ }
1375
+ function enableNextOptions(magicString, source, factory, trustedFastModeAvailable) {
1376
+ const argument = factory.arguments[0];
1377
+ if (factory.arguments.length === 0) {
1378
+ magicString.appendLeft(
1379
+ factory.end - 1,
1380
+ initializedNextOptions(trustedFastModeAvailable)
1381
+ );
1382
+ return;
1383
+ }
1384
+ if (factory.arguments.length !== 1 || argument === void 0 || argument.type === "SpreadElement") {
1385
+ throw new Error("SpotPatch init cannot safely update withSpotPatch options.");
1386
+ }
1387
+ const options = unwrapExpression(argument);
1388
+ if (options.type !== "ObjectExpression") {
1389
+ throw new Error("SpotPatch init requires withSpotPatch options to be an object.");
1390
+ }
1391
+ if (options.properties.some((property) => property.type === "SpreadElement")) {
1392
+ throw new Error(
1393
+ "SpotPatch init cannot prove externalAgent through spread withSpotPatch options."
1394
+ );
1159
1395
  }
1160
- const property = properties[0];
1161
- if (property !== void 0 && !(property.value.type === "Literal" && property.value.value === false)) {
1396
+ const dataFlowProperty = findObjectProperty(options, "dataFlow");
1397
+ const dataFlowValue = dataFlowProperty === void 0 ? void 0 : unwrapExpression(dataFlowProperty.value);
1398
+ if (dataFlowValue !== void 0 && !(dataFlowValue.type === "Literal" && dataFlowValue.value === false)) {
1162
1399
  throw new Error(
1163
1400
  "SpotPatch Next does not support component dataFlow yet; remove dataFlow or set it to false."
1164
1401
  );
1165
1402
  }
1403
+ const missing = [];
1404
+ const externalAgentProperty = findObjectProperty(options, "externalAgent");
1405
+ if (externalAgentProperty === void 0) {
1406
+ missing.push("externalAgent: true");
1407
+ } else {
1408
+ const value = unwrapExpression(externalAgentProperty.value);
1409
+ if (value.type !== "Literal" || typeof value.value !== "boolean") {
1410
+ throw new Error("SpotPatch init requires externalAgent to be a boolean literal.");
1411
+ }
1412
+ if (!value.value) {
1413
+ magicString.overwrite(value.start, value.end, "true");
1414
+ }
1415
+ }
1416
+ const trustedFastModeProperty = findObjectProperty(options, "trustedFastMode");
1417
+ if (trustedFastModeAvailable && trustedFastModeProperty === void 0) {
1418
+ missing.push("trustedFastMode: true");
1419
+ } else if (trustedFastModeProperty !== void 0) {
1420
+ const value = unwrapExpression(trustedFastModeProperty.value);
1421
+ if (value.type !== "Literal" || typeof value.value !== "boolean") {
1422
+ throw new Error(
1423
+ "SpotPatch init requires trustedFastMode to be a boolean literal."
1424
+ );
1425
+ }
1426
+ if (trustedFastModeAvailable && !value.value) {
1427
+ magicString.overwrite(value.start, value.end, "true");
1428
+ }
1429
+ }
1430
+ if (missing.length === 0) {
1431
+ return;
1432
+ }
1433
+ const indent = childIndent(source, options);
1434
+ if (options.properties.length === 0) {
1435
+ magicString.appendLeft(options.end - 1, ` ${missing.join(", ")} `);
1436
+ } else {
1437
+ magicString.appendLeft(
1438
+ options.properties[0]?.start ?? options.end - 1,
1439
+ `${missing.join(`,
1440
+ ${indent}`)},
1441
+ ${indent}`
1442
+ );
1443
+ }
1166
1444
  }
1167
1445
  function transformNextConfig(absolutePath, source, trustedFastModeAvailable) {
1168
1446
  if (absolutePath.endsWith(".cjs") || absolutePath.endsWith(".cts")) {
@@ -1182,16 +1460,8 @@ function transformNextConfig(absolutePath, source, trustedFastModeAvailable) {
1182
1460
  const wrapperName = existingWrapperName ?? chooseWrapperName(program);
1183
1461
  const existingFactory = existingWrapperName === void 0 ? void 0 : wrappedFactoryCall(defaultExport.declaration, existingWrapperName);
1184
1462
  if (existingFactory !== void 0) {
1185
- assertSupportedDataFlowOption(existingFactory);
1186
- if (trustedFastModeAvailable && existingFactory.arguments.length === 0) {
1187
- magicString.overwrite(
1188
- existingFactory.start,
1189
- existingFactory.end,
1190
- `${wrapperName}({ trustedFastMode: true })`
1191
- );
1192
- return magicString.toString();
1193
- }
1194
- return source;
1463
+ enableNextOptions(magicString, source, existingFactory, trustedFastModeAvailable);
1464
+ return magicString.toString();
1195
1465
  }
1196
1466
  if (existingWrapperName === void 0) {
1197
1467
  const specifier = wrapperName === "withSpotPatch" ? "withSpotPatch" : `withSpotPatch as ${wrapperName}`;
@@ -1208,7 +1478,7 @@ function transformNextConfig(absolutePath, source, trustedFastModeAvailable) {
1208
1478
  magicString.overwrite(
1209
1479
  defaultExport.declaration.start,
1210
1480
  defaultExport.declaration.end,
1211
- `${wrapperName}(${trustedFastModeAvailable ? "{ trustedFastMode: true }" : ""})(${expression})`
1481
+ `${wrapperName}(${initializedNextOptions(trustedFastModeAvailable)})(${expression})`
1212
1482
  );
1213
1483
  return magicString.toString();
1214
1484
  }
@@ -1233,6 +1503,230 @@ function transformInstrumentationClient(absolutePath, source) {
1233
1503
  );
1234
1504
  return magicString.toString();
1235
1505
  }
1506
+ function findVariableInitializer(program, name) {
1507
+ for (const statement of program.body) {
1508
+ if (statement.type !== "VariableDeclaration") {
1509
+ continue;
1510
+ }
1511
+ for (const declaration of statement.declarations) {
1512
+ if (declaration.id.type === "Identifier" && declaration.id.name === name && declaration.init !== null) {
1513
+ return declaration.init;
1514
+ }
1515
+ }
1516
+ }
1517
+ return void 0;
1518
+ }
1519
+ function resolveStaticNextConfigObject(program) {
1520
+ const exported = findDefaultExport(program).declaration;
1521
+ if (exported.type === "FunctionDeclaration" || exported.type === "TSDeclareFunction" || exported.type === "ClassDeclaration" || exported.type === "TSInterfaceDeclaration") {
1522
+ return void 0;
1523
+ }
1524
+ let expression = unwrapExpression(exported);
1525
+ if (expression.type === "Identifier") {
1526
+ const initializer = findVariableInitializer(program, expression.name);
1527
+ expression = initializer === void 0 ? expression : unwrapExpression(initializer);
1528
+ }
1529
+ if (expression.type === "CallExpression") {
1530
+ const factory = unwrapExpression(expression.callee);
1531
+ const hostArgument = expression.arguments[0];
1532
+ if (factory.type === "CallExpression" && hostArgument !== void 0 && hostArgument.type !== "SpreadElement") {
1533
+ expression = unwrapExpression(hostArgument);
1534
+ if (expression.type === "Identifier") {
1535
+ const initializer = findVariableInitializer(program, expression.name);
1536
+ expression = initializer === void 0 ? expression : unwrapExpression(initializer);
1537
+ }
1538
+ }
1539
+ }
1540
+ return expression.type === "ObjectExpression" ? expression : void 0;
1541
+ }
1542
+ function proxyFileExtensions(program) {
1543
+ const extensions = new Set(
1544
+ INSTRUMENTATION_EXTENSIONS.map((extension) => extension.slice(1))
1545
+ );
1546
+ const config = resolveStaticNextConfigObject(program);
1547
+ const pageExtensions = config === void 0 ? void 0 : findObjectProperty(config, "pageExtensions");
1548
+ if (pageExtensions === void 0) {
1549
+ return Object.freeze([...extensions]);
1550
+ }
1551
+ const value = unwrapExpression(pageExtensions.value);
1552
+ if (value.type !== "ArrayExpression") {
1553
+ throw new Error(
1554
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: pageExtensions must be a static string array so SpotPatch can inspect Proxy/Middleware files."
1555
+ );
1556
+ }
1557
+ for (const element of value.elements) {
1558
+ const extension = staticStringLiteral(element);
1559
+ if (typeof extension !== "string" || extension.length === 0 || extension.includes("/") || extension.includes("\\")) {
1560
+ throw new Error(
1561
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: pageExtensions must contain only static filename extensions."
1562
+ );
1563
+ }
1564
+ extensions.add(extension.replace(/^\.+/u, ""));
1565
+ }
1566
+ return Object.freeze([...extensions]);
1567
+ }
1568
+ function rewriteMatcherSource(value) {
1569
+ if (value.includes(SPOTPATCH_MATCHER_EXCLUSION)) {
1570
+ return value;
1571
+ }
1572
+ if (value === SPOTPATCH_API_BASE3 || value.startsWith(`${SPOTPATCH_API_BASE3}/`)) {
1573
+ throw new Error(
1574
+ `SPOTPATCH_PROXY_MATCHER_UNSAFE: Proxy/Middleware explicitly claims ${SPOTPATCH_API_BASE3}.`
1575
+ );
1576
+ }
1577
+ if (value.startsWith("/((?!")) {
1578
+ return `/((?!${SPOTPATCH_MATCHER_EXCLUSION}|${value.slice(5)}`;
1579
+ }
1580
+ if (value === "/:path*" || value === "/:path(.*)" || value === "/(.*)" || value === "/:slug*") {
1581
+ return `/((?!${SPOTPATCH_MATCHER_EXCLUSION}).*)`;
1582
+ }
1583
+ if (value.startsWith("/:") || value.startsWith("/(")) {
1584
+ throw new Error(
1585
+ `SPOTPATCH_PROXY_MATCHER_UNSAFE: cannot safely exclude ${SPOTPATCH_API_BASE3} from matcher ${JSON.stringify(value)}.`
1586
+ );
1587
+ }
1588
+ return value;
1589
+ }
1590
+ function rewriteMatcherLiteral(magicString, expression) {
1591
+ const value = unwrapExpression(expression);
1592
+ if (value.type !== "Literal" || typeof value.value !== "string") {
1593
+ throw new Error(
1594
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: matcher entries must use static string sources."
1595
+ );
1596
+ }
1597
+ const rewritten = rewriteMatcherSource(value.value);
1598
+ if (rewritten !== value.value) {
1599
+ magicString.overwrite(value.start, value.end, JSON.stringify(rewritten));
1600
+ }
1601
+ }
1602
+ function rewriteMatcherEntry(magicString, expression) {
1603
+ const value = unwrapExpression(expression);
1604
+ if (value.type !== "ObjectExpression") {
1605
+ rewriteMatcherLiteral(magicString, value);
1606
+ return;
1607
+ }
1608
+ if (value.properties.some((property) => property.type === "SpreadElement")) {
1609
+ throw new Error(
1610
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: matcher objects cannot contain spreads."
1611
+ );
1612
+ }
1613
+ const source = findObjectProperty(value, "source");
1614
+ if (source === void 0) {
1615
+ throw new Error(
1616
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: matcher objects require a static source."
1617
+ );
1618
+ }
1619
+ rewriteMatcherLiteral(magicString, source.value);
1620
+ }
1621
+ function transformProxyModule(absolutePath, source) {
1622
+ if (absolutePath.endsWith(".cjs") || absolutePath.endsWith(".cts")) {
1623
+ throw new Error(
1624
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: CommonJS Proxy/Middleware requires a manual __spotpatch matcher exclusion."
1625
+ );
1626
+ }
1627
+ const { program } = parseModule(absolutePath, source);
1628
+ const configDeclarations = [];
1629
+ let unsupportedConfigExport = false;
1630
+ for (const statement of program.body) {
1631
+ if (statement.type !== "ExportNamedDeclaration") {
1632
+ continue;
1633
+ }
1634
+ const declaration = statement.declaration;
1635
+ if (declaration?.type === "VariableDeclaration") {
1636
+ for (const declarator of declaration.declarations) {
1637
+ if (declarator.id.type !== "Identifier" || declarator.id.name !== "config") {
1638
+ continue;
1639
+ }
1640
+ if (declarator.init === null) {
1641
+ unsupportedConfigExport = true;
1642
+ continue;
1643
+ }
1644
+ const value2 = unwrapExpression(declarator.init);
1645
+ if (value2.type === "ObjectExpression") {
1646
+ configDeclarations.push(value2);
1647
+ } else {
1648
+ unsupportedConfigExport = true;
1649
+ }
1650
+ }
1651
+ }
1652
+ if (statement.specifiers.some(
1653
+ (specifier) => specifier.exported.type === "Identifier" && specifier.exported.name === "config"
1654
+ )) {
1655
+ unsupportedConfigExport = true;
1656
+ }
1657
+ }
1658
+ if (unsupportedConfigExport || configDeclarations.length > 1) {
1659
+ throw new Error(
1660
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: export const config must be one static object."
1661
+ );
1662
+ }
1663
+ const magicString = new MagicString(source);
1664
+ const config = configDeclarations[0];
1665
+ if (config === void 0) {
1666
+ if (collectIdentifierNames(program).has("config")) {
1667
+ throw new Error(
1668
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: a local config binding prevents a safe matcher export."
1669
+ );
1670
+ }
1671
+ magicString.append(
1672
+ `${source.endsWith("\n") || source.length === 0 ? "" : "\n"}
1673
+ export const config = { matcher: [${JSON.stringify(`/((?!${SPOTPATCH_MATCHER_EXCLUSION}).*)`)}] };
1674
+ `
1675
+ );
1676
+ return magicString.toString();
1677
+ }
1678
+ const matcher = findObjectProperty(config, "matcher");
1679
+ if (matcher === void 0) {
1680
+ const indent = childIndent(source, config);
1681
+ const property = `matcher: [${JSON.stringify(`/((?!${SPOTPATCH_MATCHER_EXCLUSION}).*)`)}]`;
1682
+ if (config.properties.length === 0) {
1683
+ magicString.appendLeft(config.end - 1, `
1684
+ ${indent}${property},
1685
+ `);
1686
+ } else {
1687
+ magicString.appendLeft(
1688
+ config.properties[0]?.start ?? config.end - 1,
1689
+ `${property},
1690
+ ${indent}`
1691
+ );
1692
+ }
1693
+ return magicString.toString();
1694
+ }
1695
+ const value = unwrapExpression(matcher.value);
1696
+ if (value.type === "ArrayExpression") {
1697
+ for (const element of value.elements) {
1698
+ if (element === null || element.type === "SpreadElement") {
1699
+ throw new Error(
1700
+ "SPOTPATCH_PROXY_MATCHER_UNSAFE: matcher arrays cannot contain holes or spreads."
1701
+ );
1702
+ }
1703
+ rewriteMatcherEntry(magicString, element);
1704
+ }
1705
+ } else {
1706
+ rewriteMatcherEntry(magicString, value);
1707
+ }
1708
+ return magicString.toString();
1709
+ }
1710
+ async function findProxyModule(appRoot, configPath, configSource) {
1711
+ const { program } = parseModule(configPath, configSource);
1712
+ const extensions = proxyFileExtensions(program);
1713
+ const candidates = (await Promise.all(
1714
+ [appRoot, path3.join(appRoot, "src")].flatMap(
1715
+ (directory) => PROXY_FILE_BASE_NAMES.flatMap(
1716
+ (baseName) => extensions.map(async (extension) => {
1717
+ const candidate = path3.join(directory, `${baseName}.${extension}`);
1718
+ return await integrationPathExists(candidate) ? candidate : void 0;
1719
+ })
1720
+ )
1721
+ )
1722
+ )).filter((value) => value !== void 0);
1723
+ if (candidates.length > 1) {
1724
+ throw new Error(
1725
+ `SPOTPATCH_PROXY_MATCHER_UNSAFE: found multiple Proxy/Middleware files (${candidates.map((candidate) => path3.relative(appRoot, candidate)).join(", ")}).`
1726
+ );
1727
+ }
1728
+ return candidates[0];
1729
+ }
1236
1730
  function parsePackageManifest(source) {
1237
1731
  let value;
1238
1732
  try {
@@ -1363,7 +1857,9 @@ async function planNextIntegration(directory = process.cwd()) {
1363
1857
  ]);
1364
1858
  const trustedFastModeAvailable = discoveredCheck !== void 0;
1365
1859
  const instrumentationPath = await resolveInstrumentationPath(appRoot, configPath);
1860
+ const proxyPath = await findProxyModule(appRoot, configPath, configSource);
1366
1861
  const instrumentationSource = await integrationPathExists(instrumentationPath) ? await readIntegrationFile(instrumentationPath) : void 0;
1862
+ const proxySource = proxyPath === void 0 ? void 0 : await readIntegrationFile(proxyPath);
1367
1863
  const changes = [
1368
1864
  createIntegrationFileChange(
1369
1865
  appRoot,
@@ -1382,7 +1878,15 @@ async function planNextIntegration(directory = process.cwd()) {
1382
1878
  packagePath,
1383
1879
  transformPackageJson(packageSource),
1384
1880
  packageSource
1385
- )
1881
+ ),
1882
+ ...proxyPath === void 0 || proxySource === void 0 ? [] : [
1883
+ createIntegrationFileChange(
1884
+ appRoot,
1885
+ proxyPath,
1886
+ transformProxyModule(proxyPath, proxySource),
1887
+ proxySource
1888
+ )
1889
+ ]
1386
1890
  ].filter((change) => change !== void 0);
1387
1891
  return Object.freeze({
1388
1892
  appRoot,