bopodev 0.1.22 → 0.1.24
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/dist/index.js +87 -22
- 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 =
|
|
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
|
|
268
|
+
function resolveInstanceRoot2() {
|
|
204
269
|
const explicit = process.env.BOPO_INSTANCE_ROOT?.trim();
|
|
205
270
|
if (explicit) {
|
|
206
|
-
return resolve2(
|
|
271
|
+
return resolve2(expandHomePrefix2(explicit));
|
|
207
272
|
}
|
|
208
|
-
const home = process.env.BOPO_HOME?.trim() ?
|
|
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
|
|
280
|
+
function expandHomePrefix2(value) {
|
|
216
281
|
if (value === "~") {
|
|
217
|
-
return
|
|
282
|
+
return homedir2();
|
|
218
283
|
}
|
|
219
284
|
if (value.startsWith("~/")) {
|
|
220
|
-
return resolve2(
|
|
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 =
|
|
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
|
|
431
|
+
const workspaceRoot = await resolveWorkspaceRootOrManaged(cwd);
|
|
367
432
|
if (!workspaceRoot) {
|
|
368
|
-
throw new Error("Could not find a
|
|
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
|
|
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
|
|
599
|
+
const workspaceRoot = await resolveWorkspaceRootOrManaged(options.cwd, { bootstrapIfMissing: true });
|
|
535
600
|
if (!workspaceRoot) {
|
|
536
|
-
throw new Error("Could not find a
|
|
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(
|
|
913
|
+
return resolve3(expandHomePrefix3(configuredDbPath));
|
|
849
914
|
}
|
|
850
|
-
const home = process.env.BOPO_HOME?.trim() ?
|
|
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
|
|
919
|
+
function expandHomePrefix3(value) {
|
|
855
920
|
if (value === "~") {
|
|
856
|
-
return
|
|
921
|
+
return homedir3();
|
|
857
922
|
}
|
|
858
923
|
if (value.startsWith("~/")) {
|
|
859
|
-
return resolve3(
|
|
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
|
|
1048
|
+
const workspaceRoot = await resolveWorkspaceRootOrManaged(cwd);
|
|
984
1049
|
if (!workspaceRoot) {
|
|
985
|
-
throw new Error("Could not find a
|
|
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 });
|