@nanopm/cli 0.1.16 → 0.1.18
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 +100 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5369,9 +5369,10 @@ function stepsLeft(plan, done) {
|
|
|
5369
5369
|
function buildDir(home, jobId) {
|
|
5370
5370
|
return `${home.replace(/\/+$/, "")}/.cache/nanopm/builds/${jobId}`;
|
|
5371
5371
|
}
|
|
5372
|
-
function buildBranch(ref, title) {
|
|
5372
|
+
function buildBranch(ref, title, jobId) {
|
|
5373
5373
|
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "");
|
|
5374
|
-
|
|
5374
|
+
const own = jobId.replace(/[^a-z0-9]/gi, "").slice(0, 6).toLowerCase();
|
|
5375
|
+
return `nano/${ref.toLowerCase()}${slug ? `-${slug}` : ""}-${own}`;
|
|
5375
5376
|
}
|
|
5376
5377
|
var REGISTRIES = {
|
|
5377
5378
|
"package-lock.json": ["registry.npmjs.org"],
|
|
@@ -5420,7 +5421,20 @@ var isDecideForMe = (answer) => /^\s*decide(\s+for\s+me)?\s*\.?\s*$/i.test(answe
|
|
|
5420
5421
|
function answerForAgent(answer) {
|
|
5421
5422
|
return isDecideForMe(answer) ? DECIDE_FOR_ME : answer;
|
|
5422
5423
|
}
|
|
5423
|
-
var BUILD_HISTORY_KINDS = ["worktree", "plan", "step", "question", "answer", "paused", "pr", "handed"];
|
|
5424
|
+
var BUILD_HISTORY_KINDS = ["worktree", "plan", "step", "question", "answer", "paused", "pr", "handed", "throw", "thrown", "closed"];
|
|
5425
|
+
function originOf(job) {
|
|
5426
|
+
const named = (job.params ?? {}).origin;
|
|
5427
|
+
return typeof named === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(named) ? named : job.id;
|
|
5428
|
+
}
|
|
5429
|
+
function throwRefusal(runs, jobId) {
|
|
5430
|
+
if (runs.some((r) => ["queued", "running", "waiting"].includes(r.status))) return "This build is on its way again: stop it first.";
|
|
5431
|
+
const newest = [...runs].sort((x, y) => y.created_at.localeCompare(x.created_at))[0];
|
|
5432
|
+
return newest?.id === jobId ? null : "This build was continued since: refresh the page.";
|
|
5433
|
+
}
|
|
5434
|
+
async function buildRuns(client, projectId, origin) {
|
|
5435
|
+
const { data } = await client.from("jobs").select("id, status, created_at, assigned_daemon_id, requested_by").eq("project_id", projectId).eq("skill", BUILD_SKILL).or(`id.eq.${origin},params->>origin.eq.${origin}`);
|
|
5436
|
+
return data ?? [];
|
|
5437
|
+
}
|
|
5424
5438
|
var BUILD_CONFLICT = { resolve: "Let Nano resolve it", mine: "I\u2019ll do it in my terminal" };
|
|
5425
5439
|
var chose = (answer, option) => {
|
|
5426
5440
|
const plain = (s) => s.trim().toLowerCase().replace(/[’‘]/g, "'");
|
|
@@ -5462,6 +5476,11 @@ function buildHistory(events, at) {
|
|
|
5462
5476
|
if (last?.kind === "paused") return found({ why: "limit" });
|
|
5463
5477
|
return found({ why: "lost" });
|
|
5464
5478
|
}
|
|
5479
|
+
function parkedDue(events, now) {
|
|
5480
|
+
const stop2 = [...events].reverse().find((e) => e.kind === "paused" || e.kind === "question");
|
|
5481
|
+
if (stop2?.kind === "paused") return Date.parse(String(stop2.payload.resets_at ?? "")) <= now;
|
|
5482
|
+
return stop2?.kind === "question" && events.some((e) => e.kind === "answer" && e.seq === stop2.seq);
|
|
5483
|
+
}
|
|
5465
5484
|
function wasOf(params) {
|
|
5466
5485
|
return params?.was === "proposed" ? "proposed" : "accepted";
|
|
5467
5486
|
}
|
|
@@ -5570,6 +5589,15 @@ async function addWorktree(clone, dir, branch) {
|
|
|
5570
5589
|
async function removeWorktree(clone, dir) {
|
|
5571
5590
|
await git(clone, ["worktree", "remove", "--force", dir]).catch(() => "");
|
|
5572
5591
|
}
|
|
5592
|
+
async function deleteBranch(clone, branch) {
|
|
5593
|
+
return git(clone, ["branch", "-D", branch]).then(
|
|
5594
|
+
() => true,
|
|
5595
|
+
() => false
|
|
5596
|
+
);
|
|
5597
|
+
}
|
|
5598
|
+
async function branchExists(clone, branch) {
|
|
5599
|
+
return Boolean(await git(clone, ["branch", "--list", branch]).catch(() => ""));
|
|
5600
|
+
}
|
|
5573
5601
|
async function commonGitDir(cwd) {
|
|
5574
5602
|
return git(cwd, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
5575
5603
|
}
|
|
@@ -5635,11 +5663,12 @@ async function ghReady(cwd) {
|
|
|
5635
5663
|
return /ENOENT/.test(errorMessage(err)) ? "the GitHub CLI is not installed on this machine: brew install gh, then gh auth login" : "the GitHub CLI is not signed in on this machine: run gh auth login in your terminal";
|
|
5636
5664
|
}
|
|
5637
5665
|
}
|
|
5638
|
-
async function
|
|
5666
|
+
async function pullRequestState(cwd, number) {
|
|
5639
5667
|
try {
|
|
5640
|
-
|
|
5668
|
+
const { state, headRefName } = JSON.parse(await gh(cwd, ["pr", "view", String(number), "--json", "state,headRefName"]));
|
|
5669
|
+
return (state === "MERGED" || state === "CLOSED" || state === "OPEN") && typeof headRefName === "string" ? { state, head: headRefName } : null;
|
|
5641
5670
|
} catch {
|
|
5642
|
-
return
|
|
5671
|
+
return null;
|
|
5643
5672
|
}
|
|
5644
5673
|
}
|
|
5645
5674
|
async function openPullRequest(cwd, pr) {
|
|
@@ -11199,7 +11228,7 @@ async function prepareBuild(client, job, clone, adapter) {
|
|
|
11199
11228
|
await startSolution(client, job.project_id, solution.ref, job.id);
|
|
11200
11229
|
return { ok: true, build: { solution, clone, ...place, worktree: worktree2, network: await network(place.dir), history } };
|
|
11201
11230
|
}
|
|
11202
|
-
const branch = buildBranch(solution.ref, solution.title);
|
|
11231
|
+
const branch = buildBranch(solution.ref, solution.title, job.id);
|
|
11203
11232
|
await mkdir5(join13(dir, ".."), { recursive: true, mode: 448 });
|
|
11204
11233
|
const { base, fork, worktree } = await addWorktree(clone, dir, branch);
|
|
11205
11234
|
made = true;
|
|
@@ -11222,8 +11251,8 @@ async function runBuildJob(input) {
|
|
|
11222
11251
|
let said = history.report;
|
|
11223
11252
|
const stop2 = async (message, opts = { keep: true }) => {
|
|
11224
11253
|
await unstartSolution(client, job.project_id, solution.ref, was).catch(() => void 0);
|
|
11225
|
-
if (opts.keep
|
|
11226
|
-
else await
|
|
11254
|
+
if (!opts.keep) await removeWorktree(clone, dir);
|
|
11255
|
+
else if (!await merging(worktree)) await commitAll(worktree, `${solution.ref}: unfinished \u2014 ${solution.title}`).catch(() => null);
|
|
11227
11256
|
return { ...failed(opts.keep ? `${message}. What was done is on ${branch}, in ${dir}.` : message), usage };
|
|
11228
11257
|
};
|
|
11229
11258
|
const park = async (why) => {
|
|
@@ -11381,8 +11410,7 @@ function askOf(input) {
|
|
|
11381
11410
|
};
|
|
11382
11411
|
}
|
|
11383
11412
|
async function loadHistory(client, job) {
|
|
11384
|
-
const
|
|
11385
|
-
const origin = typeof named === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(named) ? named : job.id;
|
|
11413
|
+
const origin = originOf(job);
|
|
11386
11414
|
const { data: runs } = await client.from("jobs").select("id").eq("project_id", job.project_id).eq("skill", BUILD_SKILL).or(`id.eq.${origin},params->>origin.eq.${origin}`);
|
|
11387
11415
|
return loadBuildHistory(client, [.../* @__PURE__ */ new Set([origin, job.id, ...(runs ?? []).map((r) => r.id)])], { home: homedir4(), jobId: origin });
|
|
11388
11416
|
}
|
|
@@ -11456,37 +11484,71 @@ async function resumePausedBuilds(client, daemonId, now = Date.now()) {
|
|
|
11456
11484
|
const { data: parked } = await client.from("jobs").select("id").eq("skill", BUILD_SKILL).eq("status", "waiting").eq("assigned_daemon_id", daemonId);
|
|
11457
11485
|
const ids = (parked ?? []).map((j) => j.id);
|
|
11458
11486
|
if (!ids.length) return [];
|
|
11459
|
-
const { data: stops } = await client.from("job_events").select("job_id, kind, payload").in("job_id", ids).in("kind", ["paused", "question"]).order("id"
|
|
11460
|
-
const
|
|
11461
|
-
for (const e of stops ?? []) if (!last.has(e.job_id)) last.set(e.job_id, e);
|
|
11462
|
-
const due = ids.filter((id) => {
|
|
11463
|
-
const stop2 = last.get(id);
|
|
11464
|
-
return stop2?.kind === "paused" && Date.parse(String(stop2.payload?.resets_at ?? "")) <= now;
|
|
11465
|
-
});
|
|
11487
|
+
const { data: stops } = await client.from("job_events").select("job_id, kind, seq, payload").in("job_id", ids).in("kind", ["paused", "question", "answer"]).order("id");
|
|
11488
|
+
const due = ids.filter((id) => parkedDue((stops ?? []).filter((e) => e.job_id === id).map((e) => ({ ...e, payload: e.payload ?? {} })), now));
|
|
11466
11489
|
if (!due.length) return [];
|
|
11467
11490
|
const { data } = await client.from("jobs").update({ status: "queued", assigned_daemon_id: null }).in("id", due).eq("status", "waiting").select("id");
|
|
11468
11491
|
return (data ?? []).map((j) => j.id);
|
|
11469
11492
|
}
|
|
11470
|
-
async function
|
|
11471
|
-
const { data:
|
|
11493
|
+
async function throwAwayBuilds(client, daemonId, userId) {
|
|
11494
|
+
const { data: throws } = await client.from("job_events").select("job_id").eq("kind", "throw");
|
|
11495
|
+
if (!throws?.length) return [];
|
|
11496
|
+
const { data: marked } = await client.from("job_events").select("job_id").eq("kind", "thrown").in("job_id", throws.map((t) => t.job_id));
|
|
11497
|
+
const open = throws.map((t) => t.job_id).filter((id) => !marked?.some((m) => m.job_id === id));
|
|
11498
|
+
if (!open.length) return [];
|
|
11499
|
+
const { data: asked } = await client.from("jobs").select("id, project_id, params").in("id", open).eq("skill", BUILD_SKILL).eq("requested_by", userId).or(`assigned_daemon_id.eq.${daemonId},target_daemon_id.eq.${daemonId}`);
|
|
11500
|
+
if (!asked?.length) return [];
|
|
11501
|
+
const { data: clones } = await client.from("clones").select("project_id, repo_path").eq("daemon_id", daemonId);
|
|
11502
|
+
const thrown = [];
|
|
11503
|
+
for (const job of asked) {
|
|
11504
|
+
const runs = await buildRuns(client, job.project_id, originOf(job));
|
|
11505
|
+
const first = runs.find((r) => r.id === originOf(job));
|
|
11506
|
+
if (!first || first.assigned_daemon_id !== daemonId || first.requested_by !== userId || throwRefusal(runs, job.id)) continue;
|
|
11507
|
+
const { place } = await loadHistory(client, job);
|
|
11508
|
+
const clone = clones?.find((c) => c.project_id === job.project_id)?.repo_path;
|
|
11509
|
+
if (place && clone && existsSync5(clone)) {
|
|
11510
|
+
if (existsSync5(place.dir)) await removeWorktree(clone, place.dir);
|
|
11511
|
+
if (existsSync5(place.dir) || await branchExists(clone, place.branch) && !await deleteBranch(clone, place.branch)) continue;
|
|
11512
|
+
} else if (place) continue;
|
|
11513
|
+
await appendJobEvent(client, job.id, "thrown", {}, "nanopm");
|
|
11514
|
+
thrown.push(job.id);
|
|
11515
|
+
}
|
|
11516
|
+
return thrown;
|
|
11517
|
+
}
|
|
11518
|
+
async function settledBuilds(client, daemonId, userId) {
|
|
11519
|
+
const { data: building } = await client.from("solutions").select("id, ref, project_id").eq("status", "building").is("superseded_by", null);
|
|
11472
11520
|
if (!building?.length) return [];
|
|
11473
11521
|
const [{ data: built }, { data: clones }] = await Promise.all([
|
|
11474
|
-
client.from("jobs").select("id, params, pr:result->pr").eq("skill", BUILD_SKILL).eq("status", "succeeded").eq("assigned_daemon_id", daemonId).eq("requested_by", userId).in("params->>solution_id", building.map((s) => s.id)),
|
|
11522
|
+
client.from("jobs").select("id, project_id, params, created_at, pr:result->pr").eq("skill", BUILD_SKILL).eq("status", "succeeded").eq("assigned_daemon_id", daemonId).eq("requested_by", userId).in("params->>solution_id", building.map((s) => s.id)).order("created_at", { ascending: false }),
|
|
11475
11523
|
client.from("clones").select("project_id, repo_path").eq("daemon_id", daemonId)
|
|
11476
11524
|
]);
|
|
11525
|
+
const newest = [...new Map((built ?? []).reverse().map((j) => [j.params?.solution_id, j])).values()];
|
|
11526
|
+
const { data: closed } = newest.length ? await client.from("job_events").select("job_id").in("job_id", newest.map((j) => j.id)).eq("kind", "closed") : { data: [] };
|
|
11477
11527
|
const cloneOf = new Map((clones ?? []).map((c) => [c.project_id, c.repo_path]));
|
|
11478
|
-
const
|
|
11479
|
-
for (const j of
|
|
11528
|
+
const settled2 = [];
|
|
11529
|
+
for (const j of newest.filter((n) => !closed?.some((c) => c.job_id === n.id))) {
|
|
11480
11530
|
const solution = building.find((s) => s.id === j.params?.solution_id);
|
|
11481
11531
|
const number = j.pr?.number;
|
|
11482
11532
|
const clone = solution ? cloneOf.get(solution.project_id) : void 0;
|
|
11483
|
-
if (!solution || typeof number !== "number" || number <= 0 || !clone || !existsSync5(clone)
|
|
11484
|
-
const {
|
|
11485
|
-
if (
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11533
|
+
if (!solution || typeof number !== "number" || number <= 0 || !clone || !existsSync5(clone)) continue;
|
|
11534
|
+
const [{ place }, pr] = await Promise.all([loadHistory(client, j), pullRequestState(clone, number)]);
|
|
11535
|
+
if (!place || !pr || pr.head !== place.branch) continue;
|
|
11536
|
+
const at = { id: j.id, project_id: solution.project_id };
|
|
11537
|
+
if (pr.state === "MERGED") {
|
|
11538
|
+
const { error } = await client.rpc("finish_solution", { p_project_id: solution.project_id, p_ref: solution.ref, p_job_id: j.id });
|
|
11539
|
+
if (error) continue;
|
|
11540
|
+
await logActivity({ client }, at, "shipped", `Pull request #${number} merged: ${solution.ref} shipped`);
|
|
11541
|
+
settled2.push({ id: j.id, ref: solution.ref, merged: true });
|
|
11542
|
+
} else if (pr.state === "CLOSED") {
|
|
11543
|
+
const { error } = await client.rpc("unstart_solution", { p_project_id: solution.project_id, p_ref: solution.ref, p_to: wasOf(j.params) });
|
|
11544
|
+
if (error) continue;
|
|
11545
|
+
await deleteBranch(clone, place.branch);
|
|
11546
|
+
await appendJobEvent(client, j.id, "closed", { number }, "nanopm");
|
|
11547
|
+
await logActivity({ client }, at, "closed", `Pull request #${number} closed without merging: ${solution.ref} back on the roadmap`);
|
|
11548
|
+
settled2.push({ id: j.id, ref: solution.ref, merged: false });
|
|
11549
|
+
}
|
|
11550
|
+
}
|
|
11551
|
+
return settled2;
|
|
11490
11552
|
}
|
|
11491
11553
|
async function tellTheApp(accessToken, jobId, fetchImpl = fetch) {
|
|
11492
11554
|
try {
|
|
@@ -11645,11 +11707,12 @@ async function runDaemon(options = {}) {
|
|
|
11645
11707
|
if (options.once && Date.now() - startedAt > onceTimeoutMs) break;
|
|
11646
11708
|
if (!options.once && Date.now() - lastPauseCheck > PAUSE_CHECK_MS) {
|
|
11647
11709
|
lastPauseCheck = Date.now();
|
|
11648
|
-
for (const id of await resumePausedBuilds(auth.client, daemonId).catch(() => [])) log(`build ${id}:
|
|
11710
|
+
for (const id of await resumePausedBuilds(auth.client, daemonId).catch(() => [])) log(`build ${id}: carrying on (its limit reset, or its answer came)`);
|
|
11711
|
+
for (const id of await throwAwayBuilds(auth.client, daemonId, auth.userId).catch(() => [])) log(`build ${id}: thrown away, its copy and its local branch`);
|
|
11649
11712
|
}
|
|
11650
11713
|
if (!options.once && Date.now() - lastShipCheck > SHIP_CHECK_MS) {
|
|
11651
11714
|
lastShipCheck = Date.now();
|
|
11652
|
-
for (const
|
|
11715
|
+
for (const b of await settledBuilds(auth.client, daemonId, auth.userId).catch(() => [])) log(`build ${b.id}: its pull request ${b.merged ? `merged, ${b.ref} shipped` : `closed without merging, ${b.ref} back on the roadmap`}`);
|
|
11653
11716
|
}
|
|
11654
11717
|
if (!options.once && Date.now() - lastDeployCheck > DEPLOY_CHECK_MS) {
|
|
11655
11718
|
lastDeployCheck = Date.now();
|
|
@@ -14604,7 +14667,7 @@ import { promisify as promisify8 } from "util";
|
|
|
14604
14667
|
// package.json
|
|
14605
14668
|
var package_default = {
|
|
14606
14669
|
name: "@nanopm/cli",
|
|
14607
|
-
version: "0.1.
|
|
14670
|
+
version: "0.1.18",
|
|
14608
14671
|
description: "An autonomous product manager for builders who run several small products. Runs on your machine, drives your own coding harness.",
|
|
14609
14672
|
type: "module",
|
|
14610
14673
|
license: "MIT",
|
|
@@ -16025,7 +16088,7 @@ async function build(ref, opts) {
|
|
|
16025
16088
|
}
|
|
16026
16089
|
const stopped = opts.continue || opts.throwAway ? await buildOf(auth.client, project.id, ["failed", "cancelled"], solution.id) : null;
|
|
16027
16090
|
if ((opts.continue || opts.throwAway) && !stopped) throw new Error(`${solution.ref} has no stopped build to ${opts.continue ? "continue" : "throw away"}.`);
|
|
16028
|
-
if (opts.throwAway) return throwAway(auth.client, stopped
|
|
16091
|
+
if (opts.throwAway) return throwAway(auth.client, stopped, repo.root, out);
|
|
16029
16092
|
const [daemon, local] = await Promise.all([awakeDaemon(auth.client, auth.userId), localDaemonId()]);
|
|
16030
16093
|
if (!local || daemon?.id !== local) throw new Error("This machine\u2019s daemon is asleep, so nothing was queued. `nanopm daemon install` starts it; then build again.");
|
|
16031
16094
|
await rememberClone(auth.client, project.id, repo.root);
|
|
@@ -16047,11 +16110,12 @@ async function buildOf(client, projectId, statuses, solutionId) {
|
|
|
16047
16110
|
const params = data?.params ?? {};
|
|
16048
16111
|
return data ? { id: data.id, solutionId: params.solution_id ?? "", was: wasOf(data.params), origin: params.origin ?? data.id } : null;
|
|
16049
16112
|
}
|
|
16050
|
-
async function throwAway(client,
|
|
16051
|
-
const { place } = await loadBuildHistory(client, [
|
|
16113
|
+
async function throwAway(client, build2, clone, out) {
|
|
16114
|
+
const { place } = await loadBuildHistory(client, [build2.origin], { home: homedir6(), jobId: build2.origin });
|
|
16052
16115
|
if (!place) throw new Error("that build left nothing on this machine.");
|
|
16053
16116
|
if (existsSync7(place.dir)) await execFileAsync9("git", ["-C", clone, "worktree", "remove", "--force", place.dir]);
|
|
16054
16117
|
await execFileAsync9("git", ["-C", clone, "branch", "-D", place.branch]).catch(() => void 0);
|
|
16118
|
+
await appendJobEvent(client, build2.id, "thrown", {}, "user");
|
|
16055
16119
|
out(`Thrown away: the copy of the code and the local branch ${place.branch}.`);
|
|
16056
16120
|
return 0;
|
|
16057
16121
|
}
|
package/package.json
CHANGED