amxx-builder 1.6.1 → 1.7.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 +8 -1
- package/README.md +234 -16
- package/action.yml +1 -1
- package/defaults/amxbuild.defaults.yml +1 -1
- package/mcp/handlers.js +51 -10
- package/mcp/registry.js +68 -2
- package/package.json +2 -1
- package/skills/amxb-migration/SKILL.md +112 -25
- package/skills/amxx-pawn-style/SKILL.md +422 -0
- package/src/build-plan.js +22 -5
- package/src/build-service.js +37 -3
- package/src/cli.js +2 -0
- package/src/collector.js +1 -1
- package/src/commands/build.js +5 -4
- package/src/commands/dry-run.js +28 -1
- package/src/commands/init.js +69 -6
- package/src/commands/serve.js +10 -3
- package/src/commands/watch.js +18 -4
- package/src/compile-utils.js +108 -2
- package/src/compiler.js +43 -16
- package/src/deps-resolver.js +46 -8
- package/src/deps-tree.js +18 -8
- package/src/github-api.js +32 -0
- package/src/include-tree.js +3 -1
- package/src/ini-builder.js +1 -1
- package/src/local-sources.js +363 -0
- package/src/manifest.js +326 -23
- package/src/opencode-skills.js +1 -1
- package/src/repo-fetcher.js +119 -11
- package/src/schema.js +1 -1
- package/src/watcher.js +27 -1
- package/templates/init-deploy.env +9 -0
- package/templates/init-gitignore +22 -0
package/AGENTS.md
CHANGED
|
@@ -21,6 +21,7 @@ Entry: `index.js` (CLI via `commander`). Action: `action-entry.js` → synthesis
|
|
|
21
21
|
- Include-path candidate lists (`['scripting/include', 'amxmodx/scripting/include', 'include', '.']`) — single source: `src/deps-resolver.js` (`resolveIncludePath`)
|
|
22
22
|
- Dep string parsing (`owner/repo@ref[:include_path]`) — single source: `src/manifest.js` (`parseDepsLines`)
|
|
23
23
|
- GitHub token resolution — `src/manifest.js` (`resolveGithubToken`); repo key / normalization — `src/deps-resolver.js` (`repoKey`, `normalize`)
|
|
24
|
+
- Local sources — `source: local` resolution and `AMXB_LOCAL_SOURCES` env-override parsing, `_localDir` resolution, `_resolvedRef = 'local'` stamping, `ensureRepoDir` — single source: `src/local-sources.js` (`applyLocalOverrides`, `parseLocalSourcesEnv`, `ensureRepoDir`)
|
|
24
25
|
- When touching an interface layer, check whether the logic already exists in `src/` before writing new code. New core exports are cheap; new duplication is debt.
|
|
25
26
|
|
|
26
27
|
## Commands
|
|
@@ -37,6 +38,7 @@ Entry: `index.js` (CLI via `commander`). Action: `action-entry.js` → synthesis
|
|
|
37
38
|
| `amxb init` | Scaffold manifest and optional files |
|
|
38
39
|
| `amxb init --force` | Scaffold, overwriting existing files (default: skip) |
|
|
39
40
|
| `amxb init --script` | Also create `build.bat` / `build.sh` quick-build scripts |
|
|
41
|
+
| `amxb init --vscode` | Also create `.vscode/extensions.json` with recommended extensions (alias `--vsc`; merges into an existing file) |
|
|
40
42
|
| `amxb clean` | Clean build/ and clone cache |
|
|
41
43
|
| `amxb clean --all` | Also clean compiler cache |
|
|
42
44
|
| `amxb cache info` | Show cache contents |
|
|
@@ -62,9 +64,12 @@ The opencode bridge plugin (`.opencode/plugin/amxb-skills.js`, generated by `amx
|
|
|
62
64
|
- **Arrays are replaced entirely** (repos, deps, assets.sources) — not merged with defaults.
|
|
63
65
|
- `version` **must be a quoted string** in YAML or parsing fails.
|
|
64
66
|
- `ref: latest` resolves to the latest GitHub release tag automatically.
|
|
65
|
-
-
|
|
67
|
+
- `ref_ttl` (optional, git `repos[]`/`deps[]` objects only) sets the TTL of the `ref → SHA` resolution cache: `never`, a duration (`30m`/`1h`/`7d`), or an integer number of seconds. Default: a named ref GitHub confirms as a **tag** is cached forever; a branch (including the default branch when no `ref` is given) revalidates after 1 hour. When a ref name exists as both a tag and a branch, the tag wins and the ref is cached as immutable. Because clones are SHA-keyed, an unchanged SHA is never re-downloaded; a force-pushed tag is only picked up via an explicit `ref_ttl` or `amxb clean`. It does not affect `source: release`/`fungun`/`local`, `latest`, or `null` refs, and the string dep form / `DEPS_LIST` cannot express it.
|
|
68
|
+
- Plugin rules (`plugins.rules:`) apply **only to local** `.sma` files, not repo plugins; `plugins.defaults` and `repos[].plugins` apply to all plugins. An effective non-`false` `ini` enables INI generation.
|
|
66
69
|
- Local `amxmodx/` always wins over repo files (intentional override layer, no conflict warning).
|
|
67
70
|
- `.sma` files ARE copied during collect (like any other file) and are also compiled; exclude them per-repo via `exclude_files` if sources should not ship.
|
|
71
|
+
- `amxmodx.exclude_files` (array of glob patterns, default `[]`, relative to the local `amxmodx.dir`) keeps matching files from the project's own local `amxmodx/` out of `build/amxmodx/` and the archive. It does not touch `repos:` files (those use per-repo `exclude_files`) and does not stop compilation of local `.sma` (use `plugins.rules[].enabled: false` for that); watch ignores excluded local files. Distinct from `deploy.exclude`, which protects server files from being overwritten during deploy/watch.
|
|
72
|
+
- `repos:`/`deps:` entries can be satisfied by a **local directory** instead of GitHub: `source: local` + `path` (relative to the manifest dir, or absolute; optional `name` gives the synthetic id `local/<name>`, defaulting to the path basename). A local repo is collected + its `.sma` compiled and may still set `amxmodx_dir` / `plugins` / `exclude` / `exclude_files` / `deps_override` (`repo`/`ref` forbidden; the deprecated `plugins_ini_postfix` is still accepted with a build warning); a local dep contributes `.inc` to `build/_includes/` (`repo`/`ref`/`id`/`url`/`asset` forbidden; object form only, the `owner/repo@ref[:include_path]` string stays git-only). Local sources are unpinned (the working tree is read as-is) so builds are not reproducible, and watch does not follow dirs outside the manifest's `amxmodx/`/`assets/`.
|
|
68
73
|
|
|
69
74
|
## GitHub Action release flow
|
|
70
75
|
```bash
|
|
@@ -101,6 +106,8 @@ This is automated in `.github/workflows/release.yml` on `v*.*.*` tags.
|
|
|
101
106
|
## Cache
|
|
102
107
|
- Win: `%LOCALAPPDATA%\amxx-builder`, Unix: `~/.cache/amxx-builder`
|
|
103
108
|
- Override: `AMXX_BUILDER_CACHE`
|
|
109
|
+
- Local source redirects: `AMXB_LOCAL_SOURCES` maps an existing repo/dep id (`owner/repo`) to a path (pairs `id=path` separated by `;`/newlines, or a JSON object), `AMXB_LOCAL_STRICT=1` makes an unmatched id an error (default: warn and ignore). `.env` next to the manifest is honored — see `src/local-sources.js`.
|
|
110
|
+
- Plugin INI debug override: `AMXB_PLUGINS_DEBUG` (read from `.env` next to the manifest, local only — `.env` is gitignored) forces the ` debug` suffix in generated `plugins-*.ini` on/off for every included plugin. `1`/`true`/`yes`/`on` force on, `0`/`false`/`no`/`off` force off, anything else (unset/empty) leaves the manifest in charge. Case-insensitive. Precedence: `AMXB_PLUGINS_DEBUG` > per-rule/per-repo `debug` > `plugins.defaults.debug`; it only affects plugins already in an INI and never enables INI generation by itself.
|
|
104
111
|
- Local per-manifest asset cache: `.amxb-cache/` next to `amxbuild.yml`
|
|
105
112
|
- Separate dirs: `repos/`, `release-deps/`, `amxxpc/` (compiler binaries)
|
|
106
113
|
|
package/README.md
CHANGED
|
@@ -60,6 +60,11 @@ $env:AMXB_VERSION="v1.2.3"; irm https://raw.githubusercontent.com/AmxxModularEco
|
|
|
60
60
|
|
|
61
61
|
Расширение для VSCode - [AMXB — AMX Mod X Builder](https://marketplace.visualstudio.com/items?itemName=amxx-modular-ecosystem.amxb-vscode).
|
|
62
62
|
|
|
63
|
+
Команда `amxb init --vscode` создаёт `.vscode/extensions.json` с рекомендациями расширений
|
|
64
|
+
для AMXX-проекта (`Faktor.amxx-pawn-all-in` + `amxx-modular-ecosystem.amxb-vscode`).
|
|
65
|
+
Существующий файл не перезаписывается: рекомендации проекта сохраняются, недостающие
|
|
66
|
+
добавляются.
|
|
67
|
+
|
|
63
68
|
## Использование
|
|
64
69
|
|
|
65
70
|
```bash
|
|
@@ -81,6 +86,7 @@ amxb init --plugin <name> # + создать amxmodx/scripting/<name>.
|
|
|
81
86
|
amxb init --workflow # + создать .github/workflows/ci.yml
|
|
82
87
|
amxb init --script # + создать build.bat / build.sh для быстрого запуска amxb build
|
|
83
88
|
amxb init --opencode # + создать .opencode/ (opencode.json с MCP-конфигом + мост скиллов)
|
|
89
|
+
amxb init --vscode # + создать .vscode/extensions.json с рекомендациями расширений (алиас: --vsc)
|
|
84
90
|
amxb init --force # перезаписать существующие файлы (по умолчанию пропускаются)
|
|
85
91
|
amxb init --force --with-manifest # + перезаписать и существующий amxbuild.yml (без --with-manifest манифест не трогается)
|
|
86
92
|
|
|
@@ -149,27 +155,97 @@ my-server/
|
|
|
149
155
|
weapon.wav
|
|
150
156
|
```
|
|
151
157
|
|
|
152
|
-
|
|
158
|
+
### Исключение локальных файлов
|
|
153
159
|
|
|
154
|
-
Поле `
|
|
160
|
+
Поле `amxmodx.exclude_files` задаёт массив glob-паттернов. Файлы локальной папки `amxmodx/`, попавшие под паттерн, не копируются в `build/amxmodx/` и не попадают в архив. По умолчанию значение пустое (`[]`), то есть копируется всё. Паттерны считаются от `amxmodx.dir` (по умолчанию `amxmodx`):
|
|
155
161
|
|
|
156
162
|
```yaml
|
|
157
|
-
|
|
163
|
+
amxmodx:
|
|
164
|
+
exclude_files:
|
|
165
|
+
- configs/*.cfg
|
|
166
|
+
- "**/*.ini"
|
|
167
|
+
- scripting/dev.sma
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Поле действует только на собственную локальную папку `amxmodx/` рядом с манифестом. Файлов из `repos:` оно не касается. Для них исключение задаётся на самом репо через `repos[].exclude_files`. Компиляцию локальных `.sma` поле не отменяет. Чтобы полностью пропустить локальный плагин, используйте `plugins.rules[].enabled: false` (см. раздел про плагины). В watch-режиме изменения исключённых локальных файлов игнорируются, поэтому они не деплоятся.
|
|
171
|
+
|
|
172
|
+
Не путайте с `deploy.exclude`. Это поле не даёт `amxb deploy` и `amxb watch` перезаписывать файлы сервера (например, существующие конфиги). А `amxmodx.exclude_files` просто не кладёт файлы в пакет.
|
|
173
|
+
|
|
174
|
+
## Управление плагинами и INI
|
|
158
175
|
|
|
176
|
+
Секция `plugins:` описывает, какие плагины попадают в INI-файлы, как эти файлы называются и добавлять ли к строке плагина ` debug`. Она состоит из двух частей:
|
|
177
|
+
|
|
178
|
+
- `defaults:` — базовый слой, применяется ко **всем** плагинам (локальным и из репо);
|
|
179
|
+
- `rules:` — glob-правила **только для локальных** `.sma` из `amxmodx/scripting/`; первое совпадение побеждает.
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
159
182
|
plugins:
|
|
160
|
-
|
|
161
|
-
ini:
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
183
|
+
defaults:
|
|
184
|
+
ini: myserver # базовый INI для всех плагинов → plugins-myserver.ini
|
|
185
|
+
debug: false # true → к каждой строке добавляется " debug"
|
|
186
|
+
rules:
|
|
187
|
+
- match: "VipM/*.sma"
|
|
188
|
+
ini: vipm # → plugins-vipm.ini (переопределяет defaults)
|
|
189
|
+
debug: true # vip_core.amxx debug
|
|
190
|
+
- match: "utils/*.sma"
|
|
191
|
+
ini: false # компилировать, но не включать ни в один INI
|
|
192
|
+
- match: "wip/*.sma"
|
|
193
|
+
enabled: false # полностью пропустить (не компилировать, не деплоить)
|
|
166
194
|
```
|
|
167
195
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
196
|
+
Плагины из репозитория настраиваются на самом репо:
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
repos:
|
|
200
|
+
- repo: Org/VipModular
|
|
201
|
+
plugins:
|
|
202
|
+
ini: vip # → plugins-vip.ini
|
|
203
|
+
debug: false
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Значения `ini`:**
|
|
207
|
+
|
|
208
|
+
| Значение | Результат |
|
|
209
|
+
| --- | --- |
|
|
210
|
+
| `false` | Скомпилировать, но не включать ни в один INI |
|
|
211
|
+
| `true` или `""` | Включить в `plugins.ini` |
|
|
212
|
+
| `"<postfix>"` | Включить в `plugins-<postfix>.ini` |
|
|
213
|
+
| не задано | Наследуется у более низкого слоя приоритета |
|
|
214
|
+
|
|
215
|
+
**Поля:**
|
|
216
|
+
|
|
217
|
+
| Поле | Где | По умолчанию | Описание |
|
|
218
|
+
| --- | --- | --- | --- |
|
|
219
|
+
| `defaults.ini` | `plugins` | — | Базовый INI для всех плагинов |
|
|
220
|
+
| `defaults.debug` | `plugins` | `false` | `true` — к строке плагина добавляется ` debug` |
|
|
221
|
+
| `rules[].match` | `plugins` | — | Glob-паттерн относительно `scripting/` |
|
|
222
|
+
| `rules[].enabled` | `plugins` | `true` | `false` — пропустить компиляцию и деплой |
|
|
223
|
+
| `rules[].ini` | `plugins` | `defaults.ini` | Значение `ini` для совпавших локальных плагинов |
|
|
224
|
+
| `rules[].debug` | `plugins` | `defaults.debug` | `debug` для совпавших локальных плагинов |
|
|
225
|
+
| `repos[].plugins.ini` | репо | `defaults.ini` | Значение `ini` для плагинов этого репо |
|
|
226
|
+
| `repos[].plugins.debug` | репо | `defaults.debug` | `debug` для плагинов этого репо |
|
|
227
|
+
|
|
228
|
+
**Приоритет:** `plugins.rules` → `repos[].plugins` → `plugins.defaults` → выключено (без INI). Правила `rules` действуют только на локальные плагины; `defaults` и `repos[].plugins` — на все.
|
|
229
|
+
|
|
230
|
+
Генерация INI включается сама, как только любое эффективное значение `ini` не `false` (задано в `defaults`, в правиле или на репо). Если `defaults.ini` при этом не задан, а INI включён только правилом или репо, все остальные плагины попадают в `plugins.ini`. Если `ini` не задано нигде — INI-файлы не создаются.
|
|
231
|
+
|
|
232
|
+
Старая форма `plugins:` — массив правил без `defaults` — ещё принимается. Поля `output.generate_ini`, верхнеуровневый `plugins_ini_postfix` и `repos[].plugins_ini_postfix` устарели: они продолжают работать, но при сборке пишут предупреждение. Используйте вместо них `plugins.defaults.ini` и `repos[].plugins`.
|
|
233
|
+
|
|
234
|
+
### Локальное переключение `debug` через `.env`
|
|
235
|
+
|
|
236
|
+
Рядом с манифестом можно положить `.env` (он в `.gitignore` и в репозиторий не попадает). Переменная `AMXB_PLUGINS_DEBUG` в этом файле принудительно включает или выключает суффикс ` debug` **у всех** плагинов в уже сгенерированных `plugins-*.ini`, независимо от манифеста:
|
|
237
|
+
|
|
238
|
+
```env
|
|
239
|
+
AMXB_PLUGINS_DEBUG=1
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
| Значение | Результат |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| `1` / `true` / `yes` / `on` | Принудительно добавить ` debug` каждому плагину в INI |
|
|
245
|
+
| `0` / `false` / `no` / `off` | Принудительно убрать ` debug` |
|
|
246
|
+
| пусто / не задано / другое | Без переопределения, решает манифест |
|
|
247
|
+
|
|
248
|
+
Значения регистронезависимы. Приоритет: `AMXB_PLUGINS_DEBUG` → `plugins.rules[].debug` / `repos[].plugins.debug` → `plugins.defaults.debug`. Переменная действует только на плагины, которые и так попадают в INI, и **не** включает генерацию INI сама по себе. Удобно для локальной отладки: включите ` debug` в `.env`, не коммитя правку в общий манифест.
|
|
173
249
|
|
|
174
250
|
## Удалённые ассеты
|
|
175
251
|
|
|
@@ -304,6 +380,72 @@ github:
|
|
|
304
380
|
|
|
305
381
|
Мапа применяется ко всему: репозитории из `repos:`, зависимости (`deps`/`DEPS_LIST`/`deps_override`), GitHub release-ассеты в `assets.sources` и команда `amxb deps-tree`. Для транзитивных зависимостей токен подбирается по владельцу автоматически.
|
|
306
382
|
|
|
383
|
+
## Локальные источники (repos/deps)
|
|
384
|
+
|
|
385
|
+
Запись в `repos:` или `deps:` можно взять не с GitHub, а из локальной папки. Содержимое читается как есть, как соседние `amxmodx/` и `assets/`: локальный репозиторий собирается вместе с проектом (его `.sma` компилируются), а локальная зависимость отдаёт свои `.inc` в `build/_includes/`.
|
|
386
|
+
|
|
387
|
+
**Локальный репозиторий** (полноценная часть сборки):
|
|
388
|
+
|
|
389
|
+
```yaml
|
|
390
|
+
repos:
|
|
391
|
+
- source: local
|
|
392
|
+
path: ../ProjectA # относительно папки манифеста (или абсолютный)
|
|
393
|
+
name: projecta # необязательно; по умолчанию basename пути
|
|
394
|
+
# также поддерживаются amxmodx_dir, plugins (ini/debug),
|
|
395
|
+
# exclude, exclude_files, deps_override
|
|
396
|
+
# устаревший plugins_ini_postfix ещё принимается (с предупреждением)
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
`path` обязателен, `name` необязателен. Внутренний id такой записи: `local/<name>`, по умолчанию `local/<basename пути>`. Поля `repo` и `ref` указывать нельзя. `DEPS_LIST` и `deps_override` локального репо продолжают работать.
|
|
400
|
+
|
|
401
|
+
**Локальная зависимость** (только `.inc` для компиляции):
|
|
402
|
+
|
|
403
|
+
```yaml
|
|
404
|
+
deps:
|
|
405
|
+
- source: local
|
|
406
|
+
path: ../Shared
|
|
407
|
+
name: shared # необязательно
|
|
408
|
+
include_path: scripting/include # необязательно
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
`path` обязателен, `name` и `include_path` необязательны. Поля `repo`, `ref`, `id`, `url`, `asset` запрещены. Строковая форма `owner/repo@ref[:include_path]` остаётся только для git, локальный dep задаётся объектом.
|
|
412
|
+
|
|
413
|
+
Все интерфейсы видят локальные источники одинаково: `amxb build`, `--dry-run`, `deps-tree`, `watch`, serve (`build.plan`) и MCP (`build_plan`, `get_dep_tree`, `validate_manifest`).
|
|
414
|
+
|
|
415
|
+
### Интеграция проектов через AMXB_LOCAL_SOURCES
|
|
416
|
+
|
|
417
|
+
Чтобы собрать проект B против локального чекаута проекта A (без релиза), манифест править не нужно. Переменная `AMXB_LOCAL_SOURCES` перенаправляет **уже объявленную** запись (`repos` или `deps`) на локальную папку по её логическому id `owner/repo` (регистр не важен). Новые записи она не создаёт.
|
|
418
|
+
|
|
419
|
+
```bash
|
|
420
|
+
# один источник
|
|
421
|
+
AMXB_LOCAL_SOURCES='AmxxModularEcosystem/VipModular=../VipModular' amxb build
|
|
422
|
+
|
|
423
|
+
# несколько: через ; или перевод строки
|
|
424
|
+
AMXB_LOCAL_SOURCES=$'Org/A=../A\nOrg/B=/abs/B' amxb build
|
|
425
|
+
|
|
426
|
+
# JSON-объект (значение начинается с {)
|
|
427
|
+
AMXB_LOCAL_SOURCES='{"Org/A":"../A","Org/B":"/abs/B"}' amxb build
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Пути считаются от папки манифеста (абсолютные тоже принимаются). Так как `.env` рядом с манифестом читается до разбора, редирект можно положить туда и держать в `.gitignore`.
|
|
431
|
+
|
|
432
|
+
**Приоритет источников:**
|
|
433
|
+
|
|
434
|
+
| Источник | Приоритет (↑ выше) |
|
|
435
|
+
| --- | --- |
|
|
436
|
+
| `AMXB_LOCAL_SOURCES` (env) | 1 |
|
|
437
|
+
| манифест, `source: local` | 2 |
|
|
438
|
+
| манифест, GitHub (`repo`/`ref`) | 3 |
|
|
439
|
+
|
|
440
|
+
`AMXB_LOCAL_STRICT=1` (или `true`) делает ошибкой id из env, для которого нет репо/dep в манифесте. Без него такой id просто пишет предупреждение и игнорируется. Каждый применённый редирект пишет предупреждение. Кривой синтаксис env тоже предупреждение, не фатальная ошибка.
|
|
441
|
+
|
|
442
|
+
### Ограничения локальных источников
|
|
443
|
+
|
|
444
|
+
- Локальные источники **не версионируются**: рабочее дерево читается как есть, поэтому сборка не воспроизводима. Это годится для разработки и интеграции, но не для релизов.
|
|
445
|
+
- `amxb watch` следит только за `amxmodx/` и `assets/` манифеста и **не** следит за папками локальных зависимостей: правки в локальном dep не запускают пересборку автоматически.
|
|
446
|
+
- Папка должна существовать: отсутствующий `path` или опечатка в нём дают жёсткую ошибку, если для этого id не задан редирект в `AMXB_LOCAL_SOURCES` (редирект имеет приоритет и может перекрыть устаревший `path`).
|
|
447
|
+
- Внутренний id локальной записи должен быть уникальным: если два источника дают одинаковый `local/<name>` (например, у папок совпал basename), сборка останавливается с ошибкой. Задайте разные `name`.
|
|
448
|
+
|
|
307
449
|
## GitHub Actions
|
|
308
450
|
|
|
309
451
|
```yaml
|
|
@@ -463,6 +605,54 @@ repos:
|
|
|
463
605
|
ref: latest # автоматически берёт тег последнего GitHub release
|
|
464
606
|
```
|
|
465
607
|
|
|
608
|
+
## Кэш резолва `ref`: `ref_ttl`
|
|
609
|
+
|
|
610
|
+
Поле `ref_ttl` управляет временем жизни кэша резолва `ref → SHA` (файл
|
|
611
|
+
`.ref-heads.json` в кэше сборки). Оно относится только к git-записям `repos[]`
|
|
612
|
+
и `deps[]` и не действует на `source: release`, `source: fungun`,
|
|
613
|
+
`source: local`, `ref: latest` и `ref` без значения (default branch).
|
|
614
|
+
|
|
615
|
+
Допустимые значения:
|
|
616
|
+
|
|
617
|
+
| Значение | Смысл |
|
|
618
|
+
| --- | --- |
|
|
619
|
+
| `never` | Кэшировать резолв вечно (регистронезависимо) |
|
|
620
|
+
| `Ns` / `Nm` / `Nh` / `Nd` | Длительность с целым N ≥ 1: секунды, минуты, часы, дни (например, `30m`, `1h`, `7d`) |
|
|
621
|
+
| целое число `N` | То же в секундах (например, `3600` = 1 час) |
|
|
622
|
+
|
|
623
|
+
Если поле не задано, TTL выводится из типа ref:
|
|
624
|
+
|
|
625
|
+
- именованный ref, который GitHub подтвердил как **тег** → кэш вечный;
|
|
626
|
+
- ветка, а также default branch при отсутствии `ref` → перепроверка раз в **1 час**.
|
|
627
|
+
|
|
628
|
+
Если одно и то же имя существует и как тег, и как ветка, приоритет у тега: такой
|
|
629
|
+
ref классифицируется как тег и кэшируется как неизменяемый.
|
|
630
|
+
|
|
631
|
+
Явный `ref_ttl` переопределяет дефолт для конкретной записи. Протухание TTL
|
|
632
|
+
инвалидирует только связку `ref → SHA`: каталог клона ключуется по SHA, поэтому
|
|
633
|
+
неизменённый SHA ничего не перекачивает. Если тег force-push'нули, вечный кэш
|
|
634
|
+
этого не заметит: обновите явный `ref_ttl` или выполните `amxb clean`.
|
|
635
|
+
|
|
636
|
+
```yaml
|
|
637
|
+
repos:
|
|
638
|
+
- repo: Org/VipModular
|
|
639
|
+
ref: main
|
|
640
|
+
ref_ttl: 30m # ветку перепроверять раз в 30 минут
|
|
641
|
+
|
|
642
|
+
- repo: Org/Stable
|
|
643
|
+
ref: v2.1.0
|
|
644
|
+
ref_ttl: never # тег: закрепить резолв навсегда
|
|
645
|
+
|
|
646
|
+
deps:
|
|
647
|
+
- repo: Org/ParamsController
|
|
648
|
+
ref: 1.4.2
|
|
649
|
+
ref_ttl: 7d # git-dep: перепроверка раз в неделю
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
Строковая форма deps (`owner/repo@ref[:include_path]`) и файл `DEPS_LIST` поле
|
|
653
|
+
`ref_ttl` не поддерживают: у них нет слота для этого поля. Используйте полную
|
|
654
|
+
(объектную) форму записи.
|
|
655
|
+
|
|
466
656
|
## deps: fungun.net
|
|
467
657
|
|
|
468
658
|
[fungun.net](https://fungun.net) — магазин закрытых AMXX-плагинов. Архивов и
|
|
@@ -628,9 +818,10 @@ curl -fsSL https://raw.githubusercontent.com/AmxxModularEcosystem/amxx-builder/m
|
|
|
628
818
|
|
|
629
819
|
| Что | Порядок (↑ выше) |
|
|
630
820
|
| --- | --- |
|
|
631
|
-
| плагины `plugins
|
|
632
|
-
| `
|
|
821
|
+
| плагины (`plugins.rules`) | правила применяются по порядку, первое совпадение побеждает |
|
|
822
|
+
| INI плагинов | `plugins.rules` → `repos[].plugins` → `plugins.defaults` → выключено |
|
|
633
823
|
| зависимости | `manifest.deps` → `deps_override` → `DEPS_LIST` файл в репо |
|
|
824
|
+
| локальные источники | `AMXB_LOCAL_SOURCES` (env) → манифест `source: local` → GitHub |
|
|
634
825
|
| ассеты | порядок в `sources:` + `on_conflict` |
|
|
635
826
|
| версия компилятора | `amxmodx.version` → последний релиз |
|
|
636
827
|
| значения манифеста | `--set` → манифест проекта → `defaults/amxbuild.defaults.yml` |
|
|
@@ -651,3 +842,30 @@ sudo dpkg --add-architecture i386
|
|
|
651
842
|
sudo apt update
|
|
652
843
|
sudo apt install libc6:i386 libstdc++6:i386
|
|
653
844
|
```
|
|
845
|
+
|
|
846
|
+
### Сборка падает на `/mnt/...` в WSL
|
|
847
|
+
|
|
848
|
+
`amxxpc` — 32-битный Linux-бинарник, но под WSL он **не читает** файлы на
|
|
849
|
+
Windows-дисках, смонтированных в `/mnt/*` (DrvFs/9p: `/mnt/c`, `/mnt/d`, `/mnt/j`, …).
|
|
850
|
+
Node/bash эти файлы видят, поэтому ошибка выглядит «мистической»:
|
|
851
|
+
|
|
852
|
+
- локальный `.sma` на `/mnt/*` → `fatal error 100: cannot read from file: ".../plugin.sma"`;
|
|
853
|
+
- локальная папка `include/` на `/mnt/*` (идёт как `-i`) → `std::bad_alloc` / `Aborted` (SIGABRT).
|
|
854
|
+
|
|
855
|
+
Плагины из `repos:` при этом собираются нормально — их исходники amxb кладёт в
|
|
856
|
+
нативный кэш `~/.cache/amxx-builder`. Запись `.amxx` на `/mnt/*`, наоборот, работает.
|
|
857
|
+
|
|
858
|
+
**Решение:** собирать из нативной Linux-файловой системы. Скопируйте проект в `~` —
|
|
859
|
+
кэш компилятора и зависимостей общий, заново ничего не скачивается:
|
|
860
|
+
|
|
861
|
+
```bash
|
|
862
|
+
mkdir -p ~/amxb-build && cp -r amxmodx assets amxbuild.yml ~/amxb-build/
|
|
863
|
+
cd ~/amxb-build && amxb build
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
Альтернативы: выполнять сборку на Windows (`build.bat` или `amxb build` в PowerShell)
|
|
867
|
+
либо в CI. Команды без запуска компилятора (`amxb validate`, `amxb deps-tree`,
|
|
868
|
+
`amxb build --dry-run`) под `/mnt/*` работают.
|
|
869
|
+
|
|
870
|
+
> `amxb build --build-dir <нативный путь>` **не** решает проблему: локальные `.sma`
|
|
871
|
+
> компилируются по месту, из папки проекта, а не из `build/`.
|
package/action.yml
CHANGED
|
@@ -34,7 +34,7 @@ inputs:
|
|
|
34
34
|
Override any manifest field. One entry per line:
|
|
35
35
|
version=1.2.3
|
|
36
36
|
output.archive_name={name}-{version}.zip
|
|
37
|
-
|
|
37
|
+
plugins.defaults.ini=myserver
|
|
38
38
|
github.tokens.MyOrg=GITHUB_TOKEN_MYORG
|
|
39
39
|
required: false
|
|
40
40
|
github-token:
|
|
@@ -7,6 +7,7 @@ version: "1.0.0"
|
|
|
7
7
|
amxmodx:
|
|
8
8
|
dir: amxmodx
|
|
9
9
|
defines: []
|
|
10
|
+
exclude_files: []
|
|
10
11
|
|
|
11
12
|
github:
|
|
12
13
|
token_env: GITHUB_TOKEN
|
|
@@ -21,7 +22,6 @@ output:
|
|
|
21
22
|
amxmodx_path: "{name}/addons/amxmodx"
|
|
22
23
|
assets_path: "{name}"
|
|
23
24
|
readme: true
|
|
24
|
-
generate_ini: false
|
|
25
25
|
pack: true
|
|
26
26
|
on_conflict: last_wins
|
|
27
27
|
|
package/mcp/handlers.js
CHANGED
|
@@ -9,12 +9,13 @@ const { resolveRefIfLatest } = require('../src/repo-fetcher');
|
|
|
9
9
|
const { fetchDepRoot, collectDepIncludeDirs } = require('../src/deps-resolver');
|
|
10
10
|
const { resolveAssets, readAssets, readDepManifest, collectDepAssets, collectLocalAssets } = require('../src/agent-assets');
|
|
11
11
|
const { getCompilerInfo, resolveStdlibVersion } = require('../src/compiler-fetcher');
|
|
12
|
-
const { resolveManifest, resolveGithubToken, parseDepString, parseDepObject, parseDocEntries, parseSkillEntries } = require('../src/manifest');
|
|
12
|
+
const { resolveManifest, resolveGithubToken, parseDepString, parseDepObject, parseRefTtl, parseDocEntries, parseSkillEntries } = require('../src/manifest');
|
|
13
13
|
const { parseManifest } = require('../src/manifest');
|
|
14
14
|
const { validateManifestFile } = require('../src/validate');
|
|
15
15
|
const { getManifestSchema } = require('../src/schema');
|
|
16
16
|
const { getCacheInfo } = require('../src/cache-info');
|
|
17
17
|
const { buildDepTree, assembleRootDeps } = require('../src/deps-tree');
|
|
18
|
+
const { isLocal, resolveLocalEntry } = require('../src/local-sources');
|
|
18
19
|
const { buildIncludeTree, fetchDepIncludeDir, parseIncludeDirective, searchIncludeFile, collectIncFiles } = require('../src/include-tree');
|
|
19
20
|
const { listReleases, listTags } = require('../src/release-lister');
|
|
20
21
|
const { buildPlanData } = require('../src/build-plan');
|
|
@@ -80,7 +81,21 @@ function parseDep(raw) {
|
|
|
80
81
|
throw new Error('Dep must be a string or an object');
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
// A local dep's declared `path` is relative to the manifest dir when one is in
|
|
85
|
+
// scope, else the process cwd.
|
|
86
|
+
function depBaseDir(args) {
|
|
87
|
+
return args?.manifest ? path.dirname(path.resolve(args.manifest)) : process.cwd();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Materialize a user-supplied `source: local` dep (parsed to `_localPathRaw`)
|
|
91
|
+
// into an absolute `_localDir` via the core helper — no path logic in adapters.
|
|
92
|
+
function resolveUserDep(dep, args) {
|
|
93
|
+
return resolveLocalEntry(dep, depBaseDir(args));
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
function resolveDepRef(dep, token) {
|
|
97
|
+
// Local sources have no remote ref to resolve — never touch the network.
|
|
98
|
+
if (isLocal(dep)) return 'local';
|
|
84
99
|
return resolveRefIfLatest(dep.ref, dep.repo, token);
|
|
85
100
|
}
|
|
86
101
|
|
|
@@ -161,7 +176,7 @@ async function handleGetDepInterface(args, token, noFetch) {
|
|
|
161
176
|
token = fallbackToken(token);
|
|
162
177
|
let dep;
|
|
163
178
|
try {
|
|
164
|
-
dep = parseDep(args?.dep || args);
|
|
179
|
+
dep = resolveUserDep(parseDep(args?.dep || args), args);
|
|
165
180
|
} catch (parseErr) {
|
|
166
181
|
return errorResult(parseErr.message);
|
|
167
182
|
}
|
|
@@ -206,7 +221,7 @@ async function handleListDepIncs(args, token, noFetch) {
|
|
|
206
221
|
token = fallbackToken(token);
|
|
207
222
|
let dep;
|
|
208
223
|
try {
|
|
209
|
-
dep = parseDep(args?.dep || args);
|
|
224
|
+
dep = resolveUserDep(parseDep(args?.dep || args), args);
|
|
210
225
|
} catch (parseErr) {
|
|
211
226
|
return errorResult(parseErr.message);
|
|
212
227
|
}
|
|
@@ -238,7 +253,9 @@ async function handleGetDepTree(args, token, noFetch) {
|
|
|
238
253
|
let tokenFor = null;
|
|
239
254
|
|
|
240
255
|
if (args?.manifest) {
|
|
241
|
-
const
|
|
256
|
+
const manifestPath = path.resolve(args.manifest);
|
|
257
|
+
loadEnv(manifestPath, { quiet: true });
|
|
258
|
+
const manifest = parseManifest(manifestPath);
|
|
242
259
|
tokenFor = (repo) => resolveGithubToken(manifest, repo);
|
|
243
260
|
const assembled = assembleRootDeps(manifest);
|
|
244
261
|
rootDeps = assembled.rootDeps;
|
|
@@ -249,7 +266,15 @@ async function handleGetDepTree(args, token, noFetch) {
|
|
|
249
266
|
const parsed = parseDep(entry);
|
|
250
267
|
return { repo: parsed.repo, ref: parsed.ref, source: parsed.source, include_path: parsed.include_path, asset: parsed.asset };
|
|
251
268
|
}
|
|
252
|
-
return {
|
|
269
|
+
return {
|
|
270
|
+
repo: entry.repo,
|
|
271
|
+
ref: entry.ref,
|
|
272
|
+
source: entry.source || 'git',
|
|
273
|
+
include_path: entry.include_path || null,
|
|
274
|
+
asset: entry.asset != null ? entry.asset : null,
|
|
275
|
+
_localDir: entry._localDir || null,
|
|
276
|
+
...(entry.ref_ttl != null ? { ref_ttl: parseRefTtl(entry.ref_ttl, `deps entry ${entry.repo}`) } : {}),
|
|
277
|
+
};
|
|
253
278
|
});
|
|
254
279
|
} else {
|
|
255
280
|
return errorResult('Provide either "manifest" or "deps"', -32602);
|
|
@@ -345,6 +370,7 @@ async function resolveStdlibState(args, noFetch) {
|
|
|
345
370
|
if (!args?.version) {
|
|
346
371
|
const manifestPath = resolveManifestPath(args?.manifest || undefined).path;
|
|
347
372
|
if (fs.existsSync(manifestPath)) {
|
|
373
|
+
loadEnv(manifestPath, { quiet: true });
|
|
348
374
|
try {
|
|
349
375
|
manifest = parseManifest(manifestPath);
|
|
350
376
|
} catch (err) {
|
|
@@ -468,6 +494,7 @@ async function handleResolveInclude(args, token, noFetch) {
|
|
|
468
494
|
const manifestPath = resolveManifestPath(args?.manifest || undefined).path;
|
|
469
495
|
const depErrors = [];
|
|
470
496
|
if (fs.existsSync(manifestPath)) {
|
|
497
|
+
loadEnv(manifestPath, { quiet: true });
|
|
471
498
|
let manifest = null;
|
|
472
499
|
try {
|
|
473
500
|
manifest = parseManifest(manifestPath);
|
|
@@ -543,7 +570,7 @@ async function handleBuildPlan(args) {
|
|
|
543
570
|
|
|
544
571
|
/**
|
|
545
572
|
* Build a parsed dep object from MCP tool args: either a full `dep` string/object
|
|
546
|
-
* or explicit { repo, ref?, source?, include_path?, asset? } fields.
|
|
573
|
+
* or explicit { repo, ref?, source?, include_path?, asset?, ref_ttl? } fields.
|
|
547
574
|
* Preserves the arg-shape handling of the former inline fetchDepRoot.
|
|
548
575
|
*/
|
|
549
576
|
function depFromArgs(args) {
|
|
@@ -555,12 +582,19 @@ function depFromArgs(args) {
|
|
|
555
582
|
const source = args.source || 'git';
|
|
556
583
|
// Release deps need a ref — default to 'latest' when omitted.
|
|
557
584
|
const ref = args.ref || (source === 'release' ? 'latest' : null);
|
|
558
|
-
dep = {
|
|
585
|
+
dep = {
|
|
586
|
+
repo: args.repo,
|
|
587
|
+
ref,
|
|
588
|
+
source,
|
|
589
|
+
include_path: args.include_path || null,
|
|
590
|
+
asset: args.asset ?? null,
|
|
591
|
+
...(args.ref_ttl != null ? { ref_ttl: parseRefTtl(args.ref_ttl, `deps entry ${args.repo}`) } : {}),
|
|
592
|
+
};
|
|
559
593
|
}
|
|
560
594
|
if (args?.source) dep.source = args.source;
|
|
561
595
|
if (args?.include_path) dep.include_path = args.include_path;
|
|
562
596
|
if (args?.asset != null) dep.asset = args.asset;
|
|
563
|
-
return dep;
|
|
597
|
+
return resolveUserDep(dep, args);
|
|
564
598
|
}
|
|
565
599
|
|
|
566
600
|
async function handleListRepoFiles(args, token, noFetch) {
|
|
@@ -654,7 +688,9 @@ function isDepMode(args) {
|
|
|
654
688
|
|
|
655
689
|
// Local-mode assets come from the current project's own manifest `docs:`/`skills:`.
|
|
656
690
|
function resolveLocalAssets(args) {
|
|
657
|
-
|
|
691
|
+
const manifestPath = resolveManifestPath(args?.manifest).path;
|
|
692
|
+
loadEnv(manifestPath, { quiet: true });
|
|
693
|
+
return collectLocalAssets(parseManifest(manifestPath));
|
|
658
694
|
}
|
|
659
695
|
|
|
660
696
|
// Shared empty-state text for the list/get handlers (never an error).
|
|
@@ -889,6 +925,7 @@ async function handleCompileSma(args, token, noFetch) {
|
|
|
889
925
|
const depErrors = [];
|
|
890
926
|
const manifestPath = resolveManifestPath(args?.manifest || undefined).path;
|
|
891
927
|
if (fs.existsSync(manifestPath)) {
|
|
928
|
+
loadEnv(manifestPath, { quiet: true });
|
|
892
929
|
let manifest = null;
|
|
893
930
|
try {
|
|
894
931
|
manifest = parseManifest(manifestPath);
|
|
@@ -952,6 +989,7 @@ async function handleCompileSma(args, token, noFetch) {
|
|
|
952
989
|
async function handleResolveAssets(args) {
|
|
953
990
|
const manifestPath = resolveManifestPath(args?.manifest).path;
|
|
954
991
|
const fullPath = path.resolve(manifestPath);
|
|
992
|
+
loadEnv(fullPath, { quiet: true });
|
|
955
993
|
|
|
956
994
|
let manifest;
|
|
957
995
|
try {
|
|
@@ -1008,6 +1046,7 @@ async function handleSearchSymbol(args, token, noFetch) {
|
|
|
1008
1046
|
const manifestPath = resolveManifestPath(args?.manifest || undefined).path;
|
|
1009
1047
|
let manifest = null;
|
|
1010
1048
|
if (fs.existsSync(manifestPath)) {
|
|
1049
|
+
loadEnv(manifestPath, { quiet: true });
|
|
1011
1050
|
try {
|
|
1012
1051
|
manifest = parseManifest(manifestPath);
|
|
1013
1052
|
} catch (err) {
|
|
@@ -1038,7 +1077,9 @@ async function handleSearchSymbol(args, token, noFetch) {
|
|
|
1038
1077
|
// canonical core helper (sequential, ssh-aware); user deps stay individual
|
|
1039
1078
|
// parallel jobs.
|
|
1040
1079
|
const manifestDeps = manifest?.globalDeps?.length ? manifest.globalDeps : [];
|
|
1041
|
-
const userDeps = manifest?.globalDeps?.length
|
|
1080
|
+
const userDeps = manifest?.globalDeps?.length
|
|
1081
|
+
? []
|
|
1082
|
+
: (args?.deps || []).map((entry) => resolveUserDep(parseDep(entry), args));
|
|
1042
1083
|
if (scope === 'all' || scope === 'deps') {
|
|
1043
1084
|
const ssh = manifest?.github?.ssh === true;
|
|
1044
1085
|
if (manifestDeps.length) {
|