@boxes-dev/dvb 0.2.28 → 0.2.30

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 (39) hide show
  1. package/dist/bin/dvb.cjs +878 -688
  2. package/dist/bin/dvb.js +2 -1
  3. package/dist/bin/dvb.js.map +1 -1
  4. package/dist/bin/dvbd.cjs +47 -16
  5. package/dist/devbox/commands/destroy.d.ts.map +1 -1
  6. package/dist/devbox/commands/destroy.js +62 -2
  7. package/dist/devbox/commands/destroy.js.map +1 -1
  8. package/dist/devbox/commands/init/codex/local.d.ts +8 -4
  9. package/dist/devbox/commands/init/codex/local.d.ts.map +1 -1
  10. package/dist/devbox/commands/init/codex/local.js +36 -12
  11. package/dist/devbox/commands/init/codex/local.js.map +1 -1
  12. package/dist/devbox/commands/init/index.d.ts.map +1 -1
  13. package/dist/devbox/commands/init/index.js +169 -173
  14. package/dist/devbox/commands/init/index.js.map +1 -1
  15. package/dist/devbox/commands/init/registry.d.ts +3 -4
  16. package/dist/devbox/commands/init/registry.d.ts.map +1 -1
  17. package/dist/devbox/commands/init/registry.js +22 -25
  18. package/dist/devbox/commands/init/registry.js.map +1 -1
  19. package/dist/devbox/commands/init/repo.d.ts +4 -1
  20. package/dist/devbox/commands/init/repo.d.ts.map +1 -1
  21. package/dist/devbox/commands/init/repo.js +57 -1
  22. package/dist/devbox/commands/init/repo.js.map +1 -1
  23. package/dist/devbox/commands/init/state.d.ts +2 -2
  24. package/dist/devbox/commands/init/state.d.ts.map +1 -1
  25. package/dist/devbox/commands/init/state.js +22 -7
  26. package/dist/devbox/commands/init/state.js.map +1 -1
  27. package/dist/devbox/commands/wezterm.d.ts.map +1 -1
  28. package/dist/devbox/commands/wezterm.js +29 -5
  29. package/dist/devbox/commands/wezterm.js.map +1 -1
  30. package/dist/devbox/completions/index.d.ts.map +1 -1
  31. package/dist/devbox/completions/index.js +42 -22
  32. package/dist/devbox/completions/index.js.map +1 -1
  33. package/dist/devbox/daemonClient.js +2 -2
  34. package/dist/devbox/daemonClient.js.map +1 -1
  35. package/dist/prompts/local-scan-env-secrets.md +1 -1
  36. package/dist/prompts/local-scan-external.md +1 -1
  37. package/dist/prompts/local-scan-extra-artifacts.md +1 -1
  38. package/dist/prompts/local-services-scan.md +1 -1
  39. package/package.json +1 -1
@@ -1,20 +1,19 @@
1
- import { randomUUID } from "node:crypto";
2
1
  import path from "node:path";
3
2
  import fs from "node:fs/promises";
4
3
  import os from "node:os";
5
4
  import { cancel as clackCancel, confirm as clackConfirm, isCancel, log as clackLog, note as clackNote, select as clackSelect, selectKey as clackSelectKey, taskLog as clackTaskLog, } from "@clack/prompts";
6
5
  import { resolveSocketInfo } from "@boxes-dev/core";
7
- import { createSecretStore, loadConfig, createSpritesClient, fingerprintFromOrigin, fingerprintFromRootCommit, normalizeGitRemoteUrl, resolveSpritesApiUrl, SpritesApiError, slugify, } from "@boxes-dev/core";
6
+ import { createSecretStore, loadConfig, createSpritesClient, normalizeGitRemoteUrl, resolveSpritesApiUrl, resolveDevboxProjectDir, SpritesApiError, slugify, } from "@boxes-dev/core";
8
7
  import { DAEMON_TIMEOUT_MS, ensureDaemonRunning, requestJson, requireDaemonFeatures, } from "../../daemonClient.js";
9
8
  import { ensureSpritesToken } from "../../auth.js";
10
9
  import { fetchSpriteDaemonRelease, getConvexUrl, issueSpriteDaemonToken, } from "../../controlPlane.js";
11
10
  import { logger } from "../../logger.js";
12
11
  import { parseInitArgs } from "./args.js";
