@superblocksteam/sdk 2.0.119-next.0 → 2.0.119

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.
Files changed (85) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/cli-replacement/dev-s3-restore.test.mjs +0 -116
  3. package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -1
  4. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs +0 -1
  5. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs.map +1 -1
  6. package/dist/cli-replacement/dev.d.mts +0 -6
  7. package/dist/cli-replacement/dev.d.mts.map +1 -1
  8. package/dist/cli-replacement/dev.mjs +6 -77
  9. package/dist/cli-replacement/dev.mjs.map +1 -1
  10. package/dist/client.d.ts +6 -67
  11. package/dist/client.d.ts.map +1 -1
  12. package/dist/client.js +0 -94
  13. package/dist/client.js.map +1 -1
  14. package/dist/dev-utils/dev-server-persist.test.mjs +17 -117
  15. package/dist/dev-utils/dev-server-persist.test.mjs.map +1 -1
  16. package/dist/dev-utils/dev-server.d.mts +1 -19
  17. package/dist/dev-utils/dev-server.d.mts.map +1 -1
  18. package/dist/dev-utils/dev-server.mjs +28 -296
  19. package/dist/dev-utils/dev-server.mjs.map +1 -1
  20. package/dist/flag.d.ts +1 -1
  21. package/dist/flag.d.ts.map +1 -1
  22. package/dist/flag.js +3 -3
  23. package/dist/flag.js.map +1 -1
  24. package/dist/index.d.ts +3 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -3
  27. package/dist/index.js.map +1 -1
  28. package/dist/sdk.d.ts +1 -8
  29. package/dist/sdk.d.ts.map +1 -1
  30. package/dist/sdk.js +1 -24
  31. package/dist/sdk.js.map +1 -1
  32. package/dist/sdk.test.js +1 -102
  33. package/dist/sdk.test.js.map +1 -1
  34. package/dist/telemetry/index.d.ts +1 -2
  35. package/dist/telemetry/index.d.ts.map +1 -1
  36. package/dist/telemetry/index.js +1 -8
  37. package/dist/telemetry/index.js.map +1 -1
  38. package/dist/telemetry/logging.d.ts +2 -1
  39. package/dist/telemetry/logging.d.ts.map +1 -1
  40. package/dist/telemetry/logging.js +1 -1
  41. package/dist/telemetry/logging.js.map +1 -1
  42. package/dist/types/common.d.ts +1 -1
  43. package/dist/types/common.d.ts.map +1 -1
  44. package/dist/version-control.d.mts.map +1 -1
  45. package/dist/version-control.mjs +19 -14
  46. package/dist/version-control.mjs.map +1 -1
  47. package/eslint.config.js +0 -6
  48. package/package.json +8 -8
  49. package/src/cli-replacement/dev-s3-restore.test.mts +0 -156
  50. package/src/cli-replacement/dev-startup-git-before-dbfs-order.test.mts +0 -1
  51. package/src/cli-replacement/dev.mts +14 -104
  52. package/src/client.ts +6 -189
  53. package/src/dev-utils/dev-server-persist.test.mts +19 -170
  54. package/src/dev-utils/dev-server.mts +32 -366
  55. package/src/flag.ts +4 -4
  56. package/src/index.ts +1 -19
  57. package/src/sdk.test.ts +6 -145
  58. package/src/sdk.ts +0 -36
  59. package/src/telemetry/index.ts +1 -9
  60. package/src/telemetry/logging.ts +2 -2
  61. package/src/types/common.ts +1 -1
  62. package/src/version-control.mts +30 -11
  63. package/test/version-control.test.mts +2 -0
  64. package/tsconfig.tsbuildinfo +1 -1
  65. package/turbo.json +0 -1
  66. package/dist/dev-utils/dedupe-async.d.ts +0 -16
  67. package/dist/dev-utils/dedupe-async.d.ts.map +0 -1
  68. package/dist/dev-utils/dedupe-async.js +0 -27
  69. package/dist/dev-utils/dedupe-async.js.map +0 -1
  70. package/dist/dev-utils/dedupe-async.test.d.ts +0 -2
  71. package/dist/dev-utils/dedupe-async.test.d.ts.map +0 -1
  72. package/dist/dev-utils/dedupe-async.test.js +0 -120
  73. package/dist/dev-utils/dedupe-async.test.js.map +0 -1
  74. package/dist/dev-utils/dev-server-metrics.d.mts +0 -95
  75. package/dist/dev-utils/dev-server-metrics.d.mts.map +0 -1
  76. package/dist/dev-utils/dev-server-metrics.mjs +0 -193
  77. package/dist/dev-utils/dev-server-metrics.mjs.map +0 -1
  78. package/dist/telemetry/memory-metrics.d.ts +0 -3
  79. package/dist/telemetry/memory-metrics.d.ts.map +0 -1
  80. package/dist/telemetry/memory-metrics.js +0 -59
  81. package/dist/telemetry/memory-metrics.js.map +0 -1
  82. package/src/dev-utils/dedupe-async.test.ts +0 -151
  83. package/src/dev-utils/dedupe-async.ts +0 -45
  84. package/src/dev-utils/dev-server-metrics.mts +0 -252
  85. package/src/telemetry/memory-metrics.ts +0 -90
