mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
115 lines
5.3 KiB
JavaScript
115 lines
5.3 KiB
JavaScript
/**
|
|
* @fileoverview Reads the generated CLI catalogue for the Docker build.
|
|
*
|
|
* `scripts/build-agent-image.mjs` is a `.mjs` and cannot import the TypeScript registry, so it
|
|
* reads `config/clis.stock.json` (generated by `scripts/generate-cli-catalog.mts`) instead.
|
|
* The pure half lives here so `src/docker-hosts.ts`'s programmatic mirror of the same build
|
|
* command can be pinned against it by a test — those two produce the docker argv independently
|
|
* and must not drift.
|
|
*/
|
|
import { readFileSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const CATALOG_PATH = fileURLToPath(new URL('../../config/clis.stock.json', import.meta.url));
|
|
|
|
/**
|
|
* npm package names the AGENT image installs in its shared `npm install -g` layer.
|
|
*
|
|
* PURE: takes the parsed catalogue, returns a sorted-by-registry-order list.
|
|
*
|
|
* ⚠️ Filters on `enabled`. That is the field the earlier attempt's export omitted, which is
|
|
* how a CLI that ships disabled still had its package baked into every image.
|
|
*
|
|
* ⚠️ An entry carrying `discovery.install.agentImageLayer` is excluded here and installed by
|
|
* its own hand-written Dockerfile layer instead, because the registry cannot express what
|
|
* makes it special — a flag, a companion package, or not being on npm at all. This used to be
|
|
* an id-keyed table duplicated between this file and `src/docker-hosts.ts` (exactly the shape
|
|
* `test/cli-registry-no-id-branching.test.ts` exists to forbid inside `src/`, which is why it
|
|
* was a blind spot rather than a pass — that test scans `src/` only). It is data now: both
|
|
* producers filter on the SAME field from the SAME catalogue entry, `reason` is required by
|
|
* `schema.ts`, and `test/docker-agent-image-coverage.test.ts` requires every one of them to
|
|
* still be present in the Dockerfile, so an exclusion cannot quietly become an omission.
|
|
*/
|
|
/** Tokens allowed in an npm package name reaching a Dockerfile build arg unquoted. */
|
|
const SAFE_PACKAGE = /^[@A-Za-z0-9][@A-Za-z0-9/._-]*$/;
|
|
|
|
export function agentImageNpmPackages(catalog) {
|
|
const packages = [];
|
|
for (const entry of catalog) {
|
|
if (!entry.enabled) continue;
|
|
if (entry.discovery?.install?.agentImageLayer) continue;
|
|
const pkg = entry.discovery?.install?.npmPackage;
|
|
if (!pkg) continue; // antigravity/grok/omp ship standalone installers, not npm
|
|
if (!SAFE_PACKAGE.test(pkg)) {
|
|
// The value is interpolated into a Dockerfile ARG that is expanded UNQUOTED (word
|
|
// splitting is how the list becomes several arguments), so a token with whitespace or
|
|
// shell metacharacters would change what the RUN line means.
|
|
// ⚠️ This exact regex is duplicated in `agentImageNpmPackages()` in
|
|
// `src/docker-hosts.ts` (that file cannot import this one — it is the TypeScript side of
|
|
// the same two-producers split this whole module exists for). Keep both literal patterns
|
|
// identical; `test/agent-image-build-args-parity.test.ts` pins that they are.
|
|
throw new Error(`Refusing unsafe npm package name for "${entry.id}": ${JSON.stringify(pkg)}`);
|
|
}
|
|
packages.push(pkg);
|
|
}
|
|
return packages;
|
|
}
|
|
|
|
/**
|
|
* Environment variable → agent.Dockerfile ARG for the optional git-host CLIs (gh, az).
|
|
* ⚠️ Mirrored by `GIT_HOST_CLI_BUILD_ARGS` in `src/docker-hosts.ts`; the parity test pins them.
|
|
*/
|
|
export const GIT_HOST_CLI_BUILD_ARGS = [
|
|
['CODEMAN_AGENT_IMAGE_INSTALL_GH', 'CODEMAN_INSTALL_GH'],
|
|
['CODEMAN_AGENT_IMAGE_INSTALL_AZ', 'CODEMAN_INSTALL_AZ'],
|
|
];
|
|
|
|
/** Environment variables passed through to the agent image's system Git configuration. */
|
|
export const GIT_IDENTITY_BUILD_ARGS = [
|
|
['CODEMAN_AGENT_IMAGE_GIT_USER_NAME', 'GIT_USER_NAME'],
|
|
['CODEMAN_AGENT_IMAGE_GIT_USER_EMAIL', 'GIT_USER_EMAIL'],
|
|
];
|
|
|
|
/**
|
|
* The `--build-arg` pairs for the optional git-host CLIs. PURE. An unset or empty variable
|
|
* contributes NOTHING, so the Dockerfile's own default (off) applies and the argv is the same
|
|
* as before these existed; anything other than 0/1 is refused rather than guessed at.
|
|
*/
|
|
export function gitHostCliBuildArgPairs(env) {
|
|
const pairs = [];
|
|
for (const [envName, argName] of GIT_HOST_CLI_BUILD_ARGS) {
|
|
const value = env[envName];
|
|
if (value === undefined || value === '') continue;
|
|
if (value !== '0' && value !== '1') {
|
|
throw new Error(`${envName} must be 0 or 1, got ${JSON.stringify(value)}`);
|
|
}
|
|
pairs.push([argName, value]);
|
|
}
|
|
return pairs;
|
|
}
|
|
|
|
/** The `--build-arg` pairs for Git identity, requiring either both values or neither. */
|
|
export function gitIdentityBuildArgPairs(env) {
|
|
const pairs = GIT_IDENTITY_BUILD_ARGS.map(([envName, argName]) => [argName, env[envName] ?? '']);
|
|
const configured = pairs.filter(([, value]) => value !== '');
|
|
if (configured.length === 0) return [];
|
|
if (configured.length !== pairs.length) {
|
|
throw new Error('Git user name and email must both be set when configuring Git identity');
|
|
}
|
|
return pairs;
|
|
}
|
|
|
|
/** The `--build-arg` pairs the agent image takes. PURE given `env`. */
|
|
export function agentImageBuildArgPairs(catalog, env = process.env) {
|
|
return [
|
|
['CLI_NPM_PACKAGES', agentImageNpmPackages(catalog).join(' ')],
|
|
...gitHostCliBuildArgPairs(env),
|
|
...gitIdentityBuildArgPairs(env),
|
|
];
|
|
}
|
|
|
|
/** Read the committed catalogue. IO. */
|
|
export function readCatalog(path = CATALOG_PATH) {
|
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
}
|