braeburn 1.3.0 → 1.4.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/setup.d.ts +9 -1
- package/dist/commands/setup.js +66 -55
- package/dist/commands/update.d.ts +2 -2
- package/dist/commands/update.js +22 -101
- package/dist/index.js +1 -13
- package/dist/runner.js +2 -2
- package/dist/steps/catalog.d.ts +2 -0
- package/dist/steps/catalog.js +13 -0
- package/dist/steps/cleanup.d.ts +1 -1
- package/dist/steps/cleanup.js +1 -1
- package/dist/steps/dotnet.d.ts +1 -1
- package/dist/steps/dotnet.js +1 -1
- package/dist/steps/homebrew.d.ts +1 -1
- package/dist/steps/homebrew.js +1 -1
- package/dist/steps/index.d.ts +2 -25
- package/dist/steps/index.js +1 -23
- package/dist/steps/macos.d.ts +1 -1
- package/dist/steps/mas.d.ts +1 -1
- package/dist/steps/mas.js +1 -1
- package/dist/steps/npm.d.ts +1 -1
- package/dist/steps/npm.js +1 -1
- package/dist/steps/nvm.d.ts +1 -1
- package/dist/steps/nvm.js +1 -1
- package/dist/steps/ohmyzsh.d.ts +1 -1
- package/dist/steps/ohmyzsh.js +1 -1
- package/dist/steps/pip.d.ts +1 -1
- package/dist/steps/pip.js +1 -1
- package/dist/steps/pyenv.d.ts +1 -1
- package/dist/steps/pyenv.js +1 -1
- package/dist/steps/runtime.d.ts +7 -0
- package/dist/steps/runtime.js +23 -0
- package/dist/steps/types.d.ts +21 -0
- package/dist/steps/types.js +1 -0
- package/dist/ui/currentStep.d.ts +2 -3
- package/dist/ui/header.d.ts +3 -4
- package/dist/ui/screen.js +5 -4
- package/dist/ui/state.d.ts +1 -30
- package/dist/ui/state.js +1 -14
- package/dist/ui/terminal.d.ts +1 -0
- package/dist/ui/terminal.js +14 -3
- package/dist/ui/versionReport.d.ts +0 -1
- package/dist/ui/versionReport.js +0 -16
- package/dist/update/displayStep.d.ts +4 -0
- package/dist/update/displayStep.js +11 -0
- package/dist/update/engine.d.ts +23 -0
- package/dist/update/engine.js +126 -0
- package/dist/update/state.d.ts +38 -0
- package/dist/update/state.js +15 -0
- package/dist/update/versionCollector.d.ts +2 -0
- package/dist/update/versionCollector.js +16 -0
- package/package.json +1 -1
package/dist/commands/setup.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type { Step } from "../steps/index.js";
|
|
2
|
+
import type { StepStage } from "../update/state.js";
|
|
2
3
|
export type SelectionState = "selected" | "deselected";
|
|
3
4
|
export type ProtectionStatus = "protected" | "configurable";
|
|
4
5
|
export type AvailabilityStatus = "available" | "unavailable";
|
|
6
|
+
export type SetupStepView = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
stage: StepStage;
|
|
11
|
+
brewPackageToInstall?: string;
|
|
12
|
+
};
|
|
5
13
|
export type SelectableStep = {
|
|
6
|
-
step:
|
|
14
|
+
step: SetupStepView;
|
|
7
15
|
selection: SelectionState;
|
|
8
16
|
protection: ProtectionStatus;
|
|
9
17
|
availability: AvailabilityStatus;
|
package/dist/commands/setup.js
CHANGED
|
@@ -73,64 +73,75 @@ export function buildSetupScreen(items, cursorIndex) {
|
|
|
73
73
|
}
|
|
74
74
|
export async function runSetupCommand(allSteps) {
|
|
75
75
|
const render = createScreenRenderer();
|
|
76
|
-
hideCursorDuringExecution();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
step,
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const item = items[cursorIndex];
|
|
105
|
-
if (item.protection === "configurable") {
|
|
106
|
-
item.selection = item.selection === "selected" ? "deselected" : "selected";
|
|
76
|
+
const restoreCursor = hideCursorDuringExecution({ screenBuffer: "alternate" });
|
|
77
|
+
try {
|
|
78
|
+
render(buildLoadingScreen());
|
|
79
|
+
const availabilityResults = await Promise.all(allSteps.map((step) => step.checkIsAvailable()));
|
|
80
|
+
const items = allSteps.map((step, stepIndex) => ({
|
|
81
|
+
step: {
|
|
82
|
+
id: step.id,
|
|
83
|
+
name: step.name,
|
|
84
|
+
description: step.description,
|
|
85
|
+
stage: step.stage,
|
|
86
|
+
brewPackageToInstall: step.brewPackageToInstall,
|
|
87
|
+
},
|
|
88
|
+
selection: PROTECTED_STEP_IDS.has(step.id) || step.stage === "tools" ? "selected" : "deselected",
|
|
89
|
+
protection: PROTECTED_STEP_IDS.has(step.id) ? "protected" : "configurable",
|
|
90
|
+
availability: availabilityResults[stepIndex] ? "available" : "unavailable",
|
|
91
|
+
}));
|
|
92
|
+
let cursorIndex = 0;
|
|
93
|
+
render(buildSetupScreen(items, cursorIndex));
|
|
94
|
+
await new Promise((resolve) => {
|
|
95
|
+
readline.emitKeypressEvents(process.stdin);
|
|
96
|
+
if (process.stdin.isTTY)
|
|
97
|
+
process.stdin.setRawMode(true);
|
|
98
|
+
const handleKeypress = (_char, key) => {
|
|
99
|
+
if (key?.ctrl && key?.name === "c") {
|
|
100
|
+
process.exit(130);
|
|
101
|
+
}
|
|
102
|
+
if (key?.name === "up" || key?.name === "k") {
|
|
103
|
+
cursorIndex = Math.max(0, cursorIndex - 1);
|
|
107
104
|
render(buildSetupScreen(items, cursorIndex));
|
|
108
105
|
}
|
|
106
|
+
else if (key?.name === "down" || key?.name === "j") {
|
|
107
|
+
cursorIndex = Math.min(items.length - 1, cursorIndex + 1);
|
|
108
|
+
render(buildSetupScreen(items, cursorIndex));
|
|
109
|
+
}
|
|
110
|
+
else if (key?.name === "space") {
|
|
111
|
+
const item = items[cursorIndex];
|
|
112
|
+
if (item.protection === "configurable") {
|
|
113
|
+
item.selection = item.selection === "selected" ? "deselected" : "selected";
|
|
114
|
+
render(buildSetupScreen(items, cursorIndex));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else if (key?.name === "return") {
|
|
118
|
+
process.stdin.removeListener("keypress", handleKeypress);
|
|
119
|
+
if (process.stdin.isTTY)
|
|
120
|
+
process.stdin.setRawMode(false);
|
|
121
|
+
process.stdin.pause();
|
|
122
|
+
resolve();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
process.stdin.on("keypress", handleKeypress);
|
|
126
|
+
process.stdin.resume();
|
|
127
|
+
});
|
|
128
|
+
const stepsConfig = {};
|
|
129
|
+
for (const item of items) {
|
|
130
|
+
if (item.protection === "configurable" && item.selection === "deselected") {
|
|
131
|
+
stepsConfig[item.step.id] = false;
|
|
109
132
|
}
|
|
110
|
-
else if (key?.name === "return") {
|
|
111
|
-
process.stdin.removeListener("keypress", handleKeypress);
|
|
112
|
-
if (process.stdin.isTTY)
|
|
113
|
-
process.stdin.setRawMode(false);
|
|
114
|
-
process.stdin.pause();
|
|
115
|
-
resolve();
|
|
116
|
-
}
|
|
117
|
-
};
|
|
118
|
-
process.stdin.on("keypress", handleKeypress);
|
|
119
|
-
process.stdin.resume();
|
|
120
|
-
});
|
|
121
|
-
const stepsConfig = {};
|
|
122
|
-
for (const item of items) {
|
|
123
|
-
if (item.protection === "configurable" && item.selection === "deselected") {
|
|
124
|
-
stepsConfig[item.step.id] = false;
|
|
125
133
|
}
|
|
134
|
+
await writeConfig({ steps: stepsConfig });
|
|
135
|
+
const confirmationLines = [
|
|
136
|
+
chalk.yellow(LOGO_ART),
|
|
137
|
+
"",
|
|
138
|
+
` ${chalk.green("\u2713")} Setup complete! Starting your first update\u2026`,
|
|
139
|
+
"",
|
|
140
|
+
];
|
|
141
|
+
render(confirmationLines.join("\n") + "\n");
|
|
142
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
restoreCursor();
|
|
126
146
|
}
|
|
127
|
-
await writeConfig({ steps: stepsConfig });
|
|
128
|
-
const confirmationLines = [
|
|
129
|
-
chalk.yellow(LOGO_ART),
|
|
130
|
-
"",
|
|
131
|
-
` ${chalk.green("\u2713")} Setup complete! Starting your first update\u2026`,
|
|
132
|
-
"",
|
|
133
|
-
];
|
|
134
|
-
render(confirmationLines.join("\n") + "\n");
|
|
135
|
-
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
136
147
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { type PromptMode } from "../update/engine.js";
|
|
2
|
+
import type { LogoVisibility } from "../update/state.js";
|
|
1
3
|
import type { Step } from "../steps/index.js";
|
|
2
|
-
type PromptMode = "interactive" | "auto-accept";
|
|
3
|
-
type LogoVisibility = "visible" | "hidden";
|
|
4
4
|
type RunUpdateCommandOptions = {
|
|
5
5
|
steps: Step[];
|
|
6
6
|
promptMode: PromptMode;
|
package/dist/commands/update.js
CHANGED
|
@@ -1,109 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createLogWriterForStep } from "../logger.js";
|
|
3
|
-
import { collectVersions } from "../ui/versionReport.js";
|
|
1
|
+
import { collectVersions } from "../update/versionCollector.js";
|
|
4
2
|
import { captureYesNo } from "../ui/prompt.js";
|
|
5
|
-
import { createInitialAppState } from "../ui/state.js";
|
|
6
3
|
import { buildScreen, createScreenRenderer } from "../ui/screen.js";
|
|
7
4
|
import { hideCursorDuringExecution } from "../ui/terminal.js";
|
|
8
|
-
import {
|
|
5
|
+
import { runUpdateEngine } from "../update/engine.js";
|
|
9
6
|
export async function runUpdateCommand(options) {
|
|
10
|
-
const { steps, version } = options;
|
|
11
|
-
let autoAccept = options.promptMode === "auto-accept";
|
|
12
|
-
const state = createInitialAppState(steps, version, options.logoVisibility);
|
|
13
7
|
const renderScreen = createScreenRenderer();
|
|
14
|
-
hideCursorDuringExecution();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
state.currentPhase = "not-available";
|
|
26
|
-
renderScreen(buildScreen(state));
|
|
27
|
-
state.completedStepRecords.push({ phase: "not-available", summaryNote: "not installed" });
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
if (!isAvailable && step.brewPackageToInstall) {
|
|
31
|
-
state.currentPhase = "prompting-to-install";
|
|
32
|
-
state.currentPrompt = {
|
|
33
|
-
question: `Install ${step.name} via Homebrew? (brew install ${step.brewPackageToInstall})`,
|
|
34
|
-
};
|
|
35
|
-
renderScreen(buildScreen(state));
|
|
36
|
-
const installAnswer = autoAccept ? "yes" : await captureYesNo();
|
|
37
|
-
if (installAnswer === "force")
|
|
38
|
-
autoAccept = true;
|
|
39
|
-
const shouldInstall = installAnswer !== "no";
|
|
40
|
-
state.currentPrompt = undefined;
|
|
41
|
-
if (!shouldInstall) {
|
|
42
|
-
state.currentPhase = "skipped";
|
|
8
|
+
const restoreCursor = hideCursorDuringExecution({ screenBuffer: "alternate" });
|
|
9
|
+
let finalScreen = "";
|
|
10
|
+
try {
|
|
11
|
+
const finalState = await runUpdateEngine({
|
|
12
|
+
steps: options.steps,
|
|
13
|
+
promptMode: options.promptMode,
|
|
14
|
+
version: options.version,
|
|
15
|
+
logoVisibility: options.logoVisibility,
|
|
16
|
+
askForConfirmation: captureYesNo,
|
|
17
|
+
collectVersions,
|
|
18
|
+
onStateChanged: (state) => {
|
|
43
19
|
renderScreen(buildScreen(state));
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
onOutputLine: (line) => {
|
|
54
|
-
state.currentOutputLines.push(line);
|
|
55
|
-
renderScreen(buildScreen(state));
|
|
56
|
-
},
|
|
57
|
-
logWriter: installLogWriter,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
state.currentPhase = "failed";
|
|
62
|
-
renderScreen(buildScreen(state));
|
|
63
|
-
state.completedStepRecords.push({ phase: "failed", summaryNote: "install failed" });
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
state.currentPhase = "prompting-to-run";
|
|
68
|
-
state.currentPrompt = { question: `Run ${step.name} update?`, warning: step.warning };
|
|
69
|
-
renderScreen(buildScreen(state));
|
|
70
|
-
const runAnswer = autoAccept ? "yes" : await captureYesNo();
|
|
71
|
-
if (runAnswer === "force")
|
|
72
|
-
autoAccept = true;
|
|
73
|
-
const shouldRun = runAnswer !== "no";
|
|
74
|
-
state.currentPrompt = undefined;
|
|
75
|
-
if (!shouldRun) {
|
|
76
|
-
state.currentPhase = "skipped";
|
|
77
|
-
renderScreen(buildScreen(state));
|
|
78
|
-
state.completedStepRecords.push({ phase: "skipped" });
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
state.currentPhase = "running";
|
|
82
|
-
state.currentOutputLines = [];
|
|
83
|
-
renderScreen(buildScreen(state));
|
|
84
|
-
const stepLogWriter = await createLogWriterForStep(step.id);
|
|
85
|
-
try {
|
|
86
|
-
await step.run(createDefaultStepRunContext((line) => {
|
|
87
|
-
state.currentOutputLines.push(line);
|
|
88
|
-
renderScreen(buildScreen(state));
|
|
89
|
-
}, stepLogWriter));
|
|
90
|
-
state.currentPhase = "complete";
|
|
91
|
-
state.currentOutputLines = [];
|
|
92
|
-
renderScreen(buildScreen(state));
|
|
93
|
-
state.completedStepRecords.push({ phase: "complete", summaryNote: "updated" });
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
97
|
-
state.currentPhase = "failed";
|
|
98
|
-
state.currentOutputLines = [];
|
|
99
|
-
renderScreen(buildScreen(state));
|
|
100
|
-
state.completedStepRecords.push({ phase: "failed", summaryNote: errorMessage });
|
|
101
|
-
}
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
finalScreen = buildScreen(finalState);
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
restoreCursor();
|
|
26
|
+
}
|
|
27
|
+
if (finalScreen) {
|
|
28
|
+
process.stdout.write(finalScreen);
|
|
102
29
|
}
|
|
103
|
-
state.runCompletion = "finished";
|
|
104
|
-
state.currentOutputLines = [];
|
|
105
|
-
state.currentPrompt = undefined;
|
|
106
|
-
renderScreen(buildScreen(state));
|
|
107
|
-
state.versionReport = await collectVersions();
|
|
108
|
-
renderScreen(buildScreen(state));
|
|
109
30
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,24 +3,12 @@ import { Command } from "commander";
|
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
|
-
import {
|
|
6
|
+
import { ALL_STEPS } from "./steps/catalog.js";
|
|
7
7
|
import { 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";
|
|
11
11
|
import { readConfig, isStepEnabled, isLogoEnabled, PROTECTED_STEP_IDS, configFileExists } from "./config.js";
|
|
12
|
-
const ALL_STEPS = [
|
|
13
|
-
pyenvStep,
|
|
14
|
-
nvmStep,
|
|
15
|
-
homebrewStep,
|
|
16
|
-
masStep,
|
|
17
|
-
ohmyzshStep,
|
|
18
|
-
npmStep,
|
|
19
|
-
pipStep,
|
|
20
|
-
dotnetStep,
|
|
21
|
-
macosStep,
|
|
22
|
-
cleanupStep,
|
|
23
|
-
];
|
|
24
12
|
const STEP_IDS_BY_NAME = new Map(ALL_STEPS.map((step) => [step.id, step]));
|
|
25
13
|
const requireFromThis = createRequire(import.meta.url);
|
|
26
14
|
const packageJson = requireFromThis(join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"));
|
package/dist/runner.js
CHANGED
|
@@ -5,14 +5,14 @@ export async function runShellCommand(options) {
|
|
|
5
5
|
reject: true,
|
|
6
6
|
});
|
|
7
7
|
subprocess.stdout?.on("data", (chunk) => {
|
|
8
|
-
const lines = String(chunk).split(
|
|
8
|
+
const lines = String(chunk).split(/\r?\n|\r/).filter(Boolean);
|
|
9
9
|
for (const line of lines) {
|
|
10
10
|
options.onOutputLine({ text: line, source: "stdout" });
|
|
11
11
|
options.logWriter(line);
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
14
|
subprocess.stderr?.on("data", (chunk) => {
|
|
15
|
-
const lines = String(chunk).split(
|
|
15
|
+
const lines = String(chunk).split(/\r?\n|\r/).filter(Boolean);
|
|
16
16
|
for (const line of lines) {
|
|
17
17
|
options.onOutputLine({ text: line, source: "stderr" });
|
|
18
18
|
options.logWriter(line);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { pyenvStep, nvmStep, homebrewStep, masStep, ohmyzshStep, npmStep, pipStep, dotnetStep, macosStep, cleanupStep, } from "./index.js";
|
|
2
|
+
export const ALL_STEPS = [
|
|
3
|
+
pyenvStep,
|
|
4
|
+
nvmStep,
|
|
5
|
+
homebrewStep,
|
|
6
|
+
masStep,
|
|
7
|
+
ohmyzshStep,
|
|
8
|
+
npmStep,
|
|
9
|
+
pipStep,
|
|
10
|
+
dotnetStep,
|
|
11
|
+
macosStep,
|
|
12
|
+
cleanupStep,
|
|
13
|
+
];
|
package/dist/steps/cleanup.d.ts
CHANGED
package/dist/steps/cleanup.js
CHANGED
package/dist/steps/dotnet.d.ts
CHANGED
package/dist/steps/dotnet.js
CHANGED
package/dist/steps/homebrew.d.ts
CHANGED
package/dist/steps/homebrew.js
CHANGED
package/dist/steps/index.d.ts
CHANGED
|
@@ -1,24 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type StepRunContext = {
|
|
4
|
-
onOutputLine: OutputLineCallback;
|
|
5
|
-
logWriter: StepLogWriter;
|
|
6
|
-
runStep: (shellCommand: string) => Promise<void>;
|
|
7
|
-
captureOutput: (options: {
|
|
8
|
-
shellCommand: string;
|
|
9
|
-
}) => Promise<string>;
|
|
10
|
-
};
|
|
11
|
-
export type StepStage = "runtime" | "tools";
|
|
12
|
-
export type Step = {
|
|
13
|
-
id: string;
|
|
14
|
-
name: string;
|
|
15
|
-
description: string;
|
|
16
|
-
stage: StepStage;
|
|
17
|
-
warning?: string;
|
|
18
|
-
brewPackageToInstall?: string;
|
|
19
|
-
checkIsAvailable: () => Promise<boolean>;
|
|
20
|
-
run: (context: StepRunContext) => Promise<void>;
|
|
21
|
-
};
|
|
1
|
+
export type { StepRunContext, StepStage, Step, } from "./types.js";
|
|
2
|
+
export { checkCommandExists, checkPathExists, runStep, createDefaultStepRunContext, } from "./runtime.js";
|
|
22
3
|
export { default as homebrewStep } from "./homebrew.js";
|
|
23
4
|
export { default as masStep } from "./mas.js";
|
|
24
5
|
export { default as ohmyzshStep } from "./ohmyzsh.js";
|
|
@@ -29,7 +10,3 @@ export { default as nvmStep } from "./nvm.js";
|
|
|
29
10
|
export { default as dotnetStep } from "./dotnet.js";
|
|
30
11
|
export { default as macosStep } from "./macos.js";
|
|
31
12
|
export { default as cleanupStep } from "./cleanup.js";
|
|
32
|
-
export declare function checkCommandExists(command: string): Promise<boolean>;
|
|
33
|
-
export declare function checkPathExists(filePath: string): Promise<boolean>;
|
|
34
|
-
export declare function runStep(shellCommand: string, context: StepRunContext): Promise<void>;
|
|
35
|
-
export declare function createDefaultStepRunContext(onOutputLine: OutputLineCallback, logWriter: StepLogWriter): StepRunContext;
|
package/dist/steps/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
export { checkCommandExists, checkPathExists, runStep, createDefaultStepRunContext, } from "./runtime.js";
|
|
2
2
|
export { default as homebrewStep } from "./homebrew.js";
|
|
3
3
|
export { default as masStep } from "./mas.js";
|
|
4
4
|
export { default as ohmyzshStep } from "./ohmyzsh.js";
|
|
@@ -9,25 +9,3 @@ export { default as nvmStep } from "./nvm.js";
|
|
|
9
9
|
export { default as dotnetStep } from "./dotnet.js";
|
|
10
10
|
export { default as macosStep } from "./macos.js";
|
|
11
11
|
export { default as cleanupStep } from "./cleanup.js";
|
|
12
|
-
export async function checkCommandExists(command) {
|
|
13
|
-
return doesShellCommandSucceed({ shellCommand: `command -v ${command}` });
|
|
14
|
-
}
|
|
15
|
-
export async function checkPathExists(filePath) {
|
|
16
|
-
return doesShellCommandSucceed({ shellCommand: `test -e "${filePath}"` });
|
|
17
|
-
}
|
|
18
|
-
export async function runStep(shellCommand, context) {
|
|
19
|
-
await runShellCommand({
|
|
20
|
-
shellCommand,
|
|
21
|
-
onOutputLine: context.onOutputLine,
|
|
22
|
-
logWriter: context.logWriter,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
export function createDefaultStepRunContext(onOutputLine, logWriter) {
|
|
26
|
-
const context = {
|
|
27
|
-
onOutputLine,
|
|
28
|
-
logWriter,
|
|
29
|
-
runStep: (shellCommand) => runStep(shellCommand, context),
|
|
30
|
-
captureOutput: captureShellCommandOutput,
|
|
31
|
-
};
|
|
32
|
-
return context;
|
|
33
|
-
}
|
package/dist/steps/macos.d.ts
CHANGED
package/dist/steps/mas.d.ts
CHANGED
package/dist/steps/mas.js
CHANGED
package/dist/steps/npm.d.ts
CHANGED
package/dist/steps/npm.js
CHANGED
package/dist/steps/nvm.d.ts
CHANGED
package/dist/steps/nvm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { checkPathExists } from "./
|
|
3
|
+
import { checkPathExists } from "./runtime.js";
|
|
4
4
|
const NVM_DIRECTORY = join(homedir(), ".nvm");
|
|
5
5
|
// nvm is a shell function sourced from nvm.sh — it cannot be invoked as a
|
|
6
6
|
// standalone binary, so we source it explicitly inside each bash invocation.
|
package/dist/steps/ohmyzsh.d.ts
CHANGED
package/dist/steps/ohmyzsh.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { checkPathExists } from "./
|
|
3
|
+
import { checkPathExists } from "./runtime.js";
|
|
4
4
|
const OH_MY_ZSH_UPGRADE_SCRIPT_PATH = join(homedir(), ".oh-my-zsh", "tools", "upgrade.sh");
|
|
5
5
|
const ohmyzshStep = {
|
|
6
6
|
id: "ohmyzsh",
|
package/dist/steps/pip.d.ts
CHANGED
package/dist/steps/pip.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkCommandExists } from "./
|
|
1
|
+
import { checkCommandExists } from "./runtime.js";
|
|
2
2
|
const PIP_UPDATE_ALL_OUTDATED_SHELL_COMMAND = "pip3 list --outdated --format=columns | tail -n +3 | awk '{print $1}' | xargs -n1 pip3 install -U";
|
|
3
3
|
const pipStep = {
|
|
4
4
|
id: "pip",
|
package/dist/steps/pyenv.d.ts
CHANGED
package/dist/steps/pyenv.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkCommandExists, } from "./
|
|
1
|
+
import { checkCommandExists, } from "./runtime.js";
|
|
2
2
|
const FIND_LATEST_STABLE_PYTHON_SHELL_COMMAND = "pyenv install -l | grep -E '^\\s+3\\.[0-9]+\\.[0-9]+$' | grep -vE 'dev|a[0-9]|b[0-9]|rc[0-9]' | tail -1 | tr -d ' '";
|
|
3
3
|
const pyenvStep = {
|
|
4
4
|
id: "pyenv",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type OutputLineCallback } from "../runner.js";
|
|
2
|
+
import type { StepLogWriter } from "../logger.js";
|
|
3
|
+
import type { StepRunContext } from "./types.js";
|
|
4
|
+
export declare function checkCommandExists(command: string): Promise<boolean>;
|
|
5
|
+
export declare function checkPathExists(filePath: string): Promise<boolean>;
|
|
6
|
+
export declare function runStep(shellCommand: string, context: StepRunContext): Promise<void>;
|
|
7
|
+
export declare function createDefaultStepRunContext(onOutputLine: OutputLineCallback, logWriter: StepLogWriter): StepRunContext;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { doesShellCommandSucceed, runShellCommand, captureShellCommandOutput, } from "../runner.js";
|
|
2
|
+
export async function checkCommandExists(command) {
|
|
3
|
+
return doesShellCommandSucceed({ shellCommand: `command -v ${command}` });
|
|
4
|
+
}
|
|
5
|
+
export async function checkPathExists(filePath) {
|
|
6
|
+
return doesShellCommandSucceed({ shellCommand: `test -e "${filePath}"` });
|
|
7
|
+
}
|
|
8
|
+
export async function runStep(shellCommand, context) {
|
|
9
|
+
await runShellCommand({
|
|
10
|
+
shellCommand,
|
|
11
|
+
onOutputLine: context.onOutputLine,
|
|
12
|
+
logWriter: context.logWriter,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function createDefaultStepRunContext(onOutputLine, logWriter) {
|
|
16
|
+
const context = {
|
|
17
|
+
onOutputLine,
|
|
18
|
+
logWriter,
|
|
19
|
+
runStep: (shellCommand) => runStep(shellCommand, context),
|
|
20
|
+
captureOutput: captureShellCommandOutput,
|
|
21
|
+
};
|
|
22
|
+
return context;
|
|
23
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OutputLineCallback } from "../runner.js";
|
|
2
|
+
import type { StepLogWriter } from "../logger.js";
|
|
3
|
+
export type StepRunContext = {
|
|
4
|
+
onOutputLine: OutputLineCallback;
|
|
5
|
+
logWriter: StepLogWriter;
|
|
6
|
+
runStep: (shellCommand: string) => Promise<void>;
|
|
7
|
+
captureOutput: (options: {
|
|
8
|
+
shellCommand: string;
|
|
9
|
+
}) => Promise<string>;
|
|
10
|
+
};
|
|
11
|
+
export type StepStage = "runtime" | "tools";
|
|
12
|
+
export type Step = {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description: string;
|
|
16
|
+
stage: StepStage;
|
|
17
|
+
warning?: string;
|
|
18
|
+
brewPackageToInstall?: string;
|
|
19
|
+
checkIsAvailable: () => Promise<boolean>;
|
|
20
|
+
run: (context: StepRunContext) => Promise<void>;
|
|
21
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/ui/currentStep.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { StepPhase } from "./state.js";
|
|
1
|
+
import type { DisplayStep, StepPhase } from "./state.js";
|
|
3
2
|
type ActiveStepOptions = {
|
|
4
|
-
step:
|
|
3
|
+
step: DisplayStep;
|
|
5
4
|
stepNumber: number;
|
|
6
5
|
totalSteps: number;
|
|
7
6
|
phase: StepPhase;
|
package/dist/ui/header.d.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { StepPhase, CompletedStepRecord, LogoVisibility } from "./state.js";
|
|
1
|
+
import type { DisplayStep, StepPhase, CompletedStepRecord, LogoVisibility } from "./state.js";
|
|
3
2
|
import type { TerminalDimensions } from "./outputBox.js";
|
|
4
3
|
type LogoLayout = "side-by-side" | "stacked" | "none";
|
|
5
4
|
export declare function determineLogoLayout(logoLines: string[], dimensions?: TerminalDimensions): LogoLayout;
|
|
6
5
|
export declare function stepTrackerIcon(phase: StepPhase): string;
|
|
7
6
|
export declare function isActivePhase(phase: StepPhase): boolean;
|
|
8
|
-
export declare function deriveAllStepPhases(steps:
|
|
7
|
+
export declare function deriveAllStepPhases(steps: DisplayStep[], currentStepIndex: number, currentPhase: StepPhase, completedStepRecords: CompletedStepRecord[]): StepPhase[];
|
|
9
8
|
type BuildHeaderOptions = {
|
|
10
|
-
steps:
|
|
9
|
+
steps: DisplayStep[];
|
|
11
10
|
version: string;
|
|
12
11
|
logoVisibility: LogoVisibility;
|
|
13
12
|
currentStepIndex: number;
|
package/dist/ui/screen.js
CHANGED
|
@@ -4,13 +4,14 @@ import { buildOutputBoxLines } from "./outputBox.js";
|
|
|
4
4
|
import { buildPromptLines } from "./prompt.js";
|
|
5
5
|
import { buildVersionReportLines } from "./versionReport.js";
|
|
6
6
|
export function createScreenRenderer(output = process.stdout) {
|
|
7
|
-
let
|
|
7
|
+
let hasAnchor = false;
|
|
8
8
|
return (content) => {
|
|
9
|
-
if (
|
|
10
|
-
output.write(
|
|
9
|
+
if (!hasAnchor) {
|
|
10
|
+
output.write("\x1b7");
|
|
11
|
+
hasAnchor = true;
|
|
11
12
|
}
|
|
13
|
+
output.write("\x1b8\x1b[J");
|
|
12
14
|
output.write(content);
|
|
13
|
-
previousLineCount = (content.match(/\n/g) ?? []).length;
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
export function buildScreen(state, terminalDimensions) {
|
package/dist/ui/state.d.ts
CHANGED
|
@@ -1,30 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import type { CommandOutputLine } from "../runner.js";
|
|
3
|
-
export type StepPhase = "pending" | "checking-availability" | "prompting-to-install" | "installing" | "prompting-to-run" | "running" | "complete" | "failed" | "skipped" | "not-available";
|
|
4
|
-
export type CompletedStepRecord = {
|
|
5
|
-
phase: StepPhase;
|
|
6
|
-
summaryNote?: string;
|
|
7
|
-
};
|
|
8
|
-
export type CurrentPrompt = {
|
|
9
|
-
question: string;
|
|
10
|
-
warning?: string;
|
|
11
|
-
};
|
|
12
|
-
export type ResolvedVersion = {
|
|
13
|
-
label: string;
|
|
14
|
-
value: string;
|
|
15
|
-
};
|
|
16
|
-
export type LogoVisibility = "visible" | "hidden";
|
|
17
|
-
export type RunCompletion = "in-progress" | "finished";
|
|
18
|
-
export type AppState = {
|
|
19
|
-
steps: Step[];
|
|
20
|
-
version: string;
|
|
21
|
-
logoVisibility: LogoVisibility;
|
|
22
|
-
currentStepIndex: number;
|
|
23
|
-
currentPhase: StepPhase;
|
|
24
|
-
completedStepRecords: CompletedStepRecord[];
|
|
25
|
-
currentOutputLines: CommandOutputLine[];
|
|
26
|
-
currentPrompt: CurrentPrompt | undefined;
|
|
27
|
-
runCompletion: RunCompletion;
|
|
28
|
-
versionReport: ResolvedVersion[] | undefined;
|
|
29
|
-
};
|
|
30
|
-
export declare function createInitialAppState(steps: Step[], version: string, logoVisibility: LogoVisibility): AppState;
|
|
1
|
+
export { type StepStage, 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/ui/state.js
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
return {
|
|
3
|
-
steps,
|
|
4
|
-
version,
|
|
5
|
-
logoVisibility,
|
|
6
|
-
currentStepIndex: 0,
|
|
7
|
-
currentPhase: "checking-availability",
|
|
8
|
-
completedStepRecords: [],
|
|
9
|
-
currentOutputLines: [],
|
|
10
|
-
currentPrompt: undefined,
|
|
11
|
-
runCompletion: "in-progress",
|
|
12
|
-
versionReport: undefined,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
1
|
+
export { createInitialUpdateState, createInitialAppState, } from "../update/state.js";
|
package/dist/ui/terminal.d.ts
CHANGED
package/dist/ui/terminal.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
export function hideCursorDuringExecution(options = {}) {
|
|
2
2
|
const output = options.output ?? process.stdout;
|
|
3
|
+
const screenBuffer = options.screenBuffer ?? "main";
|
|
4
|
+
if (screenBuffer === "alternate") {
|
|
5
|
+
output.write("\x1b[?1049h");
|
|
6
|
+
}
|
|
3
7
|
output.write("\x1b[?25l");
|
|
4
|
-
const
|
|
8
|
+
const restoreTerminal = () => {
|
|
9
|
+
output.write("\x1b[?25h");
|
|
10
|
+
if (screenBuffer === "alternate") {
|
|
11
|
+
output.write("\x1b[?1049l");
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const restoreOnExit = () => restoreTerminal();
|
|
5
15
|
const restoreAndExitOnInterrupt = () => {
|
|
6
|
-
|
|
16
|
+
restoreTerminal();
|
|
17
|
+
output.write("\n");
|
|
7
18
|
process.exit(130);
|
|
8
19
|
};
|
|
9
20
|
process.on("exit", restoreOnExit);
|
|
@@ -11,6 +22,6 @@ export function hideCursorDuringExecution(options = {}) {
|
|
|
11
22
|
return () => {
|
|
12
23
|
process.removeListener("exit", restoreOnExit);
|
|
13
24
|
process.removeListener("SIGINT", restoreAndExitOnInterrupt);
|
|
14
|
-
|
|
25
|
+
restoreTerminal();
|
|
15
26
|
};
|
|
16
27
|
}
|
package/dist/ui/versionReport.js
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { captureShellCommandOutput } from "../runner.js";
|
|
3
|
-
const VERSION_ENTRIES = [
|
|
4
|
-
{ label: "macOS", shellCommand: "sw_vers -productVersion" },
|
|
5
|
-
{ label: "Homebrew", shellCommand: "brew --version | head -n1" },
|
|
6
|
-
{ label: "Node", shellCommand: "node -v 2>/dev/null" },
|
|
7
|
-
{ label: "NPM", shellCommand: "npm -v 2>/dev/null" },
|
|
8
|
-
{ label: "Python", shellCommand: "python3 --version 2>/dev/null" },
|
|
9
|
-
{ label: "pip3", shellCommand: "pip3 --version 2>/dev/null | cut -d' ' -f1-2" },
|
|
10
|
-
{ label: "Zsh", shellCommand: "zsh --version 2>/dev/null" },
|
|
11
|
-
];
|
|
12
|
-
export async function collectVersions() {
|
|
13
|
-
return Promise.all(VERSION_ENTRIES.map(async ({ label, shellCommand }) => {
|
|
14
|
-
const value = await captureShellCommandOutput({ shellCommand }).catch(() => "");
|
|
15
|
-
return { label, value: value || "not installed" };
|
|
16
|
-
}));
|
|
17
|
-
}
|
|
18
2
|
export function buildVersionReportLines(versions) {
|
|
19
3
|
return [
|
|
20
4
|
chalk.dim(" ─── Versions ─────────────────────────"),
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type StepLogWriter } from "../logger.js";
|
|
2
|
+
import { runShellCommand } from "../runner.js";
|
|
3
|
+
import { createDefaultStepRunContext, type Step } from "../steps/index.js";
|
|
4
|
+
import { type LogoVisibility, type ResolvedVersion, type UpdateState } from "./state.js";
|
|
5
|
+
export type PromptMode = "interactive" | "auto-accept";
|
|
6
|
+
export type ConfirmationAnswer = "yes" | "no" | "force";
|
|
7
|
+
type UpdateEngineDependencies = {
|
|
8
|
+
createLogWriter: (stepId: string) => Promise<StepLogWriter>;
|
|
9
|
+
runCommand: typeof runShellCommand;
|
|
10
|
+
createStepRunContext: typeof createDefaultStepRunContext;
|
|
11
|
+
};
|
|
12
|
+
type RunUpdateEngineOptions = {
|
|
13
|
+
steps: Step[];
|
|
14
|
+
promptMode: PromptMode;
|
|
15
|
+
version: string;
|
|
16
|
+
logoVisibility: LogoVisibility;
|
|
17
|
+
askForConfirmation: () => Promise<ConfirmationAnswer>;
|
|
18
|
+
collectVersions: () => Promise<ResolvedVersion[]>;
|
|
19
|
+
onStateChanged: (state: UpdateState) => void;
|
|
20
|
+
dependencies?: Partial<UpdateEngineDependencies>;
|
|
21
|
+
};
|
|
22
|
+
export declare function runUpdateEngine(options: RunUpdateEngineOptions): Promise<UpdateState>;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { createLogWriterForStep } from "../logger.js";
|
|
2
|
+
import { runShellCommand } from "../runner.js";
|
|
3
|
+
import { createDefaultStepRunContext } from "../steps/index.js";
|
|
4
|
+
import { toDisplaySteps } from "./displayStep.js";
|
|
5
|
+
import { createInitialUpdateState, } from "./state.js";
|
|
6
|
+
function resolveDependencies(dependencyOverrides) {
|
|
7
|
+
return {
|
|
8
|
+
createLogWriter: dependencyOverrides?.createLogWriter ?? createLogWriterForStep,
|
|
9
|
+
runCommand: dependencyOverrides?.runCommand ?? runShellCommand,
|
|
10
|
+
createStepRunContext: dependencyOverrides?.createStepRunContext ?? createDefaultStepRunContext,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
async function resolvePrompt(promptMode, askForConfirmation) {
|
|
14
|
+
if (promptMode === "auto-accept") {
|
|
15
|
+
return { promptMode: "auto-accept", decision: "run" };
|
|
16
|
+
}
|
|
17
|
+
const answer = await askForConfirmation();
|
|
18
|
+
if (answer === "no") {
|
|
19
|
+
return { promptMode: "interactive", decision: "skip" };
|
|
20
|
+
}
|
|
21
|
+
if (answer === "force") {
|
|
22
|
+
return { promptMode: "auto-accept", decision: "run" };
|
|
23
|
+
}
|
|
24
|
+
return { promptMode: "interactive", decision: "run" };
|
|
25
|
+
}
|
|
26
|
+
function reportState(state, onStateChanged) {
|
|
27
|
+
onStateChanged(state);
|
|
28
|
+
}
|
|
29
|
+
export async function runUpdateEngine(options) {
|
|
30
|
+
const dependencies = resolveDependencies(options.dependencies);
|
|
31
|
+
const state = createInitialUpdateState(toDisplaySteps(options.steps), options.version, options.logoVisibility);
|
|
32
|
+
let promptMode = options.promptMode;
|
|
33
|
+
reportState(state, options.onStateChanged);
|
|
34
|
+
for (let stepIndex = 0; stepIndex < options.steps.length; stepIndex++) {
|
|
35
|
+
const step = options.steps[stepIndex];
|
|
36
|
+
state.currentStepIndex = stepIndex;
|
|
37
|
+
state.currentPhase = "checking-availability";
|
|
38
|
+
state.currentOutputLines = [];
|
|
39
|
+
state.currentPrompt = undefined;
|
|
40
|
+
reportState(state, options.onStateChanged);
|
|
41
|
+
const availabilityStatus = (await step.checkIsAvailable())
|
|
42
|
+
? "available"
|
|
43
|
+
: "unavailable";
|
|
44
|
+
if (availabilityStatus === "unavailable" && !step.brewPackageToInstall) {
|
|
45
|
+
state.currentPhase = "not-available";
|
|
46
|
+
reportState(state, options.onStateChanged);
|
|
47
|
+
state.completedStepRecords.push({ phase: "not-available", summaryNote: "not installed" });
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (availabilityStatus === "unavailable" && step.brewPackageToInstall) {
|
|
51
|
+
state.currentPhase = "prompting-to-install";
|
|
52
|
+
state.currentPrompt = {
|
|
53
|
+
question: `Install ${step.name} via Homebrew? (brew install ${step.brewPackageToInstall})`,
|
|
54
|
+
};
|
|
55
|
+
reportState(state, options.onStateChanged);
|
|
56
|
+
const installPrompt = await resolvePrompt(promptMode, options.askForConfirmation);
|
|
57
|
+
promptMode = installPrompt.promptMode;
|
|
58
|
+
state.currentPrompt = undefined;
|
|
59
|
+
if (installPrompt.decision === "skip") {
|
|
60
|
+
state.currentPhase = "skipped";
|
|
61
|
+
reportState(state, options.onStateChanged);
|
|
62
|
+
state.completedStepRecords.push({ phase: "skipped" });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
state.currentPhase = "installing";
|
|
66
|
+
reportState(state, options.onStateChanged);
|
|
67
|
+
try {
|
|
68
|
+
const installLogWriter = await dependencies.createLogWriter(`${step.id}-install`);
|
|
69
|
+
await dependencies.runCommand({
|
|
70
|
+
shellCommand: `brew install ${step.brewPackageToInstall}`,
|
|
71
|
+
onOutputLine: (line) => {
|
|
72
|
+
state.currentOutputLines.push(line);
|
|
73
|
+
reportState(state, options.onStateChanged);
|
|
74
|
+
},
|
|
75
|
+
logWriter: installLogWriter,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
state.currentPhase = "failed";
|
|
80
|
+
reportState(state, options.onStateChanged);
|
|
81
|
+
state.completedStepRecords.push({ phase: "failed", summaryNote: "install failed" });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
state.currentPhase = "prompting-to-run";
|
|
86
|
+
state.currentPrompt = { question: `Run ${step.name} update?`, warning: step.warning };
|
|
87
|
+
reportState(state, options.onStateChanged);
|
|
88
|
+
const runPrompt = await resolvePrompt(promptMode, options.askForConfirmation);
|
|
89
|
+
promptMode = runPrompt.promptMode;
|
|
90
|
+
state.currentPrompt = undefined;
|
|
91
|
+
if (runPrompt.decision === "skip") {
|
|
92
|
+
state.currentPhase = "skipped";
|
|
93
|
+
reportState(state, options.onStateChanged);
|
|
94
|
+
state.completedStepRecords.push({ phase: "skipped" });
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
state.currentPhase = "running";
|
|
98
|
+
state.currentOutputLines = [];
|
|
99
|
+
reportState(state, options.onStateChanged);
|
|
100
|
+
const stepLogWriter = await dependencies.createLogWriter(step.id);
|
|
101
|
+
try {
|
|
102
|
+
await step.run(dependencies.createStepRunContext((line) => {
|
|
103
|
+
state.currentOutputLines.push(line);
|
|
104
|
+
reportState(state, options.onStateChanged);
|
|
105
|
+
}, stepLogWriter));
|
|
106
|
+
state.currentPhase = "complete";
|
|
107
|
+
state.currentOutputLines = [];
|
|
108
|
+
reportState(state, options.onStateChanged);
|
|
109
|
+
state.completedStepRecords.push({ phase: "complete", summaryNote: "updated" });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
113
|
+
state.currentPhase = "failed";
|
|
114
|
+
state.currentOutputLines = [];
|
|
115
|
+
reportState(state, options.onStateChanged);
|
|
116
|
+
state.completedStepRecords.push({ phase: "failed", summaryNote: errorMessage });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
state.runCompletion = "finished";
|
|
120
|
+
state.currentOutputLines = [];
|
|
121
|
+
state.currentPrompt = undefined;
|
|
122
|
+
reportState(state, options.onStateChanged);
|
|
123
|
+
state.versionReport = await options.collectVersions();
|
|
124
|
+
reportState(state, options.onStateChanged);
|
|
125
|
+
return state;
|
|
126
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { CommandOutputLine } from "../runner.js";
|
|
2
|
+
export type StepStage = "runtime" | "tools";
|
|
3
|
+
export type DisplayStep = {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
stage: StepStage;
|
|
8
|
+
};
|
|
9
|
+
export type StepPhase = "pending" | "checking-availability" | "prompting-to-install" | "installing" | "prompting-to-run" | "running" | "complete" | "failed" | "skipped" | "not-available";
|
|
10
|
+
export type CompletedStepRecord = {
|
|
11
|
+
phase: StepPhase;
|
|
12
|
+
summaryNote?: string;
|
|
13
|
+
};
|
|
14
|
+
export type CurrentPrompt = {
|
|
15
|
+
question: string;
|
|
16
|
+
warning?: string;
|
|
17
|
+
};
|
|
18
|
+
export type ResolvedVersion = {
|
|
19
|
+
label: string;
|
|
20
|
+
value: string;
|
|
21
|
+
};
|
|
22
|
+
export type LogoVisibility = "visible" | "hidden";
|
|
23
|
+
export type RunCompletion = "in-progress" | "finished";
|
|
24
|
+
export type UpdateState = {
|
|
25
|
+
steps: DisplayStep[];
|
|
26
|
+
version: string;
|
|
27
|
+
logoVisibility: LogoVisibility;
|
|
28
|
+
currentStepIndex: number;
|
|
29
|
+
currentPhase: StepPhase;
|
|
30
|
+
completedStepRecords: CompletedStepRecord[];
|
|
31
|
+
currentOutputLines: CommandOutputLine[];
|
|
32
|
+
currentPrompt: CurrentPrompt | undefined;
|
|
33
|
+
runCompletion: RunCompletion;
|
|
34
|
+
versionReport: ResolvedVersion[] | undefined;
|
|
35
|
+
};
|
|
36
|
+
export type AppState = UpdateState;
|
|
37
|
+
export declare function createInitialUpdateState(steps: DisplayStep[], version: string, logoVisibility: LogoVisibility): UpdateState;
|
|
38
|
+
export declare const createInitialAppState: typeof createInitialUpdateState;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function createInitialUpdateState(steps, version, logoVisibility) {
|
|
2
|
+
return {
|
|
3
|
+
steps,
|
|
4
|
+
version,
|
|
5
|
+
logoVisibility,
|
|
6
|
+
currentStepIndex: 0,
|
|
7
|
+
currentPhase: "checking-availability",
|
|
8
|
+
completedStepRecords: [],
|
|
9
|
+
currentOutputLines: [],
|
|
10
|
+
currentPrompt: undefined,
|
|
11
|
+
runCompletion: "in-progress",
|
|
12
|
+
versionReport: undefined,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export const createInitialAppState = createInitialUpdateState;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { captureShellCommandOutput } from "../runner.js";
|
|
2
|
+
const VERSION_ENTRIES = [
|
|
3
|
+
{ label: "macOS", shellCommand: "sw_vers -productVersion" },
|
|
4
|
+
{ label: "Homebrew", shellCommand: "brew --version | head -n1" },
|
|
5
|
+
{ label: "Node", shellCommand: "node -v 2>/dev/null" },
|
|
6
|
+
{ label: "NPM", shellCommand: "npm -v 2>/dev/null" },
|
|
7
|
+
{ label: "Python", shellCommand: "python3 --version 2>/dev/null" },
|
|
8
|
+
{ label: "pip3", shellCommand: "pip3 --version 2>/dev/null | cut -d' ' -f1-2" },
|
|
9
|
+
{ label: "Zsh", shellCommand: "zsh --version 2>/dev/null" },
|
|
10
|
+
];
|
|
11
|
+
export async function collectVersions() {
|
|
12
|
+
return Promise.all(VERSION_ENTRIES.map(async ({ label, shellCommand }) => {
|
|
13
|
+
const value = await captureShellCommandOutput({ shellCommand }).catch(() => "");
|
|
14
|
+
return { label, value: value || "not installed" };
|
|
15
|
+
}));
|
|
16
|
+
}
|