Coding conventions
The repository uses TypeScript strict mode, ESLint, Prettier (added v0.2.1), and Vitest. The conventions are short.
TypeScript
- Strict mode is on. Avoid new
anyusage unless the boundary is genuinely dynamic and the reason is clear. The desktop ESLint config does not blockany, so reviewers enforce this convention. - Discriminated unions over enums when the values carry meaning
(e.g.
'windows' | 'linux' | 'cross-platform' | 'mixed'). - Prefer
async/awaitfor multi-step async logic. Short.then(...)chains still exist in hooks and one-shot dynamic imports; keep them localized and readable. - Re-export through
packages/core/src/<module>/index.ts- consumers import from the index, not from internal files. (Tests are an exception; they import the file under test directly.)
File layout
- One concern per file. Big files get split before they cross
~600 lines (the desktop ESLint config flags anything over 600 with
a
warn-levelmax-linesrule). - Filenames are kebab-case (
registry-types.ts,circular-guard.ts). - Tests sit next to the source:
foo.ts↔foo.test.ts. - Lighthouse renderer pages live in directories with
index.tsx+state/+components/+ optionalhelpers.tsxwhen the page is large enough to need the split. See the module map for the convention.
Naming
- Functions:
camelCase, verb-first. - Types:
PascalCase. - Constants:
UPPER_SNAKEonly for true constants. - Test files:
<file>.test.ts(vitest discovery). - React hooks:
use<X>instate/use<X>.ts.
Logging
- Main process: use the typed logger at
apps/desktop/electron/log.ts.scoped('module').info/warn/error/debuginstead ofconsole.*. The wrapper redacts common secret-key patterns (CSC_KEY_PASSWORD,GH_TOKEN, Azure credentials, bearer tokens, etc.) before they hit disk. - Renderer:
console.*for now - no renderer-side wrapper yet. - Core / shared: prefer letting the caller log; if a core module must log, use
console.*(the package is shared between main + renderer so the main-processlog.tsisn't importable here). - Prefer
warnfor soft failures,errorfor hard ones,infofor one-shot bookkeeping (deploy start/end, etc.),debugfor verbose tracing.
IPC envelopes / handler errors
- HTML in IPC responses is escaped - the UI renders warnings and errors as plain text.
warnings[]is the right vehicle for soft issues.errorplus a status-style code on the envelope is for hard failures.- Typed
HandlerError.codecarries machine-readable failure discriminators (e.g.CLI_REQUIRED). The renderer branches oncodefrom a regulartry/catch.
Formatting (Prettier - v0.2.1+)
npm run format- write changesnpm run format:check- verify without writing- Not gated in CI (no
format:checkstep inpr-check.ymlyet). Adopt incrementally on files you touch; mass-format runs are discouraged because they buy nothing and bury the review signal.
Dependencies
- No new dependencies without a line in the PR description explaining the need.
- Pin version ranges:
electron,electron-builder: tilde (~x.y.z) - minor-version updates should be intentional (CF-SEC-014).- Security tooling (
@cyclonedx/cyclonedx-npm,prettier,eslint-config-prettier): exact (x.y.zvia--save-exact). - Other runtime + dev deps: caret (
^x.y.z) is the default.
- Production deps must pass
npm audit --omit=dev --audit-level=high; this is enforced in PR check and release workflows.
Renderer-safe primitives in @configforge/core
The core package is pulled into the renderer Vite bundle. Node-only imports (crypto, fs, path) will break npm run build:renderer. If you need a hash in core, use the FNV-1a pattern from circular-guard.ts. If you genuinely need a Node-only helper, expose it from an explicit Node-only entry point (e.g. packages/core/src/node-only/index.ts) and don't import it from the shared barrel.
Commits
Short imperative subject lines. Examples from git log:
fix(import): emit valueName + valueType on Registry resources so editor validation passeschore(security): close CF-SEC-008/011/012/014 supply-chain residualsrefactor(ManifestNew): extract useNewManifestForm hook (Phase E.2)
Trailers (only when an AI agent is the author):
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Localization (i18n)
The desktop app uses react-i18next. English is the source language;
FR, DE, and ES are machine-translated and pending human linguistic
review. When adding user-visible strings:
- Use an approved namespace such as
common,sidebar,settings,home,manifests,manifest-editor,diff,history,compliance,cis-catalog,audit-pack,welcome, ordialogs. - Add the key and English value to
apps/desktop/src/locales/en/<namespace>.json. Prefer nested objects and lowercase-with-hyphens for multi-word keys. - Consume strings through
useTranslation(...)andt(...). Use i18next interpolation and_one/_otherplural forms rather than string concatenation. - Format dates and numbers through
apps/desktop/src/lib/format.tshooks instead oftoLocaleString(). - Do not hand-edit FR, DE, or ES catalogs for routine string additions.
Run the translation workflow and then
node scripts/review-locales.mjsto verify placeholders, glossary terms, and plurals.
Copy guidelines
- No em dashes in user-facing copy. Use a regular dash (-) or rewrite the sentence. Em dashes render inconsistently across terminals and narrow UI panels.
Import style in main process
- Use static imports in the main process. Lazy
await import(...)breaks in the esbuild bundle that electron-builder produces. If you need conditional loading, gate with a runtime check around a statically imported module, not a dynamic import.