chore: harden runner setup

This commit is contained in:
Peter Steinberger 2025-11-08 12:29:38 +00:00
parent bc773950c0
commit 0f4391e2ab
6 changed files with 113 additions and 14 deletions

View File

@ -31,6 +31,7 @@
"clean": "rimraf dist",
"dev": "tsc -w -p tsconfig.build.json",
"prepublishOnly": "pnpm check && pnpm test && pnpm build",
"docs:list": "pnpm exec tsx scripts/docs-list.ts",
"mcporter:list": "pnpm exec tsx src/cli.ts list",
"mcporter:call": "pnpm exec tsx src/cli.ts call"
},
@ -50,6 +51,7 @@
"@types/express": "^5.0.5",
"@types/node": "^24.10.0",
"@typescript/native-preview": "7.0.0-dev.20251104.1",
"bun-types": "^1.3.2",
"express": "^5.1.0",
"oxlint": "^1.25.0",
"oxlint-tsgolint": "^0.5.0",

25
pnpm-lock.yaml generated
View File

@ -48,6 +48,9 @@ importers:
'@typescript/native-preview':
specifier: 7.0.0-dev.20251104.1
version: 7.0.0-dev.20251104.1
bun-types:
specifier: ^1.3.2
version: 1.3.2(@types/react@19.2.2)
express:
specifier: ^5.1.0
version: 5.1.0
@ -631,6 +634,9 @@ packages:
'@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
'@types/react@19.2.2':
resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
'@types/send@0.17.6':
resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
@ -752,6 +758,11 @@ packages:
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
engines: {node: '>=18'}
bun-types@1.3.2:
resolution: {integrity: sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg==}
peerDependencies:
'@types/react': ^19
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@ -815,6 +826,9 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@ -1819,6 +1833,10 @@ snapshots:
'@types/range-parser@1.2.7': {}
'@types/react@19.2.2':
dependencies:
csstype: 3.1.3
'@types/send@0.17.6':
dependencies:
'@types/mime': 1.3.5
@ -1948,6 +1966,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
bun-types@1.3.2(@types/react@19.2.2):
dependencies:
'@types/node': 24.10.0
'@types/react': 19.2.2
bytes@3.1.2: {}
call-bind-apply-helpers@1.0.2:
@ -1999,6 +2022,8 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
csstype@3.1.3: {}
debug@4.4.3:
dependencies:
ms: 2.1.3

View File

