@gaosh3n/pi-package-manager 0.1.0 → 0.1.1
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/index.ts +67 -653
- package/internal/controller.ts +223 -0
- package/internal/model.ts +41 -0
- package/internal/records.ts +81 -0
- package/internal/reports.ts +351 -0
- package/internal/runtime.ts +54 -0
- package/package.json +2 -1
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ExtensionAPI,
|
|
3
|
+
type ExtensionCommandContext,
|
|
4
|
+
type ExtensionContext,
|
|
5
|
+
type SessionStartEvent,
|
|
6
|
+
} from "@earendil-works/pi-coding-agent"
|
|
7
|
+
|
|
8
|
+
import { RELOAD_COUNTDOWN_SECONDS, REPORT_ENTRY_TYPE } from "./model.ts"
|
|
9
|
+
import {
|
|
10
|
+
appendAutoUpdateRecordAndReport,
|
|
11
|
+
createAutoUpdateRecord,
|
|
12
|
+
getLastAutoUpdateRecord,
|
|
13
|
+
} from "./records.ts"
|
|
14
|
+
import {
|
|
15
|
+
clearPackageManagerWidget,
|
|
16
|
+
createAutoUpdateResultReport,
|
|
17
|
+
createStatusErrorReport,
|
|
18
|
+
createStatusReport,
|
|
19
|
+
getExecDisplayOutput,
|
|
20
|
+
getExecFailureDetail,
|
|
21
|
+
setPackageManagerWidget,
|
|
22
|
+
} from "./reports.ts"
|
|
23
|
+
import { defaultPackageManagerDeps, type PackageManagerDeps } from "./runtime.ts"
|
|
24
|
+
|
|
25
|
+
export function createPackageManagerController(
|
|
26
|
+
pi: Pick<ExtensionAPI, "appendEntry" | "sendUserMessage" | "exec">,
|
|
27
|
+
deps: PackageManagerDeps = defaultPackageManagerDeps,
|
|
28
|
+
) {
|
|
29
|
+
return {
|
|
30
|
+
onSessionStart,
|
|
31
|
+
handleStatus,
|
|
32
|
+
handleUpdate,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function onSessionStart(
|
|
36
|
+
event: Pick<SessionStartEvent, "reason">,
|
|
37
|
+
_ctx: ExtensionContext,
|
|
38
|
+
): Promise<void> {
|
|
39
|
+
if (!shouldAutoUpdateOnSessionStart(event)) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pi.sendUserMessage("/package-manager update --startup", {
|
|
44
|
+
expandPromptTemplates: true,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
|
|
49
|
+
const lastAutoUpdate = getLastAutoUpdateRecord(ctx.sessionManager.getEntries())
|
|
50
|
+
|
|
51
|
+
setPackageManagerWidget(ctx, { mode: "status-checking" })
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const availableUpdates = await deps.checkForAvailableUpdates(ctx)
|
|
55
|
+
pi.appendEntry(
|
|
56
|
+
REPORT_ENTRY_TYPE,
|
|
57
|
+
createStatusReport({
|
|
58
|
+
availableUpdates,
|
|
59
|
+
lastAutoUpdate,
|
|
60
|
+
}),
|
|
61
|
+
)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
pi.appendEntry(
|
|
64
|
+
REPORT_ENTRY_TYPE,
|
|
65
|
+
createStatusErrorReport(
|
|
66
|
+
{
|
|
67
|
+
availableUpdates: [],
|
|
68
|
+
lastAutoUpdate,
|
|
69
|
+
},
|
|
70
|
+
getErrorMessage(error),
|
|
71
|
+
),
|
|
72
|
+
)
|
|
73
|
+
} finally {
|
|
74
|
+
clearPackageManagerWidget(ctx)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function handleUpdate(
|
|
79
|
+
ctx: ExtensionCommandContext,
|
|
80
|
+
options: { startupTriggered: boolean },
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
const startedAtUtc = deps.nowIso()
|
|
83
|
+
let shouldClearWidget = true
|
|
84
|
+
|
|
85
|
+
setPackageManagerWidget(ctx, { mode: "checking" })
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
if (deps.isOffline()) {
|
|
89
|
+
appendSkippedResult(startedAtUtc, "PI_OFFLINE is set.")
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const availableUpdates = await deps.checkForAvailableUpdates(ctx)
|
|
94
|
+
|
|
95
|
+
if (availableUpdates.length === 0) {
|
|
96
|
+
appendSkippedResult(startedAtUtc, "No package updates are available.")
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setPackageManagerWidget(ctx, {
|
|
101
|
+
mode: "installing",
|
|
102
|
+
packages: availableUpdates.length,
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const result = await deps.runNativeUpdate(pi, ctx)
|
|
106
|
+
const output = getExecDisplayOutput(result)
|
|
107
|
+
|
|
108
|
+
if (result.code === 0) {
|
|
109
|
+
const record = createAutoUpdateRecord({
|
|
110
|
+
startedAtUtc,
|
|
111
|
+
endedAtUtc: deps.nowIso(),
|
|
112
|
+
outcome: "succeeded",
|
|
113
|
+
packagesUpdated: availableUpdates.length,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
appendAutoUpdateRecordAndReport(
|
|
117
|
+
pi,
|
|
118
|
+
record,
|
|
119
|
+
createAutoUpdateResultReport({
|
|
120
|
+
record,
|
|
121
|
+
output,
|
|
122
|
+
reloadAfterSeconds: RELOAD_COUNTDOWN_SECONDS,
|
|
123
|
+
}),
|
|
124
|
+
)
|
|
125
|
+
await runReloadCountdown(ctx)
|
|
126
|
+
clearPackageManagerWidget(ctx)
|
|
127
|
+
shouldClearWidget = false
|
|
128
|
+
await ctx.reload()
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const record = createAutoUpdateRecord({
|
|
133
|
+
startedAtUtc,
|
|
134
|
+
endedAtUtc: deps.nowIso(),
|
|
135
|
+
outcome: "failed",
|
|
136
|
+
packagesUpdated: 0,
|
|
137
|
+
reason: getExecFailureDetail(result),
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
appendAutoUpdateRecordAndReport(
|
|
141
|
+
pi,
|
|
142
|
+
record,
|
|
143
|
+
createAutoUpdateResultReport({ record, output }),
|
|
144
|
+
)
|
|
145
|
+
notifyStartupFailure(ctx, options.startupTriggered)
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const record = createAutoUpdateRecord({
|
|
148
|
+
startedAtUtc,
|
|
149
|
+
endedAtUtc: deps.nowIso(),
|
|
150
|
+
outcome: "failed",
|
|
151
|
+
packagesUpdated: 0,
|
|
152
|
+
reason: getErrorMessage(error),
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
appendAutoUpdateRecordAndReport(
|
|
156
|
+
pi,
|
|
157
|
+
record,
|
|
158
|
+
createAutoUpdateResultReport({ record }),
|
|
159
|
+
)
|
|
160
|
+
notifyStartupFailure(ctx, options.startupTriggered)
|
|
161
|
+
} finally {
|
|
162
|
+
if (shouldClearWidget) {
|
|
163
|
+
clearPackageManagerWidget(ctx)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function appendSkippedResult(startedAtUtc: string, reason: string): void {
|
|
169
|
+
const record = createAutoUpdateRecord({
|
|
170
|
+
startedAtUtc,
|
|
171
|
+
endedAtUtc: deps.nowIso(),
|
|
172
|
+
outcome: "skipped",
|
|
173
|
+
packagesUpdated: 0,
|
|
174
|
+
reason,
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
appendAutoUpdateRecordAndReport(
|
|
178
|
+
pi,
|
|
179
|
+
record,
|
|
180
|
+
createAutoUpdateResultReport({ record }),
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function runReloadCountdown(
|
|
185
|
+
ctx: ExtensionContext,
|
|
186
|
+
seconds = RELOAD_COUNTDOWN_SECONDS,
|
|
187
|
+
): Promise<void> {
|
|
188
|
+
for (let remaining = seconds; remaining >= 1; remaining--) {
|
|
189
|
+
setPackageManagerWidget(ctx, {
|
|
190
|
+
mode: "countdown",
|
|
191
|
+
secondsRemaining: remaining,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
await deps.sleep(1000)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function shouldAutoUpdateOnSessionStart(
|
|
200
|
+
event: Pick<SessionStartEvent, "reason">,
|
|
201
|
+
): boolean {
|
|
202
|
+
return event.reason === "startup"
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function notifyStartupFailure(
|
|
206
|
+
ctx: ExtensionCommandContext,
|
|
207
|
+
startupTriggered: boolean,
|
|
208
|
+
): void {
|
|
209
|
+
if (startupTriggered && ctx.hasUI) {
|
|
210
|
+
ctx.ui.notify(
|
|
211
|
+
"Pi Package Manager automatic startup update failed. See transcript for details.",
|
|
212
|
+
"error",
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getErrorMessage(error: unknown): string {
|
|
218
|
+
if (error instanceof Error) {
|
|
219
|
+
return error.message
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return String(error)
|
|
223
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export const AUTO_UPDATE_RECORD_ENTRY_TYPE = "package-manager-auto-update-record"
|
|
2
|
+
export const REPORT_ENTRY_TYPE = "package-manager-report"
|
|
3
|
+
export const PACKAGE_MANAGER_TITLE = "Pi Package Manager"
|
|
4
|
+
export const PACKAGE_MANAGER_WIDGET_KEY = "pi-package-manager"
|
|
5
|
+
export const RELOAD_COUNTDOWN_SECONDS = 5
|
|
6
|
+
export const OUTPUT_PREVIEW_LINE_COUNT = 8
|
|
7
|
+
export const UPDATE_COMMAND = ["update", "--extensions"] as const
|
|
8
|
+
|
|
9
|
+
export type AutoUpdateOutcome = "succeeded" | "failed" | "skipped"
|
|
10
|
+
export type ReportTone = "info" | "success" | "warning" | "error"
|
|
11
|
+
|
|
12
|
+
export type WidgetState =
|
|
13
|
+
| { mode: "status-checking" }
|
|
14
|
+
| { mode: "checking" }
|
|
15
|
+
| { mode: "installing"; packages: number }
|
|
16
|
+
| { mode: "countdown"; secondsRemaining: number }
|
|
17
|
+
|
|
18
|
+
export interface AutoUpdateRecord {
|
|
19
|
+
startedAtUtc: string
|
|
20
|
+
endedAtUtc: string
|
|
21
|
+
outcome: AutoUpdateOutcome
|
|
22
|
+
packagesUpdated: number
|
|
23
|
+
reason?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PackageManagerReport {
|
|
27
|
+
title: string
|
|
28
|
+
headline?: string
|
|
29
|
+
tone: ReportTone
|
|
30
|
+
lines: string[]
|
|
31
|
+
lineTone?: "default" | "dim"
|
|
32
|
+
output?: string
|
|
33
|
+
outputLabel?: string
|
|
34
|
+
outputTone?: "default" | "dim"
|
|
35
|
+
hideOutputWhenCollapsed?: boolean
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface PackageStatusSnapshot {
|
|
39
|
+
availableUpdates: string[]
|
|
40
|
+
lastAutoUpdate?: AutoUpdateRecord
|
|
41
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CustomEntry,
|
|
3
|
+
type ExtensionAPI,
|
|
4
|
+
type SessionEntry,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent"
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
AUTO_UPDATE_RECORD_ENTRY_TYPE,
|
|
9
|
+
REPORT_ENTRY_TYPE,
|
|
10
|
+
type AutoUpdateOutcome,
|
|
11
|
+
type AutoUpdateRecord,
|
|
12
|
+
type PackageManagerReport,
|
|
13
|
+
} from "./model.ts"
|
|
14
|
+
|
|
15
|
+
export function createAutoUpdateRecord(input: {
|
|
16
|
+
startedAtUtc: string
|
|
17
|
+
endedAtUtc: string
|
|
18
|
+
outcome: AutoUpdateOutcome
|
|
19
|
+
packagesUpdated: number
|
|
20
|
+
reason?: string
|
|
21
|
+
}): AutoUpdateRecord {
|
|
22
|
+
return {
|
|
23
|
+
startedAtUtc: input.startedAtUtc,
|
|
24
|
+
endedAtUtc: input.endedAtUtc,
|
|
25
|
+
outcome: input.outcome,
|
|
26
|
+
packagesUpdated: input.packagesUpdated,
|
|
27
|
+
reason: input.reason,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function appendAutoUpdateRecordAndReport(
|
|
32
|
+
pi: Pick<ExtensionAPI, "appendEntry">,
|
|
33
|
+
record: AutoUpdateRecord,
|
|
34
|
+
report: PackageManagerReport,
|
|
35
|
+
): void {
|
|
36
|
+
pi.appendEntry(AUTO_UPDATE_RECORD_ENTRY_TYPE, record)
|
|
37
|
+
pi.appendEntry(REPORT_ENTRY_TYPE, report)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getLastAutoUpdateRecord(
|
|
41
|
+
entries: readonly SessionEntry[],
|
|
42
|
+
): AutoUpdateRecord | undefined {
|
|
43
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
44
|
+
const entry = entries[index]
|
|
45
|
+
|
|
46
|
+
if (!isAutoUpdateRecordEntry(entry)) {
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return entry.data
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isAutoUpdateRecordEntry(
|
|
57
|
+
entry: SessionEntry,
|
|
58
|
+
): entry is CustomEntry<AutoUpdateRecord> {
|
|
59
|
+
return (
|
|
60
|
+
entry.type === "custom" &&
|
|
61
|
+
entry.customType === AUTO_UPDATE_RECORD_ENTRY_TYPE &&
|
|
62
|
+
isAutoUpdateRecord(entry.data)
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function isAutoUpdateRecord(value: unknown): value is AutoUpdateRecord {
|
|
67
|
+
if (!value || typeof value !== "object") {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const candidate = value as Partial<AutoUpdateRecord>
|
|
72
|
+
return (
|
|
73
|
+
typeof candidate.startedAtUtc === "string" &&
|
|
74
|
+
typeof candidate.endedAtUtc === "string" &&
|
|
75
|
+
(candidate.outcome === "succeeded" ||
|
|
76
|
+
candidate.outcome === "failed" ||
|
|
77
|
+
candidate.outcome === "skipped") &&
|
|
78
|
+
typeof candidate.packagesUpdated === "number" &&
|
|
79
|
+
(candidate.reason === undefined || typeof candidate.reason === "string")
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import {
|
|
2
|
+
keyText,
|
|
3
|
+
type ExtensionContext,
|
|
4
|
+
type ExecResult,
|
|
5
|
+
} from "@earendil-works/pi-coding-agent"
|
|
6
|
+
import { Box, Text } from "@earendil-works/pi-tui"
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
OUTPUT_PREVIEW_LINE_COUNT,
|
|
10
|
+
PACKAGE_MANAGER_TITLE,
|
|
11
|
+
PACKAGE_MANAGER_WIDGET_KEY,
|
|
12
|
+
type AutoUpdateOutcome,
|
|
13
|
+
type AutoUpdateRecord,
|
|
14
|
+
type PackageManagerReport,
|
|
15
|
+
type PackageStatusSnapshot,
|
|
16
|
+
type WidgetState,
|
|
17
|
+
} from "./model.ts"
|
|
18
|
+
|
|
19
|
+
interface ThemeLike {
|
|
20
|
+
fg(token: string, text: string): string
|
|
21
|
+
bg(token: string, text: string): string
|
|
22
|
+
bold(text: string): string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function createReportEntryRenderer() {
|
|
26
|
+
return (
|
|
27
|
+
entry: { data?: PackageManagerReport },
|
|
28
|
+
{ expanded }: { expanded: boolean },
|
|
29
|
+
theme: ThemeLike,
|
|
30
|
+
) => {
|
|
31
|
+
const report = entry.data
|
|
32
|
+
|
|
33
|
+
if (!report) {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const toneColor =
|
|
38
|
+
report.tone === "error"
|
|
39
|
+
? "error"
|
|
40
|
+
: report.tone === "warning"
|
|
41
|
+
? "warning"
|
|
42
|
+
: report.tone === "success"
|
|
43
|
+
? "success"
|
|
44
|
+
: "accent"
|
|
45
|
+
const lineTextTone = report.lineTone === "dim" ? "dim" : "customMessageText"
|
|
46
|
+
const outputTextTone = report.outputTone === "dim" ? "dim" : lineTextTone
|
|
47
|
+
|
|
48
|
+
const box = new Box(1, 1, (text: string) => theme.bg("customMessageBg", text))
|
|
49
|
+
|
|
50
|
+
box.addChild(
|
|
51
|
+
new Text(
|
|
52
|
+
`${theme.fg(toneColor, "●")} ${theme.bold(theme.fg("customMessageLabel", report.title))}`,
|
|
53
|
+
0,
|
|
54
|
+
0,
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if (report.headline) {
|
|
59
|
+
box.addChild(new Text(theme.fg("text", report.headline), 0, 0))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (report.lines.length > 0) {
|
|
63
|
+
box.addChild(
|
|
64
|
+
new Text(
|
|
65
|
+
report.lines.map((line) => theme.fg(lineTextTone, line)).join("\n"),
|
|
66
|
+
0,
|
|
67
|
+
0,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (report.output?.trim()) {
|
|
73
|
+
if (!expanded && report.hideOutputWhenCollapsed) {
|
|
74
|
+
box.addChild(
|
|
75
|
+
new Text(
|
|
76
|
+
formatExpandHint(theme, "to expand to see update output."),
|
|
77
|
+
0,
|
|
78
|
+
0,
|
|
79
|
+
),
|
|
80
|
+
)
|
|
81
|
+
} else {
|
|
82
|
+
const { text, truncated } = formatReportOutput(report.output, expanded)
|
|
83
|
+
|
|
84
|
+
box.addChild(
|
|
85
|
+
new Text(
|
|
86
|
+
theme.fg(
|
|
87
|
+
outputTextTone,
|
|
88
|
+
report.outputLabel ?? "Update output:",
|
|
89
|
+
),
|
|
90
|
+
0,
|
|
91
|
+
0,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
box.addChild(new Text(theme.fg(outputTextTone, text), 0, 0))
|
|
95
|
+
|
|
96
|
+
if (truncated) {
|
|
97
|
+
box.addChild(
|
|
98
|
+
new Text(
|
|
99
|
+
formatExpandHint(
|
|
100
|
+
theme,
|
|
101
|
+
"to expand to view the full update output.",
|
|
102
|
+
),
|
|
103
|
+
0,
|
|
104
|
+
0,
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return box
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function formatUtcTimestamp(isoUtc: string): string {
|
|
116
|
+
return isoUtc.replace("T", " ").replace(/\.\d{3}Z$/, " UTC+00")
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function formatStatusLines(snapshot: PackageStatusSnapshot): string[] {
|
|
120
|
+
if (!snapshot.lastAutoUpdate) {
|
|
121
|
+
return ["Latest package update: none recorded."]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const lines = [
|
|
125
|
+
`Latest update start: ${formatUtcTimestamp(snapshot.lastAutoUpdate.startedAtUtc)}`,
|
|
126
|
+
`Latest update end: ${formatUtcTimestamp(snapshot.lastAutoUpdate.endedAtUtc)}`,
|
|
127
|
+
`Latest update result: ${snapshot.lastAutoUpdate.outcome}`,
|
|
128
|
+
`Latest update packages updated: ${snapshot.lastAutoUpdate.packagesUpdated}`,
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
if (snapshot.lastAutoUpdate.reason) {
|
|
132
|
+
lines.push(`Latest update detail: ${snapshot.lastAutoUpdate.reason}`)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return lines
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function createStatusReport(
|
|
139
|
+
snapshot: PackageStatusSnapshot,
|
|
140
|
+
): PackageManagerReport {
|
|
141
|
+
return {
|
|
142
|
+
title: PACKAGE_MANAGER_TITLE,
|
|
143
|
+
headline:
|
|
144
|
+
snapshot.availableUpdates.length === 0
|
|
145
|
+
? "No package updates are available."
|
|
146
|
+
: `${snapshot.availableUpdates.length} package update${snapshot.availableUpdates.length === 1 ? " is" : "s are"} available.`,
|
|
147
|
+
tone: snapshot.availableUpdates.length > 0 ? "warning" : "info",
|
|
148
|
+
lines: formatStatusLines(snapshot),
|
|
149
|
+
lineTone: "dim",
|
|
150
|
+
output:
|
|
151
|
+
snapshot.availableUpdates.length > 0
|
|
152
|
+
? snapshot.availableUpdates.map((name) => `- ${name}`).join("\n")
|
|
153
|
+
: undefined,
|
|
154
|
+
outputLabel:
|
|
155
|
+
snapshot.availableUpdates.length > 0 ? "Available updates:" : undefined,
|
|
156
|
+
outputTone: "dim",
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function createStatusErrorReport(
|
|
161
|
+
snapshot: PackageStatusSnapshot,
|
|
162
|
+
errorMessage: string,
|
|
163
|
+
): PackageManagerReport {
|
|
164
|
+
return {
|
|
165
|
+
title: PACKAGE_MANAGER_TITLE,
|
|
166
|
+
headline: "Package status check failed.",
|
|
167
|
+
tone: "error",
|
|
168
|
+
lines: formatStatusLines(snapshot),
|
|
169
|
+
lineTone: "dim",
|
|
170
|
+
output: `Failed to check package updates: ${errorMessage}`,
|
|
171
|
+
outputLabel: "Error detail:",
|
|
172
|
+
outputTone: "dim",
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function createAutoUpdateResultReport(input: {
|
|
177
|
+
record: AutoUpdateRecord
|
|
178
|
+
output?: string
|
|
179
|
+
reloadAfterSeconds?: number
|
|
180
|
+
}): PackageManagerReport {
|
|
181
|
+
const lines = [
|
|
182
|
+
`Start: ${formatUtcTimestamp(input.record.startedAtUtc)}`,
|
|
183
|
+
`End: ${formatUtcTimestamp(input.record.endedAtUtc)}`,
|
|
184
|
+
`Result: ${input.record.outcome}`,
|
|
185
|
+
`Packages updated: ${input.record.packagesUpdated}`,
|
|
186
|
+
]
|
|
187
|
+
|
|
188
|
+
if (input.reloadAfterSeconds) {
|
|
189
|
+
lines.push(
|
|
190
|
+
`Reloading in ${input.reloadAfterSeconds} seconds to activate updated package resources.`,
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
title: PACKAGE_MANAGER_TITLE,
|
|
196
|
+
headline: formatAutomaticUpdateHeadline(input.record.outcome),
|
|
197
|
+
tone:
|
|
198
|
+
input.record.outcome === "failed"
|
|
199
|
+
? "error"
|
|
200
|
+
: input.record.outcome === "succeeded"
|
|
201
|
+
? "success"
|
|
202
|
+
: "info",
|
|
203
|
+
lines,
|
|
204
|
+
lineTone: "dim",
|
|
205
|
+
output: input.output ?? input.record.reason,
|
|
206
|
+
outputTone: "dim",
|
|
207
|
+
hideOutputWhenCollapsed: true,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function createAutomaticUpdateWidgetLines(state: WidgetState): string[] {
|
|
212
|
+
if (state.mode === "status-checking") {
|
|
213
|
+
return ["Pi package status in progress.", "Checking for package updates..."]
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (state.mode === "checking") {
|
|
217
|
+
return [
|
|
218
|
+
formatAutomaticUpdateHeadline("in-progress"),
|
|
219
|
+
"Checking for package updates...",
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (state.mode === "installing") {
|
|
224
|
+
return [
|
|
225
|
+
formatAutomaticUpdateHeadline("in-progress"),
|
|
226
|
+
`Installing ${state.packages} package update${state.packages === 1 ? "" : "s"}...`,
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return [
|
|
231
|
+
formatAutomaticUpdateHeadline("succeeded"),
|
|
232
|
+
`Reloading in ${state.secondsRemaining} second${state.secondsRemaining === 1 ? "" : "s"} to activate updated package resources.`,
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function setPackageManagerWidget(
|
|
237
|
+
ctx: ExtensionContext,
|
|
238
|
+
state: WidgetState,
|
|
239
|
+
): void {
|
|
240
|
+
if (ctx.mode !== "tui") {
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
ctx.ui.setWidget(
|
|
245
|
+
PACKAGE_MANAGER_WIDGET_KEY,
|
|
246
|
+
(_tui, theme) => {
|
|
247
|
+
const background =
|
|
248
|
+
state.mode === "countdown"
|
|
249
|
+
? (text: string) => theme.bg("toolSuccessBg", text)
|
|
250
|
+
: (text: string) => theme.bg("toolPendingBg", text)
|
|
251
|
+
const box = new Box(1, 1, background)
|
|
252
|
+
const titleColor = state.mode === "countdown" ? "success" : "accent"
|
|
253
|
+
const bodyLines = createAutomaticUpdateWidgetLines(state)
|
|
254
|
+
|
|
255
|
+
box.addChild(
|
|
256
|
+
new Text(
|
|
257
|
+
`${theme.fg(titleColor, "●")} ${theme.bold(theme.fg("customMessageLabel", PACKAGE_MANAGER_TITLE))}`,
|
|
258
|
+
0,
|
|
259
|
+
0,
|
|
260
|
+
),
|
|
261
|
+
)
|
|
262
|
+
box.addChild(
|
|
263
|
+
new Text(
|
|
264
|
+
bodyLines
|
|
265
|
+
.map((line, index) =>
|
|
266
|
+
theme.fg(
|
|
267
|
+
index === bodyLines.length - 1 ? "dim" : "text",
|
|
268
|
+
line,
|
|
269
|
+
),
|
|
270
|
+
)
|
|
271
|
+
.join("\n"),
|
|
272
|
+
0,
|
|
273
|
+
0,
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return box
|
|
278
|
+
},
|
|
279
|
+
{ placement: "aboveEditor" },
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export function clearPackageManagerWidget(ctx: ExtensionContext): void {
|
|
284
|
+
if (ctx.mode !== "tui") {
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
ctx.ui.setWidget(PACKAGE_MANAGER_WIDGET_KEY, undefined)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function getExecDisplayOutput(result: ExecResult): string | undefined {
|
|
292
|
+
const stdout = result.stdout.trim()
|
|
293
|
+
const stderr = result.stderr.trim()
|
|
294
|
+
const sections: string[] = []
|
|
295
|
+
|
|
296
|
+
if (stdout) {
|
|
297
|
+
sections.push(stdout)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (stderr) {
|
|
301
|
+
sections.push(`[stderr]\n${stderr}`)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return sections.length > 0 ? sections.join("\n\n") : undefined
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function getExecFailureDetail(result: ExecResult): string {
|
|
308
|
+
const stderr = result.stderr.trim()
|
|
309
|
+
const stdout = result.stdout.trim()
|
|
310
|
+
const detail = stderr || stdout || "Package update command failed."
|
|
311
|
+
|
|
312
|
+
return detail.length > 400 ? `${detail.slice(0, 397)}...` : detail
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function formatAutomaticUpdateHeadline(
|
|
316
|
+
state: AutoUpdateOutcome | "in-progress",
|
|
317
|
+
): string {
|
|
318
|
+
const suffix =
|
|
319
|
+
state === "in-progress"
|
|
320
|
+
? "in progress"
|
|
321
|
+
: state === "succeeded"
|
|
322
|
+
? "completed"
|
|
323
|
+
: state
|
|
324
|
+
|
|
325
|
+
return `Pi package(s) update ${suffix}.`
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function formatExpandHint(theme: ThemeLike, description: string): string {
|
|
329
|
+
const expandKey = keyText("app.tools.expand") || "Ctrl+O"
|
|
330
|
+
|
|
331
|
+
return `${theme.fg("dim", expandKey)}${theme.fg("muted", ` ${description}`)}`
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function formatReportOutput(
|
|
335
|
+
output: string,
|
|
336
|
+
expanded: boolean,
|
|
337
|
+
): { text: string; truncated: boolean } {
|
|
338
|
+
const normalizedLines = output.trim().split(/\r?\n/)
|
|
339
|
+
|
|
340
|
+
if (expanded || normalizedLines.length <= OUTPUT_PREVIEW_LINE_COUNT) {
|
|
341
|
+
return {
|
|
342
|
+
text: normalizedLines.join("\n"),
|
|
343
|
+
truncated: false,
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
text: normalizedLines.slice(0, OUTPUT_PREVIEW_LINE_COUNT).join("\n"),
|
|
349
|
+
truncated: true,
|
|
350
|
+
}
|
|
351
|
+
}
|