Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ Three reach npm for the first time in this release:
18, and Node 20 before 20.19, are no longer supported.
- `@flatbread/config` declares `@flatbread/core` as a runtime dependency. It
was a devDependency, which worked inside this monorepo and nowhere else.
- `@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`.
Comment on lines +34 to +39

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

- `@flatbread/codegen` widens its peer range on `@flatbread/config` and
`@flatbread/core` from `workspace:*` to `workspace:^`, so it publishes a
caret range instead of an exact pin.
Expand Down
6 changes: 3 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@
"graphql-compose": "9.0.8",
"lodash-es": "4.18.1",
"matcher": "5.0.0",
"plur": "5.1.0"
"plur": "5.1.0",
"vfile": "5.3.4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

},
"devDependencies": {
"@types/lodash-es": "4.17.6",
"@types/node": "16.11.47",
"tsup": "6.2.1",
"typescript": "4.7.4",
"vfile": "5.3.4"
"typescript": "4.7.4"
}
}
5 changes: 3 additions & 2 deletions packages/core/src/providers/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { generateSchema } from '../generators/schema';
import { FlatbreadConfig } from '../types';
import { initializeConfig } from '../utils/initializeConfig';

import { graphql, GraphQLArgs, GraphQLSchema } from 'graphql';
import { graphql } from 'graphql';
import type { ExecutionResult, GraphQLArgs, GraphQLSchema } from 'graphql';

/**
* **Flatbread Provider**
Expand All @@ -23,7 +24,7 @@ export class FlatbreadProvider {
* @param args GraphQLArgs needed for executing a query. Typically, this is just a standard GraphQL query.
* @returns GraphQL response
*/
async query(args: Omit<GraphQLArgs, 'schema'>) {
async query(args: Omit<GraphQLArgs, 'schema'>): Promise<ExecutionResult> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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>.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

const schema = await this.schemaPromise;
return await graphql({ schema, ...args });
}
Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import test from 'ava';
import { readFileSync } from 'node:fs';
import type {
Content,
ContentEntry,
Expand Down Expand Up @@ -37,6 +38,9 @@ type ContentNodeIdUsesIdentifierField = Assert<
type OverrideResolveReturnsUnknown = Assert<
Equal<ReturnType<Override['resolve']>, unknown>
>;
type OverrideDescriptionMatchesGraphQL = Assert<
Equal<Override['description'], string | null | undefined>
>;
Comment on lines +41 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment on lines +41 to +43

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.


test('core public content types expose narrowed relation surfaces', (t) => {
const entry: ContentEntry = {
Expand All @@ -63,9 +67,31 @@ test('core public content types expose narrowed relation surfaces', (t) => {
t.is(unsafeString, 'value');
});

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/'));
Comment on lines +70 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

});

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 ?? {}));
});

Comment on lines +70 to +90

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

void (0 as unknown as ContentEntryRefsAreTyped);
void (0 as unknown as ContentNodeKeepsUnknownFields);
void (0 as unknown as SourceFetchUsesContent);
void (0 as unknown as SourceFetchByTypeReturnsVFiles);
void (0 as unknown as ContentNodeIdUsesIdentifierField);
void (0 as unknown as OverrideResolveReturnsUnknown);
void (0 as unknown as OverrideDescriptionMatchesGraphQL);
6 changes: 3 additions & 3 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
import type {
GraphQLFieldConfigArgumentMap,
GraphQLFieldConfig,
GraphQLInputType,
GraphQLSchema,
} from 'graphql';
import { Maybe } from 'graphql/jsutils/Maybe';
import type { VFile } from 'vfile';

// Import CodegenOptions type from the codegen package
Expand Down Expand Up @@ -195,7 +195,7 @@ export interface Override {
field: string;
type: GraphQLInputType | string;
args?: GraphQLFieldConfigArgumentMap;
description?: Maybe<string>;
description?: GraphQLFieldConfig<unknown, unknown>['description'];
resolve: (
data: unknown,
extended: {
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading