fleetfizz 0.5.0 → 0.5.1

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/fleetfizz.mjs +914 -583
  2. package/package.json +1 -1
@@ -6917,6 +6917,11 @@ const ignore$1 = /*#__PURE__*/ dual((args) => isEffect$1(args[0]), (self, option
6917
6917
  });
6918
6918
  });
6919
6919
  /** @internal */
6920
+ const option$1 = (self) => match$1(self, {
6921
+ onFailure: none,
6922
+ onSuccess: some
6923
+ });
6924
+ /** @internal */
6920
6925
  const result$1 = (self) => matchEager(self, {
6921
6926
  onFailure: fail$5,
6922
6927
  onSuccess: succeed$7
@@ -11094,6 +11099,49 @@ const tap = tap$1;
11094
11099
  */
11095
11100
  const result = result$1;
11096
11101
  /**
11102
+ * Converts success to `Option.some` and failure to `Option.none`.
11103
+ *
11104
+ * **When to use**
11105
+ *
11106
+ * Use when you only care whether an effect succeeds and want recoverable
11107
+ * failures represented as `Option.none`.
11108
+ *
11109
+ * **Details**
11110
+ *
11111
+ * Success values become `Option.some`, recoverable failures become
11112
+ * `Option.none`, and defects still fail the effect.
11113
+ *
11114
+ * **Gotchas**
11115
+ *
11116
+ * `option` only captures typed, recoverable failures as `Option.none`.
11117
+ * Defects and interruptions are not captured inside the `Option` and still
11118
+ * fail the effect.
11119
+ *
11120
+ * `option` also discards typed failure values. Use `result` if the failure
11121
+ * value matters.
11122
+ *
11123
+ * **Example** (Capturing success or failure as Option)
11124
+ *
11125
+ * ```ts import.meta.vitest
11126
+ * import { Effect, Option } from "effect"
11127
+ *
11128
+ * const program = Effect.all([
11129
+ * Effect.option(Effect.succeed(1)),
11130
+ * Effect.option(Effect.fail("missing"))
11131
+ * ])
11132
+ *
11133
+ * Effect.runSync(program) // => [Option.some(1), Option.none()]
11134
+ * ```
11135
+ *
11136
+ * @see {@link result} for a version that uses `Result` instead.
11137
+ * @see {@link exit} for a version that encapsulates both recoverable errors and defects in an `Exit`.
11138
+ *
11139
+ * @stability stable
11140
+ * @category error handling
11141
+ * @since 2.0.0
11142
+ */
11143
+ const option = option$1;
11144
+ /**
11097
11145
  * Transforms an effect to encapsulate both failure and success using the `Exit`
11098
11146
  * data type.
11099
11147
  *
@@ -29233,7 +29281,7 @@ const visualLength = (text) => {
29233
29281
  * Helper function to pad strings to a specified width.
29234
29282
  * @internal
29235
29283
  */
29236
- const pad$1 = (s, width) => {
29284
+ const pad = (s, width) => {
29237
29285
  const actualLength = visualLength(s);
29238
29286
  const padding = Math.max(0, width - actualLength);
29239
29287
  return s + " ".repeat(padding);
@@ -29245,7 +29293,7 @@ const pad$1 = (s, width) => {
29245
29293
  const renderTable = (rows, widthCap) => {
29246
29294
  const maxColumn = Math.max(...rows.map((r) => visualLength(r.left))) + 4;
29247
29295
  const col = widthCap === void 0 ? maxColumn : Math.min(maxColumn, widthCap);
29248
- return rows.map(({ left, right }) => ` ${pad$1(left, Math.max(col, visualLength(left) + 1))}${right}`).join("\n");
29296
+ return rows.map(({ left, right }) => ` ${pad(left, Math.max(col, visualLength(left) + 1))}${right}`).join("\n");
29249
29297
  };
29250
29298
  const formatSubcommandName = (name, alias) => alias ? `${name}, ${alias}` : name;
29251
29299
  /**
@@ -32392,7 +32440,7 @@ const runWith = (command, config) => {
32392
32440
  };
32393
32441
  //#endregion
32394
32442
  //#region package.json
32395
- var version = "0.5.0";
32443
+ var version = "0.5.1";
32396
32444
  //#endregion
32397
32445
  //#region ../../node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
32398
32446
  /**
@@ -36039,546 +36087,6 @@ const resolveVersions = fn("resolveVersions")(function* (packages) {
36039
36087
  return Object.fromEntries(entries);
36040
36088
  });
36041
36089
  //#endregion
36042
- //#region src/ledger.ts
36043
- const Note = Struct({
36044
- kind: Literals$2([
36045
- "ask",
36046
- "answer",
36047
- "info"
36048
- ]),
36049
- text: String$5
36050
- });
36051
- const Item = Struct({
36052
- name: String$5,
36053
- kind: Literals$2([
36054
- "version",
36055
- "repo",
36056
- "config"
36057
- ]),
36058
- from: optionalKey(String$5),
36059
- to: optionalKey(String$5),
36060
- reason: optionalKey(String$5),
36061
- changes: Boolean$4,
36062
- status: Literals$2([
36063
- "idle",
36064
- "running",
36065
- "ok",
36066
- "failed",
36067
- "skipped"
36068
- ]),
36069
- doing: optionalKey(String$5),
36070
- detail: optionalKey(String$5),
36071
- output: optionalKey(ArraySchema(String$5)),
36072
- steps: ArraySchema(String$5),
36073
- started: optionalKey(Number$2),
36074
- ended: optionalKey(Number$2)
36075
- });
36076
- const MachineEntry = Struct({
36077
- name: String$5,
36078
- status: Literals$2([
36079
- "gathering",
36080
- "gathered",
36081
- "planned",
36082
- "running",
36083
- "ok",
36084
- "skipped",
36085
- "failed"
36086
- ]),
36087
- detail: optionalKey(String$5),
36088
- output: optionalKey(ArraySchema(String$5)),
36089
- items: ArraySchema(Item),
36090
- started: optionalKey(Number$2),
36091
- ended: optionalKey(Number$2)
36092
- });
36093
- const LedgerState = Struct({
36094
- from: String$5,
36095
- dryRun: Boolean$4,
36096
- notes: ArraySchema(Note),
36097
- machines: ArraySchema(MachineEntry),
36098
- started: Number$2,
36099
- ended: optionalKey(Number$2)
36100
- });
36101
- const short = (sha) => sha === void 0 ? void 0 : sha.slice(0, 7);
36102
- const FETCH = ":fetch";
36103
- const doingOf = (id) => id === "config" ? "copying" : id.endsWith(FETCH) ? "fetching" : "installing";
36104
- const itemsOf = (p, state) => {
36105
- const items = /* @__PURE__ */ new Map();
36106
- const key = (kind, name) => `${kind}:${name}`;
36107
- const base = (name, from, kind = "version") => ({
36108
- name,
36109
- kind,
36110
- ...from === void 0 ? {} : { from },
36111
- changes: false,
36112
- status: "idle",
36113
- steps: []
36114
- });
36115
- const set = (name, change, kind = "version") => items.set(key(kind, name), change(items.get(key(kind, name)) ?? base(name, void 0, kind)));
36116
- const plans = (id, to, from, reason) => ({ from: gathered, ...item }) => {
36117
- const now = from === void 0 ? gathered : from ?? void 0;
36118
- return {
36119
- ...item,
36120
- ...now === void 0 ? {} : { from: now },
36121
- to,
36122
- ...reason === void 0 ? {} : { reason },
36123
- changes: true,
36124
- steps: [...item.steps, id]
36125
- };
36126
- };
36127
- const vp = state.vp?.version ?? void 0;
36128
- items.set(key("version", "vp"), base("vp", vp));
36129
- if (state.node != null) items.set(key("version", "node"), base("node", state.node));
36130
- for (const g of state.globals) items.set(key("version", g.name), base(g.name, g.version));
36131
- for (const step of p.vp) {
36132
- const id = vpStepId(step);
36133
- switch (step.kind) {
36134
- case "bootstrap":
36135
- set("vp", plans(id, "latest", null));
36136
- break;
36137
- case "vp":
36138
- case "node":
36139
- set(step.kind, plans(id, step.version, step.from));
36140
- break;
36141
- case "reinstall":
36142
- set(step.name, plans(id, step.version, step.version, "reinstall"));
36143
- break;
36144
- case "globals": for (const pkg of step.packages) set(pkg.name, plans(id, pkg.version, pkg.from ?? null));
36145
- }
36146
- }
36147
- for (const repo of p.repos) {
36148
- const from = short(state.records[repo.name]?.commit);
36149
- const to = short(repo.head);
36150
- items.set(key("repo", repo.name), {
36151
- name: repo.name,
36152
- kind: "repo",
36153
- ...from === void 0 ? {} : { from },
36154
- ...to === void 0 ? {} : { to },
36155
- ...repo.decision.run ? { reason: repo.decision.reason } : {},
36156
- changes: repo.decision.run,
36157
- status: "idle",
36158
- steps: [stepId(repo.name, "fetch"), stepId(repo.name, "install")]
36159
- });
36160
- }
36161
- for (const s of p.skipped) set(s.what, (i) => ({
36162
- ...i,
36163
- status: "skipped",
36164
- detail: s.reason
36165
- }));
36166
- for (const f of p.failures) {
36167
- const kind = f.section === "repos" ? "repo" : "version";
36168
- set(f.what, (i) => ({
36169
- ...i,
36170
- changes: true,
36171
- status: "failed",
36172
- detail: f.reason
36173
- }), kind);
36174
- }
36175
- items.set(key("config", CONFIG_STEP), {
36176
- name: CONFIG_STEP,
36177
- kind: "config",
36178
- changes: false,
36179
- status: "idle",
36180
- steps: [CONFIG_STEP]
36181
- });
36182
- return [...items.values()];
36183
- };
36184
- const outcomeOf = (m, dryRun) => m.status === "skipped" ? "skipped" : m.status === "failed" || m.items.some((i) => i.status === "failed") ? "failed" : m.status === "ok" || m.status === "planned" && dryRun ? "ok" : void 0;
36185
- const tally = (state) => {
36186
- const outcomes = state.machines.map((m) => outcomeOf(m, state.dryRun));
36187
- const n = (o) => outcomes.filter((x) => x === o).length;
36188
- return {
36189
- ok: n("ok"),
36190
- skipped: n("skipped"),
36191
- failed: n("failed")
36192
- };
36193
- };
36194
- const makeLedger = fn("makeLedger")(function* (input) {
36195
- const started = yield* currentTimeMillis;
36196
- let snapshot = {
36197
- state: {
36198
- from: input.from,
36199
- dryRun: input.dryRun,
36200
- notes: [],
36201
- machines: input.machines.map((name) => ({
36202
- name,
36203
- status: "gathering",
36204
- items: []
36205
- })),
36206
- started
36207
- },
36208
- now: started
36209
- };
36210
- const listeners = /* @__PURE__ */ new Set();
36211
- const update = (change) => gen(function* () {
36212
- const now = yield* currentTimeMillis;
36213
- snapshot = {
36214
- state: change(snapshot.state, now),
36215
- now
36216
- };
36217
- for (const listener of listeners) listener();
36218
- });
36219
- const machine = (name, change) => update((state, now) => ({
36220
- ...state,
36221
- machines: state.machines.map((m) => m.name === name ? change(m, now) : m)
36222
- }));
36223
- const items = (name, id, change) => machine(name, (m, now) => ({
36224
- ...m,
36225
- items: m.items.map((i) => i.steps.includes(id) ? change(i, now) : i)
36226
- }));
36227
- const store = {
36228
- get: () => snapshot,
36229
- subscribe: (listener) => {
36230
- listeners.add(listener);
36231
- return () => listeners.delete(listener);
36232
- },
36233
- tick: update((state) => state)
36234
- };
36235
- return {
36236
- ledger: {
36237
- note: fn("ledger.note")(function* (kind, text) {
36238
- yield* update((state) => ({
36239
- ...state,
36240
- notes: [...state.notes, {
36241
- kind,
36242
- text
36243
- }]
36244
- }));
36245
- yield* log(noteText({
36246
- kind,
36247
- text
36248
- }, styled()));
36249
- }),
36250
- gathered: (name) => machine(name, (m) => ({
36251
- ...m,
36252
- status: "gathered"
36253
- })),
36254
- skip: (name, reason) => machine(name, (m) => ({
36255
- ...m,
36256
- status: "skipped",
36257
- detail: reason
36258
- })),
36259
- fail: (name, [reason = "failed", ...output]) => machine(name, (m, now) => ({
36260
- ...m,
36261
- status: "failed",
36262
- detail: reason,
36263
- ...output.length === 0 ? {} : { output },
36264
- ended: now
36265
- })),
36266
- plan: (name, p, state) => machine(name, (m) => ({
36267
- ...m,
36268
- status: "planned",
36269
- items: itemsOf(p, state)
36270
- })),
36271
- start: (name) => machine(name, (m, now) => ({
36272
- ...m,
36273
- status: "running",
36274
- started: now
36275
- })),
36276
- stepStarted: (name, id) => items(name, id, (item, now) => item.status === "failed" ? item : {
36277
- ...item,
36278
- changes: item.changes || !(id === "config" || id.endsWith(FETCH)),
36279
- status: "running",
36280
- doing: doingOf(id),
36281
- started: item.started ?? now
36282
- }),
36283
- stepDone: (name, id, result) => items(name, id, (item, now) => {
36284
- if (item.status === "failed") return item;
36285
- if (!result.ok) return {
36286
- ...item,
36287
- changes: true,
36288
- status: "failed",
36289
- detail: result.detail,
36290
- ...result.output.length === 0 ? {} : { output: result.output },
36291
- ended: now
36292
- };
36293
- const { doing: _, ...rest } = item;
36294
- if (id.endsWith(FETCH)) return item.changes ? item : {
36295
- ...rest,
36296
- status: "idle",
36297
- ended: now
36298
- };
36299
- return {
36300
- ...rest,
36301
- status: "ok",
36302
- ended: now
36303
- };
36304
- }),
36305
- fetched: (name, repo, head) => machine(name, (m) => ({
36306
- ...m,
36307
- items: m.items.map((i) => i.kind === "repo" && i.name === repo ? {
36308
- ...i,
36309
- to: head.slice(0, 7),
36310
- changes: i.changes || head.slice(0, 7) !== i.from
36311
- } : i)
36312
- })),
36313
- finish: (name, outcome) => machine(name, (m, now) => ({
36314
- ...m,
36315
- status: m.status === "failed" ? "failed" : outcome,
36316
- ended: m.ended ?? now
36317
- })),
36318
- end: gen(function* () {
36319
- yield* update((state, now) => ({
36320
- ...state,
36321
- ended: now,
36322
- machines: state.machines.map((m) => m.status === "running" || m.status === "gathering" ? {
36323
- ...m,
36324
- status: "failed",
36325
- detail: "stopped",
36326
- ended: now,
36327
- items: m.items.map((i) => i.status === "running" ? {
36328
- ...i,
36329
- status: "failed",
36330
- detail: "stopped",
36331
- ended: now
36332
- } : i)
36333
- } : m)
36334
- }));
36335
- return snapshot.state;
36336
- })
36337
- },
36338
- store
36339
- };
36340
- });
36341
- const duration = (ms) => {
36342
- const s = ms / 1e3;
36343
- if (s < 60) return `${s.toFixed(1)}s`;
36344
- const m = Math.floor(s / 60);
36345
- if (m < 60) return `${m}m ${String(Math.floor(s % 60)).padStart(2, "0")}s`;
36346
- return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, "0")}m`;
36347
- };
36348
- const value = (i) => i.to === void 0 ? i.from ?? "" : i.from === void 0 ? `+ ${i.to}` : i.from === i.to ? i.to : `${i.from} -> ${i.to}`;
36349
- const note = (i) => {
36350
- switch (i.status) {
36351
- case "running": return {
36352
- text: i.doing ?? "running",
36353
- style: "run"
36354
- };
36355
- case "failed": return {
36356
- text: "FAIL",
36357
- style: "fail"
36358
- };
36359
- case "skipped": return {
36360
- text: "SKIP",
36361
- style: "skip"
36362
- };
36363
- case "ok":
36364
- if (i.kind === "config") return {
36365
- text: "copied",
36366
- style: "dim"
36367
- };
36368
- if (i.kind === "repo") return i.from !== void 0 && i.from === i.to ? {
36369
- text: `rebuilt${i.reason === void 0 ? "" : `: ${i.reason}`}`,
36370
- style: "ok"
36371
- } : {
36372
- text: "installed",
36373
- style: "ok"
36374
- };
36375
- return {
36376
- text: i.reason ?? "done",
36377
- style: "ok"
36378
- };
36379
- case "idle":
36380
- if (i.changes) return {
36381
- text: i.reason ?? "",
36382
- style: "dim"
36383
- };
36384
- return {
36385
- text: i.kind === "repo" ? "up to date" : "",
36386
- style: "dim"
36387
- };
36388
- }
36389
- };
36390
- const elapsed = (started, ended, now) => started === void 0 ? "" : duration((ended ?? now) - started);
36391
- const LIVE_OUTPUT = 12;
36392
- const outputRows = (output, indent, running) => {
36393
- const cut = running && output.length > LIVE_OUTPUT;
36394
- const shown = cut ? output.slice(-12) : output;
36395
- return [...cut ? [[{
36396
- text: `${indent}(${output.length - LIVE_OUTPUT} earlier lines)`,
36397
- style: "dim"
36398
- }]] : [], ...shown.map((line) => [{ text: `${indent}${line}` }])];
36399
- };
36400
- const pad = (text, width) => text.padEnd(width);
36401
- const rows = (state, options) => {
36402
- const { full, now, live } = options;
36403
- const running = live && state.ended === void 0;
36404
- const shownItems = (m) => m.items.filter((i) => full || i.changes || i.status === "failed" || i.status === "skipped");
36405
- const visible = state.machines.flatMap(shownItems);
36406
- const nameW = Math.max(0, ...state.machines.map((m) => m.name.length));
36407
- const itemW = Math.max(0, ...visible.map((i) => i.name.length));
36408
- const valueW = Math.max(0, ...visible.map((i) => value(i).length));
36409
- const noteW = Math.max(0, ...visible.map((i) => note(i).text.length));
36410
- const out = [[]];
36411
- for (const m of state.machines) {
36412
- const items = shownItems(m);
36413
- const upToDate = !m.items.some((i) => i.changes);
36414
- const head = [{
36415
- text: pad(m.name, nameW),
36416
- style: "bold"
36417
- }, { text: " " }];
36418
- const time = {
36419
- text: elapsed(m.started, m.ended, now),
36420
- style: "dim"
36421
- };
36422
- switch (m.status) {
36423
- case "gathering":
36424
- case "gathered":
36425
- head.push({ text: " " }, {
36426
- text: m.status,
36427
- style: "dim"
36428
- });
36429
- break;
36430
- case "planned":
36431
- if (m.items.some((i) => i.status === "failed")) head.push({
36432
- text: "FAIL",
36433
- style: "fail"
36434
- });
36435
- else head.push({ text: " " }, {
36436
- text: upToDate ? "up to date" : "",
36437
- style: "dim"
36438
- });
36439
- break;
36440
- case "running":
36441
- head.push({
36442
- text: "RUN ",
36443
- style: "run"
36444
- }, { text: " " }, time);
36445
- break;
36446
- case "ok":
36447
- head.push({
36448
- text: "OK ",
36449
- style: "ok"
36450
- }, { text: " " });
36451
- head.push(upToDate ? {
36452
- text: "up to date",
36453
- style: "dim"
36454
- } : time);
36455
- break;
36456
- case "skipped":
36457
- head.push({
36458
- text: "SKIP",
36459
- style: "skip"
36460
- }, { text: " " }, { text: m.detail ?? "" });
36461
- break;
36462
- case "failed":
36463
- head.push({
36464
- text: "FAIL",
36465
- style: "fail"
36466
- }, { text: " " });
36467
- head.push(m.detail === void 0 ? time : { text: m.detail });
36468
- }
36469
- out.push(head);
36470
- for (const i of items) {
36471
- const n = note(i);
36472
- const t = elapsed(i.started, i.ended, now);
36473
- const idle = i.status === "idle" && !i.changes;
36474
- out.push([
36475
- {
36476
- text: ` ${pad(i.name, itemW)} `,
36477
- ...idle ? { style: "dim" } : {}
36478
- },
36479
- {
36480
- text: pad(value(i), valueW),
36481
- ...idle ? { style: "dim" } : {}
36482
- },
36483
- { text: " " },
36484
- {
36485
- text: pad(n.text, noteW),
36486
- ...n.style === void 0 ? {} : { style: n.style }
36487
- },
36488
- {
36489
- text: t === "" ? "" : ` ${t.padStart(6)}`,
36490
- style: "dim"
36491
- },
36492
- ...i.detail === void 0 ? [] : [{ text: ` ${i.detail}` }]
36493
- ]);
36494
- if (i.output !== void 0) out.push(...outputRows(i.output, " ", running));
36495
- }
36496
- if (m.output !== void 0) out.push(...outputRows(m.output, " ", running));
36497
- }
36498
- out.push([]);
36499
- if (state.ended !== void 0) {
36500
- const t = tally(state);
36501
- out.push([
36502
- {
36503
- text: `${state.dryRun ? "planned" : "ok"} ${t.ok}`,
36504
- ...t.failed === 0 ? { style: "ok" } : {}
36505
- },
36506
- { text: ` skipped ${t.skipped} ` },
36507
- {
36508
- text: `failed ${t.failed}`,
36509
- ...t.failed > 0 ? { style: "fail" } : {}
36510
- },
36511
- ...state.dryRun ? [] : [{
36512
- text: ` in ${duration(state.ended - state.started)}`,
36513
- style: "dim"
36514
- }]
36515
- ]);
36516
- }
36517
- if (live) {
36518
- const legend = state.ended === void 0 ? `f ${full ? "changes only" : "full list"} · ctrl-c stop` : state.dryRun ? void 0 : "full report: fizz report";
36519
- if (legend !== void 0) out.push([{
36520
- text: legend,
36521
- style: "dim"
36522
- }]);
36523
- }
36524
- while (out.length > 0 && out[out.length - 1]?.length === 0) out.pop();
36525
- return out;
36526
- };
36527
- const noteRow = (n) => n.kind === "ask" ? [{
36528
- text: "?",
36529
- style: "ask"
36530
- }, { text: ` ${n.text}` }] : n.kind === "answer" ? [{
36531
- text: ` -> ${n.text}`,
36532
- style: "dim"
36533
- }] : [{ text: n.text }];
36534
- const ANSI = {
36535
- dim: "\x1B[2m",
36536
- bold: "\x1B[1m",
36537
- ok: "\x1B[32m",
36538
- fail: "\x1B[1;31m",
36539
- skip: "\x1B[33m",
36540
- run: "\x1B[34m",
36541
- ask: "\x1B[1;35m"
36542
- };
36543
- const styled = () => Boolean(process.stdout.isTTY) && process.env.NO_COLOR === void 0;
36544
- const rowText = (row, color) => row.map((s) => color && s.style !== void 0 && s.text !== "" ? `${ANSI[s.style]}${s.text}\u001b[0m` : s.text).join("").trimEnd();
36545
- const noteText = (n, color) => rowText(noteRow(n), color);
36546
- const printRows = (list, color = styled()) => log(list.map((row) => rowText(row, color)).join("\n"));
36547
- //#endregion
36548
- //#region src/report.ts
36549
- const reportPath = () => `${STATE_DIR.replace(/^~/, process.env.HOME ?? "~")}/last-ship.json`;
36550
- const LedgerJson = fromJsonString(LedgerState);
36551
- const encode = encodeEffect(LedgerJson);
36552
- const decode = decodeEffect(LedgerJson);
36553
- const saveReport = fn("saveReport")(function* (state) {
36554
- const fs = yield* FileSystem;
36555
- const path = reportPath();
36556
- yield* fs.makeDirectory(path.slice(0, path.lastIndexOf("/")), { recursive: true });
36557
- yield* fs.writeFileString(path, yield* encode(state));
36558
- });
36559
- const report = fn("report")(function* () {
36560
- const fs = yield* FileSystem;
36561
- const path = reportPath();
36562
- if (!(yield* fs.exists(path))) return yield* log("no Ship from this Machine yet");
36563
- const state = yield* decode(yield* fs.readFileString(path));
36564
- const when = formatLocal(makeUnsafe(state.started), {
36565
- dateStyle: "medium",
36566
- timeStyle: "short"
36567
- });
36568
- yield* printRows([
36569
- [{
36570
- text: `last Ship ${when} from ${state.from}`,
36571
- style: "dim"
36572
- }],
36573
- ...state.notes.map(noteRow),
36574
- ...rows(state, {
36575
- full: true,
36576
- now: state.ended ?? state.started,
36577
- live: false
36578
- })
36579
- ]);
36580
- });
36581
- //#endregion
36582
36090
  //#region ../../node_modules/.pnpm/react@19.3.0/node_modules/react/cjs/react.production.js
36583
36091
  /**
36584
36092
  * @license React
@@ -64506,6 +64014,79 @@ const getInstance = (stdout, createInstance) => {
64506
64014
  return instance;
64507
64015
  };
64508
64016
  //#endregion
64017
+ //#region ../../node_modules/.pnpm/ink@8.0.0_@types+react@19.3.0_react@19.3.0/node_modules/ink/build/render-to-string.js
64018
+ /**
64019
+ Render a React element to a string synchronously. Unlike `render()`, this function does not write to stdout, does not set up any terminal event listeners, and returns the rendered output as a string.
64020
+
64021
+ Useful for generating documentation, writing output to files, testing, or any scenario where you need the rendered output as a string without starting a persistent terminal application.
64022
+
64023
+ **Notes:**
64024
+
64025
+ - Terminal-specific hooks (`useInput`, `useStdin`, `useStdout`, `useStderr`, `useApp`, `useFocus`, `useFocusManager`) return default no-op values since there is no terminal session. They will not throw, but they will not function as in a live terminal.
64026
+ - `useEffect` callbacks will execute during rendering (due to synchronous rendering mode), but state updates they trigger will not affect the returned output, which reflects the initial render.
64027
+ - `useLayoutEffect` callbacks fire synchronously during commit, so state updates they trigger **will** be reflected in the output.
64028
+ - The `<Static>` component is supported — its output is prepended to the dynamic output.
64029
+ - If a component throws during rendering, the error is propagated to the caller after cleanup.
64030
+
64031
+ @example
64032
+ ```
64033
+ import {renderToString, Text, Box} from 'ink';
64034
+
64035
+ const output = renderToString(
64036
+ <Box padding={1}>
64037
+ <Text color="green">Hello World</Text>
64038
+ </Box>,
64039
+ {columns: 40}
64040
+ );
64041
+
64042
+ console.log(output);
64043
+ ```
64044
+ */
64045
+ const renderToString = (node, options) => {
64046
+ const columns = options?.columns ?? 80;
64047
+ const rootNode = createNode("ink-root");
64048
+ let capturedStaticOutput = "";
64049
+ rootNode.onStaticChange = () => {
64050
+ capturedStaticOutput = "";
64051
+ };
64052
+ rootNode.onComputeLayout = () => {
64053
+ rootNode.yogaNode.setWidth(columns);
64054
+ rootNode.yogaNode.calculateLayout(void 0, void 0, Yoga.DIRECTION_LTR);
64055
+ };
64056
+ rootNode.onImmediateRender = () => {
64057
+ const { staticOutput } = renderer(rootNode, false);
64058
+ if (staticOutput !== "") capturedStaticOutput += staticOutput;
64059
+ };
64060
+ let uncaughtError;
64061
+ const container = reconciler_default.createContainer(rootNode, import_constants.LegacyRoot, null, false, null, "render-to-string", (error) => {
64062
+ uncaughtError ??= types.isNativeError(error) ? error : new Error(String(error));
64063
+ }, () => {}, () => {}, () => {}, null);
64064
+ let didTeardownSucceed = false;
64065
+ try {
64066
+ let output;
64067
+ let outputHeight;
64068
+ let staticOutput;
64069
+ try {
64070
+ reconciler_default.updateContainerSync((0, import_react.createElement)(RootNodeContext.Provider, { value: rootNode }, node), container, null, () => {});
64071
+ reconciler_default.flushSyncWork();
64072
+ ({output, outputHeight} = renderer(rootNode, false));
64073
+ staticOutput = capturedStaticOutput;
64074
+ } finally {
64075
+ reconciler_default.updateContainerSync(null, container, null, () => {});
64076
+ reconciler_default.flushSyncWork();
64077
+ didTeardownSucceed = true;
64078
+ rootNode.yogaNode.free();
64079
+ }
64080
+ if (uncaughtError !== void 0) throw uncaughtError;
64081
+ if (outputHeight > 0) return staticOutput + output;
64082
+ return staticOutput.endsWith("\n") ? staticOutput.slice(0, -1) : staticOutput;
64083
+ } finally {
64084
+ if (!didTeardownSucceed && rootNode.yogaNode) try {
64085
+ rootNode.yogaNode.freeRecursive();
64086
+ } catch {}
64087
+ }
64088
+ };
64089
+ //#endregion
64509
64090
  //#region ../../node_modules/.pnpm/ink@8.0.0_@types+react@19.3.0_react@19.3.0/node_modules/ink/build/hooks/use-stdin.js
64510
64091
  /**
64511
64092
  A React hook that returns the stdin stream and stdin-related utilities.
@@ -64591,6 +64172,341 @@ const useInput = (inputHandler, options = {}) => {
64591
64172
  }, [options.isActive, internal_eventEmitter]);
64592
64173
  };
64593
64174
  //#endregion
64175
+ //#region ../../node_modules/.pnpm/ink@8.0.0_@types+react@19.3.0_react@19.3.0/node_modules/ink/build/hooks/use-stdout.js
64176
+ /**
64177
+ A React hook that returns the stdout stream where Ink renders your app.
64178
+ */
64179
+ function useStdout() {
64180
+ return (0, import_react.use)(StdoutContext);
64181
+ }
64182
+ //#endregion
64183
+ //#region ../../node_modules/.pnpm/ink@8.0.0_@types+react@19.3.0_react@19.3.0/node_modules/ink/build/hooks/use-window-size.js
64184
+ /**
64185
+ A React hook that returns the current terminal window dimensions and re-renders the component whenever the terminal is resized.
64186
+ */
64187
+ const useWindowSize = () => {
64188
+ const { stdout } = useStdout();
64189
+ const [size, setSize] = (0, import_react.useState)(() => getWindowSize(stdout));
64190
+ (0, import_react.useEffect)(() => {
64191
+ const onResize = () => {
64192
+ setSize(getWindowSize(stdout));
64193
+ };
64194
+ stdout.on("resize", onResize);
64195
+ onResize();
64196
+ return () => {
64197
+ stdout.off("resize", onResize);
64198
+ };
64199
+ }, [stdout]);
64200
+ return size;
64201
+ };
64202
+ //#endregion
64203
+ //#region src/ledger.ts
64204
+ const Note = Struct({
64205
+ kind: Literals$2([
64206
+ "ask",
64207
+ "answer",
64208
+ "info"
64209
+ ]),
64210
+ text: String$5
64211
+ });
64212
+ const Item = Struct({
64213
+ name: String$5,
64214
+ kind: Literals$2([
64215
+ "version",
64216
+ "repo",
64217
+ "config"
64218
+ ]),
64219
+ from: optionalKey(String$5),
64220
+ to: optionalKey(String$5),
64221
+ reason: optionalKey(String$5),
64222
+ changes: Boolean$4,
64223
+ status: Literals$2([
64224
+ "idle",
64225
+ "running",
64226
+ "ok",
64227
+ "failed",
64228
+ "skipped"
64229
+ ]),
64230
+ doing: optionalKey(String$5),
64231
+ detail: optionalKey(String$5),
64232
+ output: optionalKey(ArraySchema(String$5)),
64233
+ steps: ArraySchema(String$5),
64234
+ started: optionalKey(Number$2),
64235
+ ended: optionalKey(Number$2)
64236
+ });
64237
+ const MachineEntry = Struct({
64238
+ name: String$5,
64239
+ status: Literals$2([
64240
+ "checking",
64241
+ "waiting",
64242
+ "planned",
64243
+ "running",
64244
+ "ok",
64245
+ "skipped",
64246
+ "failed"
64247
+ ]),
64248
+ detail: optionalKey(String$5),
64249
+ output: optionalKey(ArraySchema(String$5)),
64250
+ items: ArraySchema(Item),
64251
+ started: optionalKey(Number$2),
64252
+ ended: optionalKey(Number$2)
64253
+ });
64254
+ const LedgerState = Struct({
64255
+ from: String$5,
64256
+ dryRun: Boolean$4,
64257
+ notes: ArraySchema(Note),
64258
+ machines: ArraySchema(MachineEntry),
64259
+ started: Number$2,
64260
+ ended: optionalKey(Number$2)
64261
+ });
64262
+ const short = (sha) => sha === void 0 ? void 0 : sha.slice(0, 7);
64263
+ const FETCH = ":fetch";
64264
+ const doingOf = (id) => id === "config" ? "copying" : id.endsWith(FETCH) ? "fetching" : "installing";
64265
+ const itemsOf = (p, state) => {
64266
+ const items = /* @__PURE__ */ new Map();
64267
+ const key = (kind, name) => `${kind}:${name}`;
64268
+ const base = (name, from, kind = "version") => ({
64269
+ name,
64270
+ kind,
64271
+ ...from === void 0 ? {} : { from },
64272
+ changes: false,
64273
+ status: "idle",
64274
+ steps: []
64275
+ });
64276
+ const set = (name, change, kind = "version") => items.set(key(kind, name), change(items.get(key(kind, name)) ?? base(name, void 0, kind)));
64277
+ const plans = (id, to, from, reason) => ({ from: checked, ...item }) => {
64278
+ const now = from === void 0 ? checked : from ?? void 0;
64279
+ return {
64280
+ ...item,
64281
+ ...now === void 0 ? {} : { from: now },
64282
+ to,
64283
+ ...reason === void 0 ? {} : { reason },
64284
+ changes: true,
64285
+ steps: [...item.steps, id]
64286
+ };
64287
+ };
64288
+ const vp = state.vp?.version ?? void 0;
64289
+ items.set(key("version", "vp"), base("vp", vp));
64290
+ if (state.node != null) items.set(key("version", "node"), base("node", state.node));
64291
+ for (const g of state.globals) items.set(key("version", g.name), base(g.name, g.version));
64292
+ for (const step of p.vp) {
64293
+ const id = vpStepId(step);
64294
+ switch (step.kind) {
64295
+ case "bootstrap":
64296
+ set("vp", plans(id, "latest", null));
64297
+ break;
64298
+ case "vp":
64299
+ case "node":
64300
+ set(step.kind, plans(id, step.version, step.from));
64301
+ break;
64302
+ case "reinstall":
64303
+ set(step.name, plans(id, step.version, step.version, "reinstall"));
64304
+ break;
64305
+ case "globals": for (const pkg of step.packages) set(pkg.name, plans(id, pkg.version, pkg.from ?? null));
64306
+ }
64307
+ }
64308
+ for (const repo of p.repos) {
64309
+ const from = short(state.records[repo.name]?.commit);
64310
+ const to = short(repo.head);
64311
+ items.set(key("repo", repo.name), {
64312
+ name: repo.name,
64313
+ kind: "repo",
64314
+ ...from === void 0 ? {} : { from },
64315
+ ...to === void 0 ? {} : { to },
64316
+ ...repo.decision.run ? { reason: repo.decision.reason } : {},
64317
+ changes: repo.decision.run,
64318
+ status: "idle",
64319
+ steps: [stepId(repo.name, "fetch"), stepId(repo.name, "install")]
64320
+ });
64321
+ }
64322
+ for (const s of p.skipped) set(s.what, (i) => ({
64323
+ ...i,
64324
+ status: "skipped",
64325
+ detail: s.reason
64326
+ }));
64327
+ for (const f of p.failures) {
64328
+ const kind = f.section === "repos" ? "repo" : "version";
64329
+ set(f.what, (i) => ({
64330
+ ...i,
64331
+ changes: true,
64332
+ status: "failed",
64333
+ detail: f.reason
64334
+ }), kind);
64335
+ }
64336
+ items.set(key("config", CONFIG_STEP), {
64337
+ name: CONFIG_STEP,
64338
+ kind: "config",
64339
+ changes: false,
64340
+ status: "idle",
64341
+ steps: [CONFIG_STEP]
64342
+ });
64343
+ return [...items.values()];
64344
+ };
64345
+ const outcomeOf = (m, dryRun) => m.status === "skipped" ? "skipped" : m.status === "failed" || m.items.some((i) => i.status === "failed") ? "failed" : m.status === "ok" || m.status === "planned" && dryRun ? "ok" : void 0;
64346
+ const tally = (state) => {
64347
+ const outcomes = state.machines.map((m) => outcomeOf(m, state.dryRun));
64348
+ const n = (o) => outcomes.filter((x) => x === o).length;
64349
+ return {
64350
+ ok: n("ok"),
64351
+ skipped: n("skipped"),
64352
+ failed: n("failed")
64353
+ };
64354
+ };
64355
+ const makeLedger = fn("makeLedger")(function* (input) {
64356
+ const started = yield* currentTimeMillis;
64357
+ let snapshot = {
64358
+ state: {
64359
+ from: input.from,
64360
+ dryRun: input.dryRun,
64361
+ notes: [],
64362
+ machines: input.machines.map((name) => ({
64363
+ name,
64364
+ status: "checking",
64365
+ items: []
64366
+ })),
64367
+ started
64368
+ },
64369
+ now: started
64370
+ };
64371
+ const listeners = /* @__PURE__ */ new Set();
64372
+ const update = (change) => gen(function* () {
64373
+ const now = yield* currentTimeMillis;
64374
+ snapshot = {
64375
+ state: change(snapshot.state, now),
64376
+ now
64377
+ };
64378
+ for (const listener of listeners) listener();
64379
+ });
64380
+ const machine = (name, change) => update((state, now) => ({
64381
+ ...state,
64382
+ machines: state.machines.map((m) => m.name === name ? change(m, now) : m)
64383
+ }));
64384
+ const items = (name, id, change) => machine(name, (m, now) => ({
64385
+ ...m,
64386
+ items: m.items.map((i) => i.steps.includes(id) ? change(i, now) : i)
64387
+ }));
64388
+ const store = {
64389
+ get: () => snapshot,
64390
+ subscribe: (listener) => {
64391
+ listeners.add(listener);
64392
+ return () => listeners.delete(listener);
64393
+ },
64394
+ tick: update((state) => state)
64395
+ };
64396
+ return {
64397
+ ledger: {
64398
+ note: fn("ledger.note")(function* (kind, text) {
64399
+ yield* update((state) => ({
64400
+ ...state,
64401
+ notes: [...state.notes, {
64402
+ kind,
64403
+ text
64404
+ }]
64405
+ }));
64406
+ yield* input.print({
64407
+ kind,
64408
+ text
64409
+ });
64410
+ }),
64411
+ checked: (name) => machine(name, (m) => ({
64412
+ ...m,
64413
+ status: "waiting"
64414
+ })),
64415
+ skip: (name, reason) => machine(name, (m) => ({
64416
+ ...m,
64417
+ status: "skipped",
64418
+ detail: reason
64419
+ })),
64420
+ fail: (name, [reason = "failed", ...output]) => machine(name, (m, now) => ({
64421
+ ...m,
64422
+ status: "failed",
64423
+ detail: reason,
64424
+ ...output.length === 0 ? {} : { output },
64425
+ ended: now
64426
+ })),
64427
+ plan: (name, p, state) => machine(name, (m) => ({
64428
+ ...m,
64429
+ status: "planned",
64430
+ items: itemsOf(p, state)
64431
+ })),
64432
+ start: (name) => machine(name, (m, now) => ({
64433
+ ...m,
64434
+ status: "running",
64435
+ started: now
64436
+ })),
64437
+ stepStarted: (name, id) => items(name, id, (item, now) => item.status === "failed" ? item : {
64438
+ ...item,
64439
+ changes: item.changes || !(id === "config" || id.endsWith(FETCH)),
64440
+ status: "running",
64441
+ doing: doingOf(id),
64442
+ started: item.started ?? now
64443
+ }),
64444
+ stepDone: (name, id, result) => items(name, id, (item, now) => {
64445
+ if (item.status === "failed") return item;
64446
+ if (!result.ok) return {
64447
+ ...item,
64448
+ changes: true,
64449
+ status: "failed",
64450
+ detail: result.detail,
64451
+ ...result.output.length === 0 ? {} : { output: result.output },
64452
+ ended: now
64453
+ };
64454
+ const { doing: _, ...rest } = item;
64455
+ if (id.endsWith(FETCH)) return item.changes ? item : {
64456
+ ...rest,
64457
+ status: "idle",
64458
+ ended: now
64459
+ };
64460
+ return {
64461
+ ...rest,
64462
+ status: "ok",
64463
+ ended: now
64464
+ };
64465
+ }),
64466
+ fetched: (name, repo, head) => machine(name, (m) => ({
64467
+ ...m,
64468
+ items: m.items.map((i) => i.kind === "repo" && i.name === repo ? {
64469
+ ...i,
64470
+ to: head.slice(0, 7),
64471
+ changes: i.changes || head.slice(0, 7) !== i.from
64472
+ } : i)
64473
+ })),
64474
+ finish: (name, outcome) => machine(name, (m, now) => ({
64475
+ ...m,
64476
+ status: m.status === "failed" ? "failed" : outcome,
64477
+ ended: m.ended ?? now
64478
+ })),
64479
+ end: gen(function* () {
64480
+ yield* update((state, now) => ({
64481
+ ...state,
64482
+ ended: now,
64483
+ machines: state.machines.map((m) => m.status === "running" || m.status === "checking" ? {
64484
+ ...m,
64485
+ status: "failed",
64486
+ detail: "stopped",
64487
+ ended: now,
64488
+ items: m.items.map((i) => i.status === "running" ? {
64489
+ ...i,
64490
+ status: "failed",
64491
+ detail: "stopped",
64492
+ ended: now
64493
+ } : i)
64494
+ } : m)
64495
+ }));
64496
+ return snapshot.state;
64497
+ })
64498
+ },
64499
+ store
64500
+ };
64501
+ });
64502
+ const duration = (ms) => {
64503
+ const s = ms / 1e3;
64504
+ if (s < 60) return `${s.toFixed(1)}s`;
64505
+ const m = Math.floor(s / 60);
64506
+ if (m < 60) return `${m}m ${String(Math.floor(s % 60)).padStart(2, "0")}s`;
64507
+ return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, "0")}m`;
64508
+ };
64509
+ //#endregion
64594
64510
  //#region ../../node_modules/.pnpm/react@19.3.0/node_modules/react/cjs/react-jsx-runtime.production.js
64595
64511
  /**
64596
64512
  * @license React
@@ -64838,12 +64754,12 @@ var require_react_jsx_runtime_development = /* @__PURE__ */ __commonJSMin(((expo
64838
64754
  })();
64839
64755
  }));
64840
64756
  //#endregion
64841
- //#region src/live.tsx
64757
+ //#region src/view.tsx
64842
64758
  var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
64843
64759
  if (process.env.NODE_ENV === "production") module.exports = require_react_jsx_runtime_production();
64844
64760
  else module.exports = require_react_jsx_runtime_development();
64845
64761
  })))();
