Skip to content

feat(core): add sanitizePackageJson hook#4314

Closed
claude[bot] wants to merge 2 commits into
nextfrom
feat/sanitize-package-json-hook
Closed

feat(core): add sanitizePackageJson hook#4314
claude[bot] wants to merge 2 commits into
nextfrom
feat/sanitize-package-json-hook

Conversation

@claude

@claude claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Requested by Samuel Attard · Slack thread

  • I have read the contribution documentation for this project.
  • I agree to follow the code of conduct that this project follows, as appropriate.
  • The changes are appropriately documented (if applicable).
  • The changes have sufficient test coverage (if applicable).
  • The testsuite passes successfully on my local machine (if applicable).

Summarize your changes:

Adds a sanitizePackageJson hook with a built-in default that strips dev-only fields from the packaged app's package.json.

Before / After

  • Before: packaged apps ship the developer's entire package.json — devDependencies, scripts, workspace/package-manager settings, tooling config, and so on — with only config.forge removed. This is the complaint in electron/packager#1184.

  • After: Forge strips dev-only fields by default when writing the copied package.json into the build. A new sanitizePackageJson mutating hook (same shape as readPackageJson) lets users and plugins customize this; providing the hook from the Forge config or any plugin replaces the default entirely. The default implementation is exported from @electron-forge/core as defaultSanitizePackageJson, so a custom hook can call it to do "default plus extra" instead.

    Default-stripped fields: devDependencies, scripts, workspaces, packageManager, resolutions, overrides, pnpm, private, publishConfig, devEngines, jest, eslintConfig, prettier, browserslist, lint-staged, nano-staged, husky, commitlint, mocha, ava, nyc, c8, tap, xo, standard, plus config.forge (config is dropped entirely if empty afterwards, preserving today's behavior). Runtime fields like main, name, productName, version, type, exports/imports, dependencies, optionalDependencies, and peerDependencies are kept.

How

  • packages/utils/types/src/index.ts: adds sanitizePackageJson to ForgeMutatingHookSignatures (flows into ForgeHookMap/ForgeMultiHookMap), and adds hasHook(hookName) to IForgePluginInterface so core can tell whether any plugin registers a given hook.
  • packages/api/core/src/util/sanitize-package-json.ts (new): defaultSanitizePackageJson (the exported default) and sanitizeCopiedPackageJson, which reads the copied package.json, runs user/plugin sanitizePackageJson hooks via runMutatingHook when any exist (config hook first, then plugin hooks, threading the result) or the default otherwise, and writes the result back with the same writeJson(..., { spaces: 2 }) as before.
  • packages/api/core/src/api/package.ts: the built-in afterCopy step that previously only deleted config.forge now delegates to sanitizeCopiedPackageJson. It keeps its position in the serial afterCopy chain: after the packageAfterCopy Forge hooks (so it observes the final package.json that the webpack/vite plugins write into the build — their own config.forge deletion is untouched and now superseded) and before packagerConfig.afterCopy hooks and prune.
  • Prune safety: the sanitize step runs before @electron/packager's prune, which walks the copied app's package.json via galactus/flora-colossus. dependencies and optionalDependencies are deliberately not stripped (the walker needs them). Stripping devDependencies is safe: flora-colossus defaults a missing devDependencies to {} and only uses root devDependencies to walk modules it then marks as dev (pruned); modules not walked at all are pruned anyway, and modules also reachable via a prod chain are kept either way (depTypeGreater upgrades dev to prod). So the pruned module set is unchanged.
  • Tests: packages/api/core/spec/fast/util/sanitize-package-json.spec.ts covers the default denylist and config.forge handling, user-hook and plugin-hook replacement of the default (default-stripped fields survive), and that the hook's return value is what lands on disk. The existing slow e2e spec now also asserts devDependencies/scripts are absent from the packaged app.

Note: the slow e2e package spec could not run in the local sandbox (Electron binary download is blocked by the environment's proxy) and relies on CI; the full fast suite, lint, build, and knip pass locally.


Generated by Claude Code

claude added 2 commits July 3, 2026 19:13
Packaged apps currently ship the project's full package.json with only
config.forge removed. Forge now sanitizes the copied package.json by
default, stripping dev-only fields (devDependencies, scripts, workspace
and package manager settings, tooling config) while keeping runtime
fields. Providing a sanitizePackageJson hook from the Forge config or a
plugin replaces the default entirely; the default is exported as
defaultSanitizePackageJson so custom hooks can extend it.

The sanitize step runs in the packager afterCopy phase, after all
packageAfterCopy hooks and before prune. Stripping devDependencies
before prune is safe: the prune walker only uses root devDependencies
to mark dev-only modules for removal, and unwalked modules are pruned
anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPrpBevFu6CmqKzeNHmoA
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HEPrpBevFu6CmqKzeNHmoA
@github-actions github-actions Bot added the next label Jul 3, 2026
@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by the packager-level implementation in electron/packager#1931 — Samuel clarified the hook should live in packager so all consumers benefit.

@claude claude Bot closed this Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant