@hominis/fireforge 0.15.6 → 0.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +158 -15
  3. package/dist/src/commands/build.js +60 -3
  4. package/dist/src/commands/furnace/chrome-doc-templates.d.ts +17 -0
  5. package/dist/src/commands/furnace/chrome-doc-templates.js +18 -0
  6. package/dist/src/commands/furnace/chrome-doc-tests.d.ts +23 -0
  7. package/dist/src/commands/furnace/chrome-doc-tests.js +120 -0
  8. package/dist/src/commands/furnace/chrome-doc.d.ts +11 -0
  9. package/dist/src/commands/furnace/chrome-doc.js +37 -4
  10. package/dist/src/commands/furnace/create-dry-run.d.ts +38 -0
  11. package/dist/src/commands/furnace/create-dry-run.js +100 -0
  12. package/dist/src/commands/furnace/create-features.d.ts +24 -0
  13. package/dist/src/commands/furnace/create-features.js +56 -0
  14. package/dist/src/commands/furnace/create-templates.d.ts +9 -5
  15. package/dist/src/commands/furnace/create-templates.js +28 -6
  16. package/dist/src/commands/furnace/create.js +62 -63
  17. package/dist/src/commands/furnace/index.js +4 -1
  18. package/dist/src/commands/lint.d.ts +17 -2
  19. package/dist/src/commands/lint.js +25 -2
  20. package/dist/src/commands/register.d.ts +1 -1
  21. package/dist/src/commands/register.js +30 -7
  22. package/dist/src/commands/run.d.ts +15 -1
  23. package/dist/src/commands/run.js +202 -7
  24. package/dist/src/commands/test.js +113 -3
  25. package/dist/src/core/build-audit-registration.d.ts +80 -0
  26. package/dist/src/core/build-audit-registration.js +187 -0
  27. package/dist/src/core/build-audit-transforms.d.ts +23 -0
  28. package/dist/src/core/build-audit-transforms.js +94 -0
  29. package/dist/src/core/build-audit.js +107 -7
  30. package/dist/src/core/furnace-apply-ftl.d.ts +5 -3
  31. package/dist/src/core/furnace-apply-ftl.js +6 -2
  32. package/dist/src/core/furnace-apply-helpers.js +14 -4
  33. package/dist/src/core/furnace-config-custom.d.ts +14 -0
  34. package/dist/src/core/furnace-config-custom.js +64 -0
  35. package/dist/src/core/furnace-config.js +2 -39
  36. package/dist/src/core/furnace-validate-accessibility.d.ts +9 -2
  37. package/dist/src/core/furnace-validate-accessibility.js +17 -3
  38. package/dist/src/core/furnace-validate-helpers.d.ts +13 -1
  39. package/dist/src/core/furnace-validate-helpers.js +19 -0
  40. package/dist/src/core/furnace-validate-registration.d.ts +6 -4
  41. package/dist/src/core/furnace-validate-registration.js +66 -6
  42. package/dist/src/core/furnace-validate-structure.js +6 -2
  43. package/dist/src/core/furnace-validate.js +6 -3
  44. package/dist/src/core/mach-build-artifacts.d.ts +44 -0
  45. package/dist/src/core/mach-build-artifacts.js +104 -3
  46. package/dist/src/core/mach.d.ts +27 -1
  47. package/dist/src/core/mach.js +26 -2
  48. package/dist/src/core/shared-ftl.d.ts +28 -0
  49. package/dist/src/core/shared-ftl.js +42 -0
  50. package/dist/src/core/smoke-patterns.d.ts +45 -0
  51. package/dist/src/core/smoke-patterns.js +100 -0
  52. package/dist/src/core/test-stale-check.d.ts +42 -0
  53. package/dist/src/core/test-stale-check.js +114 -0
  54. package/dist/src/core/xpcshell-appdir.d.ts +143 -0
  55. package/dist/src/core/xpcshell-appdir.js +273 -0
  56. package/dist/src/errors/codes.d.ts +13 -0
  57. package/dist/src/errors/codes.js +13 -0
  58. package/dist/src/errors/run.d.ts +16 -0
  59. package/dist/src/errors/run.js +22 -0
  60. package/dist/src/types/commands/options.d.ts +64 -0
  61. package/dist/src/types/furnace.d.ts +39 -0
  62. package/dist/src/utils/process.d.ts +63 -0
  63. package/dist/src/utils/process.js +122 -0
  64. package/package.json +1 -1
@@ -234,6 +234,128 @@ export async function execInheritCapture(command, args, options = {}) {
234
234
  });
235
235
  });
236
236
  }
