fix(ci): normalize shell validation input (fixes #3)
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
|
examples/server/forgeflow-deploy text eol=lf
|
||||||
|
scripts/* text eol=lf
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
const fs = require('node:fs');
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
|
|
||||||
function normalizeRelativePosixPath(value) {
|
function normalizeRelativePosixPath(value) {
|
||||||
@@ -19,11 +20,14 @@ function bashSyntaxCheckInvocation(root, scriptPath = 'examples/server/forgeflow
|
|||||||
if (typeof root !== 'string' || !root.trim()) {
|
if (typeof root !== 'string' || !root.trim()) {
|
||||||
throw new Error('Project root is required for shell validation.');
|
throw new Error('Project root is required for shell validation.');
|
||||||
}
|
}
|
||||||
|
const relativeScriptPath = normalizeRelativePosixPath(scriptPath);
|
||||||
|
const scriptText = fs.readFileSync(path.join(root, ...relativeScriptPath.split('/')), 'utf8');
|
||||||
return {
|
return {
|
||||||
command: 'bash',
|
command: 'bash',
|
||||||
args: ['-n', normalizeRelativePosixPath(scriptPath)],
|
args: ['-n'],
|
||||||
options: {
|
options: {
|
||||||
cwd: root,
|
cwd: root,
|
||||||
|
input: scriptText.replace(/\r\n?/g, '\n'),
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
windowsHide: true
|
windowsHide: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,6 @@ import shellVerification from '../src/shared/shell-verification.cjs';
|
|||||||
|
|
||||||
const { bashSyntaxCheckInvocation, bashSyntaxCheckFromTextInvocation, normalizeRelativePosixPath, validateShellScriptStructure, shouldRunExternalBash } = shellVerification;
|
const { bashSyntaxCheckInvocation, bashSyntaxCheckFromTextInvocation, normalizeRelativePosixPath, validateShellScriptStructure, shouldRunExternalBash } = 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', () => {
|
test('Shell validation refuses absolute and escaping script paths', () => {
|
||||||
assert.throws(() => normalizeRelativePosixPath('C:\\Projects\\ForgeFlow\\script.sh'), /must be relative/);
|
assert.throws(() => normalizeRelativePosixPath('C:\\Projects\\ForgeFlow\\script.sh'), /must be relative/);
|
||||||
assert.throws(() => normalizeRelativePosixPath('/tmp/script.sh'), /must be relative/);
|
assert.throws(() => normalizeRelativePosixPath('/tmp/script.sh'), /must be relative/);
|
||||||
@@ -34,11 +25,19 @@ test('Bash syntax validation works from a project root containing spaces', async
|
|||||||
await mkdir(relativeDirectory, { recursive: true });
|
await mkdir(relativeDirectory, { recursive: true });
|
||||||
await copyFile(new URL('../examples/server/forgeflow-deploy', import.meta.url), path.join(relativeDirectory, 'forgeflow-deploy'));
|
await copyFile(new URL('../examples/server/forgeflow-deploy', import.meta.url), path.join(relativeDirectory, 'forgeflow-deploy'));
|
||||||
const invocation = bashSyntaxCheckInvocation(tempBase);
|
const invocation = bashSyntaxCheckInvocation(tempBase);
|
||||||
|
assert.equal(invocation.options.cwd, tempBase);
|
||||||
|
assert.deepEqual(invocation.args, ['-n']);
|
||||||
|
assert.equal(invocation.options.input.includes('\r'), false);
|
||||||
const result = spawnSync(invocation.command, invocation.args, invocation.options);
|
const result = spawnSync(invocation.command, invocation.args, invocation.options);
|
||||||
assert.equal(result.status, 0, result.stderr);
|
assert.equal(result.status, 0, result.stderr);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
await rm(tempBase, { recursive: true, force: true, maxRetries: 20, retryDelay: 100 });
|
await rm(tempBase, {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
maxRetries: 20,
|
||||||
|
retryDelay: 100
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Git Bash on Windows can retain a short-lived working-directory handle
|
// 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
|
// after bash -n exits. Do not fail a successful syntax test solely because
|
||||||
@@ -48,7 +47,6 @@ test('Bash syntax validation works from a project root containing spaces', async
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Bash syntax validation from text does not depend on a Windows working directory', () => {
|
test('Bash syntax validation from text does not depend on a Windows working directory', () => {
|
||||||
const invocation = bashSyntaxCheckFromTextInvocation('#!/usr/bin/env bash\nset -euo pipefail\necho ok\n');
|
const invocation = bashSyntaxCheckFromTextInvocation('#!/usr/bin/env bash\nset -euo pipefail\necho ok\n');
|
||||||
assert.equal(invocation.command, 'bash');
|
assert.equal(invocation.command, 'bash');
|
||||||
@@ -67,7 +65,6 @@ test('Bash syntax validation from text detects malformed scripts', (t) => {
|
|||||||
assert.notEqual(result.status, 0);
|
assert.notEqual(result.status, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('portable server-script validation does not require a local Bash executable', () => {
|
test('portable server-script validation does not require a local Bash executable', () => {
|
||||||
const script = `#!/usr/bin/env bash
|
const script = `#!/usr/bin/env bash
|
||||||
set -Eeuo pipefail
|
set -Eeuo pipefail
|
||||||
@@ -89,13 +86,9 @@ write_status "unhealthy"
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('portable server-script validation refuses missing deployment safety markers', () => {
|
test('portable server-script validation refuses missing deployment safety markers', () => {
|
||||||
assert.throws(
|
assert.throws(() => validateShellScriptStructure('#!/usr/bin/env bash\nset -Eeuo pipefail\necho unsafe\n'), /missing required safety marker/);
|
||||||
() => validateShellScriptStructure('#!/usr/bin/env bash\nset -Eeuo pipefail\necho unsafe\n'),
|
|
||||||
/missing required safety marker/
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Windows publication never depends on an external Bash shim', () => {
|
test('Windows publication never depends on an external Bash shim', () => {
|
||||||
assert.equal(shouldRunExternalBash('win32'), false);
|
assert.equal(shouldRunExternalBash('win32'), false);
|
||||||
assert.equal(shouldRunExternalBash('linux'), true);
|
assert.equal(shouldRunExternalBash('linux'), true);
|
||||||
|
|||||||
Reference in New Issue
Block a user