@basou/cli 0.30.0 → 0.31.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/dist/index.js +134 -32
- package/dist/index.js.map +1 -1
- package/dist/program.js +134 -32
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3049,7 +3049,6 @@ import {
|
|
|
3049
3049
|
createManifest,
|
|
3050
3050
|
ensureBasouDirectory,
|
|
3051
3051
|
resolveRepositoryRoot as resolveRepositoryRoot7,
|
|
3052
|
-
tryRemoteUrl,
|
|
3053
3052
|
writeManifest
|
|
3054
3053
|
} from "@basou/core";
|
|
3055
3054
|
function collectValue(value, previous) {
|
|
@@ -3058,7 +3057,7 @@ function collectValue(value, previous) {
|
|
|
3058
3057
|
function registerInitCommand(program2) {
|
|
3059
3058
|
program2.command("init").description("Initialize a Basou workspace at the current Git repository root").option("--name <name>", "Workspace name (defaults to the repository directory name)").option("--project-name <name>", "Project display name").option("--project-description <description>", "Project description").option(
|
|
3060
3059
|
"--repo-url <url>",
|
|
3061
|
-
"
|
|
3060
|
+
"Deprecated and ignored (project.repository_url was removed); accepted for 0.x CLI stability, removed at 1.0"
|
|
3062
3061
|
).option(
|
|
3063
3062
|
"--source-root <path>",
|
|
3064
3063
|
"Extra import source root, relative to the repo root (repeatable; aggregates sibling repos into this workspace)",
|
|
@@ -3083,11 +3082,10 @@ async function doRunInit(options, ctx) {
|
|
|
3083
3082
|
const cwd = ctx.cwd ?? process.cwd();
|
|
3084
3083
|
const repositoryRoot = await resolveRepositoryRootForInit(cwd);
|
|
3085
3084
|
const workspaceName = options.name ?? basename4(repositoryRoot);
|
|
3086
|
-
let repositoryUrl;
|
|
3087
3085
|
if (options.repoUrl !== void 0) {
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3086
|
+
console.error(
|
|
3087
|
+
"Warning: --repo-url is deprecated and ignored (project.repository_url was removed); the flag will be removed at 1.0."
|
|
3088
|
+
);
|
|
3091
3089
|
}
|
|
3092
3090
|
const sourceRoots = (options.sourceRoot ?? []).map((p) => {
|
|
3093
3091
|
const rel = relative(repositoryRoot, resolve5(cwd, p));
|
|
@@ -3098,7 +3096,6 @@ async function doRunInit(options, ctx) {
|
|
|
3098
3096
|
workspaceName,
|
|
3099
3097
|
...options.projectName !== void 0 ? { projectName: options.projectName } : {},
|
|
3100
3098
|
...options.projectDescription !== void 0 ? { projectDescription: options.projectDescription } : {},
|
|
3101
|
-
...repositoryUrl !== void 0 ? { repositoryUrl } : {},
|
|
3102
3099
|
...sourceRoots.length > 0 ? { sourceRoots } : {}
|
|
3103
3100
|
});
|
|
3104
3101
|
await writeManifest(paths, manifest, { force: options.force === true });
|
|
@@ -6707,19 +6704,19 @@ function parseInterval(value) {
|
|
|
6707
6704
|
return seconds;
|
|
6708
6705
|
}
|
|
6709
6706
|
function abortableSleep(ms, signal) {
|
|
6710
|
-
return new Promise((
|
|
6707
|
+
return new Promise((resolve14) => {
|
|
6711
6708
|
if (signal.aborted) {
|
|
6712
|
-
|
|
6709
|
+
resolve14();
|
|
6713
6710
|
return;
|
|
6714
6711
|
}
|
|
6715
6712
|
let timer;
|
|
6716
6713
|
const onAbort = () => {
|
|
6717
6714
|
clearTimeout(timer);
|
|
6718
|
-
|
|
6715
|
+
resolve14();
|
|
6719
6716
|
};
|
|
6720
6717
|
timer = setTimeout(() => {
|
|
6721
6718
|
signal.removeEventListener("abort", onAbort);
|
|
6722
|
-
|
|
6719
|
+
resolve14();
|
|
6723
6720
|
}, ms);
|
|
6724
6721
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
6725
6722
|
});
|
|
@@ -8606,6 +8603,8 @@ async function doRunStatus(options, ctx) {
|
|
|
8606
8603
|
if (findErrorCode14(error, "ENOENT")) {
|
|
8607
8604
|
throw new Error("Workspace not initialized. Run 'basou init' first.");
|
|
8608
8605
|
}
|
|
8606
|
+
const gateMessage = formatVersionGateMessage(error);
|
|
8607
|
+
if (gateMessage !== void 0) throw new Error(gateMessage, { cause: error });
|
|
8609
8608
|
throw new Error("Failed to read workspace manifest", { cause: error });
|
|
8610
8609
|
}
|
|
8611
8610
|
const snapshot = await buildStatusSnapshot({ manifest, paths });
|
|
@@ -8637,6 +8636,18 @@ async function resolveRepositoryRootForStatus(cwd) {
|
|
|
8637
8636
|
throw error;
|
|
8638
8637
|
}
|
|
8639
8638
|
}
|
|
8639
|
+
function formatVersionGateMessage(error) {
|
|
8640
|
+
const issues = error.issues;
|
|
8641
|
+
if (!Array.isArray(issues)) return void 0;
|
|
8642
|
+
for (const issue of issues) {
|
|
8643
|
+
const path = issue.path;
|
|
8644
|
+
const message = issue.message;
|
|
8645
|
+
if (Array.isArray(path) && (path.includes("schema_version") || path.includes("basou_version")) && typeof message === "string" && message.startsWith("unsupported .basou format version")) {
|
|
8646
|
+
return message;
|
|
8647
|
+
}
|
|
8648
|
+
}
|
|
8649
|
+
return void 0;
|
|
8650
|
+
}
|
|
8640
8651
|
|
|
8641
8652
|
// src/commands/task.ts
|
|
8642
8653
|
import { readFile as readFile7 } from "fs/promises";
|
|
@@ -9859,7 +9870,7 @@ async function assertWorkspaceInitialized14(basouRoot) {
|
|
|
9859
9870
|
// src/commands/view.ts
|
|
9860
9871
|
import { spawn } from "child_process";
|
|
9861
9872
|
import { createHash } from "crypto";
|
|
9862
|
-
import { basename as
|
|
9873
|
+
import { basename as basename8, resolve as resolve13 } from "path";
|
|
9863
9874
|
import {
|
|
9864
9875
|
assertBasouRootSafe as assertBasouRootSafe18,
|
|
9865
9876
|
basouPaths as basouPaths21,
|
|
@@ -9997,7 +10008,7 @@ function formatSafetyReport(result) {
|
|
|
9997
10008
|
|
|
9998
10009
|
// src/lib/view-server.ts
|
|
9999
10010
|
import { createServer } from "http";
|
|
10000
|
-
import { join as join17 } from "path";
|
|
10011
|
+
import { basename as basename7, join as join17, resolve as resolve12 } from "path";
|
|
10001
10012
|
import {
|
|
10002
10013
|
computeWorkStats as computeWorkStats2,
|
|
10003
10014
|
enumerateApprovals as enumerateApprovals2,
|
|
@@ -10013,9 +10024,42 @@ import {
|
|
|
10013
10024
|
readTaskFile as readTaskFile2,
|
|
10014
10025
|
renderDecisions as renderDecisions3,
|
|
10015
10026
|
renderHandoff as renderHandoff3,
|
|
10016
|
-
summarizeOrientation
|
|
10027
|
+
summarizeOrientation,
|
|
10028
|
+
tryRemoteUrl
|
|
10017
10029
|
} from "@basou/core";
|
|
10018
10030
|
|
|
10031
|
+
// src/lib/repo-url.ts
|
|
10032
|
+
function toBrowserUrl(remote) {
|
|
10033
|
+
const raw = remote.trim();
|
|
10034
|
+
if (raw.length === 0) return null;
|
|
10035
|
+
let host;
|
|
10036
|
+
let path;
|
|
10037
|
+
if (raw.includes("://")) {
|
|
10038
|
+
let parsed;
|
|
10039
|
+
try {
|
|
10040
|
+
parsed = new URL(raw);
|
|
10041
|
+
} catch {
|
|
10042
|
+
return null;
|
|
10043
|
+
}
|
|
10044
|
+
const scheme = parsed.protocol;
|
|
10045
|
+
if (scheme !== "ssh:" && scheme !== "git:" && scheme !== "http:" && scheme !== "https:") {
|
|
10046
|
+
return null;
|
|
10047
|
+
}
|
|
10048
|
+
host = scheme === "http:" || scheme === "https:" ? parsed.host : parsed.hostname;
|
|
10049
|
+
path = parsed.pathname;
|
|
10050
|
+
} else {
|
|
10051
|
+
const match = /^[^@/\s]+@([^:/\s@]+):(.+)$/.exec(raw);
|
|
10052
|
+
if (match === null || match[1] === void 0 || match[2] === void 0) return null;
|
|
10053
|
+
host = match[1];
|
|
10054
|
+
path = match[2];
|
|
10055
|
+
}
|
|
10056
|
+
const cleanPath = path.replace(/^\/+/, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
10057
|
+
if (host.length === 0 || cleanPath.length === 0) return null;
|
|
10058
|
+
if (/\s/.test(host) || /\s/.test(cleanPath)) return null;
|
|
10059
|
+
if (cleanPath.split("/").some((seg) => seg === "." || seg === "..")) return null;
|
|
10060
|
+
return `https://${host}/${cleanPath}`;
|
|
10061
|
+
}
|
|
10062
|
+
|
|
10019
10063
|
// src/lib/view-ui.ts
|
|
10020
10064
|
var VIEW_HTML = `<!doctype html>
|
|
10021
10065
|
<html lang="en">
|
|
@@ -10417,9 +10461,30 @@ var VIEW_HTML = `<!doctype html>
|
|
|
10417
10461
|
card(c.approvalsPending, 'approvals pending')
|
|
10418
10462
|
]);
|
|
10419
10463
|
detail.appendChild(cards);
|
|
10464
|
+
renderRepos(detail, d.repos || []);
|
|
10420
10465
|
detail.appendChild(el('p', { class: 'muted', text: 'repo: ' + d.repoRoot }));
|
|
10421
10466
|
}).catch(fail);
|
|
10422
10467
|
}
|
|
10468
|
+
// The declared roster repos, each with a LIVE git link (derived server-side
|
|
10469
|
+
// from the repo's local git config at request time, never stored). A repo
|
|
10470
|
+
// with no remote renders as "local only". Links open in a new tab.
|
|
10471
|
+
function renderRepos(detail, repos) {
|
|
10472
|
+
if (!repos.length) return;
|
|
10473
|
+
detail.appendChild(el('h3', { text: 'Repos' }));
|
|
10474
|
+
var rows = el('div', { class: 'repos' }, []);
|
|
10475
|
+
repos.forEach(function (r) {
|
|
10476
|
+
var vis = r.visibility ? (' (' + r.visibility + ')') : '';
|
|
10477
|
+
var link = r.url
|
|
10478
|
+
? el('a', { href: r.url, target: '_blank', rel: 'noopener noreferrer', text: r.url })
|
|
10479
|
+
: el('span', { class: 'muted', text: 'local only' });
|
|
10480
|
+
rows.appendChild(el('div', { class: 'f' }, [
|
|
10481
|
+
el('strong', { text: r.name }),
|
|
10482
|
+
el('span', { class: 'muted', text: vis + ' ' }),
|
|
10483
|
+
link
|
|
10484
|
+
]));
|
|
10485
|
+
});
|
|
10486
|
+
detail.appendChild(rows);
|
|
10487
|
+
}
|
|
10423
10488
|
function card(n, label) {
|
|
10424
10489
|
return el('div', { class: 'card' }, [
|
|
10425
10490
|
el('div', { class: 'n', text: String(n) }),
|
|
@@ -10658,7 +10723,7 @@ function startViewServer(opts) {
|
|
|
10658
10723
|
};
|
|
10659
10724
|
let boundPort = port;
|
|
10660
10725
|
const getPort = () => boundPort;
|
|
10661
|
-
return new Promise((
|
|
10726
|
+
return new Promise((resolve14, reject) => {
|
|
10662
10727
|
const server = createServer((req, res) => {
|
|
10663
10728
|
handleRequest(req, res, deps, getPort, runExclusive).catch((error) => {
|
|
10664
10729
|
sendError(res, error instanceof HttpError ? error.status : 500, pathlessMessage(error));
|
|
@@ -10669,7 +10734,7 @@ function startViewServer(opts) {
|
|
|
10669
10734
|
const address = server.address();
|
|
10670
10735
|
boundPort = isAddressInfo(address) ? address.port : port;
|
|
10671
10736
|
server.off("error", reject);
|
|
10672
|
-
|
|
10737
|
+
resolve14({
|
|
10673
10738
|
url: `http://${host}:${boundPort}`,
|
|
10674
10739
|
port: boundPort,
|
|
10675
10740
|
close: () => closeServer(server)
|
|
@@ -10681,8 +10746,8 @@ function isAddressInfo(value) {
|
|
|
10681
10746
|
return value !== null && typeof value === "object";
|
|
10682
10747
|
}
|
|
10683
10748
|
function closeServer(server) {
|
|
10684
|
-
return new Promise((
|
|
10685
|
-
server.close(() =>
|
|
10749
|
+
return new Promise((resolve14) => {
|
|
10750
|
+
server.close(() => resolve14());
|
|
10686
10751
|
server.closeAllConnections();
|
|
10687
10752
|
});
|
|
10688
10753
|
}
|
|
@@ -10725,20 +10790,29 @@ async function handleGet(res, pathname, deps) {
|
|
|
10725
10790
|
sendError(res, 404, "Unknown workspace");
|
|
10726
10791
|
return;
|
|
10727
10792
|
}
|
|
10728
|
-
if (!await handleWorkspaceGet(res, scoped.sub, ws, deps.nowProvider)) {
|
|
10793
|
+
if (!await handleWorkspaceGet(res, scoped.sub, ws, deps.nowProvider, remoteUrlOf(deps))) {
|
|
10729
10794
|
sendError(res, 404, "Not found");
|
|
10730
10795
|
}
|
|
10731
10796
|
return;
|
|
10732
10797
|
}
|
|
10733
10798
|
if (pathname.startsWith(API_PREFIX)) {
|
|
10734
10799
|
const sub = pathname.slice(API_PREFIX.length);
|
|
10735
|
-
if (!await handleWorkspaceGet(
|
|
10800
|
+
if (!await handleWorkspaceGet(
|
|
10801
|
+
res,
|
|
10802
|
+
sub,
|
|
10803
|
+
primaryWorkspace(deps),
|
|
10804
|
+
deps.nowProvider,
|
|
10805
|
+
remoteUrlOf(deps)
|
|
10806
|
+
)) {
|
|
10736
10807
|
sendError(res, 404, "Not found");
|
|
10737
10808
|
}
|
|
10738
10809
|
return;
|
|
10739
10810
|
}
|
|
10740
10811
|
sendError(res, 404, "Not found");
|
|
10741
10812
|
}
|
|
10813
|
+
function remoteUrlOf(deps) {
|
|
10814
|
+
return deps.remoteUrlOf ?? tryRemoteUrl;
|
|
10815
|
+
}
|
|
10742
10816
|
async function handlePost(res, pathname, body, deps, runExclusive) {
|
|
10743
10817
|
const scoped = matchWsRoute(pathname);
|
|
10744
10818
|
if (scoped !== null) {
|
|
@@ -10761,9 +10835,9 @@ async function handlePost(res, pathname, body, deps, runExclusive) {
|
|
|
10761
10835
|
}
|
|
10762
10836
|
sendError(res, 404, "Not found");
|
|
10763
10837
|
}
|
|
10764
|
-
async function handleWorkspaceGet(res, sub, ws, nowProvider) {
|
|
10838
|
+
async function handleWorkspaceGet(res, sub, ws, nowProvider, resolveRemoteUrl) {
|
|
10765
10839
|
if (sub === "overview") {
|
|
10766
|
-
sendJson(res, 200, await overview(ws, nowProvider));
|
|
10840
|
+
sendJson(res, 200, await overview(ws, nowProvider, resolveRemoteUrl));
|
|
10767
10841
|
return true;
|
|
10768
10842
|
}
|
|
10769
10843
|
if (sub === "sessions") {
|
|
@@ -10896,7 +10970,7 @@ async function captureStaleness(ws, nowIso) {
|
|
|
10896
10970
|
const probe = await probeStaleness({ ctx: ws.importCtx, paths: ws.paths, nowIso });
|
|
10897
10971
|
return probe === null ? { checked: false } : { checked: true, ...probe };
|
|
10898
10972
|
}
|
|
10899
|
-
async function overview(ws, nowProvider) {
|
|
10973
|
+
async function overview(ws, nowProvider, resolveRemoteUrl) {
|
|
10900
10974
|
let manifest;
|
|
10901
10975
|
try {
|
|
10902
10976
|
manifest = await readManifest13(ws.paths);
|
|
@@ -10909,6 +10983,7 @@ async function overview(ws, nowProvider) {
|
|
|
10909
10983
|
const nowIso = nowProvider().toISOString();
|
|
10910
10984
|
const handoff = await renderHandoff3({ paths: ws.paths, nowIso });
|
|
10911
10985
|
const approvals = await enumerateApprovals2(ws.paths);
|
|
10986
|
+
const repos = await rosterRepos(ws.repoRoot, manifest, resolveRemoteUrl);
|
|
10912
10987
|
return {
|
|
10913
10988
|
initialized: true,
|
|
10914
10989
|
repoRoot: ws.repoRoot,
|
|
@@ -10926,9 +11001,26 @@ async function overview(ws, nowProvider) {
|
|
|
10926
11001
|
approvalsPending: approvals.pending.length,
|
|
10927
11002
|
approvalsResolved: approvals.resolved.length
|
|
10928
11003
|
},
|
|
11004
|
+
repos,
|
|
10929
11005
|
generatedAt: nowIso
|
|
10930
11006
|
};
|
|
10931
11007
|
}
|
|
11008
|
+
async function rosterRepos(repoRoot, manifest, resolveRemoteUrl) {
|
|
11009
|
+
const roster = manifest.repos ?? [];
|
|
11010
|
+
return Promise.all(
|
|
11011
|
+
roster.map(async (repo) => {
|
|
11012
|
+
const abs = resolve12(repoRoot, repo.path);
|
|
11013
|
+
const remote = await resolveRemoteUrl(abs);
|
|
11014
|
+
const url = remote !== void 0 ? toBrowserUrl(remote) : null;
|
|
11015
|
+
return {
|
|
11016
|
+
name: basename7(abs),
|
|
11017
|
+
path: repo.path,
|
|
11018
|
+
...url !== null ? { url } : {},
|
|
11019
|
+
...repo.visibility !== void 0 ? { visibility: repo.visibility } : {}
|
|
11020
|
+
};
|
|
11021
|
+
})
|
|
11022
|
+
);
|
|
11023
|
+
}
|
|
10932
11024
|
async function sessionsList(ws, nowProvider) {
|
|
10933
11025
|
const entries = await loadSessionEntries4(ws.paths, { now: nowProvider() });
|
|
10934
11026
|
const sessions = entries.map((entry) => ({
|
|
@@ -11173,15 +11265,20 @@ async function buildSingleDeps(ctx, cwd) {
|
|
|
11173
11265
|
const paths = basouPaths21(repositoryRoot);
|
|
11174
11266
|
await assertWorkspaceInitialized15(paths.root);
|
|
11175
11267
|
const entry = await buildWorkspaceEntry(repositoryRoot, ctx);
|
|
11176
|
-
return {
|
|
11268
|
+
return {
|
|
11269
|
+
workspaces: [entry],
|
|
11270
|
+
mode: "single",
|
|
11271
|
+
nowProvider: nowProviderOf(ctx),
|
|
11272
|
+
...ctx.remoteUrlOf !== void 0 ? { remoteUrlOf: ctx.remoteUrlOf } : {}
|
|
11273
|
+
};
|
|
11177
11274
|
}
|
|
11178
11275
|
async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
11179
|
-
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path:
|
|
11276
|
+
const specs = workspaceFlags.length > 0 ? workspaceFlags.map((p) => ({ path: resolve13(cwd, p) })) : await loadPortfolioConfig(ctx.portfolioConfigPath);
|
|
11180
11277
|
const entries = [];
|
|
11181
11278
|
const seenPath = /* @__PURE__ */ new Set();
|
|
11182
11279
|
const seenKey = /* @__PURE__ */ new Set();
|
|
11183
11280
|
for (const spec of specs) {
|
|
11184
|
-
const repoRoot =
|
|
11281
|
+
const repoRoot = resolve13(spec.path);
|
|
11185
11282
|
if (seenPath.has(repoRoot)) continue;
|
|
11186
11283
|
seenPath.add(repoRoot);
|
|
11187
11284
|
const entry = await buildWorkspaceEntry(repoRoot, ctx, spec.label);
|
|
@@ -11191,7 +11288,12 @@ async function buildPortfolioDeps(workspaceFlags, ctx, cwd) {
|
|
|
11191
11288
|
entries.push({ ...entry, key });
|
|
11192
11289
|
}
|
|
11193
11290
|
if (entries.length === 0) throw new Error("No workspaces to show.");
|
|
11194
|
-
return {
|
|
11291
|
+
return {
|
|
11292
|
+
workspaces: entries,
|
|
11293
|
+
mode: "portfolio",
|
|
11294
|
+
nowProvider: nowProviderOf(ctx),
|
|
11295
|
+
...ctx.remoteUrlOf !== void 0 ? { remoteUrlOf: ctx.remoteUrlOf } : {}
|
|
11296
|
+
};
|
|
11195
11297
|
}
|
|
11196
11298
|
async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
11197
11299
|
const paths = basouPaths21(repoRoot);
|
|
@@ -11214,7 +11316,7 @@ async function buildWorkspaceEntry(repoRoot, ctx, labelOverride) {
|
|
|
11214
11316
|
const notFound = error instanceof Error && error.message === "YAML file not found";
|
|
11215
11317
|
return {
|
|
11216
11318
|
key: `ws-${createHash("sha1").update(repoRoot).digest("hex").slice(0, 12)}`,
|
|
11217
|
-
label: labelOverride ??
|
|
11319
|
+
label: labelOverride ?? basename8(repoRoot),
|
|
11218
11320
|
paths,
|
|
11219
11321
|
repoRoot,
|
|
11220
11322
|
importCtx,
|
|
@@ -11253,7 +11355,7 @@ function openInBrowser(url, override) {
|
|
|
11253
11355
|
}
|
|
11254
11356
|
}
|
|
11255
11357
|
function waitForShutdown(signal) {
|
|
11256
|
-
return new Promise((
|
|
11358
|
+
return new Promise((resolve14) => {
|
|
11257
11359
|
const cleanup = () => {
|
|
11258
11360
|
process.off("SIGINT", onSignal);
|
|
11259
11361
|
process.off("SIGTERM", onSignal);
|
|
@@ -11261,18 +11363,18 @@ function waitForShutdown(signal) {
|
|
|
11261
11363
|
};
|
|
11262
11364
|
const onSignal = () => {
|
|
11263
11365
|
cleanup();
|
|
11264
|
-
|
|
11366
|
+
resolve14();
|
|
11265
11367
|
};
|
|
11266
11368
|
const onAbort = () => {
|
|
11267
11369
|
cleanup();
|
|
11268
|
-
|
|
11370
|
+
resolve14();
|
|
11269
11371
|
};
|
|
11270
11372
|
process.on("SIGINT", onSignal);
|
|
11271
11373
|
process.on("SIGTERM", onSignal);
|
|
11272
11374
|
if (signal !== void 0) {
|
|
11273
11375
|
if (signal.aborted) {
|
|
11274
11376
|
cleanup();
|
|
11275
|
-
|
|
11377
|
+
resolve14();
|
|
11276
11378
|
return;
|
|
11277
11379
|
}
|
|
11278
11380
|
signal.addEventListener("abort", onAbort);
|