braeburn 1.5.2 → 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/dist/commands/update.d.ts +9 -2
- package/dist/commands/update.js +11 -0
- package/dist/index.js +3 -2
- package/dist/runner.d.ts +1 -0
- package/dist/runner.js +50 -11
- package/dist/steps/index.d.ts +1 -1
- package/dist/steps/index.js +1 -1
- package/dist/steps/macos.js +4 -4
- package/dist/steps/runtime.d.ts +1 -0
- package/dist/steps/runtime.js +9 -1
- package/dist/steps/types.d.ts +1 -0
- package/dist/ui/header.d.ts +1 -1
- package/dist/ui/outputLines.d.ts +6 -0
- package/dist/ui/outputLines.js +33 -0
- package/dist/ui/screen.d.ts +2 -2
- package/dist/ui/screen.js +59 -11
- package/dist/ui/state.d.ts +1 -1
- package/dist/ui/state.js +1 -1
- package/dist/ui/versionReport.d.ts +10 -2
- package/dist/ui/versionReport.js +12 -5
- package/dist/update/engine.js +33 -11
- package/dist/update/state.d.ts +3 -0
- package/dist/update/state.js +3 -0
- package/package.json +1 -1
- package/dist/ui/outputBox.d.ts +0 -6
- package/dist/ui/outputBox.js +0 -35
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PromptMode } from "../update/engine.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type LogoVisibility } from "../update/state.js";
|
|
3
3
|
import type { Step } from "../steps/index.js";
|
|
4
4
|
type RunUpdateCommandOptions = {
|
|
5
5
|
steps: Step[];
|
|
@@ -7,5 +7,12 @@ type RunUpdateCommandOptions = {
|
|
|
7
7
|
logoVisibility: LogoVisibility;
|
|
8
8
|
version: string;
|
|
9
9
|
};
|
|
10
|
-
export
|
|
10
|
+
export type UpdateCommandResult = {
|
|
11
|
+
failedStepCount: number;
|
|
12
|
+
};
|
|
13
|
+
type ExitCodeWritable = {
|
|
14
|
+
exitCode: string | number | null | undefined;
|
|
15
|
+
};
|
|
16
|
+
export declare function applyUpdateCommandResult(updateCommandResult: UpdateCommandResult, processWithExitCode: ExitCodeWritable): void;
|
|
17
|
+
export declare function runUpdateCommand(options: RunUpdateCommandOptions): Promise<UpdateCommandResult>;
|
|
11
18
|
export {};
|
package/dist/commands/update.js
CHANGED
|
@@ -4,10 +4,16 @@ import { captureYesNo } from "../ui/prompt.js";
|
|
|
4
4
|
import { buildScreen, buildScreenWithAnimationFrame, createScreenRenderer } from "../ui/screen.js";
|
|
5
5
|
import { hideCursorDuringExecution } from "../ui/terminal.js";
|
|
6
6
|
import { runUpdateEngine } from "../update/engine.js";
|
|
7
|
+
import { countFailedSteps } from "../update/state.js";
|
|
7
8
|
import { cancelActiveShellCommand } from "../runner.js";
|
|
8
9
|
function shouldCaptureRuntimeAbortKey(state) {
|
|
9
10
|
return state?.currentPhase === "running" || state?.currentPhase === "installing";
|
|
10
11
|
}
|
|
12
|
+
export function applyUpdateCommandResult(updateCommandResult, processWithExitCode) {
|
|
13
|
+
if (updateCommandResult.failedStepCount > 0) {
|
|
14
|
+
processWithExitCode.exitCode = 1;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
export async function runUpdateCommand(options) {
|
|
12
18
|
const renderScreen = createScreenRenderer();
|
|
13
19
|
const restoreCursor = hideCursorDuringExecution({ screenBuffer: "alternate" });
|
|
@@ -15,6 +21,7 @@ export async function runUpdateCommand(options) {
|
|
|
15
21
|
let latestState = undefined;
|
|
16
22
|
let runtimeAbortKeyCaptureEnabled = false;
|
|
17
23
|
let animationFrameIndex = 0;
|
|
24
|
+
let updateCommandResult = { failedStepCount: 0 };
|
|
18
25
|
const handleRuntimeKeypress = (typedCharacter, key) => {
|
|
19
26
|
if (key?.ctrl && key?.name === "c") {
|
|
20
27
|
process.stdout.write("\x1b[?25h\n");
|
|
@@ -82,6 +89,9 @@ export async function runUpdateCommand(options) {
|
|
|
82
89
|
},
|
|
83
90
|
});
|
|
84
91
|
finalScreen = buildScreen(finalState);
|
|
92
|
+
updateCommandResult = {
|
|
93
|
+
failedStepCount: countFailedSteps(finalState.completedStepRecords),
|
|
94
|
+
};
|
|
85
95
|
}
|
|
86
96
|
finally {
|
|
87
97
|
disableRuntimeAbortKeyCapture();
|
|
@@ -91,4 +101,5 @@ export async function runUpdateCommand(options) {
|
|
|
91
101
|
if (finalScreen) {
|
|
92
102
|
process.stdout.write(finalScreen);
|
|
93
103
|
}
|
|
104
|
+
return updateCommandResult;
|
|
94
105
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createRequire } from "node:module";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { ALL_STEPS } from "./steps/catalog.js";
|
|
7
|
-
import { runUpdateCommand } from "./commands/update.js";
|
|
7
|
+
import { applyUpdateCommandResult, runUpdateCommand } from "./commands/update.js";
|
|
8
8
|
import { runLogCommand, runLogListCommand } from "./commands/log.js";
|
|
9
9
|
import { runConfigCommand, runConfigUpdateCommand } from "./commands/config.js";
|
|
10
10
|
import { runSetupCommand } from "./commands/setup.js";
|
|
@@ -70,12 +70,13 @@ Examples:
|
|
|
70
70
|
stepsToRun = stepsToRun.filter((step) => isStepEnabled(config, step.id));
|
|
71
71
|
}
|
|
72
72
|
const logoIsEnabled = options.logo !== false && isLogoEnabled(config);
|
|
73
|
-
await runUpdateCommand({
|
|
73
|
+
const updateCommandResult = await runUpdateCommand({
|
|
74
74
|
steps: stepsToRun,
|
|
75
75
|
promptMode: autoYes ? "auto-accept" : "interactive",
|
|
76
76
|
logoVisibility: logoIsEnabled ? "visible" : "hidden",
|
|
77
77
|
version: BRAEBURN_VERSION,
|
|
78
78
|
});
|
|
79
|
+
applyUpdateCommandResult(updateCommandResult, process);
|
|
79
80
|
});
|
|
80
81
|
program
|
|
81
82
|
.command("log")
|
package/dist/runner.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare class ShellCommandCanceledError extends Error {
|
|
|
17
17
|
}
|
|
18
18
|
export declare function cancelActiveShellCommand(): boolean;
|
|
19
19
|
export declare function runShellCommand(options: RunCommandOptions): Promise<void>;
|
|
20
|
+
export declare function runShellCommandAndCaptureOutput(options: RunCommandOptions): Promise<string>;
|
|
20
21
|
type CheckCommandOptions = {
|
|
21
22
|
shellCommand: string;
|
|
22
23
|
};
|
package/dist/runner.js
CHANGED
|
@@ -60,30 +60,62 @@ function buildFailureSummaryLines(shellCommand, error) {
|
|
|
60
60
|
}
|
|
61
61
|
return summaryLines;
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
function createBufferedLineEmitter(emitLine) {
|
|
64
|
+
let pendingText = "";
|
|
65
|
+
return {
|
|
66
|
+
appendChunk(chunk) {
|
|
67
|
+
pendingText += String(chunk);
|
|
68
|
+
const lineParts = pendingText.split(/\r?\n|\r/);
|
|
69
|
+
pendingText = lineParts.pop() ?? "";
|
|
70
|
+
for (const linePart of lineParts) {
|
|
71
|
+
if (linePart.length === 0) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
emitLine(linePart);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
flushPendingLine() {
|
|
78
|
+
if (pendingText.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
emitLine(pendingText);
|
|
82
|
+
pendingText = "";
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async function executeShellCommand(options) {
|
|
87
|
+
const capturedOutputLines = [];
|
|
64
88
|
const subprocess = execa("bash", ["-c", options.shellCommand], {
|
|
65
89
|
all: true,
|
|
66
90
|
reject: true,
|
|
67
91
|
});
|
|
68
92
|
activeShellCommandSubprocess = subprocess;
|
|
93
|
+
const emitOutputLine = (line) => {
|
|
94
|
+
capturedOutputLines.push(line.text);
|
|
95
|
+
options.onOutputLine(line);
|
|
96
|
+
options.logWriter(line.text);
|
|
97
|
+
};
|
|
98
|
+
const stdoutEmitter = createBufferedLineEmitter((line) => {
|
|
99
|
+
emitOutputLine({ text: line, source: "stdout" });
|
|
100
|
+
});
|
|
101
|
+
const stderrEmitter = createBufferedLineEmitter((line) => {
|
|
102
|
+
emitOutputLine({ text: line, source: "stderr" });
|
|
103
|
+
});
|
|
69
104
|
subprocess.stdout?.on("data", (chunk) => {
|
|
70
|
-
|
|
71
|
-
for (const line of lines) {
|
|
72
|
-
options.onOutputLine({ text: line, source: "stdout" });
|
|
73
|
-
options.logWriter(line);
|
|
74
|
-
}
|
|
105
|
+
stdoutEmitter.appendChunk(chunk);
|
|
75
106
|
});
|
|
76
107
|
subprocess.stderr?.on("data", (chunk) => {
|
|
77
|
-
|
|
78
|
-
for (const line of lines) {
|
|
79
|
-
options.onOutputLine({ text: line, source: "stderr" });
|
|
80
|
-
options.logWriter(line);
|
|
81
|
-
}
|
|
108
|
+
stderrEmitter.appendChunk(chunk);
|
|
82
109
|
});
|
|
83
110
|
try {
|
|
84
111
|
await subprocess;
|
|
112
|
+
stdoutEmitter.flushPendingLine();
|
|
113
|
+
stderrEmitter.flushPendingLine();
|
|
114
|
+
return { capturedOutput: capturedOutputLines.join("\n") };
|
|
85
115
|
}
|
|
86
116
|
catch (error) {
|
|
117
|
+
stdoutEmitter.flushPendingLine();
|
|
118
|
+
stderrEmitter.flushPendingLine();
|
|
87
119
|
const commandWasCanceledByUser = userCanceledSubprocesses.has(subprocess);
|
|
88
120
|
const failureSummaryLines = buildFailureSummaryLines(options.shellCommand, error);
|
|
89
121
|
if (commandWasCanceledByUser) {
|
|
@@ -103,6 +135,13 @@ export async function runShellCommand(options) {
|
|
|
103
135
|
}
|
|
104
136
|
}
|
|
105
137
|
}
|
|
138
|
+
export async function runShellCommand(options) {
|
|
139
|
+
await executeShellCommand(options);
|
|
140
|
+
}
|
|
141
|
+
export async function runShellCommandAndCaptureOutput(options) {
|
|
142
|
+
const result = await executeShellCommand(options);
|
|
143
|
+
return result.capturedOutput;
|
|
144
|
+
}
|
|
106
145
|
export async function doesShellCommandSucceed(options) {
|
|
107
146
|
const result = await execa("bash", ["-c", options.shellCommand], {
|
|
108
147
|
reject: false,
|
package/dist/steps/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type { StepCategoryId, StepCategoryDefinition, } from "./categories.js";
|
|
|
3
3
|
export { listStepCategoryDefinitions, getStepCategoryLabel, } from "./categories.js";
|
|
4
4
|
export type { CategorySection, } from "./grouping.js";
|
|
5
5
|
export { buildCategorySectionsInOrder, } from "./grouping.js";
|
|
6
|
-
export { checkCommandExists, checkPathExists, runStep, createDefaultStepRunContext, } from "./runtime.js";
|
|
6
|
+
export { checkCommandExists, checkPathExists, runStep, runStepAndCaptureOutput, createDefaultStepRunContext, } from "./runtime.js";
|
|
7
7
|
export { default as homebrewStep } from "./homebrew.js";
|
|
8
8
|
export { default as masStep } from "./mas.js";
|
|
9
9
|
export { default as ohmyzshStep } from "./ohmyzsh.js";
|
package/dist/steps/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { listStepCategoryDefinitions, getStepCategoryLabel, } from "./categories.js";
|
|
2
2
|
export { buildCategorySectionsInOrder, } from "./grouping.js";
|
|
3
|
-
export { checkCommandExists, checkPathExists, runStep, createDefaultStepRunContext, } from "./runtime.js";
|
|
3
|
+
export { checkCommandExists, checkPathExists, runStep, runStepAndCaptureOutput, createDefaultStepRunContext, } from "./runtime.js";
|
|
4
4
|
export { default as homebrewStep } from "./homebrew.js";
|
|
5
5
|
export { default as masStep } from "./mas.js";
|
|
6
6
|
export { default as ohmyzshStep } from "./ohmyzsh.js";
|
package/dist/steps/macos.js
CHANGED
|
@@ -7,10 +7,11 @@ const macosStep = {
|
|
|
7
7
|
return true;
|
|
8
8
|
},
|
|
9
9
|
async run(context) {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
context.onOutputLine({
|
|
11
|
+
text: "Checking for macOS updates...",
|
|
12
|
+
source: "stdout",
|
|
12
13
|
});
|
|
13
|
-
context.
|
|
14
|
+
const updateListOutput = await context.runStepAndCaptureOutput("softwareupdate -l 2>&1");
|
|
14
15
|
const noUpdatesAvailable = updateListOutput.includes("No new software available");
|
|
15
16
|
if (noUpdatesAvailable) {
|
|
16
17
|
context.onOutputLine({
|
|
@@ -19,7 +20,6 @@ const macosStep = {
|
|
|
19
20
|
});
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
|
-
context.onOutputLine({ text: updateListOutput, source: "stdout" });
|
|
23
23
|
context.onOutputLine({
|
|
24
24
|
text: "Updates found — installing now...",
|
|
25
25
|
source: "stdout",
|
package/dist/steps/runtime.d.ts
CHANGED
|
@@ -4,4 +4,5 @@ import type { StepRunContext } from "./types.js";
|
|
|
4
4
|
export declare function checkCommandExists(command: string): Promise<boolean>;
|
|
5
5
|
export declare function checkPathExists(filePath: string): Promise<boolean>;
|
|
6
6
|
export declare function runStep(shellCommand: string, context: StepRunContext): Promise<void>;
|
|
7
|
+
export declare function runStepAndCaptureOutput(shellCommand: string, context: StepRunContext): Promise<string>;
|
|
7
8
|
export declare function createDefaultStepRunContext(onOutputLine: OutputLineCallback, logWriter: StepLogWriter): StepRunContext;
|
package/dist/steps/runtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { doesShellCommandSucceed, runShellCommand, captureShellCommandOutput, } from "../runner.js";
|
|
1
|
+
import { doesShellCommandSucceed, runShellCommand, captureShellCommandOutput, runShellCommandAndCaptureOutput, } from "../runner.js";
|
|
2
2
|
export async function checkCommandExists(command) {
|
|
3
3
|
return doesShellCommandSucceed({ shellCommand: `command -v ${command}` });
|
|
4
4
|
}
|
|
@@ -12,11 +12,19 @@ export async function runStep(shellCommand, context) {
|
|
|
12
12
|
logWriter: context.logWriter,
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
export async function runStepAndCaptureOutput(shellCommand, context) {
|
|
16
|
+
return runShellCommandAndCaptureOutput({
|
|
17
|
+
shellCommand,
|
|
18
|
+
onOutputLine: context.onOutputLine,
|
|
19
|
+
logWriter: context.logWriter,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
15
22
|
export function createDefaultStepRunContext(onOutputLine, logWriter) {
|
|
16
23
|
const context = {
|
|
17
24
|
onOutputLine,
|
|
18
25
|
logWriter,
|
|
19
26
|
runStep: (shellCommand) => runStep(shellCommand, context),
|
|
27
|
+
runStepAndCaptureOutput: (shellCommand) => runStepAndCaptureOutput(shellCommand, context),
|
|
20
28
|
captureOutput: captureShellCommandOutput,
|
|
21
29
|
};
|
|
22
30
|
return context;
|
package/dist/steps/types.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type StepRunContext = {
|
|
|
5
5
|
onOutputLine: OutputLineCallback;
|
|
6
6
|
logWriter: StepLogWriter;
|
|
7
7
|
runStep: (shellCommand: string) => Promise<void>;
|
|
8
|
+
runStepAndCaptureOutput: (shellCommand: string) => Promise<string>;
|
|
8
9
|
captureOutput: (options: {
|
|
9
10
|
shellCommand: string;
|
|
10
11
|
}) => Promise<string>;
|
package/dist/ui/header.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DisplayStep, StepPhase, CompletedStepRecord, LogoVisibility } from "./state.js";
|
|
2
|
-
import type { TerminalDimensions } from "./
|
|
2
|
+
import type { TerminalDimensions } from "./outputLines.js";
|
|
3
3
|
type LogoLayout = "side-by-side" | "stacked" | "none";
|
|
4
4
|
export declare function determineLogoLayout(logoLines: string[], dimensions?: TerminalDimensions): LogoLayout;
|
|
5
5
|
export declare function stepTrackerIcon(phase: StepPhase, activityFrameIndex?: number): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
const INDENT = " ";
|
|
3
|
+
const HEADER_LINES_APPROXIMATE = 18;
|
|
4
|
+
const MINIMUM_VISIBLE_LINES = 5;
|
|
5
|
+
function maxVisibleLines(rows) {
|
|
6
|
+
const available = rows - HEADER_LINES_APPROXIMATE;
|
|
7
|
+
return Math.max(MINIMUM_VISIBLE_LINES, available);
|
|
8
|
+
}
|
|
9
|
+
function maxLineWidth(columns) {
|
|
10
|
+
return Math.max(0, Math.min(columns, 120) - INDENT.length);
|
|
11
|
+
}
|
|
12
|
+
function resolveTerminalDimensions(dimensions) {
|
|
13
|
+
return dimensions ?? {
|
|
14
|
+
columns: process.stdout.columns ?? 80,
|
|
15
|
+
rows: process.stdout.rows ?? 40,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function expandRenderedLines(lines) {
|
|
19
|
+
return lines.flatMap((line) => line.text.split(/\r?\n|\r/).map((text) => ({
|
|
20
|
+
text,
|
|
21
|
+
source: line.source,
|
|
22
|
+
})));
|
|
23
|
+
}
|
|
24
|
+
export function buildStepOutputLines(lines, dimensions) {
|
|
25
|
+
const resolved = resolveTerminalDimensions(dimensions);
|
|
26
|
+
const visibleLines = expandRenderedLines(lines).slice(-maxVisibleLines(resolved.rows));
|
|
27
|
+
const width = maxLineWidth(resolved.columns);
|
|
28
|
+
return visibleLines.map((line) => {
|
|
29
|
+
const truncated = line.text.slice(0, width);
|
|
30
|
+
const colored = line.source === "stderr" ? chalk.yellow(truncated) : chalk.dim(truncated);
|
|
31
|
+
return `${INDENT}${colored}`;
|
|
32
|
+
});
|
|
33
|
+
}
|
package/dist/ui/screen.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type TerminalDimensions } from "./
|
|
2
|
-
import type
|
|
1
|
+
import { type TerminalDimensions } from "./outputLines.js";
|
|
2
|
+
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;
|
package/dist/ui/screen.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { buildHeaderLines } from "./header.js";
|
|
2
2
|
import { buildActiveStepLines } from "./currentStep.js";
|
|
3
|
-
import {
|
|
3
|
+
import { buildStepOutputLines } from "./outputLines.js";
|
|
4
4
|
import { buildPromptLines } from "./prompt.js";
|
|
5
5
|
import { buildFailedStepLogHintLines, buildVersionReportLines } from "./versionReport.js";
|
|
6
|
+
import { countFailedSteps } from "./state.js";
|
|
6
7
|
export function createScreenRenderer(output = process.stdout) {
|
|
7
8
|
return (content) => {
|
|
8
9
|
output.write("\x1b[H\x1b[2J");
|
|
@@ -12,18 +13,48 @@ export function createScreenRenderer(output = process.stdout) {
|
|
|
12
13
|
export function buildScreen(state, terminalDimensions) {
|
|
13
14
|
return buildScreenWithAnimationFrame(state, 0, terminalDimensions);
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (record.phase !== "failed") {
|
|
16
|
+
function buildFailedStepLogHints(state) {
|
|
17
|
+
return state.completedStepRecords.flatMap((completedStepRecord, stepIndex) => {
|
|
18
|
+
if (completedStepRecord.phase !== "failed") {
|
|
19
19
|
return [];
|
|
20
20
|
}
|
|
21
21
|
const failedStep = state.steps[stepIndex];
|
|
22
22
|
if (!failedStep) {
|
|
23
23
|
return [];
|
|
24
24
|
}
|
|
25
|
-
return [
|
|
25
|
+
return [{
|
|
26
|
+
stepId: failedStep.id,
|
|
27
|
+
logStepId: completedStepRecord.logStepId ?? failedStep.id,
|
|
28
|
+
}];
|
|
26
29
|
});
|
|
30
|
+
}
|
|
31
|
+
function findLatestFailedStepDisplay(state) {
|
|
32
|
+
for (let stepIndex = state.completedStepRecords.length - 1; stepIndex >= 0; stepIndex -= 1) {
|
|
33
|
+
const completedStepRecord = state.completedStepRecords[stepIndex];
|
|
34
|
+
if (completedStepRecord?.phase !== "failed") {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const failedStep = state.steps[stepIndex];
|
|
38
|
+
if (!failedStep) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const failureOutputLines = completedStepRecord.failureOutputLines;
|
|
42
|
+
if (!failureOutputLines || failureOutputLines.length === 0) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
step: failedStep,
|
|
47
|
+
stepNumber: stepIndex + 1,
|
|
48
|
+
failureOutputLines,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
export function buildScreenWithAnimationFrame(state, activityFrameIndex, terminalDimensions) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
const failedStepCount = countFailedSteps(state.completedStepRecords);
|
|
56
|
+
const failedStepLogHints = buildFailedStepLogHints(state);
|
|
57
|
+
const latestFailedStepDisplay = findLatestFailedStepDisplay(state);
|
|
27
58
|
lines.push(...buildHeaderLines({
|
|
28
59
|
steps: state.steps,
|
|
29
60
|
version: state.version,
|
|
@@ -38,11 +69,26 @@ export function buildScreenWithAnimationFrame(state, activityFrameIndex, termina
|
|
|
38
69
|
if (state.runCompletion === "finished") {
|
|
39
70
|
if (state.versionReport) {
|
|
40
71
|
lines.push("");
|
|
41
|
-
lines.push(...buildVersionReportLines(
|
|
72
|
+
lines.push(...buildVersionReportLines({
|
|
73
|
+
versions: state.versionReport,
|
|
74
|
+
failedStepCount,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
if (failedStepLogHints.length > 0) {
|
|
78
|
+
lines.push("");
|
|
79
|
+
lines.push(...buildFailedStepLogHintLines(failedStepLogHints));
|
|
42
80
|
}
|
|
43
|
-
if (
|
|
81
|
+
if (latestFailedStepDisplay) {
|
|
82
|
+
lines.push("");
|
|
83
|
+
lines.push(...buildActiveStepLines({
|
|
84
|
+
step: latestFailedStepDisplay.step,
|
|
85
|
+
stepNumber: latestFailedStepDisplay.stepNumber,
|
|
86
|
+
totalSteps: state.steps.length,
|
|
87
|
+
phase: "failed",
|
|
88
|
+
activityFrameIndex,
|
|
89
|
+
}));
|
|
44
90
|
lines.push("");
|
|
45
|
-
lines.push(...
|
|
91
|
+
lines.push(...buildStepOutputLines(latestFailedStepDisplay.failureOutputLines, terminalDimensions));
|
|
46
92
|
}
|
|
47
93
|
}
|
|
48
94
|
else {
|
|
@@ -56,11 +102,13 @@ export function buildScreenWithAnimationFrame(state, activityFrameIndex, termina
|
|
|
56
102
|
phase: state.currentPhase,
|
|
57
103
|
activityFrameIndex,
|
|
58
104
|
}));
|
|
59
|
-
const isShowingOutput = (state.currentPhase === "running" ||
|
|
105
|
+
const isShowingOutput = (state.currentPhase === "running" ||
|
|
106
|
+
state.currentPhase === "installing" ||
|
|
107
|
+
state.currentPhase === "failed") &&
|
|
60
108
|
state.currentOutputLines.length > 0;
|
|
61
109
|
if (isShowingOutput) {
|
|
62
110
|
lines.push("");
|
|
63
|
-
lines.push(...
|
|
111
|
+
lines.push(...buildStepOutputLines(state.currentOutputLines, terminalDimensions));
|
|
64
112
|
}
|
|
65
113
|
if (state.currentPrompt) {
|
|
66
114
|
lines.push("");
|
package/dist/ui/state.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
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";
|
|
1
|
+
export { type DisplayStep, type StepPhase, type CompletedStepRecord, type CurrentPrompt, type ResolvedVersion, type LogoVisibility, type RunCompletion, type UpdateState, type AppState, createInitialUpdateState, createInitialAppState, countFailedSteps, } from "../update/state.js";
|
package/dist/ui/state.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createInitialUpdateState, createInitialAppState, } from "../update/state.js";
|
|
1
|
+
export { createInitialUpdateState, createInitialAppState, countFailedSteps, } from "../update/state.js";
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
import type { ResolvedVersion } from "./state.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
2
|
+
export type VersionReportOptions = {
|
|
3
|
+
versions: ResolvedVersion[];
|
|
4
|
+
failedStepCount: number;
|
|
5
|
+
};
|
|
6
|
+
export type FailedStepLogHint = {
|
|
7
|
+
stepId: string;
|
|
8
|
+
logStepId: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function buildVersionReportLines(options: VersionReportOptions): string[];
|
|
11
|
+
export declare function buildFailedStepLogHintLines(failedStepLogHints: FailedStepLogHint[]): string[];
|
package/dist/ui/versionReport.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
|
|
2
|
+
function buildCompletionSummaryLine(failedStepCount) {
|
|
3
|
+
if (failedStepCount === 0) {
|
|
4
|
+
return ` ${chalk.green.bold("✓")} ${chalk.bold("All done!")}`;
|
|
5
|
+
}
|
|
6
|
+
const failureLabel = failedStepCount === 1 ? "1 step failed" : `${failedStepCount} steps failed`;
|
|
7
|
+
return ` ${chalk.red.bold("✗")} ${chalk.bold(`Done (${failureLabel})`)}`;
|
|
8
|
+
}
|
|
9
|
+
export function buildVersionReportLines(options) {
|
|
3
10
|
return [
|
|
4
11
|
chalk.dim(" ─── Versions ─────────────────────────"),
|
|
5
|
-
...versions.map(({ label, value }) => ` ${chalk.dim("·")} ${chalk.bold(label + ":")} ${chalk.dim(value)}`),
|
|
12
|
+
...options.versions.map(({ label, value }) => ` ${chalk.dim("·")} ${chalk.bold(label + ":")} ${chalk.dim(value)}`),
|
|
6
13
|
"",
|
|
7
|
-
|
|
14
|
+
buildCompletionSummaryLine(options.failedStepCount),
|
|
8
15
|
];
|
|
9
16
|
}
|
|
10
|
-
export function buildFailedStepLogHintLines(
|
|
11
|
-
return
|
|
17
|
+
export function buildFailedStepLogHintLines(failedStepLogHints) {
|
|
18
|
+
return failedStepLogHints.map(({ stepId, logStepId }) => ` ${chalk.red.bold("✗")} ${chalk.bold(`Step ${stepId} failed.`)} ${chalk.dim(`Please run braeburn log ${logStepId} to see what happened.`)}`);
|
|
12
19
|
}
|
package/dist/update/engine.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogWriterForStep } from "../logger.js";
|
|
2
|
-
import { runShellCommand, ShellCommandCanceledError } from "../runner.js";
|
|
2
|
+
import { runShellCommand, ShellCommandCanceledError, } from "../runner.js";
|
|
3
3
|
import { createDefaultStepRunContext } from "../steps/index.js";
|
|
4
4
|
import { toDisplaySteps } from "./displayStep.js";
|
|
5
5
|
import { createInitialUpdateState, } from "./state.js";
|
|
@@ -29,6 +29,24 @@ function reportState(state, onStateChanged) {
|
|
|
29
29
|
function wasStepCanceledByUser(error) {
|
|
30
30
|
return error instanceof ShellCommandCanceledError;
|
|
31
31
|
}
|
|
32
|
+
function createFailureOutputLines(currentOutputLines, error) {
|
|
33
|
+
if (currentOutputLines.length > 0) {
|
|
34
|
+
return [...currentOutputLines];
|
|
35
|
+
}
|
|
36
|
+
const errorMessage = error instanceof Error && error.message.length > 0
|
|
37
|
+
? error.message
|
|
38
|
+
: "Command failed.";
|
|
39
|
+
return [{ text: errorMessage, source: "stderr" }];
|
|
40
|
+
}
|
|
41
|
+
function createFailedStepRecord(options) {
|
|
42
|
+
const errorMessage = options.error instanceof Error ? options.error.message : String(options.error);
|
|
43
|
+
return {
|
|
44
|
+
phase: "failed",
|
|
45
|
+
summaryNote: wasStepCanceledByUser(options.error) ? "canceled by user" : errorMessage,
|
|
46
|
+
logStepId: options.logStepId,
|
|
47
|
+
failureOutputLines: [...options.failureOutputLines],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
32
50
|
export async function runUpdateEngine(options) {
|
|
33
51
|
const dependencies = resolveDependencies(options.dependencies);
|
|
34
52
|
const state = createInitialUpdateState(toDisplaySteps(options.steps), options.version, options.logoVisibility);
|
|
@@ -82,12 +100,15 @@ export async function runUpdateEngine(options) {
|
|
|
82
100
|
if (wasStepCanceledByUser(error)) {
|
|
83
101
|
promptMode = "interactive";
|
|
84
102
|
}
|
|
103
|
+
const failureOutputLines = createFailureOutputLines(state.currentOutputLines, error);
|
|
85
104
|
state.currentPhase = "failed";
|
|
105
|
+
state.currentOutputLines = failureOutputLines;
|
|
86
106
|
reportState(state, options.onStateChanged);
|
|
87
|
-
state.completedStepRecords.push({
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
107
|
+
state.completedStepRecords.push(createFailedStepRecord({
|
|
108
|
+
error,
|
|
109
|
+
failureOutputLines,
|
|
110
|
+
logStepId: `${step.id}-install`,
|
|
111
|
+
}));
|
|
91
112
|
continue;
|
|
92
113
|
}
|
|
93
114
|
}
|
|
@@ -121,14 +142,15 @@ export async function runUpdateEngine(options) {
|
|
|
121
142
|
if (wasStepCanceledByUser(error)) {
|
|
122
143
|
promptMode = "interactive";
|
|
123
144
|
}
|
|
124
|
-
const
|
|
145
|
+
const failureOutputLines = createFailureOutputLines(state.currentOutputLines, error);
|
|
125
146
|
state.currentPhase = "failed";
|
|
126
|
-
state.currentOutputLines =
|
|
147
|
+
state.currentOutputLines = failureOutputLines;
|
|
127
148
|
reportState(state, options.onStateChanged);
|
|
128
|
-
state.completedStepRecords.push({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
149
|
+
state.completedStepRecords.push(createFailedStepRecord({
|
|
150
|
+
error,
|
|
151
|
+
failureOutputLines,
|
|
152
|
+
logStepId: step.id,
|
|
153
|
+
}));
|
|
132
154
|
}
|
|
133
155
|
}
|
|
134
156
|
state.runCompletion = "finished";
|
package/dist/update/state.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export type StepPhase = "pending" | "checking-availability" | "prompting-to-inst
|
|
|
10
10
|
export type CompletedStepRecord = {
|
|
11
11
|
phase: StepPhase;
|
|
12
12
|
summaryNote?: string;
|
|
13
|
+
logStepId?: string;
|
|
14
|
+
failureOutputLines?: CommandOutputLine[];
|
|
13
15
|
};
|
|
14
16
|
export type CurrentPrompt = {
|
|
15
17
|
question: string;
|
|
@@ -36,3 +38,4 @@ export type UpdateState = {
|
|
|
36
38
|
export type AppState = UpdateState;
|
|
37
39
|
export declare function createInitialUpdateState(steps: DisplayStep[], version: string, logoVisibility: LogoVisibility): UpdateState;
|
|
38
40
|
export declare const createInitialAppState: typeof createInitialUpdateState;
|
|
41
|
+
export declare function countFailedSteps(completedStepRecords: CompletedStepRecord[]): number;
|
package/dist/update/state.js
CHANGED
|
@@ -13,3 +13,6 @@ export function createInitialUpdateState(steps, version, logoVisibility) {
|
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
15
|
export const createInitialAppState = createInitialUpdateState;
|
|
16
|
+
export function countFailedSteps(completedStepRecords) {
|
|
17
|
+
return completedStepRecords.filter((completedStepRecord) => completedStepRecord.phase === "failed").length;
|
|
18
|
+
}
|
package/package.json
CHANGED
package/dist/ui/outputBox.d.ts
DELETED
package/dist/ui/outputBox.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
const INDENT = " ";
|
|
3
|
-
const HEADER_LINES_APPROXIMATE = 18;
|
|
4
|
-
const OUTPUT_BOX_CHROME_LINES = 3;
|
|
5
|
-
const MINIMUM_VISIBLE_LINES = 5;
|
|
6
|
-
function maxVisibleLines(rows) {
|
|
7
|
-
const available = rows - HEADER_LINES_APPROXIMATE - OUTPUT_BOX_CHROME_LINES;
|
|
8
|
-
return Math.max(MINIMUM_VISIBLE_LINES, available);
|
|
9
|
-
}
|
|
10
|
-
function boxWidth(columns) {
|
|
11
|
-
return Math.min(columns, 120) - INDENT.length * 2;
|
|
12
|
-
}
|
|
13
|
-
function resolveTerminalDimensions(dimensions) {
|
|
14
|
-
return dimensions ?? {
|
|
15
|
-
columns: process.stdout.columns ?? 80,
|
|
16
|
-
rows: process.stdout.rows ?? 40,
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
export function buildOutputBoxLines(lines, stepName, dimensions) {
|
|
20
|
-
const resolved = resolveTerminalDimensions(dimensions);
|
|
21
|
-
const visibleLines = lines.slice(-maxVisibleLines(resolved.rows));
|
|
22
|
-
const width = boxWidth(resolved.columns);
|
|
23
|
-
const headerLabel = `─ ${stepName} output `;
|
|
24
|
-
const topDashes = "─".repeat(Math.max(0, width - headerLabel.length - 2));
|
|
25
|
-
const topBorder = chalk.dim(`${INDENT}┌${headerLabel}${topDashes}┐`);
|
|
26
|
-
const bottomBorder = chalk.dim(`${INDENT}└${"─".repeat(width - 2)}┘`);
|
|
27
|
-
const result = [topBorder];
|
|
28
|
-
for (const line of visibleLines) {
|
|
29
|
-
const truncated = line.text.slice(0, width - 4);
|
|
30
|
-
const colored = line.source === "stderr" ? chalk.yellow(truncated) : chalk.dim(truncated);
|
|
31
|
-
result.push(`${INDENT}${chalk.dim("│")} ${colored}`);
|
|
32
|
-
}
|
|
33
|
-
result.push(bottomBorder);
|
|
34
|
-
return result;
|
|
35
|
-
}
|