64846
- const STYLE = {
64762
+ const TONE = {
64847
64763
  dim: { dimColor: true },
64848
64764
  bold: { bold: true },
64849
64765
  ok: { color: "green" },
@@ -64858,6 +64774,420 @@ const STYLE = {
64858
64774
  bold: true
64859
64775
  }
64860
64776
  };
64777
+ const ModeContext = (0, import_react.createContext)({
64778
+ color: false,
64779
+ live: false
64780
+ });
64781
+ const T = (props) => {
64782
+ const { color } = (0, import_react.useContext)(ModeContext);
64783
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
64784
+ ...color && props.tone !== void 0 ? TONE[props.tone] : {},
64785
+ children: props.children
64786
+ });
64787
+ };
64788
+ const Line = (props) => {
64789
+ const { live } = (0, import_react.useContext)(ModeContext);
64790
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
64791
+ paddingLeft: props.indent ?? 0,
64792
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
64793
+ wrap: live ? "truncate-end" : "wrap",
64794
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
64795
+ tone: props.tone,
64796
+ children: props.children
64797
+ })
64798
+ })
64799
+ });
64800
+ };
64801
+ const Cell = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
64802
+ width: props.width,
64803
+ flexShrink: 0,
64804
+ justifyContent: props.right ? "flex-end" : "flex-start",
64805
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
64806
+ wrap: "truncate-end",
64807
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
64808
+ tone: props.tone,
64809
+ children: props.children
64810
+ })
64811
+ })
64812
+ });
64813
+ const value = (i) => i.to === void 0 ? i.from ?? "" : i.from === void 0 ? `+ ${i.to}` : i.from === i.to ? i.to : `${i.from} → ${i.to}`;
64814
+ const statusOf = (i, dryRun) => {
64815
+ switch (i.status) {
64816
+ case "running": return {
64817
+ text: `▸ ${i.doing ?? "running"}`,
64818
+ tone: "run"
64819
+ };
64820
+ case "failed": return {
64821
+ text: "✗ failed",
64822
+ tone: "fail",
64823
+ detail: i.detail
64824
+ };
64825
+ case "skipped": return {
64826
+ text: "– skipped",
64827
+ tone: "skip",
64828
+ detail: i.detail
64829
+ };
64830
+ case "ok":
64831
+ if (i.kind === "config") return {
64832
+ text: "✓ copied",
64833
+ tone: "dim"
64834
+ };
64835
+ if (i.kind === "repo") return i.from !== void 0 && i.from === i.to ? {
64836
+ text: "✓ rebuilt",
64837
+ tone: "ok",
64838
+ detail: i.reason
64839
+ } : {
64840
+ text: "✓ installed",
64841
+ tone: "ok"
64842
+ };
64843
+ return {
64844
+ text: "✓ done",
64845
+ tone: "ok",
64846
+ detail: i.reason
64847
+ };
64848
+ case "idle":
64849
+ if (i.changes) return {
64850
+ text: dryRun ? "· planned" : "· queued",
64851
+ tone: "dim",
64852
+ detail: i.reason
64853
+ };
64854
+ return {
64855
+ text: "",
64856
+ detail: i.kind === "repo" ? "up to date" : void 0
64857
+ };
64858
+ }
64859
+ };
64860
+ const countsOf = (m) => {
64861
+ const changes = m.items.filter((i) => i.changes && i.status !== "skipped");
64862
+ return {
64863
+ changes: changes.length,
64864
+ done: changes.filter((i) => i.status === "ok").length,
64865
+ failed: m.items.filter((i) => i.status === "failed").length,
64866
+ skipped: m.items.filter((i) => i.status === "skipped").length
64867
+ };
64868
+ };
64869
+ const settled = (m, dryRun) => dryRun || m.status === "ok" || m.status === "failed";
64870
+ const upToDate = (m, dryRun) => {
64871
+ const c = countsOf(m);
64872
+ return c.changes === 0 && c.skipped === 0 && settled(m, dryRun);
64873
+ };
64874
+ const summaryOf = (m, dryRun) => {
64875
+ if (m.status === "checking" || m.status === "waiting") return [{
64876
+ text: m.status,
64877
+ tone: "dim"
64878
+ }];
64879
+ if (m.status === "skipped") return [{
64880
+ text: "– skipped",
64881
+ tone: "skip"
64882
+ }, { text: ` ${m.detail ?? ""}` }];
64883
+ if (m.status === "failed" && m.detail !== void 0) return [{
64884
+ text: "✗ failed",
64885
+ tone: "fail"
64886
+ }, { text: ` ${m.detail}` }];
64887
+ const { changes, done, failed, skipped } = countsOf(m);
64888
+ if (changes === 0 && skipped === 0) return [{
64889
+ text: "up to date",
64890
+ tone: settled(m, dryRun) ? "ok" : "dim"
64891
+ }];
64892
+ const finished = m.status === "ok" || m.status === "failed";
64893
+ const parts = [];
64894
+ if (dryRun && m.status === "planned") {
64895
+ if (changes > failed) parts.push({
64896
+ text: `${changes - failed} to change`,
64897
+ tone: "dim"
64898
+ });
64899
+ } else if (changes > 0 && (!finished || done > 0)) parts.push({
64900
+ text: `${done} of ${changes} done`,
64901
+ tone: done === changes ? "ok" : "dim"
64902
+ });
64903
+ if (failed > 0) parts.push({
64904
+ text: `${failed} failed`,
64905
+ tone: "fail"
64906
+ });
64907
+ if (skipped > 0) parts.push({
64908
+ text: `${skipped} skipped`,
64909
+ tone: "skip"
64910
+ });
64911
+ return parts.flatMap((part, n) => n === 0 ? [part] : [{
64912
+ text: ", ",
64913
+ tone: "dim"
64914
+ }, part]);
64915
+ };
64916
+ const elapsed = (started, ended, now) => started === void 0 ? "" : duration((ended ?? now) - started);
64917
+ const Parts = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, { children: props.parts.map((part, n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
64918
+ tone: part.tone,
64919
+ children: part.text
64920
+ }, n)) });
64921
+ const GAP = 2;
64922
+ const INDENT = 2;
64923
+ const nameAt = (w) => w.status + GAP + INDENT;
64924
+ const LIVE_OUTPUT = 12;
64925
+ const outputLines = (key, output, indent, cut) => {
64926
+ const hidden = cut ? Math.max(0, output.length - LIVE_OUTPUT) : 0;
64927
+ return [...hidden > 0 ? [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Line, {
64928
+ indent,
64929
+ tone: "dim",
64930
+ children: [
64931
+ "(",
64932
+ hidden,
64933
+ " earlier lines)"
64934
+ ]
64935
+ }, `${key}/earlier`)] : [], ...output.slice(hidden).map((line, n) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Line, {
64936
+ indent,
64937
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
64938
+ tone: "fail",
64939
+ children: "│ "
64940
+ }), line]
64941
+ }, `${key}/${n}`))];
64942
+ };
64943
+ const MachineLine = (props) => {
64944
+ const { m, w } = props;
64945
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
64946
+ columnGap: GAP,
64947
+ children: [
64948
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cell, {
64949
+ width: nameAt(w) + w.item + GAP + w.value,
64950
+ tone: "bold",
64951
+ children: m.name
64952
+ }),
64953
+ w.time > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cell, {
64954
+ width: w.time,
64955
+ right: true,
64956
+ tone: "dim",
64957
+ children: upToDate(m, props.dryRun) ? "" : elapsed(m.started, m.ended, props.now)
64958
+ }),
64959
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Parts, { parts: summaryOf(m, props.dryRun) })
64960
+ ]
64961
+ });
64962
+ };
64963
+ const ItemLine = (props) => {
64964
+ const { i, w } = props;
64965
+ const status = statusOf(i, props.dryRun);
64966
+ const quiet = i.status === "idle" ? "dim" : void 0;
64967
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
64968
+ columnGap: GAP,
64969
+ children: [
64970
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cell, {
64971
+ width: w.status,
64972
+ tone: status.tone,
64973
+ children: status.text
64974
+ }),
64975
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cell, {
64976
+ width: INDENT + w.item,
64977
+ tone: quiet,
64978
+ children: `${" ".repeat(INDENT)}${i.name}`
64979
+ }),
64980
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cell, {
64981
+ width: w.value,
64982
+ tone: quiet,
64983
+ children: value(i)
64984
+ }),
64985
+ w.time > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Cell, {
64986
+ width: w.time,
64987
+ right: true,
64988
+ tone: "dim",
64989
+ children: elapsed(i.started, i.ended, props.now)
64990
+ }),
64991
+ status.detail !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, {
64992
+ tone: quiet,
64993
+ children: status.detail
64994
+ })
64995
+ ]
64996
+ });
64997
+ };
64998
+ const LedgerView = (props) => {
64999
+ const { state, full, now, height } = props;
65000
+ const { dryRun } = state;
65001
+ const { live } = (0, import_react.useContext)(ModeContext);
65002
+ const running = live && state.ended === void 0;
65003
+ const shownItems = (m) => m.items.filter((i) => full || i.changes || i.status === "failed" || i.status === "skipped");
65004
+ const visible = state.machines.flatMap(shownItems);
65005
+ const width = (texts) => Math.max(0, ...texts.map((t) => t.length));
65006
+ const item = width(visible.map((i) => i.name));
65007
+ const status = width(visible.map((i) => statusOf(i, dryRun).text));
65008
+ const w = {
65009
+ status,
65010
+ item,
65011
+ value: Math.max(width(visible.map(value)), width(state.machines.map((m) => m.name)) - (status + GAP + INDENT + item + GAP)),
65012
+ time: width([...visible.map((i) => elapsed(i.started, i.ended, now)), ...state.machines.map((m) => elapsed(m.started, m.ended, now))])
65013
+ };
65014
+ const blocks = state.machines.map((m) => ({
65015
+ m,
65016
+ head: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MachineLine, {
65017
+ m,
65018
+ w,
65019
+ dryRun,
65020
+ now
65021
+ }, m.name),
65022
+ body: [...m.output === void 0 ? [] : outputLines(m.name, m.output, nameAt(w), running), ...shownItems(m).flatMap((i) => [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ItemLine, {
65023
+ i,
65024
+ w,
65025
+ dryRun,
65026
+ now
65027
+ }, `${m.name}/${i.kind}:${i.name}`), ...i.output === void 0 ? [] : outputLines(`${m.name}/${i.kind}:${i.name}`, i.output, nameAt(w), running)])]
65028
+ }));
65029
+ const legend = state.ended === void 0 ? `f ${full ? "changes only" : "full list"} · ctrl-c stop` : void 0;
65030
+ const room = height === void 0 ? Infinity : height - (legend === void 0 ? 1 : 3);
65031
+ const count = (bs) => bs.reduce((n, b) => n + 1 + b.body.length, Math.max(0, bs.length - 1));
65032
+ const fits = count(blocks) <= room;
65033
+ let spare = room - count(blocks.map((b) => ({
65034
+ ...b,
65035
+ body: []
65036
+ })));
65037
+ let cuts = 0;
65038
+ const fitted = [];
65039
+ for (const b of blocks) {
65040
+ const open = b.m.status === "running";
65041
+ if (fits) fitted.push(b);
65042
+ else if (open && b.body.length <= spare) {
65043
+ fitted.push(b);
65044
+ spare -= b.body.length;
65045
+ } else if (open && spare > 0) {
65046
+ const ellipsis = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, {
65047
+ indent: nameAt(w),
65048
+ tone: "dim",
65049
+ children: "…"
65050
+ }, `${b.m.name}/cut`);
65051
+ fitted.push({
65052
+ ...b,
65053
+ body: [...b.body.slice(0, spare - 1), ellipsis]
65054
+ });
65055
+ spare = 0;
65056
+ cuts += 1;
65057
+ } else fitted.push({
65058
+ ...b,
65059
+ body: []
65060
+ });
65061
+ }
65062
+ const lines = fitted.flatMap((b, n) => [
65063
+ ...n === 0 ? [] : [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: " " }, `${b.m.name}/gap`)],
65064
+ b.head,
65065
+ ...b.body
65066
+ ]);
65067
+ const shown = lines.length <= room ? lines : lines.slice(0, Math.max(0, room - 1));
65068
+ const hidden = count(blocks) - (shown.length - cuts);
65069
+ if (room < 1) return null;
65070
+ const t = tally(state);
65071
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
65072
+ flexDirection: "column",
65073
+ marginTop: 1,
65074
+ children: [
65075
+ shown,
65076
+ shown.length < lines.length && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, {
65077
+ tone: "dim",
65078
+ children: "…"
65079
+ }),
65080
+ state.ended !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
65081
+ marginTop: 1,
65082
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Parts, { parts: [
65083
+ {
65084
+ text: `${dryRun ? "planned" : "ok"} ${t.ok}`,
65085
+ ...t.ok > 0 ? { tone: "ok" } : {}
65086
+ },
65087
+ { text: " " },
65088
+ {
65089
+ text: `skipped ${t.skipped}`,
65090
+ ...t.skipped > 0 ? { tone: "skip" } : {}
65091
+ },
65092
+ { text: " " },
65093
+ {
65094
+ text: `failed ${t.failed}`,
65095
+ ...t.failed > 0 ? { tone: "fail" } : {}
65096
+ },
65097
+ ...dryRun ? [] : [{
65098
+ text: ` in ${duration(state.ended - state.started)}`,
65099
+ tone: "dim"
65100
+ }]
65101
+ ] })
65102
+ }),
65103
+ state.ended !== void 0 && props.hint !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Line, {
65104
+ tone: "dim",
65105
+ children: props.hint
65106
+ }),
65107
+ legend !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Box, {
65108
+ marginTop: 1,
65109
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Line, {
65110
+ tone: "dim",
65111
+ children: [legend, hidden > 0 && ` · ${hidden} lines hidden to fit the window`]
65112
+ })
65113
+ })
65114
+ ]
65115
+ });
65116
+ };
65117
+ const NoteLine = (props) => {
65118
+ const { kind, text } = props.note;
65119
+ return kind === "ask" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
65120
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
65121
+ tone: "ask",
65122
+ children: "?"
65123
+ }),
65124
+ " ",
65125
+ text
65126
+ ] }) : kind === "answer" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
65127
+ tone: "dim",
65128
+ children: ` → ${text}`
65129
+ }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: text });
65130
+ };
65131
+ const Styled = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ModeContext.Provider, {
65132
+ value: {
65133
+ color: props.color,
65134
+ live: props.live
65135
+ },
65136
+ children: props.children
65137
+ });
65138
+ const colorful = () => Boolean(process.stdout.isTTY) && process.env.NO_COLOR === void 0;
65139
+ const columns = () => process.stdout.columns ?? 1e4;
65140
+ const toText = (node, color = colorful()) => renderToString(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Styled, {
65141
+ color,
65142
+ live: false,
65143
+ children: node
65144
+ }), { columns: columns() });
65145
+ const ledgerText = (state, options, color) => toText(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LedgerView, {
65146
+ state,
65147
+ ...options
65148
+ }), color);
65149
+ const noteText = (note, color) => toText(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(NoteLine, { note }), color);
65150
+ //#endregion
65151
+ //#region src/report.tsx
65152
+ const reportPath = () => `${STATE_DIR.replace(/^~/, process.env.HOME ?? "~")}/last-ship.json`;
65153
+ const LedgerJson = fromJsonString(LedgerState);
65154
+ const encode = encodeEffect(LedgerJson);
65155
+ const decode = decodeEffect(LedgerJson);
65156
+ const saveReport = fn("saveReport")(function* (state) {
65157
+ const fs = yield* FileSystem;
65158
+ const path = reportPath();
65159
+ yield* fs.makeDirectory(path.slice(0, path.lastIndexOf("/")), { recursive: true });
65160
+ yield* fs.writeFileString(path, yield* encode(state));
65161
+ });
65162
+ const report = fn("report")(function* () {
65163
+ const fs = yield* FileSystem;
65164
+ const path = reportPath();
65165
+ if (!(yield* fs.exists(path))) return yield* log("no Ship from this Machine yet");
65166
+ const decoded = yield* decode(yield* fs.readFileString(path)).pipe(option);
65167
+ if (isNone(decoded)) return yield* log(`the last report (${path}) is from another version of fleetfizz; the next Ship replaces it`);
65168
+ const state = decoded.value;
65169
+ const when = formatLocal(makeUnsafe(state.started), {
65170
+ dateStyle: "medium",
65171
+ timeStyle: "short"
65172
+ });
65173
+ yield* log(toText(/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
65174
+ flexDirection: "column",
65175
+ children: [
65176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
65177
+ tone: "dim",
65178
+ children: `last Ship ${when} from ${state.from}`
65179
+ }),
65180
+ state.notes.map((note, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NoteLine, { note }, i)),
65181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LedgerView, {
65182
+ state,
65183
+ full: true,
65184
+ now: state.ended ?? state.started
65185
+ })
65186
+ ]
65187
+ })));
65188
+ });
65189
+ //#endregion
65190
+ //#region src/live.tsx
64861
65191
  const cell = (initial) => {
64862
65192
  let value = initial;
64863
65193
  const listeners = /* @__PURE__ */ new Set();
@@ -64878,6 +65208,7 @@ const LiveLedger = (props) => {
64878
65208
  const asking = (0, import_react.useSyncExternalStore)(props.asking.subscribe, props.asking.get);
64879
65209
  const [full, setFull] = (0, import_react.useState)(false);
64880
65210
  const [cursor, setCursor] = (0, import_react.useState)(0);
65211
+ const { rows } = useWindowSize();
64881
65212
  const { isRawModeSupported } = useStdin();
64882
65213
  useInput((input, key) => {
64883
65214
  if (key.ctrl && input === "c") process.kill(process.pid, "SIGINT");
@@ -64889,42 +65220,36 @@ const LiveLedger = (props) => {
64889
65220
  asking.answer(cursor);
64890
65221
  } else if (input === "f") setFull((f) => !f);
64891
65222
  }, { isActive: isRawModeSupported });
64892
- const style = (s) => props.color && s !== void 0 ? STYLE[s] : {};
64893
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
64894
- flexDirection: "column",
64895
- children: [asking?.titles.map((title, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, {
64896
- wrap: "truncate-end",
64897
- children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
64898
- ...style(i === cursor ? "ask" : "dim"),
64899
- children: i === cursor ? " › " : " "
64900
- }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
64901
- ...style(i === cursor ? void 0 : "dim"),
64902
- children: title
65223
+ const choices = asking?.titles ?? [];
65224
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Styled, {
65225
+ color: props.color,
65226
+ live: true,
65227
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box, {
65228
+ flexDirection: "column",
65229
+ children: [choices.map((title, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Line, {
65230
+ tone: i === cursor ? void 0 : "dim",
65231
+ children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(T, {
65232
+ tone: i === cursor ? "ask" : "dim",
65233
+ children: i === cursor ? " › " : " "
65234
+ }), title]
65235
+ }, i)), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LedgerView, {
65236
+ state,
65237
+ full,
65238
+ now,
65239
+ height: rows - 1 - choices.length
64903
65240
  })]
64904
- }, `ask-${i}`)), rows(state, {
64905
- full,
64906
- now,
64907
- live: true
64908
- }).map((row, r) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
64909
- wrap: "truncate-end",
64910
- children: row.length === 0 ? " " : row.map((segment, s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, {
64911
- ...style(segment.style),
64912
- children: segment.text
64913
- }, s))
64914
- }, r))]
65241
+ })
64915
65242
  });
