|
| 1 | +# CLAUDE.md — Project Notes for Claude Code |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +`@continuoussecuritytooling/ajv-cli` is a CLI wrapper around [Ajv](https://ajv.js.org/) JSON Schema validator. It compiles TypeScript sources to `dist/` and ships a Docker image (`node:24-slim` base). Commands: `validate`, `compile`, `test`, `migrate`, `help`. |
| 6 | + |
| 7 | +## Commands |
| 8 | + |
| 9 | +```bash |
| 10 | +npm run build # tsc → dist/ |
| 11 | +npm run test-spec # mocha via ts-node (no lint, no coverage) |
| 12 | +npm run test-cov # mocha + nyc coverage |
| 13 | +npm run test # lint + test-cov |
| 14 | +npm run lint # eslint src/**/*.ts test/**/*.js |
| 15 | +``` |
| 16 | + |
| 17 | +## Architecture |
| 18 | + |
| 19 | +- Entry point: `src/index.ts` (CLI arg parsing via minimist) |
| 20 | +- Commands: `src/commands/{validate,compile,test,migrate,help}.ts` |
| 21 | +- Shared utilities: `src/commands/util.ts`, `src/commands/options.ts`, `src/commands/ajv.ts` |
| 22 | +- Tests are **integration-only** (run compiled `dist/index.js` via `exec()`) EXCEPT for new unit tests in `test/util.spec.ts` and `test/options.spec.ts` |
| 23 | + |
| 24 | +## Key Compatibility Notes |
| 25 | + |
| 26 | +### Node.js v24 |
| 27 | + |
| 28 | +- `import X = require(...)` (TypeScript CommonJS compat syntax) is **not supported** by Node.js v22+ native strip-only mode. All test files use `import X from "..."` or `import * as X from "..."` instead. |
| 29 | +- `String.prototype.substr` is deprecated — use `substring` (`src/commands/util.ts` already fixed). |
| 30 | + |
| 31 | +### TypeScript 5.2+ |
| 32 | + |
| 33 | +**Required** — do not downgrade. Reason: `@types/node` v24 uses `typesVersions` to route TypeScript ≤5.6 to `ts5.6/index.d.ts`, which requires `lib: esnext.disposable` (added in TypeScript 5.2). On TypeScript 4.x this produces dozens of TS1165/TS2339 errors across `@types/node` declarations. |
| 34 | + |
| 35 | +### `@types/node` v24 |
| 36 | + |
| 37 | +Required for Node.js 24 runtime types. Requires TypeScript ≥5.2 (see above). |
| 38 | + |
| 39 | +**Gotcha — `assert.strictEqual` narrowing:** `@types/node` v24 gave `assert.strictEqual` an assertion signature: |
| 40 | + |
| 41 | +```typescript |
| 42 | +function strictEqual<T>(actual: unknown, expected: T, message?): asserts actual is T |
| 43 | +``` |
| 44 | + |
| 45 | +This means TypeScript 5 narrows `actual` after a `strictEqual` call. If you call `assert.strictEqual(err.keyword, "typeof")` where `err` is typed as `DefinedError` (an ajv discriminated union that has no `"typeof"` member), TypeScript narrows `err` to `never`. Subsequent property accesses then fail with TS2339. |
| 46 | + |
| 47 | +**Fix already applied:** `assertErrors()` in `test/validate.spec.ts` returns `ErrorObject[][]` instead of `DefinedError[][]`. Use `ErrorObject` (base interface, `keyword: string`) for tests involving custom keywords; `DefinedError` only covers built-in ajv keywords. |
| 48 | + |
| 49 | +### ESLint Stack |
| 50 | + |
| 51 | +The project upgraded from a mismatched stack (eslint v6, eslint-plugin-prettier v3, prettier v3) that broke because `prettier.resolveConfig.sync` was removed in prettier v3. Current working stack: |
| 52 | + |
| 53 | +| Package | Version | |
| 54 | +| --- | --- | |
| 55 | +| eslint | ^8.0.0 | |
| 56 | +| eslint-plugin-prettier | ^5.0.0 | |
| 57 | +| eslint-config-prettier | ^10.0.0 | |
| 58 | +| @typescript-eslint/eslint-plugin | ^5.0.0 | |
| 59 | +| @typescript-eslint/parser | ^5.0.0 | |
| 60 | +| prettier | ^3.8.1 | |
| 61 | + |
| 62 | +**Gotcha — `@ajv-validator/config`:** The shared ESLint config (`@ajv-validator/config/.eslintrc`) still extends `"prettier/@typescript-eslint"`, which was merged into `"prettier"` in eslint-config-prettier v8. The `.eslintrc.js` override filters this out at runtime: |
| 63 | + |
| 64 | +```js |
| 65 | +extends: [...(tsConfig.extends || []).filter((e) => e !== "prettier/@typescript-eslint"), "prettier"], |
| 66 | +``` |
| 67 | + |
| 68 | +Do **not** remove this filter until `@ajv-validator/config` is updated upstream. |
| 69 | + |
| 70 | +**Disabled rules** (intentional — the codebase uses `any` for dynamic CLI/JSON data): |
| 71 | + |
| 72 | +- `@typescript-eslint/no-unsafe-argument` |
| 73 | +- `@typescript-eslint/no-unsafe-assignment` |
| 74 | +- `@typescript-eslint/no-unsafe-call` |
| 75 | +- `@typescript-eslint/no-unsafe-member-access` |
| 76 | +- `@typescript-eslint/no-unsafe-return` |
| 77 | +- `@typescript-eslint/no-var-requires` |
| 78 | + |
| 79 | +## Testing |
| 80 | + |
| 81 | +- **Integration tests** use `dist/index.js` — always run `npm run build` before `npm run test-spec`. |
| 82 | +- **Unit tests** (`test/util.spec.ts`, `test/options.spec.ts`) import source modules directly via ts-node; no build step needed. |
| 83 | +- `process.exit()` is called on errors in `util.ts` (`openFile`, `compile`) — only test happy paths in unit tests. |
| 84 | +- Coverage is healthy: ~97% statements, 100% functions. |
| 85 | + |
| 86 | +## Renovate |
| 87 | + |
| 88 | +ESLint-related packages are grouped in `renovate.json5` under `packageRules` (`groupName: "eslint"`) so they're always upgraded together, preventing the version mismatch that broke lint. |
| 89 | + |
| 90 | +## CI / Docker |
| 91 | + |
| 92 | +- `actions/checkout` and `actions/setup-node` must be **v4** — v5/v6 do not exist. |
| 93 | +- Build matrix tests Node.js **22.x and 24.x**. |
| 94 | +- `build-results` job depends on both `build` and `package` jobs so Docker failures block the branch. |
| 95 | +- Docker base image: `node:24.14.0-slim`. The `dist/` folder must be built before `docker build` (the `package` job does `npm run clean && npm run build` first). |
| 96 | + |
| 97 | +## CodeClimate Output Format |
| 98 | + |
| 99 | +`--errors=code-climate` emits a JSON array of CodeClimate issues to **stdout** (for easy pipe/redirect to a file). Stderr still receives the `<file> invalid` message. |
| 100 | + |
| 101 | +Each issue shape: |
| 102 | + |
| 103 | +```json |
| 104 | +{ |
| 105 | + "description": "[schema] #/path/to/field must be ...", |
| 106 | + "check_name": "json-schema", |
| 107 | + "fingerprint": "<sha1 of filepath+instancePath+message>", |
| 108 | + "severity": "major", |
| 109 | + "location": { "path": "<filepath>", "lines": { "begin": 1 } } |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +GitLab CI usage: redirect stdout to `gl-code-quality-report.json` and declare it as a Code Quality artifact. |
| 114 | + |
| 115 | +> **Note:** The fork at `jirutka/ajv-cli` also tracks exact line/column positions by parsing JSON/YAML with position metadata. Our implementation omits this (always `lines.begin: 1`) to avoid heavy dependencies — the GitLab Code Quality widget works without positions. |
| 116 | + |
| 117 | +## js-yaml Note |
| 118 | + |
| 119 | +The project uses `js-yaml` v3 (`^3.14.0`). `yaml.safeLoad` is still the primary API in v3 and is **not** deprecated there. If `js-yaml` is ever upgraded to v4, `safeLoad` must be replaced with `load` (same arguments, different function name). |
0 commit comments