237
+ /**
238
+ * Spawns `command` with `args` in its own process group (POSIX), streams
239
+ * stdout/stderr line-by-line to the caller, enforces a deadline by
240
+ * SIGTERMing the whole group when it elapses, and returns the captured
241
+ * output alongside a `timedOut` flag.
242
+ *
243
+ * Process-group semantics matter here because `mach run` execs a Python
244
+ * wrapper that then forks Firefox, which itself spawns content processes.
245
+ * Sending SIGTERM only to the Python PID leaves an orphan Firefox tree
246
+ * behind. Running the child as a process-group leader (`detached: true`
247
+ * on POSIX) and signalling `-pid` routes the kill to every descendant
248
+ * that inherited the group.
249
+ *
250
+ * Windows fallback: `detached: true` does not create an equivalent group
251
+ * there, so we degrade to `child.kill()` and log a best-effort warning
252
+ * via the `onStderrLine` callback if the caller wired one.
253
+ */
254
+ export async function execSmokeRun(command, args, options) {
255
+ return new Promise((resolve, reject) => {
256
+ const usesProcessGroup = process.platform !== 'win32';
257
+ const child = spawn(command, args, {
258
+ cwd: options.cwd,
259
+ env: { ...process.env, ...options.env },
260
+ stdio: ['ignore', 'pipe', 'pipe'],
261
+ // A new process group on POSIX lets us signal the whole descendant
262
+ // tree at once, which is essential for mach → python → firefox →
263
+ // content-process chains. On Windows spawn ignores this for our
264
+ // purposes and we fall back to child.kill below.
265
+ detached: usesProcessGroup,
266
+ });
267
+ const out = createStreamCollector(options.mirror?.stdout);
268
+ const err = createStreamCollector(options.mirror?.stderr);
269
+ let stdoutBuffer = '';
270
+ let stderrBuffer = '';
271
+ child.stdout.on('data', (data) => {
272
+ out.onData(data);
273
+ stdoutBuffer += data.toString();
274
+ stdoutBuffer = dispatchCompleteLines(stdoutBuffer, options.onStdoutLine);
275
+ });
276
+ child.stderr.on('data', (data) => {
277
+ err.onData(data);
278
+ stderrBuffer += data.toString();
279
+ stderrBuffer = dispatchCompleteLines(stderrBuffer, options.onStderrLine);
280
+ });
281
+ let timedOut = false;
282
+ let graceTimer;
283
+ const signalChildGroup = (signal) => {
284
+ if (child.exitCode !== null || child.signalCode !== null)
285
+ return;
286
+ const targetPid = child.pid;
287
+ if (targetPid === undefined)
288
+ return;
289
+ try {
290
+ if (usesProcessGroup) {
291
+ // Negative PID routes to the process group, killing the Python
292
+ // wrapper, the firefox it forked, and every content process
293
+ // that inherited the group.
294
+ process.kill(-targetPid, signal);
295
+ }
296
+ else {
297
+ child.kill(signal);
298
+ }
299
+ }
300
+ catch {
301
+ // Already gone. Nothing to do.
302
+ }
303
+ };
304
+ const deadlineTimer = setTimeout(() => {
305
+ timedOut = true;
306
+ signalChildGroup('SIGTERM');
307
+ graceTimer = setTimeout(() => {
308
+ signalChildGroup('SIGKILL');
309
+ }, options.killGraceMs ?? 10000);
310
+ graceTimer.unref();
311
+ }, options.smokeTimeoutMs);
312
+ deadlineTimer.unref();
313
+ child.on('error', (error) => {
314
+ clearTimeout(deadlineTimer);
315
+ if (graceTimer)
316
+ clearTimeout(graceTimer);
317
+ reject(error);
318
+ });
319
+ child.on('close', (code, signal) => {
320
+ clearTimeout(deadlineTimer);
321
+ if (graceTimer)
322
+ clearTimeout(graceTimer);
323
+ // Flush any remaining partial line (child ended without a trailing newline).
324
+ if (stdoutBuffer.length > 0)
325
+ options.onStdoutLine?.(stdoutBuffer);
326
+ if (stderrBuffer.length > 0)
327
+ options.onStderrLine?.(stderrBuffer);
328
+ resolve({
329
+ stdout: out.getText(),
330
+ stderr: err.getText(),
331
+ exitCode: exitCodeFromClose(code, signal),
332
+ timedOut,
333
+ });
334
+ });
335
+ });
336
+ }
337
+ /**
338
+ * Drains complete newline-terminated lines from `buffer`, dispatching each
339
+ * trimmed-of-newline line to `cb`. Returns the remaining partial line that
340
+ * has not yet been terminated — callers should keep accumulating into it.
341
+ */
342
+ function dispatchCompleteLines(buffer, cb) {
343
+ if (!cb) {
344
+ // No listener — still need to collapse accumulated newlines so the
345
+ // buffer does not grow forever. Keep only the partial-line tail.
346
+ const lastNewline = buffer.lastIndexOf('\n');
347
+ if (lastNewline === -1)
348
+ return buffer;
349
+ return buffer.slice(lastNewline + 1);
350
+ }
351
+ let idx;
352
+ while ((idx = buffer.indexOf('\n')) !== -1) {
353
+ const line = buffer.slice(0, idx).replace(/\r$/, '');
354
+ buffer = buffer.slice(idx + 1);
355
+ cb(line);
356
+ }
357
+ return buffer;
358
+ }
237
359
  /**
238
360
  * Finds an executable in the system PATH.
239
361
  * @param name - Name of the executable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hominis/fireforge",
3
- "version": "0.15.6",
3
+ "version": "0.15.8",
4
4
  "description": "FireForge — a build tool for customizing Firefox",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",