rolldown-pnpm-config 1.0.10 → 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.
- package/README.md +2 -0
- package/bin/rolldown-pnpm-config.js +2 -12
- package/cli/commands/export.js +23 -20
- package/cli/commands/preview.js +23 -20
- package/cli/commands/upgrade.js +273 -181
- package/cli/cwd.js +25 -0
- package/cli/diff/render.js +19 -37
- package/cli/load-config.js +4 -5
- package/cli/main.js +37 -0
- package/cli/preview-views.js +2 -2
- package/cli/render/legend.js +42 -0
- package/cli/render/print.js +50 -0
- package/cli/render/report.js +38 -0
- package/cli/render/tone.js +43 -0
- package/cli/root.js +22 -0
- package/cli/simulated-view.js +12 -53
- package/cli/summary.js +19 -96
- package/cli/ui/preview-screen.js +93 -0
- package/cli/ui/progress-view.js +47 -0
- package/cli/ui/progress.js +31 -0
- package/cli/ui/screens.js +40 -0
- package/cli/ui/walk-screen.js +176 -0
- package/cli/version.js +12 -0
- package/cli/walk-reducer.js +13 -24
- package/package.json +12 -10
- package/cli/ui/Preview.js +0 -65
- package/cli/ui/Walk.js +0 -89
- package/cli/ui/ansi.js +0 -20
- package/cli/ui/env.js +0 -18
- package/cli/ui/legend.js +0 -80
- package/cli/ui/run-preview.js +0 -22
- package/cli/ui/run-walk.js +0 -33
- package/cli/ui/styled.js +0 -36
|
@@ -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 };
|
package/cli/walk-reducer.js
CHANGED
|
@@ -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
|
|
80
|
-
*
|
|
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
|
|
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
|
|
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
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* the
|
|
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
|
|
144
|
+
function cellTone(candidate, selected) {
|
|
156
145
|
if (!selected || candidate.kind === "keep") return null;
|
|
157
|
-
return candidate.isMajor ? "
|
|
146
|
+
return candidate.isMajor ? "changed" : "added";
|
|
158
147
|
}
|
|
159
148
|
|
|
160
149
|
//#endregion
|
|
161
|
-
export { MAJOR_SUFFIX,
|
|
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": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A dogfooding example of our plugin",
|
|
6
6
|
"repository": {
|
|
@@ -35,17 +35,19 @@
|
|
|
35
35
|
"rolldown-pnpm-config": "bin/rolldown-pnpm-config.js"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@effect/platform-node": "4.0.0
|
|
39
|
-
"@effected/
|
|
40
|
-
"@effected/
|
|
41
|
-
"@effected/
|
|
42
|
-
"@effected/
|
|
43
|
-
"
|
|
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",
|
|
42
|
+
"@effected/npm": "^0.20.0",
|
|
43
|
+
"@effected/semver": "^0.11.0",
|
|
44
|
+
"@effected/walker": "^0.15.0",
|
|
45
|
+
"@effected/workspaces": "^0.31.0",
|
|
46
|
+
"@effected/yaml": "^0.19.0",
|
|
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.2.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 };
|