@superblocksteam/sdk 2.0.115 → 2.0.116-next.1

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 (33) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/cli-replacement/automatic-upgrades.d.ts.map +1 -1
  3. package/dist/cli-replacement/automatic-upgrades.js +16 -0
  4. package/dist/cli-replacement/automatic-upgrades.js.map +1 -1
  5. package/dist/cli-replacement/automatic-upgrades.test.js +78 -0
  6. package/dist/cli-replacement/automatic-upgrades.test.js.map +1 -1
  7. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.d.mts +2 -0
  8. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.d.mts.map +1 -0
  9. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs +341 -0
  10. package/dist/cli-replacement/dev-startup-git-before-dbfs-order.test.mjs.map +1 -0
  11. package/dist/cli-replacement/dev.d.mts.map +1 -1
  12. package/dist/cli-replacement/dev.mjs +105 -127
  13. package/dist/cli-replacement/dev.mjs.map +1 -1
  14. package/dist/cli-replacement/normalize-workspace-protocol.d.ts +15 -0
  15. package/dist/cli-replacement/normalize-workspace-protocol.d.ts.map +1 -0
  16. package/dist/cli-replacement/normalize-workspace-protocol.js +44 -0
  17. package/dist/cli-replacement/normalize-workspace-protocol.js.map +1 -0
  18. package/dist/cli-replacement/normalize-workspace-protocol.test.d.ts +2 -0
  19. package/dist/cli-replacement/normalize-workspace-protocol.test.d.ts.map +1 -0
  20. package/dist/cli-replacement/normalize-workspace-protocol.test.js +105 -0
  21. package/dist/cli-replacement/normalize-workspace-protocol.test.js.map +1 -0
  22. package/dist/dev-utils/dev-server.d.mts.map +1 -1
  23. package/dist/dev-utils/dev-server.mjs +2 -0
  24. package/dist/dev-utils/dev-server.mjs.map +1 -1
  25. package/package.json +12 -12
  26. package/src/cli-replacement/automatic-upgrades.test.ts +128 -0
  27. package/src/cli-replacement/automatic-upgrades.ts +17 -0
  28. package/src/cli-replacement/dev-startup-git-before-dbfs-order.test.mts +407 -0
  29. package/src/cli-replacement/dev.mts +163 -170
  30. package/src/cli-replacement/normalize-workspace-protocol.test.ts +122 -0
  31. package/src/cli-replacement/normalize-workspace-protocol.ts +64 -0
  32. package/src/dev-utils/dev-server.mts +6 -0
  33. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,407 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as http from "node:http";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+
