@serviceme/devtools-core 2.0.5 → 2.0.6
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/{device-BVcUUQ9t.mjs → device-Bn2NzajD.mjs} +2 -2
- package/dist/{device-BVcUUQ9t.mjs.map → device-Bn2NzajD.mjs.map} +1 -1
- package/dist/index.js +84 -27
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +84 -27
- package/dist/index.mjs.map +1 -1
- package/dist/{submit-6zQPPrOJ.mjs → submit-Cg7wSa3x.mjs} +2 -2
- package/dist/{submit-6zQPPrOJ.mjs.map → submit-Cg7wSa3x.mjs.map} +1 -1
- package/dist/{toolbox-UG4dA2TI.mjs → toolbox-CbfxpNg-.mjs} +2 -2
- package/dist/{toolbox-UG4dA2TI.mjs.map → toolbox-CbfxpNg-.mjs.map} +1 -1
- package/dist/{userHome-DOMLiHy7.mjs → userHome-CcnAQPG1.mjs} +4 -2
- package/dist/userHome-CcnAQPG1.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/device-uFAQxlNt.js +0 -874
- package/dist/device-uFAQxlNt.js.map +0 -1
- package/dist/submit-Dy0KTHzm.js +0 -98
- package/dist/submit-Dy0KTHzm.js.map +0 -1
- package/dist/toolbox-BV7vLGXZ.js +0 -614
- package/dist/toolbox-BV7vLGXZ.js.map +0 -1
- package/dist/userHome-CLlsXCvW.js +0 -518
- package/dist/userHome-CLlsXCvW.js.map +0 -1
- package/dist/userHome-DOMLiHy7.mjs.map +0 -1
package/dist/toolbox-BV7vLGXZ.js
DELETED
|
@@ -1,614 +0,0 @@
|
|
|
1
|
-
const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.js");
|
|
2
|
-
const require_userHome = require("./userHome-CLlsXCvW.js");
|
|
3
|
-
let node_fs_promises = require("node:fs/promises");
|
|
4
|
-
node_fs_promises = require_rolldown_runtime.__toESM(node_fs_promises);
|
|
5
|
-
let node_path = require("node:path");
|
|
6
|
-
node_path = require_rolldown_runtime.__toESM(node_path);
|
|
7
|
-
let node_timers_promises = require("node:timers/promises");
|
|
8
|
-
//#region src/toolbox/sort.ts
|
|
9
|
-
/**
|
|
10
|
-
* Parsed timestamp from `lastUsedAt`. Returns 0 when the field is
|
|
11
|
-
* missing or unparseable (so the entry falls below entries with a
|
|
12
|
-
* real timestamp).
|
|
13
|
-
*/
|
|
14
|
-
function lastUsedTimestamp(tool) {
|
|
15
|
-
if (!tool.lastUsedAt) return 0;
|
|
16
|
-
const ms = Date.parse(tool.lastUsedAt);
|
|
17
|
-
return Number.isFinite(ms) ? ms : 0;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Sort by "recently used" — entries with newer `lastUsedAt` float to
|
|
21
|
-
* the top, entries with no `lastUsedAt` sort to the bottom (preserving
|
|
22
|
-
* their relative `order` index when both are absent).
|
|
23
|
-
*
|
|
24
|
-
* Ties (same `lastUsedAt` or both missing) are broken by `order`, then
|
|
25
|
-
* by `id` for determinism.
|
|
26
|
-
*/
|
|
27
|
-
function sortByRecentFirst(tools) {
|
|
28
|
-
return [...tools].sort((a, b) => {
|
|
29
|
-
const tsA = lastUsedTimestamp(a);
|
|
30
|
-
const tsB = lastUsedTimestamp(b);
|
|
31
|
-
if (tsA !== tsB) return tsB - tsA;
|
|
32
|
-
const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
|
|
33
|
-
const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
|
|
34
|
-
if (orderA !== orderB) return orderA - orderB;
|
|
35
|
-
return a.id.localeCompare(b.id);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Sort by user-defined `order` field. Entries without `order` are
|
|
40
|
-
* appended in their input order (stable sort via `id` tiebreaker).
|
|
41
|
-
*/
|
|
42
|
-
function sortByUserOrder(tools) {
|
|
43
|
-
return [...tools].sort((a, b) => {
|
|
44
|
-
const orderA = a.order ?? Number.MAX_SAFE_INTEGER;
|
|
45
|
-
const orderB = b.order ?? Number.MAX_SAFE_INTEGER;
|
|
46
|
-
if (orderA !== orderB) return orderA - orderB;
|
|
47
|
-
return a.id.localeCompare(b.id);
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Merge built-in defaults with user-stored tools. Defaults keep
|
|
52
|
-
* `isDefault: true` and their `order`; user tools are appended with
|
|
53
|
-
* their stored metadata. Output is sorted by user order.
|
|
54
|
-
*/
|
|
55
|
-
function mergeWithDefaults(defaults, userTools) {
|
|
56
|
-
const defaultIds = new Set(defaults.map((d) => d.id));
|
|
57
|
-
const uniqueUserTools = userTools.filter((t) => !defaultIds.has(t.id));
|
|
58
|
-
return sortByUserOrder([...defaults, ...uniqueUserTools]);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Re-number `order` for a list of tool ids. Used by
|
|
62
|
-
* `toolbox.update` when the user drags-and-drops entries in the UI.
|
|
63
|
-
*/
|
|
64
|
-
function reindexOrder(tools, newOrderIds) {
|
|
65
|
-
const orderMap = /* @__PURE__ */ new Map();
|
|
66
|
-
for (const [index, id] of newOrderIds.entries()) orderMap.set(id, index);
|
|
67
|
-
for (const tool of tools) {
|
|
68
|
-
const next = orderMap.get(tool.id);
|
|
69
|
-
if (next !== void 0) tool.order = next;
|
|
70
|
-
}
|
|
71
|
-
return tools;
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Stamp `lastUsedAt` on the targeted tool (clones the array). Returns
|
|
75
|
-
* a new array; the input is left untouched.
|
|
76
|
-
*/
|
|
77
|
-
function touchLastUsedAt(tools, id, when = /* @__PURE__ */ new Date()) {
|
|
78
|
-
return tools.map((tool) => tool.id === id ? {
|
|
79
|
-
...tool,
|
|
80
|
-
lastUsedAt: when.toISOString()
|
|
81
|
-
} : tool);
|
|
82
|
-
}
|
|
83
|
-
//#endregion
|
|
84
|
-
//#region src/toolbox/types.ts
|
|
85
|
-
/** Schema version of the on-disk toolbox JSON files. Bumped on breaking changes. */
|
|
86
|
-
const TOOLBOX_JSON_SCHEMA_VERSION = 1;
|
|
87
|
-
/** Built-in tools rendered when the JSON file is missing or empty. */
|
|
88
|
-
const BUILTIN_DEFAULT_TOOLS = [
|
|
89
|
-
{
|
|
90
|
-
id: "builtin-docs",
|
|
91
|
-
name: "SERVICEME Docs",
|
|
92
|
-
description: "Official documentation portal",
|
|
93
|
-
icon: "book",
|
|
94
|
-
url: "https://docs.medalsoft.com/serviceme"
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
id: "builtin-issues",
|
|
98
|
-
name: "Issue Tracker",
|
|
99
|
-
description: "Report bugs and feature requests",
|
|
100
|
-
icon: "bug",
|
|
101
|
-
url: "https://github.com/medalsoftchina/ms-devtools-vscode/issues"
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
id: "builtin-changelog",
|
|
105
|
-
name: "Changelog",
|
|
106
|
-
description: "Release notes for every published version",
|
|
107
|
-
icon: "history",
|
|
108
|
-
url: "https://github.com/medalsoftchina/ms-devtools-vscode/releases"
|
|
109
|
-
}
|
|
110
|
-
];
|
|
111
|
-
//#endregion
|
|
112
|
-
//#region src/toolbox/ToolboxStore.ts
|
|
113
|
-
/**
|
|
114
|
-
* ToolboxStore — JSON persistence for the toolbox entries.
|
|
115
|
-
*
|
|
116
|
-
* Two scopes:
|
|
117
|
-
* - `user` → `~/.serviceme/toolbox.json`
|
|
118
|
-
* - `workspace` → `<cwd>/.github/.serviceme-toolbox.json`
|
|
119
|
-
*
|
|
120
|
-
* Both files share the `PersistedToolbox` shape and the same atomic
|
|
121
|
-
* write pattern (tmp + rename + fsync) as `IdentityStore`. Concurrent
|
|
122
|
-
* writers are serialized via a mkdir-based file lock (Phase 6+ may
|
|
123
|
-
* upgrade to `proper-lockfile`).
|
|
124
|
-
*
|
|
125
|
-
* Refs:
|
|
126
|
-
* - 4.功能规划.md §2.3 — `ToolboxStore.ts JSON 持久化(user + workspace scope)`
|
|
127
|
-
* - `3.功能拆分.md` §3 — toolbox wire shape
|
|
128
|
-
*/
|
|
129
|
-
const FILE_MODE = 384;
|
|
130
|
-
const LOCK_DIR_MODE = 448;
|
|
131
|
-
const DEFAULT_LOCK_TIMEOUT_MS = 5e3;
|
|
132
|
-
const DEFAULT_LOCK_RETRY_MS = 25;
|
|
133
|
-
const LOCK_STALE_GRACE_MS = 200;
|
|
134
|
-
const TMP_SUFFIX = ".tmp";
|
|
135
|
-
const WORKSPACE_TOOLBOX_RELATIVE_PATH = node_path.join(".github", ".serviceme-toolbox.json");
|
|
136
|
-
const LEGACY_WORKSPACE_TOOLBOX_FILENAME = ".ms-devtools-toolbox.json";
|
|
137
|
-
/**
|
|
138
|
-
* One-time migration: the workspace-scope toolbox file used to be named
|
|
139
|
-
* `.ms-devtools-toolbox.json`. If the new `.serviceme-toolbox.json` doesn't
|
|
140
|
-
* exist yet but the legacy file does (in the same directory), rename it
|
|
141
|
-
* forward so existing toolbox entries aren't silently lost.
|
|
142
|
-
*/
|
|
143
|
-
async function migrateLegacyWorkspaceToolboxFile(filePath) {
|
|
144
|
-
if (!filePath) return;
|
|
145
|
-
const legacyPath = node_path.join(node_path.dirname(filePath), LEGACY_WORKSPACE_TOOLBOX_FILENAME);
|
|
146
|
-
if (legacyPath === filePath) return;
|
|
147
|
-
try {
|
|
148
|
-
await node_fs_promises.access(filePath);
|
|
149
|
-
return;
|
|
150
|
-
} catch {}
|
|
151
|
-
try {
|
|
152
|
-
await node_fs_promises.rename(legacyPath, filePath);
|
|
153
|
-
} catch {}
|
|
154
|
-
}
|
|
155
|
-
var FsToolboxFileBackend = class {
|
|
156
|
-
constructor() {
|
|
157
|
-
this.maxBackupCount = 5;
|
|
158
|
-
}
|
|
159
|
-
async exists(filePath) {
|
|
160
|
-
try {
|
|
161
|
-
await node_fs_promises.access(filePath);
|
|
162
|
-
return true;
|
|
163
|
-
} catch {
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async read(filePath) {
|
|
168
|
-
let buf;
|
|
169
|
-
try {
|
|
170
|
-
buf = await node_fs_promises.readFile(filePath, "utf8");
|
|
171
|
-
} catch (err) {
|
|
172
|
-
if (isNodeError(err) && err.code === "ENOENT") return null;
|
|
173
|
-
throw err;
|
|
174
|
-
}
|
|
175
|
-
try {
|
|
176
|
-
return coercePersistedToolbox(JSON.parse(buf));
|
|
177
|
-
} catch {
|
|
178
|
-
await this.backupCorruptedFile(filePath);
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
async backupCorruptedFile(filePath) {
|
|
183
|
-
try {
|
|
184
|
-
const backupPath = `${filePath}.corrupted.${Date.now()}.bak`;
|
|
185
|
-
await node_fs_promises.copyFile(filePath, backupPath);
|
|
186
|
-
await this.purgeExcessBackups(filePath);
|
|
187
|
-
} catch {}
|
|
188
|
-
}
|
|
189
|
-
async purgeExcessBackups(filePath) {
|
|
190
|
-
const dir = node_path.dirname(filePath);
|
|
191
|
-
const base = node_path.basename(filePath);
|
|
192
|
-
let entries;
|
|
193
|
-
try {
|
|
194
|
-
entries = await node_fs_promises.readdir(dir);
|
|
195
|
-
} catch {
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const backups = entries.filter((n) => n.startsWith(base) && n.endsWith(".bak")).map((n) => ({
|
|
199
|
-
name: n,
|
|
200
|
-
filePath: node_path.join(dir, n)
|
|
201
|
-
})).sort((a, b) => {
|
|
202
|
-
return a.name.localeCompare(b.name);
|
|
203
|
-
});
|
|
204
|
-
const excess = backups.length - this.maxBackupCount;
|
|
205
|
-
if (excess <= 0) return;
|
|
206
|
-
await Promise.all(backups.slice(0, excess).map((b) => node_fs_promises.rm(b.filePath).catch(() => void 0)));
|
|
207
|
-
}
|
|
208
|
-
async write(filePath, payload) {
|
|
209
|
-
await node_fs_promises.mkdir(node_path.dirname(filePath), { recursive: true });
|
|
210
|
-
const tmpPath = `${filePath}${TMP_SUFFIX}`;
|
|
211
|
-
const bytes = Buffer.from(JSON.stringify(payload, null, " "), "utf8");
|
|
212
|
-
await node_fs_promises.rm(tmpPath, { force: true });
|
|
213
|
-
const handle = await node_fs_promises.open(tmpPath, "w", FILE_MODE);
|
|
214
|
-
try {
|
|
215
|
-
await handle.writeFile(bytes);
|
|
216
|
-
await handle.sync();
|
|
217
|
-
} finally {
|
|
218
|
-
await handle.close();
|
|
219
|
-
}
|
|
220
|
-
await node_fs_promises.rename(tmpPath, filePath);
|
|
221
|
-
await node_fs_promises.chmod(filePath, FILE_MODE).catch(() => void 0);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
function coercePersistedToolbox(parsed) {
|
|
225
|
-
if (typeof parsed !== "object" || parsed === null) throw new Error("toolbox.json: top-level must be an object");
|
|
226
|
-
const obj = parsed;
|
|
227
|
-
const version = obj.version;
|
|
228
|
-
if (version !== 1) throw new Error(`toolbox.json: unsupported schema version ${String(version)}`);
|
|
229
|
-
if (!Array.isArray(obj.tools)) throw new Error("toolbox.json: 'tools' must be an array");
|
|
230
|
-
return {
|
|
231
|
-
version,
|
|
232
|
-
tools: obj.tools
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
function isNodeError(value) {
|
|
236
|
-
return value instanceof Error && typeof value.code === "string";
|
|
237
|
-
}
|
|
238
|
-
function isProcessAlive(pid) {
|
|
239
|
-
try {
|
|
240
|
-
process.kill(pid, 0);
|
|
241
|
-
return true;
|
|
242
|
-
} catch {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
var ToolboxFileLock = class {
|
|
247
|
-
constructor(filePath, timeoutMs, retryMs) {
|
|
248
|
-
this.acquired = false;
|
|
249
|
-
this.dirPath = `${filePath}.lock`;
|
|
250
|
-
this.pidFilePath = node_path.join(this.dirPath, "pid");
|
|
251
|
-
this.timeoutMs = timeoutMs;
|
|
252
|
-
this.retryMs = retryMs;
|
|
253
|
-
}
|
|
254
|
-
async acquire() {
|
|
255
|
-
const start = Date.now();
|
|
256
|
-
while (true) try {
|
|
257
|
-
await node_fs_promises.mkdir(this.dirPath, { mode: LOCK_DIR_MODE });
|
|
258
|
-
await node_fs_promises.writeFile(this.pidFilePath, String(process.pid), "utf8").catch(() => void 0);
|
|
259
|
-
this.acquired = true;
|
|
260
|
-
return;
|
|
261
|
-
} catch (err) {
|
|
262
|
-
if (!isNodeError(err) || err.code !== "EEXIST") throw err;
|
|
263
|
-
if (await this.isStaleLock()) {
|
|
264
|
-
await node_fs_promises.rm(this.dirPath, {
|
|
265
|
-
recursive: true,
|
|
266
|
-
force: true
|
|
267
|
-
});
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
if (Date.now() - start >= this.timeoutMs) throw new Error(`ToolboxStore lock acquisition timed out for ${this.dirPath}`);
|
|
271
|
-
await (0, node_timers_promises.setTimeout)(this.retryMs);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
async isStaleLock() {
|
|
275
|
-
let pidStr;
|
|
276
|
-
try {
|
|
277
|
-
pidStr = await node_fs_promises.readFile(this.pidFilePath, "utf8");
|
|
278
|
-
} catch {
|
|
279
|
-
try {
|
|
280
|
-
const stat = await node_fs_promises.stat(this.dirPath);
|
|
281
|
-
return Date.now() - stat.mtimeMs > LOCK_STALE_GRACE_MS;
|
|
282
|
-
} catch {
|
|
283
|
-
return false;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
const pid = Number.parseInt(pidStr.trim(), 10);
|
|
287
|
-
if (!Number.isFinite(pid) || pid <= 0) return true;
|
|
288
|
-
return !isProcessAlive(pid);
|
|
289
|
-
}
|
|
290
|
-
async release() {
|
|
291
|
-
if (!this.acquired) return;
|
|
292
|
-
this.acquired = false;
|
|
293
|
-
await node_fs_promises.rm(this.dirPath, {
|
|
294
|
-
recursive: true,
|
|
295
|
-
force: true
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
};
|
|
299
|
-
function defaultWorkspacePath() {
|
|
300
|
-
if (process.env.SERVICEME_NO_WORKSPACE_TOOLBOX === "1") return null;
|
|
301
|
-
return node_path.join(process.cwd(), WORKSPACE_TOOLBOX_RELATIVE_PATH);
|
|
302
|
-
}
|
|
303
|
-
var ToolboxStore = class {
|
|
304
|
-
constructor(opts = {}) {
|
|
305
|
-
this.userFilePath = opts.userFilePath ?? require_userHome.getToolboxJsonPath();
|
|
306
|
-
this.resolveWorkspacePath = opts.resolveWorkspacePath ?? defaultWorkspacePath;
|
|
307
|
-
this.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;
|
|
308
|
-
this.hooks = opts.hooks ?? {};
|
|
309
|
-
this.backend = opts.backend ?? new FsToolboxFileBackend();
|
|
310
|
-
this.lockTimeoutMs = opts.lockTimeoutMs ?? DEFAULT_LOCK_TIMEOUT_MS;
|
|
311
|
-
this.lockRetryMs = opts.lockRetryMs ?? DEFAULT_LOCK_RETRY_MS;
|
|
312
|
-
}
|
|
313
|
-
/** Read a scope; returns the resolved toolbox (with defaults merged when empty). */
|
|
314
|
-
async read(scope) {
|
|
315
|
-
const filePath = this.filePathFor(scope);
|
|
316
|
-
if (scope === "workspace") await migrateLegacyWorkspaceToolboxFile(filePath);
|
|
317
|
-
const storedTools = (filePath ? await this.backend.read(filePath) : null)?.tools ?? [];
|
|
318
|
-
const merged = mergeWithDefaults(this.defaults.map((seed, index) => toDefaultTool(seed, index)), storedTools);
|
|
319
|
-
storedTools.filter((t) => !this.defaults.some((d) => d.id === t.id));
|
|
320
|
-
return {
|
|
321
|
-
scope,
|
|
322
|
-
tools: merged
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
/**
|
|
326
|
-
* Atomic write under a file lock. Replaces the entire `tools` array
|
|
327
|
-
* with the provided snapshot.
|
|
328
|
-
*/
|
|
329
|
-
async write(scope, tools) {
|
|
330
|
-
const filePath = this.filePathFor(scope);
|
|
331
|
-
if (!filePath) throw new Error(`Cannot write toolbox scope '${scope}': file path is unavailable`);
|
|
332
|
-
if (scope === "workspace") await migrateLegacyWorkspaceToolboxFile(filePath);
|
|
333
|
-
const payload = {
|
|
334
|
-
version: 1,
|
|
335
|
-
tools: sortByUserOrder([...tools])
|
|
336
|
-
};
|
|
337
|
-
await this.hooks.beforeWrite?.(scope, payload);
|
|
338
|
-
const lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);
|
|
339
|
-
await lock.acquire();
|
|
340
|
-
try {
|
|
341
|
-
await this.backend.write(filePath, payload);
|
|
342
|
-
} finally {
|
|
343
|
-
await lock.release();
|
|
344
|
-
}
|
|
345
|
-
await this.hooks.afterWrite?.(scope, payload);
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Read-modify-write under the file lock. The mutator receives the
|
|
349
|
-
* current user-only list (defaults not included) and returns the
|
|
350
|
-
* replacement list. Throwing inside the mutator aborts the write.
|
|
351
|
-
*/
|
|
352
|
-
async mutate(scope, mutator) {
|
|
353
|
-
const filePath = this.filePathFor(scope);
|
|
354
|
-
if (!filePath) throw new Error(`Cannot mutate toolbox scope '${scope}': file path is unavailable`);
|
|
355
|
-
if (scope === "workspace") await migrateLegacyWorkspaceToolboxFile(filePath);
|
|
356
|
-
const lock = new ToolboxFileLock(filePath, this.lockTimeoutMs, this.lockRetryMs);
|
|
357
|
-
await lock.acquire();
|
|
358
|
-
try {
|
|
359
|
-
const storedTools = (await this.backend.read(filePath))?.tools ?? [];
|
|
360
|
-
const defaultIds = new Set(this.defaults.map((d) => d.id));
|
|
361
|
-
const next = await mutator(storedTools.filter((t) => !defaultIds.has(t.id)));
|
|
362
|
-
const payload = {
|
|
363
|
-
version: 1,
|
|
364
|
-
tools: sortByUserOrder([...next])
|
|
365
|
-
};
|
|
366
|
-
await this.hooks.beforeWrite?.(scope, payload);
|
|
367
|
-
await this.backend.write(filePath, payload);
|
|
368
|
-
await this.hooks.afterWrite?.(scope, payload);
|
|
369
|
-
return next;
|
|
370
|
-
} finally {
|
|
371
|
-
await lock.release();
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
/** Wipe a scope entirely (used by `toolbox.remove --all` extensions). */
|
|
375
|
-
async clear(scope) {
|
|
376
|
-
const filePath = this.filePathFor(scope);
|
|
377
|
-
if (!filePath) return;
|
|
378
|
-
await node_fs_promises.rm(filePath, { force: true });
|
|
379
|
-
}
|
|
380
|
-
/** Test seam — resolve the user-scope file path. */
|
|
381
|
-
getUserFilePath() {
|
|
382
|
-
return this.userFilePath;
|
|
383
|
-
}
|
|
384
|
-
/** Test seam — resolve the workspace-scope file path (or null when disabled). */
|
|
385
|
-
getWorkspaceFilePath() {
|
|
386
|
-
return this.resolveWorkspacePath();
|
|
387
|
-
}
|
|
388
|
-
filePathFor(scope) {
|
|
389
|
-
return scope === "user" ? this.userFilePath : this.resolveWorkspacePath();
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
function toDefaultTool(seed, order) {
|
|
393
|
-
return {
|
|
394
|
-
id: seed.id,
|
|
395
|
-
name: seed.name,
|
|
396
|
-
description: seed.description,
|
|
397
|
-
icon: seed.icon,
|
|
398
|
-
url: seed.url,
|
|
399
|
-
isDefault: true,
|
|
400
|
-
order,
|
|
401
|
-
scope: "user"
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
//#endregion
|
|
405
|
-
//#region src/toolbox/ToolboxCore.ts
|
|
406
|
-
/** Sentinel — caller tried to remove a built-in (immutable) tool. */
|
|
407
|
-
var DefaultToolImmutableError = class extends Error {
|
|
408
|
-
constructor(toolId) {
|
|
409
|
-
super(`Cannot remove default toolbox entry: ${toolId}`);
|
|
410
|
-
this.toolId = toolId;
|
|
411
|
-
this.name = "DefaultToolImmutableError";
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
var ToolboxCore = class {
|
|
415
|
-
constructor(opts = {}) {
|
|
416
|
-
this.store = opts.store ?? new ToolboxStore();
|
|
417
|
-
this.defaults = opts.defaultTools ?? BUILTIN_DEFAULT_TOOLS;
|
|
418
|
-
this.listSort = opts.listSort ?? sortByUserOrder;
|
|
419
|
-
}
|
|
420
|
-
/** List all tools in a scope. Built-in defaults are merged in. */
|
|
421
|
-
async list(scope = "user") {
|
|
422
|
-
const resolved = await this.store.read(scope);
|
|
423
|
-
return {
|
|
424
|
-
scope,
|
|
425
|
-
tools: this.listSort(resolved.tools)
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
/**
|
|
429
|
-
* Append a new tool to the requested scope. Default tools are
|
|
430
|
-
* rejected (they are seeds, not user entries).
|
|
431
|
-
*/
|
|
432
|
-
async add(tool, scope = "user") {
|
|
433
|
-
if (this.isDefaultId(tool.id)) throw new DefaultToolImmutableError(tool.id);
|
|
434
|
-
return {
|
|
435
|
-
scope,
|
|
436
|
-
tools: await this.store.mutate(scope, async (current) => {
|
|
437
|
-
const filtered = current.filter((t) => t.id !== tool.id);
|
|
438
|
-
return [...filtered, {
|
|
439
|
-
...tool,
|
|
440
|
-
scope,
|
|
441
|
-
isDefault: false,
|
|
442
|
-
order: tool.order ?? filtered.length
|
|
443
|
-
}];
|
|
444
|
-
})
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Remove a tool by id. Returns `success: false` when the id is a
|
|
449
|
-
* built-in default (idempotent, never throws on missing entries).
|
|
450
|
-
*/
|
|
451
|
-
async remove(id, scope = "user") {
|
|
452
|
-
if (this.isDefaultId(id)) throw new DefaultToolImmutableError(id);
|
|
453
|
-
return {
|
|
454
|
-
scope,
|
|
455
|
-
toolId: id,
|
|
456
|
-
success: !(await this.store.mutate(scope, async (current) => current.filter((t) => t.id !== id))).some((t) => t.id === id)
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
/**
|
|
460
|
-
* Patch a tool by id. Default tools can only have their `order`
|
|
461
|
-
* updated; other patches are silently ignored for default entries
|
|
462
|
-
* (callers can compare before/after to detect the ignore).
|
|
463
|
-
*/
|
|
464
|
-
async update(id, patch, scope = "user") {
|
|
465
|
-
const isDefault = this.isDefaultId(id);
|
|
466
|
-
return {
|
|
467
|
-
scope,
|
|
468
|
-
tools: await this.store.mutate(scope, async (current) => {
|
|
469
|
-
if (isDefault) return current;
|
|
470
|
-
return current.map((tool) => tool.id === id ? {
|
|
471
|
-
...tool,
|
|
472
|
-
...patch,
|
|
473
|
-
scope,
|
|
474
|
-
isDefault: false
|
|
475
|
-
} : tool);
|
|
476
|
-
})
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
/**
|
|
480
|
-
* Stamp `lastUsedAt` on the targeted tool. This is the "recently
|
|
481
|
-
* used" hook the Extension's webview uses when a user clicks a
|
|
482
|
-
* toolbox entry.
|
|
483
|
-
*/
|
|
484
|
-
async recordUsage(id, scope = "user", when = /* @__PURE__ */ new Date()) {
|
|
485
|
-
if (this.isDefaultId(id)) {
|
|
486
|
-
const seed = this.defaults.find((d) => d.id === id);
|
|
487
|
-
if (!seed) return null;
|
|
488
|
-
return {
|
|
489
|
-
...seed,
|
|
490
|
-
scope,
|
|
491
|
-
isDefault: true,
|
|
492
|
-
order: 0,
|
|
493
|
-
lastUsedAt: when.toISOString()
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
let updated = null;
|
|
497
|
-
await this.store.mutate(scope, async (current) => {
|
|
498
|
-
const next = touchLastUsedAt(current, id, when);
|
|
499
|
-
updated = next.find((t) => t.id === id) ?? null;
|
|
500
|
-
return next;
|
|
501
|
-
});
|
|
502
|
-
return updated;
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Combined view: user + workspace scopes merged, sorted by recent
|
|
506
|
-
* usage. Workspace tools overlay user tools (workspace entries win
|
|
507
|
-
* on `id` collision).
|
|
508
|
-
*/
|
|
509
|
-
async listMerged() {
|
|
510
|
-
const [user, workspace] = await Promise.all([this.store.read("user"), this.store.read("workspace")]);
|
|
511
|
-
const seen = /* @__PURE__ */ new Set();
|
|
512
|
-
const merged = [];
|
|
513
|
-
for (const tool of workspace.tools) {
|
|
514
|
-
seen.add(tool.id);
|
|
515
|
-
merged.push({
|
|
516
|
-
...tool,
|
|
517
|
-
scope: "workspace"
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
for (const tool of user.tools) {
|
|
521
|
-
if (seen.has(tool.id)) continue;
|
|
522
|
-
merged.push({
|
|
523
|
-
...tool,
|
|
524
|
-
scope: "user"
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
return {
|
|
528
|
-
scope: "user",
|
|
529
|
-
tools: sortByRecentFirst(merged)
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
/** Expose the underlying store (CLI / Bridge use it for path-level access). */
|
|
533
|
-
getStore() {
|
|
534
|
-
return this.store;
|
|
535
|
-
}
|
|
536
|
-
isDefaultId(id) {
|
|
537
|
-
return this.defaults.some((d) => d.id === id);
|
|
538
|
-
}
|
|
539
|
-
};
|
|
540
|
-
//#endregion
|
|
541
|
-
Object.defineProperty(exports, "BUILTIN_DEFAULT_TOOLS", {
|
|
542
|
-
enumerable: true,
|
|
543
|
-
get: function() {
|
|
544
|
-
return BUILTIN_DEFAULT_TOOLS;
|
|
545
|
-
}
|
|
546
|
-
});
|
|
547
|
-
Object.defineProperty(exports, "DefaultToolImmutableError", {
|
|
548
|
-
enumerable: true,
|
|
549
|
-
get: function() {
|
|
550
|
-
return DefaultToolImmutableError;
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
Object.defineProperty(exports, "FsToolboxFileBackend", {
|
|
554
|
-
enumerable: true,
|
|
555
|
-
get: function() {
|
|
556
|
-
return FsToolboxFileBackend;
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
Object.defineProperty(exports, "TOOLBOX_JSON_SCHEMA_VERSION", {
|
|
560
|
-
enumerable: true,
|
|
561
|
-
get: function() {
|
|
562
|
-
return TOOLBOX_JSON_SCHEMA_VERSION;
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
|
-
Object.defineProperty(exports, "ToolboxCore", {
|
|
566
|
-
enumerable: true,
|
|
567
|
-
get: function() {
|
|
568
|
-
return ToolboxCore;
|
|
569
|
-
}
|
|
570
|
-
});
|
|
571
|
-
Object.defineProperty(exports, "ToolboxStore", {
|
|
572
|
-
enumerable: true,
|
|
573
|
-
get: function() {
|
|
574
|
-
return ToolboxStore;
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
Object.defineProperty(exports, "WORKSPACE_TOOLBOX_RELATIVE_PATH", {
|
|
578
|
-
enumerable: true,
|
|
579
|
-
get: function() {
|
|
580
|
-
return WORKSPACE_TOOLBOX_RELATIVE_PATH;
|
|
581
|
-
}
|
|
582
|
-
});
|
|
583
|
-
Object.defineProperty(exports, "mergeWithDefaults", {
|
|
584
|
-
enumerable: true,
|
|
585
|
-
get: function() {
|
|
586
|
-
return mergeWithDefaults;
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
Object.defineProperty(exports, "reindexOrder", {
|
|
590
|
-
enumerable: true,
|
|
591
|
-
get: function() {
|
|
592
|
-
return reindexOrder;
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
Object.defineProperty(exports, "sortByRecentFirst", {
|
|
596
|
-
enumerable: true,
|
|
597
|
-
get: function() {
|
|
598
|
-
return sortByRecentFirst;
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
Object.defineProperty(exports, "sortByUserOrder", {
|
|
602
|
-
enumerable: true,
|
|
603
|
-
get: function() {
|
|
604
|
-
return sortByUserOrder;
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
Object.defineProperty(exports, "touchLastUsedAt", {
|
|
608
|
-
enumerable: true,
|
|
609
|
-
get: function() {
|
|
610
|
-
return touchLastUsedAt;
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
//# sourceMappingURL=toolbox-BV7vLGXZ.js.map
|