d48cda4081
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
/**
|
|
* Tests for shebang consistency across continuous-learning-v2 shell scripts
|
|
*
|
|
* Every `*.sh` script under skills/continuous-learning-v2/ must use the
|
|
* portable `#!/usr/bin/env bash` shebang rather than the hardcoded
|
|
* `#!/bin/bash`. The hardcoded interpreter path fails on systems where bash
|
|
* is not installed at /bin/bash (NixOS, some Homebrew layouts, FreeBSD), and
|
|
* observe.sh runs on every hook invocation. This guards against the
|
|
* inconsistency (#2303) reappearing.
|
|
*
|
|
* Run with: node tests/hooks/continuous-learning-shebang-consistency.test.js
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
process.stdout.write(` ✓ ${name}\n`);
|
|
passed++;
|
|
} catch (err) {
|
|
process.stderr.write(` ✗ ${name}\n`);
|
|
process.stderr.write(` ${err && err.stack ? err.stack : String(err)}\n`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
const skillDir = path.join(repoRoot, 'skills', 'continuous-learning-v2');
|
|
const PORTABLE_SHEBANG = '#!/usr/bin/env bash';
|
|
const HARDCODED_SHEBANG = '#!/bin/bash';
|
|
|
|
function collectShellScripts(dir, acc = []) {
|
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
// Skip hidden/runtime directories (e.g. the observer's `.observer-tmp`)
|
|
// so an untracked local artifact cannot trigger a false failure; only
|
|
// committed skill scripts are checked.
|
|
if (entry.name.startsWith('.')) {
|
|
continue;
|
|
}
|
|
collectShellScripts(fullPath, acc);
|
|
} else if (entry.isFile() && entry.name.endsWith('.sh')) {
|
|
acc.push(fullPath);
|
|
}
|
|
}
|
|
return acc;
|
|
}
|
|
|
|
function firstLine(filePath) {
|
|
return fs.readFileSync(filePath, 'utf8').split(/\r?\n/, 1)[0];
|
|
}
|
|
|
|
console.log('\n=== continuous-learning-v2 shebang consistency ===\n');
|
|
|
|
const scripts = collectShellScripts(skillDir);
|
|
|
|
test('skill directory contains shell scripts to check', () => {
|
|
assert.ok(scripts.length > 0, `expected at least one .sh under ${skillDir}`);
|
|
});
|
|
|
|
for (const scriptPath of scripts) {
|
|
const rel = path.relative(repoRoot, scriptPath).split(path.sep).join('/');
|
|
test(`${rel} uses portable '#!/usr/bin/env bash'`, () => {
|
|
assert.strictEqual(
|
|
firstLine(scriptPath),
|
|
PORTABLE_SHEBANG,
|
|
`${rel} should start with '${PORTABLE_SHEBANG}'`
|
|
);
|
|
});
|
|
}
|
|
|
|
test('no continuous-learning-v2 script uses hardcoded #!/bin/bash', () => {
|
|
const offenders = scripts
|
|
.filter(scriptPath => firstLine(scriptPath) === HARDCODED_SHEBANG)
|
|
.map(scriptPath => path.relative(repoRoot, scriptPath).split(path.sep).join('/'));
|
|
assert.strictEqual(
|
|
offenders.length,
|
|
0,
|
|
`hardcoded #!/bin/bash found in: ${offenders.join(', ')}`
|
|
);
|
|
});
|
|
|
|
// ──────────────────────────────────────────────────────
|
|
// Summary
|
|
// ──────────────────────────────────────────────────────
|
|
|
|
console.log('\n=== Test Results ===');
|
|
console.log(`Passed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
console.log(`Total: ${passed + failed}\n`);
|
|
|
|
process.exit(failed > 0 ? 1 : 0);
|