@hominis/fireforge 0.34.2 → 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 +48 -0
- package/README.md +43 -5
- package/dist/bin/fireforge.d.ts +4 -0
- package/dist/bin/fireforge.js +25 -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 +17 -3
- package/dist/src/commands/config.js +30 -10
- package/dist/src/commands/discard.js +24 -5
- 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 +51 -15
- 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 +18 -6
- 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 +95 -56
- 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 +21 -7
- 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 +67 -8
- package/dist/src/core/firefox-download.js +0 -1
- package/dist/src/core/firefox-extract.d.ts +21 -1
- package/dist/src/core/firefox-extract.js +178 -9
- 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 +65 -8
- package/dist/src/core/license-headers.d.ts +16 -0
- package/dist/src/core/license-headers.js +19 -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 +27 -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 +152 -39
- 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 +48 -24
- 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 +37 -12
- package/dist/src/core/patch-manifest-query.d.ts +6 -0
- package/dist/src/core/patch-manifest-query.js +27 -12
- 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 +62 -0
- package/dist/src/core/test-path-scope.js +148 -0
- 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 +85 -0
- package/dist/src/core/toolchain-preflight.js +256 -0
- package/dist/src/core/typecheck-shim.d.ts +4 -1
- package/dist/src/core/typecheck-shim.js +61 -18
- 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 +2 -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 +20 -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
|
@@ -13,6 +13,7 @@ import { resolveFurnaceMarkerComment } from './furnace-marker.js';
|
|
|
13
13
|
import { recordFurnaceRollbackFailure } from './furnace-operation.js';
|
|
14
14
|
import { addJarMnEntries, removeCustomElementRegistration, removeJarMnEntries, } from './furnace-registration.js';
|
|
15
15
|
import { createRollbackJournal, restoreRollbackJournalOrThrow, snapshotFile, } from './furnace-rollback.js';
|
|
16
|
+
import { blockingStepErrors, hasBlockingStepErrors } from './furnace-step-errors.js';
|
|
16
17
|
import { runPostApplyConsistencyChecks } from './furnace-validate-registration.js';
|
|
17
18
|
export { computeComponentChecksums, extractComponentChecksums, hasComponentChanged, hasCustomEngineDrift, hasOverrideEngineDrift, prefixChecksums, } from './furnace-apply-helpers.js';
|
|
18
19
|
function addMissingComponentError(result, name, directoryPath) {
|
|
@@ -29,7 +30,9 @@ function buildOverrideUndeployActions(name, config, engineDir, deletedFiles, ftl
|
|
|
29
30
|
description: `Restore engine/${fileName.endsWith('.ftl') ? `${ftlDir}/${fileName}` : `${config.basePath}/${fileName}`} to Firefox baseline`,
|
|
30
31
|
}));
|
|
31
32
|
}
|
|
32
|
-
async function applyOverrideBatch(
|
|
33
|
+
async function applyOverrideBatch(ctx) {
|
|
34
|
+
const { config, furnacePaths, state, engineDir, ftlDir, dryRun } = ctx;
|
|
35
|
+
const { result, allActions, newChecksums, rollbackJournal, componentName } = ctx;
|
|
33
36
|
const overrideEntries = Object.entries(config.overrides).filter(([name]) => !componentName || name === componentName);
|
|
34
37
|
const totalOverrides = overrideEntries.length;
|
|
35
38
|
let overrideIndex = 0;
|
|
@@ -162,7 +165,9 @@ async function reconcileCustomRegistrationAfterUndeploy(engineDir, name, config,
|
|
|
162
165
|
filesAffected.push(CUSTOM_ELEMENTS_JS);
|
|
163
166
|
}
|
|
164
167
|
}
|
|
165
|
-
async function applyCustomBatch(
|
|
168
|
+
async function applyCustomBatch(ctx, root, markerComment) {
|
|
169
|
+
const { config, furnacePaths, state, engineDir, ftlDir, dryRun } = ctx;
|
|
170
|
+
const { result, allActions, newChecksums, rollbackJournal, componentName } = ctx;
|
|
166
171
|
const allKnown = new Set([
|
|
167
172
|
...config.stock,
|
|
168
173
|
...Object.keys(config.overrides),
|
|
@@ -251,7 +256,7 @@ async function applyCustomBatch(root, config, furnacePaths, state, engineDir, ft
|
|
|
251
256
|
if (dryRun && actions) {
|
|
252
257
|
allActions.push(...actions);
|
|
253
258
|
}
|
|
254
|
-
if (!dryRun && deletedFiles.length > 0 && stepErrors.length === 0) {
|
|
259
|
+
if (!dryRun && deletedFiles.length > 0 && blockingStepErrors(stepErrors).length === 0) {
|
|
255
260
|
await reconcileCustomRegistrationAfterUndeploy(engineDir, name, customConfig, deletedFiles, currentChecksums, rollbackJournal, filesAffectedTotal);
|
|
256
261
|
}
|
|
257
262
|
filesAffectedTotal.push(...filesAffected);
|
|
@@ -261,9 +266,11 @@ async function applyCustomBatch(root, config, furnacePaths, state, engineDir, ft
|
|
|
261
266
|
filesAffected: filesAffectedTotal,
|
|
262
267
|
...(stepErrors.length > 0 ? { stepErrors } : {}),
|
|
263
268
|
});
|
|
264
|
-
// Only store checksums when the component applied without
|
|
265
|
-
// so that partially failed components are re-applied on
|
|
266
|
-
|
|
269
|
+
// Only store checksums when the component applied without BLOCKING
|
|
270
|
+
// step errors, so that partially failed components are re-applied on
|
|
271
|
+
// the next run. Advisory errors (FTL degradation) still persist —
|
|
272
|
+
// re-applying cannot fix a fork that has no locale jar.mn.
|
|
273
|
+
if (!dryRun && blockingStepErrors(stepErrors).length === 0) {
|
|
267
274
|
Object.assign(newChecksums, prefixChecksums(currentChecksums, 'custom', name));
|
|
268
275
|
}
|
|
269
276
|
}
|
|
@@ -338,16 +345,38 @@ export async function applyAllComponents(root, dryRun = false, options) {
|
|
|
338
345
|
throw new FurnaceError(`Component "${componentName}" not found in furnace.json. Run "fireforge furnace list" to see registered components.`, componentName);
|
|
339
346
|
}
|
|
340
347
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
348
|
+
const batchContext = {
|
|
349
|
+
config,
|
|
350
|
+
furnacePaths,
|
|
351
|
+
state,
|
|
352
|
+
engineDir,
|
|
353
|
+
ftlDir,
|
|
354
|
+
dryRun,
|
|
355
|
+
result,
|
|
356
|
+
allActions,
|
|
357
|
+
newChecksums,
|
|
358
|
+
rollbackJournal,
|
|
359
|
+
componentName,
|
|
360
|
+
};
|
|
361
|
+
await applyOverrideBatch(batchContext);
|
|
362
|
+
await applyCustomBatch(batchContext, root, markerComment);
|
|
363
|
+
// Check for any partial failures (blocking step errors on applied
|
|
364
|
+
// components). Advisory step errors (e.g. FTL degradation) are warnings
|
|
365
|
+
// and never gate rollback.
|
|
366
|
+
const hasApplyStepErrors = result.applied.some((entry) => hasBlockingStepErrors(entry));
|
|
345
367
|
// Orphaned components are implicitly cleaned up: newChecksums only
|
|
346
368
|
// contains entries for components that still exist in furnace.json,
|
|
347
369
|
// and it fully replaces state.appliedChecksums below.
|
|
348
|
-
if (!dryRun && !
|
|
370
|
+
if (!dryRun && !hasApplyStepErrors && result.errors.length === 0) {
|
|
349
371
|
await runPostApplyConsistencyChecks(root, config, result, ftlDir);
|
|
350
372
|
}
|
|
373
|
+
// Recompute AFTER the consistency checks: they MUTATE entry.stepErrors
|
|
374
|
+
// when the applied output is inconsistent (e.g. a jar.mn entry that
|
|
375
|
+
// silently mis-landed). Reading the pre-check snapshot meant those
|
|
376
|
+
// failures skipped rollback and persisted checksums for a component
|
|
377
|
+
// known to be inconsistent — while the CLI simultaneously reported
|
|
378
|
+
// "failed to apply cleanly".
|
|
379
|
+
const hasStepErrors = result.applied.some((entry) => hasBlockingStepErrors(entry));
|
|
351
380
|
// --- Rollback on failure, persist on success (skip for dry-run) ---
|
|
352
381
|
if (!dryRun) {
|
|
353
382
|
if (result.errors.length > 0 || hasStepErrors) {
|
|
@@ -386,5 +415,10 @@ export async function applyAllComponents(root, dryRun = false, options) {
|
|
|
386
415
|
result.actions = allActions;
|
|
387
416
|
}
|
|
388
417
|
return result;
|
|
389
|
-
}
|
|
390
|
-
|
|
418
|
+
} /**
|
|
419
|
+
* Shared context threaded through the batch apply loops. Bundles the state
|
|
420
|
+
* both loops mutate (result/actions/checksums) with the immutable run
|
|
421
|
+
* parameters — the previous 12–13 positional parameters let call sites
|
|
422
|
+
* drift (the named-apply state-wipe bug) and made every new parameter a
|
|
423
|
+
* two-signature change.
|
|
424
|
+
*/
|
|
@@ -46,6 +46,17 @@ export declare function validateFurnaceConfig(data: unknown): FurnaceConfig;
|
|
|
46
46
|
* @throws Error if config doesn't exist or is invalid
|
|
47
47
|
*/
|
|
48
48
|
export declare function loadFurnaceConfig(root: string): Promise<FurnaceConfig>;
|
|
49
|
+
/**
|
|
50
|
+
* Clears applied furnace state while preserving `pendingRepair`.
|
|
51
|
+
*
|
|
52
|
+
* Used whenever the engine baseline is replaced or reset (download --force,
|
|
53
|
+
* reset, rebase, rebase --abort): every applied checksum describes content
|
|
54
|
+
* that no longer exists, but pendingRepair tracks authoring-side rollback
|
|
55
|
+
* issues in the component WORKSPACE and must survive an engine refresh.
|
|
56
|
+
* No-ops when the state file does not exist. Centralized because the same
|
|
57
|
+
* reset logic had drifted into four separate command files.
|
|
58
|
+
*/
|
|
59
|
+
export declare function clearAppliedFurnaceState(root: string): Promise<void>;
|
|
49
60
|
/**
|
|
50
61
|
* Writes a furnace configuration to furnace.json.
|
|
51
62
|
* @param root - Root directory of the project
|
|
@@ -122,6 +122,25 @@ export function validateFurnaceConfig(data) {
|
|
|
122
122
|
detectComposesCycles(custom);
|
|
123
123
|
// Validate that every composes reference points to a known component.
|
|
124
124
|
validateComposesReferences(stock, overrides, custom);
|
|
125
|
+
// Warn when two custom components share a targetPath. Nothing technically
|
|
126
|
+
// prevents co-location, but per-component removal and orphan detection
|
|
127
|
+
// both reason about "files this component deployed into its directory" —
|
|
128
|
+
// shared directories make those judgements ambiguous, and historically a
|
|
129
|
+
// shared targetPath meant `furnace remove` of one component deleted the
|
|
130
|
+
// other's deployed files.
|
|
131
|
+
const targetPathOwners = new Map();
|
|
132
|
+
for (const [name, custom_] of Object.entries(custom)) {
|
|
133
|
+
const owners = targetPathOwners.get(custom_.targetPath) ?? [];
|
|
134
|
+
owners.push(name);
|
|
135
|
+
targetPathOwners.set(custom_.targetPath, owners);
|
|
136
|
+
}
|
|
137
|
+
for (const [targetPath, owners] of targetPathOwners) {
|
|
138
|
+
if (owners.length > 1) {
|
|
139
|
+
warn(`furnace.json: custom components ${owners.join(', ')} share targetPath "${targetPath}". ` +
|
|
140
|
+
'Give each component its own directory — shared directories make per-component ' +
|
|
141
|
+
'removal and drift detection ambiguous.');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
125
144
|
const config = {
|
|
126
145
|
version: CURRENT_CONFIG_VERSION,
|
|
127
146
|
componentPrefix: migrated['componentPrefix'],
|
|
@@ -308,6 +327,24 @@ export async function loadFurnaceConfig(root) {
|
|
|
308
327
|
throw new FurnaceError(`Invalid furnace.json at ${paths.furnaceConfig}: ${toError(error).message}`);
|
|
309
328
|
}
|
|
310
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Clears applied furnace state while preserving `pendingRepair`.
|
|
332
|
+
*
|
|
333
|
+
* Used whenever the engine baseline is replaced or reset (download --force,
|
|
334
|
+
* reset, rebase, rebase --abort): every applied checksum describes content
|
|
335
|
+
* that no longer exists, but pendingRepair tracks authoring-side rollback
|
|
336
|
+
* issues in the component WORKSPACE and must survive an engine refresh.
|
|
337
|
+
* No-ops when the state file does not exist. Centralized because the same
|
|
338
|
+
* reset logic had drifted into four separate command files.
|
|
339
|
+
*/
|
|
340
|
+
export async function clearAppliedFurnaceState(root) {
|
|
341
|
+
const paths = getFurnacePaths(root);
|
|
342
|
+
if (!(await pathExists(paths.furnaceState)))
|
|
343
|
+
return;
|
|
344
|
+
await updateFurnaceState(root, (current) => ({
|
|
345
|
+
...(current.pendingRepair ? { pendingRepair: current.pendingRepair } : {}),
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
311
348
|
/**
|
|
312
349
|
* Writes a furnace configuration to furnace.json.
|
|
313
350
|
* @param root - Root directory of the project
|
|
@@ -500,4 +537,3 @@ export async function collectFurnaceManagedPrefixes(root) {
|
|
|
500
537
|
prefixes.add(FURNACE_STORYBOOK_STORIES_PREFIX);
|
|
501
538
|
return prefixes;
|
|
502
539
|
}
|
|
503
|
-
//# sourceMappingURL=furnace-config.js.map
|
|
@@ -38,9 +38,8 @@ export interface JsconfigSyncResult {
|
|
|
38
38
|
* writing.
|
|
39
39
|
*
|
|
40
40
|
* The consumer owns the jsconfig file: a missing file is an error with
|
|
41
|
-
* guidance rather than a silent scaffold
|
|
42
|
-
*
|
|
43
|
-
* JSON parser, so the error message says so explicitly.
|
|
41
|
+
* guidance rather than a silent scaffold. JSONC comments and trailing commas
|
|
42
|
+
* are preserved; Furnace edits only `compilerOptions.paths`.
|
|
44
43
|
*
|
|
45
44
|
* @param root - Project root directory
|
|
46
45
|
* @param config - Loaded Furnace configuration (must carry `typecheckJsconfig`)
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { readdir } from 'node:fs/promises';
|
|
24
24
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
25
|
+
import { applyEdits, modify, parse } from 'jsonc-parser';
|
|
25
26
|
import { FurnaceError } from '../errors/furnace.js';
|
|
26
|
-
import { pathExists,
|
|
27
|
+
import { pathExists, readText, writeTextIfChanged } from '../utils/fs.js';
|
|
27
28
|
import { info } from '../utils/logger.js';
|
|
28
29
|
import { normalizePathSlashes } from '../utils/paths.js';
|
|
29
30
|
import { getFurnacePaths } from './furnace-config.js';
|
|
@@ -89,9 +90,8 @@ function isManagedEntry(key, value, jsconfigDir, customDir) {
|
|
|
89
90
|
* writing.
|
|
90
91
|
*
|
|
91
92
|
* The consumer owns the jsconfig file: a missing file is an error with
|
|
92
|
-
* guidance rather than a silent scaffold
|
|
93
|
-
*
|
|
94
|
-
* JSON parser, so the error message says so explicitly.
|
|
93
|
+
* guidance rather than a silent scaffold. JSONC comments and trailing commas
|
|
94
|
+
* are preserved; Furnace edits only `compilerOptions.paths`.
|
|
95
95
|
*
|
|
96
96
|
* @param root - Project root directory
|
|
97
97
|
* @param config - Loaded Furnace configuration (must carry `typecheckJsconfig`)
|
|
@@ -109,19 +109,31 @@ export async function syncFurnaceJsconfigPaths(root, config, options) {
|
|
|
109
109
|
`"compilerOptions.paths" entries under ${CHROME_ELEMENTS_URL_PREFIX}*), ` +
|
|
110
110
|
'or remove the setting.');
|
|
111
111
|
}
|
|
112
|
+
const jsconfigText = await readText(jsconfigAbs);
|
|
113
|
+
const parseErrors = [];
|
|
112
114
|
let jsconfig;
|
|
113
115
|
try {
|
|
114
|
-
jsconfig =
|
|
116
|
+
jsconfig = parse(jsconfigText, parseErrors, {
|
|
117
|
+
allowTrailingComma: true,
|
|
118
|
+
disallowComments: false,
|
|
119
|
+
});
|
|
115
120
|
}
|
|
116
121
|
catch (error) {
|
|
117
|
-
throw new FurnaceError(`Could not parse ${jsconfigRel} as
|
|
118
|
-
'Furnace manages paths entries only in plain-JSON jsconfig files — JSONC comments ' +
|
|
119
|
-
'and trailing commas are not supported for the managed file.');
|
|
122
|
+
throw new FurnaceError(`Could not parse ${jsconfigRel} as JSONC: ${error instanceof Error ? error.message : String(error)}.`);
|
|
120
123
|
}
|
|
124
|
+
if (parseErrors.length > 0 ||
|
|
125
|
+
typeof jsconfig !== 'object' ||
|
|
126
|
+
jsconfig === null ||
|
|
127
|
+
Array.isArray(jsconfig)) {
|
|
128
|
+
throw new FurnaceError(`Could not parse ${jsconfigRel} as JSONC: expected an object jsconfig file.`);
|
|
129
|
+
}
|
|
130
|
+
const jsconfigObject = jsconfig;
|
|
121
131
|
const furnacePaths = getFurnacePaths(root);
|
|
122
132
|
const desired = await computeDesiredChromePathEntries(config, furnacePaths.customDir, jsconfigAbs);
|
|
123
133
|
const jsconfigDir = dirname(jsconfigAbs);
|
|
124
|
-
const currentPaths = {
|
|
134
|
+
const currentPaths = {
|
|
135
|
+
...(jsconfigObject.compilerOptions?.paths ?? {}),
|
|
136
|
+
};
|
|
125
137
|
const nextPaths = {};
|
|
126
138
|
// Preserve every unmanaged entry verbatim; prune managed entries that are
|
|
127
139
|
// no longer desired; update managed entries whose target moved.
|
|
@@ -156,14 +168,10 @@ export async function syncFurnaceJsconfigPaths(root, config, options) {
|
|
|
156
168
|
result.changed = result.added.length > 0 || result.updated.length > 0 || result.pruned.length > 0;
|
|
157
169
|
if (!result.changed || options?.dryRun === true)
|
|
158
170
|
return result;
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
paths: nextPaths,
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
await writeJson(jsconfigAbs, nextJsconfig);
|
|
171
|
+
const edits = modify(jsconfigText, ['compilerOptions', 'paths'], nextPaths, {
|
|
172
|
+
formattingOptions: { insertSpaces: true, tabSize: 2, eol: '\n' },
|
|
173
|
+
});
|
|
174
|
+
await writeTextIfChanged(jsconfigAbs, applyEdits(jsconfigText, edits));
|
|
167
175
|
return result;
|
|
168
176
|
}
|
|
169
177
|
/**
|
|
@@ -188,4 +196,3 @@ export async function reportJsconfigPathsSync(root, config, dryRun) {
|
|
|
188
196
|
info(`${dryRun ? '[dry-run] Would update' : 'Updated'} ${config.typecheckJsconfig} chrome-module paths: ` +
|
|
189
197
|
`+${sync.added.length} added, ~${sync.updated.length} updated, -${sync.pruned.length} pruned`);
|
|
190
198
|
}
|
|
191
|
-
//# sourceMappingURL=furnace-jsconfig.js.map
|
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
* AST-based custom element registration updates for customElements.js.
|
|
3
3
|
* Removal logic is in furnace-registration-remove.ts.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Returns true when `content` already contains a registration for `tagName`.
|
|
7
|
+
*
|
|
8
|
+
* Tolerates trailing line comments (e.g. a project marker like `// MYBROWSER:`)
|
|
9
|
+
* that an operator may have appended to entries written by a previous apply.
|
|
10
|
+
* Without this, a re-apply would insert a duplicate entry, and the second
|
|
11
|
+
* `setElementCreationCallback` at window-load would throw `NotSupportedError`.
|
|
12
|
+
*
|
|
13
|
+
* Matches any of:
|
|
14
|
+
* 1. `setElementCreationCallback("tag"` / `setElementCreationCallback('tag'`
|
|
15
|
+
* 2. Single-line array entry: `["tag", "..."]` — column 0 only, comments allowed after
|
|
16
|
+
* 3. Multi-line array entry: `"tag",` on its own line, optional trailing `//` comment
|
|
17
|
+
*/
|
|
18
|
+
export declare function isTagAlreadyRegistered(content: string, tagName: string): boolean;
|
|
5
19
|
/**
|
|
6
20
|
* Options that shape the lines `addCustomElementRegistration` writes into
|
|
7
21
|
* `customElements.js`. Kept optional so existing call sites keep working.
|
|
@@ -26,7 +26,7 @@ import { validateRegistrationPlacement, validateTagName } from './furnace-regist
|
|
|
26
26
|
* 2. Single-line array entry: `["tag", "..."]` — column 0 only, comments allowed after
|
|
27
27
|
* 3. Multi-line array entry: `"tag",` on its own line, optional trailing `//` comment
|
|
28
28
|
*/
|
|
29
|
-
function isTagAlreadyRegistered(content, tagName) {
|
|
29
|
+
export function isTagAlreadyRegistered(content, tagName) {
|
|
30
30
|
const escaped = escapeRegex(tagName);
|
|
31
31
|
if (content.includes(`setElementCreationCallback("${tagName}"`) ||
|
|
32
32
|
content.includes(`setElementCreationCallback('${tagName}'`)) {
|
|
@@ -403,4 +403,3 @@ export async function validateCustomElementRegistration(engineDir, tagName, modu
|
|
|
403
403
|
addRegistrationRegexFallback(content, tagName, modulePath, isESModule);
|
|
404
404
|
}
|
|
405
405
|
}
|
|
406
|
-
//# sourceMappingURL=furnace-registration-ast.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Used after both AST and legacy code paths to avoid duplicating logic.
|
|
5
5
|
*/
|
|
6
6
|
import { FurnaceError } from '../errors/furnace.js';
|
|
7
|
-
import { stripJsComments } from '../utils/regex.js';
|
|
7
|
+
import { escapeRegex, stripJsComments } from '../utils/regex.js';
|
|
8
8
|
/**
|
|
9
9
|
* Regex for valid custom element tag names. A valid name is lowercase, starts
|
|
10
10
|
* with a letter, and contains one or more hyphen-separated groups where each
|
|
@@ -88,7 +88,3 @@ function isTagInArrayConsumedInsideDOMContentLoaded(content, domContentLoadedIdx
|
|
|
88
88
|
}
|
|
89
89
|
return false;
|
|
90
90
|
}
|
|
91
|
-
function escapeRegex(value) {
|
|
92
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
93
|
-
}
|
|
94
|
-
//# sourceMappingURL=furnace-registration-validate.js.map
|
|
@@ -324,4 +324,3 @@ export async function validateJarMnInsertionForFiles(engineDir, tagName, files)
|
|
|
324
324
|
throw new FurnaceError('Could not find a content/global/ section in jar.mn for element entries. The file may be malformed.', tagName);
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
|
-
//# sourceMappingURL=furnace-registration.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-component furnace state persistence.
|
|
3
|
+
*
|
|
4
|
+
* There are exactly two sanctioned ways checksums land in
|
|
5
|
+
* furnace-state.json:
|
|
6
|
+
*
|
|
7
|
+
* 1. The BATCH path inside `applyAllComponents` (persistState: true),
|
|
8
|
+
* which replaces `appliedChecksums` wholesale — correct only when the
|
|
9
|
+
* run covered every component.
|
|
10
|
+
* 2. The per-component MERGE in this module, which rewrites only the
|
|
11
|
+
* `<type>/<name>/…` keys of the component that was applied.
|
|
12
|
+
*
|
|
13
|
+
* Every targeted (named) apply/deploy MUST use path 2 with
|
|
14
|
+
* `persistState: false`. Routing a named run through the batch path wipes
|
|
15
|
+
* every other component's checksum state: the batch loops filter to the
|
|
16
|
+
* named component, so the wholesale replace persists a state file
|
|
17
|
+
* containing only that component. Downstream, `diffDeletedFiles` and
|
|
18
|
+
* `findOrphanedEngineFiles` both key on `appliedChecksums`, so the wiped
|
|
19
|
+
* components' stale engine files become undetectable by apply AND by
|
|
20
|
+
* `furnace validate` (2026-07-05 review, finding F1 — `furnace apply
|
|
21
|
+
* <name>` had exactly this bug while `furnace deploy <name>` documented
|
|
22
|
+
* and avoided it).
|
|
23
|
+
*/
|
|
24
|
+
import { type applyAllComponents } from './furnace-apply.js';
|
|
25
|
+
import { type getFurnacePaths } from './furnace-config.js';
|
|
26
|
+
type ApplyAllResult = Awaited<ReturnType<typeof applyAllComponents>>;
|
|
27
|
+
/**
|
|
28
|
+
* Counts applied entries that carry at least one BLOCKING step error
|
|
29
|
+
* (advisory step errors are warnings and never fail a run).
|
|
30
|
+
*/
|
|
31
|
+
export declare function getStepFailureCount(result: ApplyAllResult): number;
|
|
32
|
+
/**
|
|
33
|
+
* Decides whether a single-component apply/deploy completed cleanly enough
|
|
34
|
+
* to persist its checksums into furnace-state.json.
|
|
35
|
+
*
|
|
36
|
+
* Named runs are atomic: if any apply step fails, the rollback journal
|
|
37
|
+
* restores the engine to its pre-run state and this helper returns `false`
|
|
38
|
+
* so state is not touched. The conditions must stay in lock-step with the
|
|
39
|
+
* rollback trigger in `applyAllComponents` — both read step errors, so a
|
|
40
|
+
* future refactor cannot drift them apart and accidentally persist partial
|
|
41
|
+
* state.
|
|
42
|
+
*/
|
|
43
|
+
export declare function shouldPersistSingleComponentState(result: ApplyAllResult, isDryRun: boolean): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Validates and narrows the applied entry a named run should persist.
|
|
46
|
+
*
|
|
47
|
+
* Guards against future refactors that might reorder or misroute the
|
|
48
|
+
* applied[] array: a named run persists state under a single component
|
|
49
|
+
* name, so the first applied entry MUST be that component. Persisting a
|
|
50
|
+
* different component's checksums would cause the next status/apply run to
|
|
51
|
+
* mis-report health for both components involved.
|
|
52
|
+
*
|
|
53
|
+
* @param commandLabel - Human label for error messages ("Deploy", "Apply")
|
|
54
|
+
*/
|
|
55
|
+
export declare function getPersistableAppliedEntry(commandLabel: string, name: string, appliedEntry: ApplyAllResult['applied'][number] | undefined): {
|
|
56
|
+
name: string;
|
|
57
|
+
type: 'override' | 'custom';
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Persists checksum state for a successfully applied named component by
|
|
61
|
+
* MERGING its `<type>/<name>/…` keys into the existing state — never
|
|
62
|
+
* replacing the whole map.
|
|
63
|
+
*
|
|
64
|
+
* @param projectRoot - Root directory of the project
|
|
65
|
+
* @param appliedEntry - Applied component descriptor
|
|
66
|
+
* @param furnacePaths - Resolved Furnace workspace paths
|
|
67
|
+
*/
|
|
68
|
+
export declare function persistSingleComponentState(projectRoot: string, appliedEntry: {
|
|
69
|
+
name: string;
|
|
70
|
+
type: 'override' | 'custom';
|
|
71
|
+
}, furnacePaths: ReturnType<typeof getFurnacePaths>): Promise<void>;
|
|
72
|
+
export {};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EUPL-1.2
|
|
2
|
+
/**
|
|
3
|
+
* Single-component furnace state persistence.
|
|
4
|
+
*
|
|
5
|
+
* There are exactly two sanctioned ways checksums land in
|
|
6
|
+
* furnace-state.json:
|
|
7
|
+
*
|
|
8
|
+
* 1. The BATCH path inside `applyAllComponents` (persistState: true),
|
|
9
|
+
* which replaces `appliedChecksums` wholesale — correct only when the
|
|
10
|
+
* run covered every component.
|
|
11
|
+
* 2. The per-component MERGE in this module, which rewrites only the
|
|
12
|
+
* `<type>/<name>/…` keys of the component that was applied.
|
|
13
|
+
*
|
|
14
|
+
* Every targeted (named) apply/deploy MUST use path 2 with
|
|
15
|
+
* `persistState: false`. Routing a named run through the batch path wipes
|
|
16
|
+
* every other component's checksum state: the batch loops filter to the
|
|
17
|
+
* named component, so the wholesale replace persists a state file
|
|
18
|
+
* containing only that component. Downstream, `diffDeletedFiles` and
|
|
19
|
+
* `findOrphanedEngineFiles` both key on `appliedChecksums`, so the wiped
|
|
20
|
+
* components' stale engine files become undetectable by apply AND by
|
|
21
|
+
* `furnace validate` (2026-07-05 review, finding F1 — `furnace apply
|
|
22
|
+
* <name>` had exactly this bug while `furnace deploy <name>` documented
|
|
23
|
+
* and avoided it).
|
|
24
|
+
*/
|
|
25
|
+
import { join } from 'node:path';
|
|
26
|
+
import { FurnaceError } from '../errors/furnace.js';
|
|
27
|
+
import { computeComponentChecksums, prefixChecksums, } from './furnace-apply.js';
|
|
28
|
+
import { updateFurnaceState } from './furnace-config.js';
|
|
29
|
+
import { countEntriesWithBlockingStepErrors } from './furnace-step-errors.js';
|
|
30
|
+
/**
|
|
31
|
+
* Counts applied entries that carry at least one BLOCKING step error
|
|
32
|
+
* (advisory step errors are warnings and never fail a run).
|
|
33
|
+
*/
|
|
34
|
+
export function getStepFailureCount(result) {
|
|
35
|
+
return countEntriesWithBlockingStepErrors(result.applied);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Decides whether a single-component apply/deploy completed cleanly enough
|
|
39
|
+
* to persist its checksums into furnace-state.json.
|
|
40
|
+
*
|
|
41
|
+
* Named runs are atomic: if any apply step fails, the rollback journal
|
|
42
|
+
* restores the engine to its pre-run state and this helper returns `false`
|
|
43
|
+
* so state is not touched. The conditions must stay in lock-step with the
|
|
44
|
+
* rollback trigger in `applyAllComponents` — both read step errors, so a
|
|
45
|
+
* future refactor cannot drift them apart and accidentally persist partial
|
|
46
|
+
* state.
|
|
47
|
+
*/
|
|
48
|
+
export function shouldPersistSingleComponentState(result, isDryRun) {
|
|
49
|
+
if (isDryRun)
|
|
50
|
+
return false;
|
|
51
|
+
if (result.errors.length > 0)
|
|
52
|
+
return false;
|
|
53
|
+
if (getStepFailureCount(result) > 0)
|
|
54
|
+
return false;
|
|
55
|
+
return result.applied.length > 0;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Validates and narrows the applied entry a named run should persist.
|
|
59
|
+
*
|
|
60
|
+
* Guards against future refactors that might reorder or misroute the
|
|
61
|
+
* applied[] array: a named run persists state under a single component
|
|
62
|
+
* name, so the first applied entry MUST be that component. Persisting a
|
|
63
|
+
* different component's checksums would cause the next status/apply run to
|
|
64
|
+
* mis-report health for both components involved.
|
|
65
|
+
*
|
|
66
|
+
* @param commandLabel - Human label for error messages ("Deploy", "Apply")
|
|
67
|
+
*/
|
|
68
|
+
export function getPersistableAppliedEntry(commandLabel, name, appliedEntry) {
|
|
69
|
+
if (!appliedEntry) {
|
|
70
|
+
throw new FurnaceError(`${commandLabel} for "${name}" finished without producing an applied component entry; ` +
|
|
71
|
+
`furnace state was not modified. Run "fireforge doctor --repair-furnace" to ` +
|
|
72
|
+
`reconcile state, then retry. If this persists, file a bug with the ` +
|
|
73
|
+
`output of "fireforge doctor".`);
|
|
74
|
+
}
|
|
75
|
+
if (appliedEntry.type !== 'override' && appliedEntry.type !== 'custom') {
|
|
76
|
+
throw new FurnaceError(`${commandLabel} for "${name}" returned an unsupported component type "${appliedEntry.type}"; ` +
|
|
77
|
+
`furnace state was not modified. Run "fireforge doctor --repair-furnace" to reconcile, ` +
|
|
78
|
+
`then verify the component with "fireforge furnace validate" before retrying.`);
|
|
79
|
+
}
|
|
80
|
+
if (appliedEntry.name !== name) {
|
|
81
|
+
throw new FurnaceError(`${commandLabel} for "${name}" returned an applied entry for a different component ` +
|
|
82
|
+
`("${appliedEntry.name}"); refusing to persist mismatched state. ` +
|
|
83
|
+
`Run "fireforge doctor --repair-furnace" to reconcile, then retry.`);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
name: appliedEntry.name,
|
|
87
|
+
type: appliedEntry.type,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Persists checksum state for a successfully applied named component by
|
|
92
|
+
* MERGING its `<type>/<name>/…` keys into the existing state — never
|
|
93
|
+
* replacing the whole map.
|
|
94
|
+
*
|
|
95
|
+
* @param projectRoot - Root directory of the project
|
|
96
|
+
* @param appliedEntry - Applied component descriptor
|
|
97
|
+
* @param furnacePaths - Resolved Furnace workspace paths
|
|
98
|
+
*/
|
|
99
|
+
export async function persistSingleComponentState(projectRoot, appliedEntry, furnacePaths) {
|
|
100
|
+
const componentDir = appliedEntry.type === 'override'
|
|
101
|
+
? join(furnacePaths.overridesDir, appliedEntry.name)
|
|
102
|
+
: join(furnacePaths.customDir, appliedEntry.name);
|
|
103
|
+
const checksums = await computeComponentChecksums(componentDir);
|
|
104
|
+
const prefixed = prefixChecksums(checksums, appliedEntry.type, appliedEntry.name);
|
|
105
|
+
const componentPrefix = `${appliedEntry.type}/${appliedEntry.name}/`;
|
|
106
|
+
await updateFurnaceState(projectRoot, (current) => ({
|
|
107
|
+
...current,
|
|
108
|
+
appliedChecksums: {
|
|
109
|
+
...Object.fromEntries(Object.entries(current.appliedChecksums ?? {}).filter(([key]) => !key.startsWith(componentPrefix))),
|
|
110
|
+
...prefixed,
|
|
111
|
+
},
|
|
112
|
+
engineChecksums: {
|
|
113
|
+
...Object.fromEntries(Object.entries(current.engineChecksums ?? {}).filter(([key]) => !key.startsWith(componentPrefix))),
|
|
114
|
+
...prefixed,
|
|
115
|
+
},
|
|
116
|
+
lastApply: new Date().toISOString(),
|
|
117
|
+
}));
|
|
118
|
+
}
|