@superblocksteam/sdk 2.0.123 → 2.0.124-next.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/.turbo/turbo-build.log +1 -1
- package/dist/cli-replacement/automatic-upgrades.d.ts +37 -1
- package/dist/cli-replacement/automatic-upgrades.d.ts.map +1 -1
- package/dist/cli-replacement/automatic-upgrades.js +162 -10
- package/dist/cli-replacement/automatic-upgrades.js.map +1 -1
- package/dist/cli-replacement/automatic-upgrades.test.js +377 -8
- package/dist/cli-replacement/automatic-upgrades.test.js.map +1 -1
- package/dist/cli-replacement/dependency-install-classifier.d.mts +21 -0
- package/dist/cli-replacement/dependency-install-classifier.d.mts.map +1 -0
- package/dist/cli-replacement/dependency-install-classifier.mjs +83 -0
- package/dist/cli-replacement/dependency-install-classifier.mjs.map +1 -0
- package/dist/cli-replacement/dependency-install-classifier.test.d.mts +2 -0
- package/dist/cli-replacement/dependency-install-classifier.test.d.mts.map +1 -0
- package/dist/cli-replacement/dependency-install-classifier.test.mjs +51 -0
- package/dist/cli-replacement/dependency-install-classifier.test.mjs.map +1 -0
- package/dist/cli-replacement/dev-s3-restore.test.mjs +170 -14
- package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -1
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs +33 -2
- package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs.map +1 -1
- package/dist/cli-replacement/dev-token-priming.test.d.mts +31 -0
- package/dist/cli-replacement/dev-token-priming.test.d.mts.map +1 -0
- package/dist/cli-replacement/dev-token-priming.test.mjs +87 -0
- package/dist/cli-replacement/dev-token-priming.test.mjs.map +1 -0
- package/dist/cli-replacement/dev.d.mts +36 -0
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.interception.test.d.mts +2 -0
- package/dist/cli-replacement/dev.interception.test.d.mts.map +1 -0
- package/dist/cli-replacement/dev.interception.test.mjs +68 -0
- package/dist/cli-replacement/dev.interception.test.mjs.map +1 -0
- package/dist/cli-replacement/dev.mjs +396 -62
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/dist/cli-replacement/home-npmrc.d.mts +180 -0
- package/dist/cli-replacement/home-npmrc.d.mts.map +1 -0
- package/dist/cli-replacement/home-npmrc.mjs +283 -0
- package/dist/cli-replacement/home-npmrc.mjs.map +1 -0
- package/dist/cli-replacement/home-npmrc.test.d.mts +10 -0
- package/dist/cli-replacement/home-npmrc.test.d.mts.map +1 -0
- package/dist/cli-replacement/home-npmrc.test.mjs +582 -0
- package/dist/cli-replacement/home-npmrc.test.mjs.map +1 -0
- package/dist/cli-replacement/install-packages.classify.test.d.mts +2 -0
- package/dist/cli-replacement/install-packages.classify.test.d.mts.map +1 -0
- package/dist/cli-replacement/install-packages.classify.test.mjs +125 -0
- package/dist/cli-replacement/install-packages.classify.test.mjs.map +1 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.d.mts +2 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.d.mts.map +1 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.mjs +260 -0
- package/dist/cli-replacement/install-packages.npm-registry.test.mjs.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.d.mts +58 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.d.mts.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.mjs +224 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.mjs.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.d.mts +11 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.d.mts.map +1 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.mjs +317 -0
- package/dist/cli-replacement/post-upgrade-lockfile-strip.test.mjs.map +1 -0
- package/dist/cli-replacement/userconfig-env.integration.test.d.mts +26 -0
- package/dist/cli-replacement/userconfig-env.integration.test.d.mts.map +1 -0
- package/dist/cli-replacement/userconfig-env.integration.test.mjs +148 -0
- package/dist/cli-replacement/userconfig-env.integration.test.mjs.map +1 -0
- package/dist/dev-utils/dev-server-metrics.d.mts +25 -0
- package/dist/dev-utils/dev-server-metrics.d.mts.map +1 -1
- package/dist/dev-utils/dev-server-metrics.mjs +84 -0
- package/dist/dev-utils/dev-server-metrics.mjs.map +1 -1
- package/dist/dev-utils/dev-server-metrics.test.d.mts +2 -0
- package/dist/dev-utils/dev-server-metrics.test.d.mts.map +1 -0
- package/dist/dev-utils/dev-server-metrics.test.mjs +26 -0
- package/dist/dev-utils/dev-server-metrics.test.mjs.map +1 -0
- package/dist/dev-utils/dev-server.d.mts +23 -1
- package/dist/dev-utils/dev-server.d.mts.map +1 -1
- package/dist/dev-utils/dev-server.mjs +21 -9
- package/dist/dev-utils/dev-server.mjs.map +1 -1
- package/dist/dev-utils/dev-server.status.test.d.mts +2 -0
- package/dist/dev-utils/dev-server.status.test.d.mts.map +1 -0
- package/dist/dev-utils/dev-server.status.test.mjs +41 -0
- package/dist/dev-utils/dev-server.status.test.mjs.map +1 -0
- package/dist/dev-utils/token-manager.d.ts +31 -0
- package/dist/dev-utils/token-manager.d.ts.map +1 -1
- package/dist/dev-utils/token-manager.js +34 -0
- package/dist/dev-utils/token-manager.js.map +1 -1
- package/dist/telemetry/local-obs.js +1 -1
- package/dist/telemetry/local-obs.js.map +1 -1
- package/dist/telemetry/util.js +1 -1
- package/dist/types/scoped-jwt-token-payload.d.ts +1 -0
- package/dist/types/scoped-jwt-token-payload.d.ts.map +1 -1
- package/dist/version-control.d.mts.map +1 -1
- package/dist/version-control.mjs +6 -7
- package/dist/version-control.mjs.map +1 -1
- package/package.json +12 -12
- package/src/cli-replacement/automatic-upgrades.test.ts +530 -8
- package/src/cli-replacement/automatic-upgrades.ts +179 -7
- package/src/cli-replacement/dependency-install-classifier.mts +118 -0
- package/src/cli-replacement/dependency-install-classifier.test.mts +72 -0
- package/src/cli-replacement/dev-s3-restore.test.mts +210 -14
- package/src/cli-replacement/dev-startup-git-before-dbfs-order.test.mts +35 -2
- package/src/cli-replacement/dev-token-priming.test.mts +103 -0
- package/src/cli-replacement/dev.interception.test.mts +80 -0
- package/src/cli-replacement/dev.mts +495 -92
- package/src/cli-replacement/home-npmrc.mts +409 -0
- package/src/cli-replacement/home-npmrc.test.mts +757 -0
- package/src/cli-replacement/install-packages.classify.test.mts +168 -0
- package/src/cli-replacement/install-packages.npm-registry.test.mts +345 -0
- package/src/cli-replacement/post-upgrade-lockfile-strip.mts +296 -0
- package/src/cli-replacement/post-upgrade-lockfile-strip.test.mts +482 -0
- package/src/cli-replacement/userconfig-env.integration.test.mts +189 -0
- package/src/dev-utils/dev-server-metrics.mts +96 -0
- package/src/dev-utils/dev-server-metrics.test.mts +38 -0
- package/src/dev-utils/dev-server.mts +48 -8
- package/src/dev-utils/dev-server.status.test.mts +58 -0
- package/src/dev-utils/token-manager.ts +36 -0
- package/src/telemetry/local-obs.ts +1 -1
- package/src/telemetry/util.ts +1 -1
- package/src/types/scoped-jwt-token-payload.ts +1 -0
- package/src/version-control.mts +8 -6
- package/tsconfig.tsbuildinfo +1 -1
- package/.turbo/turbo-publish-package.log +0 -0
|
@@ -16,6 +16,62 @@ import { isTelemetryInitialized } from "@superblocksteam/telemetry";
|
|
|
16
16
|
|
|
17
17
|
import { getMeter } from "../telemetry/index.js";
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Allowlist of npm/pnpm error codes the classifier can attach to a
|
|
21
|
+
* `DependencyInstallError`. Used to BOUND the `npm_error_code` metric label:
|
|
22
|
+
* the classifier's unknown-branch extracts the code via a `(\S+)` regex, so an
|
|
23
|
+
* unexpected, malformed, or registry-injected token must never become a new
|
|
24
|
+
* time series. Any code outside this set maps to `"other"`; pnpm's variable
|
|
25
|
+
* `ERR_PNPM_FETCH_<status>` collapses to a single bucket.
|
|
26
|
+
*/
|
|
27
|
+
const KNOWN_NPM_ERROR_CODES = new Set<string>([
|
|
28
|
+
// dependency-graph conflicts (the repro)
|
|
29
|
+
"ERESOLVE",
|
|
30
|
+
"ETARGET",
|
|
31
|
+
"EPEERINVALID",
|
|
32
|
+
// not in registry
|
|
33
|
+
"E404",
|
|
34
|
+
// auth
|
|
35
|
+
"E401",
|
|
36
|
+
"E403",
|
|
37
|
+
"EAUTH",
|
|
38
|
+
"EAUTHIP",
|
|
39
|
+
"EAUTHUNKNOWN",
|
|
40
|
+
"EOTP",
|
|
41
|
+
// network / unreachable
|
|
42
|
+
"ENOTFOUND",
|
|
43
|
+
"EAI_AGAIN",
|
|
44
|
+
"ECONNREFUSED",
|
|
45
|
+
"ECONNRESET",
|
|
46
|
+
"ENETUNREACH",
|
|
47
|
+
"ETIMEDOUT",
|
|
48
|
+
"ESOCKETTIMEDOUT",
|
|
49
|
+
// tls
|
|
50
|
+
"UNABLE_TO_VERIFY_LEAF_SIGNATURE",
|
|
51
|
+
"SELF_SIGNED_CERT_IN_CHAIN",
|
|
52
|
+
"DEPTH_ZERO_SELF_SIGNED_CERT",
|
|
53
|
+
"CERT_HAS_EXPIRED",
|
|
54
|
+
"CERT_NOT_YET_VALID",
|
|
55
|
+
"CERT_UNTRUSTED",
|
|
56
|
+
"UNABLE_TO_GET_ISSUER_CERT",
|
|
57
|
+
"UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
|
|
58
|
+
// pnpm
|
|
59
|
+
"ERR_PNPM_META_FETCH_FAIL",
|
|
60
|
+
"ERR_PNPM_FETCH", // collapsed bucket for ERR_PNPM_FETCH_<status>
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Normalize an npm/pnpm error code into a bounded metric-label value. Known
|
|
65
|
+
* codes pass through (keeping the diagnostic facet); everything else collapses
|
|
66
|
+
* to `"other"` (or `"none"` when absent) so the
|
|
67
|
+
* `dev_server_initial_install_failure_total` series count stays bounded.
|
|
68
|
+
*/
|
|
69
|
+
export function normalizeNpmErrorCodeTag(code: string | undefined): string {
|
|
70
|
+
if (!code) return "none";
|
|
71
|
+
const collapsed = /^ERR_PNPM_FETCH_\d+$/.test(code) ? "ERR_PNPM_FETCH" : code;
|
|
72
|
+
return KNOWN_NPM_ERROR_CODES.has(collapsed) ? collapsed : "other";
|
|
73
|
+
}
|
|
74
|
+
|
|
19
75
|
/** Dev-server endpoints that produce per-request metrics. */
|
|
20
76
|
export type DevServerEndpoint =
|
|
21
77
|
| "_sb_health"
|
|
@@ -246,6 +302,46 @@ class DevServerMetrics {
|
|
|
246
302
|
.record(durationMs);
|
|
247
303
|
});
|
|
248
304
|
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Records a degraded startup caused by a failed initial dependency install.
|
|
308
|
+
*
|
|
309
|
+
* All tags are low-cardinality:
|
|
310
|
+
* - `category` is a closed enum from DependencyInstallError (safe).
|
|
311
|
+
* - `npm_error_code` is a bounded npm error code string (e.g. "E401",
|
|
312
|
+
* "ERESOLVE") or `"none"` when absent.
|
|
313
|
+
* - `has_any_registry_configured` is a boolean string (`"true"` /
|
|
314
|
+
* `"false"`) or `"unknown"` when the tri-state is undefined.
|
|
315
|
+
*
|
|
316
|
+
* Do NOT pass raw package names, registry hostnames, or rawError — those
|
|
317
|
+
* are high-cardinality or contain customer-infra identifiers.
|
|
318
|
+
*/
|
|
319
|
+
recordInitialInstallFailure({
|
|
320
|
+
category,
|
|
321
|
+
npmErrorCode,
|
|
322
|
+
hasAnyRegistryConfigured,
|
|
323
|
+
}: {
|
|
324
|
+
category: string;
|
|
325
|
+
npmErrorCode?: string;
|
|
326
|
+
hasAnyRegistryConfigured?: boolean;
|
|
327
|
+
}): void {
|
|
328
|
+
const labels = {
|
|
329
|
+
category,
|
|
330
|
+
npm_error_code: normalizeNpmErrorCodeTag(npmErrorCode),
|
|
331
|
+
has_any_registry_configured:
|
|
332
|
+
hasAnyRegistryConfigured === undefined
|
|
333
|
+
? "unknown"
|
|
334
|
+
: String(hasAnyRegistryConfigured),
|
|
335
|
+
};
|
|
336
|
+
this.record(() => {
|
|
337
|
+
getMeter()
|
|
338
|
+
.createCounter("dev_server_initial_install_failure_total", {
|
|
339
|
+
description:
|
|
340
|
+
"Count of degraded dev-server startups caused by a failed initial dependency install.",
|
|
341
|
+
})
|
|
342
|
+
.add(1, labels);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
249
345
|
}
|
|
250
346
|
|
|
251
347
|
/** Process-wide dev-server metrics singleton. */
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { normalizeNpmErrorCodeTag } from "./dev-server-metrics.mjs";
|
|
4
|
+
|
|
5
|
+
describe("normalizeNpmErrorCodeTag (metric cardinality bound)", () => {
|
|
6
|
+
it("passes through known npm codes", () => {
|
|
7
|
+
expect(normalizeNpmErrorCodeTag("ERESOLVE")).toBe("ERESOLVE");
|
|
8
|
+
expect(normalizeNpmErrorCodeTag("E404")).toBe("E404");
|
|
9
|
+
expect(normalizeNpmErrorCodeTag("E401")).toBe("E401");
|
|
10
|
+
expect(normalizeNpmErrorCodeTag("ENOTFOUND")).toBe("ENOTFOUND");
|
|
11
|
+
expect(normalizeNpmErrorCodeTag("CERT_HAS_EXPIRED")).toBe(
|
|
12
|
+
"CERT_HAS_EXPIRED",
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("collapses variable pnpm fetch codes into one bucket", () => {
|
|
17
|
+
expect(normalizeNpmErrorCodeTag("ERR_PNPM_FETCH_404")).toBe(
|
|
18
|
+
"ERR_PNPM_FETCH",
|
|
19
|
+
);
|
|
20
|
+
expect(normalizeNpmErrorCodeTag("ERR_PNPM_FETCH_500")).toBe(
|
|
21
|
+
"ERR_PNPM_FETCH",
|
|
22
|
+
);
|
|
23
|
+
expect(normalizeNpmErrorCodeTag("ERR_PNPM_META_FETCH_FAIL")).toBe(
|
|
24
|
+
"ERR_PNPM_META_FETCH_FAIL",
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("maps unknown / arbitrary / injected tokens to 'other' (cardinality guard)", () => {
|
|
29
|
+
expect(normalizeNpmErrorCodeTag("EWEIRD")).toBe("other");
|
|
30
|
+
expect(normalizeNpmErrorCodeTag("some-random-token")).toBe("other");
|
|
31
|
+
expect(normalizeNpmErrorCodeTag("`;rm -rf /#")).toBe("other");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("maps an absent code to 'none'", () => {
|
|
35
|
+
expect(normalizeNpmErrorCodeTag(undefined)).toBe("none");
|
|
36
|
+
expect(normalizeNpmErrorCodeTag("")).toBe("none");
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -14,6 +14,7 @@ import type { HmrOptions, Plugin, UserConfig } from "vite";
|
|
|
14
14
|
import type { ViteDevServer } from "vite";
|
|
15
15
|
import tsconfigPaths from "vite-tsconfig-paths";
|
|
16
16
|
|
|
17
|
+
import type { ServerError } from "@superblocksteam/library-shared/types";
|
|
17
18
|
import {
|
|
18
19
|
JwtVerifier,
|
|
19
20
|
SUPERBLOCKS_LIVE_GIT_BRANCH,
|
|
@@ -430,6 +431,14 @@ interface CreateDevServerOptions {
|
|
|
430
431
|
* responding before the full server takes over.
|
|
431
432
|
*/
|
|
432
433
|
warmActivationStart?: number;
|
|
434
|
+
/**
|
|
435
|
+
* Server-side errors (e.g. dependency-install failures) to surface to the UI
|
|
436
|
+
* via `/_sb_connect` and `/_sb_status`. Optional and read at request time, so
|
|
437
|
+
* the caller can mutate the referenced object after `createDevServer` returns
|
|
438
|
+
* and have later requests reflect the change. Treated as `{ serverErrors: [] }`
|
|
439
|
+
* when absent.
|
|
440
|
+
*/
|
|
441
|
+
devServerStatus?: { serverErrors: ServerError[] };
|
|
433
442
|
}
|
|
434
443
|
|
|
435
444
|
let httpServer: http.Server;
|
|
@@ -472,6 +481,19 @@ function attachUpgradeMetricsListener(server: http.Server): void {
|
|
|
472
481
|
});
|
|
473
482
|
}
|
|
474
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Merges the live `serverErrors` list onto a status/health payload returned by
|
|
486
|
+
* `/_sb_connect` and `/_sb_status`. `devServerStatus` is optional and may omit
|
|
487
|
+
* `serverErrors`; both cases yield an empty array so the UI always receives a
|
|
488
|
+
* well-formed `serverErrors` field.
|
|
489
|
+
*/
|
|
490
|
+
export function buildStatusPayload<T extends object>(
|
|
491
|
+
base: T,
|
|
492
|
+
devServerStatus?: { serverErrors?: ServerError[] },
|
|
493
|
+
): T & { serverErrors: ServerError[] } {
|
|
494
|
+
return { ...base, serverErrors: devServerStatus?.serverErrors ?? [] };
|
|
495
|
+
}
|
|
496
|
+
|
|
475
497
|
// The "Dev Server" is a long-running HTTP server that manages the Vite server
|
|
476
498
|
export async function createDevServer({
|
|
477
499
|
root,
|
|
@@ -490,6 +512,7 @@ export async function createDevServer({
|
|
|
490
512
|
superblocksBaseUrl: explicitBaseUrl,
|
|
491
513
|
existingServer,
|
|
492
514
|
warmActivationStart,
|
|
515
|
+
devServerStatus,
|
|
493
516
|
}: CreateDevServerOptions) {
|
|
494
517
|
const logger = getLogger(loggerOverride);
|
|
495
518
|
if (httpServer) {
|
|
@@ -818,16 +841,32 @@ export async function createDevServer({
|
|
|
818
841
|
// Wait for it to be ready before responding.
|
|
819
842
|
try {
|
|
820
843
|
await vitePromise;
|
|
821
|
-
res.send(
|
|
844
|
+
res.send(
|
|
845
|
+
JSON.stringify(buildStatusPayload(healthResponse, devServerStatus)),
|
|
846
|
+
);
|
|
822
847
|
} catch (e) {
|
|
823
848
|
logger.error(
|
|
824
849
|
"Vite server failed to initialize",
|
|
825
850
|
getErrorMeta(e as Error),
|
|
826
851
|
);
|
|
827
852
|
res.locals.sbFailureType = "vite_init";
|
|
853
|
+
// Carry `devServerStatus.serverErrors` onto the failure body too.
|
|
854
|
+
// The 200 path uses `buildStatusPayload` to surface startup errors
|
|
855
|
+
// (e.g. dependency-install failures) to the editor's readiness path;
|
|
856
|
+
// doing the same here means a Vite-init failure that also recorded
|
|
857
|
+
// an install failure doesn't silently drop the install context from
|
|
858
|
+
// the wire — any consumer that inspects the non-ok body still sees
|
|
859
|
+
// a well-formed `serverErrors` array alongside the generic `error`.
|
|
828
860
|
res
|
|
829
861
|
.status(500)
|
|
830
|
-
.send(
|
|
862
|
+
.send(
|
|
863
|
+
JSON.stringify(
|
|
864
|
+
buildStatusPayload(
|
|
865
|
+
{ error: "Dev server failed to initialize" },
|
|
866
|
+
devServerStatus,
|
|
867
|
+
),
|
|
868
|
+
),
|
|
869
|
+
);
|
|
831
870
|
}
|
|
832
871
|
});
|
|
833
872
|
|
|
@@ -883,13 +922,14 @@ export async function createDevServer({
|
|
|
883
922
|
app.get("/_sb_status", authHandler, async (_req, res) => {
|
|
884
923
|
res.setHeader("Content-Type", "application/json");
|
|
885
924
|
if (lockService?.isLocked) {
|
|
886
|
-
res.send(
|
|
887
|
-
|
|
888
|
-
|
|
925
|
+
res.send(
|
|
926
|
+
buildStatusPayload(
|
|
927
|
+
{ connectedUsers: lockService.connectedUsers },
|
|
928
|
+
devServerStatus,
|
|
929
|
+
),
|
|
930
|
+
);
|
|
889
931
|
} else {
|
|
890
|
-
res.send({
|
|
891
|
-
connectedUsers: [],
|
|
892
|
-
});
|
|
932
|
+
res.send(buildStatusPayload({ connectedUsers: [] }, devServerStatus));
|
|
893
933
|
}
|
|
894
934
|
});
|
|
895
935
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type { ServerError } from "@superblocksteam/library-shared/types";
|
|
4
|
+
|
|
5
|
+
// Static import so the slow `dev-server.mjs` module graph (vite, react plugin,
|
|
6
|
+
// workspace deps) loads during file evaluation rather than inside each test,
|
|
7
|
+
// where the cold-import cost easily exceeds the default 5s test timeout.
|
|
8
|
+
import { buildStatusPayload } from "./dev-server.mjs";
|
|
9
|
+
|
|
10
|
+
describe("buildStatusPayload", () => {
|
|
11
|
+
it("merges serverErrors from devServerStatus onto the base payload", () => {
|
|
12
|
+
const installError: ServerError = {
|
|
13
|
+
type: "dev-server/dependency-install",
|
|
14
|
+
timestamp: "2026-05-29T00:00:00.000Z",
|
|
15
|
+
category: "not_in_registry",
|
|
16
|
+
packages: [{ name: "left-pad" }],
|
|
17
|
+
rawError: "boom",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
expect(
|
|
21
|
+
buildStatusPayload(
|
|
22
|
+
{ connectedUsers: ["a@example.com"] },
|
|
23
|
+
{ serverErrors: [installError] },
|
|
24
|
+
),
|
|
25
|
+
).toEqual({
|
|
26
|
+
connectedUsers: ["a@example.com"],
|
|
27
|
+
serverErrors: [installError],
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("preserves all base fields while adding serverErrors", () => {
|
|
32
|
+
expect(
|
|
33
|
+
buildStatusPayload(
|
|
34
|
+
{ status: "healthy", applicationId: "app-1", cliVersion: "1.2.3" },
|
|
35
|
+
{ serverErrors: [] },
|
|
36
|
+
),
|
|
37
|
+
).toEqual({
|
|
38
|
+
status: "healthy",
|
|
39
|
+
applicationId: "app-1",
|
|
40
|
+
cliVersion: "1.2.3",
|
|
41
|
+
serverErrors: [],
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("yields an empty serverErrors array when devServerStatus is absent", () => {
|
|
46
|
+
expect(buildStatusPayload({ connectedUsers: [] })).toEqual({
|
|
47
|
+
connectedUsers: [],
|
|
48
|
+
serverErrors: [],
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("yields an empty serverErrors array when devServerStatus.serverErrors is absent", () => {
|
|
53
|
+
expect(buildStatusPayload({ connectedUsers: [] }, {})).toEqual({
|
|
54
|
+
connectedUsers: [],
|
|
55
|
+
serverErrors: [],
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -2,8 +2,44 @@ import { EventEmitter } from "node:events";
|
|
|
2
2
|
|
|
3
3
|
import type { ITokenManager, TokenUpdateEvent } from "@superblocksteam/util";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Holds the active bearer credential and notifies subscribers on change.
|
|
7
|
+
*
|
|
8
|
+
* Two access patterns are intentionally supported and serve distinct roles:
|
|
9
|
+
*
|
|
10
|
+
* - `updateToken(t)` writes the current token AND emits `tokenUpdated`.
|
|
11
|
+
* The event stream is the **refresh channel**: subscribers (e.g.
|
|
12
|
+
* `AutoConnectingRpcClient`, `AiService`) react to changes (e.g. tear
|
|
13
|
+
* down a socket and reconnect with the new credential).
|
|
14
|
+
*
|
|
15
|
+
* - `getCurrentToken()` returns the most recently set token, or
|
|
16
|
+
* `undefined` if none. This is the **seed channel**: consumers that
|
|
17
|
+
* need a bearer credential at construction time read it directly.
|
|
18
|
+
* Without this, a consumer whose listener is attached *after* a prior
|
|
19
|
+
* `updateToken()` would silently observe nothing — Node `EventEmitter`
|
|
20
|
+
* has no replay. Closes the cold-start gap for `NpmRegistryClient`
|
|
21
|
+
* when the CLI primes the manager from `tokenConfig.token` at startup.
|
|
22
|
+
*
|
|
23
|
+
* Events are deliberately NOT auto-replayed on `on(...)`: a fresh
|
|
24
|
+
* subscriber receives only future emissions. Auto-replay would make a
|
|
25
|
+
* late-attached refresh handler (e.g. one that closes and reconnects a
|
|
26
|
+
* socket) fire on what is conceptually a seed read, producing spurious
|
|
27
|
+
* tear-down/reconnect churn.
|
|
28
|
+
*
|
|
29
|
+
* The current implementation does not surface a `clearToken()` API.
|
|
30
|
+
* Today's lifecycle never needs one — a process holds at most one user's
|
|
31
|
+
* credentials and exits when they log out. Add one if/when a multi-tenant
|
|
32
|
+
* or in-process re-auth path requires it.
|
|
33
|
+
*/
|
|
5
34
|
export class TokenManager extends EventEmitter implements ITokenManager {
|
|
35
|
+
private currentToken: string | undefined;
|
|
36
|
+
|
|
6
37
|
updateToken(newToken: string): void {
|
|
38
|
+
this.currentToken = newToken;
|
|
7
39
|
this.emit("tokenUpdated", { token: newToken } as TokenUpdateEvent);
|
|
8
40
|
}
|
|
41
|
+
|
|
42
|
+
getCurrentToken(): string | undefined {
|
|
43
|
+
return this.currentToken;
|
|
44
|
+
}
|
|
9
45
|
}
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import { DeploymentTypeEnum } from "@superblocksteam/shared";
|
|
27
27
|
|
|
28
|
-
const DEFAULT_LOCAL_OTEL_ENDPOINT = "http://localhost:
|
|
28
|
+
const DEFAULT_LOCAL_OTEL_ENDPOINT = "http://localhost:4318";
|
|
29
29
|
const DEFAULT_ML_APP_NAME = "superblocks-ai-code-gen";
|
|
30
30
|
|
|
31
31
|
export interface LocalObsConfig {
|
package/src/telemetry/util.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const SERVICE_NAME = "sdk-dev-server";
|
|
|
9
9
|
* 1. SUPERBLOCKS_OTEL_COLLECTOR_URL env var (for local obs mode)
|
|
10
10
|
* 2. Superblocks API endpoint derived from base URL
|
|
11
11
|
*
|
|
12
|
-
* When SUPERBLOCKS_OTEL_COLLECTOR_URL is set (e.g., http://localhost:
|
|
12
|
+
* When SUPERBLOCKS_OTEL_COLLECTOR_URL is set (e.g., http://localhost:4318),
|
|
13
13
|
* telemetry is sent directly to the local OTEL collector.
|
|
14
14
|
*
|
|
15
15
|
* Note: This returns the BASE URL. The telemetry library appends /v1/traces,
|
package/src/version-control.mts
CHANGED
|
@@ -511,13 +511,15 @@ export async function writeResourceToDisk(
|
|
|
511
511
|
getApiRepresentation(featureFlags, backendConfig);
|
|
512
512
|
|
|
513
513
|
// Write the API file(s)
|
|
514
|
+
const apiPromises: Array<Promise<void>> = [];
|
|
514
515
|
const apiInfo = await writeBackendApi(
|
|
515
516
|
resource,
|
|
516
517
|
backendDirName,
|
|
517
|
-
|
|
518
|
+
apiPromises,
|
|
518
519
|
apiRepresentation,
|
|
519
520
|
existingFilePaths,
|
|
520
521
|
);
|
|
522
|
+
await Promise.all(apiPromises);
|
|
521
523
|
if (apiRepresentation.extractLargeSourceFiles) {
|
|
522
524
|
backendConfig.sourceFiles = apiInfo.sourceFiles;
|
|
523
525
|
}
|
|
@@ -696,7 +698,6 @@ async function writeV1ApplicationToDisk(
|
|
|
696
698
|
}
|
|
697
699
|
}
|
|
698
700
|
|
|
699
|
-
const appApis: Record<string, ApiInfo> = {};
|
|
700
701
|
if (resource.apis && resource.apis.length) {
|
|
701
702
|
for (const api of resource.apis as ApiWrapper[]) {
|
|
702
703
|
const apiInfo = await writeAppApi(
|
|
@@ -719,11 +720,12 @@ async function writeV1ApplicationToDisk(
|
|
|
719
720
|
newApplicationConfig.apis[apiId] = apiInfo.name;
|
|
720
721
|
}
|
|
721
722
|
}
|
|
722
|
-
await Promise.all(apiPromises);
|
|
723
723
|
}
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
724
|
+
await Promise.all(apiPromises);
|
|
725
|
+
if (
|
|
726
|
+
!apiRepresentation.extractLargeSourceFiles &&
|
|
727
|
+
!newApplicationConfig.apis
|
|
728
|
+
) {
|
|
727
729
|
// Make sure there is an empty object even if there are no APIs
|
|
728
730
|
newApplicationConfig.apis = {};
|
|
729
731
|
}
|