@superblocksteam/sdk 2.0.124-next.0 → 2.0.124-next.2
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.mjs +234 -1
- package/dist/cli-replacement/dev-s3-restore.test.mjs.map +1 -1
- package/dist/cli-replacement/dev.d.mts +11 -0
- package/dist/cli-replacement/dev.d.mts.map +1 -1
- package/dist/cli-replacement/dev.mjs +95 -8
- package/dist/cli-replacement/dev.mjs.map +1 -1
- package/package.json +6 -6
- package/src/cli-replacement/dev-s3-restore.test.mts +345 -1
- package/src/cli-replacement/dev.mts +111 -12
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
} from "@superblocksteam/vite-plugin-file-sync/lock-service";
|
|
39
39
|
import {
|
|
40
40
|
type NpmRegistryClient,
|
|
41
|
+
type NpmRegistryFetchResult,
|
|
41
42
|
type ParseContext,
|
|
42
43
|
shouldIgnoreInstallScripts,
|
|
43
44
|
} from "@superblocksteam/vite-plugin-file-sync/npm-registry";
|
|
@@ -224,6 +225,79 @@ async function readPkgJson(cwd: string) {
|
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
async function readPackageLock(cwd: string): Promise<string | null> {
|
|
229
|
+
try {
|
|
230
|
+
return await nodeFs.readFile(path.join(cwd, "package-lock.json"), "utf8");
|
|
231
|
+
} catch {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Canonicalize a lockfile for post-install change detection. Every boot,
|
|
238
|
+
* `stripResolvedFromLockfile` (APPS-4300) removes the `resolved` URLs and
|
|
239
|
+
* the validation install writes them back, so comparing raw bytes flags
|
|
240
|
+
* "changed" on EVERY registry-validation boot — re-uploading the full
|
|
241
|
+
* workspace to DBFS forever. Dropping `resolved` from both sides of the
|
|
242
|
+
* comparison (same npm v2+ `.packages` shape the strip targets) means only
|
|
243
|
+
* material resolution changes — added/removed packages, version bumps,
|
|
244
|
+
* integrity changes — trigger the upload.
|
|
245
|
+
*/
|
|
246
|
+
export function lockfileComparisonKey(raw: string | null): string | null {
|
|
247
|
+
if (raw === null) return null;
|
|
248
|
+
try {
|
|
249
|
+
const parsed = JSON.parse(raw) as { packages?: Record<string, unknown> };
|
|
250
|
+
if (parsed?.packages && typeof parsed.packages === "object") {
|
|
251
|
+
for (const entry of Object.values(parsed.packages)) {
|
|
252
|
+
if (entry && typeof entry === "object") {
|
|
253
|
+
delete (entry as { resolved?: unknown }).resolved;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return JSON.stringify(parsed);
|
|
258
|
+
} catch {
|
|
259
|
+
// Malformed lockfile: fall back to byte-level comparison.
|
|
260
|
+
return raw;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function shouldValidatePrivateRegistryInstall(
|
|
265
|
+
npmRegistryClient: NpmRegistryClient | undefined,
|
|
266
|
+
logger: Logger,
|
|
267
|
+
): Promise<boolean> {
|
|
268
|
+
if (!npmRegistryClient) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const result: NpmRegistryFetchResult = await npmRegistryClient.getConfig();
|
|
274
|
+
// `configured` / `stale` → the org is known to route installs through a
|
|
275
|
+
// registry (`stale` = last-known-good config during a config-service
|
|
276
|
+
// outage), so the startup install must run even on identical
|
|
277
|
+
// package.json snapshots: with `resolved` stripped from the lockfiles
|
|
278
|
+
// (APPS-4300), npm re-resolves the entire baked tree through that
|
|
279
|
+
// registry, surfacing missing packages at app-creation time instead of
|
|
280
|
+
// at deploy-time bundle build (APPS-4527).
|
|
281
|
+
//
|
|
282
|
+
// `not-configured` and `unreachable` deliberately do NOT force the
|
|
283
|
+
// install. Not-configured orgs gain nothing from re-resolving against
|
|
284
|
+
// public npm on every boot. And when the config service is unreachable
|
|
285
|
+
// with no last-known-good, we cannot materialize the right `.npmrc` —
|
|
286
|
+
// forcing an install would re-resolve a private-registry org's lockfile
|
|
287
|
+
// against whatever registry happens to be on disk, "validating" against
|
|
288
|
+
// the wrong host and writing its URLs back into the lockfile. Fail
|
|
289
|
+
// closed instead (same principle as the AppShell short-circuit,
|
|
290
|
+
// APPS-4370): skip validation and keep today's snapshot decision.
|
|
291
|
+
return result.source === "configured" || result.source === "stale";
|
|
292
|
+
} catch (err) {
|
|
293
|
+
logger.warn(
|
|
294
|
+
"Could not resolve npm registry config for startup install validation; preserving package snapshot decision",
|
|
295
|
+
getErrorMeta(err),
|
|
296
|
+
);
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
227
301
|
async function normalizePackageJsonForNpm(cwd: string, logger: Logger) {
|
|
228
302
|
const packageJsonPath = path.join(cwd, "package.json");
|
|
229
303
|
let raw: string;
|
|
@@ -1221,13 +1295,22 @@ export async function dev(options: {
|
|
|
1221
1295
|
if (!packageJsonBefore && packageJsonRequiresInstall) {
|
|
1222
1296
|
logger.info("package.json was created, installing packages…");
|
|
1223
1297
|
}
|
|
1298
|
+
const npmRegistryClient = aiService?.getNpmRegistryClient();
|
|
1224
1299
|
const forcePackageInstallRequested = !!options.forcePackageInstall;
|
|
1225
1300
|
let forcePackageInstall = forcePackageInstallRequested;
|
|
1301
|
+
const privateRegistryRequiresInstallValidation = packageJsonAfter
|
|
1302
|
+
? await shouldValidatePrivateRegistryInstall(
|
|
1303
|
+
npmRegistryClient,
|
|
1304
|
+
logger,
|
|
1305
|
+
)
|
|
1306
|
+
: false;
|
|
1226
1307
|
if (
|
|
1227
1308
|
forcePackageInstallRequested &&
|
|
1228
1309
|
hasPackageJsonSnapshotBeforeRestore
|
|
1229
1310
|
) {
|
|
1230
|
-
forcePackageInstall =
|
|
1311
|
+
forcePackageInstall =
|
|
1312
|
+
packageJsonRequiresInstall ||
|
|
1313
|
+
privateRegistryRequiresInstallValidation;
|
|
1231
1314
|
}
|
|
1232
1315
|
|
|
1233
1316
|
logger.info("Package install decision", {
|
|
@@ -1237,6 +1320,7 @@ export async function dev(options: {
|
|
|
1237
1320
|
packageJsonRequiresInstall,
|
|
1238
1321
|
forcePackageInstall,
|
|
1239
1322
|
forcePackageInstallRequested,
|
|
1323
|
+
privateRegistryRequiresInstallValidation,
|
|
1240
1324
|
upgradePromiseCount: upgradePromises.length,
|
|
1241
1325
|
packageJsonSnapshotBefore: packageJsonSnapshotDiagnostic(
|
|
1242
1326
|
packageJsonSnapshotBefore,
|
|
@@ -1280,14 +1364,17 @@ export async function dev(options: {
|
|
|
1280
1364
|
}
|
|
1281
1365
|
|
|
1282
1366
|
// Run the verification install when EITHER the package.json
|
|
1283
|
-
// requires it,
|
|
1284
|
-
// (re-syncing node_modules),
|
|
1285
|
-
// the
|
|
1367
|
+
// requires it, upgrades just modified package.json/lockfile
|
|
1368
|
+
// (re-syncing node_modules), the caller forced it, or a custom
|
|
1369
|
+
// registry must validate that the required packages resolve there.
|
|
1370
|
+
let packageLockBeforeInstall: string | null = null;
|
|
1286
1371
|
if (
|
|
1287
1372
|
packageJsonRequiresInstall ||
|
|
1288
1373
|
upgradePromises.length > 0 ||
|
|
1289
|
-
forcePackageInstall
|
|
1374
|
+
forcePackageInstall ||
|
|
1375
|
+
privateRegistryRequiresInstallValidation
|
|
1290
1376
|
) {
|
|
1377
|
+
packageLockBeforeInstall = await readPackageLock(cwd);
|
|
1291
1378
|
// Launch the install while the upload/restart decisions below
|
|
1292
1379
|
// are still being evaluated. The synchronous joins at upload,
|
|
1293
1380
|
// CLI restart, and pre-Vite-startup all observe and surface
|
|
@@ -1299,11 +1386,7 @@ export async function dev(options: {
|
|
|
1299
1386
|
"installPackages",
|
|
1300
1387
|
async (span) => {
|
|
1301
1388
|
try {
|
|
1302
|
-
await installPackages(
|
|
1303
|
-
cwd,
|
|
1304
|
-
logger,
|
|
1305
|
-
aiService?.getNpmRegistryClient(),
|
|
1306
|
-
);
|
|
1389
|
+
await installPackages(cwd, logger, npmRegistryClient);
|
|
1307
1390
|
} finally {
|
|
1308
1391
|
span.end();
|
|
1309
1392
|
}
|
|
@@ -1327,7 +1410,12 @@ export async function dev(options: {
|
|
|
1327
1410
|
|
|
1328
1411
|
const shouldUploadPackageState =
|
|
1329
1412
|
hasPackageChanged || forcePackageInstall;
|
|
1330
|
-
|
|
1413
|
+
let shouldUploadAfterInstall = shouldUploadPackageState;
|
|
1414
|
+
if (
|
|
1415
|
+
shouldUploadPackageState ||
|
|
1416
|
+
uploadFirst ||
|
|
1417
|
+
privateRegistryRequiresInstallValidation
|
|
1418
|
+
) {
|
|
1331
1419
|
// Upload serializes the post-install lockfile + node_modules
|
|
1332
1420
|
// tree to DBFS, so it must observe quiesced upgrade+install.
|
|
1333
1421
|
// Upgrade first — its rejection exits before an install
|
|
@@ -1356,7 +1444,18 @@ export async function dev(options: {
|
|
|
1356
1444
|
throw joinError;
|
|
1357
1445
|
}
|
|
1358
1446
|
}
|
|
1359
|
-
if (
|
|
1447
|
+
if (
|
|
1448
|
+
privateRegistryRequiresInstallValidation &&
|
|
1449
|
+
!shouldUploadAfterInstall &&
|
|
1450
|
+
lockfileComparisonKey(packageLockBeforeInstall) !==
|
|
1451
|
+
lockfileComparisonKey(await readPackageLock(cwd))
|
|
1452
|
+
) {
|
|
1453
|
+
shouldUploadAfterInstall = true;
|
|
1454
|
+
}
|
|
1455
|
+
if (
|
|
1456
|
+
!skipStartupUploadForCliRestart &&
|
|
1457
|
+
(shouldUploadAfterInstall || uploadFirst)
|
|
1458
|
+
) {
|
|
1360
1459
|
logger.info(
|
|
1361
1460
|
`Uploading local files to branch '${activeDbfsBranchName}' on server before starting`,
|
|
1362
1461
|
);
|