13
- import { confirmCopyWorktree, findRepoRoot, mapGlobalGitConfigDestinations, readGlobalGitConfigFiles, readHeadState, readNullSeparatedPaths, readRepoOrigin, readRootCommit, readWorktreeState, resolveGitCommonDir, runCommand, } from "./repo.js";
12
+ import { confirmCopyWorktree, findRepoRoot, ensureRepoProjectId, mapGlobalGitConfigDestinations, readGlobalGitConfigFiles, readHeadState, readRepoOrigin, readWorktreeState, resolveGitCommonDir, runCommand, } from "./repo.js";
14
13
  import { createFileListArchive, createGitMetaArchive, writePatch } from "./packaging.js";
15
14
  import { bootstrapDevbox, ensureSpriteDaemonService, expandHome, ensureWeztermMuxService, installSpriteDaemon, shellQuote, stageRemoteSetupArtifacts, writeRemoteCodexConfig, } from "./remote.js";
16
15
  import { ensureWeztermMuxInstalled } from "../../../wezterm/ensureMux.js";
17
- import { ensureDevboxToml, readRepoMarker, writeRepoMarker } from "./registry.js";
16
+ import { readRepoMarker, writeRepoMarker } from "./registry.js";
18
17
  import { INIT_STEP_KEYS, readInitState, writeInitState } from "./state.js";
19
18
  import { ensureSshConfig, ensureSshKey, copyToClipboard, openBrowser, parseGitRemote, readRemoteOrigin, setRemoteOrigin, verifySshAuth, } from "./ssh.js";
20
19
  import { ensureKnownHostsFile, ensureLocalMountKey, ensureRemoteMountAccess, ensureSshdService, readLocalMountPublicKey, } from "../mountSsh.js";
@@ -34,6 +33,54 @@ const mergeLocalPaths = (existing, repoRoot) => {
34
33
  merged.add(repoRoot);
35
34
  return [...merged];
36
35
  };
