diff options
Diffstat (limited to 'scripts/watch.js')
-rw-r--r-- | scripts/watch.js | 203 |
1 files changed, 139 insertions, 64 deletions
diff --git a/scripts/watch.js b/scripts/watch.js index 91f3583..5db0d30 100644 --- a/scripts/watch.js +++ b/scripts/watch.js | |||
@@ -1,4 +1,5 @@ | |||
1 | import { spawn } from 'node:child_process'; | 1 | import { spawn } from 'node:child_process'; |
2 | import os from 'node:os'; | ||
2 | import path from 'node:path'; | 3 | import path from 'node:path'; |
3 | 4 | ||
4 | import { watch } from 'chokidar'; | 5 | import { watch } from 'chokidar'; |
@@ -20,15 +21,32 @@ const serviceSharedModule = path.join( | |||
20 | '../packages/service-shared/dist/index.mjs', | 21 | '../packages/service-shared/dist/index.mjs', |
21 | ); | 22 | ); |
22 | 23 | ||
24 | /** @type {string} */ | ||
25 | const serviceInjectModule = path.join( | ||
26 | thisDir, | ||
27 | '../packages/service-inject/dist/index.js', | ||
28 | ); | ||
29 | |||
30 | /** @type {string} */ | ||
31 | const userDataDir = path.join(thisDir, '../userDataDir/development'); | ||
32 | |||
23 | /** @type {RegExp[]} */ | 33 | /** @type {RegExp[]} */ |
24 | const stderrIgnorePatterns = [ | 34 | const stderrIgnorePatterns = [ |
25 | // warning about devtools extension | 35 | // Warning about devtools extension |
26 | // https://github.com/cawa-93/vite-electron-builder/issues/492 | 36 | // https://github.com/cawa-93/vite-electron-builder/issues/492 |
27 | // https://github.com/MarshallOfSound/electron-devtools-installer/issues/143 | 37 | // https://github.com/MarshallOfSound/electron-devtools-installer/issues/143 |
28 | /ExtensionLoadWarning/, | 38 | /ExtensionLoadWarning/, |
29 | // GPU sandbox error with the mesa GLSL cache | 39 | // GPU sandbox error with the mesa GLSL cache |
30 | // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=918433 | 40 | // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=918433 |
31 | /InitializeSandbox\(\) called with multiple threads in process gpu-process/, | 41 | /InitializeSandbox\(\) called with multiple threads in process gpu-process/, |
42 | // Very spammy GPU error reporting | ||
43 | // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=901831 | ||
44 | /GetVSyncParametersIfAvailable\(\) failed/, | ||
45 | // Does not seem to occur in production | ||
46 | // https://github.com/electron/electron/issues/32133#issuecomment-1079916988 | ||
47 | /(sandbox_bundle\.js script failed to run|object null is not iterable).+ node:electron\/js2c\/sandbox_bundle \(160\)$/, | ||
48 | // Warning when GPU-accelerated video decoding is not available (some wayland configs) | ||
49 | /Passthrough is not supported, GL is/, | ||
32 | ]; | 50 | ]; |
33 | 51 | ||
34 | /** | 52 | /** |
@@ -104,50 +122,6 @@ async function setupEsbuildWatcher(packageName, extraPaths, callback) { | |||
104 | } | 122 | } |
105 | 123 | ||
106 | /** | 124 | /** |
107 | * @param {string} packageName | ||
108 | * @returns {Promise<import('vite').ViteDevServer>} | ||
109 | */ | ||
110 | async function setupDevServer(packageName) { | ||
111 | const viteDevServer = await createServer({ | ||
112 | build: { | ||
113 | watch: { | ||
114 | skipWrite: true, | ||
115 | clearScreen: false, | ||
116 | }, | ||
117 | }, | ||
118 | configFile: path.join(thisDir, `../packages/${packageName}/vite.config.js`), | ||
119 | }); | ||
120 | await viteDevServer.listen(); | ||
121 | return viteDevServer; | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * @param {(event: import('vite').HMRPayload) => void} sendEvent | ||
126 | * @returns {Promise<void>} | ||
127 | */ | ||
128 | function setupPreloadPackageWatcher(sendEvent) { | ||
129 | return setupEsbuildWatcher('preload', [sharedModule], () => { | ||
130 | sendEvent({ | ||
131 | type: 'full-reload', | ||
132 | }); | ||
133 | }); | ||
134 | } | ||
135 | |||
136 | /** | ||
137 | * @param {string} packageName | ||
138 | * @param {(event: import('vite').HMRPayload) => void} sendEvent | ||
139 | * @returns {Promise<void>} | ||
140 | */ | ||
141 | function setupServicePackageWatcher(packageName, sendEvent) { | ||
142 | return setupEsbuildWatcher(packageName, [serviceSharedModule], () => { | ||
143 | sendEvent({ | ||
144 | type: 'custom', | ||
145 | event: 'sophie:reload-services', | ||
146 | }); | ||
147 | }); | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * @param {import('vite').ViteDevServer} viteDevServer | 125 | * @param {import('vite').ViteDevServer} viteDevServer |
152 | * @returns {Promise<void>} | 126 | * @returns {Promise<void>} |
153 | */ | 127 | */ |
@@ -163,34 +137,112 @@ function setupMainPackageWatcher(viteDevServer) { | |||
163 | const portOrDefault = port || 3000; | 137 | const portOrDefault = port || 3000; |
164 | process.env.VITE_DEV_SERVER_URL = `${protocol}//${hostOrDefault}:${portOrDefault}/`; | 138 | process.env.VITE_DEV_SERVER_URL = `${protocol}//${hostOrDefault}:${portOrDefault}/`; |
165 | 139 | ||
140 | /** @type {string[]} */ | ||
141 | let extraArgs = []; | ||
142 | |||
143 | if (['aix', 'freebsd', 'linux', 'openbsd', 'sunos'].includes(os.platform())) { | ||
144 | // Use wayland display server if available. | ||
145 | extraArgs = | ||
146 | 'WAYLAND_DISPLAY' in process.env | ||
147 | ? [ | ||
148 | '--enable-features=WaylandWindowDecorations,WebRTCPipeWireCapturer', | ||
149 | '--ozone-platform=wayland', | ||
150 | ] | ||
151 | : ['--ozone-platform=x11']; | ||
152 | } | ||
153 | |||
166 | /** @type {import('child_process').ChildProcessByStdio<null, null, import('stream').Readable> | 154 | /** @type {import('child_process').ChildProcessByStdio<null, null, import('stream').Readable> |
167 | | undefined} */ | 155 | | undefined} */ |
168 | let spawnProcess; | 156 | let childProcess; |
157 | |||
158 | // Prevent overlapping restarts and restarting while exiting. | ||
159 | let mayRestart = true; | ||
160 | |||
161 | function spawnProcess() { | ||
162 | childProcess = spawn( | ||
163 | String(electronPath), | ||
164 | [ | ||
165 | '.', | ||
166 | `--user-data-dir=${userDataDir}`, | ||
167 | ...extraArgs, | ||
168 | ...process.argv.slice(2), | ||
169 | ], | ||
170 | { | ||
171 | stdio: ['inherit', 'inherit', 'pipe'], | ||
172 | }, | ||
173 | ); | ||
174 | |||
175 | childProcess.stderr.on('data', (/** @type {Buffer} */ data) => { | ||
176 | const stderrString = data.toString('utf8').trimEnd(); | ||
177 | if (!stderrIgnorePatterns.some((r) => r.test(stderrString))) { | ||
178 | console.error(stderrString); | ||
179 | } | ||
180 | }); | ||
181 | |||
182 | childProcess.on('exit', () => process.exit()); | ||
183 | } | ||
184 | |||
185 | function killProcess() { | ||
186 | mayRestart = false; | ||
187 | if (childProcess === undefined) { | ||
188 | return; | ||
189 | } | ||
190 | childProcess.stderr.removeAllListeners('data'); | ||
191 | childProcess.kill('SIGINT'); | ||
192 | } | ||
193 | |||
194 | process.on('SIGINT', () => { | ||
195 | if (childProcess !== undefined) { | ||
196 | childProcess.removeAllListeners('exit'); | ||
197 | childProcess.on('exit', process.exit()); | ||
198 | killProcess(); | ||
199 | } | ||
200 | }); | ||
169 | 201 | ||
170 | return setupEsbuildWatcher( | 202 | return setupEsbuildWatcher( |
171 | 'main', | 203 | 'main', |
172 | [serviceSharedModule, sharedModule], | 204 | [serviceSharedModule, sharedModule], |
173 | () => { | 205 | () => { |
174 | if (spawnProcess !== undefined) { | 206 | if (!mayRestart) { |
175 | spawnProcess.kill('SIGINT'); | 207 | // We're already about to restart electron. |
176 | spawnProcess = undefined; | 208 | return; |
177 | } | 209 | } |
178 | 210 | ||
179 | spawnProcess = spawn(String(electronPath), ['.'], { | 211 | if (childProcess === undefined) { |
180 | stdio: ['inherit', 'inherit', 'pipe'], | 212 | spawnProcess(); |
181 | }); | 213 | return; |
214 | } | ||
182 | 215 | ||
183 | spawnProcess.stderr.on('data', (/** @type {Buffer} */ data) => { | 216 | childProcess.removeAllListeners('exit'); |
184 | const stderrString = data.toString('utf-8').trimEnd(); | 217 | childProcess.on('exit', () => { |
185 | if (!stderrIgnorePatterns.some((r) => r.test(stderrString))) { | 218 | spawnProcess(); |
186 | console.error(stderrString); | 219 | mayRestart = true; |
187 | } | ||
188 | }); | 220 | }); |
221 | killProcess(); | ||
189 | }, | 222 | }, |
190 | ); | 223 | ); |
191 | } | 224 | } |
192 | 225 | ||
193 | /** | 226 | /** |
227 | * @param {(event: import('vite').HMRPayload) => void} sendEvent | ||
228 | */ | ||
229 | function setupTranslationsWatcher(sendEvent) { | ||
230 | const localesDir = path.join(thisDir, '../locales'); | ||
231 | const watcher = watch(localesDir, { | ||
232 | ignored: /\.missing\.json$/, | ||
233 | ignoreInitial: true, | ||
234 | persistent: true, | ||
235 | }); | ||
236 | watcher.on('change', () => { | ||
237 | console.log(`\u26A1 Reloading translations`); | ||
238 | sendEvent({ | ||
239 | type: 'custom', | ||
240 | event: 'sophie:reload-translations', | ||
241 | }); | ||
242 | }); | ||
243 | } | ||
244 | |||
245 | /** | ||
194 | * @returns {Promise<void>} | 246 | * @returns {Promise<void>} |
195 | */ | 247 | */ |
196 | async function setupDevEnvironment() { | 248 | async function setupDevEnvironment() { |
@@ -214,7 +266,16 @@ async function setupDevEnvironment() { | |||
214 | * @returns {Promise<void>} | 266 | * @returns {Promise<void>} |
215 | */ | 267 | */ |
216 | async function startDevServer() { | 268 | async function startDevServer() { |
217 | viteDevServer = await setupDevServer('renderer'); | 269 | viteDevServer = await createServer({ |
270 | build: { | ||
271 | watch: { | ||
272 | skipWrite: true, | ||
273 | clearScreen: false, | ||
274 | }, | ||
275 | }, | ||
276 | configFile: path.join(thisDir, `../packages/renderer/vite.config.js`), | ||
277 | }); | ||
278 | await viteDevServer.listen(); | ||
218 | } | 279 | } |
219 | 280 | ||
220 | /** | 281 | /** |
@@ -223,7 +284,11 @@ async function setupDevEnvironment() { | |||
223 | async function watchRendererPackages() { | 284 | async function watchRendererPackages() { |
224 | await setupEsbuildWatcher('shared'); | 285 | await setupEsbuildWatcher('shared'); |
225 | await Promise.all([ | 286 | await Promise.all([ |
226 | setupPreloadPackageWatcher(sendEvent), | 287 | setupEsbuildWatcher('preload', [sharedModule], () => |
288 | sendEvent({ | ||
289 | type: 'full-reload', | ||
290 | }), | ||
291 | ), | ||
227 | startDevServer(), | 292 | startDevServer(), |
228 | ]); | 293 | ]); |
229 | } | 294 | } |
@@ -233,13 +298,23 @@ async function setupDevEnvironment() { | |||
233 | */ | 298 | */ |
234 | async function watchServicePackages() { | 299 | async function watchServicePackages() { |
235 | await setupEsbuildWatcher('service-shared'); | 300 | await setupEsbuildWatcher('service-shared'); |
236 | await Promise.all([ | 301 | await setupEsbuildWatcher('service-inject', [serviceSharedModule]); |
237 | setupServicePackageWatcher('service-inject', sendEvent), | 302 | await setupEsbuildWatcher( |
238 | setupServicePackageWatcher('service-preload', sendEvent), | 303 | 'service-preload', |
239 | ]); | 304 | [serviceSharedModule, serviceInjectModule], |
305 | () => | ||
306 | sendEvent({ | ||
307 | type: 'custom', | ||
308 | event: 'sophie:reload-services', | ||
309 | }), | ||
310 | ); | ||
240 | } | 311 | } |
241 | 312 | ||
242 | await Promise.all([watchRendererPackages(), watchServicePackages()]); | 313 | await Promise.all([ |
314 | watchRendererPackages(), | ||
315 | watchServicePackages(), | ||
316 | setupTranslationsWatcher(sendEvent), | ||
317 | ]); | ||
243 | 318 | ||
244 | if (viteDevServer === undefined) { | 319 | if (viteDevServer === undefined) { |
245 | console.error('Failed to create vite dev server'); | 320 | console.error('Failed to create vite dev server'); |