@superblocksteam/sdk 2.0.123-next.0 → 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
|
@@ -11,6 +11,7 @@ import fs from "fs-extra";
|
|
|
11
11
|
import { resolveCommand } from "package-manager-detector";
|
|
12
12
|
import { detect } from "package-manager-detector/detect";
|
|
13
13
|
|
|
14
|
+
import type { ServerError } from "@superblocksteam/library-shared/types";
|
|
14
15
|
import {
|
|
15
16
|
buildGithubSuperblocksSyncWorkflow,
|
|
16
17
|
buildGithubSuperblocksSyncWorkflowFromBaseUrl,
|
|
@@ -35,10 +36,16 @@ import {
|
|
|
35
36
|
LockService,
|
|
36
37
|
LockType,
|
|
37
38
|
} from "@superblocksteam/vite-plugin-file-sync/lock-service";
|
|
39
|
+
import {
|
|
40
|
+
type NpmRegistryClient,
|
|
41
|
+
type ParseContext,
|
|
42
|
+
shouldIgnoreInstallScripts,
|
|
43
|
+
} from "@superblocksteam/vite-plugin-file-sync/npm-registry";
|
|
38
44
|
import { OperationQueue } from "@superblocksteam/vite-plugin-file-sync/operation-queue";
|
|
39
45
|
import { AutoConnectingRpcClient } from "@superblocksteam/vite-plugin-file-sync/server-rpc";
|
|
40
46
|
import { SyncService } from "@superblocksteam/vite-plugin-file-sync/sync-service";
|
|
41
47
|
|
|
48
|
+
import { devServerMetrics } from "../dev-utils/dev-server-metrics.mjs";
|
|
42
49
|
import { createDevServer } from "../dev-utils/dev-server.mjs";
|
|
43
50
|
import { AUTO_UPGRADE_EXIT_CODE } from "../index.js";
|
|
44
51
|
import type {
|
|
@@ -54,11 +61,23 @@ import type {
|
|
|
54
61
|
ApplicationConfigWithTokenConfigAndUserInfo,
|
|
55
62
|
TokenConfig,
|
|
56
63
|
} from "../types/index.js";
|
|
57
|
-
import {
|
|
64
|
+
import {
|
|
65
|
+
buildInstallEnv,
|
|
66
|
+
checkVersionsAndWritePackageJson,
|
|
67
|
+
} from "./automatic-upgrades.js";
|
|
68
|
+
import {
|
|
69
|
+
classifyInitialInstallError,
|
|
70
|
+
InitialInstallFailed,
|
|
71
|
+
} from "./dependency-install-classifier.mjs";
|
|
58
72
|
import {
|
|
59
73
|
ensureRemoteHasDefaultBranch,
|
|
60
74
|
getGitErrorFields,
|
|
61
75
|
} from "./git-repo-setup.mjs";
|
|
76
|
+
import {
|
|
77
|
+
superblocksLogsPath,
|
|
78
|
+
superblocksNpmrcPath,
|
|
79
|
+
syncHomeNpmrc,
|
|
80
|
+
} from "./home-npmrc.mjs";
|
|
62
81
|
import { normalizeWorkspaceProtocolForNpm } from "./normalize-workspace-protocol.js";
|
|
63
82
|
import {
|
|
64
83
|
didPackageJsonSnapshotChange,
|
|
@@ -69,8 +88,6 @@ import {
|
|
|
69
88
|
} from "./package-json-snapshot.mjs";
|
|
70
89
|
import { getCurrentCliVersion } from "./version-detection.js";
|
|
71
90
|
|
|
72
|
-
const exec = promisify(child_process.exec);
|
|
73
|
-
|
|
74
91
|
const passErrorToVSCode = (message: string | undefined, logger: Logger) => {
|
|
75
92
|
if (message && process.env.SUPERBLOCKS_VSCODE === "true") {
|
|
76
93
|
// Prefixing with `clierr:` will make the VS code extension capture this message and show it to the user.
|
|
@@ -246,7 +263,11 @@ async function normalizePackageJsonForNpm(cwd: string, logger: Logger) {
|
|
|
246
263
|
}
|
|
247
264
|
}
|
|
248
265
|
|
|
249
|
-
async function installPackages(
|
|
266
|
+
export async function installPackages(
|
|
267
|
+
cwd: string,
|
|
268
|
+
logger: Logger,
|
|
269
|
+
npmRegistryClient?: NpmRegistryClient,
|
|
270
|
+
) {
|
|
250
271
|
try {
|
|
251
272
|
const pm = await detect({
|
|
252
273
|
strategies: [
|
|
@@ -272,11 +293,41 @@ async function installPackages(cwd: string, logger: Logger) {
|
|
|
272
293
|
await normalizePackageJsonForNpm(cwd, logger);
|
|
273
294
|
}
|
|
274
295
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
296
|
+
// Honor the per-org `allow_install_scripts` policy on the dev-server
|
|
297
|
+
// startup install just like AppShell does for agent-driven installs
|
|
298
|
+
// (see `shell.ts`). When the org has opted out (`allowInstallScripts ===
|
|
299
|
+
// false`), append `--ignore-scripts` so lifecycle scripts don't run
|
|
300
|
+
// during dev-server boot. `undefined` (LD flag off, server omitted the
|
|
301
|
+
// field, no client wired up, or the resolution itself failed) keeps
|
|
302
|
+
// today's behaviour. The client owns last-known-good / cross-outage
|
|
303
|
+
// policy preservation; we deliberately swallow resolution errors here
|
|
304
|
+
// so a transient registry-endpoint outage doesn't abort the user's
|
|
305
|
+
// startup install — the policy default is "scripts allowed" anyway.
|
|
306
|
+
let ignoreScripts = false;
|
|
307
|
+
if (npmRegistryClient) {
|
|
308
|
+
try {
|
|
309
|
+
const result = await npmRegistryClient.getConfig();
|
|
310
|
+
ignoreScripts = shouldIgnoreInstallScripts(result);
|
|
311
|
+
} catch (err) {
|
|
312
|
+
logger.warn(
|
|
313
|
+
"Could not resolve npm install-scripts policy; proceeding without --ignore-scripts",
|
|
314
|
+
getErrorMeta(err),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// `--json` makes npm channel the structured error envelope (code +
|
|
320
|
+
// summary + detail) onto stdout so a failure can be classified into a
|
|
321
|
+
// DependencyInstallError. Output-only: it does not change resolution,
|
|
322
|
+
// only how npm reports it. The success-path `logger.info(stdout)` below
|
|
323
|
+
// then logs JSON, which is acceptable.
|
|
324
|
+
const baseArgs =
|
|
325
|
+
pm.agent === "npm" ? ["--fund=false", "--audit=false", "--json"] : [];
|
|
326
|
+
const installArgs = ignoreScripts
|
|
327
|
+
? [...baseArgs, "--ignore-scripts"]
|
|
328
|
+
: baseArgs;
|
|
329
|
+
|
|
330
|
+
const installCommand = resolveCommand(pm.agent, "install", installArgs);
|
|
280
331
|
|
|
281
332
|
if (!installCommand) {
|
|
282
333
|
logger.warn(
|
|
@@ -290,15 +341,117 @@ async function installPackages(cwd: string, logger: Logger) {
|
|
|
290
341
|
`Running ${command} ${args.join(" ")} to install dependencies…`,
|
|
291
342
|
);
|
|
292
343
|
|
|
344
|
+
// Pin both npm and pnpm to the Superblocks-owned userconfig via env
|
|
345
|
+
// overlay. `buildInstallEnv` (from `automatic-upgrades.ts`) sets
|
|
346
|
+
// both `NPM_CONFIG_USERCONFIG` (npm, pnpm <= 10) and
|
|
347
|
+
// `PNPM_CONFIG_USERCONFIG` (pnpm 11+) — pnpm 11 stopped honouring
|
|
348
|
+
// the `npm_config_*` env vars. CLI flags differ between agents
|
|
349
|
+
// (`--userconfig=` for npm, `--config.userconfig=` for pnpm), so
|
|
350
|
+
// the env overlay is the uniform mechanism. Without this, customer
|
|
351
|
+
// pods using a private registry would read default `~/.npmrc`
|
|
352
|
+
// (empty after the relocation) and fail to resolve private
|
|
353
|
+
// packages. Centralised in `buildInstallEnv` so this and the CLI
|
|
354
|
+
// auto-upgrade install stay in sync.
|
|
355
|
+
// Ensure the per-app log dir exists so npm's debug log (routed via
|
|
356
|
+
// `NPM_CONFIG_LOGS_DIR` in `buildInstallEnv`) lands in a predictable
|
|
357
|
+
// place. npm would create it on demand, but doing it up front means the
|
|
358
|
+
// folder exists even for pnpm runs (which ignore the var) and for any
|
|
359
|
+
// log-collection sidecar watching `<app>/.superblocks/logs`.
|
|
360
|
+
const logsDir = superblocksLogsPath(cwd);
|
|
361
|
+
await nodeFs.mkdir(logsDir, { recursive: true }).catch(() => undefined);
|
|
362
|
+
|
|
363
|
+
// Resolve the promisified `exec` at CALL time — not module scope — so a
|
|
364
|
+
// test's `vi.mock("node:child_process")` is always honoured regardless of
|
|
365
|
+
// when `dev.mjs` was first evaluated. A module-scope capture binds to
|
|
366
|
+
// whichever `child_process.exec` was live at first import; if a sibling
|
|
367
|
+
// test imports this module before installing its own mock (e.g.
|
|
368
|
+
// `dev-token-priming` / `dev.interception`, which don't mock
|
|
369
|
+
// `child_process`), that binding is the REAL npm and the classify test's
|
|
370
|
+
// mock silently never takes effect — the exact APPS-4450 CI failure
|
|
371
|
+
// (`category: "unknown"`, `npmErrorCode: undefined` from real npm against a
|
|
372
|
+
// missing `/tmp/app`).
|
|
373
|
+
const exec = promisify(child_process.exec);
|
|
293
374
|
const { stdout } = await exec(`${command} ${args.join(" ")}`, {
|
|
294
375
|
cwd,
|
|
376
|
+
env: buildInstallEnv(superblocksNpmrcPath(), logsDir),
|
|
295
377
|
});
|
|
296
378
|
logger.info("Package installation completed successfully");
|
|
297
379
|
logger.info(stdout);
|
|
298
380
|
} catch (error) {
|
|
299
381
|
logger.error("Error during package installation", getErrorMeta(error));
|
|
300
|
-
|
|
382
|
+
// `util.promisify(child_process.exec)` preserves `.stdout`/`.stderr` on
|
|
383
|
+
// the rejected error separately; with `--json`, the structured npm error
|
|
384
|
+
// envelope lands on `.stdout`. Classify it into a DependencyInstallError
|
|
385
|
+
// and throw the `InitialInstallFailed` marker so the `dev()` catch can
|
|
386
|
+
// degrade by origin rather than crash-looping the dev-server pod.
|
|
387
|
+
const ctx = await buildInstallParseContext(cwd, npmRegistryClient);
|
|
388
|
+
const f = error as { stdout?: string; stderr?: string; message?: string };
|
|
389
|
+
const serverError = classifyInitialInstallError(
|
|
390
|
+
{ stdout: f.stdout, stderr: f.stderr, message: f.message },
|
|
391
|
+
ctx,
|
|
392
|
+
);
|
|
393
|
+
throw new InitialInstallFailed(serverError);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Build the `ParseContext` the dependency-install classifier needs from the
|
|
399
|
+
* failing install's working directory + registry client:
|
|
400
|
+
*
|
|
401
|
+
* - `requestedPackages` — the union of `dependencies` + `devDependencies`
|
|
402
|
+
* in the app's `package.json`, used by the registry-blocked renderers to
|
|
403
|
+
* name the failing specs when npm's `--json` `detail` doesn't.
|
|
404
|
+
* - `hasAnyRegistryConfigured` — the tri-state derived from the registry
|
|
405
|
+
* client's most recent `getConfig()`, mirroring AppShell's
|
|
406
|
+
* `deriveHasAnyRegistryConfigured` (`shell.ts`): `configured`/`stale`
|
|
407
|
+
* → `true` (rows known to exist), `not-configured` → `false` (deliberate
|
|
408
|
+
* "no rows"), `unreachable` → `undefined` ("we don't know" — the renderer
|
|
409
|
+
* falls back to the default variant). Left `undefined` when no client is
|
|
410
|
+
* wired in or the resolution itself throws.
|
|
411
|
+
*
|
|
412
|
+
* Best-effort throughout: a missing/unparseable package.json or a registry
|
|
413
|
+
* outage must not mask the underlying install failure, so both lookups are
|
|
414
|
+
* caught and degrade to empty/undefined.
|
|
415
|
+
*/
|
|
416
|
+
async function buildInstallParseContext(
|
|
417
|
+
cwd: string,
|
|
418
|
+
npmRegistryClient?: NpmRegistryClient,
|
|
419
|
+
): Promise<ParseContext> {
|
|
420
|
+
let requestedPackages: { name: string; version?: string }[] = [];
|
|
421
|
+
try {
|
|
422
|
+
const pkg = JSON.parse(
|
|
423
|
+
await nodeFs.readFile(path.join(cwd, "package.json"), "utf8"),
|
|
424
|
+
);
|
|
425
|
+
requestedPackages = Object.entries({
|
|
426
|
+
...(pkg.dependencies ?? {}),
|
|
427
|
+
...(pkg.devDependencies ?? {}),
|
|
428
|
+
}).map(([name, version]) => ({ name, version: String(version) }));
|
|
429
|
+
} catch {
|
|
430
|
+
/* best-effort: a missing/unparseable package.json yields no specs */
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
let hasAnyRegistryConfigured: boolean | undefined;
|
|
434
|
+
if (npmRegistryClient) {
|
|
435
|
+
try {
|
|
436
|
+
const result = await npmRegistryClient.getConfig();
|
|
437
|
+
switch (result.source) {
|
|
438
|
+
case "configured":
|
|
439
|
+
case "stale":
|
|
440
|
+
hasAnyRegistryConfigured = true;
|
|
441
|
+
break;
|
|
442
|
+
case "not-configured":
|
|
443
|
+
hasAnyRegistryConfigured = false;
|
|
444
|
+
break;
|
|
445
|
+
case "unreachable":
|
|
446
|
+
hasAnyRegistryConfigured = undefined;
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
/* leave undefined → renderers treat as "don't know" / default variant */
|
|
451
|
+
}
|
|
301
452
|
}
|
|
453
|
+
|
|
454
|
+
return { requestedPackages, hasAnyRegistryConfigured };
|
|
302
455
|
}
|
|
303
456
|
|
|
304
457
|
export enum DevServerAutoUpgradeMode {
|
|
@@ -314,6 +467,67 @@ export enum DevServerAutoUpgradeMode {
|
|
|
314
467
|
SKIP_CLI_ONLY = "skip-cli-only",
|
|
315
468
|
}
|
|
316
469
|
|
|
470
|
+
/**
|
|
471
|
+
* Seed `tokenManager` with the initial token the CLI received from auth.json
|
|
472
|
+
* (standalone dev) or `/_sb_activate` (SABS live-edit pod activation), so the
|
|
473
|
+
* `NpmRegistryClient`'s JWT source has a usable bearer credential on cold
|
|
474
|
+
* boot.
|
|
475
|
+
*
|
|
476
|
+
* Without this seeding, `TokenManager.updateToken` is only ever called by
|
|
477
|
+
* `AuthHotReloadServer` (`auth-hot-reload.mts`) — and that socket is
|
|
478
|
+
* explicitly disabled on live-edit pods (`sabs/entrypoint-local.sh` sets
|
|
479
|
+
* `SUPERBLOCKS_AUTH_HOT_RELOAD=false`), so `NpmRegistryClient.getConfig()`
|
|
480
|
+
* cannot authenticate its server fetch on cold boot. The result is
|
|
481
|
+
* `source: "unreachable"`, `syncHomeNpmrc` skips with `~/.npmrc` left
|
|
482
|
+
* untouched, and the CLI auto-upgrade that fires moments later resolves
|
|
483
|
+
* `npm install -g @superblocksteam/cli@…` through public npm instead of the
|
|
484
|
+
* customer's configured private registry.
|
|
485
|
+
*
|
|
486
|
+
* Call-site ordering is intentionally NOT load-bearing: `TokenManager`
|
|
487
|
+
* retains the current token as state and `AiService` reads
|
|
488
|
+
* `tokenManager.getCurrentToken()` synchronously at construction time, so
|
|
489
|
+
* the prime call works whether it runs before or after consumer
|
|
490
|
+
* construction. The `tokenUpdated` event stream stays as the refresh
|
|
491
|
+
* channel for future rotations (hot-reload pushes, JWT renewal), so this
|
|
492
|
+
* seed is purely additive.
|
|
493
|
+
*/
|
|
494
|
+
export function primeTokenManagerWithInitialToken(
|
|
495
|
+
tokenManager: TokenManager,
|
|
496
|
+
token: string,
|
|
497
|
+
): void {
|
|
498
|
+
if (token) {
|
|
499
|
+
tokenManager.updateToken(token);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export interface DevServerStatus {
|
|
504
|
+
serverErrors: ServerError[];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** Decide how the startup catch handles an error: degrade (record, keep Vite up)
|
|
508
|
+
* for an app-install failure (the InitialInstallFailed marker), or exit for
|
|
509
|
+
* anything else (lock/sync/upgrade). Pure + unit-tested. Does NOT call process.exit. */
|
|
510
|
+
export function handleStartupError(
|
|
511
|
+
error: unknown,
|
|
512
|
+
status: DevServerStatus,
|
|
513
|
+
logger: Logger,
|
|
514
|
+
): "degrade" | "exit" {
|
|
515
|
+
if (error instanceof InitialInstallFailed) {
|
|
516
|
+
status.serverErrors.push(error.serverError);
|
|
517
|
+
logger.error(
|
|
518
|
+
"[dev-server] initial dependency install failed; keeping dev server up",
|
|
519
|
+
getErrorMeta(error),
|
|
520
|
+
);
|
|
521
|
+
devServerMetrics.recordInitialInstallFailure({
|
|
522
|
+
category: error.serverError.category,
|
|
523
|
+
npmErrorCode: error.serverError.npmErrorCode,
|
|
524
|
+
hasAnyRegistryConfigured: error.serverError.hasAnyRegistryConfigured,
|
|
525
|
+
});
|
|
526
|
+
return "degrade";
|
|
527
|
+
}
|
|
528
|
+
return "exit";
|
|
529
|
+
}
|
|
530
|
+
|
|
317
531
|
export async function dev(options: {
|
|
318
532
|
/* cwd is required */
|
|
319
533
|
cwd: string;
|
|
@@ -380,6 +594,15 @@ export async function dev(options: {
|
|
|
380
594
|
sdk,
|
|
381
595
|
} = options;
|
|
382
596
|
|
|
597
|
+
// Seed the tokenManager with the initial CLI token so downstream consumers
|
|
598
|
+
// (AiService, AutoConnectingRpcClient, syncHomeNpmrc) have a usable
|
|
599
|
+
// bearer credential without waiting on the AuthHotReloadServer push
|
|
600
|
+
// refresh (which is disabled on live-edit pods). Order-independent: the
|
|
601
|
+
// manager retains state and AiService reads it synchronously during
|
|
602
|
+
// construction. See `primeTokenManagerWithInitialToken` for the
|
|
603
|
+
// cold-boot rationale.
|
|
604
|
+
primeTokenManagerWithInitialToken(tokenManager, tokenConfig.token);
|
|
605
|
+
|
|
383
606
|
// May be overridden by a pending snapshot restore
|
|
384
607
|
let { downloadFirst, uploadFirst } = options;
|
|
385
608
|
|
|
@@ -391,17 +614,40 @@ export async function dev(options: {
|
|
|
391
614
|
let snapshotManager: SnapshotManager | undefined;
|
|
392
615
|
let gitUserName: string | undefined;
|
|
393
616
|
let gitUserEmail: string | undefined;
|
|
394
|
-
// In-flight
|
|
395
|
-
//
|
|
396
|
-
//
|
|
617
|
+
// In-flight handles for the two background jobs we launch from sync.
|
|
618
|
+
// We keep them SEPARATE so an install rejection cannot swallow an upgrade
|
|
619
|
+
// rejection (or vice-versa) the way a single `Promise.all` would: that
|
|
620
|
+
// bundle settles on the FIRST rejection and silently absorbs the other's
|
|
621
|
+
// outcome, breaking APPS-4457's intent that auto-upgrade failures still
|
|
622
|
+
// exit (not degrade) and CLI-restart-on-upgrade still happens even if the
|
|
623
|
+
// app install fails.
|
|
624
|
+
//
|
|
625
|
+
// - packageUpgradePromise: library upgrades (and their `npm install`
|
|
626
|
+
// side-effects) from `checkVersionsAndWritePackageJson`. Rejection is
|
|
627
|
+
// OUT of scope for graceful degrade (APPS-4457) — `handleStartupError`
|
|
628
|
+
// routes it to `process.exit(1)`.
|
|
629
|
+
// - packageInstallPromise: the app's verification `npm install`.
|
|
630
|
+
// Rejection MAY be the `InitialInstallFailed` marker, which
|
|
631
|
+
// `handleStartupError` routes to the degrade path.
|
|
632
|
+
let packageUpgradePromise: Promise<void> | undefined;
|
|
397
633
|
let packageInstallPromise: Promise<void> | undefined;
|
|
398
634
|
const tracer = getTracer();
|
|
399
635
|
const logger = getLogger(options.logger);
|
|
400
|
-
// Joins the in-flight
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
636
|
+
// Joins the in-flight upgrade. Rejection propagates so the caller's step
|
|
637
|
+
// can abort cleanly (handleStartupError exits — auto-upgrade graceful
|
|
638
|
+
// degrade is OOS per APPS-4457).
|
|
639
|
+
const joinPackageUpgrade = async (reason: string): Promise<void> => {
|
|
640
|
+
if (!packageUpgradePromise) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
logger.info(`Waiting for background package upgrade (${reason})…`);
|
|
644
|
+
const promise = packageUpgradePromise;
|
|
645
|
+
packageUpgradePromise = undefined;
|
|
646
|
+
await promise;
|
|
647
|
+
};
|
|
648
|
+
// Joins the in-flight install. Rejection propagates so the caller's step
|
|
649
|
+
// can abort cleanly (handleStartupError degrades for InitialInstallFailed,
|
|
650
|
+
// exits otherwise).
|
|
405
651
|
const joinPackageInstall = async (reason: string): Promise<void> => {
|
|
406
652
|
if (!packageInstallPromise) {
|
|
407
653
|
return;
|
|
@@ -411,12 +657,36 @@ export async function dev(options: {
|
|
|
411
657
|
packageInstallPromise = undefined;
|
|
412
658
|
await promise;
|
|
413
659
|
};
|
|
660
|
+
// Settles the in-flight install WITHOUT throwing. Used on the CLI-restart
|
|
661
|
+
// path so an install failure doesn't preempt the restart (the new CLI's
|
|
662
|
+
// first boot re-runs install). Still waits for npm to finish so we don't
|
|
663
|
+
// SIGKILL it mid-rename.
|
|
664
|
+
const settlePackageInstall = async (reason: string): Promise<void> => {
|
|
665
|
+
if (!packageInstallPromise) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
logger.info(`Settling background package install (${reason})…`);
|
|
669
|
+
const promise = packageInstallPromise;
|
|
670
|
+
packageInstallPromise = undefined;
|
|
671
|
+
await Promise.allSettled([promise]);
|
|
672
|
+
};
|
|
673
|
+
// Joins upgrade THEN install. Upgrade failures surface first so
|
|
674
|
+
// handleStartupError exits (per APPS-4457) before an install rejection
|
|
675
|
+
// gets a chance to route to degrade.
|
|
676
|
+
const joinUpgradeThenInstall = async (reason: string): Promise<void> => {
|
|
677
|
+
await joinPackageUpgrade(reason);
|
|
678
|
+
await joinPackageInstall(reason);
|
|
679
|
+
};
|
|
414
680
|
const skipAutoUpgrade = autoUpgradeMode === DevServerAutoUpgradeMode.SKIP;
|
|
415
681
|
const skipCliUpgrade =
|
|
416
682
|
skipAutoUpgrade ||
|
|
417
683
|
autoUpgradeMode === DevServerAutoUpgradeMode.SKIP_CLI_ONLY;
|
|
418
684
|
|
|
419
685
|
await tracer.startActiveSpan("devServerStartup", async (startupSpan) => {
|
|
686
|
+
// Mutable startup status surfaced to the browser via createDevServer's
|
|
687
|
+
// /_sb_connect + /_sb_status. The startup catch records an app-install
|
|
688
|
+
// failure here (degrade path) so Vite still starts and serves the error.
|
|
689
|
+
const devServerStatus: DevServerStatus = { serverErrors: [] };
|
|
420
690
|
try {
|
|
421
691
|
// Add check for node_modules
|
|
422
692
|
if (!fs.existsSync(path.join(cwd, "node_modules"))) {
|
|
@@ -830,16 +1100,44 @@ export async function dev(options: {
|
|
|
830
1100
|
logger.info("[dev-startup] Skipping download, already in sync");
|
|
831
1101
|
}
|
|
832
1102
|
|
|
833
|
-
// Unconditional lockfile sanitation: strip
|
|
834
|
-
// `resolved` URLs from any lockfile on disk
|
|
835
|
-
// install runs next. The lockfile here is
|
|
836
|
-
// DBFS path (downloadFirst overwrite,
|
|
837
|
-
// import); npm honors `resolved` verbatim
|
|
838
|
-
// active registry. `integrity` is
|
|
839
|
-
// cross-registry tarball drift surfaces as
|
|
840
|
-
// there's no lockfile or no `resolved`
|
|
1103
|
+
// Unconditional lockfile sanitation (APPS-4300): strip
|
|
1104
|
+
// cross-registry `resolved` URLs from any lockfile on disk
|
|
1105
|
+
// regardless of whether install runs next. The lockfile here is
|
|
1106
|
+
// whatever survived the DBFS path (downloadFirst overwrite,
|
|
1107
|
+
// prior boot, brownfield import); npm honors `resolved` verbatim
|
|
1108
|
+
// and would bypass the active registry. `integrity` is
|
|
1109
|
+
// preserved, so genuine cross-registry tarball drift surfaces as
|
|
1110
|
+
// EINTEGRITY. No-op when there's no lockfile or no `resolved`
|
|
1111
|
+
// entries.
|
|
841
1112
|
await stripResolvedFromLockfile(cwd);
|
|
842
1113
|
|
|
1114
|
+
// Materialise `~/.superblocks/npmrc` from the server-fetched
|
|
1115
|
+
// per-org npm registry config BEFORE the global Superblocks CLI
|
|
1116
|
+
// auto-upgrade fires. With the file in place, the auto-upgrade's
|
|
1117
|
+
// `npm install -g @superblocksteam/cli@…` resolves through the
|
|
1118
|
+
// customer's private registry instead of `registry.npmjs.org`.
|
|
1119
|
+
await tracer.startActiveSpan("syncHomeNpmrc", async (span) => {
|
|
1120
|
+
try {
|
|
1121
|
+
if (!aiService) {
|
|
1122
|
+
logger.info(
|
|
1123
|
+
"[home-npmrc] skipped: AiService unavailable at startup",
|
|
1124
|
+
);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
await syncHomeNpmrc({
|
|
1128
|
+
npmRegistryClient: aiService.getNpmRegistryClient(),
|
|
1129
|
+
logger,
|
|
1130
|
+
});
|
|
1131
|
+
} catch (error) {
|
|
1132
|
+
logger.warn(
|
|
1133
|
+
"[home-npmrc] sync step failed unexpectedly; ~/.superblocks/npmrc left untouched",
|
|
1134
|
+
getErrorMeta(error),
|
|
1135
|
+
);
|
|
1136
|
+
} finally {
|
|
1137
|
+
span.end();
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
|
|
843
1141
|
let hasCliUpdated = false;
|
|
844
1142
|
let upgradePromises: Promise<void>[] = [];
|
|
845
1143
|
const forceUpgrade =
|
|
@@ -871,6 +1169,9 @@ export async function dev(options: {
|
|
|
871
1169
|
applicationConfigWithTokenConfigAndUserInfo,
|
|
872
1170
|
forceUpgrade,
|
|
873
1171
|
skipCliUpgrade,
|
|
1172
|
+
// Route the CLI-upgrade install's npm debug log into the
|
|
1173
|
+
// same `<app>/.superblocks/logs` as the startup install.
|
|
1174
|
+
cwd,
|
|
874
1175
|
);
|
|
875
1176
|
hasCliUpdated = result.cliUpdated;
|
|
876
1177
|
upgradePromises = result.upgradePromises;
|
|
@@ -948,6 +1249,40 @@ export async function dev(options: {
|
|
|
948
1249
|
),
|
|
949
1250
|
});
|
|
950
1251
|
|
|
1252
|
+
const installApplicationId = applicationConfig.id;
|
|
1253
|
+
|
|
1254
|
+
// Launch upgrades and app install as INDEPENDENT promises (not a
|
|
1255
|
+
// single `Promise.all`) so each outcome is observed on its own
|
|
1256
|
+
// join: upgrade failure exits (APPS-4457), install failure may
|
|
1257
|
+
// degrade (InitialInstallFailed). A bundled `Promise.all` would
|
|
1258
|
+
// settle on the first rejection and silently absorb the other's
|
|
1259
|
+
// result. The `.catch` backstops convert "no join fired" cases
|
|
1260
|
+
// into logged-then-handled rejections instead of unhandled ones.
|
|
1261
|
+
if (upgradePromises.length > 0) {
|
|
1262
|
+
logger.info("Starting package upgrade in background…");
|
|
1263
|
+
const launchedUpgradeCount = upgradePromises.length;
|
|
1264
|
+
packageUpgradePromise = tracer.startActiveSpan(
|
|
1265
|
+
"packageUpgrades",
|
|
1266
|
+
async (span) => {
|
|
1267
|
+
try {
|
|
1268
|
+
await Promise.all(upgradePromises);
|
|
1269
|
+
} finally {
|
|
1270
|
+
span.end();
|
|
1271
|
+
}
|
|
1272
|
+
},
|
|
1273
|
+
);
|
|
1274
|
+
packageUpgradePromise.catch((err) => {
|
|
1275
|
+
logger.error(
|
|
1276
|
+
`Background package upgrade failed [errorId=DEV_SERVER_BG_UPGRADE_FAILED applicationId=${installApplicationId} cwd=${cwd} upgradePromiseCount=${launchedUpgradeCount}]`,
|
|
1277
|
+
getErrorMeta(err),
|
|
1278
|
+
);
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// Run the verification install when EITHER the package.json
|
|
1283
|
+
// requires it, or upgrades just modified package.json/lockfile
|
|
1284
|
+
// (re-syncing node_modules), or the caller forced it. Mirrors
|
|
1285
|
+
// the original launch condition.
|
|
951
1286
|
if (
|
|
952
1287
|
packageJsonRequiresInstall ||
|
|
953
1288
|
upgradePromises.length > 0 ||
|
|
@@ -964,29 +1299,23 @@ export async function dev(options: {
|
|
|
964
1299
|
"installPackages",
|
|
965
1300
|
async (span) => {
|
|
966
1301
|
try {
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1302
|
+
await installPackages(
|
|
1303
|
+
cwd,
|
|
1304
|
+
logger,
|
|
1305
|
+
aiService?.getNpmRegistryClient(),
|
|
1306
|
+
);
|
|
972
1307
|
} finally {
|
|
973
1308
|
span.end();
|
|
974
1309
|
}
|
|
975
1310
|
},
|
|
976
1311
|
);
|
|
977
|
-
// Backstop: assigning `.catch` to a separate (discarded) promise
|
|
978
|
-
// keeps `packageInstallPromise` itself rejecting, so the joins
|
|
979
|
-
// can still observe and abort. Without this, an install that
|
|
980
|
-
// fails before any join fires becomes an unhandled rejection.
|
|
981
|
-
const installApplicationId = applicationConfig.id;
|
|
982
|
-
const installUpgradeCount = upgradePromises.length;
|
|
983
1312
|
packageInstallPromise.catch((err) => {
|
|
984
1313
|
logger.error(
|
|
985
1314
|
// errorId is encoded into the message body because the
|
|
986
1315
|
// logger.error contract limits structured attributes to
|
|
987
1316
|
// `{ error: { kind, message, stack } }`. The id stays
|
|
988
1317
|
// grep-able for Datadog/Sentry alert rules.
|
|
989
|
-
`Background package install failed [errorId=DEV_SERVER_BG_INSTALL_FAILED applicationId=${installApplicationId} cwd=${cwd}
|
|
1318
|
+
`Background package install failed [errorId=DEV_SERVER_BG_INSTALL_FAILED applicationId=${installApplicationId} cwd=${cwd}]`,
|
|
990
1319
|
getErrorMeta(err),
|
|
991
1320
|
);
|
|
992
1321
|
});
|
|
@@ -1000,25 +1329,57 @@ export async function dev(options: {
|
|
|
1000
1329
|
hasPackageChanged || forcePackageInstall;
|
|
1001
1330
|
if (shouldUploadPackageState || uploadFirst) {
|
|
1002
1331
|
// Upload serializes the post-install lockfile + node_modules
|
|
1003
|
-
// tree to DBFS, so it must observe
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1332
|
+
// tree to DBFS, so it must observe quiesced upgrade+install.
|
|
1333
|
+
// Upgrade first — its rejection exits before an install
|
|
1334
|
+
// rejection can take the degrade path (APPS-4457).
|
|
1335
|
+
//
|
|
1336
|
+
// BUT: if a successful CLI upgrade is pending restart
|
|
1337
|
+
// (`hasCliUpdated`), the restart branch below MUST win over
|
|
1338
|
+
// the install-failure degrade path. Otherwise the outer
|
|
1339
|
+
// sync/setup catch routes `InitialInstallFailed` to "degrade"
|
|
1340
|
+
// and skips the restart entirely — masking a successful CLI
|
|
1341
|
+
// upgrade. Catch ONLY the join (not the upload body) so a
|
|
1342
|
+
// post-join upload failure still propagates normally.
|
|
1343
|
+
let skipStartupUploadForCliRestart = false;
|
|
1344
|
+
try {
|
|
1345
|
+
await joinUpgradeThenInstall("before upload");
|
|
1346
|
+
} catch (joinError) {
|
|
1347
|
+
if (
|
|
1348
|
+
hasCliUpdated &&
|
|
1349
|
+
joinError instanceof InitialInstallFailed
|
|
1350
|
+
) {
|
|
1351
|
+
logger.info(
|
|
1352
|
+
"Initial package install failed before startup upload, but CLI was updated; skipping upload and letting the restart proceed",
|
|
1353
|
+
);
|
|
1354
|
+
skipStartupUploadForCliRestart = true;
|
|
1355
|
+
} else {
|
|
1356
|
+
throw joinError;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
if (!skipStartupUploadForCliRestart) {
|
|
1360
|
+
logger.info(
|
|
1361
|
+
`Uploading local files to branch '${activeDbfsBranchName}' on server before starting`,
|
|
1362
|
+
);
|
|
1363
|
+
await tracer.startActiveSpan(
|
|
1364
|
+
"uploadFirstOrPackageChanged",
|
|
1365
|
+
async (span) => {
|
|
1366
|
+
await syncService!.uploadDirectory("cli:sdk");
|
|
1367
|
+
await syncService!.uploadDirectoryNowIfNeeded("cli:sdk");
|
|
1368
|
+
span.end();
|
|
1369
|
+
},
|
|
1370
|
+
);
|
|
1371
|
+
}
|
|
1016
1372
|
}
|
|
1017
1373
|
|
|
1018
1374
|
if (hasCliUpdated) {
|
|
1019
|
-
//
|
|
1020
|
-
// the next boot
|
|
1021
|
-
|
|
1375
|
+
// Restart must NOT be preempted by an app-install failure:
|
|
1376
|
+
// the next boot re-runs install with the new CLI. Join the
|
|
1377
|
+
// upgrade (it must succeed or we exit before restarting) and
|
|
1378
|
+
// SETTLE the install (don't observe its rejection — but wait
|
|
1379
|
+
// for npm to finish so the restart doesn't SIGKILL it
|
|
1380
|
+
// mid-rename and leave a half-written lockfile).
|
|
1381
|
+
await joinPackageUpgrade("before CLI restart");
|
|
1382
|
+
await settlePackageInstall("before CLI restart");
|
|
1022
1383
|
try {
|
|
1023
1384
|
logger.info("Releasing lock before restarting the dev server");
|
|
1024
1385
|
await aiService?.removeIntegrationCache();
|
|
@@ -1034,16 +1395,23 @@ export async function dev(options: {
|
|
|
1034
1395
|
}
|
|
1035
1396
|
});
|
|
1036
1397
|
} catch (error: any) {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1398
|
+
if (
|
|
1399
|
+
handleStartupError(error, devServerStatus, logger) === "degrade"
|
|
1400
|
+
) {
|
|
1401
|
+
// app-install failure: do NOT exit — fall through to Vite startup below.
|
|
1402
|
+
// upload + CLI-restart were already skipped (the rejecting join threw first).
|
|
1403
|
+
} else {
|
|
1404
|
+
logger.error(
|
|
1405
|
+
"[dev-server] Startup failed during sync/lock/setup (exiting with code 1)",
|
|
1406
|
+
getErrorMeta(error),
|
|
1407
|
+
);
|
|
1408
|
+
try {
|
|
1409
|
+
await aiService?.removeIntegrationCache();
|
|
1410
|
+
await lockService?.shutdownAndExit();
|
|
1411
|
+
} finally {
|
|
1412
|
+
// this is redundant, but it's here to make sure the lock service is shutdown and the process exits
|
|
1413
|
+
process.exit(1);
|
|
1414
|
+
}
|
|
1047
1415
|
}
|
|
1048
1416
|
}
|
|
1049
1417
|
} else {
|
|
@@ -1057,7 +1425,37 @@ export async function dev(options: {
|
|
|
1057
1425
|
// cache embeds partial state that survives across reloads. Awaiting
|
|
1058
1426
|
// here costs at most the install's remaining wall time — the upload
|
|
1059
1427
|
// and CLI-restart joins above usually drain it first.
|
|
1060
|
-
|
|
1428
|
+
// The "before upload" / "before CLI restart" joins above run inside the
|
|
1429
|
+
// sync/lock catch that degrades on `InitialInstallFailed`. But when the
|
|
1430
|
+
// install ran yet none of those joins fired (e.g.
|
|
1431
|
+
// `packageJsonRequiresInstall && !forcePackageInstall && !upload &&
|
|
1432
|
+
// !hasCliUpdated`), the install rejection first surfaces HERE — outside
|
|
1433
|
+
// that catch. Route it through the SAME origin gate so an app-install
|
|
1434
|
+
// failure still degrades (keep Vite up + record the error) instead of
|
|
1435
|
+
// escaping `dev()` to `process.exit(1)`. Non-install errors (including
|
|
1436
|
+
// any background upgrade rejection that escaped the prior joins) take
|
|
1437
|
+
// the same explicit exit path as the sync/lock catch above so the
|
|
1438
|
+
// process actually terminates (the startupSpan catch only rethrows,
|
|
1439
|
+
// and an unhandled rejection at that depth doesn't shut the lock
|
|
1440
|
+
// service down or guarantee `process.exit(1)`).
|
|
1441
|
+
try {
|
|
1442
|
+
await joinUpgradeThenInstall("before Vite startup");
|
|
1443
|
+
} catch (error) {
|
|
1444
|
+
if (handleStartupError(error, devServerStatus, logger) === "exit") {
|
|
1445
|
+
logger.error(
|
|
1446
|
+
"[dev-server] Startup failed during pre-Vite install join (exiting with code 1)",
|
|
1447
|
+
getErrorMeta(error),
|
|
1448
|
+
);
|
|
1449
|
+
try {
|
|
1450
|
+
await aiService?.removeIntegrationCache();
|
|
1451
|
+
await lockService?.shutdownAndExit();
|
|
1452
|
+
} finally {
|
|
1453
|
+
// Redundant with `shutdownAndExit`; here so a thrown shutdown
|
|
1454
|
+
// path can't leave the process hanging on a stuck handle.
|
|
1455
|
+
process.exit(1);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1061
1459
|
|
|
1062
1460
|
const activateRuntimeGitService = async (): Promise<
|
|
1063
1461
|
GitService | undefined
|
|
@@ -1158,27 +1556,26 @@ export async function dev(options: {
|
|
|
1158
1556
|
const httpServer = await tracer.startActiveSpan(
|
|
1159
1557
|
"createDevServer",
|
|
1160
1558
|
async (span) => {
|
|
1161
|
-
const createDevServerOptions =
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
} as unknown as Parameters<typeof createDevServer>[0];
|
|
1559
|
+
const createDevServerOptions: Parameters<typeof createDevServer>[0] =
|
|
1560
|
+
{
|
|
1561
|
+
root: options.cwd,
|
|
1562
|
+
mode: "development",
|
|
1563
|
+
port: port,
|
|
1564
|
+
fsOperationQueue,
|
|
1565
|
+
syncService: syncService,
|
|
1566
|
+
lockService: lockService,
|
|
1567
|
+
aiService: aiService,
|
|
1568
|
+
gitService: gitService,
|
|
1569
|
+
gitBootstrapFailed: devStartupGitOutcome === "bootstrap_failed",
|
|
1570
|
+
activateGitService: activateRuntimeGitService,
|
|
1571
|
+
snapshotManager: snapshotManager,
|
|
1572
|
+
logger: options.logger,
|
|
1573
|
+
sdk,
|
|
1574
|
+
superblocksBaseUrl: tokenConfig.superblocksBaseUrl,
|
|
1575
|
+
existingServer: options.existingServer,
|
|
1576
|
+
warmActivationStart: options.warmActivationStart,
|
|
1577
|
+
devServerStatus,
|
|
1578
|
+
};
|
|
1182
1579
|
const result = await createDevServer(createDevServerOptions);
|
|
1183
1580
|
span.end();
|
|
1184
1581
|
return result;
|
|
@@ -1194,13 +1591,19 @@ export async function dev(options: {
|
|
|
1194
1591
|
logger.warn(`Error stopping auth hot-reload server: ${error}`);
|
|
1195
1592
|
});
|
|
1196
1593
|
}
|
|
1197
|
-
// Drain any straggler install before tear-down. By this
|
|
1198
|
-
// pre-Vite join has already run on the success path, so
|
|
1199
|
-
//
|
|
1594
|
+
// Drain any straggler upgrade/install before tear-down. By this
|
|
1595
|
+
// point the pre-Vite join has already run on the success path, so
|
|
1596
|
+
// usually both promises are undefined and this is a no-op; if abort
|
|
1200
1597
|
// races createDevServer (or fires during the inner sync block on
|
|
1201
|
-
// some error path), draining here keeps the spawned npm
|
|
1202
|
-
// being SIGKILL'd mid-rename. Errors only get logged — we are
|
|
1203
|
-
// down anyway.
|
|
1598
|
+
// some error path), draining here keeps the spawned npm children
|
|
1599
|
+
// from being SIGKILL'd mid-rename. Errors only get logged — we are
|
|
1600
|
+
// tearing down anyway.
|
|
1601
|
+
joinPackageUpgrade("during abort").catch((error: unknown) => {
|
|
1602
|
+
logger.warn(
|
|
1603
|
+
"Error draining background upgrade during abort",
|
|
1604
|
+
getErrorMeta(error),
|
|
1605
|
+
);
|
|
1606
|
+
});
|
|
1204
1607
|
joinPackageInstall("during abort").catch((error: unknown) => {
|
|
1205
1608
|
logger.warn(
|
|
1206
1609
|
"Error draining background install during abort",
|