@superblocksteam/sdk 2.0.130-next.2 → 2.0.130-next.4

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 (47) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/cli-replacement/dependency-install-classifier.d.mts.map +1 -1
  3. package/dist/cli-replacement/dependency-install-classifier.mjs +11 -0
  4. package/dist/cli-replacement/dependency-install-classifier.mjs.map +1 -1
  5. package/dist/cli-replacement/dependency-install-classifier.test.mjs +26 -0
  6. package/dist/cli-replacement/dependency-install-classifier.test.mjs.map +1 -1
  7. package/dist/cli-replacement/dev.d.mts.map +1 -1
  8. package/dist/cli-replacement/dev.mjs +63 -4
  9. package/dist/cli-replacement/dev.mjs.map +1 -1
  10. package/dist/cli-replacement/disk-space.d.mts +86 -0
  11. package/dist/cli-replacement/disk-space.d.mts.map +1 -0
  12. package/dist/cli-replacement/disk-space.mjs +133 -0
  13. package/dist/cli-replacement/disk-space.mjs.map +1 -0
  14. package/dist/cli-replacement/disk-space.test.d.mts +2 -0
  15. package/dist/cli-replacement/disk-space.test.d.mts.map +1 -0
  16. package/dist/cli-replacement/disk-space.test.mjs +186 -0
  17. package/dist/cli-replacement/disk-space.test.mjs.map +1 -0
  18. package/dist/dev-utils/dev-server-metrics.d.mts +11 -0
  19. package/dist/dev-utils/dev-server-metrics.d.mts.map +1 -1
  20. package/dist/dev-utils/dev-server-metrics.mjs +18 -0
  21. package/dist/dev-utils/dev-server-metrics.mjs.map +1 -1
  22. package/dist/telemetry/memory-metrics.d.ts +7 -0
  23. package/dist/telemetry/memory-metrics.d.ts.map +1 -1
  24. package/dist/telemetry/memory-metrics.js +221 -44
  25. package/dist/telemetry/memory-metrics.js.map +1 -1
  26. package/dist/telemetry/memory-metrics.test.d.ts +2 -0
  27. package/dist/telemetry/memory-metrics.test.d.ts.map +1 -0
  28. package/dist/telemetry/memory-metrics.test.js +256 -0
  29. package/dist/telemetry/memory-metrics.test.js.map +1 -0
  30. package/dist/vite-plugin-sdk-api-entry-point.d.mts +14 -1
  31. package/dist/vite-plugin-sdk-api-entry-point.d.mts.map +1 -1
  32. package/dist/vite-plugin-sdk-api-entry-point.mjs +68 -7
  33. package/dist/vite-plugin-sdk-api-entry-point.mjs.map +1 -1
  34. package/dist/vite-plugin-sdk-api-entry-point.test.mjs +53 -1
  35. package/dist/vite-plugin-sdk-api-entry-point.test.mjs.map +1 -1
  36. package/package.json +6 -6
  37. package/src/cli-replacement/dependency-install-classifier.mts +14 -0
  38. package/src/cli-replacement/dependency-install-classifier.test.mts +39 -0
  39. package/src/cli-replacement/dev.mts +81 -10
  40. package/src/cli-replacement/disk-space.mts +216 -0
  41. package/src/cli-replacement/disk-space.test.mts +246 -0
  42. package/src/dev-utils/dev-server-metrics.mts +20 -0
  43. package/src/telemetry/memory-metrics.test.ts +363 -0
  44. package/src/telemetry/memory-metrics.ts +329 -60
  45. package/src/vite-plugin-sdk-api-entry-point.mts +79 -9
  46. package/src/vite-plugin-sdk-api-entry-point.test.mts +134 -1
  47. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,363 @@
