rolldown-pnpm-config 1.0.11 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ import { legend, simulatedLegend } from "../render/legend.js";
2
+ import { Doc } from "@effected/cli";
3
+ import { DocView, KeyHelp, KeyTable, Styled, Tabs, Viewport, useKeys, useTerminalSize, useTheme } from "@effected/cli/ui";
4
+ import { Box, Text } from "ink";
5
+ import { useEffect, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+
8
+ //#region src/cli/ui/preview-screen.tsx
9
+ const TABS = [
10
+ {
11
+ name: "changes",
12
+ label: "Changes"
13
+ },
14
+ {
15
+ name: "full",
16
+ label: "Full"
17
+ },
18
+ {
19
+ name: "simulated",
20
+ label: "Simulated"
21
+ }
22
+ ];
23
+ /** `q` and Enter close the viewer; Esc and Ctrl-C are the host's (the command treats them as a close too). */
24
+ const close = KeyTable.make([{
25
+ keys: [{ char: "q" }, "enter"],
26
+ action: "close",
27
+ help: "close"
28
+ }]);
29
+ /**
30
+ * Lines the viewer's chrome takes besides the scrolling view: tabs, legend and
31
+ * key help, plus one so the frame stays shorter than the terminal (Ink clears
32
+ * the screen and scrollback for a frame as tall as the terminal).
33
+ */
34
+ const RESERVED = 4;
35
+ function rowsOf(views, name) {
36
+ return views[name].lines.map((_, i) => ({
37
+ _tag: "Item",
38
+ key: String(i)
39
+ }));
40
+ }
41
+ /**
42
+ * The read-only preview explorer: a tab bar over the Changes / Full /
43
+ * Simulated views, each scrolling in a viewport that fits the terminal, with
44
+ * the matching legend above it. Resolves when the viewer is closed.
45
+ *
46
+ * @internal
47
+ */
48
+ function makePreviewScreen(views) {
49
+ return ({ resolve }) => {
50
+ const Preview = () => {
51
+ const height = Math.max(1, useTerminalSize().rows - RESERVED);
52
+ const [tab, setTab] = useState("changes");
53
+ const [viewport, setViewport] = useState(() => Viewport.init(views.changes.lines.length, height));
54
+ useEffect(() => setViewport((current) => Viewport.resize(current, height)), [height]);
55
+ useKeys(Viewport.keys, (move) => setViewport((current) => Viewport.step(current, move)));
56
+ useKeys(close, () => resolve(void 0));
57
+ const lines = views[tab].lines;
58
+ const showLegend = useTheme().color !== "none";
59
+ return /* @__PURE__ */ jsxs(Box, {
60
+ flexDirection: "column",
61
+ children: [
62
+ /* @__PURE__ */ jsx(Tabs.View, {
63
+ tabs: TABS,
64
+ value: tab,
65
+ onChange: (name) => {
66
+ setTab(name);
67
+ setViewport(Viewport.init(views[name].lines.length, height));
68
+ }
69
+ }),
70
+ showLegend ? /* @__PURE__ */ jsx(DocView, { doc: tab === "simulated" ? simulatedLegend() : legend() }) : /* @__PURE__ */ jsx(Text, { children: " " }),
71
+ /* @__PURE__ */ jsx(Viewport.View, {
72
+ rows: rowsOf(views, tab),
73
+ state: viewport,
74
+ reserved: RESERVED,
75
+ renderRow: (row, highlighted) => /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, { children: highlighted ? /* @__PURE__ */ jsx(Styled, {
76
+ token: "accent",
77
+ children: "›"
78
+ }) : " " }), /* @__PURE__ */ jsx(DocView, { doc: Doc.line(row._tag === "Item" ? lines[Number(row.key)] ?? [] : []) })] })
79
+ }),
80
+ /* @__PURE__ */ jsx(KeyHelp, { tables: [
81
+ Viewport.keys,
82
+ Tabs.keys,
83
+ close
84
+ ] })
85
+ ]
86
+ });
87
+ };
88
+ return /* @__PURE__ */ jsx(Preview, {});
89
+ };
90
+ }
91
+
92
+ //#endregion
93
+ export { makePreviewScreen };
@@ -0,0 +1,47 @@
1
+ import { initialProgress, reduceProgress } from "./progress.js";
2
+ import { Status } from "@effected/cli";
3
+ import { Styled, useGlyphs, useTheme } from "@effected/cli/ui";
4
+ import { Text } from "ink";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+
7
+ //#region src/cli/ui/progress-view.tsx
8
+ function Progress({ state, frame }) {
9
+ const glyphs = useGlyphs();
10
+ const theme = useTheme();
11
+ const count = state.total > 0 ? ` ${state.done}/${state.total}` : "";
12
+ if (state.finished) return /* @__PURE__ */ jsxs(Text, { children: [
13
+ theme.status(Status.core, "success"),
14
+ " ",
15
+ state.label
16
+ ] });
17
+ return /* @__PURE__ */ jsxs(Text, { children: [
18
+ /* @__PURE__ */ jsx(Styled, {
19
+ token: "accent",
20
+ children: glyphs.spinner[frame % glyphs.spinner.length]
21
+ }),
22
+ " ",
23
+ state.label,
24
+ count
25
+ ] });
26
+ }
27
+ /**
28
+ * The resolve-progress live view, minus its events: one spinner line naming
29
+ * the phase and its count, committed as a check-marked line carrying the
30
+ * phase's finished label when it ends.
31
+ * Shared by the command and its test so the two never drift.
32
+ *
33
+ * @internal
34
+ */
35
+ const progressView = {
36
+ initial: initialProgress,
37
+ reduce: reduceProgress,
38
+ render: (state, frame) => /* @__PURE__ */ jsx(Progress, {
39
+ state,
40
+ frame
41
+ }),
42
+ isStart: (event) => event._tag === "Phase",
43
+ isTerminal: (event) => event._tag === "Finished"
44
+ };
45
+
46
+ //#endregion
47
+ export { progressView };
@@ -0,0 +1,31 @@
1
+ //#region src/cli/ui/progress.ts
2
+ /** The progress fold, kept free of React so it is tested without a terminal. @internal */
3
+ function reduceProgress(state, event) {
4
+ switch (event._tag) {
5
+ case "Phase": return {
6
+ label: event.label,
7
+ done: 0,
8
+ total: event.total,
9
+ finished: false
10
+ };
11
+ case "Step": return {
12
+ ...state,
13
+ done: event.done
14
+ };
15
+ case "Finished": return {
16
+ ...state,
17
+ label: event.label,
18
+ finished: true
19
+ };
20
+ }
21
+ }
22
+ /** The state before any phase begins. @internal */
23
+ const initialProgress = {
24
+ label: "",
25
+ done: 0,
26
+ total: 0,
27
+ finished: false
28
+ };
29
+
30
+ //#endregion
31
+ export { initialProgress, reduceProgress };
@@ -0,0 +1,40 @@
1
+ import { Effect, PubSub } from "effect";
2
+ import { CliInteractive } from "@effected/cli";
3
+ import { CliUi } from "@effected/cli/ui";
4
+
5
+ //#region src/cli/ui/screens.ts
6
+ /** The interactive upgrade table, loaded on mount. @internal */
7
+ function walkScreen(props) {
8
+ return CliUi.lazy(async () => ({ default: (await import("./walk-screen.js")).makeWalkScreen(props) }));
9
+ }
10
+ /** The read-only preview explorer, loaded on mount. @internal */
11
+ function previewScreen(views) {
12
+ return CliUi.lazy(async () => ({ default: (await import("./preview-screen.js")).makePreviewScreen(views) }));
13
+ }
14
+ /**
15
+ * Run `body` with a progress reporter. In an interactive run the reports
16
+ * drive a live spinner view that redraws in place; otherwise the reporter is a
17
+ * no-op and nothing is drawn or loaded, so piped and CI output stay clean. The
18
+ * view is closed — its last frame committed — before this returns, so a
19
+ * screen mounted next never waits on it.
20
+ *
21
+ * @internal
22
+ */
23
+ function withProgress(body) {
24
+ return Effect.gen(function* () {
25
+ if (!(yield* CliInteractive)) return yield* body(() => Effect.void);
26
+ const { progressView } = yield* Effect.promise(() => import("./progress-view.js"));
27
+ return yield* Effect.scoped(Effect.gen(function* () {
28
+ const pubsub = yield* PubSub.unbounded();
29
+ const events = yield* PubSub.subscribe(pubsub);
30
+ const view = yield* CliUi.live({
31
+ ...progressView,
32
+ events
33
+ });
34
+ return yield* body((event) => PubSub.publish(pubsub, event).pipe(Effect.asVoid)).pipe(Effect.ensuring(view.close));
35
+ }));
36
+ });
37
+ }
38
+
39
+ //#endregion
40
+ export { previewScreen, walkScreen, withProgress };
@@ -0,0 +1,176 @@
1
+ import { TONES } from "../render/tone.js";
2
+ import { computeGroupPeers } from "../interop-live.js";
3
+ import { cellTone, displayCandidates, initTable, peerFor, tableDecisions, tableLayout, tableStep, truncateEnd } from "../walk-reducer.js";
4
+ import { Fmt } from "@effected/cli";
5
+ import { KeyHelp, KeyTable, Styled, useKeys, useTerminalSize } from "@effected/cli/ui";
6
+ import { Box, Text } from "ink";
7
+ import { useRef, useState } from "react";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+
10
+ //#region src/cli/ui/walk-screen.tsx
11
+ /** The table's keys: arrows (or vim keys) to move and pick, Enter to finish. Esc and Ctrl-C are the host's. */
12
+ function walkKeys(dryRun) {
13
+ return KeyTable.make([
14
+ {
15
+ keys: ["up", { char: "k" }],
16
+ action: "up",
17
+ help: "previous"
18
+ },
19
+ {
20
+ keys: ["down", { char: "j" }],
21
+ action: "down",
22
+ help: "next"
23
+ },
24
+ {
25
+ keys: ["left", { char: "h" }],
26
+ action: "left",
27
+ help: "older"
28
+ },
29
+ {
30
+ keys: ["right", { char: "l" }],
31
+ action: "right",
32
+ help: "newer"
33
+ },
34
+ {
35
+ keys: ["enter"],
36
+ action: "submit",
37
+ help: dryRun ? "preview" : "update"
38
+ }
39
+ ]);
40
+ }
41
+ /**
42
+ * Lines the table's chrome takes besides its rows: title, banners, spacer, key
43
+ * help — plus one, because Ink treats a frame as tall as the terminal as
44
+ * fullscreen and clears the screen and its scrollback on every redraw.
45
+ */
46
+ function chromeHeight(props) {
47
+ return 4 + (props.dryRun ? 1 : 0) + (props.unresolved.length > 0 ? 1 : 0);
48
+ }
49
+ /**
50
+ * The interactive upgrade table. Every package is one row; each row is a radio
51
+ * group over its candidates with keep (index 0) preselected, so the default
52
+ * state of the table applies nothing. Enter resolves the screen with one
53
+ * decision per row; Esc and Ctrl-C cancel it (the host's `Cancelled`).
54
+ *
55
+ * @internal
56
+ */
57
+ function makeWalkScreen(props) {
58
+ const { items, dryRun, unresolved, interopModels } = props;
59
+ const keys = walkKeys(dryRun);
60
+ return ({ resolve }) => {
61
+ const Walk = () => {
62
+ const table = useRef(initTable(items));
63
+ const [state, setState] = useState(table.current);
64
+ const { columns, rows: termRows } = useTerminalSize();
65
+ useKeys(keys, (key) => {
66
+ const current = table.current;
67
+ const next = tableStep(current, items, key);
68
+ table.current = next;
69
+ setState(next);
70
+ if (next.done && !current.done) resolve(tableDecisions(next, items));
71
+ });
72
+ if (state.done) return /* @__PURE__ */ jsx(Text, { children: "Done." });
73
+ const { pkgWidth, cellWidth, maxCells, blankCell, cellText } = tableLayout(items);
74
+ const fixedPrefix = 2 + (pkgWidth + 2) + maxCells * (cellWidth + 4) + 2;
75
+ const catalogs = new Set(items.map((i) => i.entry.catalog)).size;
76
+ const height = Math.max(3, Math.min(items.length, termRows - chromeHeight(props) - catalogs));
77
+ const start = Math.max(0, Math.min(state.cursor - Math.floor(height / 2), items.length - height));
78
+ const visible = items.slice(start, start + height);
79
+ const interopPeers = /* @__PURE__ */ new Map();
80
+ for (const [catalog, model] of interopModels) {
81
+ const selected = /* @__PURE__ */ new Map();
82
+ items.forEach((it, idx) => {
83
+ if (it.entry.catalog !== catalog) return;
84
+ const cand = displayCandidates(it)[state.picks[idx] ?? 0];
85
+ if (cand) selected.set(it.entry.pkg, cand.version);
86
+ });
87
+ interopPeers.set(catalog, computeGroupPeers(model, selected));
88
+ }
89
+ const rows = [];
90
+ let lastCatalog = null;
91
+ visible.forEach((item, offset) => {
92
+ const i = start + offset;
93
+ if (item.entry.catalog !== lastCatalog) {
94
+ lastCatalog = item.entry.catalog;
95
+ rows.push(/* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx(Styled, {
96
+ token: "muted",
97
+ children: ` ── catalog: ${Fmt.sanitize(lastCatalog)} ──`
98
+ }) }, `cat-${lastCatalog}`));
99
+ }
100
+ const onCursor = i === state.cursor;
101
+ const pick = state.picks[i] ?? 0;
102
+ const candidates = displayCandidates(item);
103
+ const chosen = candidates[pick];
104
+ const gp = interopPeers.get(item.entry.catalog);
105
+ const peerText = gp?.peer.get(item.entry.pkg) ?? (chosen === void 0 ? "—" : peerFor(item, chosen));
106
+ const conflict = gp?.conflict.get(item.entry.pkg);
107
+ const room = Math.max(8, columns - fixedPrefix - peerText.length - 3);
108
+ rows.push(/* @__PURE__ */ jsxs(Text, {
109
+ wrap: "truncate-end",
110
+ children: [
111
+ onCursor ? /* @__PURE__ */ jsx(Styled, {
112
+ token: "accent",
113
+ children: "❯ "
114
+ }) : " ",
115
+ /* @__PURE__ */ jsx(Text, {
116
+ bold: onCursor,
117
+ children: Fmt.sanitize(item.entry.pkg).padEnd(pkgWidth + 2)
118
+ }),
119
+ candidates.map((c, ci) => {
120
+ const t = cellTone(c, ci === pick);
121
+ const text = cellText(c, ci === pick);
122
+ return t === null ? text : /* @__PURE__ */ jsx(Styled, {
123
+ token: TONES[t],
124
+ children: text
125
+ }, c.kind);
126
+ }),
127
+ blankCell.repeat(maxCells - candidates.length),
128
+ /* @__PURE__ */ jsx(Styled, {
129
+ token: "muted",
130
+ children: `│ ${peerText}`
131
+ }),
132
+ conflict ? /* @__PURE__ */ jsx(Styled, {
133
+ token: "failure",
134
+ children: truncateEnd(` ⚠ needs ${Fmt.sanitize(conflict)}`, room)
135
+ }) : null,
136
+ item.peerWarning ? /* @__PURE__ */ jsx(Styled, {
137
+ token: "failure",
138
+ children: truncateEnd(` ⚠ ${Fmt.sanitize(item.peerWarning.message)}`, room)
139
+ }) : null
140
+ ]
141
+ }, `${item.entry.catalog}/${item.entry.pkg}`));
142
+ });
143
+ return /* @__PURE__ */ jsxs(Box, {
144
+ flexDirection: "column",
145
+ children: [
146
+ /* @__PURE__ */ jsx(Text, {
147
+ bold: true,
148
+ wrap: "truncate-end",
149
+ children: dryRun ? "Choose versions to preview" : "Choose versions to update"
150
+ }),
151
+ dryRun ? /* @__PURE__ */ jsx(Text, {
152
+ wrap: "truncate-end",
153
+ children: /* @__PURE__ */ jsx(Styled, {
154
+ token: "warning",
155
+ children: "DRY RUN — nothing will be written to the config"
156
+ })
157
+ }) : null,
158
+ unresolved.length > 0 ? /* @__PURE__ */ jsx(Text, {
159
+ wrap: "truncate-end",
160
+ children: /* @__PURE__ */ jsx(Styled, {
161
+ token: "failure",
162
+ children: `⚠ Could not resolve from the registry — check for a typo: ${unresolved.map(Fmt.sanitize).join(", ")}`
163
+ })
164
+ }) : null,
165
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
166
+ rows,
167
+ /* @__PURE__ */ jsx(KeyHelp, { tables: [keys] })
168
+ ]
169
+ });
170
+ };
171
+ return /* @__PURE__ */ jsx(Walk, {});
172
+ };
173
+ }
174
+
175
+ //#endregion
176
+ export { makeWalkScreen };
package/cli/version.js ADDED
@@ -0,0 +1,12 @@
1
+ //#region src/cli/version.ts
2
+ /**
3
+ * The CLI's version. The bundler substitutes `__PACKAGE_VERSION__` at build
4
+ * time, so this is not a runtime environment read; source runs report
5
+ * `0.0.0`.
6
+ *
7
+ * @internal
8
+ */
9
+ const CLI_VERSION = "2.0.0";
10
+
11
+ //#endregion
12
+ export { CLI_VERSION };
@@ -69,15 +69,14 @@ function initTable(items) {
69
69
  return {
70
70
  cursor: first === -1 ? 0 : first,
71
71
  picks: items.map(() => 0),
72
- done: false,
73
- cancelled: false
72
+ done: false
74
73
  };
75
74
  }
76
75
  const clamp = (n, max) => n < 0 ? 0 : n > max ? max : n;
77
76
  /**
78
77
  * Advance the table by a key. up/down move between rows; left/right move the
79
- * radio selection within the row under the cursor; submit applies; cancel exits
80
- * without applying. Both axes clamp at their ends rather than wrapping.
78
+ * radio selection within the row under the cursor; submit applies. Both axes
79
+ * clamp at their ends rather than wrapping.
81
80
  *
82
81
  * @internal
83
82
  */
@@ -87,11 +86,6 @@ function tableStep(state, items, key) {
87
86
  ...state,
88
87
  done: true
89
88
  };
90
- if (key === "cancel") return {
91
- ...state,
92
- done: true,
93
- cancelled: true
94
- };
95
89
  if (items.length === 0) return state;
96
90
  if (key === "up") return {
97
91
  ...state,
@@ -111,13 +105,11 @@ function tableStep(state, items, key) {
111
105
  };
112
106
  }
113
107
  /**
114
- * Project the table's selections into decisions. A cancelled table yields none,
115
- * so nothing is written.
108
+ * Project the table's selections into decisions, one per row.
116
109
  *
117
110
  * @internal
118
111
  */
119
112
  function tableDecisions(state, items) {
120
- if (state.cancelled) return [];
121
113
  return items.map((item, i) => ({
122
114
  item,
123
115
  chosen: displayCandidates(item)[state.picks[i] ?? 0]
@@ -139,23 +131,20 @@ function peerFor(item, chosen) {
139
131
  return item.entry.peer?.value ?? "—";
140
132
  }
141
133
  /**
142
- * The Ink color for one candidate cell, or null for the terminal's default.
143
- *
144
- * Only a SELECTED UPGRADE is colored — green in-range, yellow for a major. A
145
- * selected KEEP is deliberately left uncolored: it is the current value, not a
146
- * change, and coloring it dim made the column the eye lands on first read as
147
- * disabled. Unselected cells are always default.
134
+ * The tone of one candidate cell, or null for the default (untoned) text.
148
135
  *
149
- * Extracted from the render so it can be unit-tested: `ink-testing-library`
150
- * strips ANSI from `lastFrame()`, so a color assertion is impossible against
151
- * the rendered output.
136
+ * Only a SELECTED UPGRADE is toned — `added` for in-range, `changed` for a
137
+ * major. A selected KEEP is deliberately left untoned in the interactive
138
+ * table: it is the current value, not a change, and dimming it made the column
139
+ * the eye lands on first read as disabled. Unselected cells are always
140
+ * default. Shared by the table and its summary mirror so the two agree.
152
141
  *
153
142
  * @internal
154
143
  */
155
- function cellColor(candidate, selected) {
144
+ function cellTone(candidate, selected) {
156
145
  if (!selected || candidate.kind === "keep") return null;
157
- return candidate.isMajor ? "yellow" : "green";
146
+ return candidate.isMajor ? "changed" : "added";
158
147
  }
159
148
 
160
149
  //#endregion
161
- export { MAJOR_SUFFIX, cellColor, displayCandidates, initTable, peerFor, tableDecisions, tableLayout, tableStep, truncateEnd };
150
+ export { MAJOR_SUFFIX, cellTone, displayCandidates, initTable, peerFor, tableDecisions, tableLayout, tableStep, truncateEnd };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rolldown-pnpm-config",
3
- "version": "1.0.11",
3
+ "version": "2.0.0",
4
4
  "private": false,
5
5
  "description": "A dogfooding example of our plugin",
6
6
  "repository": {
@@ -36,16 +36,18 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@effect/platform-node": "^4.0.0",
39
+ "@effected/cli": "^0.11.0",
40
+ "@effected/env": "^0.1.0",
41
+ "@effected/glob": "^0.10.0",
39
42
  "@effected/npm": "^0.20.0",
40
43
  "@effected/semver": "^0.11.0",
44
+ "@effected/walker": "^0.15.0",
41
45
  "@effected/workspaces": "^0.31.0",
42
46
  "@effected/yaml": "^0.19.0",
43
47
  "effect": "^4.0.0",
44
48
  "ink": "^7.1.1",
45
- "ink-tab": "^5.2.0",
46
49
  "oxc-parser": "^0.152.0",
47
- "react": "^19.3.0",
48
- "std-env": "^4.3.0"
50
+ "react": "^19.3.0"
49
51
  },
50
52
  "peerDependencies": {
51
53
  "rolldown": "^1.2.0"
package/cli/ui/Preview.js DELETED
@@ -1,65 +0,0 @@
1
- import { legendLines, simulatedLegendLines } from "./legend.js";
2
- import { Box, Text, useApp, useInput } from "ink";
3
- import { createElement, useState } from "react";
4
- import { Tab, Tabs } from "ink-tab";
5
-
6
- //#region src/cli/ui/Preview.ts
7
- const INK_PROPS = {
8
- added: { color: "green" },
9
- removed: { color: "red" },
10
- changed: { color: "yellow" },
11
- warn: { color: "red" },
12
- local: { color: "magenta" },
13
- unmanaged: { color: "#585858" },
14
- unchanged: { dimColor: true },
15
- plain: {},
16
- merge: { color: "cyan" },
17
- overwrite: { color: "magenta" }
18
- };
19
- const TabC = Tab;
20
- const TabsC = Tabs;
21
- function renderLines(lines) {
22
- return createElement(Box, { flexDirection: "column" }, ...lines.map((l, i) => {
23
- const indent = " ".repeat(l.indent);
24
- const tag = l.tag === "local" ? " (local)" : "";
25
- const body = l.segments.map((s, j) => {
26
- const props = INK_PROPS[s.style] ?? {};
27
- return createElement(Text, {
28
- key: j,
29
- ...props
30
- }, s.text);
31
- });
32
- return createElement(Text, { key: i }, `${l.gutter} ${indent}`, ...body, tag);
33
- }));
34
- }
35
- /**
36
- * Interactive export preview: an ink-tab bar over the Changes / Full /
37
- * Simulated views. `q`/Esc exits. Written with React.createElement (no JSX).
38
- *
39
- * @internal
40
- */
41
- function Preview({ views, onExit }) {
42
- const app = useApp();
43
- const [active, setActive] = useState("changes");
44
- useInput((input, key) => {
45
- if (input === "q" || key.escape || key.return) {
46
- onExit();
47
- app.exit();
48
- }
49
- });
50
- const tabs = createElement(TabsC, { onChange: (name) => setActive(name) }, createElement(TabC, {
51
- name: "changes",
52
- key: "changes"
53
- }, "Changes"), createElement(TabC, {
54
- name: "full",
55
- key: "full"
56
- }, "Full"), createElement(TabC, {
57
- name: "simulated",
58
- key: "simulated"
59
- }, "Simulated"));
60
- const legend = renderLines(active === "simulated" ? simulatedLegendLines() : legendLines());
61
- return createElement(Box, { flexDirection: "column" }, tabs, legend, renderLines(views[active]));
62
- }
63
-
64
- //#endregion
65
- export { Preview };
package/cli/ui/Walk.js DELETED
@@ -1,89 +0,0 @@
1
- import { computeGroupPeers } from "../interop-live.js";
2
- import { cellColor, displayCandidates, initTable, peerFor, tableDecisions, tableLayout, tableStep, truncateEnd } from "../walk-reducer.js";
3
- import { Box, Text, useApp, useInput } from "ink";
4
- import { createElement, useEffect, useState } from "react";
5
-
6
- //#region src/cli/ui/Walk.ts
7
- /** Rows visible at once before the viewport scrolls. */
8
- const VIEWPORT = 20;
9
- /**
10
- * Interactive upgrade table rendered with Ink. Every package is one row; each
11
- * row is a radio group over its candidates with keep (index 0) preselected, so
12
- * the default state of the table applies nothing.
13
- *
14
- * Written with React.createElement (no JSX) so the file can be plain .ts
15
- * without requiring TSX transform configuration.
16
- *
17
- * @internal
18
- */
19
- function Walk({ items, onDone, dryRun = false, unresolved = [], interopModels = /* @__PURE__ */ new Map() }) {
20
- const app = useApp();
21
- const [state, setState] = useState(() => initTable(items));
22
- useEffect(() => {
23
- if (items.length === 0) {
24
- onDone([]);
25
- app.exit();
26
- }
27
- }, []);
28
- useInput((_input, key) => {
29
- if (state.done) return;
30
- const which = key.upArrow ? "up" : key.downArrow ? "down" : key.leftArrow ? "left" : key.rightArrow ? "right" : key.return ? "submit" : key.escape ? "cancel" : null;
31
- if (!which) return;
32
- const next = tableStep(state, items, which);
33
- setState(next);
34
- if (next.done) {
35
- onDone(tableDecisions(next, items));
36
- app.exit();
37
- }
38
- });
39
- if (state.done || items.length === 0) return createElement(Text, null, "Done.");
40
- const { pkgWidth, cellWidth, maxCells, blankCell, cellText } = tableLayout(items);
41
- const termCols = process.stdout.columns ?? 100;
42
- const fixedPrefix = 2 + (pkgWidth + 2) + maxCells * (cellWidth + 4) + 2;
43
- const start = Math.max(0, Math.min(state.cursor - Math.floor(VIEWPORT / 2), items.length - VIEWPORT));
44
- const visible = items.slice(Math.max(0, start), Math.max(0, start) + VIEWPORT);
45
- const interopPeers = /* @__PURE__ */ new Map();
46
- for (const [catalog, model] of interopModels) {
47
- const selected = /* @__PURE__ */ new Map();
48
- items.forEach((it, idx) => {
49
- if (it.entry.catalog !== catalog) return;
50
- const cand = displayCandidates(it)[state.picks[idx] ?? 0];
51
- if (cand) selected.set(it.entry.pkg, cand.version);
52
- });
53
- interopPeers.set(catalog, computeGroupPeers(model, selected));
54
- }
55
- const rows = [];
56
- let lastCatalog = null;
57
- visible.forEach((item, offset) => {
58
- const i = Math.max(0, start) + offset;
59
- if (item.entry.catalog !== lastCatalog) {
60
- lastCatalog = item.entry.catalog;
61
- rows.push(createElement(Text, {
62
- key: `cat-${item.entry.catalog}`,
63
- dimColor: true
64
- }, ` ── catalog: ${lastCatalog} ──`));
65
- }
66
- const onCursor = i === state.cursor;
67
- const pick = state.picks[i] ?? 0;
68
- const candidates = displayCandidates(item);
69
- const cells = candidates.map((c, ci) => {
70
- const selected = ci === pick;
71
- const color = cellColor(c, selected);
72
- return createElement(Text, {
73
- key: c.kind,
74
- ...color ? { color } : {}
75
- }, cellText(c, selected));
76
- });
77
- for (let ci = candidates.length; ci < maxCells; ci++) cells.push(createElement(Text, { key: `blank-${ci}` }, blankCell));
78
- const chosen = candidates[pick];
79
- const gp = interopPeers.get(item.entry.catalog);
80
- const peerText = gp?.peer.get(item.entry.pkg) ?? (chosen === void 0 ? "—" : peerFor(item, chosen));
81
- const conflict = gp?.conflict.get(item.entry.pkg);
82
- const room = Math.max(8, termCols - fixedPrefix - peerText.length - 3);
83
- rows.push(createElement(Box, { key: `${item.entry.catalog}/${item.entry.pkg}` }, createElement(Text, { ...onCursor ? { color: "cyan" } : {} }, onCursor ? "❯ " : " "), createElement(Text, { bold: onCursor }, item.entry.pkg.padEnd(pkgWidth + 2)), ...cells, createElement(Text, { dimColor: true }, `│ ${peerText}`), conflict ? createElement(Text, { color: "red" }, truncateEnd(` ⚠ needs ${conflict}`, room)) : null, item.peerWarning ? createElement(Text, { color: "red" }, truncateEnd(` ⚠ ${item.peerWarning.message}`, room)) : null));
84
- });
85
- return createElement(Box, { flexDirection: "column" }, createElement(Text, { bold: true }, dryRun ? "Enter to preview • Esc to cancel" : "Enter to update • Esc to cancel"), dryRun ? createElement(Text, { color: "yellow" }, "DRY RUN — nothing will be written to the config") : null, unresolved.length > 0 ? createElement(Text, { color: "red" }, `⚠ Could not resolve from the registry — check for a typo: ${unresolved.join(", ")}`) : null, createElement(Box, { height: 1 }), ...rows);
86
- }
87
-
88
- //#endregion
89
- export { Walk };