64916
65243
  };
64917
65244
  const startLive = fn("startLive")(function* (store, color) {
64918
65245
  const asking = cell(void 0);
64919
- const view = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(LiveLedger, {
65246
+ const instance = render(/* @__PURE__ */ (0, import_jsx_runtime.jsx)(LiveLedger, {
64920
65247
  store,
64921
65248
  asking,
64922
65249
  color
64923
- });
64924
- const instance = render(view, { exitOnCtrlC: false });
65250
+ }), { exitOnCtrlC: false });
64925
65251
  yield* addFinalizer(() => promise(async () => {
64926
- instance.rerender(view);
64927
- await instance.waitUntilRenderFlush();
65252
+ instance.clear();
64928
65253
  instance.unmount();
64929
65254
  await instance.waitUntilExit();
64930
65255
  }));
@@ -64971,12 +65296,12 @@ const gather = fn("gather")(function* (target, script, ledger) {
64971
65296
  else if (line.kind === "text" && line.text.trim() !== "") output.push(line.text.trimEnd());
64972
65297
  }), ledger).pipe(timeoutOrElse({
64973
65298
  duration: GATHER_TIMEOUT,
64974
- orElse: () => ledger.fail(target.name, [`gather took more than ${GATHER_TIMEOUT}; something on that Machine is blocking`, ...output]).pipe(as({ kind: "failed" }))
65299
+ orElse: () => ledger.fail(target.name, [`checking took more than ${GATHER_TIMEOUT}; something on that Machine is blocking`, ...output]).pipe(as({ kind: "failed" }))
64975
65300
  }));
64976
65301
  if (run.kind !== "ran") return { kind: run.kind };
64977
65302
  const state = assembleState(facts);
64978
65303
  if (!state.complete) {
64979
- yield* ledger.fail(target.name, ["gather did not finish", ...output]);
65304
+ yield* ledger.fail(target.name, ["checking did not finish", ...output]);
64980
65305
  return { kind: "failed" };
64981
65306
  }
64982
65307
  return {
@@ -65132,24 +65457,30 @@ const ship = fn("ship")(function* (flags) {
65132
65457
  const targetsList = tailnet.fleet.filter((m) => fleet.has(m.name) && (flags.only.length === 0 || flags.only.includes(m.name))).toSorted((a, b) => order.indexOf(a.name) - order.indexOf(b.name));
65133
65458
  const count = `${targetsList.length} machine${targetsList.length === 1 ? "" : "s"}`;
65134
65459
  yield* log(`${flags.dryRun ? "dry run, " : ""}${count} from ${tailnet.self.name}`);
65460
+ const color = colorful();
65135
65461
  const { ledger, store } = yield* makeLedger({
65136
65462
  from: tailnet.self.name,
65137
65463
  dryRun: flags.dryRun,
65138
- machines: targetsList.map((m) => m.name)
65464
+ machines: targetsList.map((m) => m.name),
65465
+ print: (note) => log(noteText(note, color))
65139
65466
  });
65140
- const live = canRedraw() ? yield* startLive(store, styled()) : noLive;
65467
+ const redraw = canRedraw();
65141
65468
  let changing = false;
65142
65469
  const finish = fn("finish")(function* () {
65143
65470
  const state = yield* ledger.end;
65144
- if (changing) yield* saveReport(state).pipe(catch_((e) => error(`could not save the report for fizz report: ${e.message}`)));
65145
- if (live === noLive) yield* printRows(rows(state, {
65146
- full: true,
65471
+ const saved = changing && (yield* saveReport(state).pipe(as(true), catch_((e) => error(`could not save the report for fizz report: ${e.message}`).pipe(as(false)))));
65472
+ yield* log(ledgerText(state, redraw ? {
65473
+ full: false,
65147
65474
  now: state.ended ?? 0,
65148
- live: false
65149
- }));
65475
+ ...saved ? { hint: "full report: fizz report" } : {}
65476
+ } : {
65477
+ full: true,
65478
+ now: state.ended ?? 0
65479
+ }, color));
65150
65480
  if (tally(state).failed > 0) process.exitCode = 1;
65151
65481
  });
65152
65482
  yield* addFinalizer(() => finish());
65483
+ const live = redraw ? yield* startLive(store, color) : noLive;
65153
65484
  const cloneRootOf = (machine) => config.machines[machine.name]?.clones ?? "~/.local/share/fleetfizz/clones";
65154
65485
  const gatherRepos = (config.repos ?? []).map((r) => ({
65155
65486
  name: repoName(r),
@@ -65174,7 +65505,7 @@ const ship = fn("ship")(function* (flags) {
65174
65505
  local
65175
65506
  }, script, ledger);
65176
65507
  if (result.kind !== "gathered") return void 0;
65177
- yield* ledger.gathered(machine.name);
65508
+ yield* ledger.checked(machine.name);
65178
65509
  return {
65179
65510
  machine,
65180
65511
  local,
@@ -65271,7 +65602,7 @@ const ship = fn("ship")(function* (flags) {
65271
65602
  //#endregion
65272
65603
  //#region src/bin.ts
65273
65604
  const shipCommand = make("ship", {
65274
- dryRun: Boolean$1("dry-run").pipe(withDefault(false), withDescription$1("Gather and plan only. Print every Machine's plan and change nothing.")),
65605
+ dryRun: Boolean$1("dry-run").pipe(withDefault(false), withDescription$1("Check and plan only. Print every Machine's plan and change nothing.")),
65275
65606
  only: String$1("only").pipe(atLeast(0), withDescription$1("Ship only to these Machines (repeat the flag).")),
65276
65607
  rebuild: String$1("rebuild").pipe(atLeast(0), withDescription$1("Run a repo's Install Command even when its record matches; `all` for every repo.")),
65277
65608
  bump: Boolean$1("bump").pipe(withDefault(false), withDescription$1("Answer every above-range question by bumping the config.")),