7.7 KiB
Migration Plan
Goal
Refactor the Electron main-process architecture without changing product behavior. The plan is sequential and should be followed in order so future sessions can make progress without reinterpreting the architecture.
Migration Principles
- Preserve behavior before moving structure.
- Add tests around risky lifecycle behavior before refactoring it.
- Keep the NodeCG runtime model intact.
- Keep Electron minimal.
- Avoid introducing IPC, preload, or renderer code unless required by a concrete feature.
- Prefer small pure functions for paths, URLs, update metadata, asset selection, and navigation policy.
- Rewrite only the degraded areas: updater, lifecycle orchestration, and platform process shutdown.
Phase 1: Freeze Existing Behavior
Purpose:
Protect current startup, shutdown, provisioning, navigation, and update behavior before extracting modules.
Tasks:
- Add tests for first-run runtime provisioning and relaunch behavior.
- Add tests for loading window and main window ordering.
- Add tests for update scheduling behavior.
- Add tests for shutdown when NodeCG is running.
- Add tests for shutdown when NodeCG is already stopped.
- Add tests for double quit or repeated shutdown calls.
- Add tests for macOS-style activation after windows are closed.
Acceptance criteria:
- Existing tests continue to pass.
- New tests describe current behavior, not the desired future behavior.
- No folder reorganization occurs in this phase.
Phase 2: Extract Pure Path And URL Logic
Purpose:
Remove deterministic calculations from main.ts while keeping Electron side effects in the entrypoint.
Target files:
src/main/app/paths.ts
src/main/config/runtime-config.ts
Tasks:
- Extract
userDatapath calculation. - Extract managed runtime path calculation.
- Extract NodeCG dashboard URL construction.
- Extract runtime configuration parsing.
- Keep Electron
app.setPath,app.setName, and lock handling in bootstrap code.
Acceptance criteria:
- Extracted functions are pure.
- Tests cover path and URL edge cases.
- No Electron app object is required to test the extracted logic.
Phase 3: Introduce ApplicationController
Purpose:
Make lifecycle explicit without redesigning the app.
Target file:
src/main/app/application-controller.ts
Required states:
idle
preparing
starting
ready
stopping
stopped
failed
Responsibilities:
- Prepare runtime.
- Start NodeCG.
- Wait for readiness.
- Create or reuse windows through a window service.
- Load dashboards only after NodeCG readiness.
- Schedule update checks.
- Handle activation safely.
- Handle shutdown idempotently.
Non-responsibilities:
- Direct process-kill command construction.
- Direct update metadata parsing.
- Direct Electron
BrowserWindowoption construction. - Business logic inside preload or renderer code.
Acceptance criteria:
main.tsbecomes a thin bootstrap.- Activation uses readiness-aware startup behavior.
- Shutdown is idempotent.
- Tests cover state transitions and failure paths.
Phase 4: Extract Shutdown Service
Purpose:
Make shutdown behavior predictable and easy to test.
Target files:
src/main/app/shutdown-service.ts
src/main/nodecg/nodecg-process-service.ts
Tasks:
- Centralize app shutdown sequencing.
- Ensure NodeCG is stopped before app exit completes.
- Handle repeated shutdown requests.
- Handle process already exited.
- Preserve current quit behavior.
Acceptance criteria:
- Repeated quit calls do not duplicate process termination.
- Tests cover
before-quit, process exit before shutdown, and shutdown failure logging.
Phase 5: Rewrite Updater In Small Modules
Purpose:
Replace the fragile updater internals while preserving user-visible behavior.
Target files:
src/main/updates/update-service.ts
src/main/updates/update-config.ts
src/main/updates/update-download.ts
src/main/updates/update-schema.ts
Tasks:
- Define a runtime schema for update metadata.
- Validate remote JSON before using it.
- Validate update asset URLs.
- Restrict protocols to
https:unless an explicit local development mode exists. - Select platform assets through pure functions.
- Download to a safe temporary path.
- Finalize downloads atomically.
- Keep dialog behavior outside fetch and download helpers.
- Correct Spanish encoding issues.
- Add tests for malformed JSON, missing assets, invalid URLs, failed downloads, and cancelled dialogs.
Acceptance criteria:
- Invalid update metadata fails closed.
- Downloaded files cannot escape the intended temporary directory.
- User-facing strings render correctly.
- Existing update behavior is preserved where valid metadata is provided.
Phase 6: Isolate Platform Process Termination
Purpose:
Keep OS-specific process-kill details outside NodeCG lifecycle logic.
Target file:
src/main/nodecg/platform-process-killer.ts
Tasks:
- Implement a small interface for terminating process trees.
- Provide Windows and POSIX implementations.
- Validate that Windows PIDs are numeric before command construction.
- Avoid spreading platform conditionals through the process manager.
- Test command selection and error handling.
Acceptance criteria:
taskkillconstruction is isolated.- POSIX process termination is isolated.
- Process manager tests no longer need to know platform command details.
Phase 7: Harden Electron Window Policy
Purpose:
Make Electron browser security explicit and testable.
Target files:
src/main/windows/window-service.ts
src/main/windows/navigation-policy.ts
Tasks:
- Explicitly set
webSecurity: true. - Keep
nodeIntegration: false. - Keep
contextIsolation: true. - Keep
sandbox: true. - Add a permission request handler that denies by default.
- Define devtools policy by environment.
- Keep navigation allowlist limited to approved local NodeCG origins.
- Prevent unexpected new-window behavior.
- Review CSP options for NodeCG-hosted content where feasible.
Acceptance criteria:
- BrowserWindow options are covered by tests.
- Permission requests are denied unless explicitly allowed.
- Navigation policy has tests for allowed and blocked origins.
Phase 8: Normalize Scripts And Shared Constants
Purpose:
Reduce packaging fragility without changing the build system.
Targets:
scripts/
src/main/config/
Tasks:
- Make repository layout assumptions explicit.
- Validate required paths before build work starts.
- Share package/runtime constants where reasonable.
- Improve error messages for missing parent project or missing NodeCG runtime.
- Keep scripts simple
.mjsutilities.
Acceptance criteria:
- CI failures explain missing external dependencies clearly.
- Local build behavior remains unchanged.
- No new framework is introduced.
Phase 9: Reorganize Folders Last
Purpose:
Move files only after behavior is protected and new ownership is clear.
Target structure:
src/main/
app/
config/
windows/
nodecg/
updates/
logging/
src/shared/
Tasks:
- Move files into target folders after tests pass.
- Update imports mechanically.
- Avoid changing logic during moves.
- Run typecheck, tests, and lint after each group of moves.
Acceptance criteria:
- File moves are behavior-neutral.
- Test output remains unchanged.
- Imports reflect the target architecture.
Required Verification Per Phase
Run the relevant subset while iterating, then run all checks before closing a phase:
npm run typecheck
npm test
npm run lint
Stop Conditions
Stop and reassess if:
- A change requires adding IPC without a product need.
- A refactor changes the NodeCG runtime model.
- Update validation requires a product or release-server decision.
- CSP changes break NodeCG dashboards.
- CI requires external repository layout decisions outside this package.