11cb0ef41Sopenharmony_ci#!/usr/bin/env node 21cb0ef41Sopenharmony_ci 31cb0ef41Sopenharmony_ciimport { execSync, spawn } from 'node:child_process'; 41cb0ef41Sopenharmony_ciimport { promises as fs, readdirSync, statSync } from 'node:fs'; 51cb0ef41Sopenharmony_ciimport { extname, join, relative, resolve } from 'node:path'; 61cb0ef41Sopenharmony_ciimport process from 'node:process'; 71cb0ef41Sopenharmony_ci 81cb0ef41Sopenharmony_ciconst FIX_MODE_ENABLED = process.argv.includes('--fix'); 91cb0ef41Sopenharmony_ciconst USE_NPX = process.argv.includes('--from-npx'); 101cb0ef41Sopenharmony_ci 111cb0ef41Sopenharmony_ciconst SHELLCHECK_EXE_NAME = 'shellcheck'; 121cb0ef41Sopenharmony_ciconst SHELLCHECK_OPTIONS = ['--shell=sh', '--severity=info', '--enable=all']; 131cb0ef41Sopenharmony_ciif (FIX_MODE_ENABLED) SHELLCHECK_OPTIONS.push('--format=diff'); 141cb0ef41Sopenharmony_cielse if (process.env.GITHUB_ACTIONS) SHELLCHECK_OPTIONS.push('--format=json'); 151cb0ef41Sopenharmony_ci 161cb0ef41Sopenharmony_ciconst SPAWN_OPTIONS = { 171cb0ef41Sopenharmony_ci cwd: null, 181cb0ef41Sopenharmony_ci shell: false, 191cb0ef41Sopenharmony_ci stdio: ['pipe', 'pipe', 'inherit'], 201cb0ef41Sopenharmony_ci}; 211cb0ef41Sopenharmony_ci 221cb0ef41Sopenharmony_cifunction* findScriptFilesRecursively(dirPath) { 231cb0ef41Sopenharmony_ci const entries = readdirSync(dirPath, { withFileTypes: true }); 241cb0ef41Sopenharmony_ci 251cb0ef41Sopenharmony_ci for (const entry of entries) { 261cb0ef41Sopenharmony_ci const path = join(dirPath, entry.name); 271cb0ef41Sopenharmony_ci 281cb0ef41Sopenharmony_ci if ( 291cb0ef41Sopenharmony_ci entry.isDirectory() && 301cb0ef41Sopenharmony_ci entry.name !== 'build' && 311cb0ef41Sopenharmony_ci entry.name !== 'changelogs' && 321cb0ef41Sopenharmony_ci entry.name !== 'deps' && 331cb0ef41Sopenharmony_ci entry.name !== 'fixtures' && 341cb0ef41Sopenharmony_ci entry.name !== 'gyp' && 351cb0ef41Sopenharmony_ci entry.name !== 'inspector_protocol' && 361cb0ef41Sopenharmony_ci entry.name !== 'node_modules' && 371cb0ef41Sopenharmony_ci entry.name !== 'out' && 381cb0ef41Sopenharmony_ci entry.name !== 'tmp' 391cb0ef41Sopenharmony_ci ) { 401cb0ef41Sopenharmony_ci yield* findScriptFilesRecursively(path); 411cb0ef41Sopenharmony_ci } else if (entry.isFile() && extname(entry.name) === '.sh') { 421cb0ef41Sopenharmony_ci yield path; 431cb0ef41Sopenharmony_ci } 441cb0ef41Sopenharmony_ci } 451cb0ef41Sopenharmony_ci} 461cb0ef41Sopenharmony_ci 471cb0ef41Sopenharmony_ciconst expectedHashBang = Buffer.from('#!/bin/sh\n'); 481cb0ef41Sopenharmony_ciasync function hasInvalidHashBang(fd) { 491cb0ef41Sopenharmony_ci const { length } = expectedHashBang; 501cb0ef41Sopenharmony_ci 511cb0ef41Sopenharmony_ci const actual = Buffer.allocUnsafe(length); 521cb0ef41Sopenharmony_ci await fd.read(actual, 0, length, 0); 531cb0ef41Sopenharmony_ci 541cb0ef41Sopenharmony_ci return Buffer.compare(actual, expectedHashBang); 551cb0ef41Sopenharmony_ci} 561cb0ef41Sopenharmony_ci 571cb0ef41Sopenharmony_ciasync function checkFiles(...files) { 581cb0ef41Sopenharmony_ci const flags = FIX_MODE_ENABLED ? 'r+' : 'r'; 591cb0ef41Sopenharmony_ci await Promise.all( 601cb0ef41Sopenharmony_ci files.map(async (file) => { 611cb0ef41Sopenharmony_ci const fd = await fs.open(file, flags); 621cb0ef41Sopenharmony_ci if (await hasInvalidHashBang(fd)) { 631cb0ef41Sopenharmony_ci if (FIX_MODE_ENABLED) { 641cb0ef41Sopenharmony_ci const file = await fd.readFile(); 651cb0ef41Sopenharmony_ci 661cb0ef41Sopenharmony_ci const fileContent = 671cb0ef41Sopenharmony_ci file[0] === '#'.charCodeAt() ? 681cb0ef41Sopenharmony_ci file.subarray(file.indexOf('\n') + 1) : 691cb0ef41Sopenharmony_ci file; 701cb0ef41Sopenharmony_ci 711cb0ef41Sopenharmony_ci const toWrite = Buffer.concat([expectedHashBang, fileContent]); 721cb0ef41Sopenharmony_ci await fd.truncate(toWrite.length); 731cb0ef41Sopenharmony_ci await fd.write(toWrite, 0, toWrite.length, 0); 741cb0ef41Sopenharmony_ci } else { 751cb0ef41Sopenharmony_ci if (!process.exitCode) process.exitCode = 1; 761cb0ef41Sopenharmony_ci console.error( 771cb0ef41Sopenharmony_ci (process.env.GITHUB_ACTIONS ? 781cb0ef41Sopenharmony_ci `::error file=${file},line=1,col=1::` : 791cb0ef41Sopenharmony_ci 'Fixable with --fix: ') + 801cb0ef41Sopenharmony_ci `Invalid hashbang for ${file} (expected /bin/sh).`, 811cb0ef41Sopenharmony_ci ); 821cb0ef41Sopenharmony_ci } 831cb0ef41Sopenharmony_ci } 841cb0ef41Sopenharmony_ci await fd.close(); 851cb0ef41Sopenharmony_ci }), 861cb0ef41Sopenharmony_ci ); 871cb0ef41Sopenharmony_ci 881cb0ef41Sopenharmony_ci const stdout = await new Promise((resolve, reject) => { 891cb0ef41Sopenharmony_ci const SHELLCHECK_EXE = 901cb0ef41Sopenharmony_ci process.env.SHELLCHECK || 911cb0ef41Sopenharmony_ci execSync('command -v ' + (USE_NPX ? 'npx' : SHELLCHECK_EXE_NAME)) 921cb0ef41Sopenharmony_ci .toString() 931cb0ef41Sopenharmony_ci .trim(); 941cb0ef41Sopenharmony_ci const NPX_OPTIONS = USE_NPX ? [SHELLCHECK_EXE_NAME] : []; 951cb0ef41Sopenharmony_ci 961cb0ef41Sopenharmony_ci const shellcheck = spawn( 971cb0ef41Sopenharmony_ci SHELLCHECK_EXE, 981cb0ef41Sopenharmony_ci [ 991cb0ef41Sopenharmony_ci ...NPX_OPTIONS, 1001cb0ef41Sopenharmony_ci ...SHELLCHECK_OPTIONS, 1011cb0ef41Sopenharmony_ci ...(FIX_MODE_ENABLED ? 1021cb0ef41Sopenharmony_ci files.map((filePath) => relative(SPAWN_OPTIONS.cwd, filePath)) : 1031cb0ef41Sopenharmony_ci files), 1041cb0ef41Sopenharmony_ci ], 1051cb0ef41Sopenharmony_ci SPAWN_OPTIONS, 1061cb0ef41Sopenharmony_ci ); 1071cb0ef41Sopenharmony_ci shellcheck.once('error', reject); 1081cb0ef41Sopenharmony_ci 1091cb0ef41Sopenharmony_ci let json = ''; 1101cb0ef41Sopenharmony_ci let childProcess = shellcheck; 1111cb0ef41Sopenharmony_ci if (FIX_MODE_ENABLED) { 1121cb0ef41Sopenharmony_ci const GIT_EXE = 1131cb0ef41Sopenharmony_ci process.env.GIT || execSync('command -v git').toString().trim(); 1141cb0ef41Sopenharmony_ci 1151cb0ef41Sopenharmony_ci const gitApply = spawn(GIT_EXE, ['apply'], SPAWN_OPTIONS); 1161cb0ef41Sopenharmony_ci shellcheck.stdout.pipe(gitApply.stdin); 1171cb0ef41Sopenharmony_ci shellcheck.once('exit', (code) => { 1181cb0ef41Sopenharmony_ci if (!process.exitCode && code) process.exitCode = code; 1191cb0ef41Sopenharmony_ci }); 1201cb0ef41Sopenharmony_ci gitApply.stdout.pipe(process.stdout); 1211cb0ef41Sopenharmony_ci 1221cb0ef41Sopenharmony_ci gitApply.once('error', reject); 1231cb0ef41Sopenharmony_ci childProcess = gitApply; 1241cb0ef41Sopenharmony_ci } else if (process.env.GITHUB_ACTIONS) { 1251cb0ef41Sopenharmony_ci shellcheck.stdout.on('data', (chunk) => { 1261cb0ef41Sopenharmony_ci json += chunk; 1271cb0ef41Sopenharmony_ci }); 1281cb0ef41Sopenharmony_ci } else { 1291cb0ef41Sopenharmony_ci shellcheck.stdout.pipe(process.stdout); 1301cb0ef41Sopenharmony_ci } 1311cb0ef41Sopenharmony_ci childProcess.once('exit', (code) => { 1321cb0ef41Sopenharmony_ci if (!process.exitCode && code) process.exitCode = code; 1331cb0ef41Sopenharmony_ci resolve(json); 1341cb0ef41Sopenharmony_ci }); 1351cb0ef41Sopenharmony_ci }); 1361cb0ef41Sopenharmony_ci 1371cb0ef41Sopenharmony_ci if (!FIX_MODE_ENABLED && process.env.GITHUB_ACTIONS) { 1381cb0ef41Sopenharmony_ci const data = JSON.parse(stdout); 1391cb0ef41Sopenharmony_ci for (const { file, line, column, message } of data) { 1401cb0ef41Sopenharmony_ci console.error( 1411cb0ef41Sopenharmony_ci `::error file=${file},line=${line},col=${column}::${file}:${line}:${column}: ${message}`, 1421cb0ef41Sopenharmony_ci ); 1431cb0ef41Sopenharmony_ci } 1441cb0ef41Sopenharmony_ci } 1451cb0ef41Sopenharmony_ci} 1461cb0ef41Sopenharmony_ci 1471cb0ef41Sopenharmony_ciconst USAGE_STR = 1481cb0ef41Sopenharmony_ci `Usage: ${process.argv[1]} <path> [--fix] [--from-npx]\n` + 1491cb0ef41Sopenharmony_ci '\n' + 1501cb0ef41Sopenharmony_ci 'Environment variables:\n' + 1511cb0ef41Sopenharmony_ci ' - SHELLCHECK: absolute path to `shellcheck`. If not provided, the\n' + 1521cb0ef41Sopenharmony_ci ' script will use the result of `command -v shellcheck`, or\n' + 1531cb0ef41Sopenharmony_ci ' `$(command -v npx) shellcheck` if the flag `--from-npx` is provided\n' + 1541cb0ef41Sopenharmony_ci ' (may require an internet connection).\n' + 1551cb0ef41Sopenharmony_ci ' - GIT: absolute path to `git`. If not provided, the \n' + 1561cb0ef41Sopenharmony_ci ' script will use the result of `command -v git`.\n'; 1571cb0ef41Sopenharmony_ci 1581cb0ef41Sopenharmony_ciif ( 1591cb0ef41Sopenharmony_ci process.argv.length < 3 || 1601cb0ef41Sopenharmony_ci process.argv.includes('-h') || 1611cb0ef41Sopenharmony_ci process.argv.includes('--help') 1621cb0ef41Sopenharmony_ci) { 1631cb0ef41Sopenharmony_ci console.log(USAGE_STR); 1641cb0ef41Sopenharmony_ci} else { 1651cb0ef41Sopenharmony_ci console.log('Running Shell scripts checker...'); 1661cb0ef41Sopenharmony_ci const entryPoint = resolve(process.argv[2]); 1671cb0ef41Sopenharmony_ci const stats = statSync(entryPoint, { throwIfNoEntry: false }); 1681cb0ef41Sopenharmony_ci 1691cb0ef41Sopenharmony_ci const onError = (e) => { 1701cb0ef41Sopenharmony_ci console.log(USAGE_STR); 1711cb0ef41Sopenharmony_ci console.error(e); 1721cb0ef41Sopenharmony_ci process.exitCode = 1; 1731cb0ef41Sopenharmony_ci }; 1741cb0ef41Sopenharmony_ci if (stats?.isDirectory()) { 1751cb0ef41Sopenharmony_ci SPAWN_OPTIONS.cwd = entryPoint; 1761cb0ef41Sopenharmony_ci checkFiles(...findScriptFilesRecursively(entryPoint)).catch(onError); 1771cb0ef41Sopenharmony_ci } else if (stats?.isFile()) { 1781cb0ef41Sopenharmony_ci SPAWN_OPTIONS.cwd = process.cwd(); 1791cb0ef41Sopenharmony_ci checkFiles(entryPoint).catch(onError); 1801cb0ef41Sopenharmony_ci } else { 1811cb0ef41Sopenharmony_ci onError(new Error('You must provide a valid directory or file path. ' + 1821cb0ef41Sopenharmony_ci `Received '${process.argv[2]}'.`)); 1831cb0ef41Sopenharmony_ci } 1841cb0ef41Sopenharmony_ci} 185