@@ -1,5 +1,5 @@
1
1
  import * as child_process from "node:child_process";
2
- import { existsSync, readdirSync } from "node:fs";
2
+ import { existsSync } from "node:fs";
3
3
  import type http from "node:http";
4
4
  import net from "node:net";
5
5
  import os from "node:os";
@@ -37,10 +37,6 @@ import pkg from "../../package.json" with { type: "json" };
37
37
  import type { SuperblocksSdk } from "../sdk.js";
38
38
  import { getTracer } from "../telemetry/index.js";
39
39
  import { getErrorMeta, getLogger } from "../telemetry/logging.js";
40
- import {
41
- startMemoryMetrics,
42
- stopMemoryMetrics,
43
- } from "../telemetry/memory-metrics.js";
44
40
  import {
45
41
  EDIT_APPLICATION_SCOPE,
46
42
  type SuperblocksScopedJwtPayload,
@@ -52,12 +48,6 @@ import {
52
48
  customComponentsPlugin,
53
49
  isCustomComponentsEnabled,
54
50
  } from "./custom-build.mjs";
55
- import { dedupeAsync } from "./dedupe-async.js";
56
- import {
57
- type DevServerEndpoint,
58
- type DevServerFailureType,
59
- devServerMetrics,
60
- } from "./dev-server-metrics.mjs";
61
51
  import {
62
52
  formatViteDevServerStartedLog,
63
53
  logViteBuildError,
@@ -66,60 +56,6 @@ import { buildManifestStubPlugin } from "./vite-plugin-build-manifest-stub.mjs";
66
56
  import { ddRumPlugin } from "./vite-plugin-dd-rum.mjs";
67
57
 
68
58
  const tracer = getTracer();
69
- const logger = getLogger();
70
-
71
- /**
72
- * Maps a dev-server request URL onto a low-cardinality endpoint label for
73
- * metrics. Returns undefined for non-`/_sb_*` URLs (Vite-proxied traffic).
74
- */
75
- function endpointForUrl(
76
- url: string | undefined,
77
- ): DevServerEndpoint | undefined {
78
- if (!url) return undefined;
79
- const path = url.split("?", 1)[0];
80
- switch (path) {
81
- case "/_sb_health":
82
- return "_sb_health";
83
- case "/_sb_connect":
84
- return "_sb_connect";
85
- case "/_sb_activate":
86
- return "_sb_activate";
87
- case "/_sb_disconnect":
88
- return "_sb_disconnect";
89
- case "/_sb_status":
90
- return "_sb_status";
91
- case "/_sb_activity":
92
- return "_sb_activity";
93
- case "/_sb_persist":
94
- return "_sb_persist";
95
- case "/_sb_ready":
96
- return "_sb_ready";
97
- default:
98
- return undefined;
99
- }
100
- }
101
-
102
- /**
103
- * Extracts a coarse failure-type label from `res.locals.sbFailureType`,
104
- * which endpoints set just before they error out. Stays server-side
105
- * rather than leaking through response headers.
106
- */
107
- function failureTypeFromLocals(
108
- locals: Record<string, unknown>,
109
- ): DevServerFailureType {
110
- const value = locals?.sbFailureType;
111
- if (typeof value !== "string") return "none";
112
- switch (value) {
113
- case "auth":
114
- case "validation":
115
- case "branch_mismatch":
116
- case "vite_init":
117
- case "internal":
118
- return value;
119
- default:
120
- return "none";
121
- }
122
- }
123
59
 
124
60
  function reactPlugin() {
125
61
  return react({
@@ -187,29 +123,6 @@ function vitePlugins(
187
123
  ];
188
124
  }
189
125
 
190
- // .superblocks subdirectories that should survive pod recycles.
191
- // Everything else under .superblocks/ is excluded automatically.
192
- const PERSIST_SUPERBLOCKS_SAFELIST = new Set(["context", "drafts"]);
193
-
194
- /**
195
- * Build tar --exclude flags for .superblocks/ subdirectories that are NOT
196
- * in the safelist. If .superblocks/ doesn't exist we exclude it entirely.
197
- */
198
- export function getSuperblocksTarExcludes(root: string): string[] {
199
- const dotSuperblocks = path.join(root, ".superblocks");
200
- let entries: string[];
201
- try {
202
- entries = readdirSync(dotSuperblocks, { withFileTypes: true })
203
- .filter((d) => d.isDirectory())
204
- .map((d) => d.name);
205
- } catch {
206
- return ["--exclude", ".superblocks"];
207
- }
208
- return entries
209
- .filter((name) => !PERSIST_SUPERBLOCKS_SAFELIST.has(name))
210
- .flatMap((name) => ["--exclude", `.superblocks/${name}`]);
211
- }
212
-
213
126
  export async function createWorkspacePersistArchive(root: string) {
214
127
  // Warm pods already have template dependencies installed, so persist only
215
128
  // user workspace files and let startup reconcile package drift if needed.
@@ -223,7 +136,8 @@ export async function createWorkspacePersistArchive(root: string) {
223
136
  ".git",
224
137
  "--exclude",
225
138
  "node_modules",
226
- ...getSuperblocksTarExcludes(root),
139
+ "--exclude",
140
+ ".superblocks",
227
141
  "-C",
228
142
  root,
229
143
  ".",
@@ -299,64 +213,11 @@ export async function createWorkspacePersistArchive(root: string) {
299
213
  });
300
214
  }
301
215
 
302
- export function createWorkspacePersistUploadHeaders(
303
- archiveLength: number,
304
- uploadHeaders: Record<string, string> = {},
305
- ) {
306
- const headers = new Headers(uploadHeaders);
307
- if (!headers.has("Content-Type")) {
308
- headers.set("Content-Type", "application/zstd");
309
- }
310
- headers.set("Content-Length", String(archiveLength));
311
- return Object.fromEntries(headers.entries());
312
- }
313
-
314
- export function isWorkspacePersistUploadHeaders(
315
- uploadHeaders: unknown,
316
- ): uploadHeaders is Record<string, string> | undefined {
317
- if (uploadHeaders === undefined) {
318
- return true;
319
- }
320
- if (
321
- uploadHeaders === null ||
322
- typeof uploadHeaders !== "object" ||
323
- Array.isArray(uploadHeaders)
324
- ) {
325
- return false;
326
- }
327
- const seen = new Set<string>();
328
- for (const [key, value] of Object.entries(uploadHeaders)) {
329
- if (typeof key !== "string" || typeof value !== "string") {
330
- return false;
331
- }
332
- // Header names are case-insensitive. Two keys that differ only in casing
333
- // (e.g. `Content-Type` + `content-type`) would be silently combined by
334
- // `new Headers()` into a comma-joined value, which breaks S3 presigned
335
- // PUTs that signed a single canonical value. Reject up front so the
336
- // caller fixes the duplicate rather than getting a confusing S3 4xx.
337
- const lower = key.toLowerCase();
338
- if (seen.has(lower)) {
339
- return false;
340
- }
341
- seen.add(lower);
342
- }
343
- // Validate header name/value syntax by attempting to construct a Headers
344
- // instance. Doing this in the validator (rather than letting it throw
345
- // later inside `createWorkspacePersistUploadHeaders`) keeps the failure
346
- // mode as a 400 before any archive work runs.
347
- try {
348
- new Headers(uploadHeaders as Record<string, string>);
349
- } catch {
350
- return false;
351
- }
352
- return true;
353
- }
354
-
355
216
  export const RESTART_EXIT_CODE = 98;
356
217
 
357
218
  function getJwksUriWithBaseUrl(superblocksBaseUrl?: string): string {
358
219
  if (process.env.SUPERBLOCKS_JWKS_URI) {
359
- logger.debug(
220
+ console.debug(
360
221
  `[JWKS] Using override SUPERBLOCKS_JWKS_URI=${process.env.SUPERBLOCKS_JWKS_URI}`,
361
222
  );
362
223
  return process.env.SUPERBLOCKS_JWKS_URI;
@@ -393,7 +254,7 @@ function getJwksUriWithBaseUrl(superblocksBaseUrl?: string): string {
393
254
  jwksUri = "https://staging-cdn.superblocks.com/.well-known/jwks.json";
394
255
  }
395
256
 
396
- logger.debug(`[JWKS] Derived jwksUri=${jwksUri} from baseUrl=${baseUrl}`);
257
+ console.debug(`[JWKS] Derived jwksUri=${jwksUri} from baseUrl=${baseUrl}`);
397
258
  return jwksUri;
398
259
  }
399
260
 
@@ -421,57 +282,10 @@ interface CreateDevServerOptions {
421
282
  * causes 502s from the gateway during the warm -> full server transition.
422
283
  */
423
284
  existingServer?: http.Server;
424
- /**
425
- * Wall-clock timestamp (Date.now()) when the warm-standby /_sb_activate POST
426
- * was accepted. When provided, the dev server records the elapsed time from
427
- * activation acceptance to full Express handler attachment as the
428
- * `dev_server_warm_activation_handoff_duration_ms` histogram — the window
429
- * during which the gateway can return 502s if the warm server stops
430
- * responding before the full server takes over.
431
- */
432
- warmActivationStart?: number;
433
285
  }
434
286
 
435
287
  let httpServer: http.Server;
436
288
 
437
- /**
438
- * Attaches a passive metrics-only listener for WebSocket upgrade attempts on
439
- * the dev-server's HTTP socket.
440
- *
441
- * The outcome is a heuristic, not a direct accept/reject signal:
442
- * - `accepted` = the socket closed cleanly (no `hadError`) or stayed open
443
- * past the 30s defensive timer. A long-lived HMR connection
444
- * is the typical success case.
445
- * - `rejected` = the socket emitted `error` or closed with `hadError=true`.
446
- * A clean rejection by the upstream WS layer (write 401 + end) can therefore
447
- * bucket as `accepted` if the close event has no `hadError` flag — the WS
448
- * handling layer does not surface a direct accept signal we can observe here.
449
- *
450
- * MUST be called only after Vite (or another component) has registered its own
451
- * `'upgrade'` listener on the same server. Registering this listener first
452
- * suppresses Node's default close-on-unhandled behavior, which would leave
453
- * editor upgrade attempts hanging until client timeout in the pre-Vite window.
454
- */
455
- function attachUpgradeMetricsListener(server: http.Server): void {
456
- server.on("upgrade", (req, socket) => {
457
- let recorded = false;
458
- const recordOnce = (outcome: "accepted" | "rejected") => {
459
- if (recorded) return;
460
- recorded = true;
461
- devServerMetrics.recordSocketUpgrade(outcome);
462
- };
463
- socket.once("close", (hadError: boolean) => {
464
- recordOnce(hadError ? "rejected" : "accepted");
465
- });
466
- socket.once("error", () => recordOnce("rejected"));
467
- // Defensive: if neither close nor error fires within 30s, count as accepted
468
- // — a long-lived HMR connection is the success case for upgrades.
469
- setTimeout(() => recordOnce("accepted"), 30_000).unref();
470
- // Avoid lint warning for unused param without changing the listener shape.
471
- void req;
472
- });
473
- }
474
-
475
289
  // The "Dev Server" is a long-running HTTP server that manages the Vite server
476
290
  export async function createDevServer({
477
291
  root,
@@ -489,7 +303,6 @@ export async function createDevServer({
489
303
  sdk,
490
304
  superblocksBaseUrl: explicitBaseUrl,
491
305
  existingServer,
492
- warmActivationStart,
493
306
  }: CreateDevServerOptions) {
494
307
  const logger = getLogger(loggerOverride);
495
308
  if (httpServer) {
@@ -533,31 +346,7 @@ export async function createDevServer({
533
346
  // attached at the viteStartPromise call site.
534
347
  vitePromise.catch(() => {});
535
348
 
536
- // Dedupe re-entry. Multiple signals (SIGINT, SIGTERM, SIGABRT,
537
- // uncaughtException) can fire in rapid succession during shutdown —
538
- // particularly when a parent process kills the dev-server and the OS or
539
- // test harness then sends a follow-up SIGINT. Without a dedupe, every
540
- // signal handler re-runs lockService shutdown / vite close / httpServer
541
- // close on already-freed native state, which surfaces as
542
- // `double free or corruption (fasttop)` from glibc and SIGABRT killing
543
- // the process before it can exit cleanly (hq-o55a).
544
- //
545
- // First-caller-wins on args: the second+ caller's `switchingTo` /
546
- // `initiatedByEmail` are intentionally dropped (the first caller's run is
547
- // already in flight; we don't restart cleanup with new args). The
548
- // `/_sb_disconnect` HTTP handler that awaits `gracefulShutdown` therefore
549
- // returns "ok" without applying its `switchingTo` if a signal beat it to
550
- // the punch — the process exits regardless, so the dropped arg cannot
551
- // drive downstream state in this process.
552
- const gracefulShutdown = dedupeAsync(runGracefulShutdown, {
553
- onDuplicate: (args) => {
554
- logger.info(
555
- `Shutdown already in progress; ignoring duplicate signal (source=${args.source ?? "unknown"})`,
556
- );
557
- },
558
- });
559
-
560
- async function runGracefulShutdown({
349
+ async function gracefulShutdown({
561
350
  logger,
562
351
  serverInitiated,
563
352
  switchingTo,
@@ -567,16 +356,8 @@ export async function createDevServer({
567
356
  serverInitiated: boolean;
568
357
  switchingTo?: "local" | "cloud" | "none";
569
358
  initiatedByEmail?: string;
570
- /**
571
- * Identifies what triggered the shutdown (signal name, "uncaughtException",
572
- * or "/_sb_disconnect"). Threaded through so the dedupe-duplicate log can
573
- * disambiguate "4 OS signals fired" from "1 signal fired and cleanup
574
- * panicked 3 times".
575
- */
576
- source?: string;
577
- }): Promise<void> {
359
+ }) {
578
360
  try {
579
- stopMemoryMetrics();
580
361
  await lockService?.shutdown({
581
362
  serverInitiated,
582
363
  switchingTo,
@@ -620,29 +401,6 @@ export async function createDevServer({
620
401
  }),
621
402
  );
622
403
 
623
- // Per-request metrics for dev-server endpoints. Identify the endpoint by URL
624
- // pattern, time the request, record duration + status + coarse failure type
625
- // when the response finishes. Non-`/_sb_*` traffic (Vite proxied requests)
626
- // is not instrumented here — Vite's own middleware handles those.
627
- app.use((req, res, next) => {
628
- const endpoint = endpointForUrl(req.url);
629
- if (!endpoint) {
630
- return next();
631
- }
632
- const startedAt = Date.now();
633
- res.on("finish", () => {
634
- const failureType = failureTypeFromLocals(res.locals);
635
- devServerMetrics.recordEndpoint(
636
- endpoint,
637
- req.method,
638
- res.statusCode,
639
- Date.now() - startedAt,
640
- failureType,
641
- );
642
- });
643
- next();
644
- });
645
-
646
404
  app.use((req, res, next) => {
647
405
  res.setHeader("Cache-Control", "no-store, max-age=0");
648
406
  res.setHeader("x-csb-no-sw-proxy", "1");
@@ -756,7 +514,6 @@ export async function createDevServer({
756
514
  if (result.success) {
757
515
  return next();
758
516
  } else {
759
- res.locals.sbFailureType = "auth";
760
517
  res.status(result.status).send(JSON.stringify({ error: result.error }));
761
518
  }
762
519
  };
@@ -780,7 +537,6 @@ export async function createDevServer({
780
537
  logger.info(
781
538
  `[dev-server-runtime] _sb_connect application mismatch: expected ${resourceConfig!.id}, got ${req.headers["x-superblocks-application-id"]}`,
782
539
  );
783
- res.locals.sbFailureType = "validation";
784
540
  res.status(401).send(
785
541
  JSON.stringify({
786
542
  error: `Application ID header mismatch. Expected ${resourceConfig!.id}, received ${req.headers["x-superblocks-application-id"]}`,
@@ -803,7 +559,6 @@ export async function createDevServer({
803
559
  logger.info(
804
560
  `[dev-server-runtime] _sb_connect branch mismatch: expected ${currentBranch}, got ${incomingBranch}`,
805
561
  );
806
- res.locals.sbFailureType = "branch_mismatch";
807
562
  res.status(401).send(
808
563
  JSON.stringify({
809
564
  error: `Dev server expects branch ${currentBranch}, but received ${incomingBranch}. Check your current branch with 'git branch' and try again.`,
@@ -824,7 +579,6 @@ export async function createDevServer({
824
579
  "Vite server failed to initialize",
825
580
  getErrorMeta(e as Error),
826
581
  );
827
- res.locals.sbFailureType = "vite_init";
828
582
  res
829
583
  .status(500)
830
584
  .send(JSON.stringify({ error: "Dev server failed to initialize" }));
@@ -870,7 +624,6 @@ export async function createDevServer({
870
624
  serverInitiated: true,
871
625
  switchingTo,
872
626
  initiatedByEmail: initiatedByEmail as string | undefined,
873
- source: "/_sb_disconnect",
874
627
  });
875
628
  res.send("ok");
876
629
  }
@@ -928,21 +681,11 @@ export async function createDevServer({
928
681
  persistInProgress = true;
929
682
  const persistStart = Date.now();
930
683
  try {
931
- const { uploadURL, uploadHeaders } = req.body as {
932
- uploadURL: string;
933
- uploadHeaders?: unknown;
934
- };
684
+ const { uploadURL } = req.body as { uploadURL: string };
935
685
  if (!uploadURL) {
936
686
  res.status(400).json({ error: "uploadURL required" });
937
687
  return;
938
688
  }
939
- if (!isWorkspacePersistUploadHeaders(uploadHeaders)) {
940
- res.status(400).json({
941
- error:
942
- "uploadHeaders must be a string map with valid HTTP header names and values and no case-insensitive duplicate keys",
943
- });
944
- return;
945
- }
946
689
 
947
690
  logger.info("/_sb_persist: archiving and uploading workspace to S3...");
948
691
 
@@ -956,10 +699,10 @@ export async function createDevServer({
956
699
  const uploadResp = await fetch(uploadURL, {
957
700
  method: "PUT",
958
701
  body: archive,
959
- headers: createWorkspacePersistUploadHeaders(
960
- archive.length,
961
- uploadHeaders,
962
- ),
702
+ headers: {
703
+ "Content-Type": "application/zstd",
704
+ "Content-Length": String(archive.length),
705
+ },
963
706
  signal: AbortSignal.timeout(30_000),
964
707
  });
965
708
 
@@ -981,63 +724,32 @@ export async function createDevServer({
981
724
  }
982
725
  });
983
726
 
984
- // Signal handlers attach `.catch` so a synchronous throw in
985
- // `runGracefulShutdown` (e.g. logger init or lockService shutdown throwing
986
- // before the first `await`) is logged rather than surfacing as an
987
- // unhandled rejection across N handlers (one per fired signal). Symmetric
988
- // with the `uncaughtException` handler below.
989
- process.on("SIGINT", () => {
727
+ process.on("SIGINT", async () => {
990
728
  logger.info("SIGINT received");
991
- gracefulShutdown({
992
- logger,
993
- serverInitiated: false,
994
- source: "SIGINT",
995
- }).catch((err) =>
996
- logger.error("Error during SIGINT shutdown", getErrorMeta(err)),
997
- );
729
+ await gracefulShutdown({ logger, serverInitiated: false });
998
730
  });
999
731
 
1000
- process.on("SIGTERM", () => {
732
+ process.on("SIGTERM", async () => {
1001
733
  logger.info("SIGTERM received");
1002
- gracefulShutdown({
1003
- logger,
1004
- serverInitiated: false,
1005
- source: "SIGTERM",
1006
- }).catch((err) =>
1007
- logger.error("Error during SIGTERM shutdown", getErrorMeta(err)),
1008
- );
734
+ await gracefulShutdown({ logger, serverInitiated: false });
1009
735
  });
1010
736
 
1011
- process.on("SIGABRT", () => {
737
+ process.on("SIGABRT", async () => {
1012
738
  logger.info("SIGABRT received");
1013
- gracefulShutdown({
1014
- logger,
1015
- serverInitiated: false,
1016
- source: "SIGABRT",
1017
- }).catch((err) =>
1018
- logger.error("Error during SIGABRT shutdown", getErrorMeta(err)),
1019
- );
739
+ await gracefulShutdown({ logger, serverInitiated: false });
1020
740
  });
1021
741
 
1022
- // `uncaughtException` always force-exits in `.finally`, even when the
1023
- // underlying shutdown promise was already resolved by a prior signal. With
1024
- // the dedupe in place, awaiting the cached resolved promise short-circuits
1025
- // through `.then` without firing `.catch`; without the `.finally` the
1026
- // process would linger in event-loop limbo until the OS killed it.
1027
742
  process.on("uncaughtException", (error) => {
1028
743
  logger.error(`Uncaught exception: ${error.message}`, getErrorMeta(error));
1029
- gracefulShutdown({
1030
- logger,
1031
- serverInitiated: false,
1032
- source: "uncaughtException",
1033
- })
1034
- .catch((shutdownError) => {
744
+ gracefulShutdown({ logger, serverInitiated: false }).catch(
745
+ (shutdownError) => {
1035
746
  logger.error(
1036
747
  "Error during shutdown after uncaught exception:",
1037
748
  shutdownError,
1038
749
  );
1039
- })
1040
- .finally(() => process.exit(1));
750
+ process.exit(1);
751
+ },
752
+ );
1041
753
  });
1042
754
 
1043
755
  if (existingServer) {
@@ -1046,19 +758,12 @@ export async function createDevServer({
1046
758
  existingServer.removeAllListeners("request");
1047
759
  existingServer.on("request", app);
1048
760
  httpServer = existingServer;
1049
- if (warmActivationStart) {
1050
- devServerMetrics.recordWarmHandoff(Date.now() - warmActivationStart);
1051
- }
1052
761
  logger.info(
1053
762
  `Attached full dev server to existing HTTP server on port ${port}`,
1054
763
  );
1055
764
  } else {
1056
765
  logger.info(`Starting HTTP server on port ${port}`);
1057
- // `app.listen()` returns `http.Server` synchronously — the previous
1058
- // `await` was a no-op on a non-Promise. The server isn't actually bound
1059
- // until it emits `listening`; if downstream code ever depends on the
1060
- // socket being bound, switch to `new Promise(r => httpServer.once("listening", r))`.
1061
- httpServer = app.listen(port);
766
+ httpServer = await app.listen(port);
1062
767
  logger.info(`HTTP server started successfully on port ${port}`);
1063
768
  }
1064
769
 
@@ -1076,7 +781,6 @@ export async function createDevServer({
1076
781
  return undefined;
1077
782
  });
1078
783
 
1079
- const viteEagerInitStart = Date.now();
1080
784
  const viteStartPromise = startVite({
1081
785
  port,
1082
786
  app,
@@ -1104,34 +808,11 @@ export async function createDevServer({
1104
808
  // below via the rejection branch of .then().
1105
809
  viteStartPromise.then(
1106
810
  (result) => {
1107
- devServerMetrics.recordViteEagerInit(
1108
- Date.now() - viteEagerInitStart,
1109
- "success",
1110
- );
1111
811
  logger.info("Vite server initialized eagerly");
1112
812
  viteResolve();
1113
813
  viteCreationResults = result;
1114
- // Start memory metrics only for claimed pods (after Vite init, not warm standby).
1115
- // Gated by either the LD flag (cloud) or the SABS-injected env var (cloud-prem/local).
1116
- if (
1117
- featureFlags?.devServerMemoryMetricsEnabled() ||
1118
- process.env.SUPERBLOCKS_DEV_SERVER_MEMORY_METRICS_ENABLED === "true"
1119
- ) {
1120
- startMemoryMetrics();
1121
- }
1122
- // Attach the WebSocket upgrade-tracking listener AFTER Vite has
1123
- // installed its own HMR upgrade handler. Registering an `upgrade`
1124
- // listener pre-Vite would suppress Node's default close-on-unhandled
1125
- // behavior, leaving editor upgrade attempts to hang until client
1126
- // timeout. By deferring until Vite is up, our listener coexists with
1127
- // the real handler instead of replacing the default close.
1128
- attachUpgradeMetricsListener(httpServer);
1129
814
  },
1130
815
  (e) => {
1131
- devServerMetrics.recordViteEagerInit(
1132
- Date.now() - viteEagerInitStart,
1133
- "error",
1134
- );
1135
816
  logger.error("Error initializing vite server", getErrorMeta(e));
1136
817
  viteReject(e);
1137
818
  },
@@ -1350,7 +1031,7 @@ const PRE_WARM_TIMEOUT_MS = 60_000;
1350
1031
 
1351
1032
  export async function preWarmViteCache(root: string): Promise<void> {
1352
1033
  const start = Date.now();
1353
- logger.info("[warm] Pre-warming Vite dependency cache...");
1034
+ console.log("[warm] Pre-warming Vite dependency cache...");
1354
1035
  try {
1355
1036
  const server = await createServer({
1356
1037
  root,
@@ -1372,26 +1053,11 @@ export async function preWarmViteCache(root: string): Promise<void> {
1372
1053
  await new Promise((r) => setTimeout(r, pollMs));
1373
1054
  }
1374
1055
  await server.close();
1375
- devServerMetrics.recordWarmPrewarm(Date.now() - start, "success");
1376
- logger.info(`[warm] Vite cache warmed in ${Date.now() - start}ms`);
1056
+ console.log(`[warm] Vite cache warmed in ${Date.now() - start}ms`);
1377
1057
  } catch (error) {
1378
- const durationMs = Date.now() - start;
1379
- // Buffer the metric in case `flush()` is somehow drained later; in practice
1380
- // we exit before activation, so this observation is also surfaced through
1381
- // the structured log line below — telemetry isn't initialized yet in
1382
- // warm-standby (no auth token until /_sb_activate), so `configureTelemetry`
1383
- // can't run here, and synchronously POSTing an OTLP payload before exit
1384
- // would block pod recycle on a network call that may itself be hung.
1385
- devServerMetrics.recordWarmPrewarm(durationMs, "error");
1386
- // Stable inline `key=value` marker so a log-based metric (Datadog
1387
- // "Generate Metrics from Logs", Loki regex) can recover pre-warm failures
1388
- // the OTel pipeline misses. The Logger.error signature accepts only
1389
- // ErrorMeta (no arbitrary attributes) and must stay structurally
1390
- // compatible with the sister Logger in vite-plugin-file-sync, so the
1391
- // markers are embedded in the message body instead of passed as fields.
1392
- logger.error(
1393
- `[warm] Vite cache pre-warm failed — exiting for fast pod recycle metric=dev_server_warm_prewarm outcome=error duration_ms=${durationMs}`,
1394
- getErrorMeta(error),
1058
+ console.error(
1059
+ `[warm] Vite cache pre-warm failed (${Date.now() - start}ms) exiting for fast pod recycle:`,
1060
+ error,
1395
1061
  );
1396
1062
  process.exit(1);
1397
1063
  }
@@ -1405,7 +1071,7 @@ function getFreePort() {
1405
1071
  server.listen(0, () => {
1406
1072
  const address = server.address();
1407
1073
  if (typeof address === "string") {
1408
- logger.debug(
1074
+ console.debug(
1409
1075
  `Port resolution: falling back to default HMR port ${DEFAULT_HMR_PORT}`,
1410
1076
  );
1411
1077
  resolve(DEFAULT_HMR_PORT);
@@ -1413,13 +1079,13 @@ function getFreePort() {
1413
1079
  }
1414
1080
  const port = address?.port;
1415
1081
  if (!port) {
1416
- logger.debug(
1082
+ console.debug(
1417
1083
  `Port resolution: no port available, using default HMR port ${DEFAULT_HMR_PORT}`,
1418
1084
  );
1419
1085
  resolve(DEFAULT_HMR_PORT);
1420
1086
  return;
1421
1087
  }
1422
- logger.debug(`Port resolution: successfully allocated port ${port}`);
1088
+ console.debug(`Port resolution: successfully allocated port ${port}`);
1423
1089
  server.close(() => resolve(port));
1424
1090
  });
1425
1091
  });
package/src/flag.ts CHANGED
@@ -17,6 +17,10 @@ export class FeatureFlags {
17
17
  return this.flags["superblocks.git.split.large.steps.enabled"] ?? false;
18
18
  }
19
19
 
20
+ splitLargeApiStepsInNewEnabled(): boolean {
21
+ return this.flags["superblocks.git.split.large.steps.new.enabled"] ?? false;
22
+ }
23
+
20
24
  linesForLargeSteps(): number {
21
25
  return (
22
26
  this.flags["superblocks.git.split.large.step.lines"] ??
@@ -43,8 +47,4 @@ export class FeatureFlags {
43
47
  includeDotSuperblocks(): boolean {
44
48
  return this.flags["superblocks.deploy.include-dot-superblocks"] ?? false;
45
49
  }
46
-
47
- devServerMemoryMetricsEnabled(): boolean {
48
- return this.flags["superblocks.dev-server.memory-metrics.enabled"] ?? false;
49
- }
50
50
  }