@@ -7,6 +7,74 @@ import * as console from "node:console"
77
88import { copyPaths , copyWasms , copyLocales , setupLocaleWatcher } from "@roo-code/build"
99
10+ // Lock file to prevent concurrent execution
11+ const LOCK_FILE = path . join ( process . cwd ( ) , '.esbuild.lock' )
12+ const MAX_WAIT_TIME = 30000 // 30 seconds
13+ const POLL_INTERVAL = 100 // 100ms
14+
15+ async function acquireLock ( ) {
16+ const startTime = Date . now ( )
17+
18+ while ( Date . now ( ) - startTime < MAX_WAIT_TIME ) {
19+ try {
20+ // Try to create lock file exclusively
21+ fs . writeFileSync ( LOCK_FILE , process . pid . toString ( ) , { flag : 'wx' } )
22+ return true
23+ } catch ( error ) {
24+ if ( error . code === 'EEXIST' ) {
25+ // Lock file exists, check if the process is still running
26+ try {
27+ const lockPid = fs . readFileSync ( LOCK_FILE , 'utf8' ) . trim ( )
28+ const pid = parseInt ( lockPid , 10 )
29+
30+ // Check if process is still running
31+ try {
32+ process . kill ( pid , 0 ) // Signal 0 checks if process exists
33+ // Process is still running, wait
34+ await new Promise ( resolve => setTimeout ( resolve , POLL_INTERVAL ) )
35+ continue
36+ } catch ( killError ) {
37+ // Process is not running, remove stale lock
38+ fs . unlinkSync ( LOCK_FILE )
39+ continue
40+ }
41+ } catch ( readError ) {
42+ // Can't read lock file, try to remove it
43+ try {
44+ fs . unlinkSync ( LOCK_FILE )
45+ } catch ( unlinkError ) {
46+ // Ignore unlink errors
47+ }
48+ continue
49+ }
50+ } else {
51+ throw error
52+ }
53+ }
54+ }
55+
56+ throw new Error ( `Failed to acquire lock after ${ MAX_WAIT_TIME } ms` )
57+ }
58+
59+ function releaseLock ( ) {
60+ try {
61+ fs . unlinkSync ( LOCK_FILE )
62+ } catch ( error ) {
63+ // Ignore errors when releasing lock
64+ }
65+ }
66+
67+ // Ensure lock is released on process exit
68+ process . on ( 'exit' , releaseLock )
69+ process . on ( 'SIGINT' , ( ) => {
70+ releaseLock ( )
71+ process . exit ( 0 )
72+ } )
73+ process . on ( 'SIGTERM' , ( ) => {
74+ releaseLock ( )
75+ process . exit ( 0 )
76+ } )
77+
1078const __filename = fileURLToPath ( import . meta. url )
1179const __dirname = path . dirname ( __filename )
1280
@@ -17,117 +85,129 @@ async function main() {
1785 const minify = production
1886 const sourcemap = ! production
1987
20- /**
21- * @type {import('esbuild').BuildOptions }
22- */
23- const buildOptions = {
24- bundle : true ,
25- minify,
26- sourcemap,
27- logLevel : "silent" ,
28- format : "cjs" ,
29- sourcesContent : false ,
30- platform : "node" ,
31- }
88+ // Acquire lock to prevent concurrent execution
89+ console . log ( `[${ name } ] Acquiring build lock...` )
90+ await acquireLock ( )
91+ console . log ( `[${ name } ] Build lock acquired` )
3292
33- const srcDir = __dirname
34- const buildDir = __dirname
35- const distDir = path . join ( buildDir , "dist" )
93+ try {
94+ /**
95+ * @type {import('esbuild').BuildOptions }
96+ */
97+ const buildOptions = {
98+ bundle : true ,
99+ minify,
100+ sourcemap,
101+ logLevel : "silent" ,
102+ format : "cjs" ,
103+ sourcesContent : false ,
104+ platform : "node" ,
105+ }
36106
37- if ( fs . existsSync ( distDir ) ) {
38- console . log ( `[${ name } ] Cleaning dist directory: ${ distDir } ` )
39- fs . rmSync ( distDir , { recursive : true , force : true } )
40- }
107+ const srcDir = __dirname
108+ const buildDir = __dirname
109+ const distDir = path . join ( buildDir , "dist" )
110+
111+ if ( fs . existsSync ( distDir ) ) {
112+ console . log ( `[${ name } ] Cleaning dist directory: ${ distDir } ` )
113+ fs . rmSync ( distDir , { recursive : true , force : true } )
114+ }
41115
42- /**
43- * @type {import('esbuild').Plugin[] }
44- */
45- const plugins = [
46- {
47- name : "copyFiles" ,
48- setup ( build ) {
49- build . onEnd ( ( ) => {
50- copyPaths (
51- [
52- [ "../README.md" , "README.md" ] ,
53- [ "../CHANGELOG.md" , "CHANGELOG.md" ] ,
54- [ "../LICENSE" , "LICENSE" ] ,
55- [ "../.env" , ".env" , { optional : true } ] ,
56- [ "node_modules/vscode-material-icons/generated" , "assets/vscode-material-icons" ] ,
57- [ "../webview-ui/audio" , "webview-ui/audio" ] ,
58- ] ,
59- srcDir ,
60- buildDir ,
61- )
62- } )
116+ /**
117+ * @type {import('esbuild').Plugin[] }
118+ */
119+ const plugins = [
120+ {
121+ name : "copyFiles" ,
122+ setup ( build ) {
123+ build . onEnd ( ( ) => {
124+ copyPaths (
125+ [
126+ [ "../README.md" , "README.md" ] ,
127+ [ "../CHANGELOG.md" , "CHANGELOG.md" ] ,
128+ [ "../LICENSE" , "LICENSE" ] ,
129+ [ "../.env" , ".env" , { optional : true } ] ,
130+ [ "node_modules/vscode-material-icons/generated" , "assets/vscode-material-icons" ] ,
131+ [ "../webview-ui/audio" , "webview-ui/audio" ] ,
132+ ] ,
133+ srcDir ,
134+ buildDir ,
135+ )
136+ } )
137+ } ,
63138 } ,
64- } ,
65- {
66- name : "copyWasms" ,
67- setup ( build ) {
68- build . onEnd ( ( ) => copyWasms ( srcDir , distDir ) )
139+ {
140+ name : "copyWasms" ,
141+ setup ( build ) {
142+ build . onEnd ( ( ) => copyWasms ( srcDir , distDir ) )
143+ } ,
69144 } ,
70- } ,
71- {
72- name : "copyLocales" ,
73- setup ( build ) {
74- build . onEnd ( ( ) => copyLocales ( srcDir , distDir ) )
145+ {
146+ name : "copyLocales" ,
147+ setup ( build ) {
148+ build . onEnd ( ( ) => copyLocales ( srcDir , distDir ) )
149+ } ,
75150 } ,
76- } ,
77- {
78- name : "esbuild-problem-matcher" ,
79- setup ( build ) {
80- build . onStart ( ( ) => console . log ( "[esbuild-problem-matcher#onStart]" ) )
81- build . onEnd ( ( result ) => {
82- result . errors . forEach ( ( { text, location } ) => {
83- console . error ( `✘ [ERROR] ${ text } ` )
84- if ( location && location . file ) {
85- console . error ( ` ${ location . file } :${ location . line } :${ location . column } :` )
86- }
87- } )
151+ {
152+ name : "esbuild-problem-matcher" ,
153+ setup ( build ) {
154+ build . onStart ( ( ) => console . log ( "[esbuild-problem-matcher#onStart]" ) )
155+ build . onEnd ( ( result ) => {
156+ result . errors . forEach ( ( { text, location } ) => {
157+ console . error ( `✘ [ERROR] ${ text } ` )
158+ if ( location && location . file ) {
159+ console . error ( ` ${ location . file } :${ location . line } :${ location . column } :` )
160+ }
161+ } )
88162
89- console . log ( "[esbuild-problem-matcher#onEnd]" )
90- } )
163+ console . log ( "[esbuild-problem-matcher#onEnd]" )
164+ } )
165+ } ,
91166 } ,
92- } ,
93- ]
94-
95- /**
96- * @type {import('esbuild').BuildOptions }
97- */
98- const extensionConfig = {
99- ...buildOptions ,
100- plugins,
101- entryPoints : [ "extension.ts" ] ,
102- outfile : "dist/extension.js" ,
103- external : [ "vscode" ] ,
104- }
167+ ]
105168
106- /**
107- * @type {import('esbuild').BuildOptions }
108- */
109- const workerConfig = {
110- ...buildOptions ,
111- entryPoints : [ "workers/countTokens.ts" ] ,
112- outdir : "dist/workers" ,
113- }
169+ /**
170+ * @type {import('esbuild').BuildOptions }
171+ */
172+ const extensionConfig = {
173+ ...buildOptions ,
174+ plugins,
175+ entryPoints : [ "extension.ts" ] ,
176+ outfile : "dist/extension.js" ,
177+ external : [ "vscode" ] ,
178+ }
179+
180+ /**
181+ * @type {import('esbuild').BuildOptions }
182+ */
183+ const workerConfig = {
184+ ...buildOptions ,
185+ entryPoints : [ "workers/countTokens.ts" ] ,
186+ outdir : "dist/workers" ,
187+ }
188+
189+ const [ extensionCtx , workerCtx ] = await Promise . all ( [
190+ esbuild . context ( extensionConfig ) ,
191+ esbuild . context ( workerConfig ) ,
192+ ] )
114193
115- const [ extensionCtx , workerCtx ] = await Promise . all ( [
116- esbuild . context ( extensionConfig ) ,
117- esbuild . context ( workerConfig ) ,
118- ] )
119-
120- if ( watch ) {
121- await Promise . all ( [ extensionCtx . watch ( ) , workerCtx . watch ( ) ] )
122- copyLocales ( srcDir , distDir )
123- setupLocaleWatcher ( srcDir , distDir )
124- } else {
125- await Promise . all ( [ extensionCtx . rebuild ( ) , workerCtx . rebuild ( ) ] )
126- await Promise . all ( [ extensionCtx . dispose ( ) , workerCtx . dispose ( ) ] )
194+ if ( watch ) {
195+ await Promise . all ( [ extensionCtx . watch ( ) , workerCtx . watch ( ) ] )
196+ copyLocales ( srcDir , distDir )
197+ setupLocaleWatcher ( srcDir , distDir )
198+ } else {
199+ await Promise . all ( [ extensionCtx . rebuild ( ) , workerCtx . rebuild ( ) ] )
200+ await Promise . all ( [ extensionCtx . dispose ( ) , workerCtx . dispose ( ) ] )
201+ }
202+ } finally {
203+ // Always release the lock
204+ releaseLock ( )
205+ console . log ( `[ ${ name } ] Build lock released` )
127206 }
128207}
129208
130209main ( ) . catch ( ( e ) => {
131210 console . error ( e )
211+ releaseLock ( )
132212 process . exit ( 1 )
133213} )
0 commit comments