chore(scripts): improve linter portability and fix validation issues

This commit is contained in:
mkorwel
2026-03-13 17:23:21 +00:00
parent 86080cc72b
commit 6d6fc9dcf3
5 changed files with 509 additions and 380 deletions
+91 -22
View File
@@ -7,12 +7,12 @@
*/
import { execSync } from 'node:child_process';
const isWindows = process.platform === 'win32';
import { existsSync, lstatSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
const LINTERS = {
actionlint: {
check: isWindows ? 'where npx node-actionlint 2>nul' : 'command -v npx node-actionlint',
check: 'npm list @tktco/node-actionlint --depth=0',
installer: 'npm install --save-dev @tktco/node-actionlint',
run: `
npx node-actionlint \
@@ -24,29 +24,56 @@ const LINTERS = {
`,
},
shellcheck: {
check: isWindows ? 'where npx shellcheck 2>nul' : 'command -v npx shellcheck',
check: 'npm list shellcheck --depth=0',
installer: 'npm install --save-dev shellcheck',
run: `
git ls-files | grep -E '^([^.]+|.*\\.(sh|zsh|bash))' | xargs file --mime-type \
| grep "text/x-shellscript" | awk '{ print substr($1, 1, length($1)-1) }' \
| xargs npx 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: isWindows ? 'where npx yaml-lint 2>nul' : 'command -v npx yaml-lint',
check: 'npm list yaml-lint --depth=0',
installer: 'npm install --save-dev yaml-lint',
run: isWindows
? `powershell -Command "Get-ChildItem -Recurse -Include *.yaml,*.yml | ForEach-Object { npx yaml-lint $_.FullName }"`
: "git ls-files | grep -E '\\.(yaml|yml)' | xargs npx yaml-lint",
},
};
function getShellScripts() {
const allFiles = execSync('git ls-files', { encoding: 'utf-8' })
.split('\n')
.filter(Boolean);
return allFiles.filter((file) => {
if (
file.endsWith('.sh') ||
file.endsWith('.zsh') ||
file.endsWith('.bash')
) {
return true;
}
if (
!file.includes('.') &&
existsSync(file) &&
!lstatSync(file).isDirectory()
) {
try {
const content = readFileSync(file, 'utf-8');
const firstLine = content.split('\n')[0];
return (
firstLine.startsWith('#!') &&
(firstLine.includes('sh') ||
firstLine.includes('bash') ||
firstLine.includes('zsh'))
);
} catch (_e) {
return false;
}
}
return false;
});
}
function getYamlFiles() {
return execSync('git ls-files', { encoding: 'utf-8' })
.split('\n')
.filter((file) => file.endsWith('.yaml') || file.endsWith('.yml'));
}
function runCommand(command, stdio = 'inherit') {
try {
execSync(command, { stdio, env: process.env, shell: true });
@@ -58,7 +85,7 @@ function runCommand(command, stdio = 'inherit') {
export function setupLinters() {
console.log('Setting up linters...');
for (const linter in LINTERS) {
const { check, installer } = LINTERS[linter];
if (!runCommand(check, 'ignore')) {
@@ -90,14 +117,56 @@ export function runActionlint() {
export function runShellcheck() {
console.log('\nRunning shellcheck...');
if (!runCommand(LINTERS.shellcheck.run)) {
const files = getShellScripts();
if (files.length === 0) {
console.log('No shell scripts found.');
return;
}
const command = `npx shellcheck \
--check-sourced \
--enable=all \
--exclude=SC2002,SC2129,SC2310 \
--severity=style \
--format=gcc \
--color=never ${files.join(' ')}`;
try {
const output = execSync(command, {
env: process.env,
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
});
console.log(
output.replace(/note:/g, 'warning:').replace(/style:/g, 'warning:'),
);
} catch (e) {
if (e.stdout) {
console.log(
e.stdout
.toString()
.replace(/note:/g, 'warning:')
.replace(/style:/g, 'warning:'),
);
}
if (e.stderr) {
console.error(e.stderr.toString());
}
process.exit(1);
}
}
export function runYamllint() {
console.log('\nRunning yamllint...');
if (!runCommand(LINTERS.yamllint.run)) {
const files = getYamlFiles();
if (files.length === 0) {
console.log('No YAML files found.');
return;
}
// Use xargs-like behavior but via Node to avoid shell limits and ensure portability
// Passing all files at once is generally supported by yaml-lint
if (!runCommand(`npx yaml-lint ${files.join(' ')}`)) {
process.exit(1);
}
}
+46 -44
View File
@@ -5,17 +5,17 @@
set -e
if [ -z "$1" ]; then
echo "Usage: $0 <pr#> [model]"
if [[ -z "${1}" ]]; then
echo "Usage: ${0} <pr#> [model]"
exit 1
fi
pr="$1"
pr="${1}"
model="${2:-gemini-3.1-pro-preview}"
REPO="google-gemini/gemini-cli"
REVIEW_DIR="$HOME/git/review/gemini-cli"
REVIEW_DIR="${HOME}/git/review/gemini-cli"
if [ ! -d "$REVIEW_DIR" ]; then
echo "ERROR: Directory $REVIEW_DIR does not exist."
if [[ ! -d "${REVIEW_DIR}" ]]; then
echo "ERROR: Directory ${REVIEW_DIR} does not exist."
echo ""
echo "Please create a new gemini-cli clone at that directory to use for reviews."
echo "Instructions:"
@@ -26,54 +26,56 @@ if [ ! -d "$REVIEW_DIR" ]; then
fi
# 1. Check if the PR exists before doing anything else
echo "review: Validating PR $pr on $REPO..."
if ! gh pr view "$pr" -R "$REPO" > /dev/null 2>&1; then
echo "ERROR: Could not find PR #$pr in $REPO."
echo "Are you sure $pr is a Pull Request number and not an Issue number?"
echo "review: Validating PR ${pr} on ${REPO}..."
if ! gh pr view "${pr}" -R "${REPO}" > /dev/null 2>&1; then
echo "ERROR: Could not find PR #${pr} in ${REPO}."
echo "Are you sure ${pr} is a Pull Request number and not an Issue number?"
exit 1
fi
echo "review: Opening PR $pr in browser..."
if [[ "$(uname)" == "Darwin" ]]; then
open "https://github.com/$REPO/pull/$pr" || true
echo "review: Opening PR ${pr} in browser..."
OS_TYPE=$(uname)
if [[ "${OS_TYPE}" == "Darwin" ]]; then
open "https://github.com/${REPO}/pull/${pr}" || true
else
xdg-open "https://github.com/$REPO/pull/$pr" || true
xdg-open "https://github.com/${REPO}/pull/${pr}" || true
fi
echo "review: Changing directory to $REVIEW_DIR"
cd "$REVIEW_DIR" || exit 1
echo "review: Changing directory to ${REVIEW_DIR}"
cd "${REVIEW_DIR}" || exit 1
# 2. Fetch latest main to ensure we have a clean starting point
echo "review: Fetching latest from origin..."
git fetch origin main
# 3. Handle worktree creation
WORKTREE_PATH="pr_$pr"
if [ -d "$WORKTREE_PATH" ]; then
echo "review: Worktree directory $WORKTREE_PATH already exists."
WORKTREE_PATH="pr_${pr}"
if [[ -d "${WORKTREE_PATH}" ]]; then
echo "review: Worktree directory ${WORKTREE_PATH} already exists."
# Check if it's actually a registered worktree
if git worktree list | grep -q "$WORKTREE_PATH"; then
WORKTREE_LIST=$(git worktree list)
if echo "${WORKTREE_LIST}" | grep -q "${WORKTREE_PATH}"; then
echo "review: Reusing existing worktree..."
else
echo "review: Directory exists but is not a worktree. Cleaning up..."
rm -rf "$WORKTREE_PATH"
rm -rf "${WORKTREE_PATH}"
fi
fi
if [ ! -d "$WORKTREE_PATH" ]; then
echo "review: Adding new worktree at $WORKTREE_PATH..."
if [[ ! -d "${WORKTREE_PATH}" ]]; then
echo "review: Adding new worktree at ${WORKTREE_PATH}..."
# Create a detached worktree from origin/main
git worktree add --detach "$WORKTREE_PATH" origin/main
git worktree add --detach "${WORKTREE_PATH}" origin/main
fi
echo "review: Changing directory to $WORKTREE_PATH"
cd "$WORKTREE_PATH" || exit 1
echo "review: Changing directory to ${WORKTREE_PATH}"
cd "${WORKTREE_PATH}" || exit 1
# 4. Checkout the PR
echo "review: Cleaning worktree and checking out PR $pr..."
echo "review: Cleaning worktree and checking out PR ${pr}..."
git reset --hard
git clean -fd
gh pr checkout "$pr" --branch "review-$pr" -f -R "$REPO"
gh pr checkout "${pr}" --branch "review-${pr}" -f -R "${REPO}"
# 5. Clean and Build
echo "review: Clearing possibly stale node_modules..."
@@ -87,48 +89,48 @@ npm install
echo "--- build ---"
temp_dir_base="${TMPDIR:-/tmp}"
build_log_file=$(mktemp "${temp_dir_base}/npm_build_log.XXXXXX") || {
if ! build_log_file=$(mktemp "${temp_dir_base}/npm_build_log.XXXXXX"); then
echo "Attempting to create temporary file in current directory as a fallback." >&2
build_log_file=$(mktemp "./npm_build_log_fallback.XXXXXX")
if [ $? -ne 0 ] || [ -z "$build_log_file" ]; then
if ! build_log_file=$(mktemp "./npm_build_log_fallback.XXXXXX"); then
echo "ERROR: Critical - Failed to create any temporary build log file. Aborting." >&2
exit 1
fi
}
fi
build_status=0
build_command_to_run="FORCE_COLOR=1 CLICOLOR_FORCE=1 npm run build"
echo "Running build. Output (with colors) will be shown below and saved to: $build_log_file"
echo "Build command: $build_command_to_run"
echo "Running build. Output (with colors) will be shown below and saved to: ${build_log_file}"
echo "Build command: ${build_command_to_run}"
if [[ "$(uname)" == "Darwin" ]]; then
script -q "$build_log_file" /bin/sh -c "$build_command_to_run"
OS_TYPE=$(uname)
if [[ "${OS_TYPE}" == "Darwin" ]]; then
script -q "${build_log_file}" /bin/sh -c "${build_command_to_run}"
build_status=$?
else
if script -q -e -c "$build_command_to_run" "$build_log_file"; then
if script -q -e -c "${build_command_to_run}" "${build_log_file}"; then
build_status=0
else
build_status=$?
fi
fi
if [ $build_status -ne 0 ]; then
echo "ERROR: npm build failed with exit status $build_status." >&2
echo "Review output above. Full log (with color codes) was in $build_log_file." >&2
if [[ "${build_status}" -ne 0 ]]; then
echo "ERROR: npm build failed with exit status ${build_status}." >&2
echo "Review output above. Full log (with color codes) was in ${build_log_file}." >&2
exit 1
else
if grep -q -i -E "\berror\b|\bfailed\b|ERR!|FATAL|critical" "$build_log_file"; then
if grep -q -i -E "\berror\b|\bfailed\b|ERR!|FATAL|critical" "${build_log_file}"; then
echo "ERROR: npm build completed with exit status 0, but suspicious error patterns were found in the build output." >&2
echo "Review output above. Full log (with color codes) was in $build_log_file." >&2
echo "Review output above. Full log (with color codes) was in ${build_log_file}." >&2
exit 1
fi
echo "npm build completed successfully (exit status 0, no critical error patterns found in log)."
rm -f "$build_log_file"
rm -f "${build_log_file}"
fi
echo "-- running ---"
if ! npm start -- -m "$model" -i="/review-frontend $pr"; then
if ! npm start -- -m "${model}" -i="/review-frontend ${pr}"; then
echo "ERROR: npm start failed. Please check its output for details." >&2
exit 1
fi