@@ -30,6 +30,66 @@ function createTmpFile(content = 'console.log("running");', ext = '.js', basenam |
30 | 30 | return file; |
31 | 31 | } |
32 | 32 |
|
| 33 | +function runInBackground({ args = [], options = {}, completed = 'Completed running', shouldFail = false }) { |
| 34 | + let future = Promise.withResolvers(); |
| 35 | + let child; |
| 36 | + let stderr = ''; |
| 37 | + let stdout = []; |
| 38 | + |
| 39 | + const run = () => { |
| 40 | + args.unshift('--no-warnings'); |
| 41 | + child = spawn(execPath, args, { encoding: 'utf8', stdio: 'pipe', ...options }); |
| 42 | + |
| 43 | + child.stderr.on('data', (data) => { |
| 44 | + stderr += data; |
| 45 | + }); |
| 46 | + |
| 47 | + const rl = createInterface({ input: child.stdout }); |
| 48 | + rl.on('line', (data) => { |
| 49 | + if (!data.startsWith('Waiting for graceful termination') && !data.startsWith('Gracefully restarted')) { |
| 50 | + stdout.push(data); |
| 51 | + if (data.startsWith(completed)) { |
| 52 | + future.resolve({ stderr, stdout }); |
| 53 | + future = Promise.withResolvers(); |
| 54 | + stdout = []; |
| 55 | + stderr = ''; |
| 56 | + } else if (data.startsWith('Failed running')) { |
| 57 | + if (shouldFail) { |
| 58 | + future.resolve({ stderr, stdout }); |
| 59 | + } else { |
| 60 | + future.reject({ stderr, stdout }); |
| 61 | + } |
| 62 | + future = Promise.withResolvers(); |
| 63 | + stdout = []; |
| 64 | + stderr = ''; |
| 65 | + } |
| 66 | + } |
| 67 | + }); |
| 68 | + }; |
| 69 | + |
| 70 | + return { |
| 71 | + async done() { |
| 72 | + child?.kill(); |
| 73 | + future.resolve(); |
| 74 | + return { stdout, stderr }; |
| 75 | + }, |
| 76 | + restart(timeout = 1000) { |
| 77 | + if (!child) { |
| 78 | + run(); |
| 79 | + } |
| 80 | + const timer = setTimeout(() => { |
| 81 | + if (!future.resolved) { |
| 82 | + child.kill(); |
| 83 | + future.reject(new Error('Timed out waiting for restart')); |
| 84 | + } |
| 85 | + }, timeout); |
| 86 | + return future.promise.finally(() => { |
| 87 | + clearTimeout(timer); |
| 88 | + }); |
| 89 | + } |
| 90 | + }; |
| 91 | +} |
| 92 | + |
33 | 93 | async function runWriteSucceed({ |
34 | 94 | file, |
35 | 95 | watchedFile, |
@@ -132,6 +192,56 @@ describe('watch mode', { concurrency: !process.env.TEST_PARALLEL, timeout: 60_00 |
132 | 192 | ]); |
133 | 193 | }); |
134 | 194 |
|
| 195 | + it('should reload env variables when --env-file changes', async () => { |
| 196 | + const envKey = `TEST_ENV_${Date.now()}`; |
| 197 | + const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey});`); |
| 198 | + const envFile = createTmpFile(`${envKey}=value1`, '.env'); |
| 199 | + const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] }); |
| 200 | + |
| 201 | + try { |
| 202 | + await restart(); |
| 203 | + writeFileSync(envFile, `${envKey}=value2`); |
| 204 | + |
| 205 | + // Second restart, after env change |
| 206 | + const { stdout, stderr } = await restart(); |
| 207 | + |
| 208 | + assert.strictEqual(stderr, ''); |
| 209 | + assert.deepStrictEqual(stdout, [ |
| 210 | + `Restarting ${inspect(jsFile)}`, |
| 211 | + 'ENV: value2', |
| 212 | + `Completed running ${inspect(jsFile)}`, |
| 213 | + ]); |
| 214 | + } finally { |
| 215 | + await done(); |
| 216 | + } |
| 217 | + }); |
| 218 | + |
| 219 | + it('should load new env variables when --env-file changes', async () => { |
| 220 | + const envKey = `TEST_ENV_${Date.now()}`; |
| 221 | + const envKey2 = `TEST_ENV_2_${Date.now()}`; |
| 222 | + const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey} + '\\n' + 'ENV2: ' + process.env.${envKey2});`); |
| 223 | + const envFile = createTmpFile(`${envKey}=value1`, '.env'); |
| 224 | + const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] }); |
| 225 | + |
| 226 | + try { |
| 227 | + await restart(); |
| 228 | + await writeFileSync(envFile, `${envKey}=value1\n${envKey2}=newValue`); |
| 229 | + |
| 230 | + // Second restart, after env change |
| 231 | + const { stderr, stdout } = await restart(); |
| 232 | + |
| 233 | + assert.strictEqual(stderr, ''); |
| 234 | + assert.deepStrictEqual(stdout, [ |
| 235 | + `Restarting ${inspect(jsFile)}`, |
| 236 | + 'ENV: value1', |
| 237 | + 'ENV2: newValue', |
| 238 | + `Completed running ${inspect(jsFile)}`, |
| 239 | + ]); |
| 240 | + } finally { |
| 241 | + await done(); |
| 242 | + } |
| 243 | + }); |
| 244 | + |
135 | 245 | it('should watch changes to a failing file', async () => { |
136 | 246 | const file = createTmpFile('throw new Error("fails");'); |
137 | 247 | const { stderr, stdout } = await runWriteSucceed({ |
|