1
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import type * as PerfHooks from "node:perf_hooks";
5
+
6
+ import { beforeEach, describe, expect, it, vi } from "vitest";
7
+
8
+ const h = vi.hoisted(() => {
9
+ type ObservableInstrument = {
10
+ name: string;
11
+ callback?: (result: {
12
+ observe: (
13
+ instrument: { name: string },
14
+ value: number,
15
+ attrs?: Record<string, unknown>,
16
+ ) => void;
17
+ }) => void;
18
+ };
19
+
20
+ const gauges: Array<{
21
+ name: string;
22
+ callback?: (result: {
23
+ observe: (
24
+ gauge: { name: string },
25
+ value: number,
26
+ attrs?: Record<string, unknown>,
27
+ ) => void;
28
+ }) => void;
29
+ }> = [];
30
+ const observableCounters: ObservableInstrument[] = [];
31
+ const counters: Array<{
32
+ name: string;
33
+ adds: Array<{ value: number; attrs?: Record<string, unknown> }>;
34
+ }> = [];
35
+ const histograms: Array<{
36
+ name: string;
37
+ options?: Record<string, unknown>;
38
+ records: Array<{ value: number; attrs?: Record<string, unknown> }>;
39
+ }> = [];
40
+
41
+ return {
42
+ gauges,
43
+ observableCounters,
44
+ counters,
45
+ histograms,
46
+ throwOnGaugeName: undefined as string | undefined,
47
+ delayMonitor: {
48
+ enable: vi.fn(),
49
+ disable: vi.fn(),
50
+ reset: vi.fn(),
51
+ mean: 2_000_000,
52
+ max: 80_000_000,
53
+ percentile: vi.fn((p: number) => p * 1_000_000),
54
+ },
55
+ gcCallback: undefined as
56
+ | ((list: { getEntries: () => unknown[] }) => void)
57
+ | undefined,
58
+ elu: vi.fn(),
59
+ addBatchObservableCallback: vi.fn(
60
+ (
61
+ callback: (result: {
62
+ observe: (
63
+ gauge: { name: string },
64
+ value: number,
65
+ attrs?: Record<string, unknown>,
66
+ ) => void;
67
+ }) => void,
68
+ callbackInstruments: Array<{ name: string }>,
69
+ ) => {
70
+ for (const instrument of callbackInstruments) {
71
+ const captured = [...gauges, ...observableCounters].find(
72
+ (g) => g.name === instrument.name,
73
+ );
74
+ if (captured) captured.callback = callback;
75
+ }
76
+ },
77
+ ),
78
+ removeBatchObservableCallback: vi.fn(),
79
+ };
80
+ });
81
+
82
+ vi.mock("node:perf_hooks", async () => {
83
+ const actual = await vi.importActual<typeof PerfHooks>("node:perf_hooks");
84
+ return {
85
+ ...actual,
86
+ PerformanceObserver: class {
87
+ constructor(callback: (list: { getEntries: () => unknown[] }) => void) {
88
+ h.gcCallback = callback;
89
+ }
90
+ observe = vi.fn();
91
+ disconnect = vi.fn();
92
+ },
93
+ monitorEventLoopDelay: vi.fn(() => h.delayMonitor),
94
+ performance: {
95
+ ...actual.performance,
96
+ eventLoopUtilization: h.elu,
97
+ },
98
+ };
99
+ });
100
+
101
+ vi.mock("./index.js", () => ({
102
+ getMeter: () => ({
103
+ createObservableGauge: (name: string) => {
104
+ if (h.throwOnGaugeName === name) {
105
+ throw new Error(`createObservableGauge failed for ${name}`);
106
+ }
107
+ const gauge = { name };
108
+ h.gauges.push(gauge);
109
+ return gauge;
110
+ },
111
+ createObservableCounter: (name: string) => {
112
+ const counter = { name };
113
+ h.observableCounters.push(counter);
114
+ return counter;
115
+ },
116
+ createCounter: (name: string) => {
117
+ const counter: {
118
+ name: string;
119
+ adds: Array<{ value: number; attrs?: Record<string, unknown> }>;
120
+ } = { name, adds: [] };
121
+ h.counters.push(counter);
122
+ return {
123
+ add: (value: number, attrs?: Record<string, unknown>) =>
124
+ counter.adds.push({ value, attrs }),
125
+ };
126
+ },
127
+ createHistogram: (name: string, options?: Record<string, unknown>) => {
128
+ const histogram: {
129
+ name: string;
130
+ options?: Record<string, unknown>;
131
+ records: Array<{ value: number; attrs?: Record<string, unknown> }>;
132
+ } = { name, options, records: [] };
133
+ h.histograms.push(histogram);
134
+ return {
135
+ record: (value: number, attrs?: Record<string, unknown>) =>
136
+ histogram.records.push({ value, attrs }),
137
+ };
138
+ },
139
+ addBatchObservableCallback: h.addBatchObservableCallback,
140
+ removeBatchObservableCallback: h.removeBatchObservableCallback,
141
+ }),
142
+ }));
143
+
144
+ function observeAll() {
145
+ const observations: Array<{
146
+ name: string;
147
+ value: number;
148
+ attrs?: Record<string, unknown>;
149
+ }> = [];
150
+ const callback = h.gauges.find((g) => g.callback)?.callback;
151
+ if (!callback) throw new Error("missing observable callback");
152
+ callback({
153
+ observe: (gauge, value, attrs) =>
154
+ observations.push({ name: gauge.name, value, attrs }),
155
+ });
156
+ return observations;
157
+ }
158
+
159
+ describe("memory metrics", () => {
160
+ beforeEach(async () => {
161
+ vi.resetModules();
162
+ h.gauges.length = 0;
163
+ h.observableCounters.length = 0;
164
+ h.counters.length = 0;
165
+ h.histograms.length = 0;
166
+ h.throwOnGaugeName = undefined;
167
+ h.delayMonitor.enable.mockClear();
168
+ h.delayMonitor.disable.mockClear();
169
+ h.delayMonitor.reset.mockClear();
170
+ h.delayMonitor.percentile.mockClear();
171
+ h.gcCallback = undefined;
172
+ h.elu.mockReset();
173
+ h.elu
174
+ .mockReturnValueOnce({ idle: 0, active: 0, utilization: 0 })
175
+ .mockReturnValueOnce({ idle: 25, active: 75, utilization: 0.75 })
176
+ .mockReturnValue({ idle: 100, active: 200, utilization: 0.67 });
177
+ h.addBatchObservableCallback.mockClear();
178
+ h.removeBatchObservableCallback.mockClear();
179
+ });
180
+
181
+ it("observes event-loop, V8 heap, and memory-pressure gauges", async () => {
182
+ const { startMemoryMetrics, stopMemoryMetrics } =
183
+ await import("./memory-metrics.js");
184
+
185
+ startMemoryMetrics();
186
+ const observations = observeAll();
187
+ stopMemoryMetrics();
188
+
189
+ expect(h.delayMonitor.enable).toHaveBeenCalledOnce();
190
+ expect(observations.map((o) => o.name)).toEqual(
191
+ expect.arrayContaining([
192
+ "nodejs.event_loop.delay.max_ms",
193
+ "nodejs.event_loop.delay.p99_ms",
194
+ "nodejs.event_loop.utilization",
195
+ "nodejs.memory.heap_limit_bytes",
196
+ "nodejs.memory.heap_used_ratio",
197
+ ]),
198
+ );
199
+ expect(h.observableCounters.map((counter) => counter.name)).toEqual([
200
+ "nodejs.process.cpu.user_seconds",
201
+ "nodejs.process.cpu.system_seconds",
202
+ ]);
203
+ expect(
204
+ observations.find((o) => o.name === "nodejs.event_loop.delay.max_ms")
205
+ ?.value,
206
+ ).toBe(80);
207
+ expect(
208
+ observations.find((o) => o.name === "nodejs.event_loop.utilization")
209
+ ?.value,
210
+ ).toBe(0.75);
211
+ expect(h.delayMonitor.reset).toHaveBeenCalledOnce();
212
+ expect(h.delayMonitor.disable).toHaveBeenCalledOnce();
213
+ });
214
+
215
+ it("cleans up the event-loop delay monitor when setup fails", async () => {
216
+ const { startMemoryMetrics, stopMemoryMetrics } =
217
+ await import("./memory-metrics.js");
218
+
219
+ h.throwOnGaugeName = "nodejs.memory.rss_bytes";
220
+ expect(() => startMemoryMetrics()).toThrow(
221
+ "createObservableGauge failed for nodejs.memory.rss_bytes",
222
+ );
223
+ expect(h.delayMonitor.disable).toHaveBeenCalledOnce();
224
+
225
+ h.throwOnGaugeName = undefined;
226
+ startMemoryMetrics();
227
+ stopMemoryMetrics();
228
+
229
+ expect(h.delayMonitor.enable).toHaveBeenCalledOnce();
230
+ expect(h.delayMonitor.disable).toHaveBeenCalledTimes(2);
231
+ });
232
+
233
+ it("resets event-loop delay even when observation fails", async () => {
234
+ const { startMemoryMetrics, stopMemoryMetrics } =
235
+ await import("./memory-metrics.js");
236
+
237
+ startMemoryMetrics();
238
+ const callback = h.gauges.find((g) => g.callback)?.callback;
239
+ if (!callback) throw new Error("missing observable callback");
240
+
241
+ expect(() =>
242
+ callback({
243
+ observe: (instrument) => {
244
+ if (instrument.name === "nodejs.memory.rss_bytes") {
245
+ throw new Error("observe failed");
246
+ }
247
+ },
248
+ }),
249
+ ).toThrow("observe failed");
250
+
251
+ stopMemoryMetrics();
252
+ expect(h.delayMonitor.reset).toHaveBeenCalledOnce();
253
+ });
254
+
255
+ it("does not register metrics twice until stopped", async () => {
256
+ const { startMemoryMetrics, stopMemoryMetrics } =
257
+ await import("./memory-metrics.js");
258
+
259
+ startMemoryMetrics();
260
+ startMemoryMetrics();
261
+ stopMemoryMetrics();
262
+ startMemoryMetrics();
263
+ stopMemoryMetrics();
264
+
265
+ expect(h.delayMonitor.enable).toHaveBeenCalledTimes(2);
266
+ expect(h.addBatchObservableCallback).toHaveBeenCalledTimes(2);
267
+ expect(h.removeBatchObservableCallback).toHaveBeenCalledTimes(2);
268
+ });
269
+
270
+ it("records GC count and duration with bounded kind labels", async () => {
271
+ const { startMemoryMetrics, stopMemoryMetrics } =
272
+ await import("./memory-metrics.js");
273
+
274
+ startMemoryMetrics();
275
+ h.gcCallback?.({
276
+ getEntries: () => [
277
+ { detail: { kind: 1 }, duration: 12 },
278
+ { detail: { kind: 4 }, duration: 34 },
279
+ { kind: 999, duration: 56 },
280
+ ],
281
+ });
282
+ stopMemoryMetrics();
283
+
284
+ const gcCounter = h.counters.find((c) => c.name === "nodejs.gc.count");
285
+ const gcDuration = h.histograms.find(
286
+ (histogram) => histogram.name === "nodejs.gc.duration_ms",
287
+ );
288
+
289
+ expect(gcCounter?.adds.map((a) => a.attrs)).toEqual([
290
+ { kind: "minor" },
291
+ { kind: "major" },
292
+ { kind: "unknown" },
293
+ ]);
294
+ expect(gcDuration?.records).toEqual([
295
+ { value: 12, attrs: { kind: "minor" } },
296
+ { value: 34, attrs: { kind: "major" } },
297
+ { value: 56, attrs: { kind: "unknown" } },
298
+ ]);
299
+ expect(gcDuration?.options).toEqual({
300
+ description: "Garbage collection duration by collection kind",
301
+ unit: "ms",
302
+ advice: {
303
+ explicitBucketBoundaries: [0.5, 1, 2, 5, 10, 25, 50, 100, 250, 500],
304
+ },
305
+ });
306
+ });
307
+ });
308
+
309
+ describe("readCgroupMemory", () => {
310
+ beforeEach(() => {
311
+ vi.resetModules();
312
+ });
313
+
314
+ it("reads cgroup v2 memory usage and ratio", async () => {
315
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
316
+ writeFileSync(path.join(root, "memory.current"), "50\n");
317
+ writeFileSync(path.join(root, "memory.max"), "200\n");
318
+ const { readCgroupMemory } = await import("./memory-metrics.js");
319
+
320
+ expect(readCgroupMemory(root)).toEqual({
321
+ currentBytes: 50,
322
+ limitBytes: 200,
323
+ usageRatio: 0.25,
324
+ });
325
+ });
326
+
327
+ it("returns undefined when cgroup files are absent or unlimited", async () => {
328
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
329
+ const { readCgroupMemory } = await import("./memory-metrics.js");
330
+
331
+ expect(readCgroupMemory(root)).toBeUndefined();
332
+ writeFileSync(path.join(root, "memory.current"), "50\n");
333
+ writeFileSync(path.join(root, "memory.max"), "max\n");
334
+ expect(readCgroupMemory(root)).toBeUndefined();
335
+ });
336
+
337
+ it("does not combine v2 usage with v1 limits", async () => {
338
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
339
+ const memoryRoot = path.join(root, "memory");
340
+ mkdirSync(memoryRoot);
341
+ writeFileSync(path.join(root, "memory.current"), "50\n");
342
+ writeFileSync(path.join(root, "memory.max"), "max\n");
343
+ writeFileSync(path.join(memoryRoot, "memory.usage_in_bytes"), "60\n");
344
+ writeFileSync(path.join(memoryRoot, "memory.limit_in_bytes"), "200\n");
345
+ const { readCgroupMemory } = await import("./memory-metrics.js");
346
+
347
+ expect(readCgroupMemory(root)).toBeUndefined();
348
+ });
349
+
350
+ it("treats cgroup v1 sentinel limits as unlimited", async () => {
351
+ const root = mkdtempSync(path.join(tmpdir(), "sb-cgroup-"));
352
+ const memoryRoot = path.join(root, "memory");
353
+ mkdirSync(memoryRoot);
354
+ writeFileSync(path.join(memoryRoot, "memory.usage_in_bytes"), "50\n");
355
+ writeFileSync(
356
+ path.join(memoryRoot, "memory.limit_in_bytes"),
357
+ "9223372036854771712\n",
358
+ );
359
+ const { readCgroupMemory } = await import("./memory-metrics.js");
360
+
361
+ expect(readCgroupMemory(root)).toBeUndefined();
362
+ });
363
+ });