bopodev 0.1.22 → 0.1.23

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 (2) hide show
  1. package/dist/index.js +87 -22
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,12 +6,13 @@ import { cancel, outro } from "@clack/prompts";
6
6
 
7
7
  // src/lib/checks.ts
8
8
  import { access as access2, constants, stat } from "fs/promises";
9
- import { homedir } from "os";
9
+ import { homedir as homedir2 } from "os";
10
10
  import { join as join2, resolve as resolve2 } from "path";
11
11
 
12
12
  // src/lib/process.ts
13
13
  import { spawn } from "child_process";
14
- import { access } from "fs/promises";
14
+ import { access, mkdir } from "fs/promises";
15
+ import { homedir } from "os";
15
16
  import { join, resolve } from "path";
16
17
  async function commandExists(command) {
17
18
  const result = await runCommandCapture(command, ["--version"]);
@@ -91,6 +92,70 @@ async function resolveWorkspaceRoot(startDir) {
91
92
  cursor = parent;
92
93
  }
93
94
  }
95
+ async function resolveWorkspaceRootOrManaged(startDir, options) {
96
+ const directWorkspace = await resolveWorkspaceRoot(startDir);
97
+ if (directWorkspace) {
98
+ return directWorkspace;
99
+ }
100
+ const managedWorkspace = resolveManagedWorkspacePath();
101
+ const managedResolved = await resolveWorkspaceRoot(managedWorkspace);
102
+ if (managedResolved) {
103
+ return managedResolved;
104
+ }
105
+ if (!options?.bootstrapIfMissing) {
106
+ return null;
107
+ }
108
+ await bootstrapManagedWorkspace(managedWorkspace);
109
+ return await resolveWorkspaceRoot(managedWorkspace);
110
+ }
111
+ function resolveManagedWorkspacePath() {
112
+ if (process.env.BOPO_CLI_WORKSPACE_ROOT?.trim()) {
113
+ return resolve(expandHomePrefix(process.env.BOPO_CLI_WORKSPACE_ROOT.trim()));
114
+ }
115
+ const instanceRoot = resolveInstanceRoot();
116
+ return join(instanceRoot, "workspace", "bopodev");
117
+ }
118
+ async function bootstrapManagedWorkspace(workspacePath) {
119
+ const parent = resolve(workspacePath, "..");
120
+ await mkdir(parent, { recursive: true });
121
+ if (await fileExists(workspacePath)) {
122
+ const managedResolved = await resolveWorkspaceRoot(workspacePath);
123
+ if (managedResolved) {
124
+ return;
125
+ }
126
+ throw new Error(
127
+ `Managed workspace path exists but is not a valid Bopodev workspace: ${workspacePath}
128
+ Set BOPO_CLI_WORKSPACE_ROOT to another empty path or remove the existing directory and rerun onboarding.`
129
+ );
130
+ }
131
+ const repository = process.env.BOPO_REPO_URL?.trim() || "https://github.com/dkrusenstrahle/bopodev.git";
132
+ const requestedRef = process.env.BOPO_REPO_REF?.trim();
133
+ const cloneArgs = requestedRef ? ["clone", "--depth", "1", "--branch", requestedRef, repository, workspacePath] : ["clone", "--depth", "1", repository, workspacePath];
134
+ const cloneResult = await runCommandCapture("git", cloneArgs, { timeoutMs: 18e4 });
135
+ if (!cloneResult.ok) {
136
+ const details = [cloneResult.stderr, cloneResult.stdout].filter((value) => value.trim().length > 0).join("\n").trim();
137
+ throw new Error(
138
+ details.length > 0 ? details : `Failed to bootstrap managed workspace from ${repository} (exit code: ${String(cloneResult.code)}).`
139
+ );
140
+ }
141
+ }
142
+ function resolveInstanceRoot() {
143
+ if (process.env.BOPO_INSTANCE_ROOT?.trim()) {
144
+ return resolve(expandHomePrefix(process.env.BOPO_INSTANCE_ROOT.trim()));
145
+ }
146
+ const bopoHome = process.env.BOPO_HOME?.trim() ? expandHomePrefix(process.env.BOPO_HOME.trim()) : join(homedir(), ".bopodev");
147
+ const instanceId = process.env.BOPO_INSTANCE_ID?.trim() || "default";
148
+ return resolve(bopoHome, "instances", instanceId);
149
+ }
150
+ function expandHomePrefix(value) {
151
+ if (value === "~") {
152
+ return homedir();
153
+ }
154
+ if (value.startsWith("~/")) {
155
+ return resolve(homedir(), value.slice(2));
156
+ }
157
+ return value;
158
+ }
94
159
  async function fileExists(path) {
95
160
  try {
96
161
  await access(path);
@@ -151,7 +216,7 @@ async function runDoctorChecks(options) {
151
216
  details: gemini.available && gemini.exitCode === 0 ? `Command '${geminiCommand}' is available` : gemini.error ?? `Command '${geminiCommand}' exited with ${String(gemini.exitCode)}`
152
217
  });
153
218
  try {
154
- const instanceRoot = resolveInstanceRoot();
219
+ const instanceRoot = resolveInstanceRoot2();
155
220
  const storageRoot = join2(instanceRoot, "data", "storage");
156
221
  const workspaceRoot = join2(instanceRoot, "workspaces");
157
222
  checks.push({
@@ -200,24 +265,24 @@ async function ensureWritableDirectory(path) {
200
265
  return false;
201
266
  }
202
267
  }
203
- function resolveInstanceRoot() {
268
+ function resolveInstanceRoot2() {
204
269
  const explicit = process.env.BOPO_INSTANCE_ROOT?.trim();
205
270
  if (explicit) {
206
- return resolve2(expandHomePrefix(explicit));
271
+ return resolve2(expandHomePrefix2(explicit));
207
272
  }
208
- const home = process.env.BOPO_HOME?.trim() ? expandHomePrefix(process.env.BOPO_HOME.trim()) : join2(homedir(), ".bopodev");
273
+ const home = process.env.BOPO_HOME?.trim() ? expandHomePrefix2(process.env.BOPO_HOME.trim()) : join2(homedir2(), ".bopodev");
209
274
  const instanceId = process.env.BOPO_INSTANCE_ID?.trim() || "default";
210
275
  if (!SAFE_PATH_SEGMENT_RE.test(instanceId)) {
211
276
  throw new Error(`Invalid BOPO_INSTANCE_ID '${instanceId}'.`);
212
277
  }
213
278
  return resolve2(home, "instances", instanceId);
214
279
  }
215
- function expandHomePrefix(value) {
280
+ function expandHomePrefix2(value) {
216
281
  if (value === "~") {
217
- return homedir();
282
+ return homedir2();
218
283
  }
219
284
  if (value.startsWith("~/")) {
220
- return resolve2(homedir(), value.slice(2));
285
+ return resolve2(homedir2(), value.slice(2));
221
286
  }
222
287
  return value;
223
288
  }
@@ -265,7 +330,7 @@ async function runWorkspaceBackfillDryRunCheck(workspaceRoot) {
265
330
  }
266
331
  }
267
332
  async function runWorkspacePathDriftCheck(workspaceRoot) {
268
- const instanceRoot = resolveInstanceRoot();
333
+ const instanceRoot = resolveInstanceRoot2();
269
334
  const suspiciousEntries = await detectSuspiciousWorkspaceDirectories(workspaceRoot);
270
335
  if (suspiciousEntries.length === 0) {
271
336
  return {
@@ -363,9 +428,9 @@ function printSummaryCard(lines) {
363
428
 
364
429
  // src/commands/doctor.ts
365
430
  async function runDoctorCommand(cwd) {
366
- const workspaceRoot = await resolveWorkspaceRoot(cwd);
431
+ const workspaceRoot = await resolveWorkspaceRootOrManaged(cwd);
367
432
  if (!workspaceRoot) {
368
- throw new Error("Could not find a pnpm workspace root. Run this command from inside the Bopodev repo.");
433
+ throw new Error("Could not find a Bopodev workspace root. Run `bopodev onboard` first.");
369
434
  }
370
435
  printBanner();
371
436
  printSection("bopodev doctor");
@@ -383,7 +448,7 @@ async function runDoctorCommand(cwd) {
383
448
 
384
449
  // src/commands/onboard.ts
385
450
  import { access as access3, copyFile, readFile, writeFile } from "fs/promises";
386
- import { homedir as homedir2 } from "os";
451
+ import { homedir as homedir3 } from "os";
387
452
  import { join as join3, resolve as resolve3 } from "path";
388
453
  import { confirm, isCancel, log, select, spinner, text } from "@clack/prompts";
389
454
  import dotenv from "dotenv";
@@ -531,9 +596,9 @@ var defaultDeps = {
531
596
  }
532
597
  };
533
598
  async function runOnboardFlow(options, deps = defaultDeps) {
534
- const workspaceRoot = await resolveWorkspaceRoot(options.cwd);
599
+ const workspaceRoot = await resolveWorkspaceRootOrManaged(options.cwd, { bootstrapIfMissing: true });
535
600
  if (!workspaceRoot) {
536
- throw new Error("Could not find a pnpm workspace root. Run this command from inside the Bopodev repo.");
601
+ throw new Error("Could not find or bootstrap a Bopodev workspace root.");
537
602
  }
538
603
  printBanner();
539
604
  printSection("bopodev onboard");
@@ -845,18 +910,18 @@ function deriveAvailableAgentProviders(checks) {
845
910
  }
846
911
  function resolveDbPathSummary(configuredDbPath) {
847
912
  if (configuredDbPath) {
848
- return resolve3(expandHomePrefix2(configuredDbPath));
913
+ return resolve3(expandHomePrefix3(configuredDbPath));
849
914
  }
850
- const home = process.env.BOPO_HOME?.trim() ? expandHomePrefix2(process.env.BOPO_HOME.trim()) : join3(homedir2(), ".bopodev");
915
+ const home = process.env.BOPO_HOME?.trim() ? expandHomePrefix3(process.env.BOPO_HOME.trim()) : join3(homedir3(), ".bopodev");
851
916
  const instanceId = process.env.BOPO_INSTANCE_ID?.trim() || "default";
852
917
  return resolve3(home, "instances", instanceId, "db", "bopodev.db");
853
918
  }
854
- function expandHomePrefix2(value) {
919
+ function expandHomePrefix3(value) {
855
920
  if (value === "~") {
856
- return homedir2();
921
+ return homedir3();
857
922
  }
858
923
  if (value.startsWith("~/")) {
859
- return resolve3(homedir2(), value.slice(2));
924
+ return resolve3(homedir3(), value.slice(2));
860
925
  }
861
926
  return value;
862
927
  }
@@ -980,9 +1045,9 @@ async function hasExistingInstall(workspaceRoot) {
980
1045
 
981
1046
  // src/commands/start.ts
982
1047
  async function runStartCommand(cwd, options) {
983
- const workspaceRoot = await resolveWorkspaceRoot(cwd);
1048
+ const workspaceRoot = await resolveWorkspaceRootOrManaged(cwd);
984
1049
  if (!workspaceRoot) {
985
- throw new Error("Could not find a pnpm workspace root. Run this command from inside the Bopodev repo.");
1050
+ throw new Error("Could not find a Bopodev workspace root. Run `bopodev onboard` first.");
986
1051
  }
987
1052
  const script = options?.quiet === false ? "start" : "start:quiet";
988
1053
  const code = await runCommandStreaming("pnpm", [script], { cwd: workspaceRoot });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bopodev",
3
- "version": "0.1.22",
3
+ "version": "0.1.23",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "bin": {