mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 22:26:38 +03:00
reorg: public plumbing for the in-repo Rust workspace and the two-release flow
The engine binaries move from the impeccable-dist channel to this repo's own GitHub Releases (tag engine-v<ENGINE_VERSION>), and the closed detector the engine links arrives as detector-v<DETECTOR_VERSION> releases on the same repo. This commit wires the public side for that; the crates themselves land in the next commit. - Launcher (sh + cmd), npm shim, fetch-engine and check-engine-release now download from github.com/pbakaus/impeccable/releases/download/engine-v<X>/. - release.mjs gains `engine`: verifies ENGINE_VERSION against the platform package pins and the detector release, tags, pushes; release-engine.yml builds the five targets and publishes. check-detector-release.mjs is the matching release-order guard (with tests). - Root Cargo.toml (workspace, lto = false with the reason), rust-toolchain.toml (exact pin), DETECTOR_VERSION, /target ignored. - CI: rust + rust-windows jobs and an oracle job that replays the goldens against a source build, warn-only until the first detector release exists; ci-test-plan exposes a `rust` output. - docs/ENGINE.md (the crate map and the closed-detector mechanism) and the CLAUDE.md engine, release-order and rules sections. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vau2X53xGTjjTCXWMVBoNY
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
6c474b1c79
commit
e355ebf714
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Release-order guard for the closed detector.
|
||||
*
|
||||
* The open runtime links a prebuilt detector archive at build time
|
||||
* (crates/core/build.rs downloads it for the pinned DETECTOR_VERSION). An
|
||||
* engine release therefore cannot be built until the detector release exists.
|
||||
* This script verifies that `detector-v<DETECTOR_VERSION>` is fully published
|
||||
* on the public repo's GitHub Releases: one archive + .sha256 per target and
|
||||
* the browser bundle the extension vendors.
|
||||
*
|
||||
* node scripts/check-detector-release.mjs # exits 1 and lists what is missing
|
||||
* node scripts/check-detector-release.mjs --json # machine-readable
|
||||
*
|
||||
* Environment:
|
||||
* IMPECCABLE_DETECTOR_BASE release root (default: the public repo's GitHub Releases;
|
||||
* the same variable crates/core/build.rs honors)
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
export const DEFAULT_DETECTOR_BASE = 'https://github.com/pbakaus/impeccable/releases/download';
|
||||
export const DETECTOR_TARGETS = ['darwin-arm64', 'darwin-x64', 'linux-x64', 'linux-arm64', 'windows-x64'];
|
||||
export const BROWSER_BUNDLE_ASSET = 'detector-browser-bundle.zip';
|
||||
|
||||
export function readDetectorVersion(root = ROOT) {
|
||||
return fs.readFileSync(path.join(root, 'DETECTOR_VERSION'), 'utf-8').trim();
|
||||
}
|
||||
|
||||
/** The archive asset name for one target, as build.rs and the detector CI spell it. */
|
||||
export function archiveAsset(target) {
|
||||
return target.startsWith('windows-') ? `impeccable_detector-${target}.lib` : `libimpeccable_detector-${target}.a`;
|
||||
}
|
||||
|
||||
export function assetUrl(version, asset, base = process.env.IMPECCABLE_DETECTOR_BASE || DEFAULT_DETECTOR_BASE) {
|
||||
return `${base.replace(/\/$/, '')}/detector-v${version}/${asset}`;
|
||||
}
|
||||
|
||||
// A ranged GET is the most portable existence probe: GitHub release downloads
|
||||
// redirect to a signed storage URL that answers HEAD inconsistently.
|
||||
async function urlExists(url, fetchImpl = fetch) {
|
||||
try {
|
||||
const res = await fetchImpl(url, { method: 'GET', headers: { Range: 'bytes=0-0' }, redirect: 'follow' });
|
||||
if (res.body && typeof res.body.cancel === 'function') await res.body.cancel().catch(() => {});
|
||||
return res.status === 200 || res.status === 206;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<{ ok: boolean, version: string, base: string, missing: Array<{ kind: string, target?: string, what: string, url: string }> }>}
|
||||
*/
|
||||
export async function checkDetectorRelease({
|
||||
version = readDetectorVersion(),
|
||||
base = process.env.IMPECCABLE_DETECTOR_BASE || DEFAULT_DETECTOR_BASE,
|
||||
fetchImpl = fetch,
|
||||
} = {}) {
|
||||
const missing = [];
|
||||
const probes = [];
|
||||
for (const target of DETECTOR_TARGETS) {
|
||||
const asset = archiveAsset(target);
|
||||
const url = assetUrl(version, asset, base);
|
||||
probes.push(
|
||||
urlExists(url, fetchImpl).then((ok) => { if (!ok) missing.push({ kind: 'archive', target, what: asset, url }); }),
|
||||
urlExists(`${url}.sha256`, fetchImpl).then((ok) => { if (!ok) missing.push({ kind: 'checksum', target, what: `${asset}.sha256`, url: `${url}.sha256` }); }),
|
||||
);
|
||||
}
|
||||
const bundleUrl = assetUrl(version, BROWSER_BUNDLE_ASSET, base);
|
||||
probes.push(
|
||||
urlExists(bundleUrl, fetchImpl).then((ok) => { if (!ok) missing.push({ kind: 'bundle', what: BROWSER_BUNDLE_ASSET, url: bundleUrl }); }),
|
||||
);
|
||||
await Promise.all(probes);
|
||||
// Plain byte order (not localeCompare, which files punctuation before
|
||||
// letters): per-target rows first, the bundle row last.
|
||||
const order = { archive: 0, checksum: 1, bundle: 2 };
|
||||
const key = (m) => m.target || 'zz-bundle';
|
||||
missing.sort((a, b) => (key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : 0) || order[a.kind] - order[b.kind]);
|
||||
return { ok: missing.length === 0, version, base, missing };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const json = process.argv.includes('--json');
|
||||
return checkDetectorRelease().then((result) => {
|
||||
if (json) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
process.exit(result.ok ? 0 : 1);
|
||||
}
|
||||
if (result.ok) {
|
||||
console.log(`✓ detector v${result.version} release is complete: ${DETECTOR_TARGETS.length} archives + .sha256 + ${BROWSER_BUNDLE_ASSET} are published.`);
|
||||
console.log(` release base: ${result.base}`);
|
||||
process.exit(0);
|
||||
}
|
||||
console.error(`✗ detector v${result.version} release is INCOMPLETE. Missing ${result.missing.length} asset(s):`);
|
||||
for (const m of result.missing) console.error(` · ${m.what}\n ${m.url}`);
|
||||
console.error('');
|
||||
console.error(`Publish detector v${result.version} (tag v${result.version} in the private detector repo; its CI`);
|
||||
console.error(`uploads the archives to this repo's detector-v${result.version} release) BEFORE tagging an engine`);
|
||||
console.error('release: crates/core/build.rs downloads the archive for every target it builds.');
|
||||
console.error(` release base: ${result.base}`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
main();
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* The launcher (skill/scripts/impeccable), the npm shim (cli/bin/cli.js), and
|
||||
* `impeccable install` all dead-end unless the engine release for the pinned
|
||||
* ENGINE_VERSION exists FIRST: the five platform binaries in the impeccable-dist
|
||||
* ENGINE_VERSION exists FIRST: the five platform binaries in the engine-v<version> GitHub Release
|
||||
* release channel AND the five @impeccable/cli-<os>-<arch> npm platform packages.
|
||||
* Nothing else mechanically stops a maintainer from tagging the skill release (or
|
||||
* merging and letting the sync workflow rewrite provider dirs) before those assets
|
||||
@@ -23,7 +23,7 @@
|
||||
* node scripts/check-engine-release.mjs --json # machine-readable report
|
||||
*
|
||||
* Environment:
|
||||
* IMPECCABLE_DOWNLOAD_BASE dist release channel root (default: the public dist releases)
|
||||
* IMPECCABLE_DOWNLOAD_BASE release root (default: the public repo's GitHub Releases)
|
||||
*/
|
||||
import {
|
||||
ENGINE_TARGETS,
|
||||
@@ -102,7 +102,7 @@ function report(result) {
|
||||
const { ok, version, base, missing } = result;
|
||||
if (ok) {
|
||||
console.log(`✓ engine v${version} release is complete: all ${ENGINE_TARGETS.length} binaries + .sha256 + npm platform packages are published.`);
|
||||
console.log(` dist channel: ${base}`);
|
||||
console.log(` release base: ${base}`);
|
||||
return;
|
||||
}
|
||||
console.error(`✗ engine v${version} release is INCOMPLETE — ${missing.length} asset(s) missing:`);
|
||||
@@ -111,11 +111,11 @@ function report(result) {
|
||||
console.error(` ${m.url}`);
|
||||
}
|
||||
console.error('');
|
||||
console.error(`Publish engine v${version} to the impeccable-dist release channel AND the`);
|
||||
console.error(`Publish engine v${version} (tag engine-v${version}, bun run release:engine) AND the`);
|
||||
console.error('five @impeccable/cli-<os>-<arch> npm platform packages BEFORE releasing the');
|
||||
console.error('skill or merging rust-swap. Ordering: engine release → platform packages →');
|
||||
console.error('skill release/merge. See CLAUDE.md "Releases" and docs REVIEW-TRIAGE.md D4.');
|
||||
console.error(` dist channel: ${base}`);
|
||||
console.error(` release base: ${base}`);
|
||||
}
|
||||
|
||||
async function main(argv = process.argv.slice(2)) {
|
||||
|
||||
@@ -14,11 +14,24 @@ const isSchedule = eventName === 'schedule';
|
||||
const changedFiles = localNoChanges || isSchedule ? [] : getChangedFiles();
|
||||
const forceDeterministic = localNoChanges || isSchedule || eventName === 'push' || eventName === 'workflow_dispatch';
|
||||
const forceOptIn = eventName === 'workflow_dispatch';
|
||||
// The Rust workspace (the engine) builds and tests when its own inputs move.
|
||||
// tests/oracle is included: the goldens are the engine's behavior gate and
|
||||
// the oracle job replays them against a source build.
|
||||
const RUST_PATTERNS = [
|
||||
/^crates\//,
|
||||
/^Cargo\.(toml|lock)$/,
|
||||
/^rust-toolchain\.toml$/,
|
||||
/^DETECTOR_VERSION$/,
|
||||
/^tests\/oracle\//,
|
||||
/^\.github\/workflows\/ci\.yml$/,
|
||||
];
|
||||
const rustChanged = changedFiles.some((file) => RUST_PATTERNS.some((re) => re.test(file)));
|
||||
|
||||
const plan = isSchedule
|
||||
? {
|
||||
core: true,
|
||||
oracle: true,
|
||||
rust: true,
|
||||
detector: true,
|
||||
live: true,
|
||||
framework: true,
|
||||
@@ -31,6 +44,7 @@ const plan = isSchedule
|
||||
: {
|
||||
core: true,
|
||||
oracle: forceDeterministic || matchesSuiteTriggers('oracle', changedFiles),
|
||||
rust: forceDeterministic || rustChanged,
|
||||
detector: forceDeterministic || matchesSuiteTriggers('detector', changedFiles),
|
||||
live: forceDeterministic || matchesSuiteTriggers('live', changedFiles),
|
||||
framework: forceDeterministic || matchesSuiteTriggers('framework', changedFiles),
|
||||
@@ -98,7 +112,7 @@ function printSummary(outputs, files) {
|
||||
const deterministic = DEFAULT_SUITES.map((name) => `${name}=${outputs[name]}`).join(' ');
|
||||
console.log(`Event: ${eventName || 'local'}`);
|
||||
console.log(`Changed files: ${files.length}`);
|
||||
console.log(`Deterministic suites: ${deterministic}`);
|
||||
console.log(`Deterministic suites: ${deterministic} rust=${outputs.rust}`);
|
||||
console.log(
|
||||
[
|
||||
`cli_remote_e2e=${outputs.cli_remote_e2e}`,
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
* node scripts/fetch-engine.mjs --lenient # a target that cannot be fetched warns instead of failing
|
||||
*
|
||||
* Environment (same names the launcher honors):
|
||||
* IMPECCABLE_DOWNLOAD_BASE release channel root (default: the public dist releases)
|
||||
* IMPECCABLE_DOWNLOAD_BASE release channel root (default: the public repo's GitHub Releases)
|
||||
* IMPECCABLE_BIN copy this local binary for the current platform instead of downloading
|
||||
*
|
||||
* The URL scheme is the launcher's: <base>/v<version>/impeccable-<os>-<arch>[.exe],
|
||||
* The URL scheme is the launcher's: <base>/engine-v<version>/impeccable-<os>-<arch>[.exe],
|
||||
* with an optional <asset>.sha256 next to it that is verified when present.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
@@ -24,7 +24,7 @@ import { createHash } from 'node:crypto';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
export const DEFAULT_DOWNLOAD_BASE = 'https://github.com/renaissance-geek-inc/impeccable-dist/releases/download';
|
||||
export const DEFAULT_DOWNLOAD_BASE = 'https://github.com/pbakaus/impeccable/releases/download';
|
||||
export const ENGINE_TARGETS = ['darwin-arm64', 'darwin-x64', 'linux-x64', 'linux-arm64', 'windows-x64'];
|
||||
|
||||
export function readEngineVersion(root = ROOT) {
|
||||
@@ -43,7 +43,7 @@ export function binaryName(target) {
|
||||
|
||||
export function assetUrl(version, target, base = process.env.IMPECCABLE_DOWNLOAD_BASE || DEFAULT_DOWNLOAD_BASE) {
|
||||
const asset = `impeccable-${target}${target.startsWith('windows-') ? '.exe' : ''}`;
|
||||
return `${base.replace(/\/$/, '')}/v${version}/${asset}`;
|
||||
return `${base.replace(/\/$/, '')}/engine-v${version}/${asset}`;
|
||||
}
|
||||
|
||||
export function binaryPath(target, dest = path.join(ROOT, 'skill', 'scripts', 'bin')) {
|
||||
|
||||
+104
-6
@@ -1,8 +1,13 @@
|
||||
#!/usr/bin/env node
|
||||
// Tags and publishes a GitHub release for one of three independently versioned
|
||||
// components: skill, cli, extension.
|
||||
// Tags and publishes a GitHub release for one of the independently versioned
|
||||
// components: skill, cli, extension, engine.
|
||||
//
|
||||
// Usage: node scripts/release.mjs <skill|cli|extension> [--dry-run]
|
||||
// Usage: node scripts/release.mjs <skill|cli|extension|engine> [--dry-run]
|
||||
//
|
||||
// `engine` is different: it only tags `engine-v<ENGINE_VERSION>` and pushes the
|
||||
// tag; .github/workflows/release-engine.yml builds the five binaries and
|
||||
// publishes the GitHub Release. It has no changelog entry and no local
|
||||
// artifacts, and it is gated on the closed detector release the build links.
|
||||
//
|
||||
// Refuses on a dirty tree, an unpushed HEAD, or a missing changelog entry.
|
||||
// For the skill component, also reruns `bun run build:release` and refuses if the
|
||||
@@ -13,6 +18,7 @@ import { execSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { checkEngineRelease } from './check-engine-release.mjs';
|
||||
import { checkDetectorRelease, readDetectorVersion } from './check-detector-release.mjs';
|
||||
import { readEngineVersion } from './fetch-engine.mjs';
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
@@ -64,6 +70,13 @@ const COMPONENTS = {
|
||||
tweetHeader: (v) => `Impeccable browser extension v${v} is out.`,
|
||||
tweetCta: null,
|
||||
},
|
||||
engine: {
|
||||
// Version comes from the root ENGINE_VERSION file, not a JSON manifest;
|
||||
// releaseEngine() below owns this component's whole flow.
|
||||
manifest: 'ENGINE_VERSION',
|
||||
tagPrefix: 'engine-v',
|
||||
label: 'Engine',
|
||||
},
|
||||
};
|
||||
|
||||
const REPO_URL = 'https://github.com/pbakaus/impeccable';
|
||||
@@ -74,11 +87,16 @@ const dryRun = args.includes('--dry-run');
|
||||
const component = args.find((a) => !a.startsWith('--'));
|
||||
|
||||
if (!component || !COMPONENTS[component]) {
|
||||
console.error('usage: release.mjs <skill|cli|extension> [--dry-run]');
|
||||
console.error('usage: release.mjs <skill|cli|extension|engine> [--dry-run]');
|
||||
process.exit(1);
|
||||
}
|
||||
const cfg = COMPONENTS[component];
|
||||
|
||||
if (component === 'engine') {
|
||||
await releaseEngine();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
function fail(msg) {
|
||||
console.error(`✗ ${msg}`);
|
||||
process.exit(1);
|
||||
@@ -117,7 +135,7 @@ if (cfg.sibling) {
|
||||
|
||||
// Release-order guard (triage decision D4). Engine-gated components refuse to
|
||||
// tag/publish until the engine release for the pinned ENGINE_VERSION is fully
|
||||
// live: the five dist binaries + .sha256 and the five @impeccable/cli-<os>-<arch>
|
||||
// live: the five engine-v<version> release binaries + .sha256 and the five @impeccable/cli-<os>-<arch>
|
||||
// npm platform packages. Without this the launcher, the npm shim, and
|
||||
// `impeccable install` all dead-end. Set IMPECCABLE_SKIP_ENGINE_CHECK=1 only
|
||||
// when you know the assets exist and the registry probe is unreachable.
|
||||
@@ -130,7 +148,7 @@ if (cfg.engineGated && process.env.IMPECCABLE_SKIP_ENGINE_CHECK !== '1') {
|
||||
for (const m of result.missing) console.error(` · ${m.what}\n ${m.url}`);
|
||||
fail(
|
||||
`Refusing to release ${cfg.label} ${version}: engine v${engineVersion} is not fully published.\n` +
|
||||
` Publish engine v${engineVersion} to impeccable-dist AND the five @impeccable/cli-<os>-<arch>\n` +
|
||||
` Publish engine v${engineVersion} (bun run release:engine) AND the five @impeccable/cli-<os>-<arch>\n` +
|
||||
' npm platform packages first. Ordering: engine release → platform packages → skill/CLI release.\n' +
|
||||
' See CLAUDE.md "Releases" and the engine repo docs/REVIEW-TRIAGE.md D4.'
|
||||
);
|
||||
@@ -365,3 +383,83 @@ function htmlToMarkdown(html) {
|
||||
md = md.replace(/\n{3,}/g, '\n\n');
|
||||
return md.trim();
|
||||
}
|
||||
|
||||
|
||||
// The engine release: verify, tag, push. CI does the building and publishing
|
||||
// (release-engine.yml), so the maintainer's machine never needs five
|
||||
// toolchains. Refuses when the detector release the build links against is
|
||||
// not published: crates/core/build.rs downloads
|
||||
// detector-v<DETECTOR_VERSION> for every target, so a missing archive would
|
||||
// fail every matrix job after the tag is already pushed.
|
||||
async function releaseEngine() {
|
||||
step('Reading version from ENGINE_VERSION');
|
||||
const version = readEngineVersion(repoRoot);
|
||||
if (!/^\d+\.\d+\.\d+/.test(version)) fail(`ENGINE_VERSION "${version}" is not a version`);
|
||||
ok(`Engine ${version}`);
|
||||
|
||||
step('Checking package.json optionalDependencies pin the same engine version');
|
||||
const pkg = JSON.parse(readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
|
||||
const pins = Object.entries(pkg.optionalDependencies || {}).filter(([name]) => name.startsWith('@impeccable/cli-'));
|
||||
const wrong = pins.filter(([, range]) => String(range).replace(/^[^\d]*/, '') !== version);
|
||||
if (wrong.length) fail(`package.json pins ${wrong.map(([n, r]) => `${n}@${r}`).join(', ')}; expected ${version}. Bump them with ENGINE_VERSION.`);
|
||||
ok(`${pins.length} platform package pins agree`);
|
||||
|
||||
if (process.env.IMPECCABLE_SKIP_DETECTOR_CHECK !== '1') {
|
||||
const detectorVersion = readDetectorVersion(repoRoot);
|
||||
step(`Verifying detector v${detectorVersion} release assets are published (the engine build links them)`);
|
||||
const result = await checkDetectorRelease({ version: detectorVersion });
|
||||
if (!result.ok) {
|
||||
console.error('✗ Detector release is incomplete. Missing assets:');
|
||||
for (const m of result.missing) console.error(` · ${m.what}\n ${m.url}`);
|
||||
fail(
|
||||
`Refusing to tag engine ${version}: detector v${detectorVersion} is not fully published.\n` +
|
||||
' Tag the detector repo first; its CI publishes the archives to this repo\'s detector-v release.\n' +
|
||||
' Ordering: detector release → engine release → platform packages → skill/CLI release.'
|
||||
);
|
||||
}
|
||||
ok(`detector v${detectorVersion} release assets all present`);
|
||||
} else {
|
||||
step('Skipping detector release-order guard (IMPECCABLE_SKIP_DETECTOR_CHECK=1)');
|
||||
}
|
||||
|
||||
const tag = `${cfg.tagPrefix}${version}`;
|
||||
|
||||
step('Checking working tree is clean');
|
||||
const status = run('git status --porcelain');
|
||||
if (status) fail(`Working tree is dirty. Commit or stash first:\n${status}`);
|
||||
ok('clean');
|
||||
|
||||
step('Checking HEAD is pushed to origin');
|
||||
const branch = run('git rev-parse --abbrev-ref HEAD');
|
||||
const head = run('git rev-parse HEAD');
|
||||
let remoteHead;
|
||||
try {
|
||||
remoteHead = run(`git rev-parse origin/${branch}`);
|
||||
} catch {
|
||||
fail(`No tracking branch origin/${branch}. Push first.`);
|
||||
}
|
||||
if (head !== remoteHead) fail(`HEAD is ahead of origin/${branch}. Push your commits first.`);
|
||||
ok(`origin/${branch} matches HEAD`);
|
||||
|
||||
step(`Verifying tag ${tag} does not already exist`);
|
||||
let localTagExists = false;
|
||||
try {
|
||||
run(`git rev-parse -q --verify "refs/tags/${tag}"`);
|
||||
localTagExists = true;
|
||||
} catch {}
|
||||
if (localTagExists) fail(`Tag ${tag} already exists locally.`);
|
||||
const remoteTags = run('git ls-remote --tags origin');
|
||||
if (remoteTags.split('\n').some((line) => line.endsWith(`refs/tags/${tag}`))) {
|
||||
fail(`Tag ${tag} already exists on origin.`);
|
||||
}
|
||||
ok('tag is free');
|
||||
|
||||
step(`Creating annotated tag ${tag}`);
|
||||
runMutating(`git tag -a ${tag} -m "Engine ${version}"`);
|
||||
runMutating(`git push origin ${tag}`);
|
||||
|
||||
console.log(`\n✓ Engine ${version} tagged as ${tag}`);
|
||||
console.log(`\n→ Next step: watch the release-engine workflow (${REPO_URL}/actions/workflows/release-engine.yml).`);
|
||||
console.log(` It publishes the five binaries + .sha256 as ${REPO_URL}/releases/tag/${tag}.`);
|
||||
console.log(' Then publish the five @impeccable/cli-<os>-<arch> npm platform packages, then release the CLI/skill.');
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ export const SUITES = {
|
||||
'tests/hook-build.test.mjs',
|
||||
'tests/openai-plugin.test.mjs',
|
||||
'tests/release.test.mjs',
|
||||
'tests/check-detector-release.test.mjs',
|
||||
'tests/skill-reference.test.mjs',
|
||||
'tests/readme-gitignore.test.mjs',
|
||||
'tests/test-suites.test.mjs',
|
||||
|
||||
Reference in New Issue
Block a user