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
42 changes: 42 additions & 0 deletions docs/reference/search-api-graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,3 +260,45 @@ or a new version of this library emitting different GraphQL for the same
declaration – fails the test and shows the SDL diff, until you consciously
accept it (`vitest -u`) and the reviewer sees the contract change spelled out
in the PR.

### Committing the contract as a file

A snapshot guards the contract inside the test suite. A deployment that mounts
a [schema-declaration module](./search-api-server#the-schema-module) usually
wants the contract as a **published file** instead – `schema.graphql`, the
thing its consumers read and its pull requests diff. The `search-print-sdl`
bin writes it:

```sh
search-print-sdl --module ./dist/module.js --out ./schema.graphql
```

It loads the module the way the indexer and the served API load it (same
validation, same `schemaOptions` forwarding), so the file cannot describe a
different API from the one served. Regenerate it in CI and commit the
difference; a pull request that moves the surface then shows the move.

Without `--out` the SDL goes to standard output. The same thing from code – a
separate entry point, because it reads the filesystem and the main one stays
runtime-agnostic:

```ts
import { printSchemaModuleSdl } from '@lde/search-api-graphql/print-sdl';

await printSchemaModuleSdl({
modulePath: './dist/module.js',
outputPath: './schema.graphql',
});
```

#### Formatting

The output is formatted with the **Prettier configuration that applies to the
output path**, because a repository whose pre-commit hook formats every staged
file would otherwise have the hook and this writer spell the same schema
differently and overwrite each other in turn. It also keeps a surface move
readable: one field argument per line, so adding an argument is one added line.

Prettier is an **optional peer dependency** – your own version formats the
file, which is the point. Pass `--no-format` (or `format: false`) to write the
SDL exactly as GraphQL prints it, and Prettier is never loaded.
83 changes: 52 additions & 31 deletions package-lock.json

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

18 changes: 18 additions & 0 deletions packages/search-api-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,20 @@
"import": "./dist/index.js",
"development": "./src/index.ts",
"default": "./dist/index.js"
},
"./print-sdl": {
"types": "./dist/print-sdl.d.ts",
"import": "./dist/print-sdl.js",
"development": "./src/print-sdl.ts",
"default": "./dist/print-sdl.js"
}
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"bin": {
"search-print-sdl": "dist/cli.js"
},
"files": [
"dist",
"!**/*.tsbuildinfo"
Expand All @@ -29,10 +38,19 @@
"@escape.tech/graphql-armor-max-depth": "^2.4.2",
"@graphql-yoga/render-graphiql": "^5.21.2",
"@lde/search": "^0.18.1",
"commander": "^15.0.0",
"dataloader": "^2.2.3",
"graphql": "^16.9.0",
"graphql-yoga": "^5.21.2",
"negotiator": "^1.0.0",
"tslib": "^2.3.0"
},
"peerDependencies": {
"prettier": "^3.0.0"
},
"peerDependenciesMeta": {
"prettier": {
"optional": true
}
}
}
48 changes: 48 additions & 0 deletions packages/search-api-graphql/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env node
import { readFileSync } from 'node:fs';
import { Command } from 'commander';
import { printSchemaModuleSdl } from './print-sdl.js';

const { version } = JSON.parse(
readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
) as { version: string };

const program = new Command()
.name('search-print-sdl')
.description(
'Print the GraphQL contract of a mounted @lde/search schema-declaration module: the published surface consumers meet. Commit the output so every pull request that moves the surface shows the move.',
)
.version(version)
.requiredOption(
'--module <path>',
'path to the schema-declaration module, the same file the indexer and the served API mount',
)
.option('--out <path>', 'file to write the SDL to (default: standard output)')
.option(
'--no-format',
'write the SDL as GraphQL prints it, instead of formatting it with the Prettier configuration that applies to the output file',
);

program.parse();

const options = program.opts<{
module: string;
out?: string;
format: boolean;
}>();

try {
const sdl = await printSchemaModuleSdl({
modulePath: options.module,
outputPath: options.out,
format: options.format,
});
if (options.out === undefined) {
process.stdout.write(sdl);
} else {
console.info(`Wrote ${options.out}`);
}
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
Loading