qwen-code/scripts/lint.js
callmeYe 441006b0e1
feat(scripts): add local PR verification gate (#6873)
* feat(scripts): add settings schema check mode

* feat(scripts): add local PR verification runner

* fix(scripts): harden local PR verification

* docs: document local PR verification gate

* fix(scripts): isolate local verification tools

* fix(scripts): scope PR formatting checks

* fix(scripts): skip symlinked PR paths

* fix(scripts): preserve verification gate integrity

* fix(scripts): canonicalize verification temp paths

* fix(scripts): stabilize local PR verification

* fix(scripts): clear built-in test credentials

* fix(scripts): enforce isolated test environment

* fix(scripts): serialize local verification tests

* fix(scripts): address PR verification review

* fix(scripts): preserve review git wrapper environment

* refactor(scripts): avoid step helper shadowing

* fix(scripts): distinguish forwarded child signals

* fix(scripts): preserve relayed signal exit codes
2026-07-15 00:58:17 +00:00

258 lines
6.6 KiB
JavaScript

#!/usr/bin/env node
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { execSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { mkdirSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { delimiter, join } from 'node:path';
const ACTIONLINT_VERSION = '1.7.12';
const SHELLCHECK_VERSION = '0.11.0';
const YAMLLINT_VERSION = '1.35.1';
function sanitizePathPart(value) {
return value.replace(/[^A-Za-z0-9._-]/g, '_');
}
export function getLinterTempDir({
cwd = process.cwd(),
env = process.env,
} = {}) {
const baseDir = env.RUNNER_TEMP || tmpdir();
const runId = env.GITHUB_RUN_ID;
if (runId) {
return join(
baseDir,
'qwen-code-linters',
[
sanitizePathPart(runId),
sanitizePathPart(env.GITHUB_RUN_ATTEMPT || '1'),
sanitizePathPart(env.GITHUB_JOB || 'job'),
].join('-'),
);
}
const workspaceHash = createHash('sha256')
.update(cwd)
.digest('hex')
.slice(0, 16);
return join(baseDir, 'qwen-code-linters', `local-${workspaceHash}`);
}
const TEMP_DIR = getLinterTempDir();
function getPlatformArch() {
const platform = process.platform;
const arch = process.arch;
if (platform === 'linux' && arch === 'x64') {
return {
actionlint: 'linux_amd64',
shellcheck: 'linux.x86_64',
};
}
if (platform === 'darwin' && arch === 'x64') {
return {
actionlint: 'darwin_amd64',
shellcheck: 'darwin.x86_64',
};
}
if (platform === 'darwin' && arch === 'arm64') {
return {
actionlint: 'darwin_arm64',
shellcheck: 'darwin.aarch64',
};
}
throw new Error(`Unsupported platform/architecture: ${platform}/${arch}`);
}
const platformArch = getPlatformArch();
/**
* @typedef {{
* check: string;
* installer: string;
* run: string;
* }}
*/
/**
* @type {{[linterName: string]: Linter}}
*/
const LINTERS = {
actionlint: {
check: `test "$(actionlint -version 2>/dev/null)" = "${ACTIONLINT_VERSION}"`,
installer: `
mkdir -p "${TEMP_DIR}/actionlint"
curl -sSLo "${TEMP_DIR}/.actionlint.tgz" "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_${platformArch.actionlint}.tar.gz"
tar -xzf "${TEMP_DIR}/.actionlint.tgz" -C "${TEMP_DIR}/actionlint"
`,
run: `
actionlint \
-color \
-pyflakes= \
-shellcheck= \
-ignore 'SC2002:' \
-ignore 'SC2016:' \
-ignore 'SC2129:' \
-ignore 'unexpected key "deployment" for "environment" section' \
-ignore 'label ".+" is unknown'
`,
},
shellcheck: {
check: `test "$(shellcheck --version 2>/dev/null | awk '/^version:/ { print $2 }')" = "${SHELLCHECK_VERSION}"`,
installer: `
mkdir -p "${TEMP_DIR}/shellcheck"
curl -sSLo "${TEMP_DIR}/.shellcheck.txz" "https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.${platformArch.shellcheck}.tar.xz"
tar -xf "${TEMP_DIR}/.shellcheck.txz" -C "${TEMP_DIR}/shellcheck" --strip-components=1
`,
run: `
git ls-files | grep -v '^integration-tests/terminal-bench/' | grep -E '^([^.]+|.*\\.(sh|zsh|bash))' | xargs file --mime-type \
| grep "text/x-shellscript" | awk '{ print substr($1, 1, length($1)-1) }' \
| xargs shellcheck \
--check-sourced \
--enable=all \
--exclude=SC2002,SC2129,SC2310 \
--severity=style \
--format=gcc \
--color=never | sed -e 's/note:/warning:/g' -e 's/style:/warning:/g'
`,
},
yamllint: {
check: `test "$(yamllint --version 2>/dev/null)" = "yamllint ${YAMLLINT_VERSION}"`,
installer: `python3 -m pip install --target "${TEMP_DIR}/yamllint" "yamllint==${YAMLLINT_VERSION}"`,
run: "git ls-files | grep -E '\\.(yaml|yml)' | xargs yamllint --format github",
},
};
export function createLinterEnvironment({
cwd = process.cwd(),
env = process.env,
tempDir = TEMP_DIR,
} = {}) {
const yamllintTarget = join(tempDir, 'yamllint');
return {
...env,
PIP_CONFIG_FILE: '/dev/null',
PIP_REQUIRE_VIRTUALENV: 'false',
PIP_USER: 'false',
PATH: [
join(cwd, 'node_modules', '.bin'),
join(tempDir, 'actionlint'),
join(tempDir, 'shellcheck'),
join(yamllintTarget, 'bin'),
env.PATH,
]
.filter(Boolean)
.join(delimiter),
PYTHONPATH: [yamllintTarget, env.PYTHONPATH]
.filter(Boolean)
.join(delimiter),
PYTHONNOUSERSITE: '1',
};
}
function runCommand(command, stdio = 'inherit') {
try {
execSync(command, { stdio, env: createLinterEnvironment() });
return true;
} catch (_e) {
return false;
}
}
export function setupLinters() {
console.log('Setting up linters...');
rmSync(TEMP_DIR, { recursive: true, force: true });
mkdirSync(TEMP_DIR, { recursive: true });
for (const linter in LINTERS) {
const { check, installer } = LINTERS[linter];
if (!runCommand(check, 'ignore')) {
console.log(`Installing ${linter}...`);
if (!runCommand(installer)) {
console.error(
`Failed to install ${linter}. Please install it manually.`,
);
process.exit(1);
}
}
}
console.log('All required linters are available.');
}
export function runESLint() {
console.log('\nRunning ESLint...');
if (!runCommand('npm run lint:ci')) {
process.exit(1);
}
}
export function runActionlint() {
console.log('\nRunning actionlint...');
if (!runCommand(LINTERS.actionlint.run)) {
process.exit(1);
}
}
export function runShellcheck() {
console.log('\nRunning shellcheck...');
if (!runCommand(LINTERS.shellcheck.run)) {
process.exit(1);
}
}
export function runYamllint() {
console.log('\nRunning yamllint...');
if (!runCommand(LINTERS.yamllint.run)) {
process.exit(1);
}
}
export function runPrettier() {
console.log('\nRunning Prettier...');
if (!runCommand('prettier --write .')) {
process.exit(1);
}
}
function main() {
const args = process.argv.slice(2);
if (args.includes('--setup')) {
setupLinters();
}
if (args.includes('--eslint')) {
runESLint();
}
if (args.includes('--actionlint')) {
runActionlint();
}
if (args.includes('--shellcheck')) {
runShellcheck();
}
if (args.includes('--yamllint')) {
runYamllint();
}
if (args.includes('--prettier')) {
runPrettier();
}
if (args.length === 0) {
setupLinters();
runESLint();
runActionlint();
runShellcheck();
runYamllint();
runPrettier();
console.log('\nAll linting checks passed!');
}
}
main();