@pellux/goodvibes-toolchain 1.11.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/README.md +50 -0
- package/dist/bin/build-binaries.d.ts +3 -0
- package/dist/bin/build-binaries.d.ts.map +1 -0
- package/dist/bin/build-binaries.js +67 -0
- package/dist/bin/changelog-gate.d.ts +3 -0
- package/dist/bin/changelog-gate.d.ts.map +1 -0
- package/dist/bin/changelog-gate.js +13 -0
- package/dist/bin/coverage-gate.d.ts +3 -0
- package/dist/bin/coverage-gate.d.ts.map +1 -0
- package/dist/bin/coverage-gate.js +19 -0
- package/dist/bin/package-install-check.d.ts +3 -0
- package/dist/bin/package-install-check.d.ts.map +1 -0
- package/dist/bin/package-install-check.js +14 -0
- package/dist/bin/per-job-green.d.ts +3 -0
- package/dist/bin/per-job-green.d.ts.map +1 -0
- package/dist/bin/per-job-green.js +54 -0
- package/dist/bin/post-build-smoke.d.ts +3 -0
- package/dist/bin/post-build-smoke.d.ts.map +1 -0
- package/dist/bin/post-build-smoke.js +14 -0
- package/dist/bin/publish-package.d.ts +3 -0
- package/dist/bin/publish-package.d.ts.map +1 -0
- package/dist/bin/publish-package.js +25 -0
- package/dist/bin/release-cut.d.ts +3 -0
- package/dist/bin/release-cut.d.ts.map +1 -0
- package/dist/bin/release-cut.js +27 -0
- package/dist/bin/sdk-pin-gate.d.ts +3 -0
- package/dist/bin/sdk-pin-gate.d.ts.map +1 -0
- package/dist/bin/sdk-pin-gate.js +15 -0
- package/dist/bin/sha256sums.d.ts +3 -0
- package/dist/bin/sha256sums.d.ts.map +1 -0
- package/dist/bin/sha256sums.js +37 -0
- package/dist/bin/verification-ledger.d.ts +3 -0
- package/dist/bin/verification-ledger.d.ts.map +1 -0
- package/dist/bin/verification-ledger.js +28 -0
- package/dist/config.d.ts +147 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +62 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/lib/build-binaries.d.ts +45 -0
- package/dist/lib/build-binaries.d.ts.map +1 -0
- package/dist/lib/build-binaries.js +98 -0
- package/dist/lib/changelog-gate.d.ts +18 -0
- package/dist/lib/changelog-gate.d.ts.map +1 -0
- package/dist/lib/changelog-gate.js +32 -0
- package/dist/lib/coverage-gate.d.ts +25 -0
- package/dist/lib/coverage-gate.d.ts.map +1 -0
- package/dist/lib/coverage-gate.js +46 -0
- package/dist/lib/effects.d.ts +53 -0
- package/dist/lib/effects.d.ts.map +1 -0
- package/dist/lib/effects.js +78 -0
- package/dist/lib/load-config.d.ts +8 -0
- package/dist/lib/load-config.d.ts.map +1 -0
- package/dist/lib/load-config.js +15 -0
- package/dist/lib/package-install-check.d.ts +49 -0
- package/dist/lib/package-install-check.d.ts.map +1 -0
- package/dist/lib/package-install-check.js +66 -0
- package/dist/lib/per-job-green.d.ts +54 -0
- package/dist/lib/per-job-green.d.ts.map +1 -0
- package/dist/lib/per-job-green.js +247 -0
- package/dist/lib/post-build-smoke.d.ts +24 -0
- package/dist/lib/post-build-smoke.d.ts.map +1 -0
- package/dist/lib/post-build-smoke.js +33 -0
- package/dist/lib/publish-package.d.ts +45 -0
- package/dist/lib/publish-package.d.ts.map +1 -0
- package/dist/lib/publish-package.js +58 -0
- package/dist/lib/release-cut.d.ts +52 -0
- package/dist/lib/release-cut.d.ts.map +1 -0
- package/dist/lib/release-cut.js +120 -0
- package/dist/lib/sdk-pin-gate.d.ts +25 -0
- package/dist/lib/sdk-pin-gate.d.ts.map +1 -0
- package/dist/lib/sdk-pin-gate.js +146 -0
- package/dist/lib/sha256sums.d.ts +41 -0
- package/dist/lib/sha256sums.d.ts.map +1 -0
- package/dist/lib/sha256sums.js +53 -0
- package/dist/lib/verification-ledger.d.ts +30 -0
- package/dist/lib/verification-ledger.d.ts.map +1 -0
- package/dist/lib/verification-ledger.js +37 -0
- package/package.json +59 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* toolchain.config contract.
|
|
3
|
+
*
|
|
4
|
+
* Every GoodVibes repo keeps a small `toolchain.config.json` (or a
|
|
5
|
+
* `toolchain.config.ts` that default-exports a {@link ToolchainConfig}) at its
|
|
6
|
+
* root. The published `@pellux/goodvibes-toolchain` package holds the behavior;
|
|
7
|
+
* the config holds the repo-specific values (package names, binary matrix,
|
|
8
|
+
* coverage floors, publish ordering) so one implementation serves every repo.
|
|
9
|
+
*
|
|
10
|
+
* The full contract and per-repo examples are documented in
|
|
11
|
+
* `docs/release-and-publishing.md` (SDK repo).
|
|
12
|
+
*/
|
|
13
|
+
export const DEFAULT_SDK_PACKAGE = '@pellux/goodvibes-sdk';
|
|
14
|
+
export const DEFAULT_LOCKFILE = 'bun.lock';
|
|
15
|
+
export const DEFAULT_REGISTRY = 'https://registry.npmjs.org';
|
|
16
|
+
export const DEFAULT_POLL_INTERVAL_MS = 20_000;
|
|
17
|
+
export const DEFAULT_DEADLINE_MS = 1_800_000;
|
|
18
|
+
// Default transient-error posture: the GitHub API's observed flaky mode makes
|
|
19
|
+
// single-shot calls fail a meaningful fraction of the time, so every call gets
|
|
20
|
+
// ~8 bounded attempts with sleeps in the 5-10s band before its status is final.
|
|
21
|
+
export const DEFAULT_RETRY_ATTEMPTS = 8;
|
|
22
|
+
export const DEFAULT_RETRY_DELAY_MS = 7_000;
|
|
23
|
+
/** Fill an SdkPinConfig with the conventional defaults for any missing field. */
|
|
24
|
+
export function resolveSdkPinConfig(partial) {
|
|
25
|
+
const sdkPackage = partial?.sdkPackage ?? DEFAULT_SDK_PACKAGE;
|
|
26
|
+
return {
|
|
27
|
+
sdkPackage,
|
|
28
|
+
pinSource: partial?.pinSource ?? 'dependencies',
|
|
29
|
+
lockfile: partial?.lockfile ?? DEFAULT_LOCKFILE,
|
|
30
|
+
overlayMarker: partial?.overlayMarker ?? `node_modules/${sdkPackage}/.local-sdk-overlay.json`,
|
|
31
|
+
sourceRoots: partial?.sourceRoots ?? ['src'],
|
|
32
|
+
enforceExportsMap: partial?.enforceExportsMap ?? false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Fill a PerJobGreenConfig from a partial plus required identity fields. */
|
|
36
|
+
export function resolvePerJobGreenConfig(partial) {
|
|
37
|
+
return {
|
|
38
|
+
owner: partial.owner,
|
|
39
|
+
repo: partial.repo,
|
|
40
|
+
workflow: partial.workflow ?? 'ci.yml',
|
|
41
|
+
event: partial.event ?? 'push',
|
|
42
|
+
pollIntervalMs: partial.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS,
|
|
43
|
+
deadlineMs: partial.deadlineMs ?? DEFAULT_DEADLINE_MS,
|
|
44
|
+
retryAttempts: partial.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS,
|
|
45
|
+
retryDelayMs: partial.retryDelayMs ?? DEFAULT_RETRY_DELAY_MS,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse a JSON toolchain config. Kept separate from disk I/O so callers can
|
|
50
|
+
* validate a config object from any source (file, env, test fixture).
|
|
51
|
+
*/
|
|
52
|
+
export function parseToolchainConfig(raw) {
|
|
53
|
+
const value = JSON.parse(raw);
|
|
54
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
55
|
+
throw new Error('toolchain.config must be a JSON object.');
|
|
56
|
+
}
|
|
57
|
+
const record = value;
|
|
58
|
+
if (typeof record.packageName !== 'string' || record.packageName.length === 0) {
|
|
59
|
+
throw new Error('toolchain.config.packageName is required and must be a non-empty string.');
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pellux/goodvibes-toolchain
|
|
3
|
+
*
|
|
4
|
+
* Shared GoodVibes CI/CD tooling. The published library exports every tool as a
|
|
5
|
+
* policy function with injectable effects (so callers and tests supply their own
|
|
6
|
+
* I/O), plus the {@link ToolchainConfig} contract that parameterizes each repo.
|
|
7
|
+
* The package also ships a thin CLI per tool (see `bin` in package.json).
|
|
8
|
+
*/
|
|
9
|
+
export * from './config.js';
|
|
10
|
+
export * from './lib/effects.js';
|
|
11
|
+
export * from './lib/load-config.js';
|
|
12
|
+
export * from './lib/sdk-pin-gate.js';
|
|
13
|
+
export * from './lib/build-binaries.js';
|
|
14
|
+
export * from './lib/release-cut.js';
|
|
15
|
+
export * from './lib/coverage-gate.js';
|
|
16
|
+
export * from './lib/verification-ledger.js';
|
|
17
|
+
export * from './lib/post-build-smoke.js';
|
|
18
|
+
export * from './lib/package-install-check.js';
|
|
19
|
+
export * from './lib/publish-package.js';
|
|
20
|
+
export * from './lib/per-job-green.js';
|
|
21
|
+
export * from './lib/changelog-gate.js';
|
|
22
|
+
export * from './lib/sha256sums.js';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pellux/goodvibes-toolchain
|
|
3
|
+
*
|
|
4
|
+
* Shared GoodVibes CI/CD tooling. The published library exports every tool as a
|
|
5
|
+
* policy function with injectable effects (so callers and tests supply their own
|
|
6
|
+
* I/O), plus the {@link ToolchainConfig} contract that parameterizes each repo.
|
|
7
|
+
* The package also ships a thin CLI per tool (see `bin` in package.json).
|
|
8
|
+
*/
|
|
9
|
+
export * from './config.js';
|
|
10
|
+
export * from './lib/effects.js';
|
|
11
|
+
export * from './lib/load-config.js';
|
|
12
|
+
export * from './lib/sdk-pin-gate.js';
|
|
13
|
+
export * from './lib/build-binaries.js';
|
|
14
|
+
export * from './lib/release-cut.js';
|
|
15
|
+
export * from './lib/coverage-gate.js';
|
|
16
|
+
export * from './lib/verification-ledger.js';
|
|
17
|
+
export * from './lib/post-build-smoke.js';
|
|
18
|
+
export * from './lib/package-install-check.js';
|
|
19
|
+
export * from './lib/publish-package.js';
|
|
20
|
+
export * from './lib/per-job-green.js';
|
|
21
|
+
export * from './lib/changelog-gate.js';
|
|
22
|
+
export * from './lib/sha256sums.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* build-binaries — compiles standalone binaries for the configured matrix.
|
|
3
|
+
*
|
|
4
|
+
* Absorbs tui/build.ts and agent/build.ts. The TUI's daemon leg (a second
|
|
5
|
+
* `bun build --compile` of a daemon entrypoint per target) is config-driven:
|
|
6
|
+
* a target/build with a `daemonEntrypoint` + `daemonArtifact` builds two
|
|
7
|
+
* binaries, one without builds one. Native addons (sqlite-vec) stay `--external`
|
|
8
|
+
* and are copied beside the binary; a same-platform miss is fatal, a
|
|
9
|
+
* cross-target miss triggers an `npm pack` + `tar` fetch.
|
|
10
|
+
*/
|
|
11
|
+
import type { Exec, Logger } from './effects.js';
|
|
12
|
+
import type { BinaryTarget, BuildConfig } from '../config.js';
|
|
13
|
+
/** Resolve which targets to build and whether the daemon leg is forced off/only. */
|
|
14
|
+
export interface TargetSelection {
|
|
15
|
+
readonly targets: readonly BinaryTarget[];
|
|
16
|
+
readonly daemonOnly: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the target selection from argv against the matrix.
|
|
20
|
+
* - `--all` → every target.
|
|
21
|
+
* - `--target <key>` → that target; a `daemon-<key>` alias forces daemon-only.
|
|
22
|
+
* - `--daemon-only` → daemon leg only.
|
|
23
|
+
* - none → the native `${platform}-${arch}` row.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveTargets(argv: readonly string[], config: BuildConfig, nativeKey: string): TargetSelection;
|
|
26
|
+
/** Construct the `bun build --compile` argv for one entrypoint. */
|
|
27
|
+
export declare function buildCompileArgs(entrypoint: string, bunTarget: string, outfile: string, externals: readonly string[]): string[];
|
|
28
|
+
export interface BuildOutcome {
|
|
29
|
+
readonly key: string;
|
|
30
|
+
readonly ok: boolean;
|
|
31
|
+
readonly detail: string;
|
|
32
|
+
}
|
|
33
|
+
export interface RunBuildOptions {
|
|
34
|
+
readonly cwd: string;
|
|
35
|
+
readonly config: BuildConfig;
|
|
36
|
+
readonly selection: TargetSelection;
|
|
37
|
+
readonly nativeKey: string;
|
|
38
|
+
/** Copies a resolved native addon into place; returns false if it could not be provided. Injected so the real fs copy stays out of the policy. */
|
|
39
|
+
readonly provideAddon?: (target: BinaryTarget, sameHost: boolean) => boolean;
|
|
40
|
+
readonly exec?: Exec;
|
|
41
|
+
readonly logger?: Logger;
|
|
42
|
+
}
|
|
43
|
+
/** Compile the selected targets. Returns one outcome per target. */
|
|
44
|
+
export declare function runBuildBinaries(options: RunBuildOptions): BuildOutcome[];
|
|
45
|
+
//# sourceMappingURL=build-binaries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-binaries.d.ts","sourceRoot":"","sources":["../../src/lib/build-binaries.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE9D,oFAAoF;AACpF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CAwB/G;AAED,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAI/H;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kJAAkJ;IAClJ,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,YAAY,EAAE,CAyCzE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* build-binaries — compiles standalone binaries for the configured matrix.
|
|
3
|
+
*
|
|
4
|
+
* Absorbs tui/build.ts and agent/build.ts. The TUI's daemon leg (a second
|
|
5
|
+
* `bun build --compile` of a daemon entrypoint per target) is config-driven:
|
|
6
|
+
* a target/build with a `daemonEntrypoint` + `daemonArtifact` builds two
|
|
7
|
+
* binaries, one without builds one. Native addons (sqlite-vec) stay `--external`
|
|
8
|
+
* and are copied beside the binary; a same-platform miss is fatal, a
|
|
9
|
+
* cross-target miss triggers an `npm pack` + `tar` fetch.
|
|
10
|
+
*/
|
|
11
|
+
import { realExec, consoleLogger } from './effects.js';
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the target selection from argv against the matrix.
|
|
14
|
+
* - `--all` → every target.
|
|
15
|
+
* - `--target <key>` → that target; a `daemon-<key>` alias forces daemon-only.
|
|
16
|
+
* - `--daemon-only` → daemon leg only.
|
|
17
|
+
* - none → the native `${platform}-${arch}` row.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveTargets(argv, config, nativeKey) {
|
|
20
|
+
const byKey = new Map(config.targets.map((t) => [t.key, t]));
|
|
21
|
+
let daemonOnly = argv.includes('--daemon-only');
|
|
22
|
+
if (argv.includes('--all')) {
|
|
23
|
+
return { targets: config.targets, daemonOnly };
|
|
24
|
+
}
|
|
25
|
+
const targetIdx = argv.indexOf('--target');
|
|
26
|
+
if (targetIdx !== -1) {
|
|
27
|
+
let name = argv[targetIdx + 1];
|
|
28
|
+
if (!name)
|
|
29
|
+
throw new Error('--target requires a value');
|
|
30
|
+
if (name.startsWith('daemon-')) {
|
|
31
|
+
daemonOnly = true;
|
|
32
|
+
name = name.slice('daemon-'.length).replace(/^macos-/, 'darwin-');
|
|
33
|
+
}
|
|
34
|
+
const target = byKey.get(name);
|
|
35
|
+
if (!target)
|
|
36
|
+
throw new Error(`Unknown target: ${name}`);
|
|
37
|
+
return { targets: [target], daemonOnly };
|
|
38
|
+
}
|
|
39
|
+
const native = byKey.get(nativeKey);
|
|
40
|
+
if (!native)
|
|
41
|
+
throw new Error(`Unsupported host target: ${nativeKey}`);
|
|
42
|
+
return { targets: [native], daemonOnly };
|
|
43
|
+
}
|
|
44
|
+
/** Construct the `bun build --compile` argv for one entrypoint. */
|
|
45
|
+
export function buildCompileArgs(entrypoint, bunTarget, outfile, externals) {
|
|
46
|
+
const args = ['build', entrypoint, '--compile', `--target=${bunTarget}`, '--outfile', outfile];
|
|
47
|
+
for (const ext of externals)
|
|
48
|
+
args.push('--external', ext);
|
|
49
|
+
return args;
|
|
50
|
+
}
|
|
51
|
+
/** Compile the selected targets. Returns one outcome per target. */
|
|
52
|
+
export function runBuildBinaries(options) {
|
|
53
|
+
const exec = options.exec ?? realExec;
|
|
54
|
+
const logger = options.logger ?? consoleLogger;
|
|
55
|
+
const { config, selection, cwd } = options;
|
|
56
|
+
for (const cmd of config.prebuild) {
|
|
57
|
+
const [bin, ...args] = cmd;
|
|
58
|
+
if (!bin)
|
|
59
|
+
continue;
|
|
60
|
+
const res = exec(bin, args, { cwd });
|
|
61
|
+
if (res.status !== 0)
|
|
62
|
+
throw new Error(`prebuild failed: ${cmd.join(' ')}\n${res.stderr}`);
|
|
63
|
+
}
|
|
64
|
+
const outcomes = [];
|
|
65
|
+
for (const target of selection.targets) {
|
|
66
|
+
const externals = target.nativeAddonPackage ? [target.nativeAddonPackage] : [];
|
|
67
|
+
let ok = true;
|
|
68
|
+
let detail = '';
|
|
69
|
+
if (!selection.daemonOnly) {
|
|
70
|
+
const outfile = `${config.outDir}/${target.appArtifact}`;
|
|
71
|
+
const args = buildCompileArgs(config.appEntrypoint, target.bunTarget, outfile, externals);
|
|
72
|
+
const res = exec('bun', args, { cwd });
|
|
73
|
+
if (res.status !== 0) {
|
|
74
|
+
ok = false;
|
|
75
|
+
detail = `app compile failed (${res.status})`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (ok && config.daemonEntrypoint && target.daemonArtifact) {
|
|
79
|
+
const outfile = `${config.outDir}/${target.daemonArtifact}`;
|
|
80
|
+
const args = buildCompileArgs(config.daemonEntrypoint, target.bunTarget, outfile, externals);
|
|
81
|
+
const res = exec('bun', args, { cwd });
|
|
82
|
+
if (res.status !== 0) {
|
|
83
|
+
ok = false;
|
|
84
|
+
detail = `daemon compile failed (${res.status})`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (ok && target.nativeAddonPackage && target.nativeAddonFile && options.provideAddon) {
|
|
88
|
+
const provided = options.provideAddon(target, target.key === options.nativeKey);
|
|
89
|
+
if (!provided) {
|
|
90
|
+
ok = false;
|
|
91
|
+
detail = `native addon ${target.nativeAddonPackage}/${target.nativeAddonFile} unavailable`;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
outcomes.push({ key: target.key, ok, detail: ok ? 'built' : detail });
|
|
95
|
+
logger.info(`[build-binaries] ${target.key}: ${ok ? 'OK' : `FAILED — ${detail}`}`);
|
|
96
|
+
}
|
|
97
|
+
return outcomes;
|
|
98
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* changelog-gate — asserts CHANGELOG.md carries a section for a version.
|
|
3
|
+
*
|
|
4
|
+
* Supports both heading conventions in the estate: `## [1.2.3]` (SDK/TUI
|
|
5
|
+
* bracketed) and `## 1.2.3` (agent plain). Pure over injected text.
|
|
6
|
+
*/
|
|
7
|
+
export type ChangelogHeading = 'bracket' | 'plain' | 'either';
|
|
8
|
+
/** Build the anchored header matcher for a version under the given convention. */
|
|
9
|
+
export declare function changelogHeaderPattern(version: string, heading: ChangelogHeading): RegExp;
|
|
10
|
+
/** True when `changelog` contains a section for `version`. */
|
|
11
|
+
export declare function hasChangelogSection(changelog: string, version: string, heading?: ChangelogHeading): boolean;
|
|
12
|
+
export interface ChangelogGateResult {
|
|
13
|
+
readonly ok: boolean;
|
|
14
|
+
readonly detail: string;
|
|
15
|
+
}
|
|
16
|
+
/** Gate a changelog for a version; returns a structured result (no process exit). */
|
|
17
|
+
export declare function runChangelogGate(changelog: string, version: string, heading?: ChangelogHeading): ChangelogGateResult;
|
|
18
|
+
//# sourceMappingURL=changelog-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changelog-gate.d.ts","sourceRoot":"","sources":["../../src/lib/changelog-gate.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAM9D,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAKzF;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,gBAA2B,GAAG,OAAO,CAErH;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,qFAAqF;AACrF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,gBAA2B,GAAG,mBAAmB,CAQ9H"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* changelog-gate — asserts CHANGELOG.md carries a section for a version.
|
|
3
|
+
*
|
|
4
|
+
* Supports both heading conventions in the estate: `## [1.2.3]` (SDK/TUI
|
|
5
|
+
* bracketed) and `## 1.2.3` (agent plain). Pure over injected text.
|
|
6
|
+
*/
|
|
7
|
+
function escapeRegExp(value) {
|
|
8
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
9
|
+
}
|
|
10
|
+
/** Build the anchored header matcher for a version under the given convention. */
|
|
11
|
+
export function changelogHeaderPattern(version, heading) {
|
|
12
|
+
const v = escapeRegExp(version);
|
|
13
|
+
if (heading === 'bracket')
|
|
14
|
+
return new RegExp(`^##\\s*\\[${v}\\]`, 'm');
|
|
15
|
+
if (heading === 'plain')
|
|
16
|
+
return new RegExp(`^##\\s*${v}(?:\\s|$|-)`, 'm');
|
|
17
|
+
return new RegExp(`^##\\s*(?:\\[${v}\\]|${v}(?:\\s|$|-))`, 'm');
|
|
18
|
+
}
|
|
19
|
+
/** True when `changelog` contains a section for `version`. */
|
|
20
|
+
export function hasChangelogSection(changelog, version, heading = 'either') {
|
|
21
|
+
return changelogHeaderPattern(version, heading).test(changelog);
|
|
22
|
+
}
|
|
23
|
+
/** Gate a changelog for a version; returns a structured result (no process exit). */
|
|
24
|
+
export function runChangelogGate(changelog, version, heading = 'either') {
|
|
25
|
+
const ok = hasChangelogSection(changelog, version, heading);
|
|
26
|
+
return {
|
|
27
|
+
ok,
|
|
28
|
+
detail: ok
|
|
29
|
+
? `CHANGELOG contains a section for ${version}`
|
|
30
|
+
: `CHANGELOG is missing a section for ${version}. Add a "## ${heading === 'plain' ? version : `[${version}]`} - YYYY-MM-DD" heading before releasing.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coverage-gate — aggregate (single-process) coverage ratchet.
|
|
3
|
+
*
|
|
4
|
+
* Parses Bun's text coverage table ("All files | %Funcs | %Lines") and enforces
|
|
5
|
+
* per-repo floors. Test failures in the single-process run are reported as a
|
|
6
|
+
* note but do not fail the coverage gate (correctness is gated elsewhere).
|
|
7
|
+
*/
|
|
8
|
+
import type { CoverageConfig } from '../config.js';
|
|
9
|
+
export interface CoverageSummary {
|
|
10
|
+
readonly funcsPct: number;
|
|
11
|
+
readonly linesPct: number;
|
|
12
|
+
}
|
|
13
|
+
/** Extract the aggregate `All files` row percentages from Bun coverage output. */
|
|
14
|
+
export declare function parseCoverageSummary(output: string): CoverageSummary | null;
|
|
15
|
+
/** Extract a Bun "N fail" count from the run output, if present. */
|
|
16
|
+
export declare function parseFailCount(output: string): number;
|
|
17
|
+
export interface CoverageGateResult {
|
|
18
|
+
readonly ok: boolean;
|
|
19
|
+
readonly summary: CoverageSummary | null;
|
|
20
|
+
readonly failCount: number;
|
|
21
|
+
readonly detail: string;
|
|
22
|
+
}
|
|
23
|
+
/** Evaluate coverage output against the configured floors. */
|
|
24
|
+
export declare function evaluateCoverageGate(output: string, config: Pick<CoverageConfig, 'funcsFloor' | 'linesFloor'>): CoverageGateResult;
|
|
25
|
+
//# sourceMappingURL=coverage-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage-gate.d.ts","sourceRoot":"","sources":["../../src/lib/coverage-gate.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAInD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAY3E;AAED,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,8DAA8D;AAC9D,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,YAAY,GAAG,YAAY,CAAC,GAAG,kBAAkB,CAelI"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* coverage-gate — aggregate (single-process) coverage ratchet.
|
|
3
|
+
*
|
|
4
|
+
* Parses Bun's text coverage table ("All files | %Funcs | %Lines") and enforces
|
|
5
|
+
* per-repo floors. Test failures in the single-process run are reported as a
|
|
6
|
+
* note but do not fail the coverage gate (correctness is gated elsewhere).
|
|
7
|
+
*/
|
|
8
|
+
const ANSI = /\x1b\[[0-9;]*m/g;
|
|
9
|
+
/** Extract the aggregate `All files` row percentages from Bun coverage output. */
|
|
10
|
+
export function parseCoverageSummary(output) {
|
|
11
|
+
const clean = output.replace(ANSI, '');
|
|
12
|
+
for (const line of clean.split('\n')) {
|
|
13
|
+
if (!line.trimStart().startsWith('All files'))
|
|
14
|
+
continue;
|
|
15
|
+
const cells = line.split('|').map((c) => c.trim());
|
|
16
|
+
const funcs = Number.parseFloat(cells[1] ?? '');
|
|
17
|
+
const lines = Number.parseFloat(cells[2] ?? '');
|
|
18
|
+
if (Number.isFinite(funcs) && Number.isFinite(lines)) {
|
|
19
|
+
return { funcsPct: funcs, linesPct: lines };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/** Extract a Bun "N fail" count from the run output, if present. */
|
|
25
|
+
export function parseFailCount(output) {
|
|
26
|
+
const match = /^\s*(\d+)\s+fail\s*$/m.exec(output.replace(ANSI, ''));
|
|
27
|
+
return match?.[1] ? Number.parseInt(match[1], 10) : 0;
|
|
28
|
+
}
|
|
29
|
+
/** Evaluate coverage output against the configured floors. */
|
|
30
|
+
export function evaluateCoverageGate(output, config) {
|
|
31
|
+
const summary = parseCoverageSummary(output);
|
|
32
|
+
const failCount = parseFailCount(output);
|
|
33
|
+
if (!summary) {
|
|
34
|
+
return { ok: false, summary: null, failCount, detail: 'no coverage table found in output — did the run crash?' };
|
|
35
|
+
}
|
|
36
|
+
const funcsOk = summary.funcsPct >= config.funcsFloor;
|
|
37
|
+
const linesOk = summary.linesPct >= config.linesFloor;
|
|
38
|
+
const ok = funcsOk && linesOk;
|
|
39
|
+
const parts = [
|
|
40
|
+
`Funcs ${summary.funcsPct.toFixed(2)}% (floor ${config.funcsFloor}) ${funcsOk ? 'OK' : 'BELOW'}`,
|
|
41
|
+
`Lines ${summary.linesPct.toFixed(2)}% (floor ${config.linesFloor}) ${linesOk ? 'OK' : 'BELOW'}`,
|
|
42
|
+
];
|
|
43
|
+
if (failCount > 0)
|
|
44
|
+
parts.push(`note: ${failCount} single-process test failure(s) (cross-file interference debt; gated elsewhere)`);
|
|
45
|
+
return { ok, summary, failCount, detail: parts.join('; ') };
|
|
46
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable effect boundaries.
|
|
3
|
+
*
|
|
4
|
+
* Every tool is a policy function that takes its effects as parameters, so unit
|
|
5
|
+
* tests drive it with in-memory stubs (no network, no real git mutation, no
|
|
6
|
+
* child processes). The thin bins wire the real Node/Bun implementations.
|
|
7
|
+
*/
|
|
8
|
+
/** Result of running a subprocess. */
|
|
9
|
+
export interface ExecResult {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
readonly stdout: string;
|
|
12
|
+
readonly stderr: string;
|
|
13
|
+
}
|
|
14
|
+
/** Runs a subprocess to completion and returns captured output. Never throws on non-zero exit. */
|
|
15
|
+
export type Exec = (command: string, args: readonly string[], options?: {
|
|
16
|
+
readonly cwd?: string;
|
|
17
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
18
|
+
}) => ExecResult;
|
|
19
|
+
/** Minimal read-only filesystem seam. */
|
|
20
|
+
export interface FsReader {
|
|
21
|
+
readonly exists: (path: string) => boolean;
|
|
22
|
+
readonly readText: (path: string) => string;
|
|
23
|
+
readonly readDir: (path: string) => readonly string[];
|
|
24
|
+
readonly isExecutable: (path: string) => boolean;
|
|
25
|
+
}
|
|
26
|
+
/** An HTTP GET returning a parsed JSON body plus the transport status. */
|
|
27
|
+
export interface HttpResponse {
|
|
28
|
+
readonly status: number;
|
|
29
|
+
readonly body: unknown;
|
|
30
|
+
}
|
|
31
|
+
export type HttpGetJson = (url: string, headers: Readonly<Record<string, string>>) => Promise<HttpResponse>;
|
|
32
|
+
/** Structured logger seam (tests capture lines; bins print to console). */
|
|
33
|
+
export interface Logger {
|
|
34
|
+
readonly info: (line: string) => void;
|
|
35
|
+
readonly warn: (line: string) => void;
|
|
36
|
+
readonly error: (line: string) => void;
|
|
37
|
+
}
|
|
38
|
+
/** Sleep seam so pollers are deterministic in tests. */
|
|
39
|
+
export type Sleep = (ms: number) => Promise<void>;
|
|
40
|
+
/** Real subprocess runner: captures stdout/stderr, reports the exit status without throwing. */
|
|
41
|
+
export declare const realExec: Exec;
|
|
42
|
+
/** Real filesystem reader rooted at an absolute base directory. */
|
|
43
|
+
export declare function realFsReader(root: string): FsReader;
|
|
44
|
+
/** Real HTTP GET returning parsed JSON. Non-2xx does not throw — the status is reported so callers can branch (e.g. 503 fallback). */
|
|
45
|
+
export declare const realHttpGetJson: HttpGetJson;
|
|
46
|
+
/** Console-backed logger. */
|
|
47
|
+
export declare const consoleLogger: Logger;
|
|
48
|
+
/** In-memory logger for tests. */
|
|
49
|
+
export declare function captureLogger(): Logger & {
|
|
50
|
+
readonly lines: string[];
|
|
51
|
+
};
|
|
52
|
+
export declare const realSleep: Sleep;
|
|
53
|
+
//# sourceMappingURL=effects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../../src/lib/effects.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,sCAAsC;AACtC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,kGAAkG;AAClG,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAE,KAAK,UAAU,CAAC;AAEnJ,yCAAyC;AACzC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAC3C,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,MAAM,EAAE,CAAC;IACtD,QAAQ,CAAC,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAClD;AAED,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AACD,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAE5G,2EAA2E;AAC3E,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,wDAAwD;AACxD,MAAM,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAElD,gGAAgG;AAChG,eAAO,MAAM,QAAQ,EAAE,IAkBtB,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAcnD;AAED,sIAAsI;AACtI,eAAO,MAAM,eAAe,EAAE,WAU7B,CAAC;AAEF,6BAA6B;AAC7B,eAAO,MAAM,aAAa,EAAE,MAI3B,CAAC;AAEF,kCAAkC;AAClC,wBAAgB,aAAa,IAAI,MAAM,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAQrE;AAED,eAAO,MAAM,SAAS,EAAE,KAAqD,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injectable effect boundaries.
|
|
3
|
+
*
|
|
4
|
+
* Every tool is a policy function that takes its effects as parameters, so unit
|
|
5
|
+
* tests drive it with in-memory stubs (no network, no real git mutation, no
|
|
6
|
+
* child processes). The thin bins wire the real Node/Bun implementations.
|
|
7
|
+
*/
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
10
|
+
import { resolve } from 'node:path';
|
|
11
|
+
/** Real subprocess runner: captures stdout/stderr, reports the exit status without throwing. */
|
|
12
|
+
export const realExec = (command, args, options = {}) => {
|
|
13
|
+
try {
|
|
14
|
+
const stdout = execFileSync(command, args, {
|
|
15
|
+
cwd: options.cwd,
|
|
16
|
+
env: options.env ?? process.env,
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
19
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
20
|
+
});
|
|
21
|
+
return { status: 0, stdout, stderr: '' };
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const err = error;
|
|
25
|
+
return {
|
|
26
|
+
status: typeof err.status === 'number' ? err.status : 1,
|
|
27
|
+
stdout: err.stdout ? err.stdout.toString() : '',
|
|
28
|
+
stderr: err.stderr ? err.stderr.toString() : '',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/** Real filesystem reader rooted at an absolute base directory. */
|
|
33
|
+
export function realFsReader(root) {
|
|
34
|
+
const at = (path) => resolve(root, path);
|
|
35
|
+
return {
|
|
36
|
+
exists: (path) => existsSync(at(path)),
|
|
37
|
+
readText: (path) => readFileSync(at(path), 'utf8'),
|
|
38
|
+
readDir: (path) => readdirSync(at(path)),
|
|
39
|
+
isExecutable: (path) => {
|
|
40
|
+
try {
|
|
41
|
+
return (statSync(at(path)).mode & 0o111) !== 0;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** Real HTTP GET returning parsed JSON. Non-2xx does not throw — the status is reported so callers can branch (e.g. 503 fallback). */
|
|
50
|
+
export const realHttpGetJson = async (url, headers) => {
|
|
51
|
+
const response = await fetch(url, { headers });
|
|
52
|
+
const text = await response.text();
|
|
53
|
+
let body = null;
|
|
54
|
+
try {
|
|
55
|
+
body = text.length > 0 ? JSON.parse(text) : null;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
body = text;
|
|
59
|
+
}
|
|
60
|
+
return { status: response.status, body };
|
|
61
|
+
};
|
|
62
|
+
/** Console-backed logger. */
|
|
63
|
+
export const consoleLogger = {
|
|
64
|
+
info: (line) => console.log(line),
|
|
65
|
+
warn: (line) => console.warn(line),
|
|
66
|
+
error: (line) => console.error(line),
|
|
67
|
+
};
|
|
68
|
+
/** In-memory logger for tests. */
|
|
69
|
+
export function captureLogger() {
|
|
70
|
+
const lines = [];
|
|
71
|
+
return {
|
|
72
|
+
lines,
|
|
73
|
+
info: (line) => lines.push(line),
|
|
74
|
+
warn: (line) => lines.push(line),
|
|
75
|
+
error: (line) => lines.push(line),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export const realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk loader for toolchain.config. Kept separate from config.ts so the
|
|
3
|
+
* contract types stay import-safe in environments without a filesystem.
|
|
4
|
+
*/
|
|
5
|
+
import { type ToolchainConfig } from '../config.js';
|
|
6
|
+
/** Load `toolchain.config.json` from a repo root. Throws if absent or invalid. */
|
|
7
|
+
export declare function loadToolchainConfig(root?: string): ToolchainConfig;
|
|
8
|
+
//# sourceMappingURL=load-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-config.d.ts","sourceRoot":"","sources":["../../src/lib/load-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAE,KAAK,eAAe,EAAwB,MAAM,cAAc,CAAC;AAE1E,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,MAAsB,GAAG,eAAe,CAMjF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk loader for toolchain.config. Kept separate from config.ts so the
|
|
3
|
+
* contract types stay import-safe in environments without a filesystem.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { parseToolchainConfig } from '../config.js';
|
|
8
|
+
/** Load `toolchain.config.json` from a repo root. Throws if absent or invalid. */
|
|
9
|
+
export function loadToolchainConfig(root = process.cwd()) {
|
|
10
|
+
const path = resolve(root, 'toolchain.config.json');
|
|
11
|
+
if (!existsSync(path)) {
|
|
12
|
+
throw new Error(`toolchain.config.json not found at ${path}. See @pellux/goodvibes-toolchain docs for the contract.`);
|
|
13
|
+
}
|
|
14
|
+
return parseToolchainConfig(readFileSync(path, 'utf8'));
|
|
15
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* package-install-check — static verification of a package as it would install.
|
|
3
|
+
*
|
|
4
|
+
* Inspects the `npm pack --json --dry-run` tarball for required and forbidden
|
|
5
|
+
* paths and a size cap, and verifies each bin shim exists, is executable, and
|
|
6
|
+
* carries the expected shebang. Static — the actual install-into-temp smoke is
|
|
7
|
+
* a separate tool/CI job.
|
|
8
|
+
*/
|
|
9
|
+
import type { Exec, FsReader, Logger } from './effects.js';
|
|
10
|
+
import type { PublishPackageConfig } from '../config.js';
|
|
11
|
+
export interface PathPolicyResult {
|
|
12
|
+
readonly ok: boolean;
|
|
13
|
+
readonly missing: readonly string[];
|
|
14
|
+
readonly forbidden: readonly string[];
|
|
15
|
+
readonly oversize: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Check a packed file list + unpacked size against the tarball policy. Pure. */
|
|
18
|
+
export declare function evaluateTarballPaths(files: readonly string[], unpackedBytes: number, config: Pick<PublishPackageConfig, 'requiredTarballPaths' | 'forbiddenTarballPrefixes' | 'maxTarballBytes'>): PathPolicyResult;
|
|
19
|
+
export interface BinCheckResult {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly ok: boolean;
|
|
22
|
+
readonly issues: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/** Verify one bin shim: present, executable, and has the expected shebang. */
|
|
25
|
+
export declare function evaluateBinShim(fs: FsReader, path: string, name: string, shebang: string): BinCheckResult;
|
|
26
|
+
/** Parse `npm pack --json` output into a file list and unpacked size. */
|
|
27
|
+
export declare function parseNpmPack(output: string): {
|
|
28
|
+
files: string[];
|
|
29
|
+
unpackedBytes: number;
|
|
30
|
+
};
|
|
31
|
+
export interface InstallCheckOptions {
|
|
32
|
+
readonly cwd: string;
|
|
33
|
+
readonly config: PublishPackageConfig;
|
|
34
|
+
readonly bins?: readonly {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly path: string;
|
|
37
|
+
readonly shebang: string;
|
|
38
|
+
}[];
|
|
39
|
+
readonly exec?: Exec;
|
|
40
|
+
readonly fs?: FsReader;
|
|
41
|
+
readonly logger?: Logger;
|
|
42
|
+
}
|
|
43
|
+
export interface InstallCheckResult {
|
|
44
|
+
readonly ok: boolean;
|
|
45
|
+
readonly issues: readonly string[];
|
|
46
|
+
}
|
|
47
|
+
/** Run the full static install check. */
|
|
48
|
+
export declare function runPackageInstallCheck(options: InstallCheckOptions): InstallCheckResult;
|
|
49
|
+
//# sourceMappingURL=package-install-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-install-check.d.ts","sourceRoot":"","sources":["../../src/lib/package-install-check.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEzD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;CAC5B;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,IAAI,CAAC,oBAAoB,EAAE,sBAAsB,GAAG,0BAA0B,GAAG,iBAAiB,CAAC,GAC1G,gBAAgB,CAMlB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,8EAA8E;AAC9E,wBAAgB,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CASzG;AAQD,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAOvF;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACtG,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,EAAE,QAAQ,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,yCAAyC;AACzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CAyBvF"}
|