6
+ import {
7
+ afterEach,
8
+ beforeEach,
9
+ describe,
10
+ expect,
11
+ it,
12
+ vi,
13
+ type Mock,
14
+ } from "vitest";
15
+
16
+ const execMock = vi.fn();
17
+ vi.mock("node:child_process", () => ({
18
+ default: { exec: execMock },
19
+ exec: execMock,
20
+ }));
21
+
22
+ vi.mock("node:readline", () => ({
23
+ default: {
24
+ createInterface: vi.fn(() => ({ question: vi.fn(), close: vi.fn() })),
25
+ },
26
+ createInterface: vi.fn(() => ({ question: vi.fn(), close: vi.fn() })),
27
+ }));
28
+
29
+ vi.mock("fs-extra", () => ({
30
+ default: {
31
+ existsSync: vi.fn(() => true),
32
+ readFileSync: vi.fn(() => "{}"),
33
+ },
34
+ }));
35
+
36
+ vi.mock("package-manager-detector/detect", () => ({
37
+ detect: vi.fn(async () => ({
38
+ name: "npm",
39
+ agent: "npm",
40
+ version: "10.0.0",
41
+ })),
42
+ }));
43
+
44
+ vi.mock("package-manager-detector", () => ({
45
+ resolveCommand: vi.fn(() => ({
46
+ command: "npm",
47
+ args: ["install", "--fund=false", "--audit=false"],
48
+ })),
49
+ }));
50
+
51
+ vi.mock("read-pkg", () => ({
52
+ readPackage: vi.fn(async () => ({
53
+ name: "test-app",
54
+ dependencies: { "@superblocksteam/library": "1.0.0" },
55
+ })),
56
+ }));
57
+
58
+ const mockLogger = {
59
+ info: vi.fn(),
60
+ warn: vi.fn(),
61
+ error: vi.fn(),
62
+ debug: vi.fn(),
63
+ };
64
+
65
+ vi.mock("../telemetry/logging.js", () => ({
66
+ getLogger: () => mockLogger,
67
+ getErrorMeta: vi.fn(() => ({})),
68
+ }));
69
+
70
+ vi.mock("../telemetry/index.js", () => ({
71
+ getTracer: () => ({
72
+ startActiveSpan: vi.fn(
73
+ async (
74
+ _name: string,
75
+ fn: (span: {
76
+ end: () => void;
77
+ setStatus: () => void;
78
+ }) => Promise<unknown>,
79
+ ) => fn({ end: vi.fn(), setStatus: vi.fn() }),
80
+ ),
81
+ }),
82
+ }));
83
+
84
+ vi.mock("@opentelemetry/api", () => ({
85
+ SpanStatusCode: { ERROR: 2 },
86
+ }));
87
+
88
+ vi.mock("colorette", () => ({
89
+ green: (s: string) => s,
90
+ }));
91
+
92
+ vi.mock("@superblocksteam/shared", () => ({
93
+ buildGithubSuperblocksSyncWorkflow: vi.fn(),
94
+ buildGithubSuperblocksSyncWorkflowFromBaseUrl: vi.fn(),
95
+ ConflictError: class ConflictError extends Error {},
96
+ DEFAULT_NON_GIT_BRANCH: "__non_git__",
97
+ isGitHubRemoteUrl: vi.fn(() => false),
98
+ NotFoundError: class NotFoundError extends Error {},
99
+ SUPERBLOCKS_LIVE_GIT_BRANCH: "__live__",
100
+ }));
101
+
102
+ vi.mock("@superblocksteam/util", () => ({
103
+ maskUnixSignals: async function maskUnixSignals(fn: () => Promise<void>) {
104
+ return fn();
105
+ },
106
+ }));
107
+
108
+ vi.mock("@superblocksteam/vite-plugin-file-sync/ai-service", () => ({
109
+ AiService: class {
110
+ initialize = vi.fn(async () => undefined);
111
+ removeIntegrationCache = vi.fn(async () => undefined);
112
+ chatSessionStore = { invalidateCache: vi.fn() };
113
+ },
114
+ AiServiceFeatureFlags: class {
115
+ static create = vi.fn(() => ({}));
116
+ },
117
+ SnapshotManager: class {
118
+ executePendingRestore = vi.fn(async () => false);
119
+ },
120
+ isSdkApiTemplate: vi.fn(() => false),
121
+ }));
122
+
123
+ const { startupOpLog, buildMockGitService } = vi.hoisted(() => {
124
+ const startupOpLog: string[] = [];
125
+ function buildMockGitService(cwd: string) {
126
+ return {
127
+ workDir: cwd,
128
+ configure: vi.fn(async () => {
129
+ startupOpLog.push("git:configure");
130
+ }),
131
+ init: vi.fn(async () => {
132
+ startupOpLog.push("git:init");
133
+ }),
134
+ addRemote: vi.fn(async () => {
135
+ startupOpLog.push("git:addRemote");
136
+ }),
137
+ fetch: vi.fn(async () => {
138
+ startupOpLog.push("git:fetch");
139
+ }),
140
+ raw: vi.fn(async (args: string | string[]) => {
141
+ const argv = Array.isArray(args) ? args : [args];
142
+ startupOpLog.push(`git:raw:${argv.join(" ")}`);
143
+ if (argv[0] === "ls-remote" && argv.includes("__live__")) {
144
+ return "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef\trefs/heads/__live__\n";
145
+ }
146
+ if (argv[0] === "branch" && argv.includes("--show-current")) {
147
+ return "__live__\n";
148
+ }
149
+ if (argv[0] === "commit") {
150
+ return "";
151
+ }
152
+ return "";
153
+ }),
154
+ push: vi.fn(async () => {
155
+ startupOpLog.push("git:push");
156
+ }),
157
+ getDefaultBranch: vi.fn(async () => "main"),
158
+ getRemotes: vi.fn(async () => [
159
+ { name: "origin", refs: { fetch: "https://example.test/repo.git" } },
160
+ ]),
161
+ remote: vi.fn(async () => {
162
+ startupOpLog.push("git:remote");
163
+ }),
164
+ revparse: vi.fn(async (ref: string) => {
165
+ startupOpLog.push(`git:revparse:${ref}`);
166
+ return "abc123\n";
167
+ }),
168
+ checkout: vi.fn(async () => {
169
+ startupOpLog.push("git:checkout");
170
+ }),
171
+ checkoutOrCreate: vi.fn(async () => {
172
+ startupOpLog.push("git:checkoutOrCreate");
173
+ }),
174
+ };
175
+ }
176
+ return { startupOpLog, buildMockGitService };
177
+ });
178
+
179
+ vi.mock("@superblocksteam/vite-plugin-file-sync/git-service", () => ({
180
+ createGitService: vi.fn((cwd: string) => {
181
+ startupOpLog.push("createGitService");
182
+ return buildMockGitService(cwd);
183
+ }),
184
+ }));
185
+
186
+ const mockLockService = {
187
+ acquireLock: vi.fn(async () => undefined),
188
+ shutdown: vi.fn(async () => undefined),
189
+ shutdownAndExit: vi.fn(async () => undefined),
190
+ setIsRestarting: vi.fn(),
191
+ isLocked: false,
192
+ connectedUsers: [],
193
+ wasRecentlyActive: false,
194
+ timeSinceLastActivity: 0,
195
+ };
196
+
197
+ vi.mock("@superblocksteam/vite-plugin-file-sync/lock-service", () => ({
198
+ LockService: class {
199
+ acquireLock = mockLockService.acquireLock;
200
+ shutdown = mockLockService.shutdown;
201
+ shutdownAndExit = mockLockService.shutdownAndExit;
202
+ setIsRestarting = mockLockService.setIsRestarting;
203
+ isLocked = mockLockService.isLocked;
204
+ connectedUsers = mockLockService.connectedUsers;
205
+ wasRecentlyActive = mockLockService.wasRecentlyActive;
206
+ timeSinceLastActivity = mockLockService.timeSinceLastActivity;
207
+ },
208
+ LockType: { CSB: "CSB", LOCAL: "LOCAL" },
209
+ }));
210
+
211
+ vi.mock("@superblocksteam/vite-plugin-file-sync/operation-queue", () => ({
212
+ OperationQueue: class {
213
+ enqueue = vi.fn(async (fn: () => Promise<unknown>) => fn());
214
+ },
215
+ }));
216
+
217
+ vi.mock("@superblocksteam/vite-plugin-file-sync/server-rpc", () => ({
218
+ AutoConnectingRpcClient: class {},
219
+ }));
220
+
221
+ const mockSyncService = {
222
+ downloadDirectory: vi.fn(async () => {
223
+ startupOpLog.push("dbfs:downloadDirectory");
224
+ }),
225
+ uploadDirectory: vi.fn(async () => undefined),
226
+ uploadDirectoryNowIfNeeded: vi.fn(async () => undefined),
227
+ cancelPendingSync: vi.fn(),
228
+ };
229
+
230
+ vi.mock("@superblocksteam/vite-plugin-file-sync/sync-service", () => ({
231
+ SyncService: class {
232
+ downloadDirectory = mockSyncService.downloadDirectory;
233
+ uploadDirectory = mockSyncService.uploadDirectory;
234
+ uploadDirectoryNowIfNeeded = mockSyncService.uploadDirectoryNowIfNeeded;
235
+ cancelPendingSync = mockSyncService.cancelPendingSync;
236
+ },
237
+ }));
238
+
239
+ vi.mock("@superblocksteam/vite-plugin-file-sync/draft-interface", () => ({}));
240
+
241
+ vi.mock("./version-detection.js", () => ({
242
+ getCurrentCliVersion: vi.fn(async () => ({
243
+ version: "2.0.0",
244
+ alias: undefined,
245
+ })),
246
+ }));
247
+
248
+ const mockCheckVersions = vi.fn(async () => ({
249
+ cliUpdated: false,
250
+ upgradePromises: [] as Promise<void>[],
251
+ }));
252
+ vi.mock("./automatic-upgrades.js", () => ({
253
+ checkVersionsAndWritePackageJson: (
254
+ ...args: Parameters<typeof mockCheckVersions>
255
+ ) => mockCheckVersions(...args),
256
+ }));
257
+
258
+ vi.mock("./git-repo-setup.mjs", () => ({
259
+ ensureRemoteHasDefaultBranch: vi.fn(async () => undefined),
260
+ getGitErrorFields: vi.fn(() => ({})),
261
+ }));
262
+
263
+ const mockCreateDevServer = vi.fn(async () => http.createServer());
264
+ vi.mock("../dev-utils/dev-server.mjs", () => ({
265
+ createDevServer: (...args: Parameters<typeof mockCreateDevServer>) =>
266
+ mockCreateDevServer(...args),
267
+ }));
268
+
269
+ vi.mock("../index.js", () => ({
270
+ AUTO_UPGRADE_EXIT_CODE: 99,
271
+ }));
272
+
273
+ const SERVER_DBFS_HASH = "dbfs-head-after-clark";
274
+
275
+ function buildMockSdk() {
276
+ const hashLocalDirectory = vi
277
+ .fn()
278
+ .mockImplementationOnce(async () => {
279
+ startupOpLog.push("sdk:hashLocalDirectory:before-dbfs");
280
+ return { hash: "git-tree-without-uncommitted-clark-change" };
281
+ })
282
+ .mockImplementationOnce(async () => {
283
+ startupOpLog.push("sdk:hashLocalDirectory:after-dbfs");
284
+ return { hash: SERVER_DBFS_HASH };
285
+ });
286
+
287
+ return {
288
+ hashLocalDirectory,
289
+ dbfsGetApplicationDirectoryHash: vi.fn(async () => SERVER_DBFS_HASH),
290
+ getApplicationGitConfig: vi.fn(async () => ({
291
+ gitRemoteUrl: "https://github.com/example/superblocks-app.git",
292
+ hasCredential: false,
293
+ })),
294
+ fetchCurrentUser: vi.fn(async () => ({
295
+ user: {
296
+ id: "user-1",
297
+ name: "Test User",
298
+ email: "test@example.com",
299
+ currentOrganizationId: "org-1",
300
+ },
301
+ organizations: [{ id: "org-1", pluginExecutionVersions: {} }],
302
+ flagBootstrap: {},
303
+ })),
304
+ getFeatureFlagsForCurrentUser: vi.fn(async () => ({
305
+ devServerCloudInactivityTimeoutMinutes: () => 30,
306
+ devServerLocalInactivityTimeoutMinutes: () => 30,
307
+ enableSessionRecording: () => false,
308
+ })),
309
+ getFeatureFlagsForUser: vi.fn(() => ({
310
+ devServerCloudInactivityTimeoutMinutes: () => 30,
311
+ devServerLocalInactivityTimeoutMinutes: () => 30,
312
+ })),
313
+ };
314
+ }
315
+
316
+ function buildDevOptions(cwd: string, overrides: Record<string, unknown> = {}) {
317
+ return {
318
+ cwd,
319
+ downloadFirst: true,
320
+ autoUpgradeMode: "skip-upgrade",
321
+ applicationConfig: {
322
+ id: "app-test",
323
+ branchName: "main",
324
+ },
325
+ tokenConfig: {
326
+ superblocksBaseUrl: "https://example.test",
327
+ token: "test-token",
328
+ },
329
+ tokenManager: {
330
+ getToken: vi.fn(() => "test-token"),
331
+ onTokenRefresh: vi.fn(),
332
+ },
333
+ getCurrentToken: vi.fn(() => "test-token"),
334
+ sdk: buildMockSdk(),
335
+ ...overrides,
336
+ };
337
+ }
338
+
339
+ function setupExecMock() {
340
+ execMock.mockImplementation(
341
+ (
342
+ _cmd: string,
343
+ _opts: unknown,
344
+ cb?: (err: null, result: { stdout: string }) => void,
345
+ ) => {
346
+ if (typeof _opts === "function") {
347
+ (_opts as (err: null, result: { stdout: string }) => void)(null, {
348
+ stdout: "installed",
349
+ });
350
+ } else if (cb) {
351
+ cb(null, { stdout: "installed" });
352
+ }
353
+ },
354
+ );
355
+ }
356
+
357
+ describe("dev startup: git reconciliation before DBFS download", () => {
358
+ let testCwd: string;
359
+
360
+ beforeEach(async () => {
361
+ vi.clearAllMocks();
362
+ startupOpLog.length = 0;
363
+ setupExecMock();
364
+ mockCheckVersions.mockResolvedValue({
365
+ cliUpdated: false,
366
+ upgradePromises: [],
367
+ });
368
+ testCwd = await fs.mkdtemp(path.join(os.tmpdir(), "sb-dev-git-dbfs-"));
369
+ const { readPackage } = await import("read-pkg");
370
+ (readPackage as Mock).mockResolvedValue({
371
+ name: "test-app",
372
+ dependencies: { "@superblocksteam/library": "1.0.0" },
373
+ });
374
+ });
375
+
376
+ afterEach(async () => {
377
+ await fs.rm(testCwd, { recursive: true, force: true });
378
+ });
379
+
380
+ it("runs git bootstrap before local hash and DBFS download when hashes differ (Clark-only DBFS change)", async () => {
381
+ const { dev } = await import("./dev.mjs");
382
+ const { createGitService } =
383
+ await import("@superblocksteam/vite-plugin-file-sync/git-service");
384
+
385
+ await dev(buildDevOptions(testCwd) as any);
386
+
387
+ const createIdx = startupOpLog.indexOf("createGitService");
388
+ const hashIdx = startupOpLog.indexOf("sdk:hashLocalDirectory:before-dbfs");
389
+ const downloadIdx = startupOpLog.indexOf("dbfs:downloadDirectory");
390
+ const postDownloadHashIdx = startupOpLog.indexOf(
391
+ "sdk:hashLocalDirectory:after-dbfs",
392
+ );
393
+
394
+ expect(createIdx).toBeGreaterThanOrEqual(0);
395
+ expect(hashIdx).toBeGreaterThanOrEqual(0);
396
+ expect(downloadIdx).toBeGreaterThanOrEqual(0);
397
+ expect(postDownloadHashIdx).toBeGreaterThanOrEqual(0);
398
+ expect(createIdx).toBeLessThan(hashIdx);
399
+ expect(hashIdx).toBeLessThan(downloadIdx);
400
+ expect(downloadIdx).toBeLessThan(postDownloadHashIdx);
401
+ expect(createGitService).toHaveBeenCalled();
402
+ expect(mockSyncService.downloadDirectory).toHaveBeenCalled();
403
+ expect(mockLogger.info).toHaveBeenCalledWith(
404
+ "[dev-startup] Post-download hash matches server",
405
+ );
406
+ });
407
+ });