@vesk/adapter 0.2.10 → 0.2.12
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/client-bundle.d.ts +29 -0
- package/dist/client-bundle.d.ts.map +1 -1
- package/dist/client-bundle.js +333 -52
- package/dist/dev-api.d.ts +78 -0
- package/dist/dev-api.d.ts.map +1 -0
- package/dist/dev-api.js +338 -0
- package/dist/dev-config.d.ts +48 -0
- package/dist/dev-config.d.ts.map +1 -0
- package/dist/dev-config.js +964 -0
- package/dist/dev-server.d.ts +88 -0
- package/dist/dev-server.d.ts.map +1 -1
- package/dist/dev-server.js +304 -4
- package/dist/error-codeframe.d.ts +23 -0
- package/dist/error-codeframe.d.ts.map +1 -0
- package/dist/error-codeframe.js +127 -0
- package/dist/error-tips.d.ts +7 -0
- package/dist/error-tips.d.ts.map +1 -0
- package/dist/error-tips.js +91 -0
- package/dist/hmr-utils.d.ts +14 -0
- package/dist/hmr-utils.d.ts.map +1 -0
- package/dist/hmr-utils.js +56 -0
- package/dist/hmr.d.ts +40 -0
- package/dist/hmr.d.ts.map +1 -1
- package/dist/hmr.js +139 -20
- package/dist/index.d.ts +37 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +105 -22
- package/dist/plugins.d.ts +147 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +1109 -0
- package/dist/prod-server.d.ts.map +1 -1
- package/dist/prod-server.js +14 -2
- package/package.json +4 -4
package/dist/dev-api.js
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevTools API router — `createDevApiRouter` (B2 of plans/devtools.md).
|
|
3
|
+
*
|
|
4
|
+
* A self-contained, dependency-injectable router for ALL dev-panel HTTP
|
|
5
|
+
* endpoints under `/__vesk/*`. It generalizes the earlier plugin-only router
|
|
6
|
+
* into a capability-scoped surface:
|
|
7
|
+
*
|
|
8
|
+
* config GET/POST /__vesk/config
|
|
9
|
+
* plugins GET /__vesk/plugins + POST activate/deactivate/install/
|
|
10
|
+
* uninstall/update + GET search/icon/exports
|
|
11
|
+
* diagnostics GET /__vesk/diagnostics
|
|
12
|
+
* build POST /__vesk/build
|
|
13
|
+
* file.read GET /__vesk/file?path=
|
|
14
|
+
* command POST /__vesk/command
|
|
15
|
+
*
|
|
16
|
+
* ARCHITECTURE: the Dev Server is the ONLY path from browser → project files /
|
|
17
|
+
* build system. Every endpoint is gated by a capability in `CapabilityTable`
|
|
18
|
+
* (server-enforced — the browser cannot bypass it). There is NO raw
|
|
19
|
+
* `child_process` reach: commands route through the gated, allowlisted
|
|
20
|
+
* `runCommand` hook, and file access is read-only + containment-checked.
|
|
21
|
+
*
|
|
22
|
+
* Pure (fake injectable inputs, no socket/listener), mirroring the shape of
|
|
23
|
+
* `createPluginStateRouter`: returns `{ route(method, pathname, body, search) }`,
|
|
24
|
+
* yielding `null` for non-`/__vesk/*` paths so the dev server falls through.
|
|
25
|
+
*/
|
|
26
|
+
import { readFileSync, statSync, existsSync, readdirSync } from 'node:fs';
|
|
27
|
+
import { resolve } from 'node:path';
|
|
28
|
+
import { getPluginRecords, setPluginActive, installPlugin, uninstallPlugin, updatePlugin, enrichPluginRecords, searchPlugins, introspectPlugin, findPluginIcon, } from './plugins';
|
|
29
|
+
import { readConfig, writeConfigSource, applyConfigToggle, findConfigFile, addPluginToConfig, removePluginFromConfig } from './dev-config';
|
|
30
|
+
import { resolveWithin } from './paths';
|
|
31
|
+
/** Default DevTools capability set — `command` is gated off by default. */
|
|
32
|
+
export const DEFAULT_CAPABILITIES = {
|
|
33
|
+
'config.read': true,
|
|
34
|
+
'config.write': true,
|
|
35
|
+
'plugins': true,
|
|
36
|
+
'diagnostics': true,
|
|
37
|
+
'build': true,
|
|
38
|
+
'file.read': true,
|
|
39
|
+
'command': false,
|
|
40
|
+
};
|
|
41
|
+
/** Gated-command allowlist (read-only/status commands only by default). */
|
|
42
|
+
export const DEFAULT_COMMAND_ALLOWLIST = [
|
|
43
|
+
/^node -v$/,
|
|
44
|
+
/^npm -v$/,
|
|
45
|
+
/^git status/,
|
|
46
|
+
/^git log/,
|
|
47
|
+
/^git branch/,
|
|
48
|
+
/^pwd$/,
|
|
49
|
+
/^ls($|\s)/,
|
|
50
|
+
/^cat($|\s)/,
|
|
51
|
+
/^head($|\s)/,
|
|
52
|
+
];
|
|
53
|
+
/** Server-enforced capability/permission table. */
|
|
54
|
+
export class CapabilityTable {
|
|
55
|
+
caps;
|
|
56
|
+
commandAllowlist;
|
|
57
|
+
constructor(caps, commandAllowlist = DEFAULT_COMMAND_ALLOWLIST) {
|
|
58
|
+
this.caps = { ...DEFAULT_CAPABILITIES, ...(caps || {}) };
|
|
59
|
+
this.commandAllowlist = commandAllowlist;
|
|
60
|
+
}
|
|
61
|
+
allows(cap) {
|
|
62
|
+
return this.caps[cap] === true;
|
|
63
|
+
}
|
|
64
|
+
commandAllowed(argv) {
|
|
65
|
+
const joined = argv.join(' ');
|
|
66
|
+
return this.commandAllowlist.some((re) => re.test(joined));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function jsonStatus(status, data) {
|
|
70
|
+
return { status, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Build the unified DevTools API router. Exportable and dependency-injectable
|
|
74
|
+
* so the adapter + CLI dev servers can both route through it.
|
|
75
|
+
*/
|
|
76
|
+
export function createDevApiRouter(opts) {
|
|
77
|
+
const veskDir = opts.veskDir;
|
|
78
|
+
const caps = new CapabilityTable(opts.caps, opts.commandAllowlist);
|
|
79
|
+
const projectDir = opts.projectDir || resolve(opts.appDir, '..');
|
|
80
|
+
const getState = opts.getHmrState || (() => ({ status: 'up', lastCompileMs: null, error: null, hasError: false, componentCount: 0 }));
|
|
81
|
+
function denied(cap) {
|
|
82
|
+
return jsonStatus(403, { error: `capability denied: ${cap}` });
|
|
83
|
+
}
|
|
84
|
+
function badRequest(message) {
|
|
85
|
+
return jsonStatus(400, { error: message });
|
|
86
|
+
}
|
|
87
|
+
async function onChanged(event) {
|
|
88
|
+
if (typeof opts.onPluginChange === 'function')
|
|
89
|
+
await opts.onPluginChange(event);
|
|
90
|
+
}
|
|
91
|
+
async function route(method, pathname, body, search) {
|
|
92
|
+
// ── HMR state ──────────────────────────────────────────────────────────
|
|
93
|
+
if (pathname === '/__vesk/hmr/state') {
|
|
94
|
+
if (method !== 'GET' && method !== 'HEAD')
|
|
95
|
+
return jsonStatus(405, { error: 'method not allowed' });
|
|
96
|
+
return jsonStatus(200, getState());
|
|
97
|
+
}
|
|
98
|
+
// ── Config (B1) ────────────────────────────────────────────────────────
|
|
99
|
+
if (pathname === '/__vesk/config' && method === 'GET') {
|
|
100
|
+
if (!caps.allows('config.read'))
|
|
101
|
+
return denied('config.read');
|
|
102
|
+
try {
|
|
103
|
+
const cfg = await readConfig(projectDir);
|
|
104
|
+
return jsonStatus(200, { path: cfg.path, exists: cfg.exists, source: cfg.source, config: cfg.config });
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (pathname === '/__vesk/config' && method === 'POST') {
|
|
111
|
+
if (!caps.allows('config.write'))
|
|
112
|
+
return denied('config.write');
|
|
113
|
+
const b = (body || {});
|
|
114
|
+
try {
|
|
115
|
+
if (typeof b.source === 'string') {
|
|
116
|
+
const cfg = await writeConfigSource(projectDir, b.source);
|
|
117
|
+
return jsonStatus(200, { ok: true, path: cfg.path, source: cfg.source, config: cfg.config });
|
|
118
|
+
}
|
|
119
|
+
if (typeof b.key === 'string' && b.key) {
|
|
120
|
+
const { path, isTs } = findConfigFile(projectDir);
|
|
121
|
+
if (!path)
|
|
122
|
+
return badRequest('no vesk.config file to edit');
|
|
123
|
+
if (!isTs)
|
|
124
|
+
return badRequest('single-key toggle edits only apply to vesk.config.ts (use the source editor for .js)');
|
|
125
|
+
const patched = applyConfigToggle(readFileSync(path, 'utf-8'), b.key, b.value);
|
|
126
|
+
if (patched === null)
|
|
127
|
+
return badRequest('could not safely edit the config object; use the source editor');
|
|
128
|
+
const cfg = await writeConfigSource(projectDir, patched);
|
|
129
|
+
return jsonStatus(200, { ok: true, path: cfg.path, source: cfg.source, config: cfg.config });
|
|
130
|
+
}
|
|
131
|
+
return badRequest('expected { source } or { key, value }');
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
return jsonStatus(400, { error: e instanceof Error ? e.message : String(e) });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// ── Diagnostics (B3) ───────────────────────────────────────────────────
|
|
138
|
+
if (pathname === '/__vesk/diagnostics' && method === 'GET') {
|
|
139
|
+
if (!caps.allows('diagnostics'))
|
|
140
|
+
return denied('diagnostics');
|
|
141
|
+
const list = typeof opts.getDiagnostics === 'function' ? opts.getDiagnostics() : [];
|
|
142
|
+
return jsonStatus(200, { diagnostics: list });
|
|
143
|
+
}
|
|
144
|
+
// ── Build ──────────────────────────────────────────────────────────────
|
|
145
|
+
if (pathname === '/__vesk/build' && method === 'POST') {
|
|
146
|
+
if (!caps.allows('build'))
|
|
147
|
+
return denied('build');
|
|
148
|
+
if (typeof opts.rebuild !== 'function')
|
|
149
|
+
return jsonStatus(503, { error: 'build hook unavailable' });
|
|
150
|
+
try {
|
|
151
|
+
const result = await opts.rebuild();
|
|
152
|
+
return jsonStatus(200, { ok: result.ok, error: result.error || null, ms: result.ms ?? null });
|
|
153
|
+
}
|
|
154
|
+
catch (e) {
|
|
155
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ── Read-only file access (B2) ─────────────────────────────────────────
|
|
159
|
+
if (pathname === '/__vesk/file' && method === 'GET') {
|
|
160
|
+
if (!caps.allows('file.read'))
|
|
161
|
+
return denied('file.read');
|
|
162
|
+
const target = new URLSearchParams(search ?? '').get('path') || '';
|
|
163
|
+
if (!target)
|
|
164
|
+
return badRequest('missing "path" query param');
|
|
165
|
+
try {
|
|
166
|
+
const resolved = resolveWithin(projectDir, target);
|
|
167
|
+
if (!resolved)
|
|
168
|
+
return jsonStatus(403, { error: 'path escapes project root' });
|
|
169
|
+
if (!existsSync(resolved))
|
|
170
|
+
return jsonStatus(404, { error: 'not found' });
|
|
171
|
+
if (statSync(resolved).isDirectory()) {
|
|
172
|
+
const entries = readdirSync(resolved, { withFileTypes: true }).map((e) => e.name);
|
|
173
|
+
return jsonStatus(200, { ok: true, path: target, directory: true, entries });
|
|
174
|
+
}
|
|
175
|
+
if (statSync(resolved).isFile()) {
|
|
176
|
+
return jsonStatus(200, { ok: true, path: target, directory: false, content: readFileSync(resolved, 'utf-8') });
|
|
177
|
+
}
|
|
178
|
+
return badRequest('unsupported file type');
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ── Gated command runner (B2) ──────────────────────────────────────────
|
|
185
|
+
if (pathname === '/__vesk/command' && method === 'POST') {
|
|
186
|
+
if (!caps.allows('command'))
|
|
187
|
+
return denied('command');
|
|
188
|
+
const b = (body || {});
|
|
189
|
+
const argv = Array.isArray(b.argv) ? b.argv.filter((a) => typeof a === 'string') : [];
|
|
190
|
+
if (argv.length === 0)
|
|
191
|
+
return badRequest('expected { argv: string[] }');
|
|
192
|
+
if (!caps.commandAllowed(argv))
|
|
193
|
+
return jsonStatus(403, { error: 'command not in allowlist' });
|
|
194
|
+
if (typeof opts.runCommand !== 'function')
|
|
195
|
+
return jsonStatus(503, { error: 'command runner unavailable' });
|
|
196
|
+
try {
|
|
197
|
+
const result = await opts.runCommand(argv);
|
|
198
|
+
return jsonStatus(200, { ok: result.ok, code: result.code, stdout: result.stdout, stderr: result.stderr });
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// ── Plugins (existing surface, capability-gated) ───────────────────────
|
|
205
|
+
if (pathname === '/__vesk/plugins' && method === 'GET') {
|
|
206
|
+
if (!caps.allows('plugins'))
|
|
207
|
+
return denied('plugins');
|
|
208
|
+
try {
|
|
209
|
+
const records = await enrichPluginRecords(getPluginRecords(opts.appDir, veskDir, opts.configPluginNames));
|
|
210
|
+
return jsonStatus(200, { plugins: records });
|
|
211
|
+
}
|
|
212
|
+
catch (e) {
|
|
213
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (pathname === '/__vesk/plugins/activate' && method === 'POST') {
|
|
217
|
+
if (!caps.allows('plugins'))
|
|
218
|
+
return denied('plugins');
|
|
219
|
+
const name = body?.name;
|
|
220
|
+
if (typeof name !== 'string' || !name)
|
|
221
|
+
return badRequest('missing "name" in body');
|
|
222
|
+
try {
|
|
223
|
+
setPluginActive(veskDir, name, true);
|
|
224
|
+
const record = getPluginRecords(opts.appDir, veskDir, opts.configPluginNames).find((r) => r.name === name);
|
|
225
|
+
await onChanged({ type: 'activate', name });
|
|
226
|
+
return jsonStatus(200, { ok: true, record: record || null });
|
|
227
|
+
}
|
|
228
|
+
catch (e) {
|
|
229
|
+
return jsonStatus(400, { error: e instanceof Error ? e.message : String(e) });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (pathname === '/__vesk/plugins/deactivate' && method === 'POST') {
|
|
233
|
+
if (!caps.allows('plugins'))
|
|
234
|
+
return denied('plugins');
|
|
235
|
+
const name = body?.name;
|
|
236
|
+
if (typeof name !== 'string' || !name)
|
|
237
|
+
return badRequest('missing "name" in body');
|
|
238
|
+
try {
|
|
239
|
+
setPluginActive(veskDir, name, false);
|
|
240
|
+
const record = getPluginRecords(opts.appDir, veskDir, opts.configPluginNames).find((r) => r.name === name);
|
|
241
|
+
await onChanged({ type: 'deactivate', name });
|
|
242
|
+
return jsonStatus(200, { ok: true, record: record || null });
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
return jsonStatus(400, { error: e instanceof Error ? e.message : String(e) });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (pathname === '/__vesk/plugins/install' && method === 'POST') {
|
|
249
|
+
if (!caps.allows('plugins'))
|
|
250
|
+
return denied('plugins');
|
|
251
|
+
const pkg = body?.package;
|
|
252
|
+
if (typeof pkg !== 'string' || !pkg)
|
|
253
|
+
return badRequest('missing "package" in body');
|
|
254
|
+
try {
|
|
255
|
+
const record = await installPlugin(opts.appDir, veskDir, pkg);
|
|
256
|
+
// auto-register in vesk.config.ts (best-effort, does not fail install)
|
|
257
|
+
try {
|
|
258
|
+
await addPluginToConfig(projectDir, pkg);
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
await onChanged({ type: 'install', name: record.name });
|
|
262
|
+
return jsonStatus(200, { ok: true, record });
|
|
263
|
+
}
|
|
264
|
+
catch (e) {
|
|
265
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (pathname === '/__vesk/plugins/uninstall' && method === 'POST') {
|
|
269
|
+
if (!caps.allows('plugins'))
|
|
270
|
+
return denied('plugins');
|
|
271
|
+
const pkg = body?.package;
|
|
272
|
+
if (typeof pkg !== 'string' || !pkg)
|
|
273
|
+
return badRequest('missing "package" in body');
|
|
274
|
+
try {
|
|
275
|
+
await uninstallPlugin(opts.appDir, veskDir, pkg);
|
|
276
|
+
// auto-remove from vesk.config.ts (best-effort, surgical) + npm uninstall already done in uninstallPlugin
|
|
277
|
+
try {
|
|
278
|
+
await removePluginFromConfig(projectDir, pkg);
|
|
279
|
+
}
|
|
280
|
+
catch { }
|
|
281
|
+
await onChanged({ type: 'uninstall', name: pkg });
|
|
282
|
+
return jsonStatus(200, { ok: true });
|
|
283
|
+
}
|
|
284
|
+
catch (e) {
|
|
285
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (pathname === '/__vesk/plugins/update' && method === 'POST') {
|
|
289
|
+
if (!caps.allows('plugins'))
|
|
290
|
+
return denied('plugins');
|
|
291
|
+
const pkg = body?.package;
|
|
292
|
+
if (typeof pkg !== 'string' || !pkg)
|
|
293
|
+
return badRequest('missing "package" in body');
|
|
294
|
+
try {
|
|
295
|
+
const record = await updatePlugin(opts.appDir, veskDir, pkg);
|
|
296
|
+
await onChanged({ type: 'update', name: record.name });
|
|
297
|
+
return jsonStatus(200, { ok: true, record });
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
return jsonStatus(500, { error: e instanceof Error ? e.message : String(e) });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (pathname === '/__vesk/plugins/search' && method === 'GET') {
|
|
304
|
+
if (!caps.allows('plugins'))
|
|
305
|
+
return denied('plugins');
|
|
306
|
+
const q = new URLSearchParams(search ?? '').get('q') ?? '';
|
|
307
|
+
const results = await searchPlugins(q);
|
|
308
|
+
return jsonStatus(200, { results });
|
|
309
|
+
}
|
|
310
|
+
const perPlugin = /^\/__vesk\/plugins\/([^/]+)\/(icon|exports)$/.exec(pathname);
|
|
311
|
+
if (perPlugin) {
|
|
312
|
+
if (!caps.allows('plugins'))
|
|
313
|
+
return denied('plugins');
|
|
314
|
+
const pluginName = decodeURIComponent(perPlugin[1]);
|
|
315
|
+
if (perPlugin[2] === 'icon') {
|
|
316
|
+
if (method !== 'GET' && method !== 'HEAD')
|
|
317
|
+
return jsonStatus(405, { error: 'method not allowed' });
|
|
318
|
+
const icon = findPluginIcon(opts.appDir, pluginName);
|
|
319
|
+
if (!icon)
|
|
320
|
+
return jsonStatus(404, { error: `no icon for plugin "${pluginName}"` });
|
|
321
|
+
return {
|
|
322
|
+
status: 200,
|
|
323
|
+
headers: { 'Content-Type': icon.mime, 'Cache-Control': 'private, max-age=60' },
|
|
324
|
+
body: readFileSync(icon.file).toString('base64'),
|
|
325
|
+
encoding: 'base64',
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (method !== 'GET' && method !== 'HEAD')
|
|
329
|
+
return jsonStatus(405, { error: 'method not allowed' });
|
|
330
|
+
return jsonStatus(200, introspectPlugin(opts.appDir, pluginName));
|
|
331
|
+
}
|
|
332
|
+
if (pathname.startsWith('/__vesk/')) {
|
|
333
|
+
return jsonStatus(404, { error: 'Not found' });
|
|
334
|
+
}
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
return { route };
|
|
338
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { VeskConfig } from '@vesk/types';
|
|
2
|
+
export interface ConfigReadResult {
|
|
3
|
+
path: string | null;
|
|
4
|
+
exists: boolean;
|
|
5
|
+
source: string;
|
|
6
|
+
config: VeskConfig;
|
|
7
|
+
}
|
|
8
|
+
export interface ConfigFileInfo {
|
|
9
|
+
path: string | null;
|
|
10
|
+
isTs: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** Locate the project's config file (vesk.config.ts preferred, then .js). */
|
|
13
|
+
export declare function findConfigFile(projectDir: string): ConfigFileInfo;
|
|
14
|
+
/**
|
|
15
|
+
* Transpile+parse a config source into a validated VeskConfig. Shared by read
|
|
16
|
+
* and write paths so they agree on what "valid" means. Host code is executed
|
|
17
|
+
* in an isolated temp module so edits re-evaluate fresh on every call.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseConfigSource(source: string, isTs: boolean, projectDir?: string): Promise<VeskConfig>;
|
|
20
|
+
/** Read + parse the project config. Throws on an invalid config. */
|
|
21
|
+
export declare function readConfig(projectDir: string): Promise<ConfigReadResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Write a full new config source back, after validation. Guarantees an invalid
|
|
24
|
+
* config never clobbers the file (throws before writing).
|
|
25
|
+
*/
|
|
26
|
+
export declare function writeConfigSource(projectDir: string, source: string): Promise<ConfigReadResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Apply a single `key` → `value` toggle to a `vesk.config.ts` source by
|
|
29
|
+
* editing the object literal passed to `defineConfig(...)`, preserving all
|
|
30
|
+
* other formatting/comments. Returns the new source, or `null` when there is
|
|
31
|
+
* no safe literal-edit point (caller falls back to the direct editor).
|
|
32
|
+
*/
|
|
33
|
+
export declare function applyConfigToggle(source: string, key: string, value: unknown): string | null;
|
|
34
|
+
/** Derive a safe import identifier for a package spec. `importNameForPackage('@vesk/plugin-tailwind')` -> `tailwindcss` (known) else `myPlugin` etc. */
|
|
35
|
+
export declare function importNameForPackage(pkg: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Surgically add a plugin import + `plugins: [...]` entry to `vesk.config.ts`.
|
|
38
|
+
* Idempotent - no duplicate import/entry if already present. Validates via
|
|
39
|
+
* `parseConfigSource` before writing so an invalid file is never clobbered.
|
|
40
|
+
* Uses AST for all syntax analysis (no regex for import/plugins detection).
|
|
41
|
+
*/
|
|
42
|
+
export declare function addPluginToConfig(projectDir: string, pkg: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Surgically remove a plugin's import and its `plugins: [...]` entry.
|
|
45
|
+
* Returns true if file was changed. Uses AST for import/plugins detection.
|
|
46
|
+
*/
|
|
47
|
+
export declare function removePluginFromConfig(projectDir: string, pkg: string): Promise<boolean>;
|
|
48
|
+
//# sourceMappingURL=dev-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-config.d.ts","sourceRoot":"","sources":["../src/dev-config.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,CAMjE;AAQD;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAmC/G;AAED,oEAAoE;AACpE,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAM9E;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAQrG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAc5F;AAoID,wJAAwJ;AACxJ,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAcxD;AAyYD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA2EtF;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqC9F"}
|