Skip to content

feat: type the package.json webjs.* config block - #295

Merged
vivek7405 merged 3 commits into
mainfrom
feat/type-webjs-config
Jun 3, 2026
Merged

feat: type the package.json webjs.* config block#295
vivek7405 merged 3 commits into
mainfrom
feat/type-webjs-config

Conversation

@vivek7405

Copy link
Copy Markdown
Collaborator

Closes #259

What

webjs reads a webjs object from package.json (elide, headers, redirects, csp, trailingSlash, and the body-limit / timeout knobs), but there was no type, schema, or validation, so a typo'd key was silently dropped and the feature it toggled stayed at default with no diagnostic. The config equivalent of an untyped API that fails open.

Surface

  • JSON Schema (packages/server/webjs-config.schema.json, draft-07, additionalProperties: false). Every one of the 10 keys carries a type/enum/description matched to what the server reader actually accepts (statusCode is exactly the 301/302/303/307/308 the redirect resolver allows; the numeric knobs floor at 0 because 0 disables; csp is oneOf(boolean, object) and the directive map stays open so a valid CSP config is never false-flagged). Shipped in the server package files + an exports subpath.
  • Exported WebjsConfig type (packages/core/src/webjs-config.d.ts, pure types-only) for a typed reference, re-exported from @webjsdev/core.
  • Scaffold editor association: packages/cli/templates/.vscode/settings.json associates the schema with package.json's webjs property via json.schemas, so a new app validates the block natively (a typo like redirect for redirects is flagged inline).

webjs check stays errors-only: an unknown-key error would be a forward-compat hazard (an app using a newer key on an older install would fail check), so the editor schema is the diagnostic path the acceptance allows.

A gitignore trap this surfaced

The .vscode/settings.json template was silently excluded by both the root and template .gitignore (.vscode/), so the scaffold copy step (guarded by existsSync) skipped it on any clean checkout and the editor-association half shipped to nobody. Fixed with the same .vscode/* + !.vscode/settings.json shape the .webjs/ vendor exception uses, with a scaffold-integration assertion that a generated app contains the file. This is exactly what the self-review loop caught.

Tests

  • Schema drift test (packages/server/test/config/webjs-config-schema.test.js): asserts the schema covers every reader key and declares no extra, that additionalProperties:false is set, the key shapes match the reader contracts, AND that the WebjsConfig type's keys equal the reader keys (so schema, type, and readers stay a three-way lockstep). 9/9.
  • tsc type fixture (test/types/webjs-config.test-d.ts): a valid config compiles; @ts-expect-error on a wrong-typed value, a bad enum, an unknown/typo'd key, a bad status code, a missing destination, and a value: true header (now rejected). Self-checking via unused-directive detection.
  • Scaffold integration: asserts a generated app ships .vscode/settings.json with the schema $ref.
  • Full suite 1879/1879 (the +9 over main are the drift + fixture cases). Types/config + JSON only, no browser wire change, so no new browser/e2e layer; blog e2e still the dogfood gate.

Docs

agent-docs/typescript.md (the WebjsConfig + JSON Schema section), root AGENTS.md (core type-export table), packages/server/AGENTS.md (the schema file + the single documented "add a key, update schema + type + reader" procedure), packages/core/AGENTS.md, packages/cli/templates/AGENTS.md (the scaffold association).

Dogfood

website / docs / ui-website boot 200/307; scaffold tests 12/12 incl. the new .vscode assertion. No version bump (batched core/server release).

t added 3 commits June 3, 2026 12:55
webjs reads a webjs.* object from package.json (elide, headers, redirects, csp, trailingSlash, and the body-limit / timeout knobs), but there was no type, schema, or validation, so a typo'd key was silently dropped and the feature it toggled stayed at default with no diagnostic. The config equivalent of an untyped API that fails open.

Ship a standard draft-07 JSON Schema (packages/server/webjs-config.schema.json, additionalProperties:false so an unknown key is flagged) plus an exported WebjsConfig type (packages/core/src/webjs-config.d.ts), both mirroring exactly what the server readers accept. The scaffold's .vscode/settings.json associates the schema with package.json's webjs property so editors validate it natively. A drift test asserts the schema covers every key the readers consume, and a tsc fixture proves a wrong-typed or unknown key is a compile error. webjs check stays errors-only (an unknown-key error would be a forward-compat hazard), so the editor schema is the diagnostic path.

Closes #259
The #259 editor-association feature added packages/cli/templates/.vscode/settings.json (the webjs-config JSON Schema association for package.json), but both the root and template .gitignore exclude .vscode/, so the file was never committed and the scaffold copy step (guarded by existsSync) silently skipped it on any clean checkout or published package. The schema-association half of the feature reached zero scaffolded apps.

Re-include the file in both .gitignore files (the same .vscode/* plus !.vscode/settings.json shape the .webjs/ vendor exception uses, since a bare .vscode/ dir exclusion blocks any child negation), stage the template file, and assert in scaffold-integration that a generated app contains .vscode/settings.json with the schema $ref so this cannot regress silently.
Two review follow-ups for the webjs-config typing.

Add a drift test asserting the WebjsConfig type's top-level keys equal the reader keys (KNOWN_KEYS), closing the third edge of the lockstep triangle: the schema was already cross-checked against the readers, but the type could silently fall out of sync with both.

Tighten a header rule's value from string|null|boolean to string|null|false in both the schema and the type. A value of true is never meaningful (the runtime stringifies it to the literal "true"); only a string sets a header and null/false remove it. A new fixture counterfactual proves value:true is now a compile error.
@vivek7405 vivek7405 self-assigned this Jun 3, 2026

@vivek7405 vivek7405 left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against the readers, since the value of the schema and type is that they mirror what the server actually accepts. All 10 keys check out: statusCode is exactly the redirect-resolver's 301/302/303/307/308, the numeric knobs floor at 0 because 0 disables, and csp stays oneOf(boolean, object) with the directive map left open so a valid CSP config is never false-flagged.

The self-review loop earned its keep. Round 1 found that the scaffold .vscode/settings.json was gitignored by both the root and template .gitignore, so the editor-association half of the feature shipped to nobody (the copy step is existsSync-guarded, so it silently no-ops on a clean checkout). Fixed with the same .vscode/* plus !.vscode/settings.json shape the .webjs vendor exception uses, and I verified empirically in a throwaway repo that a scaffolded app commits its own settings.json while still ignoring other .vscode files. Added a scaffold-integration assertion so it cannot regress.

Also closed the schema-vs-type drift edge (a test now asserts the WebjsConfig type keys equal the reader keys, so schema, type, and readers are a three-way lockstep) and tightened a header value from boolean to string|null|false (true would stringify to the literal 'true'). Round 2 came back clean. Full suite 1880, scaffold tests pass. Good to merge once CI is green.

@vivek7405
vivek7405 merged commit 19dde26 into main Jun 3, 2026
5 checks passed
@vivek7405
vivek7405 deleted the feat/type-webjs-config branch June 3, 2026 07: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.

Type the package.json webjs.* configuration block

1 participant