mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-30 01:51:20 +00:00
Merge d564e1bbf2 into 6bb99767ff
This commit is contained in:
commit
b211831f80
3 changed files with 152 additions and 0 deletions
|
|
@ -15,6 +15,15 @@ This project uses the [PolyForm Noncommercial License 1.0.0](https://polyformpro
|
|||
|
||||
**Prerequisites:** Node.js — `gitnexus/` requires `^22.18.0 || >=24.11.0` and `gitnexus-web/` requires `^20.19.0 || >=22.12.0` (enforced via the `engines` field in each package). Use `nvm install` to match the local version.
|
||||
|
||||
One unit test (`gitnexus/test/unit/publish-rc-guard.test.ts`) also spawns a
|
||||
real `bash` to exercise the release-subject regex under `nocasematch`
|
||||
semantics. On Linux and macOS the PATH `bash` is used as-is. On Windows the
|
||||
test probes Git for Windows' `bash.exe` (both the Program Files and the
|
||||
per-user install) before falling back to the PATH entry, because the PATH
|
||||
`bash` is frequently the WSL launcher. The suite skips with an explicit
|
||||
message when no suitable bash is found; point `GITNEXUS_TEST_BASH` at a
|
||||
specific executable to override discovery.
|
||||
|
||||
1. Clone the repository.
|
||||
2. **CLI / MCP package:** `cd gitnexus && npm install && npm run build`
|
||||
`prepare` / `scripts/build.js` compiles `gitnexus-shared` with
|
||||
|
|
|
|||
|
|
@ -284,6 +284,10 @@ const SPAWN_CLI = [
|
|||
// tree-sitter grammars at import time. The offender regexes match grammar
|
||||
// paths with either separator, which only the Windows runner proves.
|
||||
'test/integration/optional-grammars/registry-import-closure.test.ts',
|
||||
// Spawns `bash` to exercise the rc-guard release-subject regex under real
|
||||
// nocasematch semantics; bash availability/behavior differs on Windows and
|
||||
// macOS runners, so the suite must cover it beyond the Ubuntu full run.
|
||||
'test/unit/publish-rc-guard.test.ts',
|
||||
];
|
||||
|
||||
// Worker threads tests — exercise real worker_threads which have
|
||||
|
|
|
|||
139
gitnexus/test/unit/publish-rc-guard.test.ts
Normal file
139
gitnexus/test/unit/publish-rc-guard.test.ts
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import { readFileSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
/**
|
||||
* Regression coverage for the rc-guard release-PR skip in
|
||||
* .github/workflows/publish.yml. The `chore: release vX.Y.Z` subject match is
|
||||
* load-bearing: it prevents an RC build firing on the release-PR commit from
|
||||
* racing the imminent stable-tag push on the same SHA (see the v1.6.4 race
|
||||
* history referenced in the workflow comments). This test pins the exact
|
||||
* regex shipped in the workflow by extracting it from the YAML and running it
|
||||
* under the same bash semantics (`shopt -s nocasematch`, anchored POSIX ERE).
|
||||
*/
|
||||
const REPO_ROOT = path.resolve(__dirname, '../../..');
|
||||
const WORKFLOW = path.join(REPO_ROOT, '.github/workflows/publish.yml');
|
||||
|
||||
function releaseSubjectRegex(): string {
|
||||
const yaml = readFileSync(WORKFLOW, 'utf8');
|
||||
const match = yaml.match(/RELEASE_SUBJECT_RE='([^']+)'/);
|
||||
if (!match) {
|
||||
throw new Error(
|
||||
'RELEASE_SUBJECT_RE not found in publish.yml — did the rc-guard Decide step change?',
|
||||
);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function subjectMatches(subject: string, regex: string): boolean {
|
||||
const script = [
|
||||
'set -euo pipefail',
|
||||
'shopt -s nocasematch',
|
||||
`SUBJECT=${JSON.stringify(subject)}`,
|
||||
`REGEX=${JSON.stringify(regex)}`,
|
||||
'[[ "$SUBJECT" =~ $REGEX ]] && echo MATCH || echo NO_MATCH',
|
||||
'shopt -u nocasematch',
|
||||
].join('\n');
|
||||
const out = execFileSync(BASH, ['-c', script], { encoding: 'utf8' }).trim();
|
||||
return out === 'MATCH';
|
||||
}
|
||||
|
||||
// Probe script: only a real bash with nocasematch semantics (the behavior
|
||||
// this suite pins) prints BASH_OK. A Windows PATH `bash` that is actually
|
||||
// the WSL launcher exits with an error when no distribution is installed,
|
||||
// so it fails this probe instead of failing six unit tests.
|
||||
const BASH_PROBE = 'shopt -s nocasematch; [[ "Chore" =~ ^chore$ ]] && echo BASH_OK';
|
||||
|
||||
function probeBash(candidate: string): boolean {
|
||||
try {
|
||||
const out = execFileSync(candidate, ['-c', BASH_PROBE], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
}).trim();
|
||||
return out === 'BASH_OK';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve a bash executable that can actually run the nocasematch ERE
|
||||
// semantics this suite extracts from publish.yml. Candidates are probed in
|
||||
// order; the first one that passes the nocasematch probe wins:
|
||||
// 1. GITNEXUS_TEST_BASH (explicit override for unusual installs)
|
||||
// 2. on win32, Git for Windows' bash.exe — probed before the PATH entry
|
||||
// because the PATH `bash` on Windows is frequently the System32 WSL
|
||||
// launcher (both the Program Files and the per-user install)
|
||||
// 3. plain `bash` from PATH (the POSIX default)
|
||||
// When no candidate passes, the suite below skips with an explicit reason
|
||||
// instead of failing: CONTRIBUTING lists Node.js as the prerequisite, so a
|
||||
// contributor without any suitable bash is supported, not broken.
|
||||
function resolveBash(): string | null {
|
||||
const candidates: string[] = [];
|
||||
const override = process.env['GITNEXUS_TEST_BASH'];
|
||||
if (override) {
|
||||
candidates.push(override);
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
const programFiles = process.env['ProgramFiles'];
|
||||
const localAppData = process.env['LocalAppData'];
|
||||
if (programFiles) {
|
||||
candidates.push(path.join(programFiles, 'Git', 'bin', 'bash.exe'));
|
||||
}
|
||||
if (localAppData) {
|
||||
candidates.push(path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'));
|
||||
}
|
||||
}
|
||||
candidates.push('bash');
|
||||
for (const candidate of candidates) {
|
||||
if (probeBash(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const BASH = resolveBash();
|
||||
|
||||
describe.skipIf(!BASH)(
|
||||
`rc-guard release-subject regex (publish.yml)${BASH ? '' : ' — skipped: no bash with nocasematch semantics found (install Git for Windows or point GITNEXUS_TEST_BASH at one)'}`,
|
||||
() => {
|
||||
const regex = releaseSubjectRegex();
|
||||
|
||||
it('matches canonical release subjects', () => {
|
||||
expect(subjectMatches('chore: release v1.6.4', regex)).toBe(true);
|
||||
expect(subjectMatches('chore: release v10.20.30', regex)).toBe(true);
|
||||
});
|
||||
|
||||
it('matches squash-merge subjects with the (#NNNN) suffix', () => {
|
||||
expect(subjectMatches('chore: release v1.6.4 (#1474)', regex)).toBe(true);
|
||||
});
|
||||
|
||||
it('stays case-insensitive for IDE auto-capitalization', () => {
|
||||
expect(subjectMatches('Chore: Release v1.2.3', regex)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not match ordinary chore commits', () => {
|
||||
expect(subjectMatches('chore: bump deps (#1500)', regex)).toBe(false);
|
||||
});
|
||||
|
||||
it('does not match release-like subjects with extra suffixes or prefixes', () => {
|
||||
expect(subjectMatches('chore: release v1.6.4 hotfix', regex)).toBe(false);
|
||||
expect(subjectMatches('revert: chore: release v1.6.4', regex)).toBe(false);
|
||||
});
|
||||
|
||||
it('requires a full semver', () => {
|
||||
expect(subjectMatches('chore: release v1.6', regex)).toBe(false);
|
||||
expect(subjectMatches('chore: release v1.6.x', regex)).toBe(false);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
// Runs on every platform, including machines where the suite above skips:
|
||||
// proves the capability probe actually rejects an unusable candidate instead
|
||||
// of silently treating every spawn failure as "bash found".
|
||||
describe('rc-guard bash resolution probe', () => {
|
||||
it('rejects a candidate that cannot run the nocasematch probe', () => {
|
||||
expect(probeBash('gitnexus-definitely-not-a-shell')).toBe(false);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue