amxx-builder 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +111 -0
- package/README.md +485 -0
- package/action-entry.js +42 -0
- package/action.yml +55 -0
- package/defaults/amxbuild.defaults.yml +40 -0
- package/index.js +4 -0
- package/mcp/dep-resolver.js +75 -0
- package/mcp/handlers.js +862 -0
- package/mcp/mcp-server.js +112 -0
- package/mcp/registry.js +863 -0
- package/mcp/symbol-index.js +171 -0
- package/package.json +68 -0
- package/src/archiver.js +140 -0
- package/src/asset-fetcher.js +277 -0
- package/src/build-plan.js +101 -0
- package/src/build-service.js +188 -0
- package/src/cache-dir.js +21 -0
- package/src/cache-info.js +104 -0
- package/src/cli.js +302 -0
- package/src/collector.js +89 -0
- package/src/commands/build.js +49 -0
- package/src/commands/cache.js +77 -0
- package/src/commands/clean.js +33 -0
- package/src/commands/compile-renderer.js +38 -0
- package/src/commands/deploy.js +40 -0
- package/src/commands/deps-tree.js +92 -0
- package/src/commands/doctor.js +77 -0
- package/src/commands/dry-run.js +64 -0
- package/src/commands/init.js +228 -0
- package/src/commands/mcp.js +13 -0
- package/src/commands/releases.js +45 -0
- package/src/commands/resolve-manifest.js +27 -0
- package/src/commands/serve.js +489 -0
- package/src/commands/shared.js +24 -0
- package/src/commands/validate.js +34 -0
- package/src/commands/watch.js +209 -0
- package/src/compile-utils.js +65 -0
- package/src/compiler-fetcher.js +327 -0
- package/src/compiler.js +228 -0
- package/src/dep-graph.js +92 -0
- package/src/deployer.js +197 -0
- package/src/deps-resolver.js +127 -0
- package/src/deps-tree.js +202 -0
- package/src/env.js +18 -0
- package/src/events.js +29 -0
- package/src/format.js +23 -0
- package/src/fs-utils.js +87 -0
- package/src/include-tree.js +845 -0
- package/src/ini-builder.js +44 -0
- package/src/jsonrpc-transport.js +195 -0
- package/src/logger.js +50 -0
- package/src/manifest-path.js +34 -0
- package/src/manifest.js +373 -0
- package/src/progress.js +66 -0
- package/src/rcon.js +103 -0
- package/src/release-fetcher.js +206 -0
- package/src/release-lister.js +79 -0
- package/src/repo-fetcher.js +273 -0
- package/src/retry.js +50 -0
- package/src/schema.js +54 -0
- package/src/update-check.js +114 -0
- package/src/validate.js +69 -0
- package/src/watcher.js +135 -0
- package/templates/init-build.bat +11 -0
- package/templates/init-build.sh +7 -0
- package/templates/init-deploy.env +14 -0
- package/templates/init-manifest.yml +6 -0
- package/templates/init-workflow.yml +59 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const logger = require('../logger');
|
|
6
|
+
const { parseManifest, resolveGithubToken } = require('../manifest');
|
|
7
|
+
const { resolveRepoRefs } = require('../repo-fetcher');
|
|
8
|
+
const { buildDepTree, assembleRootDeps } = require('../deps-tree');
|
|
9
|
+
const { resolveManifestPath, loadEnv } = require('./shared');
|
|
10
|
+
|
|
11
|
+
async function runDepsTree(options) {
|
|
12
|
+
const manifestPath = options.manifest ? path.resolve(options.manifest) : resolveManifestPath(undefined);
|
|
13
|
+
loadEnv(manifestPath);
|
|
14
|
+
|
|
15
|
+
const noFetch = options.fetch === false;
|
|
16
|
+
const asJson = options.json || false;
|
|
17
|
+
const cycleOnly = options.cycleOnly || false;
|
|
18
|
+
|
|
19
|
+
const manifest = parseManifest(manifestPath);
|
|
20
|
+
|
|
21
|
+
await resolveRepoRefs(manifest.repos, (repo) => resolveGithubToken(manifest, repo));
|
|
22
|
+
|
|
23
|
+
const { rootDeps, getDepsOverride } = assembleRootDeps(manifest);
|
|
24
|
+
|
|
25
|
+
const tree = await buildDepTree(rootDeps, {
|
|
26
|
+
tokenFor: (repo) => resolveGithubToken(manifest, repo),
|
|
27
|
+
noFetch,
|
|
28
|
+
depth: Number.isInteger(options.depth) ? options.depth : 0,
|
|
29
|
+
from: 'manifest',
|
|
30
|
+
getDepsOverride,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const filtered = cycleOnly ? filterCycles(tree) : tree;
|
|
34
|
+
|
|
35
|
+
if (asJson) {
|
|
36
|
+
process.stdout.write(JSON.stringify(filtered, null, 2) + '\n');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
printTree(filtered);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function printTree(tree) {
|
|
44
|
+
if (!tree.dependencies || tree.dependencies.length === 0) {
|
|
45
|
+
logger.info('No dependencies');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
logger.info('Dependency tree:');
|
|
49
|
+
for (const node of tree.dependencies) {
|
|
50
|
+
printNode(node, '', true);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function printNode(node, prefix, isLast) {
|
|
55
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
56
|
+
const childPrefix = isLast ? ' ' : '│ ';
|
|
57
|
+
|
|
58
|
+
const tag = buildNodeTag(node);
|
|
59
|
+
logger.dim(`${prefix}${connector}${node.repo}@${node.ref || 'HEAD'}${tag}`);
|
|
60
|
+
|
|
61
|
+
if (node.cycle) return;
|
|
62
|
+
|
|
63
|
+
for (let i = 0; i < node.dependencies.length; i++) {
|
|
64
|
+
printNode(node.dependencies[i], prefix + childPrefix, i === node.dependencies.length - 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function buildNodeTag(node) {
|
|
69
|
+
const parts = [];
|
|
70
|
+
if (node.from) parts.push(`from ${node.from}`);
|
|
71
|
+
if (node.resolvedRef && node.ref !== node.resolvedRef) {
|
|
72
|
+
parts.push(`→ ${node.resolvedRef}`);
|
|
73
|
+
}
|
|
74
|
+
if (node.cycle) parts.push('⚠ cycle');
|
|
75
|
+
if (node.shared) parts.push('(shared)');
|
|
76
|
+
if (node.error) parts.push(`✗ ${node.error}`);
|
|
77
|
+
return parts.length ? ` (${parts.join(', ')})` : '';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function filterCycles(tree) {
|
|
81
|
+
const collect = [];
|
|
82
|
+
function walk(nodes) {
|
|
83
|
+
for (const n of nodes) {
|
|
84
|
+
if (n.cycle) collect.push(n);
|
|
85
|
+
else walk(n.dependencies);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
walk(tree.dependencies || []);
|
|
89
|
+
return { dependencies: collect };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { runDepsTree };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const logger = require('../logger');
|
|
8
|
+
const { getCacheDir } = require('../cache-dir');
|
|
9
|
+
const { dirSize, fmtSize } = require('../cache-info');
|
|
10
|
+
|
|
11
|
+
async function runDoctor(options) {
|
|
12
|
+
const ok = [];
|
|
13
|
+
const warn = [];
|
|
14
|
+
const note = [];
|
|
15
|
+
|
|
16
|
+
const nodeMajor = parseInt(process.version.slice(1).split('.')[0], 10);
|
|
17
|
+
(nodeMajor >= 16 ? ok : warn).push(`Node.js: ${process.version.slice(1)}${nodeMajor >= 16 ? '' : ' (minimum 16 required)'}`);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const gitVer = execSync('git --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
21
|
+
ok.push(`Git: ${gitVer.replace('git version ', '')} (optional — only for github.ssh: true)`);
|
|
22
|
+
} catch {
|
|
23
|
+
note.push('Git: not found (optional — only needed for github.ssh: true)');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const npmVer = execSync('npm --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
28
|
+
ok.push(`npm: ${npmVer}`);
|
|
29
|
+
} catch {
|
|
30
|
+
warn.push('npm: not found in PATH');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const axios = require('axios');
|
|
35
|
+
const resp = await axios.get('https://api.github.com', { timeout: 5000 });
|
|
36
|
+
if (resp.status === 200 || resp.status === 403) {
|
|
37
|
+
ok.push('GitHub API: reachable');
|
|
38
|
+
} else {
|
|
39
|
+
warn.push(`GitHub API: returned ${resp.status}`);
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
warn.push('GitHub API: unreachable (check internet)');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const manifestPath = options.manifest
|
|
46
|
+
? path.resolve(options.manifest)
|
|
47
|
+
: fs.existsSync('./amxbuild.yml') ? './amxbuild.yml'
|
|
48
|
+
: fs.existsSync('./amxbuild.yaml') ? './amxbuild.yaml'
|
|
49
|
+
: null;
|
|
50
|
+
|
|
51
|
+
if (manifestPath && fs.existsSync(manifestPath)) {
|
|
52
|
+
try {
|
|
53
|
+
const { parseManifest } = require('../manifest');
|
|
54
|
+
parseManifest(manifestPath);
|
|
55
|
+
ok.push(`Manifest: valid (${path.basename(manifestPath)})`);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
warn.push(`Manifest: invalid — ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
ok.push('Manifest: not found (run amxb init)');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const cacheDir = getCacheDir();
|
|
64
|
+
const cacheSize = fmtSize(dirSize(cacheDir));
|
|
65
|
+
ok.push(`Cache: ${cacheDir} (${cacheSize})`);
|
|
66
|
+
|
|
67
|
+
logger.info('=== System Check ===');
|
|
68
|
+
for (const msg of ok) logger.success(` ✓ ${msg}`);
|
|
69
|
+
for (const msg of note) logger.info(` · ${msg}`);
|
|
70
|
+
for (const msg of warn) logger.warn(` ⚠ ${msg}`);
|
|
71
|
+
|
|
72
|
+
if (warn.length) {
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { runDoctor };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const logger = require('../logger');
|
|
5
|
+
const { buildPlanData } = require('../build-plan');
|
|
6
|
+
|
|
7
|
+
function printDryRun(manifest) {
|
|
8
|
+
const out = manifest.output;
|
|
9
|
+
const expand = (tpl) => tpl.replaceAll('{name}', manifest.name).replaceAll('{version}', manifest.version);
|
|
10
|
+
|
|
11
|
+
logger.info(`=== DRY RUN: ${manifest.name} v${manifest.version} ===`);
|
|
12
|
+
|
|
13
|
+
logger.info(`\nCompiler:`);
|
|
14
|
+
logger.dim(` amxxpc ${manifest.amxmodx.version || 'latest'} — dir: ${manifest.amxmodx.dir}`);
|
|
15
|
+
if (manifest.platform) logger.dim(` target platform: ${manifest.platform}`);
|
|
16
|
+
if (manifest.amxmodx.defines.length) {
|
|
17
|
+
logger.dim(` defines: ${manifest.amxmodx.defines.map(d => `-D${d}`).join(' ')}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (manifest.repos.length) {
|
|
21
|
+
logger.info(`\nRepos (${manifest.repos.length}):`);
|
|
22
|
+
for (const r of manifest.repos) {
|
|
23
|
+
const ref = r.ref || 'default branch';
|
|
24
|
+
logger.dim(` ${r.repo} @ ${ref} [dir: ${r.amxmodx_dir}]`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (manifest.globalDeps.length) {
|
|
29
|
+
logger.info(`\nGlobal deps (${manifest.globalDeps.length}):`);
|
|
30
|
+
for (const d of manifest.globalDeps) {
|
|
31
|
+
const src = d.source === 'release' ? 'release' : 'git';
|
|
32
|
+
logger.dim(` [${src}] ${d.repo}@${d.ref}${d.include_path ? ':' + d.include_path : ''}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (manifest.assets.sources.length) {
|
|
37
|
+
logger.info(`\nAsset sources (${manifest.assets.sources.length}):`);
|
|
38
|
+
for (const s of manifest.assets.sources) {
|
|
39
|
+
if (s.type === 'amxmodx') {
|
|
40
|
+
logger.dim(` [amxmodx] ${manifest.amxmodx.version || 'latest'} (${manifest.platform || 'host'})`);
|
|
41
|
+
} else if (s.type === 'release') {
|
|
42
|
+
logger.dim(` [release] ${s.repo}@${s.ref} cache: ${s.cache || 'global'}`);
|
|
43
|
+
} else if (s.type === 'local') {
|
|
44
|
+
logger.dim(` [local] assets/`);
|
|
45
|
+
} else {
|
|
46
|
+
logger.dim(` [url] ${s.url} cache: ${s.cache || 'none'}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
logger.info(`\nOutput:`);
|
|
52
|
+
if (out.pack === false) {
|
|
53
|
+
logger.dim(` copy → ${path.resolve(out.dir)}/${expand(out.amxmodx_path)}/`);
|
|
54
|
+
} else {
|
|
55
|
+
logger.dim(` archive → ${path.resolve(out.dir)}/${expand(out.archive_name)}`);
|
|
56
|
+
logger.dim(` amxmodx path in archive: ${expand(out.amxmodx_path)}/`);
|
|
57
|
+
}
|
|
58
|
+
if (out.assets_path) logger.dim(` assets path: ${expand(out.assets_path)}/`);
|
|
59
|
+
logger.dim(` generate_ini: ${out.generate_ini} | on_conflict: ${out.on_conflict}`);
|
|
60
|
+
|
|
61
|
+
logger.info(`\n=== END DRY RUN ===`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = { printDryRun, buildPlanData };
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const logger = require('../logger');
|
|
7
|
+
|
|
8
|
+
const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates');
|
|
9
|
+
const SCHEMA_URL = 'https://raw.githubusercontent.com/AmxxModularEcosystem/amxx-builder/master/schema/amxbuild.schema.json';
|
|
10
|
+
|
|
11
|
+
async function runInitInteractive(options) {
|
|
12
|
+
const { Input, Confirm } = require('enquirer');
|
|
13
|
+
const defaultName = options.name || path.basename(process.cwd());
|
|
14
|
+
|
|
15
|
+
const name = await new Input({
|
|
16
|
+
name: 'name',
|
|
17
|
+
message: 'Project name',
|
|
18
|
+
initial: defaultName,
|
|
19
|
+
}).run();
|
|
20
|
+
|
|
21
|
+
await new Input({
|
|
22
|
+
name: 'description',
|
|
23
|
+
message: 'Project description (optional)',
|
|
24
|
+
initial: '',
|
|
25
|
+
}).run();
|
|
26
|
+
|
|
27
|
+
const doWorkflow = await new Confirm({
|
|
28
|
+
name: 'workflow',
|
|
29
|
+
message: 'Generate GitHub CI workflow?',
|
|
30
|
+
initial: false,
|
|
31
|
+
}).run();
|
|
32
|
+
|
|
33
|
+
const doPlugin = await new Confirm({
|
|
34
|
+
name: 'plugin',
|
|
35
|
+
message: 'Create a plugin .sma file?',
|
|
36
|
+
initial: true,
|
|
37
|
+
}).run();
|
|
38
|
+
const pluginName = doPlugin ? await new Input({
|
|
39
|
+
name: 'pluginName',
|
|
40
|
+
message: 'Plugin filename (without .sma)',
|
|
41
|
+
initial: name,
|
|
42
|
+
}).run() : null;
|
|
43
|
+
|
|
44
|
+
const doGitignore = await new Confirm({
|
|
45
|
+
name: 'gitignore',
|
|
46
|
+
message: 'Create .gitignore?',
|
|
47
|
+
initial: true,
|
|
48
|
+
}).run();
|
|
49
|
+
|
|
50
|
+
const doDeploy = await new Confirm({
|
|
51
|
+
name: 'deploy',
|
|
52
|
+
message: 'Create .env with deploy stubs?',
|
|
53
|
+
initial: false,
|
|
54
|
+
}).run();
|
|
55
|
+
|
|
56
|
+
const doOpencode = await new Confirm({
|
|
57
|
+
name: 'opencode',
|
|
58
|
+
message: 'Create .opencode/opencode.json with MCP config (amxb mcp)?',
|
|
59
|
+
initial: false,
|
|
60
|
+
}).run();
|
|
61
|
+
|
|
62
|
+
const doScript = await new Confirm({
|
|
63
|
+
name: 'script',
|
|
64
|
+
message: 'Create build.bat / build.sh quick-build scripts?',
|
|
65
|
+
initial: true,
|
|
66
|
+
}).run();
|
|
67
|
+
|
|
68
|
+
const actions = [];
|
|
69
|
+
actions.push('amxbuild.yml');
|
|
70
|
+
if (doWorkflow) actions.push('.github/workflows/ci.yml');
|
|
71
|
+
if (pluginName) actions.push(`amxmodx/scripting/${pluginName}.sma`);
|
|
72
|
+
if (doGitignore) actions.push('.gitignore');
|
|
73
|
+
if (doDeploy) actions.push('.env');
|
|
74
|
+
if (doOpencode) actions.push('.opencode/opencode.json');
|
|
75
|
+
if (doScript) actions.push('build.bat', 'build.sh');
|
|
76
|
+
|
|
77
|
+
logger.info('Creating:');
|
|
78
|
+
for (const a of actions) logger.dim(` ${a}`);
|
|
79
|
+
|
|
80
|
+
const version = require('../../package.json').version;
|
|
81
|
+
const actionTag = `v${version.split('.')[0]}`;
|
|
82
|
+
|
|
83
|
+
writeIfAbsent('amxbuild.yml', renderTemplate('init-manifest.yml', { name, schemaUrl: SCHEMA_URL }));
|
|
84
|
+
|
|
85
|
+
if (doWorkflow) {
|
|
86
|
+
const dest = path.join('.github', 'workflows', 'ci.yml');
|
|
87
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
88
|
+
writeIfAbsent(dest, renderTemplate('init-workflow.yml', { actionTag }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (pluginName) {
|
|
92
|
+
const dest = path.join('amxmodx', 'scripting', `${pluginName}.sma`);
|
|
93
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
94
|
+
writeIfAbsent(dest, '');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (doGitignore) {
|
|
98
|
+
writeIfAbsent('.gitignore', [
|
|
99
|
+
'*.amxx', '*.zip', '.env', '.amxb-cache', '.claude', 'build', 'dist', '',
|
|
100
|
+
].join('\n'));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (doDeploy) {
|
|
104
|
+
writeIfAbsent('.env', renderTemplate('init-deploy.env'));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (doOpencode) {
|
|
108
|
+
writeOpencodeConfig();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (doScript) {
|
|
112
|
+
writeBuildScripts();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function runInit(options) {
|
|
117
|
+
const pkgName = options.name || path.basename(process.cwd());
|
|
118
|
+
const version = require('../../package.json').version;
|
|
119
|
+
const actionTag = `v${version.split('.')[0]}`;
|
|
120
|
+
|
|
121
|
+
writeIfAbsent('amxbuild.yml', renderTemplate('init-manifest.yml', { name: pkgName, schemaUrl: SCHEMA_URL }));
|
|
122
|
+
|
|
123
|
+
if (options.workflow || options.ci) {
|
|
124
|
+
const dest = path.join('.github', 'workflows', 'ci.yml');
|
|
125
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
126
|
+
writeIfAbsent(dest, renderTemplate('init-workflow.yml', { actionTag }));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (options.plugin) {
|
|
130
|
+
const dest = path.join('amxmodx', 'scripting', `${options.plugin}.sma`);
|
|
131
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
132
|
+
writeIfAbsent(dest, '');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (options.gitignore) {
|
|
136
|
+
writeIfAbsent('.gitignore', [
|
|
137
|
+
'*.amxx', '*.zip', '.env', '.amxb-cache', '.claude', 'build', 'dist', '',
|
|
138
|
+
].join('\n'));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (options.deploy) {
|
|
142
|
+
writeIfAbsent('.env', renderTemplate('init-deploy.env'));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (options.opencode) {
|
|
146
|
+
writeOpencodeConfig();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (options.script) {
|
|
150
|
+
writeBuildScripts();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function writeBuildScripts() {
|
|
155
|
+
const batCreated = writeIfAbsent('build.bat', renderTemplate('init-build.bat').replace(/\r?\n/g, '\r\n'));
|
|
156
|
+
const shCreated = writeIfAbsent('build.sh', renderTemplate('init-build.sh'));
|
|
157
|
+
|
|
158
|
+
if (shCreated && process.platform !== 'win32') {
|
|
159
|
+
try {
|
|
160
|
+
fs.chmodSync('build.sh', 0o755);
|
|
161
|
+
logger.dim(' chmod +x build.sh');
|
|
162
|
+
} catch (err) {
|
|
163
|
+
logger.warn(`Could not make build.sh executable: ${err.message}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return batCreated || shCreated;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function writeIfAbsent(filePath, content) {
|
|
171
|
+
if (fs.existsSync(filePath)) {
|
|
172
|
+
logger.warn(`${filePath} already exists, skipping`);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
fs.writeFileSync(filePath, content);
|
|
176
|
+
logger.success(`Created ${filePath}`);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function writeOpencodeConfig() {
|
|
181
|
+
const dir = '.opencode';
|
|
182
|
+
const file = path.join(dir, 'opencode.json');
|
|
183
|
+
const mcpKey = 'amxx-dep-resolver';
|
|
184
|
+
const mcpConfig = {
|
|
185
|
+
type: 'local',
|
|
186
|
+
command: ['amxb', 'mcp'],
|
|
187
|
+
enabled: true,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
if (!fs.existsSync(file)) {
|
|
191
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
192
|
+
fs.writeFileSync(file, JSON.stringify({
|
|
193
|
+
$schema: 'https://opencode.ai/config.json',
|
|
194
|
+
mcp: { [mcpKey]: mcpConfig },
|
|
195
|
+
}, null, 2) + '\n');
|
|
196
|
+
logger.success(`Created ${file}`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let cfg;
|
|
201
|
+
try {
|
|
202
|
+
cfg = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
203
|
+
} catch (err) {
|
|
204
|
+
logger.warn(`${file} exists but is invalid JSON, skipping merge`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (cfg.mcp?.[mcpKey]) {
|
|
209
|
+
logger.warn(`${file} already has MCP config (amxx-dep-resolver), skipping`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
cfg.mcp = cfg.mcp || {};
|
|
214
|
+
cfg.mcp[mcpKey] = mcpConfig;
|
|
215
|
+
cfg.$schema = cfg.$schema || 'https://opencode.ai/config.json';
|
|
216
|
+
fs.writeFileSync(file, JSON.stringify(cfg, null, 2) + '\n');
|
|
217
|
+
logger.success(`Updated ${file} with MCP config (amxb mcp)`);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function renderTemplate(name, vars = {}) {
|
|
221
|
+
let content = fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf8');
|
|
222
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
223
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
224
|
+
}
|
|
225
|
+
return content;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
module.exports = { runInit, runInitInteractive };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { startServer } = require('../../mcp/dep-resolver');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Run the MCP server (stdin/stdout JSON-RPC).
|
|
7
|
+
* Intended to be spawned by opencode as a subprocess.
|
|
8
|
+
*/
|
|
9
|
+
async function runMcp() {
|
|
10
|
+
await startServer();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = { runMcp };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const logger = require('../logger');
|
|
4
|
+
const { listReleases, listTags } = require('../release-lister');
|
|
5
|
+
|
|
6
|
+
async function runReleases(repo, options) {
|
|
7
|
+
require('dotenv').config({ override: true });
|
|
8
|
+
const token = process.env.GITHUB_TOKEN || null;
|
|
9
|
+
const limit = options.limit || 10;
|
|
10
|
+
const asJson = options.json || false;
|
|
11
|
+
|
|
12
|
+
let entries;
|
|
13
|
+
if (options.tags) {
|
|
14
|
+
entries = await listTags(repo, { token, limit });
|
|
15
|
+
} else {
|
|
16
|
+
entries = await listReleases(repo, { token, limit, includeAssets: options.assets });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (asJson) {
|
|
20
|
+
process.stdout.write(JSON.stringify(entries, null, 2) + '\n');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (entries.length === 0) {
|
|
25
|
+
logger.info(`No ${options.tags ? 'tags' : 'releases'} found for ${repo}`);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const label = options.tags ? 'Tags' : 'Releases';
|
|
30
|
+
logger.info(`${label} for ${repo} (${entries.length}):`);
|
|
31
|
+
for (const e of entries) {
|
|
32
|
+
const line = options.tags
|
|
33
|
+
? ` ${e.name}`
|
|
34
|
+
: ` ${e.tagName} ${e.prerelease ? '(pre) ' : ''}${e.publishedAt ? `— ${e.publishedAt.slice(0, 10)}` : ''}`;
|
|
35
|
+
logger.dim(line);
|
|
36
|
+
|
|
37
|
+
if (e.assets && e.assets.length > 0) {
|
|
38
|
+
for (const a of e.assets) {
|
|
39
|
+
logger.dim(` └ assets/${a.name} (${(a.size / 1024).toFixed(0)} KB)`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { runReleases };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const logger = require('../logger');
|
|
6
|
+
const { resolveManifest } = require('../manifest');
|
|
7
|
+
const { resolveManifestPath, loadEnv } = require('./shared');
|
|
8
|
+
|
|
9
|
+
async function runResolveManifest(options) {
|
|
10
|
+
const manifestPath = options.manifest ? path.resolve(options.manifest) : resolveManifestPath(undefined);
|
|
11
|
+
loadEnv(manifestPath);
|
|
12
|
+
|
|
13
|
+
const manifest = resolveManifest(manifestPath, {
|
|
14
|
+
set: options.set,
|
|
15
|
+
define: options.define,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (options.json) {
|
|
19
|
+
process.stdout.write(JSON.stringify(manifest, null, 2) + '\n');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
logger.info('Resolved manifest:');
|
|
24
|
+
logger.dim(JSON.stringify(manifest, null, 2));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { runResolveManifest };
|