opencode-navigator 0.27.3 → 0.27.5
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/CHANGELOG.md +16 -0
- package/HOST_API.md +17 -0
- package/PROVIDERS.md +4 -0
- package/README.md +72 -5
- package/dist/{chunk-fd7394y1.js → chunk-4rh7rb74.js} +15 -1
- package/dist/chunk-sm9ej5ac.js +6 -0
- package/dist/chunk-tq2z58v5.js +23 -0
- package/dist/chunk-v6qckzat.js +838 -0
- package/dist/gui-experimental.cjs +9567 -8828
- package/dist/native-todo-setup.js +5 -830
- package/dist/navigator-updates.js +671 -321
- package/dist/provider-limits.js +491 -239
- package/dist/sidebar-settings.js +14 -17
- package/dist/tui.js +172 -132
- package/dist/upgrade-notes.js +4 -2
- package/package.json +5 -1
- package/dist/chunk-rrvwmnyh.js +0 -22
|
@@ -1,339 +1,689 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
PLUGIN_ID
|
|
4
|
+
} from "./chunk-dm1y8x49.js";
|
|
5
|
+
import {
|
|
6
|
+
parseTree,
|
|
7
|
+
findNodeAtLocation,
|
|
8
|
+
getNodeValue
|
|
9
|
+
} from "./chunk-v6qckzat.js";
|
|
10
|
+
import {
|
|
11
|
+
readBundledChangelog2
|
|
12
|
+
} from "./chunk-sm9ej5ac.js";
|
|
13
|
+
import {
|
|
14
|
+
isNewerVersion,
|
|
15
|
+
isSameMajorVersion,
|
|
16
|
+
parseChangelog2
|
|
17
|
+
} from "./chunk-4rh7rb74.js";
|
|
18
|
+
|
|
19
|
+
// src/features/version-footer/model/version-status.ts
|
|
20
|
+
import { createSignal } from "solid-js";
|
|
21
|
+
var NAVIGATOR_PACKAGE_URL = "https://registry.npmjs.org/opencode-navigator/latest";
|
|
22
|
+
var REQUEST_TIMEOUT_MS = 5000;
|
|
23
|
+
async function readNavigatorVersion(request, signal) {
|
|
24
|
+
const response = await request(NAVIGATOR_PACKAGE_URL, { headers: { accept: "application/json" }, signal });
|
|
25
|
+
if (!response.ok)
|
|
26
|
+
throw new Error("Update source unavailable");
|
|
27
|
+
const metadata = await response.json();
|
|
28
|
+
if (typeof metadata.version !== "string" || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?(?:\+[\w.-]+)?$/.test(metadata.version))
|
|
29
|
+
throw new Error("Invalid update version");
|
|
30
|
+
return metadata.version;
|
|
17
31
|
}
|
|
18
|
-
function createVersionStatus(api,navigatorVersion,request=fetch,timeoutMs=REQUEST_TIMEOUT_MS){
|
|
19
|
-
const [
|
|
20
|
-
const [
|
|
21
|
-
|
|
22
|
-
let
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
api.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
active
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
32
|
+
function createVersionStatus(api, navigatorVersion, request = fetch, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
33
|
+
const [openCodeUpdate, setOpenCodeUpdate] = createSignal();
|
|
34
|
+
const [navigatorUpdate, setNavigatorUpdate] = createSignal();
|
|
35
|
+
const [navigatorCheckState, setNavigatorCheckState] = createSignal({ status: "idle" });
|
|
36
|
+
let controller;
|
|
37
|
+
let pending;
|
|
38
|
+
let disposed = false;
|
|
39
|
+
const unsubscribe = api.event.on("installation.update-available", (event) => {
|
|
40
|
+
const version = event.properties.version;
|
|
41
|
+
setOpenCodeUpdate(isSameMajorVersion(version, api.app.version) && isNewerVersion(version, api.app.version) ? version : undefined);
|
|
42
|
+
});
|
|
43
|
+
api.lifecycle.onDispose(() => {
|
|
44
|
+
disposed = true;
|
|
45
|
+
controller?.abort();
|
|
46
|
+
unsubscribe();
|
|
47
|
+
});
|
|
48
|
+
function refreshNavigator() {
|
|
49
|
+
if (disposed)
|
|
50
|
+
return Promise.resolve("unavailable");
|
|
51
|
+
if (pending)
|
|
52
|
+
return pending;
|
|
53
|
+
setNavigatorCheckState((previous) => ({ ...previous, status: "checking" }));
|
|
54
|
+
const active = new AbortController;
|
|
55
|
+
controller = active;
|
|
56
|
+
const timeout = setTimeout(() => active.abort(), timeoutMs);
|
|
57
|
+
let abort;
|
|
58
|
+
const cancelled = new Promise((_, reject) => {
|
|
59
|
+
abort = () => reject(new Error("Update check interrupted"));
|
|
60
|
+
active.signal.addEventListener("abort", abort, { once: true });
|
|
61
|
+
});
|
|
62
|
+
const reading = Promise.race([readNavigatorVersion(request, active.signal), cancelled]).then((version) => {
|
|
63
|
+
if (disposed || active.signal.aborted)
|
|
64
|
+
return "unavailable";
|
|
65
|
+
const newer = isNewerVersion(version, navigatorVersion);
|
|
66
|
+
setNavigatorUpdate(newer ? version : undefined);
|
|
67
|
+
setNavigatorCheckState({ status: newer ? "available" : "current", latest: version, checkedAt: Date.now() });
|
|
68
|
+
return newer ? "update" : "current";
|
|
69
|
+
}).catch(() => {
|
|
70
|
+
if (!disposed)
|
|
71
|
+
setNavigatorCheckState((previous) => ({ ...previous, status: "error", checkedAt: Date.now() }));
|
|
72
|
+
return "unavailable";
|
|
73
|
+
}).finally(() => {
|
|
74
|
+
clearTimeout(timeout);
|
|
75
|
+
active.signal.removeEventListener("abort", abort);
|
|
76
|
+
if (controller === active)
|
|
77
|
+
controller = undefined;
|
|
78
|
+
if (pending === reading)
|
|
79
|
+
pending = undefined;
|
|
80
|
+
});
|
|
81
|
+
pending = reading;
|
|
82
|
+
return reading;
|
|
83
|
+
}
|
|
84
|
+
refreshNavigator();
|
|
85
|
+
return { openCodeUpdate, navigatorUpdate, refreshNavigator, navigatorCheckState };
|
|
61
86
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
import {
|
|
68
|
-
import {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
import {
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
76
|
-
import {
|
|
77
|
-
import {
|
|
78
|
-
import {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
import {
|
|
82
|
-
import {
|
|
83
|
-
import {
|
|
84
|
-
import {
|
|
85
|
-
import {
|
|
86
|
-
import {
|
|
87
|
-
import {setProp as _$
|
|
88
|
-
import {
|
|
89
|
-
import {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
import {
|
|
93
|
-
import {
|
|
94
|
-
import {
|
|
95
|
-
import {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return
|
|
87
|
+
// src/features/version-footer/ui/version-footer.tsx
|
|
88
|
+
import { effect as _$effect4 } from "@opentui/solid";
|
|
89
|
+
import { insertNode as _$insertNode4 } from "@opentui/solid";
|
|
90
|
+
import { createComponent as _$createComponent3 } from "@opentui/solid";
|
|
91
|
+
import { insert as _$insert4 } from "@opentui/solid";
|
|
92
|
+
import { setProp as _$setProp4 } from "@opentui/solid";
|
|
93
|
+
import { createElement as _$createElement4 } from "@opentui/solid";
|
|
94
|
+
|
|
95
|
+
// src/features/version-footer/ui/version-summary.tsx
|
|
96
|
+
import { createTextNode as _$createTextNode3 } from "@opentui/solid";
|
|
97
|
+
import { insertNode as _$insertNode3 } from "@opentui/solid";
|
|
98
|
+
import { setProp as _$setProp3 } from "@opentui/solid";
|
|
99
|
+
import { effect as _$effect3 } from "@opentui/solid";
|
|
100
|
+
import { insert as _$insert3 } from "@opentui/solid";
|
|
101
|
+
import { memo as _$memo2 } from "@opentui/solid";
|
|
102
|
+
import { createComponent as _$createComponent2 } from "@opentui/solid";
|
|
103
|
+
import { createElement as _$createElement3 } from "@opentui/solid";
|
|
104
|
+
|
|
105
|
+
// src/features/version-footer/ui/version-control.tsx
|
|
106
|
+
import { effect as _$effect2 } from "@opentui/solid";
|
|
107
|
+
import { createTextNode as _$createTextNode2 } from "@opentui/solid";
|
|
108
|
+
import { insertNode as _$insertNode2 } from "@opentui/solid";
|
|
109
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
110
|
+
import { insert as _$insert2 } from "@opentui/solid";
|
|
111
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
112
|
+
import { setProp as _$setProp2 } from "@opentui/solid";
|
|
113
|
+
import { createElement as _$createElement2 } from "@opentui/solid";
|
|
114
|
+
import { useIcons } from "./tui.js";
|
|
115
|
+
|
|
116
|
+
// src/features/version-footer/ui/install-progress.tsx
|
|
117
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
118
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
119
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
120
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
121
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
122
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
123
|
+
import { createEffect, createSignal as createSignal2, onCleanup } from "solid-js";
|
|
124
|
+
// src/features/version-footer/ui/version-update.tsx
|
|
125
|
+
import { createComponent as _$createComponent5 } from "@opentui/solid";
|
|
126
|
+
import { createSignal as createSignal3 } from "solid-js";
|
|
127
|
+
|
|
128
|
+
// src/features/version-footer/model/manual-update-check.ts
|
|
129
|
+
function createManualUpdateCheck(api, status, current, onUpdate) {
|
|
130
|
+
let checking = false;
|
|
131
|
+
async function read() {
|
|
132
|
+
try {
|
|
133
|
+
return await status.refreshNavigator?.() ?? "unavailable";
|
|
134
|
+
} catch {
|
|
135
|
+
return "unavailable";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
checking: () => checking,
|
|
140
|
+
async run(offerUpdate = false) {
|
|
141
|
+
if (!current() || checking)
|
|
142
|
+
return;
|
|
143
|
+
checking = true;
|
|
144
|
+
api.ui.toast({ variant: "info", title: "Navigator update", message: "Checking for updates\u2026" });
|
|
145
|
+
try {
|
|
146
|
+
const result = await read();
|
|
147
|
+
if (!current())
|
|
148
|
+
return;
|
|
149
|
+
if (result === "update" && offerUpdate) {
|
|
150
|
+
onUpdate();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
api.ui.toast({
|
|
154
|
+
variant: result === "unavailable" ? "warning" : "info",
|
|
155
|
+
title: "Navigator update",
|
|
156
|
+
message: {
|
|
157
|
+
update: `Navigator ${status.navigatorUpdate()} is available. Use u in Navigator shortcuts to update.`,
|
|
158
|
+
current: "Navigator is up to date.",
|
|
159
|
+
unavailable: "Could not check Navigator updates. Try again."
|
|
160
|
+
}[result]
|
|
161
|
+
});
|
|
162
|
+
} finally {
|
|
163
|
+
checking = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
109
167
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return
|
|
120
|
-
},
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
168
|
+
|
|
169
|
+
// src/features/version-footer/ui/nested-version-confirmation.tsx
|
|
170
|
+
import { createComponent as _$createComponent4 } from "@opentui/solid";
|
|
171
|
+
import { onCleanup as onCleanup2 } from "solid-js";
|
|
172
|
+
import { GuardedConfirmation, SelectionMenu } from "./tui.js";
|
|
173
|
+
function NestedVersionConfirmation(props) {
|
|
174
|
+
onCleanup2(props.dismiss);
|
|
175
|
+
return _$createComponent4(GuardedConfirmation, {
|
|
176
|
+
get api() {
|
|
177
|
+
return props.api;
|
|
178
|
+
},
|
|
179
|
+
get current() {
|
|
180
|
+
return props.current;
|
|
181
|
+
},
|
|
182
|
+
get title() {
|
|
183
|
+
return `Update ${props.name}`;
|
|
184
|
+
},
|
|
185
|
+
get message() {
|
|
186
|
+
return `Install ${props.version} using the current installation method?`;
|
|
187
|
+
},
|
|
188
|
+
get onConfirm() {
|
|
189
|
+
return props.confirm;
|
|
190
|
+
},
|
|
191
|
+
render: ({
|
|
192
|
+
confirm
|
|
193
|
+
}) => _$createComponent4(SelectionMenu, {
|
|
194
|
+
get api() {
|
|
195
|
+
return props.api;
|
|
196
|
+
},
|
|
197
|
+
get title() {
|
|
198
|
+
return `Update ${props.name}`;
|
|
199
|
+
},
|
|
200
|
+
get options() {
|
|
201
|
+
return [{
|
|
202
|
+
title: "Cancel",
|
|
203
|
+
value: "cancel",
|
|
204
|
+
description: "Return without installing an update"
|
|
205
|
+
}, {
|
|
206
|
+
title: `Install ${props.name} ${props.version}`,
|
|
207
|
+
value: "install",
|
|
208
|
+
description: "Keep the current installation method and scope; restart OpenCode afterward"
|
|
209
|
+
}];
|
|
210
|
+
},
|
|
211
|
+
onSelect: (option) => option.value === "install" ? confirm() : props.cancel()
|
|
212
|
+
})
|
|
213
|
+
});
|
|
133
214
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
215
|
+
|
|
216
|
+
// src/features/version-footer/ui/version-update.tsx
|
|
217
|
+
function createVersionUpdateActions(api, status, updater, rememberNavigatorVersion) {
|
|
218
|
+
const busy = new Set;
|
|
219
|
+
const [navigatorUpdating, setNavigatorUpdating] = createSignal3(false);
|
|
220
|
+
const [navigatorInstalled, setNavigatorInstalled] = createSignal3();
|
|
221
|
+
const [navigatorError, setNavigatorError] = createSignal3();
|
|
222
|
+
const [progress, setProgress] = createSignal3({});
|
|
223
|
+
const installed = {};
|
|
224
|
+
let disposed = false;
|
|
225
|
+
let confirmation = 0;
|
|
226
|
+
function target(kind) {
|
|
227
|
+
return kind === "openCode" ? status.openCodeUpdate() : status.navigatorUpdate();
|
|
228
|
+
}
|
|
229
|
+
function label(kind) {
|
|
230
|
+
return kind === "openCode" ? "OpenCode" : "Navigator";
|
|
231
|
+
}
|
|
232
|
+
function update(kind, version, navigation) {
|
|
233
|
+
if (disposed || busy.has(kind))
|
|
234
|
+
return;
|
|
235
|
+
if (version !== target(kind)) {
|
|
236
|
+
api.ui.toast({
|
|
237
|
+
variant: "warning",
|
|
238
|
+
title: "Update changed",
|
|
239
|
+
message: "Check for updates again before confirming."
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (navigation)
|
|
244
|
+
navigation.back();
|
|
245
|
+
else
|
|
246
|
+
api.ui.dialog.clear();
|
|
247
|
+
busy.add(kind);
|
|
248
|
+
setProgress((previous) => ({
|
|
249
|
+
...previous,
|
|
250
|
+
[kind]: {
|
|
251
|
+
phase: "preparing"
|
|
252
|
+
}
|
|
253
|
+
}));
|
|
254
|
+
if (kind === "navigator") {
|
|
255
|
+
setNavigatorUpdating(true);
|
|
256
|
+
setNavigatorError(undefined);
|
|
257
|
+
}
|
|
258
|
+
api.ui.toast({
|
|
259
|
+
variant: "info",
|
|
260
|
+
title: `Updating ${label(kind)}`,
|
|
261
|
+
message: `Installing ${version}...`
|
|
262
|
+
});
|
|
263
|
+
(async () => {
|
|
264
|
+
if (kind === "navigator")
|
|
265
|
+
await rememberNavigatorVersion?.();
|
|
266
|
+
if (disposed || version !== target(kind))
|
|
267
|
+
throw new Error("Update target changed; check again");
|
|
268
|
+
const report = (value) => {
|
|
269
|
+
if (!disposed && busy.has(kind))
|
|
270
|
+
setProgress((previous) => ({
|
|
271
|
+
...previous,
|
|
272
|
+
[kind]: value
|
|
273
|
+
}));
|
|
274
|
+
};
|
|
275
|
+
report({
|
|
276
|
+
phase: "installing"
|
|
277
|
+
});
|
|
278
|
+
await updater[kind](version, report);
|
|
279
|
+
})().then(() => {
|
|
280
|
+
installed[kind] = version;
|
|
281
|
+
if (kind === "navigator")
|
|
282
|
+
setNavigatorInstalled(version);
|
|
283
|
+
if (disposed)
|
|
284
|
+
return;
|
|
285
|
+
api.ui.toast({
|
|
286
|
+
variant: "success",
|
|
287
|
+
title: `${label(kind)} updated`,
|
|
288
|
+
message: "Restart OpenCode to use the new version.",
|
|
289
|
+
duration: 6000
|
|
290
|
+
});
|
|
291
|
+
}).catch((error) => {
|
|
292
|
+
if (disposed)
|
|
293
|
+
return;
|
|
294
|
+
if (kind === "navigator")
|
|
295
|
+
setNavigatorError(error instanceof Error ? error.message : "Update failed");
|
|
296
|
+
api.ui.toast({
|
|
297
|
+
variant: "error",
|
|
298
|
+
title: `Could not update ${label(kind)}`,
|
|
299
|
+
message: error instanceof Error ? error.message : "Update failed",
|
|
300
|
+
duration: 6000
|
|
301
|
+
});
|
|
302
|
+
}).finally(() => {
|
|
303
|
+
busy.delete(kind);
|
|
304
|
+
setProgress((previous) => ({
|
|
305
|
+
...previous,
|
|
306
|
+
[kind]: undefined
|
|
307
|
+
}));
|
|
308
|
+
if (kind === "navigator")
|
|
309
|
+
setNavigatorUpdating(false);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
function open(kind, version = target(kind), navigation) {
|
|
313
|
+
if (disposed || !version || version !== target(kind) || busy.has(kind))
|
|
314
|
+
return;
|
|
315
|
+
if (installed[kind] === version) {
|
|
316
|
+
api.ui.toast({
|
|
317
|
+
variant: "info",
|
|
318
|
+
title: `${label(kind)} updated`,
|
|
319
|
+
message: "Restart OpenCode to use the installed update."
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const id = ++confirmation;
|
|
324
|
+
const onConfirm = () => {
|
|
325
|
+
if (id !== confirmation)
|
|
326
|
+
return;
|
|
327
|
+
confirmation++;
|
|
328
|
+
update(kind, version, navigation);
|
|
329
|
+
};
|
|
330
|
+
if (navigation) {
|
|
331
|
+
navigation.open(() => _$createComponent5(NestedVersionConfirmation, {
|
|
332
|
+
api,
|
|
333
|
+
get name() {
|
|
334
|
+
return label(kind);
|
|
335
|
+
},
|
|
336
|
+
version,
|
|
337
|
+
current: () => !disposed && id === confirmation && version === target(kind),
|
|
338
|
+
confirm: onConfirm,
|
|
339
|
+
cancel: () => {
|
|
340
|
+
if (id !== confirmation)
|
|
341
|
+
return;
|
|
342
|
+
confirmation++;
|
|
343
|
+
navigation.back();
|
|
344
|
+
},
|
|
345
|
+
dismiss: () => {
|
|
346
|
+
if (id === confirmation)
|
|
347
|
+
confirmation++;
|
|
348
|
+
}
|
|
349
|
+
}));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
api.ui.dialog.replace(() => _$createComponent5(api.ui.DialogConfirm, {
|
|
353
|
+
get title() {
|
|
354
|
+
return `Update ${label(kind)}`;
|
|
355
|
+
},
|
|
356
|
+
message: `Update to ${version} using the current installation method? OpenCode must be restarted afterward.`,
|
|
357
|
+
onConfirm: () => {
|
|
358
|
+
if (id !== confirmation)
|
|
359
|
+
return;
|
|
360
|
+
confirmation++;
|
|
361
|
+
update(kind, version);
|
|
362
|
+
},
|
|
363
|
+
onCancel: () => {
|
|
364
|
+
if (id !== confirmation)
|
|
365
|
+
return;
|
|
366
|
+
confirmation++;
|
|
367
|
+
api.ui.dialog.clear();
|
|
368
|
+
}
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
const manual = createManualUpdateCheck(api, status, () => !disposed && !busy.has("navigator"), () => open("navigator"));
|
|
372
|
+
const unregister = api.keymap.registerLayer({
|
|
373
|
+
commands: [{
|
|
374
|
+
name: `${PLUGIN_ID}.check-update`,
|
|
375
|
+
title: "Check Navigator updates",
|
|
376
|
+
category: "Navigator",
|
|
377
|
+
namespace: "palette",
|
|
378
|
+
enabled: () => !manual.checking() && !busy.has("navigator"),
|
|
379
|
+
run: () => void manual.run()
|
|
380
|
+
}, {
|
|
381
|
+
name: `${PLUGIN_ID}.update-opencode`,
|
|
382
|
+
title: "Update OpenCode",
|
|
383
|
+
category: "Navigator",
|
|
384
|
+
namespace: "palette",
|
|
385
|
+
enabled: () => Boolean(target("openCode")) && !busy.has("openCode"),
|
|
386
|
+
run: () => open("openCode")
|
|
387
|
+
}, {
|
|
388
|
+
name: `${PLUGIN_ID}.update-navigator`,
|
|
389
|
+
title: "Update Navigator",
|
|
390
|
+
category: "Navigator",
|
|
391
|
+
namespace: "palette",
|
|
392
|
+
enabled: () => !manual.checking() && !busy.has("navigator"),
|
|
393
|
+
run: () => void manual.run(true)
|
|
394
|
+
}]
|
|
395
|
+
});
|
|
396
|
+
api.lifecycle.onDispose(() => {
|
|
397
|
+
disposed = true;
|
|
398
|
+
unregister();
|
|
399
|
+
});
|
|
400
|
+
return {
|
|
401
|
+
openCode: (version) => open("openCode", version),
|
|
402
|
+
navigator: (version, navigation) => open("navigator", version, navigation),
|
|
403
|
+
navigatorUpdating,
|
|
404
|
+
navigatorInstalled,
|
|
405
|
+
navigatorError,
|
|
406
|
+
navigatorProgress: () => progress().navigator,
|
|
407
|
+
openCodeProgress: () => progress().openCode
|
|
408
|
+
};
|
|
142
409
|
}
|
|
143
|
-
|
|
410
|
+
// src/app/version-updaters.ts
|
|
411
|
+
import { homedir as homedir2 } from "os";
|
|
412
|
+
import { basename, dirname, isAbsolute, join as join2 } from "path";
|
|
413
|
+
import { fileURLToPath } from "url";
|
|
414
|
+
|
|
415
|
+
// src/app/cli-pin.ts
|
|
416
|
+
import { randomUUID } from "crypto";
|
|
417
|
+
import { readFile, realpath, rename, stat, unlink, writeFile } from "fs/promises";
|
|
418
|
+
import { homedir } from "os";
|
|
419
|
+
import { join } from "path";
|
|
420
|
+
|
|
421
|
+
// src/shared/lib/server-file/index.ts
|
|
422
|
+
function serverFileTarget(path) {
|
|
423
|
+
const windows = /^[a-z]:[\\/]|^\\\\/iu.test(path);
|
|
424
|
+
const absolute = windows ? /^[a-z]:[\\/]|^\\\\[^\\/]+[\\/][^\\/]+[\\/]/iu.test(path) : path.startsWith("/");
|
|
425
|
+
const pieces = path.split(windows ? /[\\/]/u : /\//u);
|
|
426
|
+
const index = windows ? Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) : path.lastIndexOf("/");
|
|
427
|
+
const file = path.slice(index + 1);
|
|
428
|
+
if (!absolute || path.includes("\x00") || !file || pieces.some((piece) => piece === "." || piece === ".."))
|
|
429
|
+
throw new Error("OpenCode returned an unsafe or non-absolute configuration path.");
|
|
430
|
+
let directory = path.slice(0, index);
|
|
431
|
+
if (index === 0)
|
|
432
|
+
directory = "/";
|
|
433
|
+
else if (windows && index === 2)
|
|
434
|
+
directory = path.slice(0, 3);
|
|
435
|
+
return { location: { directory }, path: file };
|
|
144
436
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return
|
|
437
|
+
|
|
438
|
+
// src/app/plugin-pin.ts
|
|
439
|
+
function exactNavigatorPin(target) {
|
|
440
|
+
return /^opencode-navigator@(\d+\.\d+\.\d+)$/.exec(target)?.[1];
|
|
150
441
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
442
|
+
function pluginPinEdit(text, target, version) {
|
|
443
|
+
const old = exactNavigatorPin(target);
|
|
444
|
+
if (!old || !isNewerVersion(version, old))
|
|
445
|
+
throw new Error("Invalid pinned Navigator update target");
|
|
446
|
+
const errors = [];
|
|
447
|
+
const root = parseTree(text, errors, { allowTrailingComma: true });
|
|
448
|
+
if (!root || errors.length > 0)
|
|
449
|
+
throw new Error("Navigator configuration is invalid; no changes were made");
|
|
450
|
+
const paths = [];
|
|
451
|
+
for (const key of ["plugins", "plugin"]) {
|
|
452
|
+
const list = findNodeAtLocation(root, [key]);
|
|
453
|
+
const nodes = list?.type === "array" ? list.children ?? [] : [];
|
|
454
|
+
for (const [index, node] of nodes.entries()) {
|
|
455
|
+
const value = getNodeValue(node);
|
|
456
|
+
const path = [key, index];
|
|
457
|
+
if (value === target)
|
|
458
|
+
paths.push(path);
|
|
459
|
+
else if (Array.isArray(value) && value[0] === target)
|
|
460
|
+
paths.push([...path, 0]);
|
|
461
|
+
else if (value?.package === target)
|
|
462
|
+
paths.push([...path, "package"]);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (paths.length === 0)
|
|
466
|
+
return;
|
|
467
|
+
if (paths.length !== 1)
|
|
468
|
+
throw new Error("Navigator pin is ambiguous; no changes were made");
|
|
469
|
+
const node = findNodeAtLocation(root, paths[0]);
|
|
470
|
+
return text.slice(0, node.offset) + JSON.stringify(`opencode-navigator@${version}`) + text.slice(node.offset + node.length);
|
|
166
471
|
}
|
|
167
|
-
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
472
|
+
async function advanceServerPin(client, location, target, version, signal, valid = () => true) {
|
|
473
|
+
const entries = await client.config.get(location, { signal });
|
|
474
|
+
const candidates = [];
|
|
475
|
+
for (const entry of entries) {
|
|
476
|
+
if (entry.type !== "document" || !entry.path)
|
|
477
|
+
continue;
|
|
478
|
+
const file = serverFileTarget(entry.path);
|
|
479
|
+
const text = new TextDecoder().decode(await client.file.read(file, { signal }));
|
|
480
|
+
const next = pluginPinEdit(text, target, version);
|
|
481
|
+
if (next)
|
|
482
|
+
candidates.push({ file, text, next });
|
|
483
|
+
}
|
|
484
|
+
if (candidates.length !== 1)
|
|
485
|
+
throw new Error("Cannot identify one owning Navigator configuration; no changes were made");
|
|
486
|
+
const selected = candidates[0];
|
|
487
|
+
const current = new TextDecoder().decode(await client.file.read(selected.file, { signal }));
|
|
488
|
+
if (current !== selected.text)
|
|
489
|
+
throw new Error("Navigator configuration changed; check again before updating");
|
|
490
|
+
const active = await client.plugin.list(location, { signal });
|
|
491
|
+
if (!valid() || active.data.every((item) => !(item.source.type === "package" && item.source.target === target)))
|
|
492
|
+
throw new Error("Navigator installation changed; check again before updating");
|
|
493
|
+
if (new TextDecoder().decode(await client.file.read(selected.file, { signal })) !== selected.text || !valid())
|
|
494
|
+
throw new Error("Navigator configuration changed; check again before updating");
|
|
495
|
+
signal.throwIfAborted();
|
|
496
|
+
await client.experimental.file.write({ ...selected.file, payload: new TextEncoder().encode(selected.next) }, { signal });
|
|
497
|
+
if (new TextDecoder().decode(await client.file.read(selected.file, { signal })) !== selected.next)
|
|
498
|
+
throw new Error("Could not verify the Navigator configuration update; inspect it before retrying");
|
|
187
499
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
500
|
+
|
|
501
|
+
// src/app/cli-pin.ts
|
|
502
|
+
async function advanceCliPin(target, version, path = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "opencode", "cli.json"), valid = () => true) {
|
|
503
|
+
if (process.env.OPENCODE_CLI_CONFIG_CONTENT !== undefined)
|
|
504
|
+
throw new Error("Inline CLI configuration is active; update its Navigator pin explicitly");
|
|
505
|
+
let resolved;
|
|
506
|
+
try {
|
|
507
|
+
resolved = await realpath(path);
|
|
508
|
+
} catch (error) {
|
|
509
|
+
if (error.code === "ENOENT")
|
|
510
|
+
return false;
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
const original = await readFile(resolved, "utf8");
|
|
514
|
+
const next = pluginPinEdit(original, target, version);
|
|
515
|
+
if (!next)
|
|
516
|
+
return false;
|
|
517
|
+
const temporary = `${resolved}.navigator-${randomUUID()}`;
|
|
518
|
+
const metadata = await stat(resolved);
|
|
519
|
+
const mode = metadata.mode;
|
|
520
|
+
try {
|
|
521
|
+
await writeFile(temporary, next, { flag: "wx", mode });
|
|
522
|
+
if (await readFile(resolved, "utf8") !== original || await realpath(path) !== resolved || !valid())
|
|
523
|
+
throw new Error("CLI configuration changed; check again before updating");
|
|
524
|
+
await rename(temporary, resolved);
|
|
525
|
+
if (await readFile(resolved, "utf8") !== next)
|
|
526
|
+
throw new Error("Could not verify the Navigator configuration update");
|
|
527
|
+
} finally {
|
|
528
|
+
await unlink(temporary).catch(() => {});
|
|
529
|
+
}
|
|
530
|
+
return true;
|
|
193
531
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
532
|
+
|
|
533
|
+
// src/app/snapshot-update-process.ts
|
|
534
|
+
var prefix = "navigator-update-progress:";
|
|
535
|
+
function reportLine(line, report) {
|
|
536
|
+
if (!line.startsWith(prefix) || line.length > 1024)
|
|
537
|
+
return;
|
|
538
|
+
try {
|
|
539
|
+
const data = JSON.parse(line.slice(prefix.length));
|
|
540
|
+
if (["preparing", "downloading", "installing", "verifying"].includes(data.phase ?? ""))
|
|
541
|
+
report({ phase: data.phase });
|
|
542
|
+
} catch {}
|
|
199
543
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
544
|
+
async function runSnapshotUpdate(command, report) {
|
|
545
|
+
const child = Bun.spawn([...command], { stdout: "pipe", stderr: "pipe" });
|
|
546
|
+
const error = new Response(child.stderr).text();
|
|
547
|
+
const reader = child.stdout.getReader();
|
|
548
|
+
const decoder = new TextDecoder;
|
|
549
|
+
let buffer = "";
|
|
550
|
+
const reading = (async () => {
|
|
551
|
+
while (true) {
|
|
552
|
+
const { done, value } = await reader.read();
|
|
553
|
+
if (done)
|
|
554
|
+
return;
|
|
555
|
+
buffer += decoder.decode(value, { stream: true });
|
|
556
|
+
const lines = buffer.split(`
|
|
557
|
+
`);
|
|
558
|
+
buffer = (lines.pop() ?? "").slice(-1024);
|
|
559
|
+
for (const line of lines)
|
|
560
|
+
reportLine(line, report);
|
|
561
|
+
}
|
|
562
|
+
})().catch(() => {});
|
|
563
|
+
const exit = await child.exited;
|
|
564
|
+
await reader.cancel().catch(() => {});
|
|
565
|
+
await reading;
|
|
566
|
+
const message = await error;
|
|
567
|
+
if (exit !== 0)
|
|
568
|
+
throw new Error(message.trim() || `Snapshot installer failed (${exit})`);
|
|
209
569
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return
|
|
217
|
-
confirmation++
|
|
218
|
-
api.ui.dialog.clear()
|
|
219
|
-
}}))
|
|
570
|
+
|
|
571
|
+
// src/app/version-updaters.ts
|
|
572
|
+
async function upgradeHost(current, target, execute) {
|
|
573
|
+
if (!isSameMajorVersion(current, target))
|
|
574
|
+
throw new Error("OpenCode updates must stay on the current major version");
|
|
575
|
+
await execute([process.execPath, "upgrade", target]);
|
|
220
576
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
function
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const reader=child.stdout.getReader()
|
|
245
|
-
const decoder=new TextDecoder
|
|
246
|
-
let buffer=""
|
|
247
|
-
const reading=(async ()=>{while(true){const{done,value}=await reader.read()
|
|
248
|
-
if(done)
|
|
249
|
-
return
|
|
250
|
-
buffer += decoder.decode(value,{stream:true})
|
|
251
|
-
const lines=buffer.split(`
|
|
252
|
-
`)
|
|
253
|
-
buffer=(lines.pop()??"").slice(-1024)
|
|
254
|
-
for(const line of lines)
|
|
255
|
-
reportLine(line,report)
|
|
256
|
-
}})().catch(()=>{})
|
|
257
|
-
const exit=await child.exited
|
|
258
|
-
await reader.cancel().catch(()=>{})
|
|
259
|
-
await reading
|
|
260
|
-
const message=await error
|
|
261
|
-
if(exit!==0)
|
|
262
|
-
throw new Error(message.trim()||`Snapshot installer failed (${exit})`)
|
|
577
|
+
var run = async (command, progress) => {
|
|
578
|
+
if (progress && command[1]?.endsWith("/update-local-snapshot.mjs"))
|
|
579
|
+
return runSnapshotUpdate(command, progress);
|
|
580
|
+
const child = Bun.spawn([...command], { stdout: "ignore", stderr: "pipe" });
|
|
581
|
+
const error = new Response(child.stderr).text();
|
|
582
|
+
const exitCode = await child.exited;
|
|
583
|
+
if (exitCode === 0)
|
|
584
|
+
return;
|
|
585
|
+
const message = await error;
|
|
586
|
+
throw new Error(message.trim() || `Command failed with exit code ${child.exitCode}`);
|
|
587
|
+
};
|
|
588
|
+
var readConfig = async (path) => {
|
|
589
|
+
const file = Bun.file(path);
|
|
590
|
+
return await file.exists() ? Bun.JSONC.parse(await file.text()) : undefined;
|
|
591
|
+
};
|
|
592
|
+
function pinnedUpdate(wrapper, target, execute, progress) {
|
|
593
|
+
if (basename(wrapper) !== "tui.js" || basename(dirname(wrapper)) !== "opencode-navigator")
|
|
594
|
+
throw new Error("Unrecognized local Navigator source");
|
|
595
|
+
const installer = fileURLToPath(new URL("../scripts/update-local-snapshot.mjs", import.meta.url));
|
|
596
|
+
const command = ["node", installer, wrapper, target];
|
|
597
|
+
if (progress)
|
|
598
|
+
return execute([...command, "--progress-json"], progress);
|
|
599
|
+
return execute(command);
|
|
263
600
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
601
|
+
function configHasPlugin(value, spec) {
|
|
602
|
+
if (!value || typeof value !== "object")
|
|
603
|
+
return false;
|
|
604
|
+
const plugins = value.plugin;
|
|
605
|
+
return Array.isArray(plugins) && plugins.some((item) => {
|
|
606
|
+
let configured = item;
|
|
607
|
+
if (Array.isArray(item))
|
|
608
|
+
configured = item[0];
|
|
609
|
+
else if (item && typeof item === "object")
|
|
610
|
+
configured = item.package;
|
|
611
|
+
return configured === spec;
|
|
612
|
+
});
|
|
267
613
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
const
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
614
|
+
async function v1Global(api, spec, read) {
|
|
615
|
+
const configRoot = process.env.OPENCODE_CONFIG_DIR ?? join2(process.env.XDG_CONFIG_HOME ?? join2(homedir2(), ".config"), "opencode");
|
|
616
|
+
const global = join2(configRoot, "tui.json");
|
|
617
|
+
const configured = api.state.path.config.endsWith(".json") ? api.state.path.config : join2(api.state.path.config, "tui.json");
|
|
618
|
+
const local = new Set([configured, join2(api.state.path.worktree, ".opencode", "tui.json")]);
|
|
619
|
+
local.delete(global);
|
|
620
|
+
for (const path of local)
|
|
621
|
+
if (configHasPlugin(await read(path), spec))
|
|
622
|
+
return false;
|
|
623
|
+
return configHasPlugin(await read(global), spec);
|
|
277
624
|
}
|
|
278
|
-
|
|
279
|
-
return
|
|
625
|
+
function createOpenCodeV1VersionUpdater(api, meta, execute = run, read = readConfig) {
|
|
626
|
+
return {
|
|
627
|
+
openCode: (target) => upgradeHost(api.app.version, target, execute),
|
|
628
|
+
async navigator(target, progress) {
|
|
629
|
+
if (meta.source === "file") {
|
|
630
|
+
const wrapper = meta.spec.startsWith("file://") ? fileURLToPath(meta.spec) : meta.spec;
|
|
631
|
+
return pinnedUpdate(wrapper, target, execute, progress);
|
|
632
|
+
}
|
|
633
|
+
if (meta.source !== "npm")
|
|
634
|
+
throw new Error("Unrecognized local Navigator source");
|
|
635
|
+
const result = await api.plugins.install(`opencode-navigator@${target}`, {
|
|
636
|
+
global: await v1Global(api, meta.spec, read)
|
|
637
|
+
});
|
|
638
|
+
if (!result.ok)
|
|
639
|
+
throw new Error(result.message);
|
|
640
|
+
}
|
|
641
|
+
};
|
|
280
642
|
}
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
643
|
+
function createOpenCodeV2VersionUpdater(context, execute = run) {
|
|
644
|
+
return {
|
|
645
|
+
openCode: (target) => upgradeHost(context.app.version, target, execute),
|
|
646
|
+
async navigator(target, progress) {
|
|
647
|
+
progress?.({ phase: "preparing" });
|
|
648
|
+
const client = context.client;
|
|
649
|
+
const location = JSON.stringify(context.location);
|
|
650
|
+
const response = await client.plugin.list(context.location ? { location: context.location } : {});
|
|
651
|
+
const item = response.data.find((plugin) => plugin.id === "opencode-navigator.todo-server" || plugin.id === PLUGIN_ID);
|
|
652
|
+
if (!item)
|
|
653
|
+
throw new Error("Navigator is not in the active plugin list");
|
|
654
|
+
if (item.source.type === "local")
|
|
655
|
+
return pinnedUpdate(basename(item.source.path) === "tui.js" ? item.source.path : join2(item.source.path, "tui.js"), target, execute, progress);
|
|
656
|
+
if (item.source.type !== "package")
|
|
657
|
+
throw new Error("Unrecognized local Navigator source");
|
|
658
|
+
if (isAbsolute(item.source.target))
|
|
659
|
+
return pinnedUpdate(join2(item.source.target, "tui.js"), target, execute, progress);
|
|
660
|
+
if (item.source.target.startsWith("file://"))
|
|
661
|
+
return pinnedUpdate(join2(fileURLToPath(item.source.target), "tui.js"), target, execute, progress);
|
|
662
|
+
if (item.source.target.startsWith("."))
|
|
663
|
+
throw new Error("Unrecognized local Navigator source");
|
|
664
|
+
if (exactNavigatorPin(item.source.target)) {
|
|
665
|
+
if (context.client !== client || JSON.stringify(context.location) !== location)
|
|
666
|
+
throw new Error("Navigator installation changed; check again");
|
|
667
|
+
const loadedPin = typeof __NAVIGATOR_VERSION__ === "string" ? `opencode-navigator@${__NAVIGATOR_VERSION__}` : item.source.target;
|
|
668
|
+
if (exactNavigatorPin(loadedPin) !== target && await advanceCliPin(loadedPin, target, undefined, () => context.client === client && JSON.stringify(context.location) === location)) {
|
|
669
|
+
progress?.({ phase: "verifying" });
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
if (exactNavigatorPin(item.source.target) !== target)
|
|
673
|
+
await advanceServerPin(client, { location: response.location }, item.source.target, target, AbortSignal.timeout(120000), () => context.client === client && JSON.stringify(context.location) === location);
|
|
674
|
+
progress?.({ phase: "verifying" });
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
progress?.({ phase: "installing" });
|
|
678
|
+
await context.client.plugin.update({ location: response.location, targets: [item.source.target] });
|
|
679
|
+
}
|
|
680
|
+
};
|
|
288
681
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
return configured===spec
|
|
298
|
-
})
|
|
299
|
-
}
|
|
300
|
-
async function v1Global(api,spec,read){const configRoot=process.env.OPENCODE_CONFIG_DIR??join(process.env.XDG_CONFIG_HOME??join(homedir(),".config"),"opencode")
|
|
301
|
-
const global=join(configRoot,"tui.json")
|
|
302
|
-
const configured=api.state.path.config.endsWith(".json")?api.state.path.config:join(api.state.path.config,"tui.json")
|
|
303
|
-
const local=new Set([configured,join(api.state.path.worktree,".opencode","tui.json")])
|
|
304
|
-
local.delete(global)
|
|
305
|
-
for(const path of local)
|
|
306
|
-
if(configHasPlugin(await read(path),spec))
|
|
307
|
-
return false
|
|
308
|
-
return configHasPlugin(await read(global),spec)
|
|
309
|
-
}
|
|
310
|
-
function createOpenCodeV1VersionUpdater(api,meta,execute=run,read=readConfig){return{openCode:(target)=>upgradeHost(api.app.version,target,execute),async navigator(target,progress){if(meta.source==="file"){const wrapper=meta.spec.startsWith("file://")?fileURLToPath(meta.spec):meta.spec
|
|
311
|
-
return pinnedUpdate(wrapper,target,execute,progress)
|
|
312
|
-
}
|
|
313
|
-
if(meta.source!=="npm")
|
|
314
|
-
throw new Error("Unrecognized local Navigator source")
|
|
315
|
-
const result=await api.plugins.install(`opencode-navigator@${target}`,{global:await v1Global(api,meta.spec,read)
|
|
316
|
-
})
|
|
317
|
-
if(!result.ok)
|
|
318
|
-
throw new Error(result.message)
|
|
319
|
-
}}}
|
|
320
|
-
function createOpenCodeV2VersionUpdater(context,execute=run){return{openCode:(target)=>upgradeHost(context.app.version,target,execute),async navigator(target,progress){progress?.({phase:"preparing"})
|
|
321
|
-
const response=await context.client.plugin.list(context.location?{location:context.location}:{})
|
|
322
|
-
const item=response.data.find((plugin)=>plugin.id===PLUGIN_ID)
|
|
323
|
-
if(!item)
|
|
324
|
-
throw new Error("Navigator is not in the active plugin list")
|
|
325
|
-
if(item.source.type==="local")
|
|
326
|
-
return pinnedUpdate(basename(item.source.path)==="tui.js"?item.source.path:join(item.source.path,"tui.js"),target,execute,progress)
|
|
327
|
-
if(item.source.type!=="package")
|
|
328
|
-
throw new Error("Unrecognized local Navigator source")
|
|
329
|
-
if(isAbsolute(item.source.target))
|
|
330
|
-
return pinnedUpdate(join(item.source.target,"tui.js"),target,execute,progress)
|
|
331
|
-
if(item.source.target.startsWith("file://"))
|
|
332
|
-
return pinnedUpdate(join(fileURLToPath(item.source.target),"tui.js"),target,execute,progress)
|
|
333
|
-
if(item.source.target.startsWith("."))
|
|
334
|
-
throw new Error("Unrecognized local Navigator source")
|
|
335
|
-
progress?.({phase:"installing"})
|
|
336
|
-
await context.client.plugin.update({location:response.location,targets:[item.source.target]})
|
|
337
|
-
}}}
|
|
338
|
-
export{createOpenCodeV1VersionUpdater,createOpenCodeV2VersionUpdater,createVersionStatus,createVersionUpdateActions,parseChangelog2 as parseChangelog,readBundledChangelog2 as readBundledChangelog
|
|
339
|
-
}
|
|
682
|
+
export {
|
|
683
|
+
createOpenCodeV1VersionUpdater,
|
|
684
|
+
createOpenCodeV2VersionUpdater,
|
|
685
|
+
createVersionStatus,
|
|
686
|
+
createVersionUpdateActions,
|
|
687
|
+
parseChangelog2 as parseChangelog,
|
|
688
|
+
readBundledChangelog2 as readBundledChangelog
|
|
689
|
+
};
|