@tablation/crew 0.72.1 → 0.72.2
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/cli.js +60 -14
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2587,7 +2587,9 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2587
2587
|
}
|
|
2588
2588
|
}
|
|
2589
2589
|
const hasRepos = Object.keys(repoDirs).length > 0;
|
|
2590
|
-
const
|
|
2590
|
+
const reposBasePath = expand(String(c?.reposBasePath ?? raw.ship?.reposBasePath ?? DEFAULT_REPOS_BASE_PATH), base);
|
|
2591
|
+
const dirIsCloneBase = !c?.dir && !hasRepos && c?.repos && typeof c.repos === "object" && !Array.isArray(c.repos);
|
|
2592
|
+
const dir = c?.dir ? expand(String(c.dir), base) : hasRepos ? Object.values(repoDirs)[0] : dirIsCloneBase ? join7(reposBasePath, splitRoute(route)[0]) : missing.req(void 0, `${where}.dir (or ${where}.repos)`) ?? "";
|
|
2591
2593
|
const weight = c?.weight === void 0 ? void 0 : Number(c.weight);
|
|
2592
2594
|
if (weight !== void 0 && !(Number.isFinite(weight) && weight > 0)) {
|
|
2593
2595
|
missing.add(`${where}.weight must be a positive number (got "${c?.weight}")`);
|
|
@@ -2597,8 +2599,9 @@ function parseOneRoute(c, where, file, base, stateDir, raw, missing, crewHome, t
|
|
|
2597
2599
|
enabled: c?.enabled === true,
|
|
2598
2600
|
area: c?.area,
|
|
2599
2601
|
dir,
|
|
2602
|
+
...dirIsCloneBase ? { dirIsCloneBase: true } : {},
|
|
2600
2603
|
repos: repoDirs,
|
|
2601
|
-
reposBasePath
|
|
2604
|
+
reposBasePath,
|
|
2602
2605
|
repoOverrides,
|
|
2603
2606
|
worktreePrefix: c?.worktreePrefix ? String(c.worktreePrefix) : void 0,
|
|
2604
2607
|
...toolchain !== void 0 ? { toolchain } : {},
|
|
@@ -2656,7 +2659,7 @@ function dirForRepo(r, repoId) {
|
|
|
2656
2659
|
}
|
|
2657
2660
|
function repoTargetFor(r, repoId) {
|
|
2658
2661
|
const names = r.resolved?.repoNames;
|
|
2659
|
-
if (!repoId || !names) return Object.keys(r.repos).length ? null : { name: basename3(r.dir), dir: r.dir };
|
|
2662
|
+
if (!repoId || !names) return Object.keys(r.repos).length || r.dirIsCloneBase ? null : { name: basename3(r.dir), dir: r.dir };
|
|
2660
2663
|
const name = names[repoId];
|
|
2661
2664
|
if (!name) return null;
|
|
2662
2665
|
return reposOf(r).find((t) => t.name === name) ?? null;
|
|
@@ -2747,6 +2750,15 @@ function reposOf(r) {
|
|
|
2747
2750
|
const named = Object.entries(r.repos);
|
|
2748
2751
|
if (named.length > 0) return named.map(([name, dir]) => ({ name, dir }));
|
|
2749
2752
|
const trackerNames = r.resolved?.repoNames ? Object.values(r.resolved.repoNames) : [];
|
|
2753
|
+
if (r.dirIsCloneBase) {
|
|
2754
|
+
const [workspace2] = splitRoute(r.route);
|
|
2755
|
+
const remotes2 = r.resolved?.repoRemotes ?? {};
|
|
2756
|
+
return Object.entries(r.resolved?.repoNames ?? {}).map(([id, name]) => ({
|
|
2757
|
+
name,
|
|
2758
|
+
dir: defaultRepoDir(r.reposBasePath, workspace2, name),
|
|
2759
|
+
remote: remotes2[id]
|
|
2760
|
+
}));
|
|
2761
|
+
}
|
|
2750
2762
|
if (trackerNames.length <= 1) return [{ name: basename3(r.dir), dir: r.dir }];
|
|
2751
2763
|
const remotes = r.resolved?.repoRemotes ?? {};
|
|
2752
2764
|
const remoteByName = {};
|
|
@@ -8632,6 +8644,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
8632
8644
|
"key",
|
|
8633
8645
|
"level",
|
|
8634
8646
|
"limit",
|
|
8647
|
+
"machine-id",
|
|
8635
8648
|
"max-roles",
|
|
8636
8649
|
"mcp",
|
|
8637
8650
|
"member",
|
|
@@ -8644,6 +8657,7 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
|
8644
8657
|
"ship-name",
|
|
8645
8658
|
"tail",
|
|
8646
8659
|
"ticket",
|
|
8660
|
+
"toolchain",
|
|
8647
8661
|
"workspace",
|
|
8648
8662
|
"workspace-id",
|
|
8649
8663
|
"write"
|
|
@@ -13399,7 +13413,7 @@ function renderConnection(d, route, dir, opts = {}) {
|
|
|
13399
13413
|
return ` - route: ${route}
|
|
13400
13414
|
enabled: false # arm it deliberately, once doctor is green
|
|
13401
13415
|
${opts.area ? ` area: "${opts.area}"
|
|
13402
|
-
` : ""} dir: "${dir}"
|
|
13416
|
+
` : ""} ${dir === null ? "repos: {} # `crew repos clone` / `crew repos add` fill this in" : `dir: "${dir}"`}
|
|
13403
13417
|
# worktreePrefix: omitted \u2014 each repo derives its own from its checkout's
|
|
13404
13418
|
# directory name (ISSUE-400). Only set this if every repo this route
|
|
13405
13419
|
# serves genuinely wants the SAME prefix, which is rare once an area has
|
|
@@ -13944,6 +13958,15 @@ function configuredRepos(text, route, base) {
|
|
|
13944
13958
|
const dir = m.get("dir");
|
|
13945
13959
|
return dir ? [{ name: basename8(String(dir)), dir: expandDir(String(dir), base) }] : [];
|
|
13946
13960
|
}
|
|
13961
|
+
function cloneBaseOf(text, route, base, isCheckout) {
|
|
13962
|
+
const m = routeMapOf(parseDocument2(text), route);
|
|
13963
|
+
const repos = m.get("repos", true);
|
|
13964
|
+
if (isMap(repos) && repos.items.length > 0) return null;
|
|
13965
|
+
const dir = m.get("dir");
|
|
13966
|
+
if (!dir) return null;
|
|
13967
|
+
const abs = expandDir(String(dir), base);
|
|
13968
|
+
return isCheckout(abs) ? null : abs;
|
|
13969
|
+
}
|
|
13947
13970
|
function routeSetsHooks(text, route, name) {
|
|
13948
13971
|
const m = routeMapOf(parseDocument2(text), route);
|
|
13949
13972
|
const repos = m.get("repos", true);
|
|
@@ -13954,7 +13977,8 @@ function routeSetsHooks(text, route, name) {
|
|
|
13954
13977
|
return isMap(hooks) && hooks.items.length > 0;
|
|
13955
13978
|
}
|
|
13956
13979
|
function listRepoRows(i) {
|
|
13957
|
-
const
|
|
13980
|
+
const cloneBase = i.isCheckout ? cloneBaseOf(i.text, i.route, i.base, i.isCheckout) : null;
|
|
13981
|
+
const checkouts = configuredRepos(i.text, i.route, i.base).filter((c) => c.dir !== cloneBase);
|
|
13958
13982
|
const claimed = /* @__PURE__ */ new Set();
|
|
13959
13983
|
const hooksSourceOf = (c) => i.exists(c.dir) && i.repoHasHooks(c.dir) ? "repo" : routeSetsHooks(i.text, i.route, c.name) ? "route" : "none";
|
|
13960
13984
|
const originFor = (c, remote) => {
|
|
@@ -14004,8 +14028,19 @@ function planRepoAdd(i) {
|
|
|
14004
14028
|
const doc = parseDocument2(i.text);
|
|
14005
14029
|
if (doc.errors.length > 0) throw new ReposError(`config is not valid YAML \u2014 ${doc.errors[0].message}`);
|
|
14006
14030
|
const m = routeMapOf(doc, i.route);
|
|
14007
|
-
|
|
14031
|
+
let existing = configuredRepos(i.text, i.route, i.base);
|
|
14008
14032
|
const target = expandDir(i.path, i.base);
|
|
14033
|
+
let droppedDir;
|
|
14034
|
+
const placeholder = m.get("dir");
|
|
14035
|
+
const reposNow = m.get("repos", true);
|
|
14036
|
+
if (i.isCheckout && placeholder && !(isMap(reposNow) && reposNow.items.length > 0)) {
|
|
14037
|
+
const placeholderDir = expandDir(String(placeholder), i.base);
|
|
14038
|
+
if (placeholderDir !== target && !i.isCheckout(placeholderDir)) {
|
|
14039
|
+
droppedDir = placeholderDir;
|
|
14040
|
+
m.delete("dir");
|
|
14041
|
+
existing = [];
|
|
14042
|
+
}
|
|
14043
|
+
}
|
|
14009
14044
|
if (existing.some((e) => e.dir === target)) {
|
|
14010
14045
|
throw new ReposError(`${target} is already configured on route ${i.route}`);
|
|
14011
14046
|
}
|
|
@@ -14031,11 +14066,14 @@ function planRepoAdd(i) {
|
|
|
14031
14066
|
pair.key = doc.createNode("repos");
|
|
14032
14067
|
pair.value = doc.createNode({ [oldName]: oldDir, [name]: target });
|
|
14033
14068
|
mode = "repos";
|
|
14069
|
+
} else if (droppedDir) {
|
|
14070
|
+
m.set("repos", doc.createNode({ [name]: target }));
|
|
14071
|
+
mode = "repos";
|
|
14034
14072
|
} else {
|
|
14035
14073
|
m.set("dir", target);
|
|
14036
14074
|
mode = "dir";
|
|
14037
14075
|
}
|
|
14038
|
-
return { text: String(doc), mode, name, block: renderBlock(m), warning, unmatched: !matched };
|
|
14076
|
+
return { text: String(doc), mode, name, block: renderBlock(m), warning, unmatched: !matched, droppedDir };
|
|
14039
14077
|
}
|
|
14040
14078
|
function planRepoRemove(text, base, route, path) {
|
|
14041
14079
|
const doc = parseDocument2(text);
|
|
@@ -14125,7 +14163,7 @@ function planRepoClone(i) {
|
|
|
14125
14163
|
const ship = doc.get("ship", true);
|
|
14126
14164
|
const basePath = String(m.get("reposBasePath") ?? (isMap(ship) ? ship.get("reposBasePath") : void 0) ?? "~/Crew");
|
|
14127
14165
|
const target = i.dir ? expandDir(i.dir, i.base) : join26(expandDir(basePath, i.base), i.route.split("/")[0], name);
|
|
14128
|
-
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name });
|
|
14166
|
+
const add = planRepoAdd({ text: i.text, base: i.base, route: i.route, path: target, origin: remote, resolved: i.resolved, name, isCheckout: i.isCheckout });
|
|
14129
14167
|
return { ...add, remote, target };
|
|
14130
14168
|
}
|
|
14131
14169
|
|
|
@@ -18416,6 +18454,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18416
18454
|
const originOf = (dir) => gitOk(dir, ["remote", "get-url", "origin"]) ?? void 0;
|
|
18417
18455
|
const text = configText;
|
|
18418
18456
|
const base = dirname20(cfg.configFile);
|
|
18457
|
+
const isCheckout = (d) => existsSync34(`${d}/.git`);
|
|
18419
18458
|
try {
|
|
18420
18459
|
if (sub === "list") {
|
|
18421
18460
|
if (flag("json")) {
|
|
@@ -18426,6 +18465,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18426
18465
|
resolved: resolvedRemotes,
|
|
18427
18466
|
originOf,
|
|
18428
18467
|
exists: (d) => existsSync34(d),
|
|
18468
|
+
isCheckout,
|
|
18429
18469
|
repoHasHooks: (d) => REPO_CONFIG_FILENAMES.some((f) => {
|
|
18430
18470
|
try {
|
|
18431
18471
|
return Object.keys(parseRepoConfig(readFileSync28(resolvePath(d, f), "utf8"), f).hooks).length > 0;
|
|
@@ -18434,11 +18474,12 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18434
18474
|
}
|
|
18435
18475
|
})
|
|
18436
18476
|
});
|
|
18437
|
-
process.stdout.write(`${JSON.stringify({ route: routeName, repos: rows })}
|
|
18477
|
+
process.stdout.write(`${JSON.stringify({ route: routeName, repos: rows, cloneBase: cloneBaseOf(text, routeName, base, isCheckout) })}
|
|
18438
18478
|
`);
|
|
18439
18479
|
break;
|
|
18440
18480
|
}
|
|
18441
|
-
const
|
|
18481
|
+
const cloneBase = cloneBaseOf(text, routeName, base, isCheckout);
|
|
18482
|
+
const repos = configuredRepos(text, routeName, base).filter((r) => r.dir !== cloneBase);
|
|
18442
18483
|
if (repos.length === 0) process.stdout.write(`route ${routeName} has no checkout configured \u2014 \`crew repos add ${routeName} <path>\`
|
|
18443
18484
|
`);
|
|
18444
18485
|
for (const r of repos) {
|
|
@@ -18455,7 +18496,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18455
18496
|
const say = (event, extra, human) => process.stdout.write(json ? `${JSON.stringify({ event, ...extra })}
|
|
18456
18497
|
` : `${human}
|
|
18457
18498
|
`);
|
|
18458
|
-
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir") });
|
|
18499
|
+
const plan2 = planRepoClone({ text, base, route: routeName, name: addName, resolved: resolvedRemotes, dir: value("dir"), isCheckout });
|
|
18459
18500
|
if (existsSync34(plan2.target) && (existsSync34(`${plan2.target}/.git`) || readdirSync14(plan2.target).length > 0)) {
|
|
18460
18501
|
throw new ReposError(
|
|
18461
18502
|
existsSync34(`${plan2.target}/.git`) ? `${plan2.target} is already a checkout \u2014 attach it with \`crew repos add ${routeName} ${plan2.name} ${plan2.target}\`` : `${plan2.target} exists and is not empty \u2014 pass --dir to clone somewhere else`
|
|
@@ -18477,6 +18518,8 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18477
18518
|
crew.yaml was not changed`);
|
|
18478
18519
|
}
|
|
18479
18520
|
writeFileSync22(cfg.configFile, plan2.text);
|
|
18521
|
+
if (plan2.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan2.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
18522
|
+
`);
|
|
18480
18523
|
say("attached", { route: routeName, name: plan2.name, dir: plan2.target, mode: plan2.mode }, `cloned and attached ${plan2.target} to ${routeName} as ${plan2.mode === "dir" ? "dir" : `repos.${plan2.name}`} in ${cfg.configFile}`);
|
|
18481
18524
|
break;
|
|
18482
18525
|
}
|
|
@@ -18492,8 +18535,11 @@ ${changed2.map((c) => ` - ${c}
|
|
|
18492
18535
|
origin,
|
|
18493
18536
|
resolved: resolvedRemotes,
|
|
18494
18537
|
name: value("name") ?? addName,
|
|
18495
|
-
nameForExisting: (d) => matchRepoName(originOf(d), resolvedRemotes)
|
|
18538
|
+
nameForExisting: (d) => matchRepoName(originOf(d), resolvedRemotes),
|
|
18539
|
+
isCheckout
|
|
18496
18540
|
});
|
|
18541
|
+
if (plan.droppedDir) process.stderr.write(`crew repos: dropped dir: ${plan.droppedDir} from route ${routeName} \u2014 it is not a checkout (the clone base); checkouts now live under repos:
|
|
18542
|
+
`);
|
|
18497
18543
|
let rowTracker;
|
|
18498
18544
|
let rowPayload;
|
|
18499
18545
|
if (plan.unmatched && origin && !flag("no-create")) {
|
|
@@ -19110,11 +19156,11 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
19110
19156
|
const keyPersisted = keychainBacked || apiKeyFile !== void 0;
|
|
19111
19157
|
const wizard = firstRun;
|
|
19112
19158
|
const writeFirstRun = wizard !== void 0 && !dryRun && keyPersisted;
|
|
19113
|
-
const routeDir = value("dir")
|
|
19159
|
+
const routeDir = value("dir");
|
|
19114
19160
|
const routeBlock = renderConnection(
|
|
19115
19161
|
found,
|
|
19116
19162
|
route2,
|
|
19117
|
-
routeDir ?? "REPLACE \u2014 the local checkout this route works",
|
|
19163
|
+
routeDir ?? (writeFirstRun ? null : "REPLACE \u2014 the local checkout this route works"),
|
|
19118
19164
|
{
|
|
19119
19165
|
area: value("area"),
|
|
19120
19166
|
apiKeyFile,
|
package/package.json
CHANGED