@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
@@ -58,6 +58,7 @@ import {
58
58
  ensureRemoteHasDefaultBranch,
59
59
  getGitErrorFields,
60
60
  } from "./git-repo-setup.mjs";
61
+ import { normalizeWorkspaceProtocolForNpm } from "./normalize-workspace-protocol.js";
61
62
  import {
62
63
  didPackageJsonSnapshotChange,
63
64
  packageJsonSnapshot,
@@ -205,6 +206,45 @@ async function readPkgJson(cwd: string) {
205
206
  }
206
207
  }
207
208
 
209
+ async function normalizePackageJsonForNpm(cwd: string, logger: Logger) {
210
+ const packageJsonPath = path.join(cwd, "package.json");
211
+ let raw: string;
212
+ try {
213
+ raw = await nodeFs.readFile(packageJsonPath, "utf8");
214
+ } catch (err) {
215
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
216
+ throw err;
217
+ }
218
+ return;
219
+ }
220
+ let parsed: ReturnType<typeof JSON.parse>;
221
+ try {
222
+ parsed = JSON.parse(raw);
223
+ } catch (err) {
224
+ logger.warn(
225
+ `Could not parse package.json at ${packageJsonPath} for workspace-protocol normalization: ${err instanceof Error ? err.message : String(err)}`,
226
+ );
227
+ return;
228
+ }
229
+
230
+ const result = normalizeWorkspaceProtocolForNpm(parsed);
231
+ if (!result.changed) return;
232
+
233
+ for (const warning of result.warnings) {
234
+ logger.warn(warning);
235
+ }
236
+ try {
237
+ await nodeFs.writeFile(
238
+ packageJsonPath,
239
+ `${JSON.stringify(result.packageJson, null, 2)}\n`,
240
+ );
241
+ } catch (err) {
242
+ throw new Error(
243
+ `Failed to write normalized package.json at ${packageJsonPath}: ${err instanceof Error ? err.message : String(err)}`,
244
+ );
245
+ }
246
+ }
247
+
208
248
  async function installPackages(cwd: string, logger: Logger) {
209
249
  try {
210
250
  const pm = await detect({
@@ -224,6 +264,13 @@ async function installPackages(cwd: string, logger: Logger) {
224
264
  return;
225
265
  }
226
266
 
267
+ if (pm.agent === "npm") {
268
+ // npm rejects pnpm's `workspace:` protocol with EUNSUPPORTEDPROTOCOL,
269
+ // crash-looping the SABS dev-server pod. Strip the prefix so npm can
270
+ // resolve a published version instead.
271
+ await normalizePackageJsonForNpm(cwd, logger);
272
+ }
273
+
227
274
  const installCommand = resolveCommand(
228
275
  pm.agent,
229
276
  "install",
@@ -482,11 +529,11 @@ export async function dev(options: {
482
529
  );
483
530
  }
484
531
 
485
- const [localContents, serverHash, currentUser] =
486
- await tracer.startActiveSpan("fetchInitInfo", async (span) => {
532
+ const [serverHash, currentUser] = await tracer.startActiveSpan(
533
+ "fetchUserAndServerHash",
534
+ async (span) => {
487
535
  try {
488
- const results = await Promise.all([
489
- sdk.hashLocalDirectory(cwd),
536
+ return await Promise.all([
490
537
  getDraftOrLiveEditHash(
491
538
  sdk,
492
539
  applicationConfig.id,
@@ -495,11 +542,15 @@ export async function dev(options: {
495
542
  ),
496
543
  sdk.fetchCurrentUser(),
497
544
  ]);
498
- return results;
499
545
  } finally {
500
546
  span.end();
501
547
  }
502
- });
548
+ },
549
+ );
550
+
551
+ logger.info(
552
+ `[dev-startup] Fetched server hash for branch '${activeDbfsBranchName}'`,
553
+ );
503
554
 
504
555
  gitUserName = currentUser.user.name;
505
556
  gitUserEmail = currentUser.user.email;
@@ -558,60 +609,19 @@ export async function dev(options: {
558
609
  },
559
610
  );
560
611
 
561
- const isSynced = localContents.hash === serverHash;
562
-
563
- if (isSynced) {
564
- logger.info(
565
- `Local files are in sync with the server on branch '${activeDbfsBranchName}', local hash: ${localContents.hash}, server hash: ${serverHash}`,
566
- );
567
- } else {
568
- logger.info(
569
- `Local files are out of sync with the server on branch '${activeDbfsBranchName}', local hash: ${localContents.hash}, server hash: ${serverHash}`,
570
- );
571
- }
572
-
573
- if (!(downloadFirst || uploadFirst)) {
574
- throw new Error(
575
- "You must choose --download-first or --upload-first to use the dev command",
576
- );
577
- }
578
- if (downloadFirst && uploadFirst) {
579
- throw new Error(
580
- "Choose either --download-first or --upload-first",
581
- );
582
- }
583
-
584
- let hasPackageChanged = false;
585
- let packageJsonRequiresInstall = false;
586
- const hasPackageJsonSnapshotBeforeRestore =
587
- options.packageJsonSnapshotBeforeRestore !== undefined;
588
-
589
- const packageJsonBefore = await readPkgJson(cwd);
590
-
591
- if (downloadFirst && !isSynced) {
592
- await tracer.startActiveSpan("downloadFirst", async (span) => {
593
- logger.info(
594
- `Starting download of branch '${activeDbfsBranchName}'`,
595
- );
596
-
597
- await syncService!.downloadDirectory();
598
- if (
599
- options.normalizeManagedPackageDependencies &&
600
- (await restoreManagedPackageDependencies(
601
- cwd,
602
- packageJsonBefore,
603
- ))
604
- ) {
605
- logger.info(
606
- "Restored managed package dependencies to the warm template versions after DBFS download",
607
- );
608
- }
609
- span.end();
610
- });
611
- }
612
+ logger.info(
613
+ `[dev-startup] Starting git sync (${downloadFirst ? "download" : "upload"}-first) on branch '${activeDbfsBranchName}'`,
614
+ );
612
615
 
613
- // Git sync step — runs after DBFS download, before version check.
614
- // Best-effort: never blocks startup on failure.
616
+ // Git sync step — runs before DBFS download so the working tree can
617
+ // be reconciled with remotes/branches first; DBFS download then
618
+ // overwrites app files with server state (source of truth). The
619
+ // .git directory is not managed by DBFS. Best-effort: never blocks
620
+ // startup on failure.
621
+ let devStartupGitOutcome:
622
+ | "skipped_no_remote"
623
+ | "bootstrap_failed"
624
+ | "completed" = "skipped_no_remote";
615
625
  await tracer.startActiveSpan("gitSync", async (gitSpan) => {
616
626
  try {
617
627
  const bootstrappedGitService = await bootstrapGitService({
@@ -625,6 +635,7 @@ export async function dev(options: {
625
635
  });
626
636
 
627
637
  if (!bootstrappedGitService) {
638
+ devStartupGitOutcome = "skipped_no_remote";
628
639
  logger.info("[git] startup git sync skipped", {
629
640
  gitCategory: "setup",
630
641
  gitOperation: "bootstrap",
@@ -633,104 +644,11 @@ export async function dev(options: {
633
644
  applicationId: applicationConfig.id,
634
645
  workDir: cwd,
635
646
  });
636
-
637
- // In CSB mode, schedule a background retry loop. The app's
638
- // git config may not be persisted yet at claim time (e.g. git
639
- // was connected moments before the CSB was assigned). Without
640
- // this, the CSB runs its entire lifetime without git — meaning
641
- // changes are only in DBFS and are lost if the CSB recycles.
642
- if (lockType === LockType.CSB) {
643
- const GIT_RETRY_DELAY_MS = 30_000;
644
- const GIT_RETRY_MAX_ATTEMPTS = 10;
645
- const scheduleRetry = (attempt: number): void => {
646
- if (attempt > GIT_RETRY_MAX_ATTEMPTS) {
647
- logger.info(
648
- "[git] background bootstrap retry exhausted, giving up",
649
- {
650
- gitCategory: "setup",
651
- gitOperation: "background-retry",
652
- gitOutcome: "exhausted",
653
- gitAttempt: attempt - 1,
654
- applicationId: applicationConfig.id,
655
- },
656
- );
657
- return;
658
- }
659
- const timer = setTimeout(() => {
660
- void (async () => {
661
- if (gitService) return;
662
- try {
663
- const svc = await bootstrapGitService({
664
- sdk,
665
- applicationId: applicationConfig.id,
666
- cwd,
667
- logger,
668
- userName: gitUserName,
669
- userEmail: gitUserEmail,
670
- superblocksBaseUrl:
671
- tokenConfig.superblocksBaseUrl,
672
- });
673
- if (!svc) {
674
- scheduleRetry(attempt + 1);
675
- return;
676
- }
677
-
678
- gitService = svc;
679
-
680
- try {
681
- await fetchAndEnsureLiveBranch(
682
- svc,
683
- "Git background retry fetch failed",
684
- );
685
- } catch {
686
- // non-fatal
687
- }
688
-
689
- activeDbfsBranchName =
690
- await ensureRuntimeDbfsBranchConsistency({
691
- sdk,
692
- applicationConfig,
693
- logger,
694
- lockService,
695
- syncService,
696
- currentBranchName: activeDbfsBranchName,
697
- });
698
-
699
- logger.info(
700
- "[git] background bootstrap retry succeeded",
701
- {
702
- gitCategory: "setup",
703
- gitOperation: "background-retry",
704
- gitOutcome: "success",
705
- gitAttempt: attempt,
706
- applicationId: applicationConfig.id,
707
- },
708
- );
709
- } catch (err) {
710
- logger.warn(
711
- "[git] background bootstrap retry failed",
712
- {
713
- gitCategory: "setup",
714
- gitOperation: "background-retry",
715
- gitOutcome: "failed",
716
- gitAttempt: attempt,
717
- applicationId: applicationConfig.id,
718
- ...getGitErrorFields(err),
719
- },
720
- );
721
- scheduleRetry(attempt + 1);
722
- }
723
- })();
724
- }, GIT_RETRY_DELAY_MS);
725
- timer.unref();
726
- };
727
- scheduleRetry(1);
728
- }
729
-
730
647
  gitSpan.end();
731
648
  return;
732
649
  }
733
650
 
651
+ devStartupGitOutcome = "completed";
734
652
  gitService = bootstrappedGitService;
735
653
 
736
654
  // At this point the local repo is usable for status(),
@@ -774,6 +692,7 @@ export async function dev(options: {
774
692
  }
775
693
  } catch (gitError) {
776
694
  // Init/configure/ensureGitRepo failed — repo is not usable
695
+ devStartupGitOutcome = "bootstrap_failed";
777
696
  gitService = undefined;
778
697
  logger.warn(
779
698
  "[git] startup git setup failed, continuing without git",
@@ -798,6 +717,93 @@ export async function dev(options: {
798
717
  }
799
718
  });
800
719
 
720
+ logger.info(
721
+ `[dev-startup] Git sync complete (outcome=${devStartupGitOutcome})`,
722
+ );
723
+
724
+ const localContents = await tracer.startActiveSpan(
725
+ "hashLocalDirectory",
726
+ async (span) => {
727
+ try {
728
+ return await sdk.hashLocalDirectory(cwd);
729
+ } finally {
730
+ span.end();
731
+ }
732
+ },
733
+ );
734
+
735
+ const isSynced = localContents.hash === serverHash;
736
+
737
+ logger.info(
738
+ `[dev-startup] Local directory ${isSynced ? "in sync" : "out of sync"} with server`,
739
+ );
740
+
741
+ if (!(downloadFirst || uploadFirst)) {
742
+ throw new Error(
743
+ "You must choose --download-first or --upload-first to use the dev command",
744
+ );
745
+ }
746
+ if (downloadFirst && uploadFirst) {
747
+ throw new Error(
748
+ "Choose either --download-first or --upload-first",
749
+ );
750
+ }
751
+
752
+ let hasPackageChanged = false;
753
+ let packageJsonRequiresInstall = false;
754
+ const hasPackageJsonSnapshotBeforeRestore =
755
+ options.packageJsonSnapshotBeforeRestore !== undefined;
756
+
757
+ const packageJsonBefore = await readPkgJson(cwd);
758
+
759
+ if (downloadFirst && !isSynced) {
760
+ await tracer.startActiveSpan("downloadFirst", async (span) => {
761
+ logger.info(
762
+ `Starting download of branch '${activeDbfsBranchName}'`,
763
+ );
764
+
765
+ await syncService!.downloadDirectory();
766
+ logger.info("[dev-startup] DBFS download complete");
767
+ try {
768
+ const postDownloadLocalContents =
769
+ await tracer.startActiveSpan(
770
+ "hashLocalDirectoryAfterDbfsDownload",
771
+ async (hashSpan) => {
772
+ try {
773
+ return await sdk.hashLocalDirectory(cwd);
774
+ } finally {
775
+ hashSpan.end();
776
+ }
777
+ },
778
+ );
779
+ const hashMatchesServer =
780
+ postDownloadLocalContents.hash === serverHash;
781
+ logger.info(
782
+ `[dev-startup] Post-download hash ${hashMatchesServer ? "matches" : "does not match"} server`,
783
+ );
784
+ } catch (hashError) {
785
+ logger.warn(
786
+ "[dev-startup] Failed to compute post-download hash",
787
+ getErrorMeta(hashError),
788
+ );
789
+ }
790
+ if (
791
+ options.normalizeManagedPackageDependencies &&
792
+ (await restoreManagedPackageDependencies(
793
+ cwd,
794
+ packageJsonBefore,
795
+ ))
796
+ ) {
797
+ logger.info(
798
+ "Restored managed package dependencies to the warm template versions after DBFS download",
799
+ );
800
+ }
801
+ span.end();
802
+ });
803
+ } else if (downloadFirst && isSynced) {
804
+ logger.info("[dev-startup] Skipping download, already in sync");
805
+ }
806
+
801
807
  let hasCliUpdated = false;
802
808
  let upgradePromises: Promise<void>[] = [];
803
809
  const forceUpgrade =
@@ -1413,23 +1419,10 @@ async function ensureLiveBranchCheckedOutAfterFetch(
1413
1419
  // If remote live branch exists, initialize/switch local live branch from it.
1414
1420
  // This handles fresh repos where HEAD is unborn before first fetch.
1415
1421
  if (await canResolveRef(git, `origin/${SUPERBLOCKS_LIVE_GIT_BRANCH}`)) {
1416
- try {
1417
- await git.checkoutOrCreate(
1418
- SUPERBLOCKS_LIVE_GIT_BRANCH,
1419
- `origin/${SUPERBLOCKS_LIVE_GIT_BRANCH}`,
1420
- );
1421
- } catch {
1422
- // Pre-warmed CSBs download DBFS files before git is set up, leaving
1423
- // untracked working tree files that collide with the branch contents.
1424
- // Force-checkout is safe here — the files are identical.
1425
- await git.raw([
1426
- "checkout",
1427
- "-f",
1428
- "-B",
1429
- SUPERBLOCKS_LIVE_GIT_BRANCH,
1430
- `origin/${SUPERBLOCKS_LIVE_GIT_BRANCH}`,
1431
- ]);
1432
- }
1422
+ await git.checkoutOrCreate(
1423
+ SUPERBLOCKS_LIVE_GIT_BRANCH,
1424
+ `origin/${SUPERBLOCKS_LIVE_GIT_BRANCH}`,
1425
+ );
1433
1426
  return;
1434
1427
  }
1435
1428
 
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect } from "vitest";
2
+
3
+ import { normalizeWorkspaceProtocolForNpm } from "./normalize-workspace-protocol.js";
4
+
5
+ describe("normalizeWorkspaceProtocolForNpm", () => {
6
+ it("returns unchanged when no workspace: deps are present", () => {
7
+ const pkg = {
8
+ dependencies: { react: "^18.0.0", "@superblocksteam/library": "2.5.0" },
9
+ devDependencies: { vitest: "^1.0.0" },
10
+ };
11
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
12
+
13
+ expect(result.changed).toBe(false);
14
+ expect(result.warnings).toHaveLength(0);
15
+ expect(result.packageJson).toEqual(pkg);
16
+ });
17
+
18
+ it("strips workspace: prefix from dependencies and reports a warning per dep", () => {
19
+ const pkg = {
20
+ dependencies: {
21
+ "@superblocksteam/library": "workspace:*",
22
+ "@superblocksteam/sdk-api": "workspace:*",
23
+ react: "^18.0.0",
24
+ },
25
+ };
26
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
27
+
28
+ expect(result.changed).toBe(true);
29
+ expect(result.packageJson.dependencies).toEqual({
30
+ "@superblocksteam/library": "*",
31
+ "@superblocksteam/sdk-api": "*",
32
+ react: "^18.0.0",
33
+ });
34
+ expect(result.warnings).toHaveLength(2);
35
+ expect(result.warnings.join(" ")).toContain("@superblocksteam/library");
36
+ expect(result.warnings.join(" ")).toContain("@superblocksteam/sdk-api");
37
+ });
38
+
39
+ it("preserves the version range after the workspace: prefix", () => {
40
+ const pkg = {
41
+ dependencies: {
42
+ a: "workspace:^1.2.3",
43
+ b: "workspace:~2.0.0",
44
+ c: "workspace:1.0.0",
45
+ },
46
+ };
47
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
48
+
49
+ expect(result.changed).toBe(true);
50
+ expect(result.packageJson.dependencies).toEqual({
51
+ a: "^1.2.3",
52
+ b: "~2.0.0",
53
+ c: "1.0.0",
54
+ });
55
+ });
56
+
57
+ it("normalizes across all dependency buckets (devDependencies, optionalDependencies, peerDependencies)", () => {
58
+ const pkg = {
59
+ dependencies: { a: "workspace:*" },
60
+ devDependencies: { b: "workspace:*" },
61
+ optionalDependencies: { c: "workspace:*" },
62
+ peerDependencies: { d: "workspace:*" },
63
+ };
64
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
65
+
66
+ expect(result.changed).toBe(true);
67
+ expect(result.packageJson.dependencies?.a).toBe("*");
68
+ expect(result.packageJson.devDependencies?.b).toBe("*");
69
+ expect(result.packageJson.optionalDependencies?.c).toBe("*");
70
+ expect(result.packageJson.peerDependencies?.d).toBe("*");
71
+ });
72
+
73
+ it("does not touch values that merely contain the substring 'workspace' but don't use the protocol", () => {
74
+ const pkg = {
75
+ dependencies: {
76
+ "some-workspace-tool": "1.0.0",
77
+ a: "git+ssh://git@github.com/foo/workspace.git",
78
+ },
79
+ };
80
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
81
+
82
+ expect(result.changed).toBe(false);
83
+ expect(result.packageJson).toEqual(pkg);
84
+ });
85
+
86
+ it("does not mutate the input object", () => {
87
+ const pkg = {
88
+ dependencies: { a: "workspace:*" },
89
+ };
90
+ const original = JSON.parse(JSON.stringify(pkg));
91
+ normalizeWorkspaceProtocolForNpm(pkg);
92
+ expect(pkg).toEqual(original);
93
+ });
94
+
95
+ it("handles a package.json with no dependency buckets", () => {
96
+ const pkg = { name: "test", version: "1.0.0" };
97
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
98
+
99
+ expect(result.changed).toBe(false);
100
+ expect(result.warnings).toHaveLength(0);
101
+ expect(result.packageJson).toEqual(pkg);
102
+ });
103
+
104
+ it("bare workspace: (no version after colon) falls back to *", () => {
105
+ const pkg = { dependencies: { a: "workspace:" } };
106
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
107
+
108
+ expect(result.changed).toBe(true);
109
+ expect(result.packageJson.dependencies?.a).toBe("*");
110
+ });
111
+
112
+ it("operator-only forms (workspace:^ and workspace:~) preserve the operator as-is", () => {
113
+ const pkg = {
114
+ dependencies: { a: "workspace:^", b: "workspace:~" },
115
+ };
116
+ const result = normalizeWorkspaceProtocolForNpm(pkg);
117
+
118
+ expect(result.changed).toBe(true);
119
+ expect(result.packageJson.dependencies?.a).toBe("^");
120
+ expect(result.packageJson.dependencies?.b).toBe("~");
121
+ });
122
+ });
@@ -0,0 +1,64 @@
1
+ // pnpm's `workspace:` protocol is not understood by npm — `npm install` rejects
2
+ // it with EUNSUPPORTEDPROTOCOL. In the SABS dev-server pod the user app's
3
+ // package.json can arrive with `workspace:*` deps (build-time rewrite was
4
+ // missed, DBFS row predates the rewrite, etc.) and the install crash-loops the
5
+ // pod with no useful signal. This helper strips the `workspace:` prefix so npm
6
+ // can resolve a published version. It runs in the cloud pod only — local dev
7
+ // uses pnpm and needs the protocol intact for workspace linking.
8
+
9
+ type PackageJson = {
10
+ dependencies?: Record<string, string>;
11
+ devDependencies?: Record<string, string>;
12
+ optionalDependencies?: Record<string, string>;
13
+ peerDependencies?: Record<string, string>;
14
+ [key: string]: unknown;
15
+ };
16
+
17
+ const DEPENDENCY_BUCKETS = [
18
+ "dependencies",
19
+ "devDependencies",
20
+ "optionalDependencies",
21
+ "peerDependencies",
22
+ ] as const;
23
+
24
+ const WORKSPACE_PROTOCOL = "workspace:";
25
+
26
+ export interface NormalizeResult {
27
+ changed: boolean;
28
+ warnings: string[];
29
+ packageJson: PackageJson;
30
+ }
31
+
32
+ export function normalizeWorkspaceProtocolForNpm(
33
+ packageJson: PackageJson,
34
+ ): NormalizeResult {
35
+ const next: PackageJson = { ...packageJson };
36
+ const warnings: string[] = [];
37
+ let changed = false;
38
+
39
+ for (const bucket of DEPENDENCY_BUCKETS) {
40
+ const deps = packageJson[bucket];
41
+ if (!deps) continue;
42
+
43
+ const nextDeps: Record<string, string> = {};
44
+ for (const [name, value] of Object.entries(deps)) {
45
+ if (typeof value === "string" && value.startsWith(WORKSPACE_PROTOCOL)) {
46
+ const stripped = value.slice(WORKSPACE_PROTOCOL.length) || "*";
47
+ nextDeps[name] = stripped;
48
+ changed = true;
49
+ warnings.push(
50
+ // For @superblocksteam/sdk-api this falls back to the latest published
51
+ // non-ephemeral package. To pin an ephemeral version, fix the upstream
52
+ // rewrite path (scripts/setup-template.sh) so workspace:* never ships
53
+ // to the pod.
54
+ `Stripped pnpm "workspace:" protocol from ${bucket}.${name} (was "${value}", now "${stripped}") so npm install can resolve it.`,
55
+ );
56
+ } else {
57
+ nextDeps[name] = String(value);
58
+ }
59
+ }
60
+ next[bucket] = nextDeps;
61
+ }
62
+
63
+ return { changed, warnings, packageJson: next };
64
+ }
@@ -532,6 +532,9 @@ export async function createDevServer({
532
532
  logger.error(
533
533
  `Application ID header mismatch, expected ${resourceConfig!.id}, received ${req.headers["x-superblocks-application-id"]}`,
534
534
  );
535
+ logger.info(
536
+ `[dev-server-runtime] _sb_connect application mismatch: expected ${resourceConfig!.id}, got ${req.headers["x-superblocks-application-id"]}`,
537
+ );
535
538
  res.status(401).send(
536
539
  JSON.stringify({
537
540
  error: `Application ID header mismatch. Expected ${resourceConfig!.id}, received ${req.headers["x-superblocks-application-id"]}`,
@@ -551,6 +554,9 @@ export async function createDevServer({
551
554
  logger.error(
552
555
  `Dev server expects branch ${currentBranch}, but received ${incomingBranch}. Check your current branch with 'git branch' and try again.`,
553
556
  );
557
+ logger.info(
558
+ `[dev-server-runtime] _sb_connect branch mismatch: expected ${currentBranch}, got ${incomingBranch}`,
559
+ );
554
560
  res.status(401).send(
555
561
  JSON.stringify({
556
562
  error: `Dev server expects branch ${currentBranch}, but received ${incomingBranch}. Check your current branch with 'git branch' and try again.`,