@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 +548 -44
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +15 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +15 -4
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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.
|
|
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
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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
|
-
|
|
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
|
|
537
|
-
|
|
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
|
|
543
|
-
|
|
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
|
-
|
|
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
|
-
|
|
885
|
-
|
|
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
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
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 (
|
|
1158
|
-
throw new Error(
|
|
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
|
|
1161
|
-
|
|
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
|
-
|
|
1186
|
-
|
|
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
|
|
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,
|