@hominis/fireforge 0.35.0 → 0.36.0
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.
- package/CHANGELOG.md +29 -0
- package/README.md +34 -9
- package/dist/bin/fireforge.js +21 -6
- package/dist/src/cli.js +22 -5
- package/dist/src/commands/bootstrap-checks.js +0 -1
- package/dist/src/commands/bootstrap.d.ts +2 -1
- package/dist/src/commands/bootstrap.js +16 -7
- package/dist/src/commands/build.js +4 -3
- package/dist/src/commands/config.js +30 -10
- package/dist/src/commands/discard.js +18 -3
- package/dist/src/commands/doctor/post-rebase-audit.js +0 -1
- package/dist/src/commands/doctor-check-core.js +0 -1
- package/dist/src/commands/doctor-external-toolchains.d.ts +3 -0
- package/dist/src/commands/doctor-external-toolchains.js +54 -0
- package/dist/src/commands/doctor-furnace-jar.js +0 -1
- package/dist/src/commands/doctor-furnace-manifest-sync.js +0 -1
- package/dist/src/commands/doctor-furnace.js +2 -2
- package/dist/src/commands/doctor-working-tree.js +0 -1
- package/dist/src/commands/doctor.js +4 -1
- package/dist/src/commands/download.js +28 -13
- package/dist/src/commands/export-all.js +5 -3
- package/dist/src/commands/export-flow.js +12 -7
- package/dist/src/commands/export-placement-gate.js +0 -1
- package/dist/src/commands/export-placement-policy.js +0 -1
- package/dist/src/commands/export-shared.d.ts +5 -1
- package/dist/src/commands/export-shared.js +18 -6
- package/dist/src/commands/export.js +15 -10
- package/dist/src/commands/furnace/apply.js +22 -6
- package/dist/src/commands/furnace/chrome-doc-remove.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-templates.js +0 -1
- package/dist/src/commands/furnace/chrome-doc-tests.js +0 -1
- package/dist/src/commands/furnace/chrome-doc.js +0 -1
- package/dist/src/commands/furnace/create-browser-test.js +0 -1
- package/dist/src/commands/furnace/create-dry-run.js +0 -1
- package/dist/src/commands/furnace/create-features.js +0 -1
- package/dist/src/commands/furnace/create-mochikit.js +0 -1
- package/dist/src/commands/furnace/create-readback.js +0 -1
- package/dist/src/commands/furnace/create-templates.js +0 -1
- package/dist/src/commands/furnace/create-validation.js +0 -1
- package/dist/src/commands/furnace/create-xpcshell.js +0 -1
- package/dist/src/commands/furnace/create.js +0 -1
- package/dist/src/commands/furnace/deploy.js +7 -83
- package/dist/src/commands/furnace/diff.js +0 -1
- package/dist/src/commands/furnace/index.js +59 -25
- package/dist/src/commands/furnace/init.js +0 -1
- package/dist/src/commands/furnace/list.js +0 -1
- package/dist/src/commands/furnace/override.js +0 -1
- package/dist/src/commands/furnace/preview.js +3 -3
- package/dist/src/commands/furnace/refresh.js +0 -1
- package/dist/src/commands/furnace/remove-state.d.ts +15 -0
- package/dist/src/commands/furnace/remove-state.js +47 -1
- package/dist/src/commands/furnace/remove.js +3 -9
- package/dist/src/commands/furnace/rename-browser-test.js +1 -5
- package/dist/src/commands/furnace/rename-helpers.d.ts +6 -0
- package/dist/src/commands/furnace/rename-helpers.js +30 -1
- package/dist/src/commands/furnace/rename-xpcshell.js +7 -6
- package/dist/src/commands/furnace/rename.js +15 -36
- package/dist/src/commands/furnace/scan.js +0 -1
- package/dist/src/commands/furnace/status.js +0 -1
- package/dist/src/commands/furnace/sync.js +76 -4
- package/dist/src/commands/furnace/validate.js +0 -1
- package/dist/src/commands/furnace/validation-output.js +0 -1
- package/dist/src/commands/import.js +35 -9
- package/dist/src/commands/lint-per-patch.js +19 -7
- package/dist/src/commands/lint.js +13 -3
- package/dist/src/commands/manifest.js +1 -2
- package/dist/src/commands/package.js +2 -1
- package/dist/src/commands/patch/compact.js +1 -1
- package/dist/src/commands/patch/delete.js +1 -1
- package/dist/src/commands/patch/index.js +1 -1
- package/dist/src/commands/patch/lint-ignore.js +21 -6
- package/dist/src/commands/patch/move-files.js +1 -1
- package/dist/src/commands/patch/patch-context.js +0 -1
- package/dist/src/commands/patch/rename.js +4 -4
- package/dist/src/commands/patch/reorder.js +3 -4
- package/dist/src/commands/patch/split-plan.js +2 -1
- package/dist/src/commands/patch/split.js +4 -4
- package/dist/src/commands/patch/staged-dependency.js +1 -1
- package/dist/src/commands/patch/tier.js +18 -5
- package/dist/src/commands/re-export-bulk-scan.js +0 -1
- package/dist/src/commands/re-export-files.js +1 -1
- package/dist/src/commands/re-export-options.js +0 -1
- package/dist/src/commands/re-export-scan.js +0 -1
- package/dist/src/commands/re-export.js +6 -4
- package/dist/src/commands/rebase/abort.js +2 -9
- package/dist/src/commands/rebase/confirm.js +0 -1
- package/dist/src/commands/rebase/conflict-summary.js +0 -1
- package/dist/src/commands/rebase/continue.js +0 -1
- package/dist/src/commands/rebase/index.d.ts +1 -1
- package/dist/src/commands/rebase/index.js +10 -15
- package/dist/src/commands/rebase/patch-loop.js +38 -7
- package/dist/src/commands/rebase/summary.js +2 -3
- package/dist/src/commands/rebase.js +0 -1
- package/dist/src/commands/register.js +1 -1
- package/dist/src/commands/reset.js +16 -9
- package/dist/src/commands/resolve.js +2 -2
- package/dist/src/commands/run.js +4 -4
- package/dist/src/commands/setup-support.js +0 -1
- package/dist/src/commands/setup.js +1 -2
- package/dist/src/commands/source.js +1 -2
- package/dist/src/commands/status-output.js +1 -12
- package/dist/src/commands/status.js +147 -133
- package/dist/src/commands/test-appdir.js +0 -1
- package/dist/src/commands/test-diagnose.js +18 -2
- package/dist/src/commands/test-modes.d.ts +15 -0
- package/dist/src/commands/test-modes.js +61 -0
- package/dist/src/commands/test-register.d.ts +4 -0
- package/dist/src/commands/test-register.js +64 -0
- package/dist/src/commands/test-run.d.ts +23 -7
- package/dist/src/commands/test-run.js +15 -14
- package/dist/src/commands/test.d.ts +0 -4
- package/dist/src/commands/test.js +85 -86
- package/dist/src/commands/token-coverage.js +0 -1
- package/dist/src/commands/token.js +1 -2
- package/dist/src/commands/typecheck.js +1 -1
- package/dist/src/commands/verify.js +3 -9
- package/dist/src/commands/watch.js +5 -1
- package/dist/src/commands/wire.js +1 -1
- package/dist/src/core/ast-utils.js +0 -1
- package/dist/src/core/brand-validation.js +0 -1
- package/dist/src/core/branding.js +0 -1
- package/dist/src/core/browser-wire.js +0 -1
- package/dist/src/core/build-audit-platform.js +0 -1
- package/dist/src/core/build-audit-registration.js +0 -1
- package/dist/src/core/build-audit-resolve.js +0 -1
- package/dist/src/core/build-audit-transforms.js +0 -1
- package/dist/src/core/build-audit.js +0 -1
- package/dist/src/core/build-baseline-types.js +0 -1
- package/dist/src/core/build-baseline.js +0 -1
- package/dist/src/core/build-prepare.js +2 -2
- package/dist/src/core/config-mutate.js +0 -1
- package/dist/src/core/config-paths.d.ts +2 -2
- package/dist/src/core/config-paths.js +6 -1
- package/dist/src/core/config-state.js +0 -1
- package/dist/src/core/config-validate-patch-policy.js +0 -1
- package/dist/src/core/config-validate-test-toolchains.d.ts +8 -0
- package/dist/src/core/config-validate-test-toolchains.js +105 -0
- package/dist/src/core/config-validate.js +3 -1
- package/dist/src/core/config.js +0 -1
- package/dist/src/core/destructive.js +0 -1
- package/dist/src/core/diff-hunks.js +13 -7
- package/dist/src/core/engine-changes.js +0 -1
- package/dist/src/core/engine-session-lock.d.ts +12 -0
- package/dist/src/core/engine-session-lock.js +45 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +157 -48
- package/dist/src/core/firefox-archive.d.ts +4 -0
- package/dist/src/core/firefox-archive.js +6 -1
- package/dist/src/core/firefox-cache.js +60 -4
- package/dist/src/core/firefox-download.js +0 -1
- package/dist/src/core/firefox-extract.js +75 -16
- package/dist/src/core/firefox-ignorefile.js +0 -1
- package/dist/src/core/firefox.d.ts +12 -0
- package/dist/src/core/firefox.js +64 -2
- package/dist/src/core/furnace-apply-dry-run.js +0 -1
- package/dist/src/core/furnace-apply-ftl.d.ts +7 -3
- package/dist/src/core/furnace-apply-ftl.js +10 -5
- package/dist/src/core/furnace-apply-helpers.js +0 -1
- package/dist/src/core/furnace-apply-output.js +2 -2
- package/dist/src/core/furnace-apply.d.ts +7 -1
- package/dist/src/core/furnace-apply.js +47 -13
- package/dist/src/core/furnace-checksum-utils.js +0 -1
- package/dist/src/core/furnace-config-array-utils.js +0 -1
- package/dist/src/core/furnace-config-custom.js +0 -1
- package/dist/src/core/furnace-config-order.js +0 -1
- package/dist/src/core/furnace-config-tokens.js +0 -1
- package/dist/src/core/furnace-config-validate.js +0 -1
- package/dist/src/core/furnace-config.d.ts +11 -0
- package/dist/src/core/furnace-config.js +37 -1
- package/dist/src/core/furnace-constants.js +0 -1
- package/dist/src/core/furnace-css-fragments.js +0 -1
- package/dist/src/core/furnace-graph-utils.js +0 -1
- package/dist/src/core/furnace-jsconfig.d.ts +2 -3
- package/dist/src/core/furnace-jsconfig.js +25 -18
- package/dist/src/core/furnace-marker.js +0 -1
- package/dist/src/core/furnace-operation.js +0 -1
- package/dist/src/core/furnace-refresh.js +0 -1
- package/dist/src/core/furnace-registration-ast.d.ts +14 -0
- package/dist/src/core/furnace-registration-ast.js +1 -2
- package/dist/src/core/furnace-registration-remove.js +0 -1
- package/dist/src/core/furnace-registration-validate.js +1 -5
- package/dist/src/core/furnace-registration.js +0 -1
- package/dist/src/core/furnace-rollback.js +0 -1
- package/dist/src/core/furnace-scanner.js +0 -1
- package/dist/src/core/furnace-staleness.js +0 -1
- package/dist/src/core/furnace-state-persist.d.ts +72 -0
- package/dist/src/core/furnace-state-persist.js +118 -0
- package/dist/src/core/furnace-step-errors.d.ts +23 -0
- package/dist/src/core/furnace-step-errors.js +25 -0
- package/dist/src/core/furnace-stories.js +0 -1
- package/dist/src/core/furnace-validate-accessibility.js +0 -1
- package/dist/src/core/furnace-validate-checks.js +0 -1
- package/dist/src/core/furnace-validate-compatibility.js +0 -1
- package/dist/src/core/furnace-validate-helpers.js +0 -1
- package/dist/src/core/furnace-validate-registration.js +7 -7
- package/dist/src/core/furnace-validate-structure.d.ts +8 -0
- package/dist/src/core/furnace-validate-structure.js +11 -2
- package/dist/src/core/furnace-validate.js +0 -1
- package/dist/src/core/furnace-version-drift.d.ts +6 -3
- package/dist/src/core/furnace-version-drift.js +6 -4
- package/dist/src/core/git-base.js +0 -1
- package/dist/src/core/git-diff.js +0 -1
- package/dist/src/core/git-file-ops.js +0 -1
- package/dist/src/core/git-status.d.ts +13 -0
- package/dist/src/core/git-status.js +32 -2
- package/dist/src/core/git.d.ts +10 -1
- package/dist/src/core/git.js +63 -6
- package/dist/src/core/license-headers.js +0 -1
- package/dist/src/core/lint-cache.js +0 -1
- package/dist/src/core/lint-projection.js +0 -1
- package/dist/src/core/mach-build-artifacts.js +0 -1
- package/dist/src/core/mach-error-hints.js +0 -1
- package/dist/src/core/mach-mozconfig.js +0 -1
- package/dist/src/core/mach-python.js +0 -1
- package/dist/src/core/mach-resource-shim.js +0 -1
- package/dist/src/core/mach.js +0 -1
- package/dist/src/core/manifest-helpers.js +0 -1
- package/dist/src/core/manifest-register.js +0 -1
- package/dist/src/core/manifest-rules.js +0 -1
- package/dist/src/core/manifest-tokenizers.js +0 -1
- package/dist/src/core/marionette-port.d.ts +8 -0
- package/dist/src/core/marionette-port.js +34 -3
- package/dist/src/core/marionette-preflight.js +0 -1
- package/dist/src/core/markdown-table.js +0 -1
- package/dist/src/core/ownership-table.js +2 -9
- package/dist/src/core/parser-fallback.js +0 -1
- package/dist/src/core/patch-apply-fuzz.d.ts +27 -9
- package/dist/src/core/patch-apply-fuzz.js +71 -34
- package/dist/src/core/patch-apply.d.ts +30 -0
- package/dist/src/core/patch-apply.js +101 -18
- package/dist/src/core/patch-artifact-normalize.d.ts +18 -5
- package/dist/src/core/patch-artifact-normalize.js +19 -15
- package/dist/src/core/patch-export-coverage.js +0 -1
- package/dist/src/core/patch-export-metadata.js +0 -1
- package/dist/src/core/patch-export-update.js +2 -3
- package/dist/src/core/patch-export.d.ts +2 -6
- package/dist/src/core/patch-export.js +24 -48
- package/dist/src/core/patch-files.js +0 -1
- package/dist/src/core/patch-identifier-suggest.js +0 -1
- package/dist/src/core/patch-lint-checkjs.js +0 -1
- package/dist/src/core/patch-lint-chrome-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-cross.js +0 -1
- package/dist/src/core/patch-lint-css.js +0 -1
- package/dist/src/core/patch-lint-diff-tag.js +0 -1
- package/dist/src/core/patch-lint-diff.d.ts +2 -1
- package/dist/src/core/patch-lint-diff.js +17 -32
- package/dist/src/core/patch-lint-imports.js +0 -1
- package/dist/src/core/patch-lint-jsdoc.js +0 -1
- package/dist/src/core/patch-lint-observer.js +0 -1
- package/dist/src/core/patch-lint-ownership.js +0 -1
- package/dist/src/core/patch-lint-reexports.js +0 -1
- package/dist/src/core/patch-lint.d.ts +37 -2
- package/dist/src/core/patch-lint.js +47 -34
- package/dist/src/core/patch-lock.js +0 -1
- package/dist/src/core/patch-manifest-consistency.js +9 -1
- package/dist/src/core/patch-manifest-io.d.ts +16 -0
- package/dist/src/core/patch-manifest-io.js +30 -5
- package/dist/src/core/patch-manifest-query.js +18 -10
- package/dist/src/core/patch-manifest-resolve.js +0 -1
- package/dist/src/core/patch-manifest-validate.js +0 -1
- package/dist/src/core/patch-manifest.d.ts +1 -1
- package/dist/src/core/patch-manifest.js +1 -2
- package/dist/src/core/patch-parse.d.ts +59 -0
- package/dist/src/core/patch-parse.js +209 -92
- package/dist/src/core/patch-policy.js +0 -1
- package/dist/src/core/patch-registration-refs.js +16 -26
- package/dist/src/core/patch-source-metadata.js +0 -1
- package/dist/src/core/patch-transform.d.ts +4 -0
- package/dist/src/core/patch-transform.js +30 -38
- package/dist/src/core/rebase-session.js +0 -1
- package/dist/src/core/register-browser-content.js +0 -1
- package/dist/src/core/register-module.js +0 -1
- package/dist/src/core/register-result.js +0 -1
- package/dist/src/core/register-scaffold.js +0 -1
- package/dist/src/core/register-shared-css.js +0 -1
- package/dist/src/core/register-test-manifest.js +0 -1
- package/dist/src/core/register-xpcshell-test.js +0 -1
- package/dist/src/core/shared-ftl.js +0 -1
- package/dist/src/core/signal-critical.js +0 -1
- package/dist/src/core/smoke-patterns.js +0 -1
- package/dist/src/core/state-file.js +0 -1
- package/dist/src/core/status-classify.d.ts +16 -0
- package/dist/src/core/status-classify.js +24 -16
- package/dist/src/core/test-harness-crash.d.ts +64 -7
- package/dist/src/core/test-harness-crash.js +196 -15
- package/dist/src/core/test-harness-output.js +0 -1
- package/dist/src/core/test-path-scope.d.ts +21 -13
- package/dist/src/core/test-path-scope.js +43 -28
- package/dist/src/core/test-stale-check.js +0 -1
- package/dist/src/core/test-stale-symlink.js +0 -1
- package/dist/src/core/test-xpcshell-retry.js +0 -1
- package/dist/src/core/token-coverage.js +0 -1
- package/dist/src/core/token-dark-mode.js +0 -1
- package/dist/src/core/token-docs.js +0 -1
- package/dist/src/core/token-manager.js +0 -1
- package/dist/src/core/token-scaffold.js +0 -1
- package/dist/src/core/token-variant.js +0 -1
- package/dist/src/core/toolchain-preflight.d.ts +31 -10
- package/dist/src/core/toolchain-preflight.js +97 -36
- package/dist/src/core/typecheck-shim.js +13 -1
- package/dist/src/core/typecheck.js +0 -1
- package/dist/src/core/wire-destroy.js +0 -1
- package/dist/src/core/wire-dom-fragment.js +0 -1
- package/dist/src/core/wire-init.js +0 -1
- package/dist/src/core/wire-subscript.js +0 -1
- package/dist/src/core/wire-targets.js +0 -1
- package/dist/src/core/wire-utils.js +0 -1
- package/dist/src/core/xpcshell-appdir.js +1 -4
- package/dist/src/errors/base.d.ts +29 -0
- package/dist/src/errors/base.js +35 -1
- package/dist/src/errors/build.js +0 -1
- package/dist/src/errors/codes.d.ts +6 -0
- package/dist/src/errors/codes.js +6 -1
- package/dist/src/errors/config.js +0 -1
- package/dist/src/errors/download.js +0 -1
- package/dist/src/errors/furnace.d.ts +11 -1
- package/dist/src/errors/furnace.js +6 -5
- package/dist/src/errors/git.js +0 -1
- package/dist/src/errors/patch.d.ts +17 -0
- package/dist/src/errors/patch.js +29 -1
- package/dist/src/errors/rebase.js +0 -1
- package/dist/src/errors/run.js +1 -1
- package/dist/src/index.js +0 -1
- package/dist/src/types/cli.js +0 -1
- package/dist/src/types/commands/index.js +0 -1
- package/dist/src/types/commands/options.d.ts +13 -5
- package/dist/src/types/commands/options.js +0 -1
- package/dist/src/types/commands/patches.d.ts +8 -0
- package/dist/src/types/commands/patches.js +0 -1
- package/dist/src/types/commands/project.d.ts +11 -2
- package/dist/src/types/commands/project.js +0 -1
- package/dist/src/types/config.d.ts +32 -0
- package/dist/src/types/config.js +0 -1
- package/dist/src/types/furnace.d.ts +10 -0
- package/dist/src/types/furnace.js +0 -1
- package/dist/src/types/index.js +0 -1
- package/dist/src/types/typecheck.js +0 -1
- package/dist/src/utils/elapsed.js +0 -1
- package/dist/src/utils/errors.js +0 -1
- package/dist/src/utils/fs.js +37 -5
- package/dist/src/utils/logger.d.ts +7 -0
- package/dist/src/utils/logger.js +85 -3
- package/dist/src/utils/options.d.ts +15 -0
- package/dist/src/utils/options.js +25 -1
- package/dist/src/utils/package-root.js +11 -4
- package/dist/src/utils/parse.js +0 -1
- package/dist/src/utils/paths.js +0 -1
- package/dist/src/utils/platform.js +0 -1
- package/dist/src/utils/process.d.ts +33 -4
- package/dist/src/utils/process.js +183 -28
- package/dist/src/utils/regex.d.ts +13 -3
- package/dist/src/utils/regex.js +53 -4
- package/dist/src/utils/validation.d.ts +0 -12
- package/dist/src/utils/validation.js +13 -26
- package/package.json +8 -7
|
@@ -8,6 +8,16 @@ export interface ExecResult {
|
|
|
8
8
|
stderr: string;
|
|
9
9
|
/** Process exit code */
|
|
10
10
|
exitCode: number;
|
|
11
|
+
/**
|
|
12
|
+
* True when `stdout` was cut off at the 50 MB collector cap. Optional so
|
|
13
|
+
* existing mocks stay valid; absent means "not truncated". Callers that
|
|
14
|
+
* feed the output into safety decisions (e.g. archive-listing scans) must
|
|
15
|
+
* treat `true` as a hard failure — a truncated listing looks exactly like
|
|
16
|
+
* a complete one otherwise.
|
|
17
|
+
*/
|
|
18
|
+
stdoutTruncated?: boolean;
|
|
19
|
+
/** True when `stderr` was cut off at the 50 MB collector cap. See {@link ExecResult.stdoutTruncated}. */
|
|
20
|
+
stderrTruncated?: boolean;
|
|
11
21
|
}
|
|
12
22
|
/**
|
|
13
23
|
* Options for command execution.
|
|
@@ -49,6 +59,13 @@ export interface StreamOptions extends ExecOptions {
|
|
|
49
59
|
* @returns Exit code of the process
|
|
50
60
|
*/
|
|
51
61
|
export declare function execStream(command: string, args: string[], options?: StreamOptions): Promise<number>;
|
|
62
|
+
/**
|
|
63
|
+
* Waits (bounded) for every tracked child process to close. Called by the
|
|
64
|
+
* bin signal handler after forwarding SIGINT/SIGTERM, so the parent stays
|
|
65
|
+
* alive long enough for the grace-then-SIGKILL escalation to actually run.
|
|
66
|
+
* Resolves immediately when no children are active.
|
|
67
|
+
*/
|
|
68
|
+
export declare function waitForActiveChildShutdown(timeoutMs: number): Promise<void>;
|
|
52
69
|
/**
|
|
53
70
|
* Executes a command and inherits stdio (shows output directly).
|
|
54
71
|
*
|
|
@@ -81,8 +98,16 @@ export declare function execInheritCapture(command: string, args: string[], opti
|
|
|
81
98
|
}): Promise<ExecResult>;
|
|
82
99
|
/** Per-line callback for smoke-run stream dispatch. */
|
|
83
100
|
export type SmokeLineCallback = (line: string) => void;
|
|
84
|
-
/**
|
|
85
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Options for {@link execSmokeRun}.
|
|
103
|
+
*
|
|
104
|
+
* Deliberately omits `ExecOptions.timeout`: the smoke run's only deadline is
|
|
105
|
+
* {@link SmokeRunOptions.smokeTimeoutMs}, which signals the whole process
|
|
106
|
+
* group. Inheriting `timeout` used to be a leaky trap — it was accepted by
|
|
107
|
+
* the type but silently ignored, so a caller setting it got no deadline at
|
|
108
|
+
* all.
|
|
109
|
+
*/
|
|
110
|
+
export interface SmokeRunOptions extends Omit<ExecOptions, 'timeout'> {
|
|
86
111
|
/**
|
|
87
112
|
* Hard deadline in milliseconds. When it elapses the child process
|
|
88
113
|
* group is sent SIGTERM and, after `killGraceMs`, SIGKILL. The returned
|
|
@@ -138,8 +163,12 @@ export interface SmokeRunResult extends ExecResult {
|
|
|
138
163
|
* that inherited the group.
|
|
139
164
|
*
|
|
140
165
|
* Windows fallback: `detached: true` does not create an equivalent group
|
|
141
|
-
* there, so we
|
|
142
|
-
*
|
|
166
|
+
* there, so we kill the descendant tree with `taskkill /pid <pid> /T /F`
|
|
167
|
+
* instead. taskkill has no graceful-signal equivalent — both the deadline
|
|
168
|
+
* and the grace re-invocation are forced kills, so Windows children get
|
|
169
|
+
* no shutdown window (best-effort; Windows is untested, see README).
|
|
170
|
+
* `child.kill()` is still sent as a direct-child fallback in case
|
|
171
|
+
* taskkill itself is unavailable.
|
|
143
172
|
*/
|
|
144
173
|
export declare function execSmokeRun(command: string, args: string[], options: SmokeRunOptions): Promise<SmokeRunResult>;
|
|
145
174
|
/**
|
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { constants as osConstants } from 'node:os';
|
|
4
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
5
|
+
import { ExecTimeoutError } from '../errors/base.js';
|
|
4
6
|
// 50 MB cap per stream to prevent OOM — large toolchain builds (e.g. Firefox, Chromium)
|
|
5
7
|
// can easily blow past this, so we truncate rather than let the buffer grow unbounded.
|
|
8
|
+
// Callers for whom truncation is unacceptable (e.g. the archive-safety preflight
|
|
9
|
+
// scanning a full `tar -tvf` listing) must check `wasTruncated` and fail hard.
|
|
6
10
|
const MAX_OUTPUT_SIZE = 50 * 1024 * 1024;
|
|
7
11
|
function createStreamCollector(mirror) {
|
|
8
12
|
const chunks = [];
|
|
9
13
|
let totalLength = 0;
|
|
10
14
|
let truncated = false;
|
|
15
|
+
// A per-stream StringDecoder keeps multibyte UTF-8 characters split across
|
|
16
|
+
// pipe chunk boundaries intact. Per-chunk `Buffer.toString()` turned a
|
|
17
|
+
// straddling character into two U+FFFD replacement chars, which silently
|
|
18
|
+
// broke regex matching over captured output (smoke-run allowlists, mach
|
|
19
|
+
// error hints) whenever a non-ASCII path or localized message landed on a
|
|
20
|
+
// 64 KB chunk boundary.
|
|
21
|
+
const decoder = new StringDecoder('utf8');
|
|
11
22
|
return {
|
|
12
23
|
onData: (data) => {
|
|
13
|
-
const chunk =
|
|
24
|
+
const chunk = decoder.write(data);
|
|
25
|
+
if (chunk.length === 0)
|
|
26
|
+
return;
|
|
14
27
|
mirror?.write(chunk);
|
|
15
28
|
if (truncated)
|
|
16
29
|
return;
|
|
@@ -27,6 +40,7 @@ function createStreamCollector(mirror) {
|
|
|
27
40
|
}
|
|
28
41
|
},
|
|
29
42
|
getText: () => chunks.join(''),
|
|
43
|
+
wasTruncated: () => truncated,
|
|
30
44
|
};
|
|
31
45
|
}
|
|
32
46
|
function buildSignalFromTimeout(timeout) {
|
|
@@ -34,6 +48,19 @@ function buildSignalFromTimeout(timeout) {
|
|
|
34
48
|
return undefined;
|
|
35
49
|
return AbortSignal.timeout(timeout);
|
|
36
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Maps a child-process `error` event to the rejection the caller sees.
|
|
53
|
+
* When the caller set a `timeout` and the error is the AbortError produced
|
|
54
|
+
* by `AbortSignal.timeout` firing, rejects with a typed
|
|
55
|
+
* {@link ExecTimeoutError} naming the command and budget instead of Node's
|
|
56
|
+
* opaque `AbortError: The operation was aborted`.
|
|
57
|
+
*/
|
|
58
|
+
function toExecRejection(error, command, args, timeout) {
|
|
59
|
+
if (timeout !== undefined && error.name === 'AbortError') {
|
|
60
|
+
return new ExecTimeoutError(command, args, timeout, error);
|
|
61
|
+
}
|
|
62
|
+
return error;
|
|
63
|
+
}
|
|
37
64
|
function exitCodeFromClose(code, signal) {
|
|
38
65
|
if (code !== null) {
|
|
39
66
|
return code;
|
|
@@ -66,13 +93,15 @@ export async function exec(command, args, options = {}) {
|
|
|
66
93
|
child.stdout.on('data', out.onData);
|
|
67
94
|
child.stderr.on('data', err.onData);
|
|
68
95
|
child.on('error', (error) => {
|
|
69
|
-
reject(error);
|
|
96
|
+
reject(toExecRejection(error, command, args, options.timeout));
|
|
70
97
|
});
|
|
71
98
|
child.on('close', (code, signal) => {
|
|
72
99
|
resolve({
|
|
73
100
|
stdout: out.getText(),
|
|
74
101
|
stderr: err.getText(),
|
|
75
102
|
exitCode: exitCodeFromClose(code, signal),
|
|
103
|
+
stdoutTruncated: out.wasTruncated(),
|
|
104
|
+
stderrTruncated: err.wasTruncated(),
|
|
76
105
|
});
|
|
77
106
|
});
|
|
78
107
|
});
|
|
@@ -92,20 +121,66 @@ export async function execStream(command, args, options = {}) {
|
|
|
92
121
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
93
122
|
signal: buildSignalFromTimeout(options.timeout),
|
|
94
123
|
});
|
|
124
|
+
const stdoutDecoder = new StringDecoder('utf8');
|
|
125
|
+
const stderrDecoder = new StringDecoder('utf8');
|
|
95
126
|
child.stdout.on('data', (data) => {
|
|
96
|
-
|
|
127
|
+
const chunk = stdoutDecoder.write(data);
|
|
128
|
+
if (chunk.length > 0)
|
|
129
|
+
options.onStdout?.(chunk);
|
|
97
130
|
});
|
|
98
131
|
child.stderr.on('data', (data) => {
|
|
99
|
-
|
|
132
|
+
const chunk = stderrDecoder.write(data);
|
|
133
|
+
if (chunk.length > 0)
|
|
134
|
+
options.onStderr?.(chunk);
|
|
100
135
|
});
|
|
101
136
|
child.on('error', (error) => {
|
|
102
|
-
reject(error);
|
|
137
|
+
reject(toExecRejection(error, command, args, options.timeout));
|
|
103
138
|
});
|
|
104
139
|
child.on('close', (code, signal) => {
|
|
105
140
|
resolve(exitCodeFromClose(code, signal));
|
|
106
141
|
});
|
|
107
142
|
});
|
|
108
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Close-promises of children whose shutdown the CLI must wait for when a
|
|
146
|
+
* termination signal arrives. The bin signal handler used to `process.exit`
|
|
147
|
+
* within a microtask of the first Ctrl+C, which made `execInherit`'s
|
|
148
|
+
* documented SIGTERM → grace → SIGKILL escalation unreachable: the parent
|
|
149
|
+
* was gone before the 1500 ms grace timer could fire, so a hung Firefox
|
|
150
|
+
* was orphaned forever instead of being SIGKILLed and a healthy one lost
|
|
151
|
+
* its AsyncShutdown flush window.
|
|
152
|
+
*/
|
|
153
|
+
const activeChildClosures = new Set();
|
|
154
|
+
function trackChildClosure() {
|
|
155
|
+
let resolveClosed;
|
|
156
|
+
const closed = new Promise((resolve) => {
|
|
157
|
+
resolveClosed = resolve;
|
|
158
|
+
});
|
|
159
|
+
activeChildClosures.add(closed);
|
|
160
|
+
return {
|
|
161
|
+
settle: () => {
|
|
162
|
+
resolveClosed?.();
|
|
163
|
+
activeChildClosures.delete(closed);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Waits (bounded) for every tracked child process to close. Called by the
|
|
169
|
+
* bin signal handler after forwarding SIGINT/SIGTERM, so the parent stays
|
|
170
|
+
* alive long enough for the grace-then-SIGKILL escalation to actually run.
|
|
171
|
+
* Resolves immediately when no children are active.
|
|
172
|
+
*/
|
|
173
|
+
export async function waitForActiveChildShutdown(timeoutMs) {
|
|
174
|
+
if (activeChildClosures.size === 0)
|
|
175
|
+
return;
|
|
176
|
+
await Promise.race([
|
|
177
|
+
Promise.allSettled([...activeChildClosures]),
|
|
178
|
+
new Promise((resolve) => {
|
|
179
|
+
const timer = setTimeout(resolve, timeoutMs);
|
|
180
|
+
timer.unref();
|
|
181
|
+
}),
|
|
182
|
+
]);
|
|
183
|
+
}
|
|
109
184
|
/**
|
|
110
185
|
* Executes a command and inherits stdio (shows output directly).
|
|
111
186
|
*
|
|
@@ -132,12 +207,15 @@ export async function execInherit(command, args, options = {}) {
|
|
|
132
207
|
});
|
|
133
208
|
const graceMs = options.shutdownGraceMs ?? 1500;
|
|
134
209
|
const { dispose } = installGracefulShutdownForwarder(child, graceMs);
|
|
210
|
+
const closure = trackChildClosure();
|
|
135
211
|
child.on('error', (error) => {
|
|
136
212
|
dispose();
|
|
137
|
-
|
|
213
|
+
closure.settle();
|
|
214
|
+
reject(toExecRejection(error, command, args, options.timeout));
|
|
138
215
|
});
|
|
139
216
|
child.on('close', (code, signal) => {
|
|
140
217
|
dispose();
|
|
218
|
+
closure.settle();
|
|
141
219
|
resolve(exitCodeFromClose(code, signal));
|
|
142
220
|
});
|
|
143
221
|
});
|
|
@@ -220,16 +298,21 @@ export async function execInheritCapture(command, args, options = {}) {
|
|
|
220
298
|
child.stderr.on('data', err.onData);
|
|
221
299
|
const graceMs = options.shutdownGraceMs ?? 1500;
|
|
222
300
|
const { dispose } = installGracefulShutdownForwarder(child, graceMs);
|
|
301
|
+
const closure = trackChildClosure();
|
|
223
302
|
child.on('error', (error) => {
|
|
224
303
|
dispose();
|
|
225
|
-
|
|
304
|
+
closure.settle();
|
|
305
|
+
reject(toExecRejection(error, command, args, options.timeout));
|
|
226
306
|
});
|
|
227
307
|
child.on('close', (code, signal) => {
|
|
228
308
|
dispose();
|
|
309
|
+
closure.settle();
|
|
229
310
|
resolve({
|
|
230
311
|
stdout: out.getText(),
|
|
231
312
|
stderr: err.getText(),
|
|
232
313
|
exitCode: exitCodeFromClose(code, signal),
|
|
314
|
+
stdoutTruncated: out.wasTruncated(),
|
|
315
|
+
stderrTruncated: err.wasTruncated(),
|
|
233
316
|
});
|
|
234
317
|
});
|
|
235
318
|
});
|
|
@@ -248,8 +331,12 @@ export async function execInheritCapture(command, args, options = {}) {
|
|
|
248
331
|
* that inherited the group.
|
|
249
332
|
*
|
|
250
333
|
* Windows fallback: `detached: true` does not create an equivalent group
|
|
251
|
-
* there, so we
|
|
252
|
-
*
|
|
334
|
+
* there, so we kill the descendant tree with `taskkill /pid <pid> /T /F`
|
|
335
|
+
* instead. taskkill has no graceful-signal equivalent — both the deadline
|
|
336
|
+
* and the grace re-invocation are forced kills, so Windows children get
|
|
337
|
+
* no shutdown window (best-effort; Windows is untested, see README).
|
|
338
|
+
* `child.kill()` is still sent as a direct-child fallback in case
|
|
339
|
+
* taskkill itself is unavailable.
|
|
253
340
|
*/
|
|
254
341
|
export async function execSmokeRun(command, args, options) {
|
|
255
342
|
return new Promise((resolve, reject) => {
|
|
@@ -261,25 +348,28 @@ export async function execSmokeRun(command, args, options) {
|
|
|
261
348
|
// A new process group on POSIX lets us signal the whole descendant
|
|
262
349
|
// tree at once, which is essential for mach → python → firefox →
|
|
263
350
|
// content-process chains. On Windows spawn ignores this for our
|
|
264
|
-
// purposes and we fall back to
|
|
351
|
+
// purposes and we fall back to a taskkill /T tree kill below.
|
|
265
352
|
detached: usesProcessGroup,
|
|
266
353
|
});
|
|
267
354
|
const out = createStreamCollector(options.mirror?.stdout);
|
|
268
355
|
const err = createStreamCollector(options.mirror?.stderr);
|
|
356
|
+
const stdoutLineDecoder = new StringDecoder('utf8');
|
|
357
|
+
const stderrLineDecoder = new StringDecoder('utf8');
|
|
269
358
|
let stdoutBuffer = '';
|
|
270
359
|
let stderrBuffer = '';
|
|
271
360
|
child.stdout.on('data', (data) => {
|
|
272
361
|
out.onData(data);
|
|
273
|
-
stdoutBuffer +=
|
|
362
|
+
stdoutBuffer += stdoutLineDecoder.write(data);
|
|
274
363
|
stdoutBuffer = dispatchCompleteLines(stdoutBuffer, options.onStdoutLine);
|
|
275
364
|
});
|
|
276
365
|
child.stderr.on('data', (data) => {
|
|
277
366
|
err.onData(data);
|
|
278
|
-
stderrBuffer +=
|
|
367
|
+
stderrBuffer += stderrLineDecoder.write(data);
|
|
279
368
|
stderrBuffer = dispatchCompleteLines(stderrBuffer, options.onStderrLine);
|
|
280
369
|
});
|
|
281
370
|
let timedOut = false;
|
|
282
371
|
let graceTimer;
|
|
372
|
+
let signalGraceTimer;
|
|
283
373
|
const signalChildGroup = (signal) => {
|
|
284
374
|
if (child.exitCode !== null || child.signalCode !== null)
|
|
285
375
|
return;
|
|
@@ -294,6 +384,15 @@ export async function execSmokeRun(command, args, options) {
|
|
|
294
384
|
process.kill(-targetPid, signal);
|
|
295
385
|
}
|
|
296
386
|
else {
|
|
387
|
+
// No process group on Windows — taskkill /T walks the descendant
|
|
388
|
+
// tree instead. Always forced (/F): there is no SIGTERM analogue,
|
|
389
|
+
// so the grace window only exists on POSIX.
|
|
390
|
+
spawn('taskkill', ['/pid', String(targetPid), '/T', '/F'], {
|
|
391
|
+
stdio: 'ignore',
|
|
392
|
+
}).on('error', () => {
|
|
393
|
+
// taskkill unavailable — nothing more we can do beyond the
|
|
394
|
+
// direct-child kill below.
|
|
395
|
+
});
|
|
297
396
|
child.kill(signal);
|
|
298
397
|
}
|
|
299
398
|
}
|
|
@@ -310,16 +409,52 @@ export async function execSmokeRun(command, args, options) {
|
|
|
310
409
|
graceTimer.unref();
|
|
311
410
|
}, options.smokeTimeoutMs);
|
|
312
411
|
deadlineTimer.unref();
|
|
412
|
+
// Forward parent SIGINT/SIGTERM to the whole child process group. The
|
|
413
|
+
// smoke child is a group leader (detached), so it does NOT receive the
|
|
414
|
+
// terminal's Ctrl+C SIGINT — without this forwarder, the parent exits
|
|
415
|
+
// and the entire mach → firefox tree is orphaned. Second signal (or
|
|
416
|
+
// grace expiry) escalates to a group SIGKILL.
|
|
417
|
+
const forwardedSignals = new Set();
|
|
418
|
+
const onParentSignal = (signal) => {
|
|
419
|
+
if (forwardedSignals.has(signal)) {
|
|
420
|
+
signalChildGroup('SIGKILL');
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
forwardedSignals.add(signal);
|
|
424
|
+
signalChildGroup('SIGTERM');
|
|
425
|
+
signalGraceTimer = setTimeout(() => {
|
|
426
|
+
signalChildGroup('SIGKILL');
|
|
427
|
+
}, options.killGraceMs ?? 10000);
|
|
428
|
+
signalGraceTimer.unref();
|
|
429
|
+
};
|
|
430
|
+
const onSigint = () => {
|
|
431
|
+
onParentSignal('SIGINT');
|
|
432
|
+
};
|
|
433
|
+
const onSigterm = () => {
|
|
434
|
+
onParentSignal('SIGTERM');
|
|
435
|
+
};
|
|
436
|
+
process.on('SIGINT', onSigint);
|
|
437
|
+
process.on('SIGTERM', onSigterm);
|
|
438
|
+
const closure = trackChildClosure();
|
|
439
|
+
const cleanupSignalForwarding = () => {
|
|
440
|
+
process.off('SIGINT', onSigint);
|
|
441
|
+
process.off('SIGTERM', onSigterm);
|
|
442
|
+
if (signalGraceTimer)
|
|
443
|
+
clearTimeout(signalGraceTimer);
|
|
444
|
+
closure.settle();
|
|
445
|
+
};
|
|
313
446
|
child.on('error', (error) => {
|
|
314
447
|
clearTimeout(deadlineTimer);
|
|
315
448
|
if (graceTimer)
|
|
316
449
|
clearTimeout(graceTimer);
|
|
450
|
+
cleanupSignalForwarding();
|
|
317
451
|
reject(error);
|
|
318
452
|
});
|
|
319
453
|
child.on('close', (code, signal) => {
|
|
320
454
|
clearTimeout(deadlineTimer);
|
|
321
455
|
if (graceTimer)
|
|
322
456
|
clearTimeout(graceTimer);
|
|
457
|
+
cleanupSignalForwarding();
|
|
323
458
|
// Flush any remaining partial line (child ended without a trailing newline).
|
|
324
459
|
if (stdoutBuffer.length > 0)
|
|
325
460
|
options.onStdoutLine?.(stdoutBuffer);
|
|
@@ -335,24 +470,45 @@ export async function execSmokeRun(command, args, options) {
|
|
|
335
470
|
});
|
|
336
471
|
}
|
|
337
472
|
/**
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
473
|
+
* Cap on the partial-line tail kept between chunks. A child emitting one
|
|
474
|
+
* enormous line with no terminator (minified JS dumped to stderr, a raw
|
|
475
|
+
* binary blob) previously grew the buffer without bound — the 50 MB cap
|
|
476
|
+
* only guarded the collector, not these line buffers. When the tail
|
|
477
|
+
* exceeds the cap it is dispatched as a synthetic (oversized) line so the
|
|
478
|
+
* matchers still get to see its content, then the buffer resets.
|
|
479
|
+
*/
|
|
480
|
+
const MAX_PARTIAL_LINE_SIZE = 1024 * 1024;
|
|
481
|
+
/**
|
|
482
|
+
* Drains complete lines from `buffer`, dispatching each to `cb`. Treats
|
|
483
|
+
* `\n`, `\r\n`, and lone `\r` as line terminators — the lone-`\r` case
|
|
484
|
+
* matters for progress-bar style output (mach, cargo) that repaints a line
|
|
485
|
+
* with carriage returns and never sends a newline; before `\r` counted as a
|
|
486
|
+
* terminator those updates accumulated indefinitely. A single trailing `\r`
|
|
487
|
+
* is held back since it may be the first half of a `\r\n` pair split across
|
|
488
|
+
* chunks. Returns the remaining partial line — callers keep accumulating
|
|
489
|
+
* into it.
|
|
341
490
|
*/
|
|
342
491
|
function dispatchCompleteLines(buffer, cb) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
492
|
+
let searchFrom = 0;
|
|
493
|
+
for (;;) {
|
|
494
|
+
const nl = buffer.indexOf('\n', searchFrom);
|
|
495
|
+
const cr = buffer.indexOf('\r', searchFrom);
|
|
496
|
+
const idx = nl === -1 ? cr : cr === -1 ? nl : Math.min(nl, cr);
|
|
497
|
+
if (idx === -1)
|
|
498
|
+
break;
|
|
499
|
+
if (buffer[idx] === '\r' && idx === buffer.length - 1) {
|
|
500
|
+
// Possible first half of a chunk-split \r\n — wait for the next chunk.
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
const line = buffer.slice(0, idx);
|
|
504
|
+
const terminatorLength = buffer[idx] === '\r' && buffer[idx + 1] === '\n' ? 2 : 1;
|
|
505
|
+
buffer = buffer.slice(idx + terminatorLength);
|
|
506
|
+
searchFrom = 0;
|
|
507
|
+
cb?.(line);
|
|
350
508
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
buffer = buffer.slice(idx + 1);
|
|
355
|
-
cb(line);
|
|
509
|
+
if (buffer.length > MAX_PARTIAL_LINE_SIZE) {
|
|
510
|
+
cb?.(buffer);
|
|
511
|
+
return '';
|
|
356
512
|
}
|
|
357
513
|
return buffer;
|
|
358
514
|
}
|
|
@@ -388,4 +544,3 @@ export async function executableExists(name) {
|
|
|
388
544
|
const path = await findExecutable(name);
|
|
389
545
|
return path !== undefined;
|
|
390
546
|
}
|
|
391
|
-
//# sourceMappingURL=process.js.map
|
|
@@ -7,9 +7,19 @@ export declare function escapeRegex(str: string): string;
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function hasRawCssColors(content: string): boolean;
|
|
9
9
|
/**
|
|
10
|
-
* Strips JS single-line and multi-line comments from source code, replacing
|
|
11
|
-
* with spaces
|
|
12
|
-
*
|
|
10
|
+
* Strips JS single-line and multi-line comments from source code, replacing
|
|
11
|
+
* comment bytes with spaces (newlines inside block comments are kept) so
|
|
12
|
+
* both character offsets AND line numbers are preserved. String literals
|
|
13
|
+
* are preserved intact.
|
|
14
|
+
*
|
|
15
|
+
* Uses acorn's tokenizer, which understands regex literals — the previous
|
|
16
|
+
* pure-regex implementation did not model them, so two adjacent slashes
|
|
17
|
+
* inside a regex (e.g. the tail of `/https?:\/\//`) were treated as a `//`
|
|
18
|
+
* line comment and the rest of the line was blanked, corrupting whatever
|
|
19
|
+
* lint pass consumed the stripped text. Firefox chrome sources contain
|
|
20
|
+
* preprocessor directives (`#ifdef`) and other constructs acorn cannot
|
|
21
|
+
* tokenize; those fall back to the legacy regex strip, which handles
|
|
22
|
+
* strings but NOT regex literals (documented limitation of the fallback).
|
|
13
23
|
*/
|
|
14
24
|
export declare function stripJsComments(source: string): string;
|
|
15
25
|
/**
|
package/dist/src/utils/regex.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
import { tokenizer as acornTokenizer, tokTypes as acornTokTypes } from 'acorn';
|
|
2
3
|
/**
|
|
3
4
|
* Escapes special regex characters in a string.
|
|
4
5
|
*/
|
|
@@ -22,11 +23,60 @@ export function hasRawCssColors(content) {
|
|
|
22
23
|
return CSS_HEX_COLOR.test(content) || CSS_RGB_COLOR.test(content) || CSS_HSL_COLOR.test(content);
|
|
23
24
|
}
|
|
24
25
|
/**
|
|
25
|
-
* Strips JS single-line and multi-line comments from source code, replacing
|
|
26
|
-
* with spaces
|
|
27
|
-
*
|
|
26
|
+
* Strips JS single-line and multi-line comments from source code, replacing
|
|
27
|
+
* comment bytes with spaces (newlines inside block comments are kept) so
|
|
28
|
+
* both character offsets AND line numbers are preserved. String literals
|
|
29
|
+
* are preserved intact.
|
|
30
|
+
*
|
|
31
|
+
* Uses acorn's tokenizer, which understands regex literals — the previous
|
|
32
|
+
* pure-regex implementation did not model them, so two adjacent slashes
|
|
33
|
+
* inside a regex (e.g. the tail of `/https?:\/\//`) were treated as a `//`
|
|
34
|
+
* line comment and the rest of the line was blanked, corrupting whatever
|
|
35
|
+
* lint pass consumed the stripped text. Firefox chrome sources contain
|
|
36
|
+
* preprocessor directives (`#ifdef`) and other constructs acorn cannot
|
|
37
|
+
* tokenize; those fall back to the legacy regex strip, which handles
|
|
38
|
+
* strings but NOT regex literals (documented limitation of the fallback).
|
|
28
39
|
*/
|
|
29
40
|
export function stripJsComments(source) {
|
|
41
|
+
const comments = [];
|
|
42
|
+
try {
|
|
43
|
+
const t = acornTokenizer(source, {
|
|
44
|
+
ecmaVersion: 'latest',
|
|
45
|
+
sourceType: 'module',
|
|
46
|
+
allowHashBang: true,
|
|
47
|
+
onComment: (_isBlock, _text, start, end) => {
|
|
48
|
+
comments.push({ start, end });
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
// Drive the tokenizer to EOF; we only care about onComment callbacks.
|
|
52
|
+
for (;;) {
|
|
53
|
+
const token = t.getToken();
|
|
54
|
+
if (token.type === acornTokTypes.eof)
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return stripJsCommentsLegacy(source);
|
|
60
|
+
}
|
|
61
|
+
if (comments.length === 0)
|
|
62
|
+
return source;
|
|
63
|
+
const chars = source.split('');
|
|
64
|
+
for (const { start, end } of comments) {
|
|
65
|
+
for (let i = start; i < end; i++) {
|
|
66
|
+
if (chars[i] !== '\n' && chars[i] !== '\r') {
|
|
67
|
+
chars[i] = ' ';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return chars.join('');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Legacy pure-regex comment strip, used only when acorn cannot tokenize
|
|
75
|
+
* the source (preprocessor directives, syntax errors). Strings survive;
|
|
76
|
+
* regex literals are NOT modeled, so `//` inside a regex blanks the rest
|
|
77
|
+
* of the line.
|
|
78
|
+
*/
|
|
79
|
+
function stripJsCommentsLegacy(source) {
|
|
30
80
|
return source.replace(/\/\/.*$|\/\*[\s\S]*?\*\/|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`/gm, (match) => (match.startsWith('/') ? ' '.repeat(match.length) : match));
|
|
31
81
|
}
|
|
32
82
|
/**
|
|
@@ -37,4 +87,3 @@ export function countRawCssColors(content) {
|
|
|
37
87
|
(content.match(CSS_RGB_COLOR_G)?.length ?? 0) +
|
|
38
88
|
(content.match(CSS_HSL_COLOR_G)?.length ?? 0));
|
|
39
89
|
}
|
|
40
|
-
//# sourceMappingURL=regex.js.map
|
|
@@ -52,18 +52,6 @@ export declare function isObject(value: unknown): value is Record<string, unknow
|
|
|
52
52
|
* @returns True if value is an array
|
|
53
53
|
*/
|
|
54
54
|
export declare function isArray(value: unknown): value is unknown[];
|
|
55
|
-
/**
|
|
56
|
-
* Asserts that a value is a string or throws.
|
|
57
|
-
* @param value - Value to check
|
|
58
|
-
* @param name - Name of the field for error messages
|
|
59
|
-
*/
|
|
60
|
-
export declare function assertString(value: unknown, name: string): asserts value is string;
|
|
61
|
-
/**
|
|
62
|
-
* Asserts that a value is a non-null object or throws.
|
|
63
|
-
* @param value - Value to check
|
|
64
|
-
* @param name - Name of the field for error messages
|
|
65
|
-
*/
|
|
66
|
-
export declare function assertObject(value: unknown, name: string): asserts value is Record<string, unknown>;
|
|
67
55
|
/**
|
|
68
56
|
* Validates a Firefox version string.
|
|
69
57
|
* Accepts formats like "140.9.0", "140.9.1", "140.9.0esr", "147.0b1"
|
|
@@ -18,7 +18,10 @@ export function isString(value) {
|
|
|
18
18
|
* @returns True if value is a finite number
|
|
19
19
|
*/
|
|
20
20
|
export function isNumber(value) {
|
|
21
|
-
|
|
21
|
+
// Number.isFinite matches the documented contract: NaN AND ±Infinity are
|
|
22
|
+
// excluded (a JSON config field of 1e999 parses to Infinity and used to
|
|
23
|
+
// validate as a legal number).
|
|
24
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Checks whether a value is a positive integer (greater than zero).
|
|
@@ -75,26 +78,6 @@ export function isObject(value) {
|
|
|
75
78
|
export function isArray(value) {
|
|
76
79
|
return Array.isArray(value);
|
|
77
80
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Asserts that a value is a string or throws.
|
|
80
|
-
* @param value - Value to check
|
|
81
|
-
* @param name - Name of the field for error messages
|
|
82
|
-
*/
|
|
83
|
-
export function assertString(value, name) {
|
|
84
|
-
if (!isString(value)) {
|
|
85
|
-
throw new InvalidArgumentError(`Expected ${name} to be a string, got ${typeof value}`, name);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Asserts that a value is a non-null object or throws.
|
|
90
|
-
* @param value - Value to check
|
|
91
|
-
* @param name - Name of the field for error messages
|
|
92
|
-
*/
|
|
93
|
-
export function assertObject(value, name) {
|
|
94
|
-
if (!isObject(value)) {
|
|
95
|
-
throw new InvalidArgumentError(`Expected ${name} to be an object, got ${typeof value}`, name);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
81
|
/**
|
|
99
82
|
* Validates a Firefox version string.
|
|
100
83
|
* Accepts formats like "140.9.0", "140.9.1", "140.9.0esr", "147.0b1"
|
|
@@ -232,11 +215,16 @@ export function validateTokenName(name) {
|
|
|
232
215
|
if (/[\x00-\x1f\x7f]/.test(ident)) {
|
|
233
216
|
return `Token name must not contain control characters: "${name}"`;
|
|
234
217
|
}
|
|
235
|
-
if (ident.includes('*/')) {
|
|
236
|
-
|
|
218
|
+
if (ident.includes('*/') || ident.includes('/*')) {
|
|
219
|
+
// `/*` is the worse of the two by this function's own standard: an
|
|
220
|
+
// ident containing it opens an UNTERMINATED comment in tokens.css
|
|
221
|
+
// that swallows every following declaration.
|
|
222
|
+
return `Token name must not contain "/*" or "*/" (would break CSS comments): "${name}"`;
|
|
237
223
|
}
|
|
238
|
-
// Reject characters that would break CSS declaration syntax
|
|
239
|
-
|
|
224
|
+
// Reject characters that would break CSS declaration syntax. Quotes are
|
|
225
|
+
// included: a quote character in an ident opens a CSS string that eats
|
|
226
|
+
// the rest of the declaration block.
|
|
227
|
+
if (/[{}();!'"]/.test(ident)) {
|
|
240
228
|
return `Token name contains characters that would corrupt CSS syntax: "${name}"`;
|
|
241
229
|
}
|
|
242
230
|
return undefined;
|
|
@@ -270,4 +258,3 @@ export function validatePatchName(name) {
|
|
|
270
258
|
return 'Name can only contain letters, numbers, hyphens, underscores, and spaces';
|
|
271
259
|
return undefined;
|
|
272
260
|
}
|
|
273
|
-
//# sourceMappingURL=validation.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hominis/fireforge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "FireForge — a build tool for customizing Firefox",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"fireforge": "tsx bin/fireforge.ts",
|
|
21
21
|
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
22
22
|
"clean": "node ./scripts/clean.mjs",
|
|
23
|
-
"lint": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts",
|
|
24
|
-
"lint:ci": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts --max-warnings 0",
|
|
25
|
-
"lint:fix": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts --fix",
|
|
23
|
+
"lint": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts vitest.pack.config.ts",
|
|
24
|
+
"lint:ci": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts vitest.pack.config.ts --max-warnings 0",
|
|
25
|
+
"lint:fix": "eslint src/ bin/ scripts/ eslint.config.js vitest.config.ts vitest.pack.config.ts --fix",
|
|
26
26
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
27
27
|
"deadcode:check": "knip",
|
|
28
28
|
"cycles:check": "dpdm --no-output --no-tree --no-warning --exit-code circular:1 src/index.ts bin/fireforge.ts",
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"test:coverage": "vitest run --coverage && node ./scripts/check-coverage-thresholds.mjs",
|
|
33
33
|
"test:firefox-full": "node ./scripts/run-full-firefox-integration.mjs",
|
|
34
34
|
"whitespace:check": "node ./scripts/check-worktree-whitespace.mjs",
|
|
35
|
-
"pack:verify": "vitest run
|
|
35
|
+
"pack:verify": "vitest run --config vitest.pack.config.ts",
|
|
36
36
|
"pack:dry-run": "npm pack --dry-run --json --silent",
|
|
37
|
-
"format": "prettier --write --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\"",
|
|
38
|
-
"format:check": "prettier --check --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\"",
|
|
37
|
+
"format": "prettier --write --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"vitest.pack.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\" \"docs/**/*.md\" \"knip.json\" \"docs/**/*.md\" \"knip.json\"",
|
|
38
|
+
"format:check": "prettier --check --ignore-unknown \"src/**/*.ts\" \"bin/**/*.ts\" \"scripts/**/*.mjs\" \"package.json\" \"eslint.config.js\" \"vitest.config.ts\" \"vitest.pack.config.ts\" \"tsconfig.json\" \"tsconfig.build.json\" \".lintstagedrc.json\" \".prettierignore\" \".prettierrc\" \".gitignore\" \"README.md\" \"CHANGELOG.md\" \"docs/**/*.md\" \"knip.json\" \"docs/**/*.md\" \"knip.json\"",
|
|
39
39
|
"prepare": "node -e \"import('./scripts/prepare.mjs').catch((error) => { if (error && typeof error === 'object' && 'code' in error && error.code === 'ERR_MODULE_NOT_FOUND') process.exit(0); throw error; })\"",
|
|
40
40
|
"prepack": "npm run build",
|
|
41
41
|
"release:check": "npm run format:check && npm run whitespace:check && npm run lint:ci && npm run typecheck && npm run deadcode:check && npm run cycles:check && npm run test:coverage && npm run pack:verify && npm run pack:dry-run",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"acorn": "^8.14.0",
|
|
58
58
|
"commander": "^15.0.0",
|
|
59
59
|
"estree-walker": "^3.0.3",
|
|
60
|
+
"jsonc-parser": "^3.3.1",
|
|
60
61
|
"magic-string": "^0.30.17",
|
|
61
62
|
"picocolors": "^1.1.0"
|
|
62
63
|
},
|