amxx-builder 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +13 -3
- package/README.md +276 -20
- package/action.yml +1 -1
- package/defaults/amxbuild.defaults.yml +7 -1
- package/mcp/handlers.js +342 -152
- package/mcp/registry.js +264 -25
- package/package.json +1 -1
- package/skills/amxb-migration/SKILL.md +126 -27
- package/skills/amxx-pawn-style/SKILL.md +422 -0
- package/src/agent-assets.js +206 -0
- package/src/asset-fetcher.js +33 -48
- package/src/build-plan.js +22 -5
- package/src/build-service.js +54 -11
- package/src/cli.js +38 -4
- package/src/collector.js +9 -1
- package/src/commands/build.js +5 -4
- package/src/commands/dry-run.js +28 -1
- package/src/commands/init.js +125 -11
- package/src/commands/opencode-skills.js +47 -0
- package/src/commands/serve.js +309 -111
- package/src/commands/skills-dir.js +21 -0
- package/src/commands/watch.js +66 -23
- package/src/compile-utils.js +119 -4
- package/src/compiler-fetcher.js +196 -41
- package/src/compiler.js +68 -29
- package/src/dep-graph.js +27 -0
- package/src/deployer.js +39 -15
- package/src/deps-resolver.js +151 -11
- package/src/deps-tree.js +20 -9
- package/src/download.js +132 -0
- package/src/fs-utils.js +35 -1
- package/src/fungun-fetcher.js +28 -8
- package/src/github-api.js +32 -0
- package/src/include-tree.js +30 -61
- package/src/ini-builder.js +1 -1
- package/src/jsonrpc-transport.js +53 -5
- package/src/local-sources.js +363 -0
- package/src/manifest-path.js +18 -1
- package/src/manifest.js +398 -40
- package/src/opencode-skills.js +262 -0
- package/src/release-fetcher.js +26 -33
- package/src/repo-fetcher.js +388 -37
- package/src/retry.js +3 -1
- package/src/schema.js +1 -1
- package/templates/init-deploy.env +9 -0
- package/templates/init-gitignore +22 -0
- package/src/dep-docs.js +0 -115
package/src/commands/serve.js
CHANGED
|
@@ -16,8 +16,11 @@
|
|
|
16
16
|
* Method table:
|
|
17
17
|
* manifest.validate → validate.manifestFile
|
|
18
18
|
* manifest.resolve → env.loadEnv + manifest.resolveManifest
|
|
19
|
+
* manifest.dep → agent-assets readDepManifest + parseDocEntries/parseSkillEntries
|
|
20
|
+
* docs.list / docs.get → agent-assets collectDepAssets / collectLocalAssets
|
|
21
|
+
* skills.list / skills.get → agent-assets collectDepAssets / collectLocalAssets
|
|
19
22
|
* include.resolve → include-tree parseIncludeDirective + searchIncludeFile
|
|
20
|
-
* include.list →
|
|
23
|
+
* include.list → deps-resolver collectDepIncludeDirs + include-tree collectIncFiles
|
|
21
24
|
* amxmodx.includes.list → compiler-fetcher fetchCompiler + glob
|
|
22
25
|
* amxmodx.include.get → compiler-fetcher fetchCompiler + glob + read
|
|
23
26
|
* deps.tree → deps-tree buildDepTree + assembleRootDeps
|
|
@@ -51,11 +54,13 @@ const { on, off, EVENTS } = require('../events');
|
|
|
51
54
|
|
|
52
55
|
const { loadEnv } = require('../env');
|
|
53
56
|
const { resolveManifestPath } = require('../manifest-path');
|
|
54
|
-
const { resolveManifest, parseManifest, resolveGithubToken, parseDepString } = require('../manifest');
|
|
57
|
+
const { resolveManifest, parseManifest, resolveGithubToken, parseDepString, parseDepObject, parseRefTtl, parseDocEntries, parseSkillEntries } = require('../manifest');
|
|
58
|
+
const { resolveLocalEntry } = require('../local-sources');
|
|
59
|
+
const { readDepManifest, collectDepAssets, collectLocalAssets } = require('../agent-assets');
|
|
55
60
|
const { validateManifestFile } = require('../validate');
|
|
56
|
-
const {
|
|
57
|
-
const { depLabel } = require('../deps-resolver');
|
|
58
|
-
const { fetchCompiler, resolveAmxmodxVersion, getCompilerInfo } = require('../compiler-fetcher');
|
|
61
|
+
const { collectIncFiles, parseIncludeDirective, searchIncludeFile } = require('../include-tree');
|
|
62
|
+
const { depLabel, collectDepIncludeDirs } = require('../deps-resolver');
|
|
63
|
+
const { fetchCompiler, resolveAmxmodxVersion, getCompilerInfo, getHostPlatform, resolveStdlibVersion } = require('../compiler-fetcher');
|
|
59
64
|
const { buildDepTree, assembleRootDeps } = require('../deps-tree');
|
|
60
65
|
const { listReleases, listTags } = require('../release-lister');
|
|
61
66
|
const {
|
|
@@ -83,20 +88,74 @@ function readFileSafe(absPath) {
|
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
91
|
+
// JSON-RPC params spell no-fetch either `noFetch` (serve convention) or
|
|
92
|
+
// `no_fetch` (snake_case convention) — normalize once here.
|
|
93
|
+
function noFetchParam(params) {
|
|
94
|
+
return params?.noFetch === true || params?.no_fetch === true;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Resolve a concrete compiler version string for handlers that go on to call
|
|
98
|
+
// fetchCompiler() (dep-graph.get, compile.single). Explicit `version` arg wins,
|
|
99
|
+
// then the project manifest's amxmodx.version, then latest — priority lives in
|
|
100
|
+
// core (compiler-fetcher.resolveAmxmodxVersion). An invalid explicit version is
|
|
101
|
+
// a JSON-RPC param error (-32602); everything else (incl. LATEST_NOT_CACHED)
|
|
102
|
+
// propagates — these handlers fetch a real compiler, so a graceful empty state
|
|
103
|
+
// is meaningless here.
|
|
89
104
|
async function resolveVersionFromParams(params) {
|
|
90
|
-
|
|
105
|
+
try {
|
|
106
|
+
if (params?.version) {
|
|
107
|
+
// await is required: a returned non-awaited promise bypasses this
|
|
108
|
+
// try/catch, so INVALID_AMXMODX_VERSION would never map to -32602.
|
|
109
|
+
return await resolveAmxmodxVersion(null, { version: params.version, noFetch: noFetchParam(params) });
|
|
110
|
+
}
|
|
91
111
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
112
|
+
const manifestPath = params?.manifest
|
|
113
|
+
? path.resolve(params.manifest)
|
|
114
|
+
: resolveManifestPath().path;
|
|
115
|
+
let manifest = null;
|
|
116
|
+
if (fs.existsSync(manifestPath)) {
|
|
117
|
+
try { manifest = parseManifest(manifestPath); } catch { manifest = null; }
|
|
118
|
+
}
|
|
119
|
+
return await resolveAmxmodxVersion(manifest, { noFetch: noFetchParam(params) });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (err && err.code === 'INVALID_AMXMODX_VERSION') {
|
|
122
|
+
err.code = -32602;
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Informational stdlib lookup for manifest-less / offline clients. Never
|
|
130
|
+
// downloads when noFetch; if latest cannot be resolved offline, degrades to
|
|
131
|
+
// the newest cached compiler instead of failing (degraded: true). Nothing
|
|
132
|
+
// cached at all → graceful empty state (version: null), not an error.
|
|
133
|
+
// Version-priority + offline degradation live in core
|
|
134
|
+
// (compiler-fetcher.resolveStdlibVersion); this wrapper only does arg/manifest
|
|
135
|
+
// discovery, the INVALID→-32602 remap and getCompilerInfo state shaping.
|
|
136
|
+
async function resolveStdlibState(params) {
|
|
137
|
+
const noFetch = noFetchParam(params);
|
|
95
138
|
let manifest = null;
|
|
96
|
-
if (
|
|
97
|
-
|
|
139
|
+
if (!params?.version) {
|
|
140
|
+
const manifestPath = manifestPathFor(params);
|
|
141
|
+
if (fs.existsSync(manifestPath)) {
|
|
142
|
+
try { manifest = parseManifest(manifestPath); } catch { manifest = null; }
|
|
143
|
+
}
|
|
98
144
|
}
|
|
99
|
-
|
|
145
|
+
const { version, degraded, error } = await resolveStdlibVersion({
|
|
146
|
+
version: params?.version,
|
|
147
|
+
manifest,
|
|
148
|
+
noFetch,
|
|
149
|
+
});
|
|
150
|
+
if (error) {
|
|
151
|
+
if (error.code === 'INVALID_AMXMODX_VERSION') error.code = -32602;
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
if (version === null) {
|
|
155
|
+
return { version: null, degraded: false, platform: getHostPlatform(), compilerPath: null, includeDir: null, cached: false };
|
|
156
|
+
}
|
|
157
|
+
const info = await getCompilerInfo(version, { noFetch });
|
|
158
|
+
return { version: info.version, degraded, platform: info.platform, compilerPath: info.compilerPath, includeDir: info.includeDir, cached: info.cached };
|
|
100
159
|
}
|
|
101
160
|
|
|
102
161
|
function manifestPathFor(params) {
|
|
@@ -121,6 +180,43 @@ function resolveGithubTokenFor(params, repo) {
|
|
|
121
180
|
return params?.token || process.env.GITHUB_TOKEN || null;
|
|
122
181
|
}
|
|
123
182
|
|
|
183
|
+
// Build a parsed dep object from params: either a full `dep` string/object or
|
|
184
|
+
// explicit { repo, ref?, source?, include_path?, asset? } fields.
|
|
185
|
+
function depFromParams(params) {
|
|
186
|
+
let dep;
|
|
187
|
+
if (params?.dep) {
|
|
188
|
+
if (typeof params.dep === 'string') dep = parseDepString(params.dep);
|
|
189
|
+
else if (params.dep.source === 'local') dep = parseDepObject(params.dep);
|
|
190
|
+
else dep = { ...params.dep };
|
|
191
|
+
} else {
|
|
192
|
+
if (!params?.repo) throw new Error('Provide either "dep" or "repo"');
|
|
193
|
+
const source = params.source || 'git';
|
|
194
|
+
const ref = params.ref || (source === 'release' ? 'latest' : null);
|
|
195
|
+
dep = { repo: params.repo, ref, source, include_path: params.include_path || null, asset: params.asset ?? null };
|
|
196
|
+
}
|
|
197
|
+
if (params?.source) dep.source = params.source;
|
|
198
|
+
if (params?.include_path) dep.include_path = params.include_path;
|
|
199
|
+
if (params?.asset != null) dep.asset = params.asset;
|
|
200
|
+
return resolveLocalEntry(
|
|
201
|
+
dep,
|
|
202
|
+
params?.manifest ? path.dirname(path.resolve(params.manifest)) : process.cwd()
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Agent docs/skills: dep mode when dep/repo is present, otherwise the local
|
|
207
|
+
// project's own manifest.
|
|
208
|
+
async function collectAgentAssets(params) {
|
|
209
|
+
const noFetch = noFetchParam(params);
|
|
210
|
+
if (params?.dep || params?.repo) {
|
|
211
|
+
const dep = depFromParams(params);
|
|
212
|
+
const token = resolveGithubTokenFor(params, dep.repo);
|
|
213
|
+
return collectDepAssets(dep, { token, noFetch });
|
|
214
|
+
}
|
|
215
|
+
const manifestPath = manifestPathFor(params);
|
|
216
|
+
loadEnvQuiet(manifestPath);
|
|
217
|
+
return collectLocalAssets(parseManifest(manifestPath));
|
|
218
|
+
}
|
|
219
|
+
|
|
124
220
|
/**
|
|
125
221
|
* Shape a GitHub API error into the JSON-RPC error contract: -32603 with
|
|
126
222
|
* error.data = { status, repo, message }. Returns null for non-GitHub errors
|
|
@@ -198,6 +294,89 @@ function createServeServer() {
|
|
|
198
294
|
return resolveManifest(manifestPath, { set: params?.set, define: params?.define });
|
|
199
295
|
});
|
|
200
296
|
|
|
297
|
+
// ─── Dependency manifest + agent assets ──────────────────────────────────
|
|
298
|
+
|
|
299
|
+
server.onRequest('manifest.dep', async (params) => {
|
|
300
|
+
let dep;
|
|
301
|
+
try {
|
|
302
|
+
dep = depFromParams(params);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
err.code = -32602;
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
307
|
+
const token = resolveGithubTokenFor(params, dep.repo);
|
|
308
|
+
const m = await readDepManifest(dep, { token, noFetch: noFetchParam(params) });
|
|
309
|
+
|
|
310
|
+
let docs = [];
|
|
311
|
+
let skills = [];
|
|
312
|
+
try { docs = parseDocEntries(m.raw?.docs || []); } catch { docs = []; }
|
|
313
|
+
try { skills = parseSkillEntries(m.raw?.skills || []); } catch { skills = []; }
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
label: m.label,
|
|
317
|
+
manifestPath: m.manifestPath,
|
|
318
|
+
manifestName: m.raw?.name || null,
|
|
319
|
+
raw: m.raw,
|
|
320
|
+
docs: docs.map((d) => ({ name: d.name, description: d.description, file: d.file })),
|
|
321
|
+
skills: skills.map((s) => ({ name: s.name, description: s.description, file: s.file, dir: s.dir })),
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
server.onRequest('docs.list', async (params) => {
|
|
326
|
+
const assets = await collectAgentAssets(params);
|
|
327
|
+
return {
|
|
328
|
+
label: assets.label || null,
|
|
329
|
+
docs: assets.docs.map((d) => ({ name: d.name, description: d.description, file: d.file })),
|
|
330
|
+
missing: assets.missing,
|
|
331
|
+
};
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
server.onRequest('docs.get', async (params) => {
|
|
335
|
+
const assets = await collectAgentAssets(params);
|
|
336
|
+
let docs = assets.docs;
|
|
337
|
+
if (params?.name || params?.file) {
|
|
338
|
+
docs = docs.filter(
|
|
339
|
+
(d) => (params.name && d.name === params.name) || (params.file && d.file === params.file)
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
return {
|
|
343
|
+
label: assets.label || null,
|
|
344
|
+
docs: docs.map((d) => ({ name: d.name, description: d.description, file: d.file, content: d.content })),
|
|
345
|
+
missing: assets.missing,
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
server.onRequest('skills.list', async (params) => {
|
|
350
|
+
const assets = await collectAgentAssets(params);
|
|
351
|
+
return {
|
|
352
|
+
label: assets.label || null,
|
|
353
|
+
skills: assets.skills.map((s) => ({
|
|
354
|
+
name: s.name,
|
|
355
|
+
description: s.description,
|
|
356
|
+
kind: s.kind,
|
|
357
|
+
file: s.file,
|
|
358
|
+
dir: s.dir,
|
|
359
|
+
files: s.kind === 'dir' ? s.files.map((f) => f.rel) : undefined,
|
|
360
|
+
})),
|
|
361
|
+
missing: assets.missing,
|
|
362
|
+
};
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
server.onRequest('skills.get', async (params) => {
|
|
366
|
+
const assets = await collectAgentAssets(params);
|
|
367
|
+
let skills = assets.skills;
|
|
368
|
+
if (params?.name) skills = skills.filter((s) => s.name === params.name);
|
|
369
|
+
return {
|
|
370
|
+
label: assets.label || null,
|
|
371
|
+
skills: skills.map((s) => (
|
|
372
|
+
s.kind === 'dir'
|
|
373
|
+
? { name: s.name, description: s.description, kind: 'dir', dir: s.dir, files: s.files.map((f) => ({ rel: f.rel, content: f.content })) }
|
|
374
|
+
: { name: s.name, description: s.description, kind: 'file', file: s.file, content: s.content }
|
|
375
|
+
)),
|
|
376
|
+
missing: assets.missing,
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
|
|
201
380
|
// ─── Include resolution ──────────────────────────────────────────────────
|
|
202
381
|
|
|
203
382
|
server.onRequest('include.resolve', async (params) => {
|
|
@@ -230,25 +409,23 @@ function createServeServer() {
|
|
|
230
409
|
loadEnvQuiet(manifestPath);
|
|
231
410
|
try {
|
|
232
411
|
manifest = parseManifest(manifestPath);
|
|
233
|
-
for (const dep of manifest.globalDeps) {
|
|
234
|
-
try {
|
|
235
|
-
const depDir = await fetchDepIncludeDir(
|
|
236
|
-
dep, resolveGithubToken(manifest, dep.repo),
|
|
237
|
-
params?.noFetch === true, manifest.github.ssh
|
|
238
|
-
);
|
|
239
|
-
searchPaths.push({ path: depDir, label: depLabel(dep) });
|
|
240
|
-
} catch (err) {
|
|
241
|
-
errors.push(`${depLabel(dep)}: ${err.message}`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
412
|
} catch (err) {
|
|
245
413
|
errors.push(`manifest ${manifestPath}: ${err.message}`);
|
|
246
414
|
}
|
|
247
415
|
}
|
|
416
|
+
if (manifest) {
|
|
417
|
+
const { dirs, errors: depErrors } = await collectDepIncludeDirs(manifest, {
|
|
418
|
+
noFetch: noFetchParam(params),
|
|
419
|
+
ssh: manifest.github.ssh,
|
|
420
|
+
});
|
|
421
|
+
manifest.globalDeps.forEach((dep, i) => {
|
|
422
|
+
if (dirs[i]) searchPaths.push({ path: dirs[i], label: depLabel(dep) });
|
|
423
|
+
else if (depErrors[i]) errors.push(`${depLabel(dep)}: ${depErrors[i]}`);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
248
426
|
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
if (includeDir) searchPaths.push({ path: includeDir, label: `AMXX stdlib ${version}` });
|
|
427
|
+
const stdlib = await resolveStdlibState(params);
|
|
428
|
+
if (stdlib.includeDir) searchPaths.push({ path: stdlib.includeDir, label: `AMXX stdlib ${stdlib.version}` });
|
|
252
429
|
|
|
253
430
|
const result = searchIncludeFile(searchPaths, filename);
|
|
254
431
|
if (!result) {
|
|
@@ -279,21 +456,27 @@ function createServeServer() {
|
|
|
279
456
|
loadEnvQuiet(manifestPath);
|
|
280
457
|
const manifest = parseManifest(manifestPath);
|
|
281
458
|
|
|
459
|
+
const { dirs, errors } = await collectDepIncludeDirs(manifest, {
|
|
460
|
+
noFetch: noFetchParam(params),
|
|
461
|
+
ssh: manifest.github.ssh,
|
|
462
|
+
});
|
|
463
|
+
|
|
282
464
|
const deps = [];
|
|
283
|
-
for (
|
|
465
|
+
for (let i = 0; i < manifest.globalDeps.length; i++) {
|
|
466
|
+
const dep = manifest.globalDeps[i];
|
|
284
467
|
const base = dep.source === 'fungun'
|
|
285
468
|
? { source: 'fungun', id: dep.id, url: dep.url }
|
|
286
469
|
: { source: dep.source || 'git', repo: dep.repo, ref: dep.ref };
|
|
470
|
+
if (errors[i]) {
|
|
471
|
+
deps.push({ ...base, error: errors[i], files: [], count: 0 });
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
287
474
|
try {
|
|
288
|
-
const
|
|
289
|
-
dep, resolveGithubToken(manifest, dep.repo),
|
|
290
|
-
params?.noFetch === true, manifest.github.ssh
|
|
291
|
-
);
|
|
292
|
-
const files = await collectIncFiles(includeDir);
|
|
475
|
+
const files = await collectIncFiles(dirs[i]);
|
|
293
476
|
deps.push({
|
|
294
477
|
...base,
|
|
295
478
|
include_path: dep.include_path || null,
|
|
296
|
-
include_dir:
|
|
479
|
+
include_dir: dirs[i],
|
|
297
480
|
count: files.length,
|
|
298
481
|
files: files.map((f) => ({ rel: f.rel, abs: f.abs })),
|
|
299
482
|
});
|
|
@@ -307,39 +490,49 @@ function createServeServer() {
|
|
|
307
490
|
// ─── AMXX standard includes ──────────────────────────────────────────────
|
|
308
491
|
|
|
309
492
|
server.onRequest('amxmodx.includes.list', async (params) => {
|
|
310
|
-
const
|
|
493
|
+
const state = await resolveStdlibState(params);
|
|
311
494
|
const pattern = params?.pattern || '*.inc';
|
|
312
495
|
|
|
313
|
-
|
|
314
|
-
|
|
496
|
+
if (!state.includeDir) {
|
|
497
|
+
const result = { version: state.version, includeDir: null, pattern, count: 0, files: [] };
|
|
498
|
+
if (state.degraded) result.degraded = true;
|
|
499
|
+
return result;
|
|
500
|
+
}
|
|
315
501
|
|
|
316
|
-
const files = await glob(pattern, { cwd: includeDir, dot: false });
|
|
502
|
+
const files = await glob(pattern, { cwd: state.includeDir, dot: false });
|
|
317
503
|
files.sort();
|
|
318
|
-
|
|
504
|
+
const result = { version: state.version, includeDir: state.includeDir, pattern, count: files.length, files };
|
|
505
|
+
if (state.degraded) result.degraded = true;
|
|
506
|
+
return result;
|
|
319
507
|
});
|
|
320
508
|
|
|
321
509
|
server.onRequest('amxmodx.include.get', async (params) => {
|
|
322
|
-
const
|
|
510
|
+
const state = await resolveStdlibState(params);
|
|
323
511
|
const pattern = params?.file || params?.pattern || '*.inc';
|
|
324
512
|
|
|
325
|
-
|
|
326
|
-
|
|
513
|
+
if (!state.includeDir) {
|
|
514
|
+
const result = { version: state.version, includeDir: null, count: 0, files: [] };
|
|
515
|
+
if (state.degraded) result.degraded = true;
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
327
518
|
|
|
328
|
-
const files = await glob(pattern, { cwd: includeDir, dot: false });
|
|
519
|
+
const files = await glob(pattern, { cwd: state.includeDir, dot: false });
|
|
329
520
|
files.sort();
|
|
330
|
-
|
|
331
|
-
version,
|
|
332
|
-
includeDir,
|
|
521
|
+
const result = {
|
|
522
|
+
version: state.version,
|
|
523
|
+
includeDir: state.includeDir,
|
|
333
524
|
count: files.length,
|
|
334
|
-
files: files.map((rel) => ({ rel, content: readFileSafe(path.join(includeDir, rel)) })),
|
|
525
|
+
files: files.map((rel) => ({ rel, content: readFileSafe(path.join(state.includeDir, rel)) })),
|
|
335
526
|
};
|
|
527
|
+
if (state.degraded) result.degraded = true;
|
|
528
|
+
return result;
|
|
336
529
|
});
|
|
337
530
|
|
|
338
531
|
// ─── Deps tree ───────────────────────────────────────────────────────────
|
|
339
532
|
|
|
340
533
|
server.onRequest('deps.tree', async (params) => {
|
|
341
534
|
const depth = params?.depth || 0;
|
|
342
|
-
const noFetch = params
|
|
535
|
+
const noFetch = noFetchParam(params);
|
|
343
536
|
|
|
344
537
|
if (params?.deps) {
|
|
345
538
|
const rootDeps = params.deps.map((entry) => {
|
|
@@ -353,6 +546,7 @@ function createServeServer() {
|
|
|
353
546
|
source: entry.source || 'git',
|
|
354
547
|
include_path: entry.include_path || null,
|
|
355
548
|
asset: entry.asset != null ? entry.asset : null,
|
|
549
|
+
...(entry.ref_ttl != null ? { ref_ttl: parseRefTtl(entry.ref_ttl, `deps entry ${entry.repo}`) } : {}),
|
|
356
550
|
};
|
|
357
551
|
});
|
|
358
552
|
return buildDepTree(rootDeps, { token: params?.token, noFetch, depth, from: 'user' });
|
|
@@ -387,7 +581,7 @@ function createServeServer() {
|
|
|
387
581
|
throw err;
|
|
388
582
|
}
|
|
389
583
|
|
|
390
|
-
const noFetch = params
|
|
584
|
+
const noFetch = noFetchParam(params);
|
|
391
585
|
|
|
392
586
|
const manifestPath = manifestPathFor(params);
|
|
393
587
|
let manifest = null;
|
|
@@ -402,13 +596,8 @@ function createServeServer() {
|
|
|
402
596
|
// Dep includes come BEFORE the stdlib — matching the real build's search order.
|
|
403
597
|
const includeDirs = [];
|
|
404
598
|
if (manifest) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
includeDirs.push(await fetchDepIncludeDir(
|
|
408
|
-
dep, resolveGithubToken(manifest, dep.repo), noFetch, manifest.github.ssh
|
|
409
|
-
));
|
|
410
|
-
} catch (err) { /* keep the rest of the dirs on partial failure */ }
|
|
411
|
-
}
|
|
599
|
+
const { dirs } = await collectDepIncludeDirs(manifest, { noFetch, ssh: manifest.github.ssh });
|
|
600
|
+
for (const dir of dirs) if (dir) includeDirs.push(dir);
|
|
412
601
|
}
|
|
413
602
|
if (includeDir) includeDirs.push(includeDir);
|
|
414
603
|
for (const d of (params?.include_dirs || [])) includeDirs.push(path.resolve(d));
|
|
@@ -515,8 +704,10 @@ function createServeServer() {
|
|
|
515
704
|
});
|
|
516
705
|
|
|
517
706
|
server.onRequest('compiler.info', async (params) => {
|
|
518
|
-
const
|
|
519
|
-
|
|
707
|
+
const state = await resolveStdlibState(params);
|
|
708
|
+
const result = { version: state.version, platform: state.platform, compilerPath: state.compilerPath, includeDir: state.includeDir, cached: state.cached };
|
|
709
|
+
if (state.degraded) result.degraded = true;
|
|
710
|
+
return result;
|
|
520
711
|
});
|
|
521
712
|
|
|
522
713
|
server.onRequest('build.plan', (params) => {
|
|
@@ -541,37 +732,43 @@ function createServeServer() {
|
|
|
541
732
|
const controller = new AbortController();
|
|
542
733
|
activeBuild = controller;
|
|
543
734
|
|
|
544
|
-
const manifestPath = manifestPathFor(params);
|
|
545
|
-
loadEnvQuiet(manifestPath);
|
|
546
|
-
const manifest = resolveManifest(manifestPath, { set: params?.set, define: params?.define });
|
|
547
|
-
|
|
548
|
-
// Forward core lifecycle events as server→client notifications while the
|
|
549
|
-
// build runs. COMPILED/PROGRESS are emitted by compiler.js/progress.js on
|
|
550
|
-
// the bus; STAGE/DONE/ERROR by build-service.
|
|
551
|
-
const listeners = [
|
|
552
|
-
[EVENTS.STAGE, (p) => server.notify('build.stage', p)],
|
|
553
|
-
[EVENTS.COMPILED, (p) => server.notify('build.compiled', p)],
|
|
554
|
-
[EVENTS.PROGRESS, (p) => server.notify('build.progress', p)],
|
|
555
|
-
[EVENTS.DONE, (p) => server.notify('build.done', p)],
|
|
556
|
-
[EVENTS.ERROR, (p) => server.notify('build.error', p)],
|
|
557
|
-
];
|
|
558
|
-
for (const [ev, fn] of listeners) on(ev, fn);
|
|
559
|
-
|
|
560
735
|
try {
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
736
|
+
const manifestPath = manifestPathFor(params);
|
|
737
|
+
loadEnvQuiet(manifestPath);
|
|
738
|
+
const manifest = resolveManifest(manifestPath, { set: params?.set, define: params?.define });
|
|
739
|
+
|
|
740
|
+
// Forward core lifecycle events as server→client notifications while the
|
|
741
|
+
// build runs. COMPILED/PROGRESS are emitted by compiler.js/progress.js on
|
|
742
|
+
// the bus; STAGE/DONE/ERROR by build-service.
|
|
743
|
+
const listeners = [
|
|
744
|
+
[EVENTS.STAGE, (p) => server.notify('build.stage', p)],
|
|
745
|
+
[EVENTS.COMPILED, (p) => server.notify('build.compiled', p)],
|
|
746
|
+
[EVENTS.PROGRESS, (p) => server.notify('build.progress', p)],
|
|
747
|
+
[EVENTS.DONE, (p) => server.notify('build.done', p)],
|
|
748
|
+
[EVENTS.ERROR, (p) => server.notify('build.error', p)],
|
|
749
|
+
];
|
|
750
|
+
for (const [ev, fn] of listeners) on(ev, fn);
|
|
751
|
+
|
|
752
|
+
try {
|
|
753
|
+
const result = await runBuild(manifest, {
|
|
754
|
+
buildDir: params?.buildDir,
|
|
755
|
+
fetch: params?.fetch,
|
|
756
|
+
archive: params?.archive,
|
|
757
|
+
signal: controller.signal,
|
|
758
|
+
});
|
|
759
|
+
return result;
|
|
760
|
+
} catch (err) {
|
|
761
|
+
if (err.code === 'CANCELLED') {
|
|
762
|
+
return { ok: false, cancelled: true, message: err.message };
|
|
763
|
+
}
|
|
764
|
+
return { ok: false, message: err.message };
|
|
765
|
+
} finally {
|
|
766
|
+
for (const [ev, fn] of listeners) off(ev, fn);
|
|
571
767
|
}
|
|
572
|
-
return { ok: false, message: err.message };
|
|
573
768
|
} finally {
|
|
574
|
-
|
|
769
|
+
// Even a pre-build failure (manifest missing, bad --set) must release the
|
|
770
|
+
// single-build lock, or every later build.start dies with "Build already
|
|
771
|
+
// running" until the process is restarted.
|
|
575
772
|
activeBuild = null;
|
|
576
773
|
}
|
|
577
774
|
});
|
|
@@ -676,7 +873,7 @@ function createServeServer() {
|
|
|
676
873
|
throw err;
|
|
677
874
|
}
|
|
678
875
|
|
|
679
|
-
const noFetch = params
|
|
876
|
+
const noFetch = noFetchParam(params);
|
|
680
877
|
|
|
681
878
|
const manifestPath = manifestPathFor(params);
|
|
682
879
|
let manifest = null;
|
|
@@ -692,28 +889,26 @@ function createServeServer() {
|
|
|
692
889
|
const depDirs = [];
|
|
693
890
|
const depErrors = [];
|
|
694
891
|
if (manifest) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
} catch (err) {
|
|
701
|
-
depErrors.push(`${depLabel(dep)}: ${err.message}`);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
892
|
+
const { dirs, errors } = await collectDepIncludeDirs(manifest, { noFetch, ssh: manifest.github.ssh });
|
|
893
|
+
manifest.globalDeps.forEach((dep, i) => {
|
|
894
|
+
if (dirs[i]) depDirs.push(dirs[i]);
|
|
895
|
+
else if (errors[i]) depErrors.push(`${depLabel(dep)}: ${errors[i]}`);
|
|
896
|
+
});
|
|
704
897
|
}
|
|
705
898
|
|
|
706
899
|
const includeDirs = [...depDirs];
|
|
707
900
|
if (includeDir) includeDirs.push(includeDir);
|
|
708
901
|
for (const d of (params?.include_dirs || [])) includeDirs.push(path.resolve(d));
|
|
709
902
|
|
|
710
|
-
|
|
903
|
+
// Unique per-call build dir + event tag: the transport dispatches requests
|
|
904
|
+
// concurrently, so two compile.single calls for same-named plugins must not
|
|
905
|
+
// share an output path nor mis-attribute COMPILED events (cf. MCP).
|
|
906
|
+
const runId = `${process.pid}_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
907
|
+
const buildDir = path.join(os.tmpdir(), 'amxb-serve-compile', runId);
|
|
711
908
|
const compileManifest = manifest || { amxmodx: { defines: [] } };
|
|
712
909
|
|
|
713
|
-
// Capture the compiler output emitted as an event by compileSingle.
|
|
714
|
-
const baseName = path.basename(smaPath);
|
|
715
910
|
let compiled = null;
|
|
716
|
-
const onCompiled = (p) => { if (p.
|
|
911
|
+
const onCompiled = (p) => { if (p.tag === runId) compiled = p; };
|
|
717
912
|
on(EVENTS.COMPILED, onCompiled);
|
|
718
913
|
let amxxName;
|
|
719
914
|
try {
|
|
@@ -723,17 +918,19 @@ function createServeServer() {
|
|
|
723
918
|
compilerPath,
|
|
724
919
|
includeDirs,
|
|
725
920
|
buildDir,
|
|
726
|
-
params?.scripting_root ? path.resolve(params.scripting_root) : undefined
|
|
921
|
+
params?.scripting_root ? path.resolve(params.scripting_root) : undefined,
|
|
922
|
+
runId
|
|
727
923
|
);
|
|
728
924
|
} finally {
|
|
729
925
|
off(EVENTS.COMPILED, onCompiled);
|
|
730
926
|
}
|
|
731
927
|
|
|
928
|
+
const outputPath = amxxName ? path.join(buildDir, 'amxmodx', 'plugins', amxxName) : null;
|
|
732
929
|
return {
|
|
733
930
|
ok: amxxName != null,
|
|
734
931
|
amxxName,
|
|
735
932
|
output: compiled ? compiled.output : undefined,
|
|
736
|
-
output_path:
|
|
933
|
+
output_path: outputPath,
|
|
737
934
|
dep_errors: depErrors.length ? depErrors : undefined,
|
|
738
935
|
};
|
|
739
936
|
});
|
|
@@ -748,6 +945,7 @@ function createServeServer() {
|
|
|
748
945
|
}
|
|
749
946
|
|
|
750
947
|
const manifestPath = manifestPathFor(params);
|
|
948
|
+
loadEnvQuiet(manifestPath);
|
|
751
949
|
const manifest = parseManifest(manifestPath);
|
|
752
950
|
|
|
753
951
|
const notify = (kind, extra = {}) => server.notify('watch.changed', { kind, ...extra });
|
|
@@ -773,13 +971,13 @@ function createServeServer() {
|
|
|
773
971
|
return server;
|
|
774
972
|
}
|
|
775
973
|
|
|
776
|
-
// Load project .env from the workspace root like the CLI does; keep stdout free
|
|
777
|
-
// for JSON-RPC (logs → stderr, progress bars disabled).
|
|
778
|
-
function prepareEnvironment() {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
974
|
+
// Load project .env from the workspace root like the CLI does; keep stdout free
|
|
975
|
+
// for JSON-RPC (logs → stderr, progress bars disabled).
|
|
976
|
+
function prepareEnvironment() {
|
|
977
|
+
dotenv.config({ path: path.join(process.cwd(), '.env'), quiet: true });
|
|
978
|
+
logger.setStderr(true);
|
|
979
|
+
progress.setEnabled(false);
|
|
980
|
+
}
|
|
783
981
|
|
|
784
982
|
/**
|
|
785
983
|
* Start the serve server — listens on stdin/stdout forever.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
// Absolute path to the skills/ directory bundled with this amxb installation.
|
|
7
|
+
// Resolved relative to the running code, so it works for any install method
|
|
8
|
+
// (npm -g, install.sh/ps1, linked dev checkout) without consulting npm.
|
|
9
|
+
const SKILLS_DIR = path.join(__dirname, '..', '..', 'skills');
|
|
10
|
+
|
|
11
|
+
function runSkillsDir() {
|
|
12
|
+
if (!fs.existsSync(SKILLS_DIR)) {
|
|
13
|
+
process.stderr.write(`skills directory not found: ${SKILLS_DIR}\n`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Pure stdout — consumers (e.g. the opencode bridge plugin) parse exactly this line.
|
|
18
|
+
process.stdout.write(SKILLS_DIR + '\n');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { runSkillsDir, SKILLS_DIR };
|