Skip to content

refactor: internal patterns (errors helper, DRY, dispatch tables) — v1 prep 1/3#1

Merged
Changsik00 merged 7 commits into
mainfrom
refactor/internal-patterns
May 17, 2026
Merged

refactor: internal patterns (errors helper, DRY, dispatch tables) — v1 prep 1/3#1
Changsik00 merged 7 commits into
mainfrom
refactor/internal-patterns

Conversation

@Changsik00
Copy link
Copy Markdown
Owner

Summary

First of three stacked PRs preparing for v1.0.0. Internal-only refactoring — no public API changes; the published surface is unchanged.

Lays the foundations the next two PRs build on:

  • New docs/ARCHITECTURE.md documents the layering rules, file/dir naming, ESM resolution, and the seven core code patterns the codebase follows.
  • raise(code, msg, opts) helper in errors.ts replaces 18 direct throw new NodeSettingsError(...) sites.
  • New utils/zod-issues.ts consolidates three duplicated ZodError → path/message formatters into one source of truth.
  • cli/generate.ts rewritten around a HANDLERS dispatch table instead of a 90-line switch.
  • introspect.ts primitive-type detection moved from an if/else chain to a PRIMITIVE_TYPE_RESOLVERS lookup.
  • cli/check.ts, cli/inspect.ts, cli/preflight.ts share a new cli/workspace-runner.ts for workspace setup, header rendering, and per-package banners (~80 lines of duplicated boilerplate removed).
  • What-restating inline comments pruned (-33 comment lines).
  • Error message punctuation/casing normalised.

Commits

# Commit Phase
1 docs(architecture): add ARCHITECTURE.md for contributors 1
2 refactor(errors): extract raise() helper and zod-issues formatter 2
3 refactor(generate,introspect): replace branching with lookup tables 4
4 refactor(cli): extract workspace-runner helpers 5
5 test(cli): cover empty-workspace branch in workspace-runner (test fix, split from c9882b4)
6 refactor: prune what-restating inline comments 6
7 refactor(errors): normalise punctuation and casing in messages 7

Stacked PRs

This is 1/3. The next two stack on top of this branch:

  • 2/3 feat/error-catalog-v1 — new error codes, loadViteEnv removal (breaking), ERROR_CATALOG + reportError().
  • 3/3 docs/test-taxonomy-and-finaltest:unit/integ/e2e/contract scripts + docs/TESTING.md + final README/AGENTS/ARCHITECTURE refresh.

Merge in order so each diff is reviewable in isolation.

Test plan

  • pnpm typecheck — clean
  • pnpm test — 294 passing
  • pnpm verify — 8-layer chain (verify:errors arrives in 2/3)
  • Review docs/ARCHITECTURE.md — does the layering description match what you'd expect a new contributor to need?
  • Review the workspace-runner extraction — does it cover everything that was identical across check/inspect/preflight?

Code-level reference covering layering rules, file/directory naming,
ESM resolution conventions, core code patterns (factory + frozen
loader, define-time vs load-time validation, CLI subcommand triplet,
generator purity, error throwing, preset pattern, build-tool plugin
pattern), testing strategy, the new-feature checklist, and anti-patterns.

Complements AGENTS.md (what the library does) and CONTRIBUTING.md
(dev loop) — focuses on the code patterns a new contributor must
follow to keep changes consistent.
- Add raise(code, msg, opts): never to errors.ts, replacing 18
  throw new NodeSettingsError(...) sites across define-settings,
  client-env, and validate-options.
- New utils/zod-issues.ts centralises ZodError → text and
  ZodError → {path, message}[] conversion. Removes three
  near-identical inline formatters (define-settings, client-env,
  cli/validate).
- Move assertSettingsLoaderShape into validate-options.ts and
  share it between defineSettings's eager extends check and the
  full validateDefineSettingsOptions pass — was duplicated.
