@superblocksteam/sdk 2.0.115-next.1 → 2.0.115
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/dev-s3-restore.test.d.mts +2 -0
- package/dist/cli-replacement/dev-s3-restore.test.d.mts.map +1 -0
- package/dist/cli-replacement/dev-s3-restore.test.mjs +457 -0
- package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -0
- package/dist/cli-replacement/dev.d.mts +7 -0
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.mjs +49 -2
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/dist/cli-replacement/package-json-snapshot.d.mts +26 -0
- package/dist/cli-replacement/package-json-snapshot.d.mts.map +1 -0
- package/dist/cli-replacement/package-json-snapshot.mjs +222 -0
- package/dist/cli-replacement/package-json-snapshot.mjs.map +1 -0
- package/dist/cli-replacement/package-json-snapshot.test.d.mts +2 -0
- package/dist/cli-replacement/package-json-snapshot.test.d.mts.map +1 -0
- package/dist/cli-replacement/package-json-snapshot.test.mjs +207 -0
- package/dist/cli-replacement/package-json-snapshot.test.mjs.map +1 -0
- package/dist/dev-utils/dev-server-persist.test.d.mts +2 -0
- package/dist/dev-utils/dev-server-persist.test.d.mts.map +1 -0
- package/dist/dev-utils/dev-server-persist.test.mjs +77 -0
- package/dist/dev-utils/dev-server-persist.test.mjs.map +1 -0
- package/dist/dev-utils/dev-server.d.mts +1 -0
- package/dist/dev-utils/dev-server.d.mts.map +1 -1
- package/dist/dev-utils/dev-server.mjs +75 -53
- package/dist/dev-utils/dev-server.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/cli-replacement/dev-s3-restore.test.mts +599 -0
- package/src/cli-replacement/dev.mts +91 -2
- package/src/cli-replacement/package-json-snapshot.mts +328 -0
- package/src/cli-replacement/package-json-snapshot.test.mts +250 -0
- package/src/dev-utils/dev-server-persist.test.mts +96 -0
- package/src/dev-utils/dev-server.mts +91 -73
- package/src/index.ts +15 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,599 @@
|
|
|
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 { beforeEach, describe, expect, it, vi, type Mock } from "vitest";
|
|
7
|
+
|
|
8
|
+
import { packageJsonSnapshot } from "./package-json-snapshot.mjs";
|
|
9
|
+
|
|
10
|
+
const execMock = vi.fn();
|
|
11
|
+
vi.mock("node:child_process", () => ({
|
|
12
|
+
default: { exec: execMock },
|
|
13
|
+
exec: execMock,
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
vi.mock("node:readline", () => ({
|
|
17
|
+
default: {
|
|
18
|
+
createInterface: vi.fn(() => ({ question: vi.fn(), close: vi.fn() })),
|
|
19
|
+
},
|
|
20
|
+
createInterface: vi.fn(() => ({ question: vi.fn(), close: vi.fn() })),
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock("fs-extra", () => ({
|
|
24
|
+
default: {
|
|
25
|
+
existsSync: vi.fn(() => true),
|
|
26
|
+
readFileSync: vi.fn(() => "{}"),
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
vi.mock("package-manager-detector/detect", () => ({
|
|
31
|
+
detect: vi.fn(async () => ({
|
|
32
|
+
name: "npm",
|
|
33
|
+
agent: "npm",
|
|
34
|
+
version: "10.0.0",
|
|
35
|
+
})),
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
vi.mock("package-manager-detector", () => ({
|
|
39
|
+
resolveCommand: vi.fn(() => ({
|
|
40
|
+
command: "npm",
|
|
41
|
+
args: ["install", "--fund=false", "--audit=false"],
|
|
42
|
+
})),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
vi.mock("read-pkg", () => ({
|
|
46
|
+
readPackage: vi.fn(async () => ({
|
|
47
|
+
name: "test-app",
|
|
48
|
+
dependencies: { "@superblocksteam/library": "1.0.0" },
|
|
49
|
+
})),
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
const mockLogger = {
|
|
53
|
+
info: vi.fn(),
|
|
54
|
+
warn: vi.fn(),
|
|
55
|
+
error: vi.fn(),
|
|
56
|
+
debug: vi.fn(),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
vi.mock("../telemetry/logging.js", () => ({
|
|
60
|
+
getLogger: () => mockLogger,
|
|
61
|
+
getErrorMeta: vi.fn(() => ({})),
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
vi.mock("../telemetry/index.js", () => ({
|
|
65
|
+
getTracer: () => ({
|
|
66
|
+
startActiveSpan: vi.fn(
|
|
67
|
+
async (
|
|
68
|
+
_name: string,
|
|
69
|
+
fn: (span: {
|
|
70
|
+
end: () => void;
|
|
71
|
+
setStatus: () => void;
|
|
72
|
+
}) => Promise<unknown>,
|
|
73
|
+
) => fn({ end: vi.fn(), setStatus: vi.fn() }),
|
|
74
|
+
),
|
|
75
|
+
}),
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
vi.mock("@opentelemetry/api", () => ({
|
|
79
|
+
SpanStatusCode: { ERROR: 2 },
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
vi.mock("colorette", () => ({
|
|
83
|
+
green: (s: string) => s,
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
vi.mock("@superblocksteam/shared", () => ({
|
|
87
|
+
buildGithubSuperblocksSyncWorkflow: vi.fn(),
|
|
88
|
+
buildGithubSuperblocksSyncWorkflowFromBaseUrl: vi.fn(),
|
|
89
|
+
ConflictError: class ConflictError extends Error {},
|
|
90
|
+
DEFAULT_NON_GIT_BRANCH: "__non_git__",
|
|
91
|
+
isGitHubRemoteUrl: vi.fn(() => false),
|
|
92
|
+
NotFoundError: class NotFoundError extends Error {},
|
|
93
|
+
SUPERBLOCKS_LIVE_GIT_BRANCH: "__live__",
|
|
94
|
+
}));
|
|
95
|
+
|
|
96
|
+
vi.mock("@superblocksteam/util", () => ({
|
|
97
|
+
maskUnixSignals: async function maskUnixSignals(fn: () => Promise<void>) {
|
|
98
|
+
return fn();
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
vi.mock("@superblocksteam/vite-plugin-file-sync/ai-service", () => ({
|
|
103
|
+
AiService: class {
|
|
104
|
+
initialize = vi.fn(async () => undefined);
|
|
105
|
+
removeIntegrationCache = vi.fn(async () => undefined);
|
|
106
|
+
chatSessionStore = { invalidateCache: vi.fn() };
|
|
107
|
+
},
|
|
108
|
+
AiServiceFeatureFlags: class {
|
|
109
|
+
static create = vi.fn(() => ({}));
|
|
110
|
+
},
|
|
111
|
+
SnapshotManager: class {
|
|
112
|
+
executePendingRestore = vi.fn(async () => false);
|
|
113
|
+
},
|
|
114
|
+
isSdkApiTemplate: vi.fn(() => false),
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
vi.mock("@superblocksteam/vite-plugin-file-sync/git-service", () => ({
|
|
118
|
+
createGitService: vi.fn(async () => undefined),
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
const mockLockService = {
|
|
122
|
+
acquireLock: vi.fn(async () => undefined),
|
|
123
|
+
shutdown: vi.fn(async () => undefined),
|
|
124
|
+
shutdownAndExit: vi.fn(async () => undefined),
|
|
125
|
+
setIsRestarting: vi.fn(),
|
|
126
|
+
isLocked: false,
|
|
127
|
+
connectedUsers: [],
|
|
128
|
+
wasRecentlyActive: false,
|
|
129
|
+
timeSinceLastActivity: 0,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
vi.mock("@superblocksteam/vite-plugin-file-sync/lock-service", () => ({
|
|
133
|
+
LockService: class {
|
|
134
|
+
acquireLock = mockLockService.acquireLock;
|
|
135
|
+
shutdown = mockLockService.shutdown;
|
|
136
|
+
shutdownAndExit = mockLockService.shutdownAndExit;
|
|
137
|
+
setIsRestarting = mockLockService.setIsRestarting;
|
|
138
|
+
isLocked = mockLockService.isLocked;
|
|
139
|
+
connectedUsers = mockLockService.connectedUsers;
|
|
140
|
+
wasRecentlyActive = mockLockService.wasRecentlyActive;
|
|
141
|
+
timeSinceLastActivity = mockLockService.timeSinceLastActivity;
|
|
142
|
+
},
|
|
143
|
+
LockType: { CSB: "CSB", LOCAL: "LOCAL" },
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
vi.mock("@superblocksteam/vite-plugin-file-sync/operation-queue", () => ({
|
|
147
|
+
OperationQueue: class {
|
|
148
|
+
enqueue = vi.fn(async (fn: () => Promise<unknown>) => fn());
|
|
149
|
+
},
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
vi.mock("@superblocksteam/vite-plugin-file-sync/server-rpc", () => ({
|
|
153
|
+
AutoConnectingRpcClient: class {},
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
const mockSyncService = {
|
|
157
|
+
downloadDirectory: vi.fn(async () => undefined),
|
|
158
|
+
uploadDirectory: vi.fn(async () => undefined),
|
|
159
|
+
uploadDirectoryNowIfNeeded: vi.fn(async () => undefined),
|
|
160
|
+
cancelPendingSync: vi.fn(),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
vi.mock("@superblocksteam/vite-plugin-file-sync/sync-service", () => ({
|
|
164
|
+
SyncService: class {
|
|
165
|
+
downloadDirectory = mockSyncService.downloadDirectory;
|
|
166
|
+
uploadDirectory = mockSyncService.uploadDirectory;
|
|
167
|
+
uploadDirectoryNowIfNeeded = mockSyncService.uploadDirectoryNowIfNeeded;
|
|
168
|
+
cancelPendingSync = mockSyncService.cancelPendingSync;
|
|
169
|
+
},
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
vi.mock("@superblocksteam/vite-plugin-file-sync/draft-interface", () => ({}));
|
|
173
|
+
|
|
174
|
+
vi.mock("./version-detection.js", () => ({
|
|
175
|
+
getCurrentCliVersion: vi.fn(async () => ({
|
|
176
|
+
version: "2.0.0",
|
|
177
|
+
alias: undefined,
|
|
178
|
+
})),
|
|
179
|
+
}));
|
|
180
|
+
|
|
181
|
+
const mockCheckVersions = vi.fn(async () => ({
|
|
182
|
+
cliUpdated: false,
|
|
183
|
+
upgradePromises: [] as Promise<void>[],
|
|
184
|
+
}));
|
|
185
|
+
vi.mock("./automatic-upgrades.js", () => ({
|
|
186
|
+
checkVersionsAndWritePackageJson: (
|
|
187
|
+
...args: Parameters<typeof mockCheckVersions>
|
|
188
|
+
) => mockCheckVersions(...args),
|
|
189
|
+
}));
|
|
190
|
+
|
|
191
|
+
vi.mock("./git-repo-setup.mjs", () => ({
|
|
192
|
+
ensureRemoteHasDefaultBranch: vi.fn(async () => undefined),
|
|
193
|
+
getGitErrorFields: vi.fn(() => ({})),
|
|
194
|
+
}));
|
|
195
|
+
|
|
196
|
+
const mockCreateDevServer = vi.fn(async () => http.createServer());
|
|
197
|
+
vi.mock("../dev-utils/dev-server.mjs", () => ({
|
|
198
|
+
createDevServer: (...args: Parameters<typeof mockCreateDevServer>) =>
|
|
199
|
+
mockCreateDevServer(...args),
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
vi.mock("../index.js", () => ({
|
|
203
|
+
AUTO_UPGRADE_EXIT_CODE: 99,
|
|
204
|
+
}));
|
|
205
|
+
|
|
206
|
+
function buildMockSdk() {
|
|
207
|
+
return {
|
|
208
|
+
hashLocalDirectory: vi.fn(async () => ({ hash: "same" })),
|
|
209
|
+
dbfsGetApplicationDirectoryHash: vi.fn(async () => "same"),
|
|
210
|
+
getApplicationGitConfig: vi.fn(async () => null),
|
|
211
|
+
fetchCurrentUser: vi.fn(async () => ({
|
|
212
|
+
user: {
|
|
213
|
+
id: "user-1",
|
|
214
|
+
name: "Test User",
|
|
215
|
+
email: "test@example.com",
|
|
216
|
+
currentOrganizationId: "org-1",
|
|
217
|
+
},
|
|
218
|
+
organizations: [{ id: "org-1", pluginExecutionVersions: {} }],
|
|
219
|
+
flagBootstrap: {},
|
|
220
|
+
})),
|
|
221
|
+
getFeatureFlagsForCurrentUser: vi.fn(async () => ({
|
|
222
|
+
devServerCloudInactivityTimeoutMinutes: () => 30,
|
|
223
|
+
devServerLocalInactivityTimeoutMinutes: () => 30,
|
|
224
|
+
enableSessionRecording: () => false,
|
|
225
|
+
})),
|
|
226
|
+
getFeatureFlagsForUser: vi.fn(() => ({
|
|
227
|
+
devServerCloudInactivityTimeoutMinutes: () => 30,
|
|
228
|
+
devServerLocalInactivityTimeoutMinutes: () => 30,
|
|
229
|
+
})),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function buildDevOptions(overrides: Record<string, unknown> = {}) {
|
|
234
|
+
return {
|
|
235
|
+
cwd: "/tmp/test-app",
|
|
236
|
+
downloadFirst: true,
|
|
237
|
+
applicationConfig: {
|
|
238
|
+
id: "app-test",
|
|
239
|
+
branchName: "main",
|
|
240
|
+
},
|
|
241
|
+
tokenConfig: {
|
|
242
|
+
superblocksBaseUrl: "https://example.test",
|
|
243
|
+
token: "test-token",
|
|
244
|
+
},
|
|
245
|
+
tokenManager: {
|
|
246
|
+
getToken: vi.fn(() => "test-token"),
|
|
247
|
+
onTokenRefresh: vi.fn(),
|
|
248
|
+
},
|
|
249
|
+
getCurrentToken: vi.fn(() => "test-token"),
|
|
250
|
+
sdk: buildMockSdk(),
|
|
251
|
+
...overrides,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function setupExecMock() {
|
|
256
|
+
execMock.mockImplementation(
|
|
257
|
+
(
|
|
258
|
+
_cmd: string,
|
|
259
|
+
_opts: unknown,
|
|
260
|
+
cb?: (err: null, result: { stdout: string }) => void,
|
|
261
|
+
) => {
|
|
262
|
+
if (typeof _opts === "function") {
|
|
263
|
+
(_opts as (err: null, result: { stdout: string }) => void)(null, {
|
|
264
|
+
stdout: "installed",
|
|
265
|
+
});
|
|
266
|
+
} else if (cb) {
|
|
267
|
+
cb(null, { stdout: "installed" });
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function expectInstallToHaveRun() {
|
|
274
|
+
const installCall = execMock.mock.calls.find(
|
|
275
|
+
(call: unknown[]) =>
|
|
276
|
+
typeof call[0] === "string" && (call[0] as string).includes("install"),
|
|
277
|
+
);
|
|
278
|
+
expect(installCall).toBeDefined();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
describe("dev startup after S3 workspace restore", () => {
|
|
282
|
+
beforeEach(async () => {
|
|
283
|
+
vi.clearAllMocks();
|
|
284
|
+
setupExecMock();
|
|
285
|
+
mockCheckVersions.mockResolvedValue({
|
|
286
|
+
cliUpdated: false,
|
|
287
|
+
upgradePromises: [],
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const { readPackage } = await import("read-pkg");
|
|
291
|
+
(readPackage as Mock).mockResolvedValue({
|
|
292
|
+
name: "test-app",
|
|
293
|
+
dependencies: { "@superblocksteam/library": "1.0.0" },
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("keeps the existing no-op path when package.json is unchanged", async () => {
|
|
298
|
+
const { dev } = await import("./dev.mjs");
|
|
299
|
+
|
|
300
|
+
await dev(buildDevOptions() as any);
|
|
301
|
+
|
|
302
|
+
expect(execMock).not.toHaveBeenCalled();
|
|
303
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
304
|
+
"Package install decision",
|
|
305
|
+
expect.objectContaining({
|
|
306
|
+
packageJsonRequiresInstall: false,
|
|
307
|
+
forcePackageInstall: false,
|
|
308
|
+
upgradePromiseCount: 0,
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("runs npm install when auto-upgrade only rewrites @superblocksteam/library", async () => {
|
|
314
|
+
const { readPackage } = await import("read-pkg");
|
|
315
|
+
(readPackage as Mock)
|
|
316
|
+
.mockResolvedValueOnce({
|
|
317
|
+
name: "test-app",
|
|
318
|
+
dependencies: { "@superblocksteam/library": "1.0.0" },
|
|
319
|
+
})
|
|
320
|
+
.mockResolvedValueOnce({
|
|
321
|
+
name: "test-app",
|
|
322
|
+
dependencies: { "@superblocksteam/library": "2.0.0" },
|
|
323
|
+
});
|
|
324
|
+
const { dev } = await import("./dev.mjs");
|
|
325
|
+
|
|
326
|
+
await dev(buildDevOptions() as any);
|
|
327
|
+
|
|
328
|
+
expectInstallToHaveRun();
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("normalizes library drift introduced by warm DBFS download before package comparison", async () => {
|
|
332
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "sdk-dev-dbfs-"));
|
|
333
|
+
const warmPackageJson = {
|
|
334
|
+
name: "test-app",
|
|
335
|
+
dependencies: {
|
|
336
|
+
"@superblocksteam/library": "2.0.0",
|
|
337
|
+
react: "19.0.0",
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
const staleLibraryPackageJson = {
|
|
341
|
+
name: "test-app",
|
|
342
|
+
dependencies: {
|
|
343
|
+
"@superblocksteam/library": "1.0.0",
|
|
344
|
+
react: "19.0.0",
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
await fs.writeFile(
|
|
350
|
+
path.join(tmpDir, "package.json"),
|
|
351
|
+
JSON.stringify(warmPackageJson, null, 2),
|
|
352
|
+
);
|
|
353
|
+
const { readPackage } = await import("read-pkg");
|
|
354
|
+
(readPackage as Mock).mockImplementation(async ({ cwd }) =>
|
|
355
|
+
JSON.parse(await fs.readFile(path.join(cwd, "package.json"), "utf-8")),
|
|
356
|
+
);
|
|
357
|
+
mockSyncService.downloadDirectory.mockImplementationOnce(async () => {
|
|
358
|
+
await fs.writeFile(
|
|
359
|
+
path.join(tmpDir, "package.json"),
|
|
360
|
+
JSON.stringify(staleLibraryPackageJson, null, 2),
|
|
361
|
+
);
|
|
362
|
+
});
|
|
363
|
+
const sdk = buildMockSdk();
|
|
364
|
+
sdk.hashLocalDirectory = vi.fn(async () => ({ hash: "local" }));
|
|
365
|
+
sdk.dbfsGetApplicationDirectoryHash = vi.fn(async () => "server");
|
|
366
|
+
const { dev } = await import("./dev.mjs");
|
|
367
|
+
|
|
368
|
+
await dev(
|
|
369
|
+
buildDevOptions({
|
|
370
|
+
cwd: tmpDir,
|
|
371
|
+
sdk,
|
|
372
|
+
normalizeManagedPackageDependencies: true,
|
|
373
|
+
}) as any,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
const finalPackageJson = JSON.parse(
|
|
377
|
+
await fs.readFile(path.join(tmpDir, "package.json"), "utf-8"),
|
|
378
|
+
);
|
|
379
|
+
expect(finalPackageJson.dependencies["@superblocksteam/library"]).toBe(
|
|
380
|
+
"2.0.0",
|
|
381
|
+
);
|
|
382
|
+
expect(execMock).not.toHaveBeenCalled();
|
|
383
|
+
} finally {
|
|
384
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("normalizes managed template dependency drift introduced by warm DBFS download before package comparison", async () => {
|
|
389
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "sdk-dev-dbfs-"));
|
|
390
|
+
const warmPackageJson = {
|
|
391
|
+
name: "test-app",
|
|
392
|
+
dependencies: {
|
|
393
|
+
"@superblocksteam/library": "2.0.0",
|
|
394
|
+
"@superblocksteam/sdk-api": "2.0.0",
|
|
395
|
+
react: "19.0.0",
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
const staleManagedPackageJson = {
|
|
399
|
+
name: "test-app",
|
|
400
|
+
dependencies: {
|
|
401
|
+
"@superblocksteam/library": "2.0.0",
|
|
402
|
+
"@superblocksteam/sdk-api": "1.0.0",
|
|
403
|
+
react: "19.0.0",
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
try {
|
|
408
|
+
await fs.writeFile(
|
|
409
|
+
path.join(tmpDir, "package.json"),
|
|
410
|
+
JSON.stringify(warmPackageJson, null, 2),
|
|
411
|
+
);
|
|
412
|
+
const { readPackage } = await import("read-pkg");
|
|
413
|
+
(readPackage as Mock).mockImplementation(async ({ cwd }) =>
|
|
414
|
+
JSON.parse(await fs.readFile(path.join(cwd, "package.json"), "utf-8")),
|
|
415
|
+
);
|
|
416
|
+
mockSyncService.downloadDirectory.mockImplementationOnce(async () => {
|
|
417
|
+
await fs.writeFile(
|
|
418
|
+
path.join(tmpDir, "package.json"),
|
|
419
|
+
JSON.stringify(staleManagedPackageJson, null, 2),
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
const sdk = buildMockSdk();
|
|
423
|
+
sdk.hashLocalDirectory = vi.fn(async () => ({ hash: "local" }));
|
|
424
|
+
sdk.dbfsGetApplicationDirectoryHash = vi.fn(async () => "server");
|
|
425
|
+
const { dev } = await import("./dev.mjs");
|
|
426
|
+
|
|
427
|
+
await dev(
|
|
428
|
+
buildDevOptions({
|
|
429
|
+
cwd: tmpDir,
|
|
430
|
+
sdk,
|
|
431
|
+
normalizeManagedPackageDependencies: true,
|
|
432
|
+
}) as any,
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
const finalPackageJson = JSON.parse(
|
|
436
|
+
await fs.readFile(path.join(tmpDir, "package.json"), "utf-8"),
|
|
437
|
+
);
|
|
438
|
+
expect(finalPackageJson.dependencies["@superblocksteam/sdk-api"]).toBe(
|
|
439
|
+
"2.0.0",
|
|
440
|
+
);
|
|
441
|
+
expect(execMock).not.toHaveBeenCalled();
|
|
442
|
+
} finally {
|
|
443
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("does not normalize library drift for normal DBFS download-first startup", async () => {
|
|
448
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "sdk-dev-dbfs-"));
|
|
449
|
+
const localPackageJson = {
|
|
450
|
+
name: "test-app",
|
|
451
|
+
dependencies: {
|
|
452
|
+
"@superblocksteam/library": "2.0.0",
|
|
453
|
+
react: "19.0.0",
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
const serverPackageJson = {
|
|
457
|
+
name: "test-app",
|
|
458
|
+
dependencies: {
|
|
459
|
+
"@superblocksteam/library": "1.0.0",
|
|
460
|
+
react: "19.0.0",
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
await fs.writeFile(
|
|
466
|
+
path.join(tmpDir, "package.json"),
|
|
467
|
+
JSON.stringify(localPackageJson, null, 2),
|
|
468
|
+
);
|
|
469
|
+
const { readPackage } = await import("read-pkg");
|
|
470
|
+
(readPackage as Mock).mockImplementation(async ({ cwd }) =>
|
|
471
|
+
JSON.parse(await fs.readFile(path.join(cwd, "package.json"), "utf-8")),
|
|
472
|
+
);
|
|
473
|
+
mockSyncService.downloadDirectory.mockImplementationOnce(async () => {
|
|
474
|
+
await fs.writeFile(
|
|
475
|
+
path.join(tmpDir, "package.json"),
|
|
476
|
+
JSON.stringify(serverPackageJson, null, 2),
|
|
477
|
+
);
|
|
478
|
+
});
|
|
479
|
+
const sdk = buildMockSdk();
|
|
480
|
+
sdk.hashLocalDirectory = vi.fn(async () => ({ hash: "local" }));
|
|
481
|
+
sdk.dbfsGetApplicationDirectoryHash = vi.fn(async () => "server");
|
|
482
|
+
const { dev } = await import("./dev.mjs");
|
|
483
|
+
|
|
484
|
+
await dev(
|
|
485
|
+
buildDevOptions({
|
|
486
|
+
cwd: tmpDir,
|
|
487
|
+
sdk,
|
|
488
|
+
}) as any,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
const finalPackageJson = JSON.parse(
|
|
492
|
+
await fs.readFile(path.join(tmpDir, "package.json"), "utf-8"),
|
|
493
|
+
);
|
|
494
|
+
expect(finalPackageJson.dependencies["@superblocksteam/library"]).toBe(
|
|
495
|
+
"1.0.0",
|
|
496
|
+
);
|
|
497
|
+
expectInstallToHaveRun();
|
|
498
|
+
} finally {
|
|
499
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("runs npm install when S3 restore changes package.json install inputs", async () => {
|
|
504
|
+
const { dev } = await import("./dev.mjs");
|
|
505
|
+
|
|
506
|
+
await dev(
|
|
507
|
+
buildDevOptions({
|
|
508
|
+
forcePackageInstall: true,
|
|
509
|
+
}) as any,
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
expectInstallToHaveRun();
|
|
513
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
514
|
+
"Package install decision",
|
|
515
|
+
expect.objectContaining({
|
|
516
|
+
packageJsonRequiresInstall: false,
|
|
517
|
+
forcePackageInstall: true,
|
|
518
|
+
upgradePromiseCount: 0,
|
|
519
|
+
}),
|
|
520
|
+
);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it("uploads local package state when S3 restore forces package installation", async () => {
|
|
524
|
+
const { dev } = await import("./dev.mjs");
|
|
525
|
+
|
|
526
|
+
await dev(
|
|
527
|
+
buildDevOptions({
|
|
528
|
+
forcePackageInstall: true,
|
|
529
|
+
}) as any,
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
expect(mockSyncService.uploadDirectory).toHaveBeenCalledWith("cli:sdk");
|
|
533
|
+
expect(mockSyncService.uploadDirectoryNowIfNeeded).toHaveBeenCalledWith(
|
|
534
|
+
"cli:sdk",
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
it("clears forced npm install when DBFS restores package.json to the pre-restore install inputs", async () => {
|
|
539
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "sdk-dev-dbfs-"));
|
|
540
|
+
const warmPackageJson = {
|
|
541
|
+
name: "test-app",
|
|
542
|
+
dependencies: {
|
|
543
|
+
"@superblocksteam/library": "2.0.0",
|
|
544
|
+
react: "19.0.0",
|
|
545
|
+
},
|
|
546
|
+
};
|
|
547
|
+
const restoredPackageJson = {
|
|
548
|
+
name: "test-app",
|
|
549
|
+
dependencies: {
|
|
550
|
+
"@superblocksteam/library": "2.0.0",
|
|
551
|
+
random: "1.0.0",
|
|
552
|
+
react: "19.0.0",
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
try {
|
|
557
|
+
await fs.writeFile(
|
|
558
|
+
path.join(tmpDir, "package.json"),
|
|
559
|
+
JSON.stringify(restoredPackageJson, null, 2),
|
|
560
|
+
);
|
|
561
|
+
const { readPackage } = await import("read-pkg");
|
|
562
|
+
(readPackage as Mock).mockImplementation(async ({ cwd }) =>
|
|
563
|
+
JSON.parse(await fs.readFile(path.join(cwd, "package.json"), "utf-8")),
|
|
564
|
+
);
|
|
565
|
+
mockSyncService.downloadDirectory.mockImplementationOnce(async () => {
|
|
566
|
+
await fs.writeFile(
|
|
567
|
+
path.join(tmpDir, "package.json"),
|
|
568
|
+
JSON.stringify(warmPackageJson, null, 2),
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
const sdk = buildMockSdk();
|
|
572
|
+
sdk.hashLocalDirectory = vi.fn(async () => ({ hash: "local" }));
|
|
573
|
+
sdk.dbfsGetApplicationDirectoryHash = vi.fn(async () => "server");
|
|
574
|
+
const { dev } = await import("./dev.mjs");
|
|
575
|
+
|
|
576
|
+
await dev(
|
|
577
|
+
buildDevOptions({
|
|
578
|
+
cwd: tmpDir,
|
|
579
|
+
forcePackageInstall: true,
|
|
580
|
+
packageJsonSnapshotBeforeRestore:
|
|
581
|
+
packageJsonSnapshot(warmPackageJson),
|
|
582
|
+
sdk,
|
|
583
|
+
}) as any,
|
|
584
|
+
);
|
|
585
|
+
|
|
586
|
+
expect(execMock).not.toHaveBeenCalled();
|
|
587
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
588
|
+
"Package install decision",
|
|
589
|
+
expect.objectContaining({
|
|
590
|
+
forcePackageInstall: false,
|
|
591
|
+
forcePackageInstallRequested: true,
|
|
592
|
+
packageJsonRequiresInstall: false,
|
|
593
|
+
}),
|
|
594
|
+
);
|
|
595
|
+
} finally {
|
|
596
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
});
|