braeburn 1.4.1 → 1.4.3
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.js +19 -2
- package/dist/runner.js +51 -1
- package/dist/steps/nvm.js +11 -3
- 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 +7 -5
- package/dist/ui/screen.d.ts +1 -0
- package/dist/ui/screen.js +20 -1
- package/dist/ui/versionReport.d.ts +1 -0
- package/dist/ui/versionReport.js +3 -0
- package/package.json +1 -1
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/runner.js
CHANGED
|
@@ -1,4 +1,45 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
|
+
const FAILURE_OUTPUT_TAIL_LINE_LIMIT = 20;
|
|
3
|
+
function splitNonEmptyLines(text) {
|
|
4
|
+
if (!text) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
return text.split(/\r?\n|\r/).filter(Boolean);
|
|
8
|
+
}
|
|
9
|
+
function buildFailureSummaryLines(shellCommand, error) {
|
|
10
|
+
const defaultMessage = error instanceof Error ? error.message : String(error);
|
|
11
|
+
if (typeof error !== "object" || error === null) {
|
|
12
|
+
return [
|
|
13
|
+
`[braeburn] Command failed: ${shellCommand}`,
|
|
14
|
+
`[braeburn] Error: ${defaultMessage}`,
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
const errorDetails = error;
|
|
18
|
+
const summaryLines = [`[braeburn] Command failed: ${shellCommand}`];
|
|
19
|
+
if (typeof errorDetails.exitCode === "number") {
|
|
20
|
+
summaryLines.push(`[braeburn] Exit code: ${errorDetails.exitCode}`);
|
|
21
|
+
}
|
|
22
|
+
if (typeof errorDetails.signal === "string") {
|
|
23
|
+
summaryLines.push(`[braeburn] Signal: ${errorDetails.signal}`);
|
|
24
|
+
}
|
|
25
|
+
if (typeof errorDetails.shortMessage === "string" && errorDetails.shortMessage.length > 0) {
|
|
26
|
+
summaryLines.push(`[braeburn] ${errorDetails.shortMessage}`);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
summaryLines.push(`[braeburn] Error: ${defaultMessage}`);
|
|
30
|
+
}
|
|
31
|
+
const stderrTailLines = splitNonEmptyLines(errorDetails.stderr).slice(-FAILURE_OUTPUT_TAIL_LINE_LIMIT);
|
|
32
|
+
if (stderrTailLines.length > 0) {
|
|
33
|
+
summaryLines.push(`[braeburn] stderr tail (${stderrTailLines.length}):`);
|
|
34
|
+
summaryLines.push(...stderrTailLines.map((line) => ` ${line}`));
|
|
35
|
+
}
|
|
36
|
+
const stdoutTailLines = splitNonEmptyLines(errorDetails.stdout).slice(-FAILURE_OUTPUT_TAIL_LINE_LIMIT);
|
|
37
|
+
if (stdoutTailLines.length > 0) {
|
|
38
|
+
summaryLines.push(`[braeburn] stdout tail (${stdoutTailLines.length}):`);
|
|
39
|
+
summaryLines.push(...stdoutTailLines.map((line) => ` ${line}`));
|
|
40
|
+
}
|
|
41
|
+
return summaryLines;
|
|
42
|
+
}
|
|
2
43
|
export async function runShellCommand(options) {
|
|
3
44
|
const subprocess = execa("bash", ["-c", options.shellCommand], {
|
|
4
45
|
all: true,
|
|
@@ -18,7 +59,16 @@ export async function runShellCommand(options) {
|
|
|
18
59
|
options.logWriter(line);
|
|
19
60
|
}
|
|
20
61
|
});
|
|
21
|
-
|
|
62
|
+
try {
|
|
63
|
+
await subprocess;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const failureSummaryLines = buildFailureSummaryLines(options.shellCommand, error);
|
|
67
|
+
for (const line of failureSummaryLines) {
|
|
68
|
+
await options.logWriter(line);
|
|
69
|
+
}
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
22
72
|
}
|
|
23
73
|
export async function doesShellCommandSucceed(options) {
|
|
24
74
|
const result = await execa("bash", ["-c", options.shellCommand], {
|
package/dist/steps/nvm.js
CHANGED
|
@@ -2,19 +2,27 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { checkPathExists } from "./runtime.js";
|
|
4
4
|
const NVM_DIRECTORY = join(homedir(), ".nvm");
|
|
5
|
+
const NVM_SCRIPT_PATH = join(NVM_DIRECTORY, "nvm.sh");
|
|
5
6
|
// nvm is a shell function sourced from nvm.sh — it cannot be invoked as a
|
|
6
7
|
// standalone binary, so we source it explicitly inside each bash invocation.
|
|
7
|
-
const NVM_SOURCE_PREFIX = `export NVM_DIR="${NVM_DIRECTORY}" && source "$NVM_DIR/nvm.sh"`;
|
|
8
|
+
const NVM_SOURCE_PREFIX = `export NVM_DIR="${NVM_DIRECTORY}" && source "$NVM_DIR/nvm.sh" --no-use`;
|
|
9
|
+
const NVM_INSTALL_COMMAND = `${NVM_SOURCE_PREFIX} && ` +
|
|
10
|
+
`CURRENT_NODE_VERSION="$(nvm current)" && ` +
|
|
11
|
+
`if [ "$CURRENT_NODE_VERSION" = "none" ] || [ "$CURRENT_NODE_VERSION" = "system" ]; then ` +
|
|
12
|
+
`nvm install node; ` +
|
|
13
|
+
`else ` +
|
|
14
|
+
`nvm install node --reinstall-packages-from="$CURRENT_NODE_VERSION"; ` +
|
|
15
|
+
`fi`;
|
|
8
16
|
const nvmStep = {
|
|
9
17
|
id: "nvm",
|
|
10
18
|
name: "Node.js (nvm)",
|
|
11
19
|
stage: "runtime",
|
|
12
20
|
description: "Install the latest Node.js via nvm, migrating packages from the current version",
|
|
13
21
|
async checkIsAvailable() {
|
|
14
|
-
return checkPathExists(
|
|
22
|
+
return checkPathExists(NVM_SCRIPT_PATH);
|
|
15
23
|
},
|
|
16
24
|
async run(context) {
|
|
17
|
-
await context.runStep(
|
|
25
|
+
await context.runStep(NVM_INSTALL_COMMAND);
|
|
18
26
|
},
|
|
19
27
|
};
|
|
20
28
|
export default nvmStep;
|
|
@@ -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,6 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { LOGO_ART } from "../logo.js";
|
|
3
|
+
import { getActivityIndicatorFrame } from "./activityIndicator.js";
|
|
3
4
|
const LOGO_COLUMN_WIDTH = 32;
|
|
4
5
|
const LOGO_SEPARATOR = " ";
|
|
5
6
|
const MIN_SIDE_BY_SIDE_COLS = LOGO_COLUMN_WIDTH + LOGO_SEPARATOR.length + 20; // 56
|
|
@@ -14,16 +15,16 @@ export function determineLogoLayout(logoLines, dimensions) {
|
|
|
14
15
|
}
|
|
15
16
|
return "none";
|
|
16
17
|
}
|
|
17
|
-
export function stepTrackerIcon(phase) {
|
|
18
|
+
export function stepTrackerIcon(phase, activityFrameIndex = 0) {
|
|
18
19
|
if (phase === "complete")
|
|
19
20
|
return chalk.green("✓ ");
|
|
20
21
|
if (phase === "failed")
|
|
21
22
|
return chalk.red("✗ ");
|
|
22
23
|
if (phase === "skipped" || phase === "not-available")
|
|
23
24
|
return chalk.dim("– ");
|
|
24
|
-
if (phase === "running" ||
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
if (phase === "running" || phase === "installing")
|
|
26
|
+
return chalk.cyan(`${getActivityIndicatorFrame(activityFrameIndex)} `);
|
|
27
|
+
if (phase === "prompting-to-run" ||
|
|
27
28
|
phase === "prompting-to-install" ||
|
|
28
29
|
phase === "checking-availability")
|
|
29
30
|
return chalk.cyan("→ ");
|
|
@@ -47,6 +48,7 @@ export function deriveAllStepPhases(steps, currentStepIndex, currentPhase, compl
|
|
|
47
48
|
}
|
|
48
49
|
export function buildHeaderLines(options) {
|
|
49
50
|
const { steps, version, logoVisibility, currentStepIndex, currentPhase, completedStepRecords } = options;
|
|
51
|
+
const activityFrameIndex = options.activityFrameIndex ?? 0;
|
|
50
52
|
const phases = deriveAllStepPhases(steps, currentStepIndex, currentPhase, completedStepRecords);
|
|
51
53
|
const hasRuntimeSteps = steps.some((step) => step.stage === "runtime");
|
|
52
54
|
const hasToolsSteps = steps.some((step) => step.stage === "tools");
|
|
@@ -64,7 +66,7 @@ export function buildHeaderLines(options) {
|
|
|
64
66
|
stepLines.push(chalk.dim("Tools"));
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
|
-
const icon = stepTrackerIcon(phases[stepIndex]);
|
|
69
|
+
const icon = stepTrackerIcon(phases[stepIndex], activityFrameIndex);
|
|
68
70
|
const name = isActivePhase(phases[stepIndex]) ? chalk.white(step.name) : chalk.dim(step.name);
|
|
69
71
|
stepLines.push(`${icon}${name}`);
|
|
70
72
|
}
|
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
|
@@ -2,7 +2,7 @@ import { buildHeaderLines } from "./header.js";
|
|
|
2
2
|
import { buildActiveStepLines } from "./currentStep.js";
|
|
3
3
|
import { buildOutputBoxLines } from "./outputBox.js";
|
|
4
4
|
import { buildPromptLines } from "./prompt.js";
|
|
5
|
-
import { buildVersionReportLines } from "./versionReport.js";
|
|
5
|
+
import { buildFailedStepLogHintLines, buildVersionReportLines } from "./versionReport.js";
|
|
6
6
|
export function createScreenRenderer(output = process.stdout) {
|
|
7
7
|
return (content) => {
|
|
8
8
|
output.write("\x1b[H\x1b[2J");
|
|
@@ -10,7 +10,20 @@ 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 = [];
|
|
17
|
+
const failedStepIds = state.completedStepRecords.flatMap((record, stepIndex) => {
|
|
18
|
+
if (record.phase !== "failed") {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const failedStep = state.steps[stepIndex];
|
|
22
|
+
if (!failedStep) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
return [failedStep.id];
|
|
26
|
+
});
|
|
14
27
|
lines.push(...buildHeaderLines({
|
|
15
28
|
steps: state.steps,
|
|
16
29
|
version: state.version,
|
|
@@ -18,6 +31,7 @@ export function buildScreen(state, terminalDimensions) {
|
|
|
18
31
|
currentStepIndex: state.currentStepIndex,
|
|
19
32
|
currentPhase: state.currentPhase,
|
|
20
33
|
completedStepRecords: state.completedStepRecords,
|
|
34
|
+
activityFrameIndex,
|
|
21
35
|
terminalDimensions,
|
|
22
36
|
}));
|
|
23
37
|
lines.push("");
|
|
@@ -26,6 +40,10 @@ export function buildScreen(state, terminalDimensions) {
|
|
|
26
40
|
lines.push("");
|
|
27
41
|
lines.push(...buildVersionReportLines(state.versionReport));
|
|
28
42
|
}
|
|
43
|
+
if (failedStepIds.length > 0) {
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push(...buildFailedStepLogHintLines(failedStepIds));
|
|
46
|
+
}
|
|
29
47
|
}
|
|
30
48
|
else {
|
|
31
49
|
const currentStep = state.steps[state.currentStepIndex];
|
|
@@ -36,6 +54,7 @@ export function buildScreen(state, terminalDimensions) {
|
|
|
36
54
|
stepNumber: state.currentStepIndex + 1,
|
|
37
55
|
totalSteps: state.steps.length,
|
|
38
56
|
phase: state.currentPhase,
|
|
57
|
+
activityFrameIndex,
|
|
39
58
|
}));
|
|
40
59
|
const isShowingOutput = (state.currentPhase === "running" || state.currentPhase === "installing") &&
|
|
41
60
|
state.currentOutputLines.length > 0;
|
package/dist/ui/versionReport.js
CHANGED
|
@@ -7,3 +7,6 @@ export function buildVersionReportLines(versions) {
|
|
|
7
7
|
` ${chalk.green.bold("✓")} ${chalk.bold("All done!")}`,
|
|
8
8
|
];
|
|
9
9
|
}
|
|
10
|
+
export function buildFailedStepLogHintLines(failedStepIds) {
|
|
11
|
+
return failedStepIds.map((stepId) => ` ${chalk.red.bold("✗")} ${chalk.bold(`Step ${stepId} failed.`)} ${chalk.dim(`Please run braeburn log --${stepId} to see what happened.`)}`);
|
|
12
|
+
}
|