Skip to content

Refactor: modernize toolchain, improve architecture, and 100% test coverage#153

Merged
patmood merged 11 commits into
mainfrom
refactor
Apr 23, 2026
Merged

Refactor: modernize toolchain, improve architecture, and 100% test coverage#153
patmood merged 11 commits into
mainfrom
refactor

Conversation

@patmood

@patmood patmood commented Apr 23, 2026

Copy link
Copy Markdown
Owner

A broad refactor that upgrades the project's toolchain, improves code architecture for library usability, and brings unit test coverage to 100%.

Toolchain upgrades

  • Jest → Vitest — faster test runner with native ESM/TypeScript support
  • esbuild → tsup — simpler build config with dual entrypoints (dist/cli.js + dist/index.js)
  • TypeScript 4.9 → 5.9 — modern language version
  • ESLint legacy config → flat config (eslint.config.js with typescript-eslint)

Architecture improvements

  • Programmatic API — new generateFromSchema() export in src/index.ts makes the library usable without the CLI. The package exports map points to dist/index.js for consumers.
  • Strategy pattern in CLI — main() refactored from a monolithic if/else chain into resolveSchemaSource() returning a SchemaSource object, separating schema resolution from execution.
  • Injected fetch dependency — http.ts now takes fetchFn as a parameter instead of coupling to globalThis.fetch, making HTTP functions testable without network calls.
  • Thrown errors over process.exit() — schema loaders (fromURLWithToken, fromURLWithPassword) now throw errors instead of calling process.exit(), making them safe to use
    programmatically.

Bug fixes

  • Stable sort — generate() now uses localeCompare instead of <= for collection name sorting, ensuring deterministic output across environments.

Testing

  • 100% unit test coverage — new test files for http, schema, sqlite, and utils; existing tests updated for Vitest snapshots.

patmood added 11 commits April 22, 2026 20:30
Schema loaders (fromURLWithToken, fromURLWithPassword) previously
called process.exit(1) on failure, making them untestable and
unusable from programmatic code. Now they throw Errors instead,
and the CLI catches those errors and exits — keeping the side
effect at the orchestration layer.
Move the CLI entry point (commander + main) into cli.ts and
repurpose index.ts as the public library API, exporting
generateFromSchema() which accepts an array of CollectionRecord
and returns the generated TypeScript string.

- src/index.ts: now exports generateFromSchema() and GenerateOptions
- src/cli.ts: full CLI entry point with commander setup and shebang
- build.js: produces dist/cli.js (bin) and dist/index.js (library)
- package.json: bin -> dist/cli.js, main/exports -> dist/index.js
- test/fromJSON.test.ts: imports generateFromSchema directly
- README.md: add programmatic API usage section
The sort comparator  returned -1
when names were equal instead of 0, producing an unstable sort. Replaced
with  which correctly returns 0 for equal
names and provides locale-aware string comparison.
Extract the 50-line if/else chain in main() into resolveSchemaSource()
and resolveFromEnv(), which return a SchemaSource object with a lazy
resolve() method. This eliminates duplicated URL/token/password logic
between the explicit-args and env-var branches, and makes each schema
source independently testable.

Also fix a pre-existing bug in the integration test: the prior commit
that split src/index.ts into src/cli.ts + src/index.ts changed the
build output to dist/cli.js (CLI) and dist/index.js (library), but
test/integration/test.sh still referenced dist/index.js, causing all
integration tests to silently produce no output.
…pling

Replaced module-level (globalThis as any).fetch and (globalThis as any).FormData
with dependency injection — both fetchWithAuth and loginAndFetch now accept an
optional fetchFn parameter defaulting to globalThis.fetch.

This eliminates the need for monkey-patching globals in tests and makes HTTP
calls fully unit-testable. Added test/http.test.ts with 4 tests covering both
functions (auth header passthrough, error handling, login-then-fetch flow),
bringing http.ts statement coverage from 14% to 100%.
Add tests for all uncovered branches and functions across the codebase:

- fields.test.ts: user type with maxSelect > 1 (RecordIdString[] branch)
- http.test.ts: default fetchFn parameter for fetchWithAuth and loginAndFetch
- lib.test.ts: createRecordType with empty fields array returning never
- utils.test.ts: saveFile with mocked fs.writeFile and console.log
- schema.test.ts (new): fromDatabase (fields/schema/empty-object fallbacks),
  fromURLWithToken and fromURLWithPassword (success, default params, error)
- sqlite.test.ts (new): getSQLiteAdapter returns adapter, queryAll creates
  readonly DB and closes it

Add istanbul ignore comments on sqlite.ts Bun-only code (not testable in Node).
- Replace .eslintrc.json with eslint.config.js using tseslint.config()
- Upgrade eslint 8 -> 9, typescript-eslint 8 (replaces separate
  @typescript-eslint/parser + @typescript-eslint/eslint-plugin)
- Upgrade eslint-config-prettier 8 -> 10 (native flat config support)
- Drop eslint-plugin-sort-keys-fix (unmaintained, incompatible with
  ESLint 9); use built-in sort-keys rule instead (same options, no
  auto-fix)
- Disable @typescript-eslint/no-require-imports for test files (needed
  for mocking patterns)
- Add explicit ignores for dist, generated types, and integration
  test fixtures
- Upgrade typescript dependency from ^4.9.5 to ^5.9.3 (latest stable 5.x)
- Upgrade ts-jest from ^29.0.5 to ^29.4.9 for official TS 5.x support
  (eliminates peer range warnings)
- Update moduleResolution in tsconfig.json from deprecated 'node' to
  'node10' (same behavior, canonical name in TS 5.x)
- All typechecks, builds, and 72 tests pass with 100% coverage
Replaces Jest/ts-jest with Vitest to simplify ESM/CJS handling and
improve test speed. Vitest natively supports ESM, eliminating the need
for the CommonJS compatibility layer that ts-jest required.

Changes:
- Remove jest, ts-jest, @types/jest, tslint-config-prettier dependencies
- Add vitest and @vitest/coverage-v8 dependencies
- Add vitest.config.ts for test configuration
- Switch tsconfig module from commonjs to esnext with bundler resolution
- Replace all jest.fn/mock/spyOn with vi equivalents in test files
- Replace require() calls with ESM-compatible dynamic imports
- Replace require.resolve() with path.resolve(__dirname, ...) using
  fileURLToPath for ESM __dirname compatibility
- Use vi.hoisted() + vi.mock() for ESM module mocking (fs/promises)
- Use vi.mocked() instead of jest.MockedFunction<> type casts
- Remove jest config block from package.json
- Remove @typescript-eslint/no-require-imports override from eslint
  config (no more require calls in tests)
- Regenerate all snapshots in Vitest format
- Remove build.js (custom esbuild script) and esbuild/esbuild-node-externals deps
- Add tsup.config.ts with equivalent config (ESM, node18, bun:sqlite external)
- Update build script in package.json from 'rm -rf dist && node build.js' to 'tsup'
- Output is byte-for-byte identical to the previous esbuild output
@patmood patmood merged commit 7981958 into main Apr 23, 2026
2 checks passed
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