mirror of
https://github.com/JuliusBrussee/caveman.git
synced 2026-08-11 13:21:09 +02:00
irm .../install.ps1 | iex crashed with "Cannot bind argument to parameter 'Path' because it is null": under iex there is no script file, so $MyInvocation.MyCommand.Path is $null and Split-Path threw. The top-level param() block also can't receive arguments through a pipe. - wrap all logic in Install-Caveman, invoked at the bottom with the automatic $args (populated for file runs, empty under iex) - replace $MyInvocation.MyCommand.Path with $PSCommandPath, guarded — pipe installs skip the local-clone branch and go straight to npx - static regression tests in tests/installer/ps1-pipe.test.mjs (CI has no pwsh, so pin the pipe-safety contract textually) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011kmm2umRGb5nLxdimrwweZ
59 lines
2.4 KiB
JavaScript
59 lines
2.4 KiB
JavaScript
// Regression for #565: `irm .../install.ps1 | iex` crashed with
|
|
// "Cannot bind argument to parameter 'Path' because it is null."
|
|
//
|
|
// Two pipe-execution rules for install.ps1 (static checks — CI has no pwsh):
|
|
// 1. No top-level param() block. iex executes the file as a string, so a
|
|
// top-level param can never receive arguments and (depending on host)
|
|
// trips parsing. All logic lives in a function invoked at the bottom.
|
|
// 2. Script-path variables ($PSCommandPath / $MyInvocation.MyCommand.Path)
|
|
// are $null under iex — any use must be guarded, never passed straight
|
|
// into Split-Path (that was the #565 crash).
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
const REPO_ROOT = path.resolve(HERE, '..', '..');
|
|
const PS1 = fs.readFileSync(path.join(REPO_ROOT, 'install.ps1'), 'utf8');
|
|
|
|
// Strip comment lines so doc mentions of param()/path vars don't false-positive.
|
|
const code = PS1.split('\n').filter(l => !/^\s*#/.test(l)).join('\n');
|
|
|
|
test('#565 install.ps1 has no top-level param block (everything inside a function)', () => {
|
|
const beforeFunction = code.slice(0, code.indexOf('function '));
|
|
assert.ok(code.includes('function '), 'install.ps1 must wrap its logic in a function for iex piping');
|
|
assert.ok(
|
|
!/param\s*\(/i.test(beforeFunction),
|
|
'install.ps1 must not declare a top-level param() — it cannot receive args under `irm | iex` (issue #565)',
|
|
);
|
|
});
|
|
|
|
test('#565 install.ps1 never uses $MyInvocation.MyCommand.Path (null under iex)', () => {
|
|
assert.ok(
|
|
!/\$MyInvocation\.MyCommand\.Path/i.test(code),
|
|
'install.ps1 must not rely on $MyInvocation.MyCommand.Path — it is $null when piped to iex (issue #565)',
|
|
);
|
|
});
|
|
|
|
test('#565 install.ps1 guards $PSCommandPath before Split-Path', () => {
|
|
if (/\$PSCommandPath/i.test(code)) {
|
|
assert.match(
|
|
code,
|
|
/if\s*\(\s*\$PSCommandPath\s*\)/i,
|
|
'$PSCommandPath is $null under `irm | iex` — it must be truthiness-guarded before use (issue #565)',
|
|
);
|
|
}
|
|
});
|
|
|
|
test('#565 install.ps1 invokes its function at the bottom (script still does something)', () => {
|
|
const lastLines = code.trim().split('\n').slice(-3).join('\n');
|
|
assert.match(
|
|
lastLines,
|
|
/Install-Caveman/,
|
|
'install.ps1 must actually invoke Install-Caveman after defining it',
|
|
);
|
|
});
|