@ -15,7 +15,9 @@ function walkMarkdownFiles(dir: string, base: string = dir): string[] {
const entries = readdirSync(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
if (entry.name.startsWith('.')) continue;
if (entry.name.startsWith('.')) {
continue;
}
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
if (EXCLUDED_DIRS.has(entry.name)) {

View File

@ -56,6 +56,9 @@ export function findGitSubcommand(commandArgs: string[]): GitCommandInfo | null
while (index < commandArgs.length) {
const token = commandArgs[index];
if (token === undefined) {
break;
}
if (token === '--') {
const next = commandArgs[index + 1];
return next ? { name: next, index: index + 1 } : null;
@ -83,6 +86,9 @@ export function determineGitWorkdir(baseDir: string, gitArgs: string[], command:
while (index < limit) {
const token = gitArgs[index];
if (token === undefined) {
break;
}
if (token === '-C') {
const next = gitArgs[index + 1];
if (next) {

View File

@ -156,13 +156,23 @@ function shouldExtendTimeout(commandArgs: string[]): boolean {
return false;
}
const [first, ...rest] = tokens;
const first = tokens[0];
if (!first) {
return false;
}
const rest = tokens.slice(1);
if (first === 'pnpm') {
if (rest.length === 0) {
return false;
}
const subcommand = rest[0];
if (!subcommand) {
return false;
}
if (!subcommand) {
return false;
}
if (subcommand === 'run') {
const script = rest[1];
if (!script) {
@ -218,13 +228,20 @@ function shouldUseLintTimeout(commandArgs: string[]): boolean {
return false;
}
const [first, ...rest] = tokens;
const first = tokens[0];
if (!first) {
return false;
}
const rest = tokens.slice(1);
if (first === 'pnpm') {
if (rest.length === 0) {
return false;
}
const subcommand = rest[0];
if (!subcommand) {
return false;
}
if (subcommand === 'run') {
const script = rest[1];
return typeof script === 'string' && script.startsWith('lint');
@ -257,7 +274,11 @@ function isSingleTestInvocation(commandArgs: string[]): boolean {
}
}
const [first, ...rest] = tokens;
const first = tokens[0];
if (!first) {
return false;
}
const rest = tokens.slice(1);
if (first === 'pnpm') {
if (rest[0] === 'test:file') {
return true;
@ -293,6 +314,9 @@ function tokenReferencesIntegrationTest(token: string): boolean {
function referencesIntegrationSpec(tokens: string[]): boolean {
for (let index = 0; index < tokens.length; index += 1) {
const token = tokens[index];
if (!token) {
continue;
}
if (token === '--run' || token === '--include') {
const next = tokens[index + 1];
if (next && tokenReferencesIntegrationTest(next)) {
@ -316,13 +340,34 @@ function matchesScriptKeyword(script: string, keywords: readonly string[]): bool
function stripWrappersAndAssignments(args: string[]): string[] {
const tokens = [...args];
while (tokens.length > 0 && isEnvAssignment(tokens[0])) {
while (tokens.length > 0) {
const candidate = tokens[0];
if (!candidate) {
break;
}
if (!isEnvAssignment(candidate)) {
break;
}
tokens.shift();
}
while (tokens.length > 0 && WRAPPER_COMMANDS.has(tokens[0])) {
while (tokens.length > 0) {
const wrapper = tokens[0];
if (!wrapper) {
break;
}
if (!WRAPPER_COMMANDS.has(wrapper)) {
break;
}
tokens.shift();
while (tokens.length > 0 && isEnvAssignment(tokens[0])) {
while (tokens.length > 0) {
const assignment = tokens[0];
if (!assignment) {
break;
}
if (!isEnvAssignment(assignment)) {
break;
}
tokens.shift();
}
}
@ -344,6 +389,9 @@ function isTestRunnerSuiteInvocation(tokens: string[], suite: string): boolean {
const normalizedSuite = suite.toLowerCase();
for (let index = 0; index < tokens.length; index += 1) {
const token = tokens[index];
if (!token) {
continue;
}
const normalizedToken = token.replace(/^[./\\]+/, '');
if (normalizedToken === 'scripts/test-runner.ts' || normalizedToken.endsWith('/scripts/test-runner.ts')) {
const suiteToken = tokens[index + 1]?.toLowerCase();
@ -363,7 +411,11 @@ function shouldUseLongTimeout(commandArgs: string[]): boolean {
return false;
}
const [first, ...rest] = tokens;
const first = tokens[0];
if (!first) {
return false;
}
const rest = tokens.slice(1);
const matches = (token: string): boolean => matchesScriptKeyword(token, LONG_SCRIPT_KEYWORDS);
if (first === 'pnpm') {
@ -371,6 +423,9 @@ function shouldUseLongTimeout(commandArgs: string[]): boolean {
return false;
}
const subcommand = rest[0];
if (!subcommand) {
return false;
}
if (subcommand === 'run') {
const script = rest[1];
if (script && matches(script)) {
@ -506,19 +561,22 @@ function buildExecutionParams(commandArgs: string[]): { command: string; args: s
for (const token of commandArgs) {
if (!commandStarted && isEnvAssignment(token)) {
const [key, ...rest] = token.split('=');
env[key] = rest.join('=');
if (key) {
env[key] = rest.join('=');
}
continue;
}
commandStarted = true;
args.push(token);
}
if (args.length === 0) {
if (args.length === 0 || !args[0]) {
printUsage('Missing command to execute.');
process.exit(1);
}
return { command: args[0], args: args.slice(1), env };
const [command, ...restArgs] = args;
return { command, args: restArgs, env };
}
// Forwards termination signals to the child and returns an unregister hook.
@ -818,7 +876,7 @@ async function buildFindDeletePlan(findArgs: string[], workspaceDir: string): Pr
process.exit(exitCode);
}
const matches = stdoutBuf.split('\0').filter((entry) => entry.length > 0);
const matches = stdoutBuf.split('\0').filter((entry: string) => entry.length > 0);
if (matches.length === 0) {
return { paths: [] };
}
@ -853,6 +911,9 @@ function parseRmArguments(argv: string[]): { targets: string[]; force: boolean;
let index = 1;
while (index < argv.length) {
const token = argv[index];
if (token === undefined) {
break;
}
if (!treatAsTarget && token === '--') {
treatAsTarget = true;
index += 1;
@ -894,6 +955,9 @@ function parseGitRmArguments(argv: string[], command: GitCommandInfo): GitRmPlan
let index = command.index + 1;
while (index < argv.length) {
const token = argv[index];
if (token === undefined) {
break;
}
if (!treatAsPath && token === '--') {
treatAsPath = true;
index += 1;
@ -1002,7 +1066,7 @@ async function movePathsToTrash(
stdout: 'ignore',
stderr: 'pipe',
});
const [exitCode, stderrText] = await Promise.all([proc.exited, proc.stderr?.text() ?? Promise.resolve('')]);
const [exitCode, stderrText] = await Promise.all([proc.exited, readProcessStream(proc.stderr)]);
if (exitCode === 0) {
return { missing, errors: [] };
}

View File

@ -11,7 +11,7 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"noEmit": true,
"types": ["node"]
"types": ["node", "bun-types"]
},
"include": ["src", "tests", "scripts", "docs"]
}