braeburn 1.4.2 → 1.5.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 +12 -12
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/config.js +56 -57
- package/dist/commands/setup.d.ts +2 -3
- package/dist/commands/setup.js +19 -20
- package/dist/commands/update.js +19 -2
- package/dist/config.d.ts +5 -1
- package/dist/config.js +55 -16
- package/dist/index.js +13 -7
- package/dist/steps/catalog.js +24 -11
- package/dist/steps/categories.d.ts +7 -0
- package/dist/steps/categories.js +18 -0
- package/dist/steps/cleanup.js +2 -2
- package/dist/steps/dotnet.js +1 -1
- package/dist/steps/grouping.d.ts +10 -0
- package/dist/steps/grouping.js +15 -0
- package/dist/steps/homebrew.js +1 -1
- package/dist/steps/index.d.ts +5 -1
- package/dist/steps/index.js +2 -0
- package/dist/steps/macos.js +1 -1
- package/dist/steps/mas.js +1 -1
- package/dist/steps/npm.js +1 -1
- package/dist/steps/nvm.js +1 -1
- package/dist/steps/ohmyzsh.js +1 -1
- package/dist/steps/pip.js +1 -1
- package/dist/steps/pyenv.js +1 -1
- package/dist/steps/types.d.ts +2 -2
- package/dist/ui/activityIndicator.d.ts +1 -0
- package/dist/ui/activityIndicator.js +5 -0
- package/dist/ui/currentStep.d.ts +1 -0
- package/dist/ui/currentStep.js +3 -1
- package/dist/ui/header.d.ts +2 -1
- package/dist/ui/header.js +14 -21
- package/dist/ui/screen.d.ts +1 -0
- package/dist/ui/screen.js +5 -0
- package/dist/ui/state.d.ts +1 -1
- package/dist/update/displayStep.js +1 -1
- package/dist/update/state.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,20 +32,20 @@ braeburn homebrew npm # run specific steps only
|
|
|
32
32
|
|
|
33
33
|
## Steps
|
|
34
34
|
|
|
35
|
-
Steps
|
|
35
|
+
Steps are grouped by system capability. For new setups, braeburn uses a conservative default profile: only package-manager-driven CLI/tooling updates are enabled by default.
|
|
36
36
|
|
|
37
|
-
| Step |
|
|
37
|
+
| Step | Category | Default (new setup) | Requires |
|
|
38
38
|
|---|---|---|---|
|
|
39
|
-
| `pyenv` |
|
|
40
|
-
| `nvm` |
|
|
41
|
-
| `homebrew` |
|
|
42
|
-
| `mas` |
|
|
43
|
-
| `
|
|
44
|
-
| `npm` |
|
|
45
|
-
| `pip` |
|
|
46
|
-
| `dotnet` |
|
|
47
|
-
| `
|
|
48
|
-
| `cleanup` |
|
|
39
|
+
| `pyenv` | Runtimes | off | `pyenv` or Homebrew |
|
|
40
|
+
| `nvm` | Runtimes | off | `~/.nvm` |
|
|
41
|
+
| `homebrew` | Apps & Packages | on | `brew` (required) |
|
|
42
|
+
| `mas` | Apps & Packages | off | `mas` |
|
|
43
|
+
| `macos` | Apps & Packages | off | — |
|
|
44
|
+
| `npm` | CLI Tools | on | `npm` |
|
|
45
|
+
| `pip` | CLI Tools | on | `pip3` |
|
|
46
|
+
| `dotnet` | CLI Tools | on | `dotnet` |
|
|
47
|
+
| `ohmyzsh` | Shell | off | `~/.oh-my-zsh` |
|
|
48
|
+
| `cleanup` (`homebrew cleanup`) | Maintenance | off | `brew` |
|
|
49
49
|
|
|
50
50
|
## Requirements
|
|
51
51
|
|
package/dist/commands/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { readConfig, writeConfig, resolveConfigPath, isSettingEnabled, isStepEnabled, isLogoEnabled, applySettingToConfig,
|
|
3
|
+
import { readConfig, writeConfig, resolveConfigPath, isSettingEnabled, isStepEnabled, isLogoEnabled, applySettingToConfig, cleanConfigForWrite, PROTECTED_STEP_IDS, } from "../config.js";
|
|
4
|
+
import { buildCategorySectionsInOrder, getStepCategoryLabel, } from "../steps/index.js";
|
|
4
5
|
import { createScreenRenderer } from "../ui/screen.js";
|
|
5
6
|
import { hideCursorDuringExecution } from "../ui/terminal.js";
|
|
6
7
|
export async function runConfigCommand(options) {
|
|
@@ -50,7 +51,7 @@ export async function runConfigUpdateCommand(options) {
|
|
|
50
51
|
}
|
|
51
52
|
const config = await readConfig();
|
|
52
53
|
const { updatedConfig, changes } = applyConfigUpdates(config, settingUpdates);
|
|
53
|
-
await
|
|
54
|
+
await writeConfig(cleanConfigForWrite(updatedConfig));
|
|
54
55
|
if (changes.length === 0) {
|
|
55
56
|
process.stdout.write("No changes — already set as requested.\n");
|
|
56
57
|
return;
|
|
@@ -66,52 +67,61 @@ export async function runConfigUpdateCommand(options) {
|
|
|
66
67
|
export function buildConfigTableOutput(options) {
|
|
67
68
|
const { config, configPath, allSteps } = options;
|
|
68
69
|
const lines = [];
|
|
69
|
-
const
|
|
70
|
-
const divider = "─".repeat(stepColumnWidth + 16);
|
|
70
|
+
const settingColumnWidth = 24;
|
|
71
71
|
lines.push(`Config: ${chalk.dim(configPath)}`);
|
|
72
72
|
lines.push("");
|
|
73
|
-
lines.push(
|
|
74
|
-
lines.push(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
73
|
+
lines.push(chalk.bold("System"));
|
|
74
|
+
lines.push(`${"Setting".padEnd(settingColumnWidth)}Status`);
|
|
75
|
+
lines.push("─".repeat(settingColumnWidth + 16));
|
|
76
|
+
for (const section of buildCategorySectionsInOrder(allSteps)) {
|
|
77
|
+
lines.push(chalk.dim(` ${getStepCategoryLabel(section.categoryId)}`));
|
|
78
|
+
for (const step of section.items) {
|
|
79
|
+
const isProtected = PROTECTED_STEP_IDS.has(step.id);
|
|
80
|
+
const enabled = isStepEnabled(config, step.id);
|
|
81
|
+
let statusText;
|
|
82
|
+
if (isProtected) {
|
|
83
|
+
statusText = chalk.dim("always enabled");
|
|
84
|
+
}
|
|
85
|
+
else if (enabled) {
|
|
86
|
+
statusText = chalk.green("enabled");
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
statusText = chalk.red("disabled");
|
|
90
|
+
}
|
|
91
|
+
lines.push(`${step.name.padEnd(settingColumnWidth)}${statusText}`);
|
|
90
92
|
}
|
|
91
|
-
lines.push(
|
|
93
|
+
lines.push("");
|
|
92
94
|
}
|
|
95
|
+
lines.push(chalk.bold("Interface"));
|
|
96
|
+
lines.push(`${"Setting".padEnd(settingColumnWidth)}Status`);
|
|
97
|
+
lines.push("─".repeat(settingColumnWidth + 16));
|
|
98
|
+
const logoEnabled = isLogoEnabled(config);
|
|
99
|
+
lines.push(`${"logo".padEnd(settingColumnWidth)}${logoEnabled ? chalk.green("enabled") : chalk.red("disabled")}`);
|
|
93
100
|
lines.push("");
|
|
94
101
|
return lines.join("\n") + "\n";
|
|
95
102
|
}
|
|
96
103
|
function buildConfigViewItems(config, allSteps) {
|
|
97
|
-
const viewItems = [
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
label: step.id,
|
|
110
|
-
description: step.description,
|
|
111
|
-
protection: PROTECTED_STEP_IDS.has(step.id) ? "protected" : "configurable",
|
|
112
|
-
selection: isStepEnabled(config, step.id) ? "enabled" : "disabled",
|
|
113
|
-
});
|
|
104
|
+
const viewItems = [];
|
|
105
|
+
for (const section of buildCategorySectionsInOrder(allSteps)) {
|
|
106
|
+
for (const step of section.items) {
|
|
107
|
+
viewItems.push({
|
|
108
|
+
id: step.id,
|
|
109
|
+
label: step.name,
|
|
110
|
+
description: step.description,
|
|
111
|
+
sectionLabel: `System / ${getStepCategoryLabel(step.categoryId)}`,
|
|
112
|
+
protection: PROTECTED_STEP_IDS.has(step.id) ? "protected" : "configurable",
|
|
113
|
+
selection: isStepEnabled(config, step.id) ? "enabled" : "disabled",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
114
116
|
}
|
|
117
|
+
viewItems.push({
|
|
118
|
+
id: "logo",
|
|
119
|
+
label: "logo",
|
|
120
|
+
description: "Show the braeburn logo in command output",
|
|
121
|
+
sectionLabel: "Interface",
|
|
122
|
+
protection: "configurable",
|
|
123
|
+
selection: isLogoEnabled(config) ? "enabled" : "disabled",
|
|
124
|
+
});
|
|
115
125
|
return viewItems;
|
|
116
126
|
}
|
|
117
127
|
function buildInteractiveConfigScreen(options) {
|
|
@@ -124,9 +134,13 @@ function buildInteractiveConfigScreen(options) {
|
|
|
124
134
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
125
135
|
const item = items[itemIndex];
|
|
126
136
|
const isCursor = itemIndex === cursorIndex;
|
|
137
|
+
const previousItem = items[itemIndex - 1];
|
|
138
|
+
if (!previousItem || previousItem.sectionLabel !== item.sectionLabel) {
|
|
139
|
+
lines.push(` ${chalk.dim(`── ${item.sectionLabel} ──────────────────────────────────────────────`)}`);
|
|
140
|
+
}
|
|
127
141
|
const cursor = isCursor ? chalk.cyan("›") : " ";
|
|
128
142
|
const marker = item.selection === "enabled" ? chalk.green("●") : chalk.dim("○");
|
|
129
|
-
const label = isCursor ? chalk.bold.white(item.label.padEnd(
|
|
143
|
+
const label = isCursor ? chalk.bold.white(item.label.padEnd(24)) : chalk.white(item.label.padEnd(24));
|
|
130
144
|
let status;
|
|
131
145
|
if (item.protection === "protected") {
|
|
132
146
|
status = chalk.dim("always enabled");
|
|
@@ -170,7 +184,7 @@ async function runInteractiveConfigView(options) {
|
|
|
170
184
|
process.stdin.pause();
|
|
171
185
|
resolve(result);
|
|
172
186
|
};
|
|
173
|
-
const onKeypress = (
|
|
187
|
+
const onKeypress = (_typedCharacter, key) => {
|
|
174
188
|
if (key.ctrl && key.name === "c") {
|
|
175
189
|
process.exit(130);
|
|
176
190
|
}
|
|
@@ -219,7 +233,7 @@ async function runInteractiveConfigView(options) {
|
|
|
219
233
|
completionOutput = "No changes — already set as requested.\n";
|
|
220
234
|
}
|
|
221
235
|
else {
|
|
222
|
-
await
|
|
236
|
+
await writeConfig(cleanConfigForWrite(updatedConfig));
|
|
223
237
|
const outputLines = [];
|
|
224
238
|
for (const { label, from, to } of changes) {
|
|
225
239
|
const fromLabel = from === "enable" ? chalk.green("enabled") : chalk.red("disabled");
|
|
@@ -237,18 +251,3 @@ async function runInteractiveConfigView(options) {
|
|
|
237
251
|
}
|
|
238
252
|
process.stdout.write(completionOutput);
|
|
239
253
|
}
|
|
240
|
-
async function writeCleanConfig(config) {
|
|
241
|
-
const cleaned = { steps: {} };
|
|
242
|
-
for (const [stepId, value] of Object.entries(config.steps)) {
|
|
243
|
-
if (value === false && !DEFAULT_OFF_STEP_IDS.has(stepId)) {
|
|
244
|
-
cleaned.steps[stepId] = false;
|
|
245
|
-
}
|
|
246
|
-
else if (value === true && DEFAULT_OFF_STEP_IDS.has(stepId)) {
|
|
247
|
-
cleaned.steps[stepId] = true;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
if (config.logo === false) {
|
|
251
|
-
cleaned.logo = false;
|
|
252
|
-
}
|
|
253
|
-
await writeConfig(cleaned);
|
|
254
|
-
}
|
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { StepStage } from "../update/state.js";
|
|
1
|
+
import { type Step, type StepCategoryId } from "../steps/index.js";
|
|
3
2
|
export type SelectionState = "selected" | "deselected";
|
|
4
3
|
export type ProtectionStatus = "protected" | "configurable";
|
|
5
4
|
export type AvailabilityStatus = "available" | "unavailable";
|
|
@@ -7,7 +6,7 @@ export type SetupStepView = {
|
|
|
7
6
|
id: string;
|
|
8
7
|
name: string;
|
|
9
8
|
description: string;
|
|
10
|
-
|
|
9
|
+
categoryId: StepCategoryId;
|
|
11
10
|
brewPackageToInstall?: string;
|
|
12
11
|
};
|
|
13
12
|
export type SelectableStep = {
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import readline from "node:readline";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { writeConfig, PROTECTED_STEP_IDS } from "../config.js";
|
|
3
|
+
import { writeConfig, PROTECTED_STEP_IDS, CONSERVATIVE_DEFAULT_ON_STEP_IDS, cleanConfigForWrite, } from "../config.js";
|
|
4
4
|
import { LOGO_ART } from "../logo.js";
|
|
5
5
|
import { createScreenRenderer } from "../ui/screen.js";
|
|
6
6
|
import { hideCursorDuringExecution } from "../ui/terminal.js";
|
|
7
|
+
import { getStepCategoryLabel, } from "../steps/index.js";
|
|
7
8
|
export function buildLoadingScreen() {
|
|
8
9
|
const lines = [
|
|
9
10
|
chalk.yellow(LOGO_ART),
|
|
@@ -27,21 +28,13 @@ export function buildSetupScreen(items, cursorIndex) {
|
|
|
27
28
|
` ${chalk.dim("\u2191\u2193 navigate Space toggle Return confirm")}`,
|
|
28
29
|
"",
|
|
29
30
|
];
|
|
30
|
-
const hasRuntimeItems = items.some((item) => item.step.stage === "runtime");
|
|
31
|
-
const hasToolsItems = items.some((item) => item.step.stage === "tools");
|
|
32
|
-
const showStageLabels = hasRuntimeItems && hasToolsItems;
|
|
33
31
|
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
|
34
32
|
const item = items[itemIndex];
|
|
35
33
|
const isCursor = itemIndex === cursorIndex;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
lines.push(` ${chalk.dim("── Runtimes ─────────────────────────────────────────────────────────")}`);
|
|
41
|
-
}
|
|
42
|
-
else if (isFirstTools) {
|
|
43
|
-
lines.push(` ${chalk.dim("── Tools ────────────────────────────────────────────────────────────")}`);
|
|
44
|
-
}
|
|
34
|
+
const previousItem = items[itemIndex - 1];
|
|
35
|
+
if (!previousItem || previousItem.step.categoryId !== item.step.categoryId) {
|
|
36
|
+
const categoryLabel = getStepCategoryLabel(item.step.categoryId);
|
|
37
|
+
lines.push(` ${chalk.dim(`── System / ${categoryLabel} ──────────────────────────────────────────`)}`);
|
|
45
38
|
}
|
|
46
39
|
const cursor = isCursor ? chalk.cyan("\u203a") : " ";
|
|
47
40
|
const checkbox = item.selection === "selected" ? chalk.green("\u25cf") : chalk.dim("\u25cb");
|
|
@@ -82,10 +75,12 @@ export async function runSetupCommand(allSteps) {
|
|
|
82
75
|
id: step.id,
|
|
83
76
|
name: step.name,
|
|
84
77
|
description: step.description,
|
|
85
|
-
|
|
78
|
+
categoryId: step.categoryId,
|
|
86
79
|
brewPackageToInstall: step.brewPackageToInstall,
|
|
87
80
|
},
|
|
88
|
-
selection: PROTECTED_STEP_IDS.has(step.id) || step.
|
|
81
|
+
selection: PROTECTED_STEP_IDS.has(step.id) || CONSERVATIVE_DEFAULT_ON_STEP_IDS.has(step.id)
|
|
82
|
+
? "selected"
|
|
83
|
+
: "deselected",
|
|
89
84
|
protection: PROTECTED_STEP_IDS.has(step.id) ? "protected" : "configurable",
|
|
90
85
|
availability: availabilityResults[stepIndex] ? "available" : "unavailable",
|
|
91
86
|
}));
|
|
@@ -95,7 +90,7 @@ export async function runSetupCommand(allSteps) {
|
|
|
95
90
|
readline.emitKeypressEvents(process.stdin);
|
|
96
91
|
if (process.stdin.isTTY)
|
|
97
92
|
process.stdin.setRawMode(true);
|
|
98
|
-
const handleKeypress = (
|
|
93
|
+
const handleKeypress = (_typedCharacter, key) => {
|
|
99
94
|
if (key?.ctrl && key?.name === "c") {
|
|
100
95
|
process.exit(130);
|
|
101
96
|
}
|
|
@@ -125,13 +120,17 @@ export async function runSetupCommand(allSteps) {
|
|
|
125
120
|
process.stdin.on("keypress", handleKeypress);
|
|
126
121
|
process.stdin.resume();
|
|
127
122
|
});
|
|
128
|
-
const
|
|
123
|
+
const draftConfig = {
|
|
124
|
+
defaultsProfile: "conservative-v2",
|
|
125
|
+
steps: {},
|
|
126
|
+
};
|
|
129
127
|
for (const item of items) {
|
|
130
|
-
if (item.protection === "
|
|
131
|
-
|
|
128
|
+
if (item.protection === "protected") {
|
|
129
|
+
continue;
|
|
132
130
|
}
|
|
131
|
+
draftConfig.steps[item.step.id] = item.selection === "selected";
|
|
133
132
|
}
|
|
134
|
-
await writeConfig(
|
|
133
|
+
await writeConfig(cleanConfigForWrite(draftConfig));
|
|
135
134
|
const confirmationLines = [
|
|
136
135
|
chalk.yellow(LOGO_ART),
|
|
137
136
|
"",
|
package/dist/commands/update.js
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import { collectVersions } from "../update/versionCollector.js";
|
|
2
2
|
import { captureYesNo } from "../ui/prompt.js";
|
|
3
|
-
import { buildScreen, createScreenRenderer } from "../ui/screen.js";
|
|
3
|
+
import { buildScreen, buildScreenWithAnimationFrame, createScreenRenderer } from "../ui/screen.js";
|
|
4
4
|
import { hideCursorDuringExecution } from "../ui/terminal.js";
|
|
5
5
|
import { runUpdateEngine } from "../update/engine.js";
|
|
6
6
|
export async function runUpdateCommand(options) {
|
|
7
7
|
const renderScreen = createScreenRenderer();
|
|
8
8
|
const restoreCursor = hideCursorDuringExecution({ screenBuffer: "alternate" });
|
|
9
9
|
let finalScreen = "";
|
|
10
|
+
let latestState = undefined;
|
|
11
|
+
let animationFrameIndex = 0;
|
|
12
|
+
const animationTimer = setInterval(() => {
|
|
13
|
+
if (!latestState) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (latestState.runCompletion === "finished") {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (latestState.currentPhase !== "running" && latestState.currentPhase !== "installing") {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
animationFrameIndex += 1;
|
|
23
|
+
renderScreen(buildScreenWithAnimationFrame(latestState, animationFrameIndex));
|
|
24
|
+
}, 100);
|
|
10
25
|
try {
|
|
11
26
|
const finalState = await runUpdateEngine({
|
|
12
27
|
steps: options.steps,
|
|
@@ -16,12 +31,14 @@ export async function runUpdateCommand(options) {
|
|
|
16
31
|
askForConfirmation: captureYesNo,
|
|
17
32
|
collectVersions,
|
|
18
33
|
onStateChanged: (state) => {
|
|
19
|
-
|
|
34
|
+
latestState = state;
|
|
35
|
+
renderScreen(buildScreenWithAnimationFrame(state, animationFrameIndex));
|
|
20
36
|
},
|
|
21
37
|
});
|
|
22
38
|
finalScreen = buildScreen(finalState);
|
|
23
39
|
}
|
|
24
40
|
finally {
|
|
41
|
+
clearInterval(animationTimer);
|
|
25
42
|
restoreCursor();
|
|
26
43
|
}
|
|
27
44
|
if (finalScreen) {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export declare const PROTECTED_STEP_IDS: Set<string>;
|
|
2
|
-
export
|
|
2
|
+
export type ConfigDefaultsProfile = "legacy" | "conservative-v2";
|
|
3
|
+
export declare const LEGACY_DEFAULT_OFF_STEP_IDS: Set<string>;
|
|
4
|
+
export declare const CONSERVATIVE_DEFAULT_ON_STEP_IDS: Set<string>;
|
|
3
5
|
export type BraeburnConfig = {
|
|
4
6
|
steps: Record<string, boolean>;
|
|
5
7
|
logo?: boolean;
|
|
8
|
+
defaultsProfile?: ConfigDefaultsProfile;
|
|
6
9
|
};
|
|
7
10
|
export declare function resolveConfigPath(): Promise<string>;
|
|
8
11
|
export declare function configFileExists(): Promise<boolean>;
|
|
@@ -12,3 +15,4 @@ export declare function isSettingEnabled(config: BraeburnConfig, settingId: stri
|
|
|
12
15
|
export declare function isStepEnabled(config: BraeburnConfig, stepId: string): boolean;
|
|
13
16
|
export declare function isLogoEnabled(config: BraeburnConfig): boolean;
|
|
14
17
|
export declare function applySettingToConfig(config: BraeburnConfig, settingId: string, desiredState: "enable" | "disable"): BraeburnConfig;
|
|
18
|
+
export declare function cleanConfigForWrite(config: BraeburnConfig): BraeburnConfig;
|
package/dist/config.js
CHANGED
|
@@ -3,9 +3,30 @@ import { join } from "node:path";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { parse, stringify } from "smol-toml";
|
|
5
5
|
export const PROTECTED_STEP_IDS = new Set(["homebrew"]);
|
|
6
|
-
export const
|
|
6
|
+
export const LEGACY_DEFAULT_OFF_STEP_IDS = new Set(["nvm", "pyenv"]);
|
|
7
|
+
export const CONSERVATIVE_DEFAULT_ON_STEP_IDS = new Set(["homebrew", "npm", "pip", "dotnet"]);
|
|
7
8
|
const EMPTY_CONFIG = { steps: {} };
|
|
8
9
|
const LOGO_SETTING_ID = "logo";
|
|
10
|
+
const DEFAULTS_PROFILE_SETTING_ID = "defaultsProfile";
|
|
11
|
+
const LEGACY_PROFILE = "legacy";
|
|
12
|
+
function resolveDefaultsProfile(config) {
|
|
13
|
+
return config.defaultsProfile ?? LEGACY_PROFILE;
|
|
14
|
+
}
|
|
15
|
+
function parseDefaultsProfile(rawValue) {
|
|
16
|
+
if (rawValue === "legacy" || rawValue === "conservative-v2") {
|
|
17
|
+
return rawValue;
|
|
18
|
+
}
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
function isStepEnabledByDefault(profile, stepId) {
|
|
22
|
+
if (profile === "conservative-v2") {
|
|
23
|
+
return CONSERVATIVE_DEFAULT_ON_STEP_IDS.has(stepId);
|
|
24
|
+
}
|
|
25
|
+
return !LEGACY_DEFAULT_OFF_STEP_IDS.has(stepId);
|
|
26
|
+
}
|
|
27
|
+
function shouldPersistStepOverride(profile, stepId, desiredState) {
|
|
28
|
+
return isStepEnabledByDefault(profile, stepId) !== (desiredState === "enable");
|
|
29
|
+
}
|
|
9
30
|
async function pathExists(targetPath) {
|
|
10
31
|
try {
|
|
11
32
|
await access(targetPath);
|
|
@@ -31,7 +52,9 @@ export async function readConfig() {
|
|
|
31
52
|
try {
|
|
32
53
|
const raw = await readFile(configPath, "utf-8");
|
|
33
54
|
const parsed = parse(raw);
|
|
34
|
-
|
|
55
|
+
const parsedSteps = parsed.steps;
|
|
56
|
+
const defaultsProfile = parseDefaultsProfile(parsed[DEFAULTS_PROFILE_SETTING_ID]);
|
|
57
|
+
return { steps: parsedSteps ?? {}, logo: parsed.logo, defaultsProfile };
|
|
35
58
|
}
|
|
36
59
|
catch {
|
|
37
60
|
return structuredClone(EMPTY_CONFIG);
|
|
@@ -47,9 +70,12 @@ export function isSettingEnabled(config, settingId) {
|
|
|
47
70
|
return true;
|
|
48
71
|
if (settingId === LOGO_SETTING_ID)
|
|
49
72
|
return config.logo !== false;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
const defaultsProfile = resolveDefaultsProfile(config);
|
|
74
|
+
const explicitStepOverride = config.steps[settingId];
|
|
75
|
+
if (explicitStepOverride === undefined) {
|
|
76
|
+
return isStepEnabledByDefault(defaultsProfile, settingId);
|
|
77
|
+
}
|
|
78
|
+
return explicitStepOverride === true;
|
|
53
79
|
}
|
|
54
80
|
export function isStepEnabled(config, stepId) {
|
|
55
81
|
return isSettingEnabled(config, stepId);
|
|
@@ -68,20 +94,33 @@ export function applySettingToConfig(config, settingId, desiredState) {
|
|
|
68
94
|
}
|
|
69
95
|
return updatedConfig;
|
|
70
96
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
const defaultsProfile = resolveDefaultsProfile(updatedConfig);
|
|
98
|
+
const shouldPersistOverride = shouldPersistStepOverride(defaultsProfile, settingId, desiredState);
|
|
99
|
+
if (!shouldPersistOverride) {
|
|
100
|
+
delete updatedConfig.steps[settingId];
|
|
101
|
+
return updatedConfig;
|
|
102
|
+
}
|
|
103
|
+
updatedConfig.steps[settingId] = desiredState === "enable";
|
|
104
|
+
return updatedConfig;
|
|
105
|
+
}
|
|
106
|
+
export function cleanConfigForWrite(config) {
|
|
107
|
+
const defaultsProfile = resolveDefaultsProfile(config);
|
|
108
|
+
const cleaned = { steps: {} };
|
|
109
|
+
for (const [stepId, explicitState] of Object.entries(config.steps)) {
|
|
110
|
+
if (PROTECTED_STEP_IDS.has(stepId)) {
|
|
111
|
+
continue;
|
|
74
112
|
}
|
|
75
|
-
|
|
76
|
-
|
|
113
|
+
const desiredState = explicitState === true ? "enable" : "disable";
|
|
114
|
+
if (!shouldPersistStepOverride(defaultsProfile, stepId, desiredState)) {
|
|
115
|
+
continue;
|
|
77
116
|
}
|
|
78
|
-
|
|
117
|
+
cleaned.steps[stepId] = explicitState;
|
|
79
118
|
}
|
|
80
|
-
if (
|
|
81
|
-
|
|
119
|
+
if (config.logo === false) {
|
|
120
|
+
cleaned.logo = false;
|
|
82
121
|
}
|
|
83
|
-
|
|
84
|
-
|
|
122
|
+
if (config.defaultsProfile && config.defaultsProfile !== LEGACY_PROFILE) {
|
|
123
|
+
cleaned.defaultsProfile = config.defaultsProfile;
|
|
85
124
|
}
|
|
86
|
-
return
|
|
125
|
+
return cleaned;
|
|
87
126
|
}
|
package/dist/index.js
CHANGED
|
@@ -28,19 +28,25 @@ program
|
|
|
28
28
|
.option("--no-logo", "Hide the logo")
|
|
29
29
|
.addHelpText("after", `
|
|
30
30
|
Step descriptions:
|
|
31
|
-
|
|
31
|
+
System / Runtimes (default: off — larger changes, enabled intentionally):
|
|
32
32
|
pyenv Upgrade pyenv, install latest Python 3.x (requires: pyenv or brew)
|
|
33
33
|
nvm Install latest Node.js via nvm (requires: ~/.nvm)
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
System / Apps & Packages:
|
|
36
36
|
homebrew Update Homebrew itself and all installed formulae
|
|
37
37
|
mas Upgrade Mac App Store apps (requires: mas)
|
|
38
|
-
|
|
38
|
+
macos Check for macOS updates, prompt to install
|
|
39
|
+
|
|
40
|
+
System / CLI Tools:
|
|
39
41
|
npm Update global npm packages (requires: npm)
|
|
40
42
|
pip Update global pip3 packages (requires: pip3) ⚠ may be fragile
|
|
41
43
|
dotnet Update .NET global tools (requires: dotnet)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
|
|
45
|
+
System / Shell:
|
|
46
|
+
ohmyzsh Update Oh My Zsh (requires: ~/.oh-my-zsh)
|
|
47
|
+
|
|
48
|
+
System / Maintenance:
|
|
49
|
+
cleanup homebrew cleanup (remove outdated Homebrew cache/downloads)
|
|
44
50
|
|
|
45
51
|
Examples:
|
|
46
52
|
braeburn Run all enabled steps interactively
|
|
@@ -48,7 +54,7 @@ Examples:
|
|
|
48
54
|
braeburn -fy Same as above
|
|
49
55
|
braeburn homebrew npm Run only the homebrew and npm steps
|
|
50
56
|
braeburn homebrew -y Run only homebrew, auto-accept
|
|
51
|
-
braeburn nvm pyenv Run only the runtime
|
|
57
|
+
braeburn nvm pyenv Run only the runtime steps
|
|
52
58
|
`)
|
|
53
59
|
.action(async (stepArguments, options) => {
|
|
54
60
|
const autoYes = options.yes === true || options.force === true;
|
|
@@ -83,7 +89,7 @@ program
|
|
|
83
89
|
.option("--nvm", "Show latest nvm log")
|
|
84
90
|
.option("--dotnet", "Show latest .NET log")
|
|
85
91
|
.option("--macos", "Show latest macOS update log")
|
|
86
|
-
.option("--cleanup", "Show latest cleanup log")
|
|
92
|
+
.option("--cleanup", "Show latest homebrew cleanup log")
|
|
87
93
|
.addHelpText("after", `
|
|
88
94
|
Examples:
|
|
89
95
|
braeburn log List all available step logs
|
package/dist/steps/catalog.js
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { pyenvStep, nvmStep, homebrewStep, masStep, ohmyzshStep, npmStep, pipStep, dotnetStep, macosStep, cleanupStep, } from "./index.js";
|
|
2
|
-
|
|
3
|
-
pyenvStep,
|
|
4
|
-
nvmStep,
|
|
5
|
-
homebrewStep,
|
|
6
|
-
masStep,
|
|
7
|
-
|
|
8
|
-
npmStep,
|
|
9
|
-
pipStep,
|
|
10
|
-
dotnetStep,
|
|
11
|
-
|
|
12
|
-
cleanupStep,
|
|
2
|
+
const STEP_BY_ID = {
|
|
3
|
+
pyenv: pyenvStep,
|
|
4
|
+
nvm: nvmStep,
|
|
5
|
+
homebrew: homebrewStep,
|
|
6
|
+
mas: masStep,
|
|
7
|
+
macos: macosStep,
|
|
8
|
+
npm: npmStep,
|
|
9
|
+
pip: pipStep,
|
|
10
|
+
dotnet: dotnetStep,
|
|
11
|
+
ohmyzsh: ohmyzshStep,
|
|
12
|
+
cleanup: cleanupStep,
|
|
13
|
+
};
|
|
14
|
+
const STEP_EXECUTION_ORDER = [
|
|
15
|
+
"pyenv",
|
|
16
|
+
"nvm",
|
|
17
|
+
"homebrew",
|
|
18
|
+
"mas",
|
|
19
|
+
"macos",
|
|
20
|
+
"npm",
|
|
21
|
+
"pip",
|
|
22
|
+
"dotnet",
|
|
23
|
+
"ohmyzsh",
|
|
24
|
+
"cleanup",
|
|
13
25
|
];
|
|
26
|
+
export const ALL_STEPS = STEP_EXECUTION_ORDER.map((stepId) => STEP_BY_ID[stepId]);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type StepCategoryId = "apps-packages" | "cli-tools" | "runtimes" | "shell" | "maintenance";
|
|
2
|
+
export type StepCategoryDefinition = {
|
|
3
|
+
id: StepCategoryId;
|
|
4
|
+
label: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function listStepCategoryDefinitions(): StepCategoryDefinition[];
|
|
7
|
+
export declare function getStepCategoryLabel(categoryId: StepCategoryId): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const STEP_CATEGORY_DEFINITIONS = [
|
|
2
|
+
{ id: "runtimes", label: "Runtimes" },
|
|
3
|
+
{ id: "apps-packages", label: "Apps & Packages" },
|
|
4
|
+
{ id: "cli-tools", label: "CLI Tools" },
|
|
5
|
+
{ id: "shell", label: "Shell" },
|
|
6
|
+
{ id: "maintenance", label: "Maintenance" },
|
|
7
|
+
];
|
|
8
|
+
const STEP_CATEGORY_DEFINITION_BY_ID = new Map(STEP_CATEGORY_DEFINITIONS.map((categoryDefinition) => [categoryDefinition.id, categoryDefinition]));
|
|
9
|
+
export function listStepCategoryDefinitions() {
|
|
10
|
+
return [...STEP_CATEGORY_DEFINITIONS];
|
|
11
|
+
}
|
|
12
|
+
export function getStepCategoryLabel(categoryId) {
|
|
13
|
+
const categoryDefinition = STEP_CATEGORY_DEFINITION_BY_ID.get(categoryId);
|
|
14
|
+
if (!categoryDefinition) {
|
|
15
|
+
return categoryId;
|
|
16
|
+
}
|
|
17
|
+
return categoryDefinition.label;
|
|
18
|
+
}
|
package/dist/steps/cleanup.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { checkCommandExists } from "./runtime.js";
|
|
2
2
|
const cleanupStep = {
|
|
3
3
|
id: "cleanup",
|
|
4
|
-
name: "
|
|
5
|
-
|
|
4
|
+
name: "homebrew cleanup",
|
|
5
|
+
categoryId: "maintenance",
|
|
6
6
|
description: "Remove outdated Homebrew downloads and cached versions",
|
|
7
7
|
async checkIsAvailable() {
|
|
8
8
|
return checkCommandExists("brew");
|
package/dist/steps/dotnet.js
CHANGED
|
@@ -2,7 +2,7 @@ import { checkCommandExists } from "./runtime.js";
|
|
|
2
2
|
const dotnetStep = {
|
|
3
3
|
id: "dotnet",
|
|
4
4
|
name: ".NET",
|
|
5
|
-
|
|
5
|
+
categoryId: "cli-tools",
|
|
6
6
|
description: "Update all globally installed .NET tools",
|
|
7
7
|
async checkIsAvailable() {
|
|
8
8
|
return checkCommandExists("dotnet");
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StepCategoryId } from "./categories.js";
|
|
2
|
+
type CategorizedItem = {
|
|
3
|
+
categoryId: StepCategoryId;
|
|
4
|
+
};
|
|
5
|
+
export type CategorySection<TItem> = {
|
|
6
|
+
categoryId: StepCategoryId;
|
|
7
|
+
items: TItem[];
|
|
8
|
+
};
|
|
9
|
+
export declare function buildCategorySectionsInOrder<TItem extends CategorizedItem>(orderedItems: TItem[]): CategorySection<TItem>[];
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function buildCategorySectionsInOrder(orderedItems) {
|
|
2
|
+
const sections = [];
|
|
3
|
+
for (const item of orderedItems) {
|
|
4
|
+
const lastSection = sections[sections.length - 1];
|
|
5
|
+
if (!lastSection || lastSection.categoryId !== item.categoryId) {
|
|
6
|
+
sections.push({
|
|
7
|
+
categoryId: item.categoryId,
|
|
8
|
+
items: [item],
|
|
9
|
+
});
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
lastSection.items.push(item);
|
|
13
|
+
}
|
|
14
|
+
return sections;
|
|
15
|
+
}
|
package/dist/steps/homebrew.js
CHANGED
|
@@ -2,7 +2,7 @@ import { checkCommandExists } from "./runtime.js";
|
|
|
2
2
|
const homebrewStep = {
|
|
3
3
|
id: "homebrew",
|
|
4
4
|
name: "Homebrew",
|
|
5
|
-
|
|
5
|
+
categoryId: "apps-packages",
|
|
6
6
|
description: "Update Homebrew itself and upgrade all installed formulae",
|
|
7
7
|
async checkIsAvailable() {
|
|
8
8
|
return checkCommandExists("brew");
|
package/dist/steps/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
export type { StepRunContext,
|
|
1
|
+
export type { StepRunContext, Step, } from "./types.js";
|
|
2
|
+
export type { StepCategoryId, StepCategoryDefinition, } from "./categories.js";
|
|
3
|
+
export { listStepCategoryDefinitions, getStepCategoryLabel, } from "./categories.js";
|
|
4
|
+
export type { CategorySection, } from "./grouping.js";
|
|
5
|
+
export { buildCategorySectionsInOrder, } from "./grouping.js";
|
|
2
6
|
export { checkCommandExists, checkPathExists, runStep, createDefaultStepRunContext, } from "./runtime.js";
|
|
3
7
|
export { default as homebrewStep } from "./homebrew.js";
|
|
4
8
|
export { default as masStep } from "./mas.js";
|
package/dist/steps/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { listStepCategoryDefinitions, getStepCategoryLabel, } from "./categories.js";
|
|
2
|
+
export { buildCategorySectionsInOrder, } from "./grouping.js";
|
|
1
3
|
export { checkCommandExists, checkPathExists, runStep, createDefaultStepRunContext, } from "./runtime.js";
|
|
2
4
|
export { default as homebrewStep } from "./homebrew.js";
|
|
3
5
|
export { default as masStep } from "./mas.js";
|
package/dist/steps/macos.js
CHANGED
package/dist/steps/mas.js
CHANGED
|
@@ -2,7 +2,7 @@ import { checkCommandExists } from "./runtime.js";
|
|
|
2
2
|
const masStep = {
|
|
3
3
|
id: "mas",
|
|
4
4
|
name: "Mac App Store",
|
|
5
|
-
|
|
5
|
+
categoryId: "apps-packages",
|
|
6
6
|
description: "Upgrade all Mac App Store apps via the mas CLI tool",
|
|
7
7
|
brewPackageToInstall: "mas",
|
|
8
8
|
async checkIsAvailable() {
|
package/dist/steps/npm.js
CHANGED
|
@@ -2,7 +2,7 @@ import { checkCommandExists } from "./runtime.js";
|
|
|
2
2
|
const npmStep = {
|
|
3
3
|
id: "npm",
|
|
4
4
|
name: "npm",
|
|
5
|
-
|
|
5
|
+
categoryId: "cli-tools",
|
|
6
6
|
description: "Update all globally installed npm packages",
|
|
7
7
|
async checkIsAvailable() {
|
|
8
8
|
return checkCommandExists("npm");
|
package/dist/steps/nvm.js
CHANGED
|
@@ -16,7 +16,7 @@ const NVM_INSTALL_COMMAND = `${NVM_SOURCE_PREFIX} && ` +
|
|
|
16
16
|
const nvmStep = {
|
|
17
17
|
id: "nvm",
|
|
18
18
|
name: "Node.js (nvm)",
|
|
19
|
-
|
|
19
|
+
categoryId: "runtimes",
|
|
20
20
|
description: "Install the latest Node.js via nvm, migrating packages from the current version",
|
|
21
21
|
async checkIsAvailable() {
|
|
22
22
|
return checkPathExists(NVM_SCRIPT_PATH);
|
package/dist/steps/ohmyzsh.js
CHANGED
|
@@ -5,7 +5,7 @@ const OH_MY_ZSH_UPGRADE_SCRIPT_PATH = join(homedir(), ".oh-my-zsh", "tools", "up
|
|
|
5
5
|
const ohmyzshStep = {
|
|
6
6
|
id: "ohmyzsh",
|
|
7
7
|
name: "Oh My Zsh",
|
|
8
|
-
|
|
8
|
+
categoryId: "shell",
|
|
9
9
|
description: "Update Oh My Zsh to the latest version",
|
|
10
10
|
async checkIsAvailable() {
|
|
11
11
|
return checkPathExists(OH_MY_ZSH_UPGRADE_SCRIPT_PATH);
|
package/dist/steps/pip.js
CHANGED
|
@@ -3,7 +3,7 @@ const PIP_UPDATE_ALL_OUTDATED_SHELL_COMMAND = "pip3 list --outdated --format=col
|
|
|
3
3
|
const pipStep = {
|
|
4
4
|
id: "pip",
|
|
5
5
|
name: "pip3",
|
|
6
|
-
|
|
6
|
+
categoryId: "cli-tools",
|
|
7
7
|
description: "Update all globally installed pip3 packages",
|
|
8
8
|
warning: "This updates all global pip3 packages, which can occasionally break tools.",
|
|
9
9
|
async checkIsAvailable() {
|
package/dist/steps/pyenv.js
CHANGED
|
@@ -3,7 +3,7 @@ const FIND_LATEST_STABLE_PYTHON_SHELL_COMMAND = "pyenv install -l | grep -E '^\\
|
|
|
3
3
|
const pyenvStep = {
|
|
4
4
|
id: "pyenv",
|
|
5
5
|
name: "pyenv",
|
|
6
|
-
|
|
6
|
+
categoryId: "runtimes",
|
|
7
7
|
description: "Upgrade pyenv via Homebrew and install the latest Python 3.x",
|
|
8
8
|
brewPackageToInstall: "pyenv",
|
|
9
9
|
async checkIsAvailable() {
|
package/dist/steps/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OutputLineCallback } from "../runner.js";
|
|
2
2
|
import type { StepLogWriter } from "../logger.js";
|
|
3
|
+
import type { StepCategoryId } from "./categories.js";
|
|
3
4
|
export type StepRunContext = {
|
|
4
5
|
onOutputLine: OutputLineCallback;
|
|
5
6
|
logWriter: StepLogWriter;
|
|
@@ -8,12 +9,11 @@ export type StepRunContext = {
|
|
|
8
9
|
shellCommand: string;
|
|
9
10
|
}) => Promise<string>;
|
|
10
11
|
};
|
|
11
|
-
export type StepStage = "runtime" | "tools";
|
|
12
12
|
export type Step = {
|
|
13
13
|
id: string;
|
|
14
14
|
name: string;
|
|
15
15
|
description: string;
|
|
16
|
-
|
|
16
|
+
categoryId: StepCategoryId;
|
|
17
17
|
warning?: string;
|
|
18
18
|
brewPackageToInstall?: string;
|
|
19
19
|
checkIsAvailable: () => Promise<boolean>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getActivityIndicatorFrame(frameIndex: number): string;
|
package/dist/ui/currentStep.d.ts
CHANGED
package/dist/ui/currentStep.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { getActivityIndicatorFrame } from "./activityIndicator.js";
|
|
2
3
|
export function buildActiveStepLines(options) {
|
|
3
4
|
const { step, stepNumber, totalSteps, phase } = options;
|
|
5
|
+
const activityFrameIndex = options.activityFrameIndex ?? 0;
|
|
4
6
|
const isRunning = phase === "running" || phase === "installing";
|
|
5
7
|
const lines = [
|
|
6
8
|
chalk.dim(` ${"─".repeat(3)} Step ${stepNumber}/${totalSteps} `) +
|
|
@@ -10,7 +12,7 @@ export function buildActiveStepLines(options) {
|
|
|
10
12
|
];
|
|
11
13
|
if (isRunning) {
|
|
12
14
|
const label = phase === "installing" ? "Installing..." : "Running...";
|
|
13
|
-
lines.push(` ${chalk.blue(
|
|
15
|
+
lines.push(` ${chalk.blue(getActivityIndicatorFrame(activityFrameIndex))} ${label}`);
|
|
14
16
|
}
|
|
15
17
|
return lines;
|
|
16
18
|
}
|
package/dist/ui/header.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { DisplayStep, StepPhase, CompletedStepRecord, LogoVisibility } from
|
|
|
2
2
|
import type { TerminalDimensions } from "./outputBox.js";
|
|
3
3
|
type LogoLayout = "side-by-side" | "stacked" | "none";
|
|
4
4
|
export declare function determineLogoLayout(logoLines: string[], dimensions?: TerminalDimensions): LogoLayout;
|
|
5
|
-
export declare function stepTrackerIcon(phase: StepPhase): string;
|
|
5
|
+
export declare function stepTrackerIcon(phase: StepPhase, activityFrameIndex?: number): string;
|
|
6
6
|
export declare function isActivePhase(phase: StepPhase): boolean;
|
|
7
7
|
export declare function deriveAllStepPhases(steps: DisplayStep[], currentStepIndex: number, currentPhase: StepPhase, completedStepRecords: CompletedStepRecord[]): StepPhase[];
|
|
8
8
|
type BuildHeaderOptions = {
|
|
@@ -12,6 +12,7 @@ type BuildHeaderOptions = {
|
|
|
12
12
|
currentStepIndex: number;
|
|
13
13
|
currentPhase: StepPhase;
|
|
14
14
|
completedStepRecords: CompletedStepRecord[];
|
|
15
|
+
activityFrameIndex?: number;
|
|
15
16
|
terminalDimensions?: TerminalDimensions;
|
|
16
17
|
};
|
|
17
18
|
export declare function buildHeaderLines(options: BuildHeaderOptions): string[];
|
package/dist/ui/header.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { LOGO_ART } from "../logo.js";
|
|
3
|
+
import { buildCategorySectionsInOrder, getStepCategoryLabel } from "../steps/index.js";
|
|
4
|
+
import { getActivityIndicatorFrame } from "./activityIndicator.js";
|
|
3
5
|
const LOGO_COLUMN_WIDTH = 32;
|
|
4
6
|
const LOGO_SEPARATOR = " ";
|
|
5
7
|
const MIN_SIDE_BY_SIDE_COLS = LOGO_COLUMN_WIDTH + LOGO_SEPARATOR.length + 20; // 56
|
|
@@ -14,16 +16,16 @@ export function determineLogoLayout(logoLines, dimensions) {
|
|
|
14
16
|
}
|
|
15
17
|
return "none";
|
|
16
18
|
}
|
|
17
|
-
export function stepTrackerIcon(phase) {
|
|
19
|
+
export function stepTrackerIcon(phase, activityFrameIndex = 0) {
|
|
18
20
|
if (phase === "complete")
|
|
19
21
|
return chalk.green("✓ ");
|
|
20
22
|
if (phase === "failed")
|
|
21
23
|
return chalk.red("✗ ");
|
|
22
24
|
if (phase === "skipped" || phase === "not-available")
|
|
23
25
|
return chalk.dim("– ");
|
|
24
|
-
if (phase === "running" ||
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
if (phase === "running" || phase === "installing")
|
|
27
|
+
return chalk.cyan(`${getActivityIndicatorFrame(activityFrameIndex)} `);
|
|
28
|
+
if (phase === "prompting-to-run" ||
|
|
27
29
|
phase === "prompting-to-install" ||
|
|
28
30
|
phase === "checking-availability")
|
|
29
31
|
return chalk.cyan("→ ");
|
|
@@ -47,26 +49,17 @@ export function deriveAllStepPhases(steps, currentStepIndex, currentPhase, compl
|
|
|
47
49
|
}
|
|
48
50
|
export function buildHeaderLines(options) {
|
|
49
51
|
const { steps, version, logoVisibility, currentStepIndex, currentPhase, completedStepRecords } = options;
|
|
52
|
+
const activityFrameIndex = options.activityFrameIndex ?? 0;
|
|
50
53
|
const phases = deriveAllStepPhases(steps, currentStepIndex, currentPhase, completedStepRecords);
|
|
51
|
-
const hasRuntimeSteps = steps.some((step) => step.stage === "runtime");
|
|
52
|
-
const hasToolsSteps = steps.some((step) => step.stage === "tools");
|
|
53
|
-
const showStageLabels = hasRuntimeSteps && hasToolsSteps;
|
|
54
54
|
const stepLines = [];
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
else if (isFirstTools) {
|
|
64
|
-
stepLines.push(chalk.dim("Tools"));
|
|
65
|
-
}
|
|
55
|
+
const indexedSteps = steps.map((step, stepIndex) => ({ step, stepIndex, categoryId: step.categoryId }));
|
|
56
|
+
for (const section of buildCategorySectionsInOrder(indexedSteps)) {
|
|
57
|
+
stepLines.push(chalk.dim(`System / ${getStepCategoryLabel(section.categoryId)}`));
|
|
58
|
+
for (const indexedStep of section.items) {
|
|
59
|
+
const icon = stepTrackerIcon(phases[indexedStep.stepIndex], activityFrameIndex);
|
|
60
|
+
const name = isActivePhase(phases[indexedStep.stepIndex]) ? chalk.white(indexedStep.step.name) : chalk.dim(indexedStep.step.name);
|
|
61
|
+
stepLines.push(`${icon}${name}`);
|
|
66
62
|
}
|
|
67
|
-
const icon = stepTrackerIcon(phases[stepIndex]);
|
|
68
|
-
const name = isActivePhase(phases[stepIndex]) ? chalk.white(step.name) : chalk.dim(step.name);
|
|
69
|
-
stepLines.push(`${icon}${name}`);
|
|
70
63
|
}
|
|
71
64
|
const rightColumnLines = [
|
|
72
65
|
`${chalk.bold.white("braeburn")} ${chalk.dim("v" + version)}`,
|
package/dist/ui/screen.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ import type { AppState } from "./state.js";
|
|
|
3
3
|
export type ScreenRenderer = (content: string) => void;
|
|
4
4
|
export declare function createScreenRenderer(output?: NodeJS.WritableStream): ScreenRenderer;
|
|
5
5
|
export declare function buildScreen(state: AppState, terminalDimensions?: TerminalDimensions): string;
|
|
6
|
+
export declare function buildScreenWithAnimationFrame(state: AppState, activityFrameIndex: number, terminalDimensions?: TerminalDimensions): string;
|
package/dist/ui/screen.js
CHANGED
|
@@ -10,6 +10,9 @@ export function createScreenRenderer(output = process.stdout) {
|
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
export function buildScreen(state, terminalDimensions) {
|
|
13
|
+
return buildScreenWithAnimationFrame(state, 0, terminalDimensions);
|
|
14
|
+
}
|
|
15
|
+
export function buildScreenWithAnimationFrame(state, activityFrameIndex, terminalDimensions) {
|
|
13
16
|
const lines = [];
|
|
14
17
|
const failedStepIds = state.completedStepRecords.flatMap((record, stepIndex) => {
|
|
15
18
|
if (record.phase !== "failed") {
|
|
@@ -28,6 +31,7 @@ export function buildScreen(state, terminalDimensions) {
|
|
|
28
31
|
currentStepIndex: state.currentStepIndex,
|
|
29
32
|
currentPhase: state.currentPhase,
|
|
30
33
|
completedStepRecords: state.completedStepRecords,
|
|
34
|
+
activityFrameIndex,
|
|
31
35
|
terminalDimensions,
|
|
32
36
|
}));
|
|
33
37
|
lines.push("");
|
|
@@ -50,6 +54,7 @@ export function buildScreen(state, terminalDimensions) {
|
|
|
50
54
|
stepNumber: state.currentStepIndex + 1,
|
|
51
55
|
totalSteps: state.steps.length,
|
|
52
56
|
phase: state.currentPhase,
|
|
57
|
+
activityFrameIndex,
|
|
53
58
|
}));
|
|
54
59
|
const isShowingOutput = (state.currentPhase === "running" || state.currentPhase === "installing") &&
|
|
55
60
|
state.currentOutputLines.length > 0;
|
package/dist/ui/state.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { type
|
|
1
|
+
export { type DisplayStep, type StepPhase, type CompletedStepRecord, type CurrentPrompt, type ResolvedVersion, type LogoVisibility, type RunCompletion, type UpdateState, type AppState, createInitialUpdateState, createInitialAppState, } from "../update/state.js";
|
package/dist/update/state.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { CommandOutputLine } from "../runner.js";
|
|
2
|
-
|
|
2
|
+
import type { StepCategoryId } from "../steps/categories.js";
|
|
3
3
|
export type DisplayStep = {
|
|
4
4
|
id: string;
|
|
5
5
|
name: string;
|
|
6
6
|
description: string;
|
|
7
|
-
|
|
7
|
+
categoryId: StepCategoryId;
|
|
8
8
|
};
|
|
9
9
|
export type StepPhase = "pending" | "checking-availability" | "prompting-to-install" | "installing" | "prompting-to-run" | "running" | "complete" | "failed" | "skipped" | "not-available";
|
|
10
10
|
export type CompletedStepRecord = {
|