33// Uses installed tools when present, otherwise falls back to pinned hooks where
44// possible, then runs repo-specific workflow guards.
55import { spawnSync } from "node:child_process" ;
6- import { readdirSync } from "node:fs" ;
6+ import { mkdtempSync , readdirSync , rmSync } from "node:fs" ;
7+ import { tmpdir } from "node:os" ;
78import { join } from "node:path" ;
89
910const ACTIONLINT_VERSION = "1.7.11" ;
11+ const PRE_COMMIT_VERSION = "4.2.0" ;
1012const WORKFLOW_DIR = ".github/workflows" ;
1113
1214function commandExists ( command , args = [ "--version" ] ) {
@@ -25,6 +27,59 @@ function run(command, args) {
2527 }
2628}
2729
30+ function runChecked ( command , args ) {
31+ const result = spawnSync ( command , args , { stdio : "inherit" } ) ;
32+ if ( result . error ) {
33+ return {
34+ message : `[check-workflows] failed to run ${ command } : ${ result . error . message } ` ,
35+ status : 1 ,
36+ } ;
37+ }
38+ if ( result . status !== 0 ) {
39+ return {
40+ message : null ,
41+ status : result . status ?? 1 ,
42+ } ;
43+ }
44+ return null ;
45+ }
46+
47+ function runPreCommitFromTempVenv ( hook , hookArgs ) {
48+ if ( ! commandExists ( "python3" , [ "--version" ] ) ) {
49+ return false ;
50+ }
51+ const venvDir = mkdtempSync ( join ( tmpdir ( ) , "openclaw-check-workflows-pre-commit-" ) ) ;
52+ const python = join ( venvDir , process . platform === "win32" ? "Scripts/python.exe" : "bin/python" ) ;
53+ let failure ;
54+ try {
55+ failure = runChecked ( "python3" , [ "-m" , "venv" , venvDir ] ) ;
56+ if ( ! failure ) {
57+ failure = runChecked ( python , [
58+ "-m" ,
59+ "pip" ,
60+ "install" ,
61+ "--disable-pip-version-check" ,
62+ `pre-commit==${ PRE_COMMIT_VERSION } ` ,
63+ ] ) ;
64+ }
65+ if ( ! failure ) {
66+ failure = runChecked ( python , [ "-m" , "pre_commit" , ...hookArgs ] ) ;
67+ }
68+ if ( failure ) {
69+ return false ;
70+ }
71+ return true ;
72+ } finally {
73+ rmSync ( venvDir , { force : true , recursive : true } ) ;
74+ if ( failure ) {
75+ if ( failure . message ) {
76+ console . error ( failure . message ) ;
77+ }
78+ process . exit ( failure . status ) ;
79+ }
80+ }
81+ }
82+
2883function workflowFiles ( ) {
2984 return readdirSync ( WORKFLOW_DIR )
3085 . filter ( ( file ) => file . endsWith ( ".yml" ) || file . endsWith ( ".yaml" ) )
@@ -42,9 +97,12 @@ function runPreCommitHook(hook, files) {
4297 run ( "python3" , [ "-m" , "pre_commit" , ...hookArgs ] ) ;
4398 return ;
4499 }
100+ if ( runPreCommitFromTempVenv ( hook , hookArgs ) ) {
101+ return ;
102+ }
45103
46104 console . error (
47- `[check-workflows] missing pre-commit runtime for ${ hook } : install pre-commit or python3 pre_commit .` ,
105+ `[check-workflows] missing pre-commit runtime for ${ hook } : install pre-commit or Python venv support for pre-commit ${ PRE_COMMIT_VERSION } .` ,
48106 ) ;
49107 process . exit ( 1 ) ;
50108}
@@ -57,7 +115,8 @@ if (commandExists("actionlint")) {
57115 run ( "go" , [ "run" , `github.com/rhysd/actionlint/cmd/actionlint@v${ ACTIONLINT_VERSION } ` ] ) ;
58116} else if (
59117 commandExists ( "pre-commit" ) ||
60- commandExists ( "python3" , [ "-m" , "pre_commit" , "--version" ] )
118+ commandExists ( "python3" , [ "-m" , "pre_commit" , "--version" ] ) ||
119+ commandExists ( "python3" , [ "--version" ] )
61120) {
62121 runPreCommitHook ( "actionlint" , workflows ) ;
63122} else {
0 commit comments