36
+ const ensurePrivateDir = async (dir) => {
37
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
38
+ try {
39
+ await fs.chmod(dir, 0o700);
40
+ }
41
+ catch {
42
+ // best effort on filesystems that do not support chmod
43
+ }
44
+ };
45
+ const migrateLegacyRepoDevboxDir = async ({ repoRoot, projectDir, }) => {
46
+ const legacyDir = path.join(repoRoot, ".devbox");
47
+ try {
48
+ const stat = await fs.stat(legacyDir);
49
+ if (!stat.isDirectory())
50
+ return;
51
+ }
52
+ catch {
53
+ return;
54
+ }
55
+ await ensurePrivateDir(projectDir);
56
+ const entries = [
57
+ "box.json",
58
+ "init-state.json",
59
+ "setup.json",
60
+ "services.json",
61
+ "setup-artifacts.tgz",
62
+ "setup-artifacts.json",
63
+ "scans",
64
+ "logs",
65
+ ];
66
+ for (const entry of entries) {
67
+ const src = path.join(legacyDir, entry);
68
+ const dest = path.join(projectDir, entry);
69
+ try {
70
+ await fs.access(dest);
71
+ continue;
72
+ }
73
+ catch {
74
+ // missing -> migrate
75
+ }
76
+ try {
77
+ await fs.cp(src, dest, { recursive: true });
78
+ }
79
+ catch {
80
+ // best effort
81
+ }
82
+ }
83
+ };
37
84
  const buildServicesTomlUpdates = (services) => {
38
85
  const updates = {};
39
86
  for (const service of services) {
@@ -281,14 +328,14 @@ export const runInit = async (args) => {
281
328
  // - `complete` must only be true when the setup is actually usable without
282
329
  // additional `dvb init --resume` work.
283
330
  // See: apps/cli/docs/INIT_RECOVERABILITY.md
284
- const repoMarker = await readRepoMarker(repoRoot);
331
+ const projectId = await ensureRepoProjectId(repoRoot);
332
+ const projectDir = resolveDevboxProjectDir(projectId, localHomeDir);
333
+ await migrateLegacyRepoDevboxDir({ repoRoot, projectDir });
334
+ const repoMarker = await readRepoMarker(projectDir);
285
335
  const origin = await readRepoOrigin(repoRoot);
286
336
  const normalizedOrigin = origin ? normalizeGitRemoteUrl(origin) : null;
287
- const fingerprint = origin
288
- ? fingerprintFromOrigin(origin)
289
- : repoMarker?.fingerprint ??
290
- fingerprintFromRootCommit(await readRootCommit(repoRoot), randomUUID());
291
- let initState = await readInitState(repoRoot);
337
+ const fingerprint = projectId;
338
+ let initState = await readInitState(projectDir);
292
339
  const initFingerprintMismatch = Boolean(initState?.fingerprint) &&
293
340
  initState?.fingerprint !== fingerprint;
294
341
  if (!initFingerprintMismatch &&
@@ -298,13 +345,15 @@ export const runInit = async (args) => {
298
345
  // been applied yet. Treat this as incomplete so `dvb init --resume` can
299
346
  // recover.
300
347
  initState = { ...initState, complete: false };
301
- await writeInitState(repoRoot, initState);
348
+ await writeInitState(projectDir, initState);
302
349
  }
303
350
  return {
304
351
  repoRoot,
305
352
  repoName,
306
353
  slug,
307
354
  localHomeDir,
355
+ projectId,
356
+ projectDir,
308
357
  repoMarker,
309
358
  origin,
310
359
  normalizedOrigin,
@@ -314,7 +363,7 @@ export const runInit = async (args) => {
314
363
  };
315
364
  },
316
365
  });
317
- const { repoRoot, repoName, slug, localHomeDir, repoMarker, origin, normalizedOrigin, fingerprint, } = detected;
366
+ const { repoRoot, repoName, slug, localHomeDir, projectId, projectDir, repoMarker, origin, normalizedOrigin, fingerprint, } = detected;
318
367
  let initState = detected.initState;
319
368
  const initFingerprintMismatch = detected.initFingerprintMismatch;
320
369
  if (parsed.status) {
@@ -346,25 +395,28 @@ export const runInit = async (args) => {
346
395
  (!initState || initFingerprintMismatch) &&
347
396
  Boolean(registryProject?.initStatus && registryProject.initStatus !== "complete");
348
397
  const lines = [];
398
+ const markerPath = path.join(projectDir, "box.json");
399
+ const checkpointPath = path.join(projectDir, "init-state.json");
349
400
  lines.push("INIT STATUS");
350
401
  lines.push("");
351
402
  lines.push("Repo");
352
403
  lines.push(` root: ${repoRoot}`);
353
404
  lines.push(` origin: ${normalizedOrigin ?? origin ?? "(none)"}`);
354
- lines.push(` fingerprint: ${fingerprint}`);
405
+ lines.push(` projectId (git config devbox.projectId): ${projectId}`);
406
+ lines.push(` local state dir: ${projectDir}`);
355
407
  lines.push("");
356
408
  lines.push("Local state");
357
409
  if (repoMarker?.canonical) {
358
- lines.push(` box marker (.devbox/box.json): present (alias: ${repoMarker.alias ?? "(none)"}, box: ${repoMarker.canonical})`);
410
+ lines.push(` box marker (${markerPath}): present (alias: ${repoMarker.alias ?? "(none)"}, box: ${repoMarker.canonical})`);
359
411
  }
360
412
  else {
361
- lines.push(" box marker (.devbox/box.json): missing");
413
+ lines.push(` box marker (${markerPath}): missing`);
362
414
  }
363
415
  if (initState) {
364
416
  const completeText = initFingerprintMismatch
365
- ? `${String(Boolean(initState.complete))} (fingerprint mismatch)`
417
+ ? `${String(Boolean(initState.complete))} (projectId mismatch)`
366
418
  : String(Boolean(initState.complete));
367
- lines.push(` init checkpoint (.devbox/init-state.json): present (updated: ${initState.updatedAt}, complete: ${completeText})`);
419
+ lines.push(` init checkpoint (${checkpointPath}): present (updated: ${initState.updatedAt}, complete: ${completeText})`);
368
420
  if (initState.canonical || initState.alias || initState.workdir) {
369
421
  lines.push(` box: ${initState.canonical ?? "(unknown)"} (alias: ${initState.alias ?? "(unknown)"})`);
370
422
  lines.push(` workdir: ${initState.workdir ?? "(unknown)"}`);
@@ -390,7 +442,7 @@ export const runInit = async (args) => {
390
442
  }
391
443
  }
392
444
  else {
393
- lines.push(" init checkpoint (.devbox/init-state.json): missing");
445
+ lines.push(` init checkpoint (${checkpointPath}): missing`);
394
446
  }
395
447
  lines.push("");
396
448
  lines.push("Registry");
@@ -462,7 +514,7 @@ export const runInit = async (args) => {
462
514
  }
463
515
  if (parsed.resume) {
464
516
  if (initFingerprintMismatch) {
465
- throw new Error("Init state does not match this repo. Remove .devbox/init-state.json and run `dvb init` again.");
517
+ throw new Error(`Init state does not match this repo. Remove ${path.join(projectDir, "init-state.json")} and run \`dvb init\` again.`);
466
518
  }
467
519
  if (!initState) {
468
520
  throw new Error("No init state to resume. Run `dvb init` to start a new init.");
@@ -508,7 +560,7 @@ export const runInit = async (args) => {
508
560
  },
509
561
  updatedAt: new Date().toISOString(),
510
562
  };
511
- await writeInitState(repoRoot, initState);
563
+ await writeInitState(projectDir, initState);
512
564
  };
513
565
  const recordCodexCheckpoint = async ({ client, canonical, phase, }) => {
514
566
  const createdAt = new Date().toISOString();
@@ -571,14 +623,14 @@ export const runInit = async (args) => {
571
623
  if (!repoMarker?.canonical) {
572
624
  throw new Error("Repo is not initialized. Run `dvb init` first.");
573
625
  }
574
- const setupDir = path.join(repoRoot, ".devbox");
626
+ const setupDir = projectDir;
575
627
  const setupPath = path.join(setupDir, "setup.json");
576
628
  const servicesPath = path.join(setupDir, "services.json");
577
629
  try {
578
630
  await fs.access(setupPath);
579
631
  }
580
632
  catch {
581
- throw new Error("Missing .devbox/setup.json. Run `dvb init` first.");
633
+ throw new Error(`Missing setup plan (${setupPath}). Run \`dvb init\` first.`);
582
634
  }
583
635
  const localArtifactsBundlePath = path.join(setupDir, "setup-artifacts.tgz");
584
636
  const localArtifactsManifestPath = path.join(setupDir, "setup-artifacts.json");
@@ -1040,10 +1092,11 @@ export const runInit = async (args) => {
1040
1092
  }
1041
1093
  },
1042
1094
  });
1043
- const setupDir = path.join(repoRoot, ".devbox");
1095
+ const setupDir = projectDir;
1044
1096
  const setupPath = path.join(setupDir, "setup.json");
1045
1097
  const servicesPath = path.join(setupDir, "services.json");
1046
1098
  const scansDir = path.join(setupDir, "scans");
1099
+ const logDir = path.join(setupDir, "logs");
1047
1100
  const setupEnvSecretsScanPath = path.join(scansDir, "setup-env-secrets.json");
1048
1101
  const setupExternalScanPath = path.join(scansDir, "setup-external.json");
1049
1102
  const setupExtraArtifactsScanPath = path.join(scansDir, "setup-extra-artifacts.json");
@@ -1059,7 +1112,13 @@ export const runInit = async (args) => {
1059
1112
  let approvedServices = null;
1060
1113
  const setupTempDir = await fs.mkdtemp(path.join(os.tmpdir(), "devbox-setup-"));
1061
1114
  try {
1062
- await fs.mkdir(setupDir, { recursive: true });
1115
+ await fs.mkdir(setupDir, { recursive: true, mode: 0o700 });
1116
+ try {
1117
+ await fs.chmod(setupDir, 0o700);
1118
+ }
1119
+ catch {
1120
+ // best effort on filesystems that do not support chmod
1121
+ }
1063
1122
  const tryReadSetupPlan = async () => {
1064
1123
  try {
1065
1124
  return await readSetupPlan(setupPath);
@@ -1189,6 +1248,7 @@ export const runInit = async (args) => {
1189
1248
  : runCodexScanWithImmediateRetry({
1190
1249
  run: async () => await runLocalSetupEnvSecretsScan({
1191
1250
  cwd: repoRoot,
1251
+ logDir,
1192
1252
  schemaPath: envSecretsSchemaPath,
1193
1253
  outputPath: setupEnvSecretsScanPath,
1194
1254
  onProgress: updateEnvSecrets,
@@ -1205,6 +1265,7 @@ export const runInit = async (args) => {
1205
1265
  : runCodexScanWithImmediateRetry({
1206
1266
  run: async () => await runLocalSetupExternalScan({
1207
1267
  cwd: repoRoot,
1268
+ logDir,
1208
1269
  schemaPath: externalSchemaPath,
1209
1270
  outputPath: setupExternalScanPath,
1210
1271
  homeDir: localHomeDir,
@@ -1222,6 +1283,7 @@ export const runInit = async (args) => {
1222
1283
  : runCodexScanWithImmediateRetry({
1223
1284
  run: async () => await runLocalSetupExtraArtifactsScan({
1224
1285
  cwd: repoRoot,
1286
+ logDir,
1225
1287
  schemaPath: extraArtifactsSchemaPath,
1226
1288
  outputPath: setupExtraArtifactsScanPath,
1227
1289
  onProgress: updateExtraArtifacts,
@@ -1236,6 +1298,7 @@ export const runInit = async (args) => {
1236
1298
  : runCodexScanWithImmediateRetry({
1237
1299
  run: async () => await runLocalServicesScan({
1238
1300
  cwd: repoRoot,
1301
+ logDir,
1239
1302
  schemaPath: servicesSchemaPath,
1240
1303
  outputPath: servicesPath,
1241
1304
  homeDir: localHomeDir,
@@ -1644,7 +1707,6 @@ export const runInit = async (args) => {
1644
1707
  else if (!skipProvision || !skipSetupUpload) {
1645
1708
  const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "devbox-init-"));
1646
1709
  const bundlePath = path.join(tempDir, "repo.bundle");
1647
- const archivePath = path.join(tempDir, "repo.tgz");
1648
1710
  const gitMetaPath = path.join(tempDir, "git-meta.tgz");
1649
1711
  const gitMetaListPath = path.join(tempDir, "git-meta.list");
1650
1712
  const stagedPatchPath = path.join(tempDir, "staged.patch");
@@ -1655,12 +1717,11 @@ export const runInit = async (args) => {
1655
1717
  const globalGitConfigMappings = mapGlobalGitConfigDestinations(globalGitConfigSources, localHomeDir);
1656
1718
  try {
1657
1719
  const remoteBundlePath = "/home/sprite/.devbox/upload.bundle";
1658
- const remoteArchivePath = "/home/sprite/.devbox/upload.tgz";
1659
1720
  const remoteGitMetaPath = "/home/sprite/.devbox/git-meta.tgz";
1660
1721
  const remoteStagedPatchPath = "/home/sprite/.devbox/staged.patch";
1661
1722
  const remoteUnstagedPatchPath = "/home/sprite/.devbox/unstaged.patch";
1662
1723
  const remoteUntrackedPath = "/home/sprite/.devbox/untracked.tgz";
1663
- const remoteHasGit = await runInitStep({
1724
+ await runInitStep({
1664
1725
  enabled: progressEnabled,
1665
1726
  title: "Preparing remote directories",
1666
1727
  fn: async ({ status }) => {
@@ -1670,10 +1731,14 @@ export const runInit = async (args) => {
1670
1731
  "-lc",
1671
1732
  "git --version",
1672
1733
  ]);
1673
- const remoteHasGit = gitCheck.exitCode === 0;
1734
+ if (gitCheck.exitCode !== 0) {
1735
+ const details = gitCheck.stderr || gitCheck.stdout || "";
1736
+ throw new Error(details
1737
+ ? `Remote git unavailable: ${details.trim()}`
1738
+ : "Remote git unavailable");
1739
+ }
1674
1740
  const remoteDirs = new Set();
1675
1741
  remoteDirs.add(path.posix.dirname(remoteBundlePath));
1676
- remoteDirs.add(path.posix.dirname(remoteArchivePath));
1677
1742
  remoteDirs.add(path.posix.dirname(remoteGitMetaPath));
1678
1743
  remoteDirs.add(path.posix.dirname(remoteStagedPatchPath));
1679
1744
  remoteDirs.add(path.posix.dirname(remoteUnstagedPatchPath));
@@ -1692,7 +1757,6 @@ export const runInit = async (args) => {
1692
1757
  throw new Error(prepResult.stderr || "Failed to prepare remote dirs");
1693
1758
  }
1694
1759
  }
1695
- return remoteHasGit;
1696
1760
  },
1697
1761
  });
1698
1762
  const { headState, gitCommonDir, worktreeState, copyWorktree } = await runInitStep({
@@ -1730,43 +1794,20 @@ export const runInit = async (args) => {
1730
1794
  let stagedPatchCreated = false;
1731
1795
  let unstagedPatchCreated = false;
1732
1796
  let untrackedCreated = false;
1733
- if (remoteHasGit) {
1734
- status.stage("Packaging repo bundle");
1735
- await runCommand(repoRoot, "git", [
1736
- "bundle",
1737
- "create",
1738
- bundlePath,
1739
- "--all",
1740
- ]);
1741
- status.stage("Packaging git metadata");
1742
- gitMetaCreated = await createGitMetaArchive(gitCommonDir, gitMetaPath, gitMetaListPath);
1743
- if (copyWorktree) {
1744
- status.stage("Packaging repo changes");
1745
- stagedPatchCreated = await writePatch(repoRoot, ["diff", "--binary", "--cached"], stagedPatchPath);
1746
- unstagedPatchCreated = await writePatch(repoRoot, ["diff", "--binary"], unstagedPatchPath);
1747
- untrackedCreated = await createFileListArchive(repoRoot, worktreeState.untracked, untrackedPath, untrackedListPath);
1748
- }
1749
- }
1750
- else {
1751
- status.stage("Packaging repo archive");
1752
- if (copyWorktree) {
1753
- const workingFiles = await readNullSeparatedPaths(repoRoot, [
1754
- "ls-files",
1755
- "--cached",
1756
- "--others",
1757
- "--exclude-standard",
1758
- ]);
1759
- await createFileListArchive(repoRoot, workingFiles, archivePath, untrackedListPath);
1760
- }
1761
- else {
1762
- await runCommand(repoRoot, "git", [
1763
- "archive",
1764
- "--format=tar.gz",
1765
- "-o",
1766
- archivePath,
1767
- "HEAD",
1768
- ]);
1769
- }
1797
+ status.stage("Packaging repo bundle");
1798
+ await runCommand(repoRoot, "git", [
1799
+ "bundle",
1800
+ "create",
1801
+ bundlePath,
1802
+ "--all",
1803
+ ]);
1804
+ status.stage("Packaging git metadata");
1805
+ gitMetaCreated = await createGitMetaArchive(gitCommonDir, gitMetaPath, gitMetaListPath);
1806
+ if (copyWorktree) {
1807
+ status.stage("Packaging repo changes");
1808
+ stagedPatchCreated = await writePatch(repoRoot, ["diff", "--binary", "--cached"], stagedPatchPath);
1809
+ unstagedPatchCreated = await writePatch(repoRoot, ["diff", "--binary"], unstagedPatchPath);
1810
+ untrackedCreated = await createFileListArchive(repoRoot, worktreeState.untracked, untrackedPath, untrackedListPath);
1770
1811
  }
1771
1812
  return {
1772
1813
  gitMetaCreated,
@@ -1780,31 +1821,25 @@ export const runInit = async (args) => {
1780
1821
  enabled: progressEnabled,
1781
1822
  title: "Uploading repo",
1782
1823
  fn: async ({ status }) => {
1783
- if (remoteHasGit) {
1784
- status.stage("Uploading repo bundle");
1785
- const bundleData = await fs.readFile(bundlePath);
1786
- await client.writeFile(canonical, remoteBundlePath, bundleData);
1787
- if (packaged.gitMetaCreated) {
1788
- status.stage("Uploading git metadata");
1789
- const gitMetaData = await fs.readFile(gitMetaPath);
1790
- await client.writeFile(canonical, remoteGitMetaPath, gitMetaData);
1791
- }
1792
- if (packaged.stagedPatchCreated) {
1793
- status.stage("Uploading staged changes");
1794
- await client.writeFile(canonical, remoteStagedPatchPath, await fs.readFile(stagedPatchPath));
1795
- }
1796
- if (packaged.unstagedPatchCreated) {
1797
- status.stage("Uploading unstaged changes");
1798
- await client.writeFile(canonical, remoteUnstagedPatchPath, await fs.readFile(unstagedPatchPath));
1799
- }
1800
- if (packaged.untrackedCreated) {
1801
- status.stage("Uploading untracked files");
1802
- await client.writeFile(canonical, remoteUntrackedPath, await fs.readFile(untrackedPath));
1803
- }
1824
+ status.stage("Uploading repo bundle");
1825
+ const bundleData = await fs.readFile(bundlePath);
1826
+ await client.writeFile(canonical, remoteBundlePath, bundleData);
1827
+ if (packaged.gitMetaCreated) {
1828
+ status.stage("Uploading git metadata");
1829
+ const gitMetaData = await fs.readFile(gitMetaPath);
1830
+ await client.writeFile(canonical, remoteGitMetaPath, gitMetaData);
1804
1831
  }
1805
- else {
1806
- status.stage("Uploading repo archive");
1807
- await client.writeFile(canonical, remoteArchivePath, await fs.readFile(archivePath));
1832
+ if (packaged.stagedPatchCreated) {
1833
+ status.stage("Uploading staged changes");
1834
+ await client.writeFile(canonical, remoteStagedPatchPath, await fs.readFile(stagedPatchPath));
1835
+ }
1836
+ if (packaged.unstagedPatchCreated) {
1837
+ status.stage("Uploading unstaged changes");
1838
+ await client.writeFile(canonical, remoteUnstagedPatchPath, await fs.readFile(unstagedPatchPath));
1839
+ }
1840
+ if (packaged.untrackedCreated) {
1841
+ status.stage("Uploading untracked files");
1842
+ await client.writeFile(canonical, remoteUntrackedPath, await fs.readFile(untrackedPath));
1808
1843
  }
1809
1844
  if (globalGitConfigMappings.length > 0) {
1810
1845
  status.stage("Uploading git config");
@@ -1820,77 +1855,48 @@ export const runInit = async (args) => {
1820
1855
  title: "Provisioning workdir",
1821
1856
  fn: async () => {
1822
1857
  const backup = `${expandedWorkdir}.bak-${Date.now()}`;
1823
- if (remoteHasGit) {
1824
- const checkoutCommand = headState.branch
1825
- ? `git checkout -B ${shellQuote(headState.branch)} ${shellQuote(headState.commit)}`
1826
- : `git checkout --detach ${shellQuote(headState.commit)}`;
1827
- const remoteCommand = [
1828
- "set -euo pipefail",
1829
- "unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE",
1830
- `if [ -d ${shellQuote(expandedWorkdir)} ]; then`,
1831
- parsed.force
1832
- ? ` mv ${shellQuote(expandedWorkdir)} ${shellQuote(backup)}`
1833
- : ` echo "Target exists: ${expandedWorkdir}" >&2; exit 1`,
1834
- "fi",
1835
- `mkdir -p ${shellQuote(path.dirname(expandedWorkdir))}`,
1836
- `git init -b devbox-init ${shellQuote(expandedWorkdir)}`,
1837
- `cd ${shellQuote(expandedWorkdir)}`,
1838
- `git fetch ${shellQuote(remoteBundlePath)} 'refs/*:refs/*'`,
1839
- `if [ -f ${shellQuote(remoteGitMetaPath)} ]; then`,
1840
- ` tar -xzf ${shellQuote(remoteGitMetaPath)} -C .git`,
1841
- "fi",
1842
- checkoutCommand,
1843
- `if [ -f ${shellQuote(remoteStagedPatchPath)} ]; then`,
1844
- ` git apply --index ${shellQuote(remoteStagedPatchPath)}`,
1845
- "fi",
1846
- `if [ -f ${shellQuote(remoteUnstagedPatchPath)} ]; then`,
1847
- ` git apply ${shellQuote(remoteUnstagedPatchPath)}`,
1848
- "fi",
1849
- `if [ -f ${shellQuote(remoteUntrackedPath)} ]; then`,
1850
- ` tar -xzf ${shellQuote(remoteUntrackedPath)} -C .`,
1851
- "fi",
1852
- ].join("\n");
1853
- const execResult = await client.exec(canonical, [
1854
- "/bin/bash",
1855
- "--noprofile",
1856
- "--norc",
1857
- "-e",
1858
- "-u",
1859
- "-o",
1860
- "pipefail",
1861
- "-c",
1862
- remoteCommand,
1863
- ]);
1864
- if (execResult.exitCode !== 0) {
1865
- throw new Error(execResult.stderr || "Remote init failed");
1866
- }
1867
- }
1868
- else {
1869
- const remoteCommand = [
1870
- "set -euo pipefail",
1871
- "unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE",
1872
- `if [ -d ${shellQuote(expandedWorkdir)} ]; then`,
1873
- parsed.force
1874
- ? ` mv ${shellQuote(expandedWorkdir)} ${shellQuote(backup)}`
1875
- : ` echo "Target exists: ${expandedWorkdir}" >&2; exit 1`,
1876
- "fi",
1877
- `mkdir -p ${shellQuote(expandedWorkdir)}`,
1878
- `tar -xzf ${shellQuote(remoteArchivePath)} -C ${shellQuote(expandedWorkdir)}`,
1879
- ].join("\n");
1880
- const execResult = await client.exec(canonical, [
1881
- "/bin/bash",
1882
- "--noprofile",
1883
- "--norc",
1884
- "-e",
1885
- "-u",
1886
- "-o",
1887
- "pipefail",
1888
- "-c",
1889
- remoteCommand,
1890
- ]);
1891
- if (execResult.exitCode !== 0) {
1892
- throw new Error(execResult.stderr || "Remote init failed");
1893
- }
1858
+ const checkoutCommand = headState.branch
1859
+ ? `git checkout -B ${shellQuote(headState.branch)} ${shellQuote(headState.commit)}`
1860
+ : `git checkout --detach ${shellQuote(headState.commit)}`;
1861
+ const remoteCommand = [
1862
+ "set -euo pipefail",
1863
+ "unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE",
1864
+ `if [ -d ${shellQuote(expandedWorkdir)} ]; then`,
1865
+ parsed.force
1866
+ ? ` mv ${shellQuote(expandedWorkdir)} ${shellQuote(backup)}`
1867
+ : ` echo "Target exists: ${expandedWorkdir}" >&2; exit 1`,
1868
+ "fi",
1869
+ `mkdir -p ${shellQuote(path.dirname(expandedWorkdir))}`,
1870
+ `git init -b devbox-init ${shellQuote(expandedWorkdir)}`,
1871
+ `cd ${shellQuote(expandedWorkdir)}`,
1872
+ `git fetch ${shellQuote(remoteBundlePath)} 'refs/*:refs/*'`,
1873
+ `if [ -f ${shellQuote(remoteGitMetaPath)} ]; then`,
1874
+ ` tar -xzf ${shellQuote(remoteGitMetaPath)} -C .git`,
1875
+ "fi",
1876
+ checkoutCommand,
1877
+ `if [ -f ${shellQuote(remoteStagedPatchPath)} ]; then`,
1878
+ ` git apply --index ${shellQuote(remoteStagedPatchPath)}`,
1879
+ "fi",
1880
+ `if [ -f ${shellQuote(remoteUnstagedPatchPath)} ]; then`,
1881
+ ` git apply ${shellQuote(remoteUnstagedPatchPath)}`,
1882
+ "fi",
1883
+ `if [ -f ${shellQuote(remoteUntrackedPath)} ]; then`,
1884
+ ` tar -xzf ${shellQuote(remoteUntrackedPath)} -C .`,
1885
+ "fi",
1886
+ ].join("\n");
1887
+ const execResult = await client.exec(canonical, [
1888
+ "/bin/bash",
1889
+ "--noprofile",
1890
+ "--norc",
1891
+ "-e",
1892
+ "-u",
1893
+ "-o",
1894
+ "pipefail",
1895
+ "-c",
1896
+ remoteCommand,
1897
+ ]);
1898
+ if (execResult.exitCode !== 0) {
1899
+ throw new Error(execResult.stderr || "Remote init failed");
1894
1900
  }
1895
1901
  await updateInitState({ steps: { workdirProvisioned: true } });
1896
1902
  },
@@ -2243,8 +2249,7 @@ export const runInit = async (args) => {
2243
2249
  enabled: progressEnabled,
2244
2250
  title: "Writing local metadata",
2245
2251
  fn: async () => {
2246
- await ensureDevboxToml(repoRoot, repoName, slug);
2247
- await writeRepoMarker(repoRoot, { fingerprint, canonical, alias });
2252
+ await writeRepoMarker(projectDir, { fingerprint, canonical, alias });
2248
2253
  },
2249
2254
  });
2250
2255
  await runInitStep({
@@ -2265,15 +2270,6 @@ export const runInit = async (args) => {
2265
2270
  }
2266
2271
  },
2267
2272
  });
2268
- try {
2269
- const gitignore = await fs.readFile(path.join(repoRoot, ".gitignore"), "utf8");
2270
- if (!gitignore.includes(".devbox")) {
2271
- console.log("Note: add .devbox/ to .gitignore to keep local marker private.");
2272
- }
2273
- }
2274
- catch {
2275
- // ignore missing .gitignore
2276
- }
2277
2273
  const skipSetupArtifactsStage = skipCodexApply ||
2278
2274
  (shouldResume && initState?.steps.setupArtifactsStaged);
2279
2275
  if (!skipSetupArtifactsStage) {