mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-18 09:06:53 +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,53 @@
|
||||
/**
|
||||
* scripts/check-detector-release.mjs: the release-order guard for the closed
|
||||
* detector archives that crates/core/build.rs downloads. Probes are injected
|
||||
* so the test never touches the network.
|
||||
*/
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
archiveAsset, assetUrl, checkDetectorRelease, DETECTOR_TARGETS, BROWSER_BUNDLE_ASSET, DEFAULT_DETECTOR_BASE,
|
||||
} from '../scripts/check-detector-release.mjs';
|
||||
|
||||
const okResponse = { status: 206, body: { cancel: async () => {} } };
|
||||
const missingResponse = { status: 404, body: null };
|
||||
|
||||
describe('check-detector-release', () => {
|
||||
it('names one archive per target, .lib on Windows, and the detector-v tag in the URL', () => {
|
||||
assert.equal(archiveAsset('darwin-arm64'), 'libimpeccable_detector-darwin-arm64.a');
|
||||
assert.equal(archiveAsset('windows-x64'), 'impeccable_detector-windows-x64.lib');
|
||||
assert.equal(
|
||||
assetUrl('0.1.0', archiveAsset('linux-x64'), 'https://example.test/dl/'),
|
||||
'https://example.test/dl/detector-v0.1.0/libimpeccable_detector-linux-x64.a',
|
||||
);
|
||||
assert.equal(DEFAULT_DETECTOR_BASE, 'https://github.com/pbakaus/impeccable/releases/download');
|
||||
});
|
||||
|
||||
it('passes when every archive, checksum and the browser bundle answer', async () => {
|
||||
const seen = [];
|
||||
const fetchImpl = async (url) => { seen.push(url); return okResponse; };
|
||||
const result = await checkDetectorRelease({ version: '0.1.0', base: 'https://example.test/dl', fetchImpl });
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.missing.length, 0);
|
||||
assert.equal(seen.length, DETECTOR_TARGETS.length * 2 + 1);
|
||||
assert.ok(seen.includes(`https://example.test/dl/detector-v0.1.0/${BROWSER_BUNDLE_ASSET}`));
|
||||
});
|
||||
|
||||
it('lists every missing asset, sorted by target then archive/checksum/bundle', async () => {
|
||||
const fetchImpl = async (url) => (url.includes('windows-x64') || url.endsWith(BROWSER_BUNDLE_ASSET) ? missingResponse : okResponse);
|
||||
const result = await checkDetectorRelease({ version: '0.1.0', base: 'https://example.test/dl', fetchImpl });
|
||||
assert.equal(result.ok, false);
|
||||
assert.deepEqual(result.missing.map((m) => m.what), [
|
||||
'impeccable_detector-windows-x64.lib',
|
||||
'impeccable_detector-windows-x64.lib.sha256',
|
||||
BROWSER_BUNDLE_ASSET,
|
||||
]);
|
||||
});
|
||||
|
||||
it('treats a network error as a missing asset instead of throwing', async () => {
|
||||
const fetchImpl = async () => { throw new Error('offline'); };
|
||||
const result = await checkDetectorRelease({ version: '0.1.0', base: 'https://example.test/dl', fetchImpl });
|
||||
assert.equal(result.ok, false);
|
||||
assert.equal(result.missing.length, DETECTOR_TARGETS.length * 2 + 1);
|
||||
});
|
||||
});
|
||||
+39
-3
@@ -52,7 +52,7 @@ function runRelease(cwd, ...args) {
|
||||
// The D4 engine release-order guard would otherwise probe the network for
|
||||
// published engine assets; these guards predate it and only exercise the
|
||||
// version/changelog/artifact checks, so take its documented escape hatch.
|
||||
env: { ...process.env, IMPECCABLE_SKIP_ENGINE_CHECK: '1' },
|
||||
env: { ...process.env, IMPECCABLE_SKIP_ENGINE_CHECK: '1', IMPECCABLE_SKIP_DETECTOR_CHECK: '1' },
|
||||
});
|
||||
return { code: 0, stdout, stderr: '' };
|
||||
} catch (err) {
|
||||
@@ -88,12 +88,18 @@ describe('release.mjs guards', () => {
|
||||
// (and check-engine-release.mjs imports fetch-engine.mjs), so stage them
|
||||
// too or the dry runs fail to resolve the modules instead of exercising
|
||||
// the guard.
|
||||
for (const dep of ['check-engine-release.mjs', 'fetch-engine.mjs']) {
|
||||
for (const dep of ['check-engine-release.mjs', 'check-detector-release.mjs', 'fetch-engine.mjs']) {
|
||||
fs.copyFileSync(path.join(REPO_ROOT, 'scripts', dep), path.join(workDir, 'scripts', dep));
|
||||
}
|
||||
write('.claude-plugin/plugin.json', JSON.stringify({ name: 'impeccable', version: '1.2.3' }));
|
||||
write('.claude-plugin/marketplace.json', JSON.stringify({ plugins: [{ name: 'impeccable', version: '1.2.3' }] }));
|
||||
write('package.json', JSON.stringify({ name: 'impeccable', version: '9.9.9' }));
|
||||
write('package.json', JSON.stringify({
|
||||
name: 'impeccable',
|
||||
version: '9.9.9',
|
||||
optionalDependencies: { '@impeccable/cli-darwin-arm64': '0.1.0', '@impeccable/cli-linux-x64': '0.1.0' },
|
||||
}));
|
||||
write('ENGINE_VERSION', '0.1.0\n');
|
||||
write('DETECTOR_VERSION', '0.1.0\n');
|
||||
write('extension/manifest.json', JSON.stringify({ version: '2.0.0' }));
|
||||
write('site/pages/changelog.astro', CHANGELOG);
|
||||
write('dist/universal.zip', 'zip');
|
||||
@@ -130,6 +136,36 @@ describe('release.mjs guards', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('dry-runs a clean engine release: tags only, CI publishes', () => {
|
||||
const { code, stdout } = runRelease(workDir, 'engine');
|
||||
assert.equal(code, 0, stdout);
|
||||
assert.match(stdout, /Engine 0\.1\.0/);
|
||||
assert.match(stdout, /2 platform package pins agree/);
|
||||
assert.match(stdout, /Skipping detector release-order guard/);
|
||||
assert.match(stdout, /\[dry-run\] git tag -a engine-v0\.1\.0/);
|
||||
assert.match(stdout, /\[dry-run\] git push origin engine-v0\.1\.0/);
|
||||
assert.doesNotMatch(stdout, /gh release create/);
|
||||
assert.match(stdout, /release-engine workflow/);
|
||||
});
|
||||
|
||||
it('engine: refuses when package.json platform pins disagree with ENGINE_VERSION', () => {
|
||||
write('ENGINE_VERSION', '0.2.0\n');
|
||||
git(workDir, 'commit', '-am', 'bump engine');
|
||||
git(workDir, 'push', 'origin', 'main');
|
||||
const { code, stderr } = runRelease(workDir, 'engine');
|
||||
assert.notEqual(code, 0);
|
||||
assert.match(stderr, /pins @impeccable\/cli-darwin-arm64@0\.1\.0.*expected 0\.2\.0/);
|
||||
});
|
||||
|
||||
it('engine: refuses when the tag already exists on origin', () => {
|
||||
git(workDir, 'tag', 'engine-v0.1.0');
|
||||
git(workDir, 'push', 'origin', 'engine-v0.1.0');
|
||||
git(workDir, 'tag', '-d', 'engine-v0.1.0');
|
||||
const { code, stderr } = runRelease(workDir, 'engine');
|
||||
assert.notEqual(code, 0);
|
||||
assert.match(stderr, /engine-v0\.1\.0 already exists on origin/);
|
||||
});
|
||||
|
||||
it('dry-runs a clean skill release end to end', () => {
|
||||
const { code, stdout } = runRelease(workDir, 'skill');
|
||||
assert.equal(code, 0, stdout);
|
||||
|
||||
Reference in New Issue
Block a user