- Collapse the parallel ZodEnum / ZodNativeEnum branches in
  validate-options into a single enumValuesOf() helper.

Internal-only. No public API or error-message changes.
cli/generate.ts:
The 90-line switch statement on the <target> positional becomes a
HANDLERS registry — each entry declares its aliases and a self-contained
run(loader, args) function. Adding a new generator now means adding one
entry to the array; the dispatch is a single Map lookup. Each handler
returns an outcome tagged with kind: 'output' | 'done' | 'exit' so the
control flow is explicit. optionalString() consolidates the repeated
'flag is set → put in opts' spread idiom.

introspect.ts:
Replace the if/else if chain over inner ZodType names with
PRIMITIVE_TYPE_RESOLVERS, a record keyed by typeName. Extending support
for new primitive types now only requires adding one entry to the map.
cli/check.ts, cli/inspect.ts, and cli/preflight.ts all duplicated the
same "discover workspace → iterate packages → print header + banners"
shell. New cli/workspace-runner.ts centralises:

- setupWorkspaceRun(args)   workspace root discovery + 'no packages'
                            error (consistent across subcommands)
- printWorkspaceHeader()    'workspace root: …\npackages: N\n' header
- printPackageBanner()      '=== name (rel/path) ===' banner
- printPackageError()       '  [node-settings] <error>'

Each subcommand keeps its bespoke per-package result type and
aggregation logic — the helpers only own the bits that were
character-for-character identical. Dropped one inconsistent
'scanned: packages/*' hint from check (was check-only and stale once
pnpm/npm workspace config is present).
setupWorkspaceRun() emits a 'no packages found' error in both text and
--format=json modes, but no existing test exercised it. Adding the two
missing tests recovers branch coverage to 80.34% (above the 80% floor)
after the workspace-runner refactor introduced the helper.

(Split from the original c9882b4; the verify-dist sync for the
loadViteEnv removal lands separately alongside that removal.)
Drop comments that paraphrase the line below them. Identifiers and
control flow already communicate the 'what'; the remaining comments
should justify a choice or surface a non-obvious constraint.

Touched files:
- validate-options.ts   step-number banners ('1. envSchema must…',
                        '2. envKey must…', …) removed. The throw codes
                        right under each block are the same labels.
                        Kept the WHY tails on the enum-subset and
                        eager-extends checks.
- presets.ts            'Platform presets' ASCII divider — type
                        names already structure the file.
- check-per-env.ts      'Leaf — test the leaf path' — restates the
                        type-narrowing the branch already proved.
- introspect.ts         'Walk wrapper layers until we hit the
                        underlying primitive' — what the loop does.
- dotenv-cascade.ts     tightened step-numbered banners; kept the
                        non-obvious WHY (load-order rationale,
                        skipped-entry semantics, appEnvKey fallback).
- env-example.ts        'Trim trailing blank line' — the .pop()
                        loop says this directly.
- workspace.ts          empty '// skip' catch comment replaced with
                        a block comment that names the actual reason.
- define-settings.ts    removed 'Now validate the merged shape…'
                        — function name already says so.

Net change: -33 comment lines. No behavior change.
Three small consistency fixes across NodeSettingsError messages:

- Add trailing period to the two 'Known keys: …' hints in
  validate-options.ts so they match every other hint in the package.
- Add trailing period to 'Known branches: …' in PER_ENV_BRANCH_MISSING
  for the same reason.
- 'yaml input not found' / 'failed to read yaml input' → 'YAML' so
  the case matches 'failed to parse YAML' and 'invalid YAML' a few
  lines away in diff-k8s.ts.

Single-sentence messages always end with a period; messages that
end with an embedded cause (':' + err.message) or a multi-line list
do not — that convention is now uniform.
@Changsik00 Changsik00 merged commit d62711a into main May 17, 2026
9 checks passed
@Changsik00 Changsik00 deleted the refactor/internal-patterns branch May 17, 2026 01:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant