50 lines
2.4 KiB
JavaScript
50 lines
2.4 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { mkdtemp, mkdir, copyFile, rm } from 'node:fs/promises';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import test from 'node:test';
|
|
import shellVerification from '../src/shared/shell-verification.cjs';
|
|
|
|
const { bashSyntaxCheckInvocation, normalizeRelativePosixPath } = shellVerification;
|
|
|
|
test('Bash syntax validation keeps Windows project roots in cwd and passes a relative POSIX path', () => {
|
|
const invocation = bashSyntaxCheckInvocation('C:\\Projects\\ForgeFlow');
|
|
assert.equal(invocation.command, 'bash');
|
|
assert.deepEqual(invocation.args, ['-n', 'examples/server/forgeflow-deploy']);
|
|
assert.equal(invocation.options.cwd, 'C:\\Projects\\ForgeFlow');
|
|
assert.equal(invocation.args[1].includes('\\'), false);
|
|
assert.equal(/^[A-Za-z]:/.test(invocation.args[1]), false);
|
|
});
|
|
|
|
test('Shell validation refuses absolute and escaping script paths', () => {
|
|
assert.throws(() => normalizeRelativePosixPath('C:\\Projects\\ForgeFlow\\script.sh'), /must be relative/);
|
|
assert.throws(() => normalizeRelativePosixPath('/tmp/script.sh'), /must be relative/);
|
|
assert.throws(() => normalizeRelativePosixPath('../script.sh'), /may not escape/);
|
|
});
|
|
|
|
test('Bash syntax validation works from a project root containing spaces', async (t) => {
|
|
if (spawnSync('bash', ['--version'], { encoding: 'utf8' }).status !== 0) {
|
|
t.skip('Bash is not available in this environment.');
|
|
return;
|
|
}
|
|
const tempBase = await mkdtemp(path.join(os.tmpdir(), 'forge flow verify '));
|
|
try {
|
|
const relativeDirectory = path.join(tempBase, 'examples', 'server');
|
|
await mkdir(relativeDirectory, { recursive: true });
|
|
await copyFile(new URL('../examples/server/forgeflow-deploy', import.meta.url), path.join(relativeDirectory, 'forgeflow-deploy'));
|
|
const invocation = bashSyntaxCheckInvocation(tempBase);
|
|
const result = spawnSync(invocation.command, invocation.args, invocation.options);
|
|
assert.equal(result.status, 0, result.stderr);
|
|
} finally {
|
|
try {
|
|
await rm(tempBase, { recursive: true, force: true, maxRetries: 20, retryDelay: 100 });
|
|
} catch (error) {
|
|
// Git Bash on Windows can retain a short-lived working-directory handle
|
|
// after bash -n exits. Do not fail a successful syntax test solely because
|
|
// Windows delayed releasing that temporary directory.
|
|
if (!['EBUSY', 'EPERM', 'ENOTEMPTY'].includes(error?.code)) throw error;
|
|
}
|
|
}
|
|
});
|