@superblocksteam/sdk 2.0.110 → 2.0.111
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/.turbo/turbo-build.log +1 -1
- package/dist/cli-replacement/automatic-upgrades.d.ts +1 -1
- package/dist/cli-replacement/automatic-upgrades.d.ts.map +1 -1
- package/dist/cli-replacement/automatic-upgrades.js +4 -2
- package/dist/cli-replacement/automatic-upgrades.js.map +1 -1
- package/dist/cli-replacement/automatic-upgrades.test.d.ts +11 -0
- package/dist/cli-replacement/automatic-upgrades.test.d.ts.map +1 -0
- package/dist/cli-replacement/automatic-upgrades.test.js +175 -0
- package/dist/cli-replacement/automatic-upgrades.test.js.map +1 -0
- package/dist/cli-replacement/dev.d.mts +12 -1
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.mjs +21 -3
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/dist/dev-utils/dev-server.d.mts +9 -1
- package/dist/dev-utils/dev-server.d.mts.map +1 -1
- package/dist/dev-utils/dev-server.mjs +287 -75
- package/dist/dev-utils/dev-server.mjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/cli-replacement/automatic-upgrades.test.ts +251 -0
- package/src/cli-replacement/automatic-upgrades.ts +6 -1
- package/src/cli-replacement/dev.mts +27 -2
- package/src/dev-utils/dev-server.mts +358 -81
- package/src/index.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as child_process from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
1
3
|
import type http from "node:http";
|
|
2
4
|
import net from "node:net";
|
|
3
5
|
import os from "node:os";
|
|
@@ -8,7 +10,7 @@ import cors from "cors";
|
|
|
8
10
|
import express from "express";
|
|
9
11
|
import type { IRouter, RequestHandler } from "express";
|
|
10
12
|
import { createLogger, createServer, loadEnv } from "vite";
|
|
11
|
-
import type { HmrOptions } from "vite";
|
|
13
|
+
import type { HmrOptions, Plugin, UserConfig } from "vite";
|
|
12
14
|
import type { ViteDevServer } from "vite";
|
|
13
15
|
import tsconfigPaths from "vite-tsconfig-paths";
|
|
14
16
|
|
|
@@ -51,6 +53,72 @@ import { ddRumPlugin } from "./vite-plugin-dd-rum.mjs";
|
|
|
51
53
|
|
|
52
54
|
const tracer = getTracer();
|
|
53
55
|
|
|
56
|
+
function reactPlugin() {
|
|
57
|
+
return react({
|
|
58
|
+
babel: {
|
|
59
|
+
plugins: [
|
|
60
|
+
[
|
|
61
|
+
import.meta.resolve("@babel/plugin-proposal-decorators"),
|
|
62
|
+
{ version: "2023-11" },
|
|
63
|
+
],
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Shared `optimizeDeps` config for both pre-warm and activation Vite servers.
|
|
71
|
+
*
|
|
72
|
+
* Vite includes the stringified `optimizeDeps` config in its dep cache hash,
|
|
73
|
+
* so both servers must use identical config or activation will discard the
|
|
74
|
+
* pre-warmed cache and re-run full dependency optimization.
|
|
75
|
+
*/
|
|
76
|
+
const OPTIMIZE_DEPS_CONFIG: UserConfig["optimizeDeps"] = {
|
|
77
|
+
esbuildOptions: {
|
|
78
|
+
supported: {
|
|
79
|
+
destructuring: true,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Builds the Vite plugin list shared by both pre-warm and activation.
|
|
86
|
+
*
|
|
87
|
+
* Vite hashes plugin names to validate the dep optimization cache, so
|
|
88
|
+
* both paths must produce the same list. Call `fileSyncVitePlugin()`
|
|
89
|
+
* with no args for the noop version during pre-warm.
|
|
90
|
+
*/
|
|
91
|
+
function vitePlugins(
|
|
92
|
+
root: string,
|
|
93
|
+
opts: {
|
|
94
|
+
fileSync: Plugin[];
|
|
95
|
+
ddRum?: {
|
|
96
|
+
clientToken: string;
|
|
97
|
+
applicationId: string;
|
|
98
|
+
env: string;
|
|
99
|
+
version: string;
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
) {
|
|
103
|
+
return [
|
|
104
|
+
tsconfigPaths(),
|
|
105
|
+
sdkApiEntryPointPlugin(root),
|
|
106
|
+
customComponentsPlugin(),
|
|
107
|
+
opts.fileSync,
|
|
108
|
+
buildManifestStubPlugin(),
|
|
109
|
+
reactPlugin(),
|
|
110
|
+
createLucideReactImportOptimizer(),
|
|
111
|
+
ddRumPlugin(
|
|
112
|
+
opts.ddRum ?? {
|
|
113
|
+
clientToken: "",
|
|
114
|
+
applicationId: "",
|
|
115
|
+
env: "",
|
|
116
|
+
version: "",
|
|
117
|
+
},
|
|
118
|
+
),
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
|
|
54
122
|
export const RESTART_EXIT_CODE = 98;
|
|
55
123
|
|
|
56
124
|
function getJwksUriWithBaseUrl(superblocksBaseUrl?: string): string {
|
|
@@ -112,6 +180,13 @@ interface CreateDevServerOptions {
|
|
|
112
180
|
sdk?: SuperblocksSdk;
|
|
113
181
|
/** Explicitly provided base URL from the CLI; preferred over auth-file reads. */
|
|
114
182
|
superblocksBaseUrl?: string;
|
|
183
|
+
/**
|
|
184
|
+
* Pre-existing HTTP server from warm standby mode. When provided, the dev
|
|
185
|
+
* server attaches its Express app as a request handler on this server instead
|
|
186
|
+
* of creating a new one and calling listen(). This avoids the port gap that
|
|
187
|
+
* causes 502s from the gateway during the warm -> full server transition.
|
|
188
|
+
*/
|
|
189
|
+
existingServer?: http.Server;
|
|
115
190
|
}
|
|
116
191
|
|
|
117
192
|
let httpServer: http.Server;
|
|
@@ -131,6 +206,7 @@ export async function createDevServer({
|
|
|
131
206
|
port,
|
|
132
207
|
sdk,
|
|
133
208
|
superblocksBaseUrl: explicitBaseUrl,
|
|
209
|
+
existingServer,
|
|
134
210
|
}: CreateDevServerOptions) {
|
|
135
211
|
const logger = getLogger(loggerOverride);
|
|
136
212
|
if (httpServer) {
|
|
@@ -167,6 +243,12 @@ export async function createDevServer({
|
|
|
167
243
|
viteResolve = resolve;
|
|
168
244
|
viteReject = reject;
|
|
169
245
|
});
|
|
246
|
+
// Attach a no-op catch so that if viteReject() fires before any request
|
|
247
|
+
// handler awaits vitePromise (e.g. Vite fails during eager init, before
|
|
248
|
+
// /_sb_connect), the process doesn't crash with UnhandledPromiseRejection.
|
|
249
|
+
// The actual error is still logged via the .then(_, onRejected) handler
|
|
250
|
+
// attached at the viteStartPromise call site.
|
|
251
|
+
vitePromise.catch(() => {});
|
|
170
252
|
|
|
171
253
|
async function gracefulShutdown({
|
|
172
254
|
logger,
|
|
@@ -384,50 +466,21 @@ export async function createDevServer({
|
|
|
384
466
|
}
|
|
385
467
|
}
|
|
386
468
|
|
|
387
|
-
logger.info("
|
|
388
|
-
|
|
389
|
-
|
|
469
|
+
logger.info("Received connect request, waiting for vite server...");
|
|
470
|
+
// Vite is started eagerly after HTTP server listen (see below).
|
|
471
|
+
// Wait for it to be ready before responding.
|
|
472
|
+
try {
|
|
473
|
+
await vitePromise;
|
|
390
474
|
res.send(JSON.stringify(healthResponse));
|
|
391
|
-
|
|
475
|
+
} catch (e) {
|
|
476
|
+
logger.error(
|
|
477
|
+
"Vite server failed to initialize",
|
|
478
|
+
getErrorMeta(e as Error),
|
|
479
|
+
);
|
|
480
|
+
res
|
|
481
|
+
.status(500)
|
|
482
|
+
.send(JSON.stringify({ error: "Dev server failed to initialize" }));
|
|
392
483
|
}
|
|
393
|
-
logger.info("Starting dev server");
|
|
394
|
-
isViteServerInitialized = true;
|
|
395
|
-
|
|
396
|
-
const featureFlags = await sdk?.getFeatureFlagsForCurrentUser();
|
|
397
|
-
|
|
398
|
-
// TODO(code-mode): should this include any validation checks, such as getting a token?
|
|
399
|
-
|
|
400
|
-
startVite({
|
|
401
|
-
port,
|
|
402
|
-
app,
|
|
403
|
-
root,
|
|
404
|
-
mode,
|
|
405
|
-
fsOperationQueue,
|
|
406
|
-
syncService,
|
|
407
|
-
lockService,
|
|
408
|
-
aiService,
|
|
409
|
-
gitService,
|
|
410
|
-
activateGitService,
|
|
411
|
-
snapshotManager,
|
|
412
|
-
checkAuthorization,
|
|
413
|
-
logger: loggerOverride,
|
|
414
|
-
httpServer,
|
|
415
|
-
superblocksBaseUrl: explicitBaseUrl || localToken?.superblocksBaseUrl,
|
|
416
|
-
features: {
|
|
417
|
-
enableSessionRecording: featureFlags?.enableSessionRecording() ?? false,
|
|
418
|
-
},
|
|
419
|
-
}).then(
|
|
420
|
-
(result) => {
|
|
421
|
-
logger.info("Dev server initialized");
|
|
422
|
-
viteResolve();
|
|
423
|
-
viteCreationResults = result;
|
|
424
|
-
res.send(JSON.stringify(healthResponse));
|
|
425
|
-
},
|
|
426
|
-
(e) => {
|
|
427
|
-
logger.error("Error initializing dev server", getErrorMeta(e));
|
|
428
|
-
viteReject(e);
|
|
429
|
-
},
|
|
430
|
-
);
|
|
431
484
|
});
|
|
432
485
|
|
|
433
486
|
app.post("/_sb_disconnect", authHandler, async (req, res) => {
|
|
@@ -457,6 +510,7 @@ export async function createDevServer({
|
|
|
457
510
|
viteResolve = resolve;
|
|
458
511
|
viteReject = reject;
|
|
459
512
|
});
|
|
513
|
+
vitePromise.catch(() => {});
|
|
460
514
|
isViteServerInitialized = false;
|
|
461
515
|
}
|
|
462
516
|
res.send("ok");
|
|
@@ -500,8 +554,143 @@ export async function createDevServer({
|
|
|
500
554
|
timeSinceLastActivityMs: lockService.timeSinceLastActivity,
|
|
501
555
|
});
|
|
502
556
|
} else {
|
|
503
|
-
|
|
504
|
-
|
|
557
|
+
// Lock service may be undefined during warm pool activation (sync is
|
|
558
|
+
// deferred until the first /_sb_connect). Return a safe default instead
|
|
559
|
+
// of rejecting vitePromise which would crash the server.
|
|
560
|
+
// Use MAX_SAFE_INTEGER so idle-detection callers don't mistake this for recent activity.
|
|
561
|
+
res.json({
|
|
562
|
+
isUserActive: false,
|
|
563
|
+
connectedUsers: [],
|
|
564
|
+
timeSinceLastActivityMs: Number.MAX_SAFE_INTEGER,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// /_sb_persist: SABS POSTs a presigned S3 upload URL at teardown.
|
|
570
|
+
// This endpoint creates a tar+zstd archive of the workspace and uploads
|
|
571
|
+
// it directly to S3 via the presigned PUT URL. Registered on the Express
|
|
572
|
+
// app so it's available on ALL dev server instances (warm and cold-start, D-24).
|
|
573
|
+
let persistInProgress = false;
|
|
574
|
+
app.post("/_sb_persist", authHandler, async (req, res) => {
|
|
575
|
+
if (persistInProgress) {
|
|
576
|
+
res.status(409).json({ error: "persist already in progress" });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
persistInProgress = true;
|
|
580
|
+
const persistStart = Date.now();
|
|
581
|
+
try {
|
|
582
|
+
const { uploadURL } = req.body as { uploadURL: string };
|
|
583
|
+
if (!uploadURL) {
|
|
584
|
+
res.status(400).json({ error: "uploadURL required" });
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
logger.info("/_sb_persist: archiving and uploading workspace to S3...");
|
|
589
|
+
|
|
590
|
+
// Create tar+zstd archive excluding .git/ and node_modules/.cache/
|
|
591
|
+
// then stream directly to S3 via presigned PUT URL.
|
|
592
|
+
const archive = await new Promise<Buffer>(
|
|
593
|
+
(resolveArchive, rejectArchive) => {
|
|
594
|
+
const tarProc = child_process.spawn(
|
|
595
|
+
"tar",
|
|
596
|
+
[
|
|
597
|
+
"cf",
|
|
598
|
+
"-",
|
|
599
|
+
"--exclude",
|
|
600
|
+
".git",
|
|
601
|
+
"--exclude",
|
|
602
|
+
"node_modules/.cache",
|
|
603
|
+
"--exclude",
|
|
604
|
+
".superblocks",
|
|
605
|
+
"-C",
|
|
606
|
+
process.cwd(),
|
|
607
|
+
".",
|
|
608
|
+
],
|
|
609
|
+
{ stdio: ["ignore", "pipe", "pipe"] },
|
|
610
|
+
);
|
|
611
|
+
const zstdProc = child_process.spawn(
|
|
612
|
+
"zstd",
|
|
613
|
+
["-1", "--no-progress"],
|
|
614
|
+
{
|
|
615
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
616
|
+
},
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
tarProc.stdout.pipe(zstdProc.stdin);
|
|
620
|
+
|
|
621
|
+
// Attach error handlers on the piped stdin. ChildProcess-level 'error'
|
|
622
|
+
// events don't catch stream errors like EPIPE from writing to a closed
|
|
623
|
+
// pipe if the downstream process crashes.
|
|
624
|
+
zstdProc.stdin.on("error", (err) => {
|
|
625
|
+
rejectArchive(new Error(`zstd stdin stream error: ${err.message}`));
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const chunks: Buffer[] = [];
|
|
629
|
+
zstdProc.stdout.on("data", (chunk: Buffer) => chunks.push(chunk));
|
|
630
|
+
|
|
631
|
+
let stderr = "";
|
|
632
|
+
tarProc.stderr.on("data", (chunk: Buffer) => {
|
|
633
|
+
stderr += chunk.toString();
|
|
634
|
+
});
|
|
635
|
+
zstdProc.stderr.on("data", (chunk: Buffer) => {
|
|
636
|
+
stderr += chunk.toString();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
let tarExitCode: number | null = null;
|
|
640
|
+
tarProc.on("close", (code) => {
|
|
641
|
+
tarExitCode = code;
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
zstdProc.on("close", (code) => {
|
|
645
|
+
if (code !== 0) {
|
|
646
|
+
rejectArchive(
|
|
647
|
+
new Error(`zstd compression failed (code ${code}): ${stderr}`),
|
|
648
|
+
);
|
|
649
|
+
} else if (tarExitCode !== null && tarExitCode !== 0) {
|
|
650
|
+
rejectArchive(
|
|
651
|
+
new Error(
|
|
652
|
+
`tar archival failed (code ${tarExitCode}): ${stderr}`,
|
|
653
|
+
),
|
|
654
|
+
);
|
|
655
|
+
} else {
|
|
656
|
+
resolveArchive(Buffer.concat(chunks));
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
tarProc.on("error", rejectArchive);
|
|
660
|
+
zstdProc.on("error", rejectArchive);
|
|
661
|
+
},
|
|
662
|
+
);
|
|
663
|
+
|
|
664
|
+
logger.info(
|
|
665
|
+
`/_sb_persist: archive created (${(archive.length / 1024 / 1024).toFixed(1)}MB) in ${Date.now() - persistStart}ms, uploading...`,
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
const uploadStart = Date.now();
|
|
669
|
+
const uploadResp = await fetch(uploadURL, {
|
|
670
|
+
method: "PUT",
|
|
671
|
+
body: archive,
|
|
672
|
+
headers: {
|
|
673
|
+
"Content-Type": "application/zstd",
|
|
674
|
+
"Content-Length": String(archive.length),
|
|
675
|
+
},
|
|
676
|
+
signal: AbortSignal.timeout(30_000),
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (!uploadResp.ok) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`S3 upload failed: ${uploadResp.status} ${uploadResp.statusText}`,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
logger.info(
|
|
686
|
+
`/_sb_persist: uploaded in ${Date.now() - uploadStart}ms (total ${Date.now() - persistStart}ms)`,
|
|
687
|
+
);
|
|
688
|
+
res.status(200).json({ status: "uploaded" });
|
|
689
|
+
} catch (error) {
|
|
690
|
+
logger.error(`/_sb_persist failed: ${error}`);
|
|
691
|
+
res.status(500).json({ error: String(error) });
|
|
692
|
+
} finally {
|
|
693
|
+
persistInProgress = false;
|
|
505
694
|
}
|
|
506
695
|
});
|
|
507
696
|
|
|
@@ -533,9 +722,72 @@ export async function createDevServer({
|
|
|
533
722
|
);
|
|
534
723
|
});
|
|
535
724
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
725
|
+
if (existingServer) {
|
|
726
|
+
// Warm standby mode: reuse the pre-existing HTTP server to avoid a port gap.
|
|
727
|
+
// Replace the warm server's request handler with the full Express app.
|
|
728
|
+
existingServer.removeAllListeners("request");
|
|
729
|
+
existingServer.on("request", app);
|
|
730
|
+
httpServer = existingServer;
|
|
731
|
+
logger.info(
|
|
732
|
+
`Attached full dev server to existing HTTP server on port ${port}`,
|
|
733
|
+
);
|
|
734
|
+
} else {
|
|
735
|
+
logger.info(`Starting HTTP server on port ${port}`);
|
|
736
|
+
httpServer = await app.listen(port);
|
|
737
|
+
logger.info(`HTTP server started successfully on port ${port}`);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Start vite eagerly so it's ready (or nearly ready) by the time the editor
|
|
741
|
+
// connects via /_sb_connect. Previously vite was started lazily on first
|
|
742
|
+
// /_sb_connect, adding ~5s to perceived connection time.
|
|
743
|
+
if (!isViteServerInitialized) {
|
|
744
|
+
isViteServerInitialized = true;
|
|
745
|
+
logger.info("Eagerly starting vite server...");
|
|
746
|
+
|
|
747
|
+
const featureFlags = await sdk
|
|
748
|
+
?.getFeatureFlagsForCurrentUser()
|
|
749
|
+
.catch((err: unknown) => {
|
|
750
|
+
logger.warn(`Failed to fetch feature flags, using defaults: ${err}`);
|
|
751
|
+
return undefined;
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
const viteStartPromise = startVite({
|
|
755
|
+
port,
|
|
756
|
+
app,
|
|
757
|
+
root,
|
|
758
|
+
mode,
|
|
759
|
+
fsOperationQueue,
|
|
760
|
+
syncService,
|
|
761
|
+
lockService,
|
|
762
|
+
aiService,
|
|
763
|
+
gitService,
|
|
764
|
+
activateGitService,
|
|
765
|
+
snapshotManager,
|
|
766
|
+
checkAuthorization,
|
|
767
|
+
logger: loggerOverride,
|
|
768
|
+
httpServer,
|
|
769
|
+
superblocksBaseUrl: explicitBaseUrl || localToken?.superblocksBaseUrl,
|
|
770
|
+
features: {
|
|
771
|
+
enableSessionRecording: featureFlags?.enableSessionRecording() ?? false,
|
|
772
|
+
},
|
|
773
|
+
});
|
|
774
|
+
// Note: vitePromise itself gets a no-op .catch() at its construction site
|
|
775
|
+
// (see lines ~179 / ~443) to prevent UnhandledPromiseRejection if Vite
|
|
776
|
+
// fails before any request awaits vitePromise. The actual error is logged
|
|
777
|
+
// below via the rejection branch of .then().
|
|
778
|
+
viteStartPromise.then(
|
|
779
|
+
(result) => {
|
|
780
|
+
logger.info("Vite server initialized eagerly");
|
|
781
|
+
viteResolve();
|
|
782
|
+
viteCreationResults = result;
|
|
783
|
+
},
|
|
784
|
+
(e) => {
|
|
785
|
+
logger.error("Error initializing vite server", getErrorMeta(e));
|
|
786
|
+
viteReject(e);
|
|
787
|
+
},
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
539
791
|
return httpServer;
|
|
540
792
|
}
|
|
541
793
|
|
|
@@ -653,13 +905,7 @@ async function startVite({
|
|
|
653
905
|
mode,
|
|
654
906
|
clearScreen: process.env.SUPERBLOCKS_CLI_ENV !== "local",
|
|
655
907
|
customLogger: viteLogger,
|
|
656
|
-
optimizeDeps:
|
|
657
|
-
esbuildOptions: {
|
|
658
|
-
supported: {
|
|
659
|
-
destructuring: true,
|
|
660
|
-
},
|
|
661
|
-
},
|
|
662
|
-
},
|
|
908
|
+
optimizeDeps: OPTIMIZE_DEPS_CONFIG,
|
|
663
909
|
server: {
|
|
664
910
|
middlewareMode: true,
|
|
665
911
|
watch: {
|
|
@@ -685,30 +931,9 @@ async function startVite({
|
|
|
685
931
|
)
|
|
686
932
|
: "undefined",
|
|
687
933
|
},
|
|
688
|
-
plugins:
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
customComponentsPlugin(),
|
|
692
|
-
fileSyncPluginCreator.plugin,
|
|
693
|
-
// Add a virtual "stub" module for the build manifest
|
|
694
|
-
buildManifestStubPlugin(),
|
|
695
|
-
react({
|
|
696
|
-
babel: {
|
|
697
|
-
plugins: [
|
|
698
|
-
// We will bring this back later
|
|
699
|
-
// "babel-plugin-react-compiler",
|
|
700
|
-
[
|
|
701
|
-
import.meta
|
|
702
|
-
.resolve("@babel/plugin-proposal-decorators"),
|
|
703
|
-
{
|
|
704
|
-
version: "2023-11",
|
|
705
|
-
},
|
|
706
|
-
],
|
|
707
|
-
],
|
|
708
|
-
},
|
|
709
|
-
}),
|
|
710
|
-
createLucideReactImportOptimizer(),
|
|
711
|
-
ddRumPlugin({
|
|
934
|
+
plugins: vitePlugins(root, {
|
|
935
|
+
fileSync: fileSyncPluginCreator.plugin,
|
|
936
|
+
ddRum: {
|
|
712
937
|
clientToken: env.SUPERBLOCKS_LIBRARY_DD_CLIENT_TOKEN ?? "",
|
|
713
938
|
applicationId:
|
|
714
939
|
env.SUPERBLOCKS_LIBRARY_DD_APPLICATION_ID ?? "",
|
|
@@ -717,8 +942,8 @@ async function startVite({
|
|
|
717
942
|
// CLI auto-upgrade still works - the caching changes in this PR only affect detection speed.
|
|
718
943
|
// Replace with dynamic library version injection once available.
|
|
719
944
|
version: "1.0.0",
|
|
720
|
-
}
|
|
721
|
-
|
|
945
|
+
},
|
|
946
|
+
}),
|
|
722
947
|
});
|
|
723
948
|
return server;
|
|
724
949
|
} finally {
|
|
@@ -744,6 +969,58 @@ async function startVite({
|
|
|
744
969
|
});
|
|
745
970
|
}
|
|
746
971
|
|
|
972
|
+
/**
|
|
973
|
+
* Pre-warm the Vite dependency cache by creating and immediately closing a
|
|
974
|
+
* minimal Vite server. This populates `node_modules/.vite/deps` with
|
|
975
|
+
* pre-bundled dependencies so that `createDevServer` starts ~1-2s faster.
|
|
976
|
+
*
|
|
977
|
+
* Call this during warm standby phase while waiting for activation. The
|
|
978
|
+
* K8s startup probe (`/_sb_ready`) gates on this resolving.
|
|
979
|
+
*
|
|
980
|
+
* Any failure (timeout, createServer throw, unexpected error) is fatal:
|
|
981
|
+
* we log it and `process.exit(1)`. Warm pods have `RestartPolicy: Never`,
|
|
982
|
+
* so the process exit fails the pod, and the reconciler replaces it
|
|
983
|
+
* within seconds. Letting the probe kill the container instead would
|
|
984
|
+
* add ~2min of idle time. We don't call `server.close()` on failure —
|
|
985
|
+
* for the timeout case esbuild is still running and close crashes Node
|
|
986
|
+
* anyway, and for other cases `process.exit` reaps everything cleanly.
|
|
987
|
+
*/
|
|
988
|
+
const PRE_WARM_TIMEOUT_MS = 60_000;
|
|
989
|
+
|
|
990
|
+
export async function preWarmViteCache(root: string): Promise<void> {
|
|
991
|
+
const start = Date.now();
|
|
992
|
+
console.log("[warm] Pre-warming Vite dependency cache...");
|
|
993
|
+
try {
|
|
994
|
+
const server = await createServer({
|
|
995
|
+
root,
|
|
996
|
+
mode: "development",
|
|
997
|
+
server: { middlewareMode: true, hmr: false },
|
|
998
|
+
plugins: vitePlugins(root, { fileSync: fileSyncVitePlugin().plugin }),
|
|
999
|
+
logLevel: "warn",
|
|
1000
|
+
optimizeDeps: OPTIMIZE_DEPS_CONFIG,
|
|
1001
|
+
});
|
|
1002
|
+
const depsDir = path.join(root, "node_modules", ".vite", "deps");
|
|
1003
|
+
const pollMs = 100;
|
|
1004
|
+
const deadline = Date.now() + PRE_WARM_TIMEOUT_MS;
|
|
1005
|
+
while (!existsSync(depsDir)) {
|
|
1006
|
+
if (Date.now() > deadline) {
|
|
1007
|
+
throw new Error(
|
|
1008
|
+
`Vite dep cache not created after ${PRE_WARM_TIMEOUT_MS}ms; optimizer likely hung`,
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
1012
|
+
}
|
|
1013
|
+
await server.close();
|
|
1014
|
+
console.log(`[warm] Vite cache warmed in ${Date.now() - start}ms`);
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
console.error(
|
|
1017
|
+
`[warm] Vite cache pre-warm failed (${Date.now() - start}ms) — exiting for fast pod recycle:`,
|
|
1018
|
+
error,
|
|
1019
|
+
);
|
|
1020
|
+
process.exit(1);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
747
1024
|
const DEFAULT_HMR_PORT = 24678;
|
|
748
1025
|
|
|
749
1026
|
function getFreePort() {
|
package/src/index.ts
CHANGED
|
@@ -64,7 +64,7 @@ export {
|
|
|
64
64
|
export { AUTO_UPGRADE_EXIT_CODE } from "./cli-replacement/automatic-upgrades.js";
|
|
65
65
|
export { RESTART_EXIT_CODE } from "./dev-utils/dev-server.mjs";
|
|
66
66
|
|
|
67
|
-
export { createDevServer } from "./dev-utils/dev-server.mjs";
|
|
67
|
+
export { createDevServer, preWarmViteCache } from "./dev-utils/dev-server.mjs";
|
|
68
68
|
|
|
69
69
|
export { TokenManager } from "./dev-utils/token-manager.js";
|
|
70
70
|
|