aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/watch.js
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/watch.js')
-rw-r--r--scripts/watch.js203
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 @@
1import { spawn } from 'node:child_process'; 1import { spawn } from 'node:child_process';
2import os from 'node:os';
2import path from 'node:path'; 3import path from 'node:path';
3 4
4import { watch } from 'chokidar'; 5import { 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} */
25const serviceInjectModule = path.join(
26 thisDir,
27 '../packages/service-inject/dist/index.js',
28);
29
30/** @type {string} */
31const userDataDir = path.join(thisDir, '../userDataDir/development');
32
23/** @type {RegExp[]} */ 33/** @type {RegExp[]} */
24const stderrIgnorePatterns = [ 34const 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 */
110async 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 */
128function 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 */
141function 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 */
229function 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 */
196async function setupDevEnvironment() { 248async 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');