fix: make core declarations portable - #239
Conversation
|
This pull request is part of a Mergify stack:
|
Use GraphQL's public types in emitted declarations and declare vfile as a runtime dependency because the public API exposes VFile. Add checks that prevent private GraphQL paths and missing declaration dependencies from returning.\n\nTest plan: pnpm verify; strict clean-consumer TypeScript compile. Change-Id: I3f66f068fa24441f598224f2cfbbec48e722695c
2d66900 to
a9543f2
Compare
2bc8968 to
5169a39
Compare
Revision history
|
There was a problem hiding this comment.
Stale comment
Review verdict
REQUEST_CHANGES — consensus HIGH on
packages/core/src/types.test.ts:70-90(declaration guard is negative-only and build-order-coupled) plus a non-empty coverage plan for the documented packed-install /ExecutionResultcontract while non-test source changed.The source fix itself looks sound (public GraphQL types, explicit
ExecutionResult,vfile→ dependencies). The blocking issue is that the new guards can false-pass and do not sticky-lock the CHANGELOG contract.Chunk-bound feedback
- HIGH
packages/core/src/types.test.ts:70-90— keep the jsutils ban; add missing-distmessage + positivePromise<ExecutionResult>/ publicfrom 'graphql'(and ideallyfrom 'vfile') asserts.- MED
packages/core/src/providers/base.ts:27-27— add compile-timeEqual<ReturnType<FlatbreadProvider['query']>, Promise<ExecutionResult>>.- MED
packages/core/src/types.test.ts:41-43— assert equality toGraphQLFieldConfig<...>['description'], not only the widened union.Coverage plan (priority)
- Missing
dist/index.d.ts→ explicit assertion failure (not rawENOENT).- Emitted declarations contain
Promise<ExecutionResult>and publicfrom 'graphql'(keep jsutils ban).- Compile-time sticky assert on
FlatbreadProvider['query']return type.- Prefer a packed-install / clean-consumer
tscsmoke for the CHANGELOG claim (or a dedicated CI job if too heavy for AVA).- Stronger
Override['description']equality + optionalfrom 'vfile'emit check / import-specifier ↔ dependencies scan.Reviewer scoreboard
- correctness-and-contracts: 0 findings, 3 coverage gaps, signal:MED
- test-coverage-robustness: 4 findings, 6 coverage gaps, signal:HIGH
- release-discipline: 0 findings, 2 coverage gaps, signal:MED
- docs-and-positioning: 0 findings, 3 coverage gaps, signal:MED
- dependency-runtime-surface: 1 findings, 4 coverage gaps, signal:MED
Perspectives: correctness-and-contracts, test-coverage-robustness, release-discipline, docs-and-positioning, dependency-runtime-surface. Models: grok-4.5 (effort=high) / composer-2.5. Scope:
a9543f2...5169a39.Sent by Cursor Automation: Flatbread PR Review
| test('core declarations use only the public GraphQL type surface', (t) => { | ||
| const declarations = readFileSync( | ||
| new URL('../dist/index.d.ts', import.meta.url), | ||
| 'utf8' | ||
| ); | ||
|
|
||
| t.false(declarations.includes('graphql/jsutils/')); | ||
| }); | ||
|
|
||
| test('core publishes dependencies used by its declarations', (t) => { | ||
| const manifest = JSON.parse( | ||
| readFileSync(new URL('../package.json', import.meta.url), 'utf8') | ||
| ) as { | ||
| dependencies?: Record<string, string>; | ||
| devDependencies?: Record<string, string>; | ||
| }; | ||
|
|
||
| t.is(manifest.dependencies?.vfile, '5.3.4'); | ||
| t.false('vfile' in (manifest.devDependencies ?? {})); | ||
| }); | ||
|
|
There was a problem hiding this comment.
HIGH (consensus: test-coverage-robustness, dependency-runtime-surface)
The declaration guard is negative-only (t.false(...includes('graphql/jsutils/'))) and build-order-coupled (readFileSync on dist/index.d.ts → opaque ENOENT if dist is missing). Empty or wrong declarations can still pass, and nothing locks Promise<ExecutionResult> / public from 'graphql' (or from 'vfile') stickiness.
Minimal fix: fail missing dist with an explicit assertion message; keep the jsutils ban; add positive asserts that declarations include Promise<ExecutionResult> and public graphql/vfile imports; optionally assert every bare import specifier is covered by package.json dependencies.
| * @returns GraphQL response | ||
| */ | ||
| async query(args: Omit<GraphQLArgs, 'schema'>) { | ||
| async query(args: Omit<GraphQLArgs, 'schema'>): Promise<ExecutionResult> { |
There was a problem hiding this comment.
MED (test-coverage-robustness)
Explicit Promise<ExecutionResult> fixes the ObjMap leak, but nothing type- or emit-checks that return contract. Promise<any> / unknown would still satisfy a jsutils-only ban.
Minimal fix: add Equal<ReturnType<FlatbreadProvider['query']>, Promise<ExecutionResult>> and/or a dist positive match for query(...): Promise<ExecutionResult>.
| type OverrideDescriptionMatchesGraphQL = Assert< | ||
| Equal<Override['description'], string | null | undefined> | ||
| >; |
There was a problem hiding this comment.
MED (test-coverage-robustness)
Equal<Override['description'], string | null | undefined> also held for the old Maybe<string> import, so it does not prove portable public-surface derivation.
Minimal fix: assert equality to GraphQLFieldConfig<unknown, unknown>['description'] and rely on stronger dist positives for the portability claim.
There was a problem hiding this comment.
Review verdict
REQUEST_CHANGES — Consensus HIGH on packages/core/src/types.test.ts:70-90: the new portability suite is negative-only (graphql/jsutils/ absent) plus a manifest pin, so a wrong-but-jsutils-free emit, a dropped ExecutionResult/vfile surface, or a stale/missing dist can still ship green under test:ava. Source contracts themselves look correct; the merge blocks on under-locked regression coverage of the documented packed-types fix.
Chunk-bound feedback (priority)
- HIGH
types.test.ts:70-90— Add positive.d.tssubstrings (ExecutionResult,query(…): Promise<ExecutionResult>,from 'vfile'), fail clearly on missing/staledist, and ideally require everyfrom '…'indist/index.d.ts∈dependencies. - MED
providers/base.ts:27— LockReturnType<FlatbreadProvider['query']>toPromise<ExecutionResult>(and/or.d.tssignature check). - MED
types.test.ts:41-43— PinOverride['description']toGraphQLFieldConfig<…>['description'], not the expanded union alone. - MED
package.jsonvfile— Keep the runtime move; assert declarations still namevfile(manifest pin alone is incomplete). - LOW CHANGELOG — Separate
Mayberemoval vsExecutionResult(ObjMap) return annotation when wording is next touched.
Coverage plan
- Positive emit asserts + fresh-
distguard (HIGH). - Declaration dependency closure over
from '…'specifiers (HIGH/MED). - Compile-time
Equalforqueryreturn andOverride.description(MED). - Keep
vfiledependencies pin; optionalgraphqlmirror (MED).
Reviewer scoreboard
correctness-and-contracts: COMMENT — solid contract read; undersold emit-gap severity.test-coverage-robustness: REQUEST_CHANGES — decisive on false-pass / positive-emit holes.release-discipline: COMMENT — stack/1.0.0fold-in OK; CHANGELOG mechanism wording LOW.dependency-runtime-surface: APPROVE — packaging surface correct; specifier∈deps folds into coverage plan.
Models: grok-4.5 (HIGH) / composer-2.5 (MED). Canvas: dag-review-5169a39.canvas.tsx.
Sent by Cursor Automation: Flatbread PR Review
| test('core declarations use only the public GraphQL type surface', (t) => { | ||
| const declarations = readFileSync( | ||
| new URL('../dist/index.d.ts', import.meta.url), | ||
| 'utf8' | ||
| ); | ||
|
|
||
| t.false(declarations.includes('graphql/jsutils/')); |
There was a problem hiding this comment.
HIGH — Negative-only portability guard.
t.false(…includes('graphql/jsutils/')) plus the manifest pin below can still green a wrong emit (no ExecutionResult / no vfile), or a stale/missing dist under bare test:ava.
Minimal fix: After ensuring dist/index.d.ts is present/fresh (clear failure if not), assert positive substrings for public ExecutionResult / query(…): Promise<ExecutionResult>, from 'vfile', and ideally that every from '…' package in the emit appears under dependencies.
| * @returns GraphQL response | ||
| */ | ||
| async query(args: Omit<GraphQLArgs, 'schema'>) { | ||
| async query(args: Omit<GraphQLArgs, 'schema'>): Promise<ExecutionResult> { |
There was a problem hiding this comment.
MED — Explicit Promise<ExecutionResult> is the right public return, but nothing type- or .d.ts-locks FlatbreadProvider['query'] to that contract.
Minimal fix: Add Equal<ReturnType<FlatbreadProvider['query']>, Promise<ExecutionResult>> and/or a .d.ts signature substring check beside the emit suite.
| type OverrideDescriptionMatchesGraphQL = Assert< | ||
| Equal<Override['description'], string | null | undefined> | ||
| >; |
There was a problem hiding this comment.
MED — This pins the expanded Maybe<string> union, not stickiness to GraphQLFieldConfig<…>['description']. A hardcoded identical union still passes.
Minimal fix: Equal<Override['description'], GraphQLFieldConfig<unknown, unknown>['description']> (optional null assignability) and rely on a strengthened emit scan for published stickiness.
| "matcher": "5.0.0", | ||
| "plur": "5.1.0" | ||
| "plur": "5.1.0", | ||
| "vfile": "5.3.4" |
There was a problem hiding this comment.
MED — Runtime vfile placement is correct, but the new test only checks manifest shape, not that declarations still name vfile.
Minimal fix: Assert dist/index.d.ts imports vfile alongside the existing dependencies pin. Exact 5.3.4 is fine if stack pin discipline is intentional.
| - `@flatbread/core` no longer emits type declarations a packed install cannot | ||
| resolve. Its `.d.ts` files reached into the private paths | ||
| `graphql/jsutils/Maybe` and `graphql/jsutils/ObjMap`; they now use public | ||
| GraphQL types, and `FlatbreadProvider.query()` declares its return as | ||
| GraphQL's public `ExecutionResult`. `vfile@5.3.4` moves from devDependencies | ||
| to dependencies, because the public types name `VFile`. |
There was a problem hiding this comment.
LOW — Outcome is accurate post-build, but the bullet attributes both Maybe and ObjMap to “.d.ts reached into private paths.” Only Maybe was removed in source; ObjMap leakage was closed by the explicit ExecutionResult return on query().
Minimal fix: Split or qualify so both mechanisms are visible.


Use GraphQL's public types in emitted declarations and declare vfile as a runtime dependency because the public API exposes VFile. Add checks that prevent private GraphQL paths and missing declaration dependencies from returning.\n\nTest plan: pnpm verify; strict clean-consumer TypeScript compile.
Depends-On: #238