diff --git a/graphile/graphile-authz/package.json b/graphile/graphile-authz/package.json index 7319d4ab0..1e185811a 100644 --- a/graphile/graphile-authz/package.json +++ b/graphile/graphile-authz/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/graphile/graphile-query/package.json b/graphile/graphile-query/package.json index 2a3c3675a..c03309d52 100644 --- a/graphile/graphile-query/package.json +++ b/graphile/graphile-query/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/graphile/graphile-schema/README.md b/graphile/graphile-schema/README.md new file mode 100644 index 000000000..4049037ee --- /dev/null +++ b/graphile/graphile-schema/README.md @@ -0,0 +1,97 @@ +# graphile-schema + +

+ +

+ +

+ + + + + + + + + +

+ +Lightweight GraphQL SDL builder for PostgreSQL using PostGraphile v5. Build schemas directly from a database or fetch them from a running GraphQL endpoint — no server dependencies required. + +## Installation + +```bash +npm install graphile-schema +``` + +## Usage + +### Build SDL from a PostgreSQL Database + +```typescript +import { buildSchemaSDL } from 'graphile-schema'; + +const sdl = await buildSchemaSDL({ + database: 'mydb', + schemas: ['app_public'], +}); + +console.log(sdl); +``` + +### Fetch SDL from a GraphQL Endpoint + +```typescript +import { fetchEndpointSchemaSDL } from 'graphile-schema'; + +const sdl = await fetchEndpointSchemaSDL('https://api.example.com/graphql', { + auth: 'Bearer my-token', + headers: { 'X-Custom-Header': 'value' }, +}); + +console.log(sdl); +``` + +### With Custom Graphile Presets + +```typescript +import { buildSchemaSDL } from 'graphile-schema'; + +const sdl = await buildSchemaSDL({ + database: 'mydb', + schemas: ['app_public', 'app_private'], + graphile: { + extends: [MyCustomPreset], + schema: { pgSimplifyPatch: false }, + }, +}); +``` + +## API + +### `buildSchemaSDL(opts)` + +Builds a GraphQL SDL string directly from a PostgreSQL database using PostGraphile v5 introspection. + +| Option | Type | Description | +|--------|------|-------------| +| `database` | `string` | Database name (default: `'constructive'`) | +| `schemas` | `string[]` | PostgreSQL schemas to introspect | +| `graphile` | `Partial` | Optional Graphile preset overrides | + +### `fetchEndpointSchemaSDL(endpoint, opts?)` + +Fetches a GraphQL SDL string from a running GraphQL endpoint via introspection query. + +| Option | Type | Description | +|--------|------|-------------| +| `endpoint` | `string` | GraphQL endpoint URL | +| `opts.headerHost` | `string` | Override the `Host` header | +| `opts.auth` | `string` | `Authorization` header value | +| `opts.headers` | `Record` | Additional request headers | + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. diff --git a/graphile/graphile-schema/__tests__/fetch-endpoint-schema.test.ts b/graphile/graphile-schema/__tests__/fetch-endpoint-schema.test.ts new file mode 100644 index 000000000..c94afba81 --- /dev/null +++ b/graphile/graphile-schema/__tests__/fetch-endpoint-schema.test.ts @@ -0,0 +1,109 @@ +import * as http from 'node:http'; +import { getIntrospectionQuery, buildSchema, introspectionFromSchema } from 'graphql'; + +import { fetchEndpointSchemaSDL } from '../src/fetch-endpoint-schema'; + +const TEST_SDL = ` + type Query { + hello: String + version: Int + } +`; + +function createMockServer(handler: (req: http.IncomingMessage, res: http.ServerResponse) => void): Promise<{ server: http.Server; port: number }> { + return new Promise((resolve) => { + const server = http.createServer(handler); + server.listen(0, '127.0.0.1', () => { + const addr = server.address() as { port: number }; + resolve({ server, port: addr.port }); + }); + }); +} + +function introspectionHandler(_req: http.IncomingMessage, res: http.ServerResponse) { + const schema = buildSchema(TEST_SDL); + const introspection = introspectionFromSchema(schema); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ data: introspection })); +} + +describe('fetchEndpointSchemaSDL', () => { + let server: http.Server; + let port: number; + + beforeAll(async () => { + ({ server, port } = await createMockServer(introspectionHandler)); + }); + + afterAll(() => { + server.close(); + }); + + it('fetches and returns SDL from a live endpoint', async () => { + const sdl = await fetchEndpointSchemaSDL(`http://127.0.0.1:${port}/graphql`); + + expect(sdl).toContain('type Query'); + expect(sdl).toContain('hello'); + expect(sdl).toContain('version'); + }); + + it('throws on HTTP error responses', async () => { + const { server: errServer, port: errPort } = await createMockServer((_req, res) => { + res.writeHead(500); + res.end('Internal Server Error'); + }); + + await expect( + fetchEndpointSchemaSDL(`http://127.0.0.1:${errPort}/graphql`), + ).rejects.toThrow('HTTP 500'); + + errServer.close(); + }); + + it('throws on invalid JSON response', async () => { + const { server: badServer, port: badPort } = await createMockServer((_req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end('not json'); + }); + + await expect( + fetchEndpointSchemaSDL(`http://127.0.0.1:${badPort}/graphql`), + ).rejects.toThrow('Failed to parse response'); + + badServer.close(); + }); + + it('throws when introspection returns errors', async () => { + const { server: errServer, port: errPort } = await createMockServer((_req, res) => { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ errors: [{ message: 'Not allowed' }] })); + }); + + await expect( + fetchEndpointSchemaSDL(`http://127.0.0.1:${errPort}/graphql`), + ).rejects.toThrow('Introspection returned errors'); + + errServer.close(); + }); + + it('passes custom headers to the endpoint', async () => { + let receivedHeaders: http.IncomingHttpHeaders = {}; + + const { server: headerServer, port: headerPort } = await createMockServer((req, res) => { + receivedHeaders = req.headers; + introspectionHandler(req, res); + }); + + await fetchEndpointSchemaSDL(`http://127.0.0.1:${headerPort}/graphql`, { + auth: 'Bearer test-token', + headerHost: 'custom.host.io', + headers: { 'X-Custom': 'value123' }, + }); + + expect(receivedHeaders['authorization']).toBe('Bearer test-token'); + expect(receivedHeaders['host']).toBe('custom.host.io'); + expect(receivedHeaders['x-custom']).toBe('value123'); + + headerServer.close(); + }); +}); diff --git a/graphile/graphile-schema/jest.config.js b/graphile/graphile-schema/jest.config.js new file mode 100644 index 000000000..057a9420e --- /dev/null +++ b/graphile/graphile-schema/jest.config.js @@ -0,0 +1,18 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + babelConfig: false, + tsconfig: 'tsconfig.json', + }, + ], + }, + transformIgnorePatterns: [`/node_modules/*`], + testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + modulePathIgnorePatterns: ['dist/*'] +}; diff --git a/graphile/graphile-schema/package.json b/graphile/graphile-schema/package.json new file mode 100644 index 000000000..f9ab9b29c --- /dev/null +++ b/graphile/graphile-schema/package.json @@ -0,0 +1,53 @@ +{ + "name": "graphile-schema", + "version": "1.0.0", + "author": "Constructive ", + "description": "Build GraphQL SDL from PostgreSQL databases using PostGraphile v5", + "main": "index.js", + "module": "esm/index.js", + "types": "index.d.ts", + "homepage": "https://github.com/constructive-io/constructive", + "license": "MIT", + "publishConfig": { + "access": "public", + "directory": "dist" + }, + "repository": { + "type": "git", + "url": "https://github.com/constructive-io/constructive" + }, + "bugs": { + "url": "https://github.com/constructive-io/constructive/issues" + }, + "scripts": { + "clean": "makage clean", + "prepack": "npm run build", + "build": "makage build", + "build:dev": "makage build --dev", + "lint": "eslint . --fix", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "deepmerge": "^4.3.1", + "graphile-build": "^5.0.0-rc.3", + "graphile-config": "1.0.0-rc.3", + "graphile-settings": "workspace:^", + "graphql": "^16.9.0", + "pg-cache": "workspace:^", + "pg-env": "workspace:^" + }, + "devDependencies": { + "makage": "^0.1.10", + "ts-node": "^10.9.2" + }, + "keywords": [ + "graphile", + "schema", + "graphql", + "sdl", + "postgraphile", + "introspection", + "constructive" + ] +} diff --git a/graphile/graphile-schema/src/build-schema.ts b/graphile/graphile-schema/src/build-schema.ts new file mode 100644 index 000000000..34cf5b3ab --- /dev/null +++ b/graphile/graphile-schema/src/build-schema.ts @@ -0,0 +1,44 @@ +import deepmerge from 'deepmerge' +import { printSchema } from 'graphql' +import { ConstructivePreset, makePgService } from 'graphile-settings' +import { makeSchema } from 'graphile-build' +import { buildConnectionString } from 'pg-cache' +import { getPgEnvOptions } from 'pg-env' +import type { GraphileConfig } from 'graphile-config' + +export type BuildSchemaOptions = { + database?: string; + schemas: string[]; + graphile?: Partial; +}; + +export async function buildSchemaSDL(opts: BuildSchemaOptions): Promise { + const database = opts.database ?? 'constructive' + const schemas = Array.isArray(opts.schemas) ? opts.schemas : [] + + const config = getPgEnvOptions({ database }) + const connectionString = buildConnectionString( + config.user, + config.password, + config.host, + config.port, + config.database, + ) + + const basePreset: GraphileConfig.Preset = { + extends: [ConstructivePreset], + pgServices: [ + makePgService({ + connectionString, + schemas, + }), + ], + } + + const preset: GraphileConfig.Preset = opts.graphile + ? deepmerge(basePreset, opts.graphile) + : basePreset + + const { schema } = await makeSchema(preset) + return printSchema(schema) +} diff --git a/graphql/server/src/schema.ts b/graphile/graphile-schema/src/fetch-endpoint-schema.ts similarity index 52% rename from graphql/server/src/schema.ts rename to graphile/graphile-schema/src/fetch-endpoint-schema.ts index 29daf8305..11a8d33b9 100644 --- a/graphql/server/src/schema.ts +++ b/graphile/graphile-schema/src/fetch-endpoint-schema.ts @@ -1,51 +1,14 @@ -import { printSchema, getIntrospectionQuery, buildClientSchema } from 'graphql' -import { ConstructivePreset, makePgService } from 'graphile-settings' -import { makeSchema } from 'graphile-build' -import { getPgPool } from 'pg-cache' -import type { GraphileConfig } from 'graphile-config' +import { getIntrospectionQuery, buildClientSchema, printSchema } from 'graphql' import * as http from 'node:http' import * as https from 'node:https' -export type BuildSchemaOptions = { - database?: string; - schemas: string[]; - graphile?: Partial; +export type FetchEndpointSchemaOptions = { + headerHost?: string; + headers?: Record; + auth?: string; }; -// Build GraphQL Schema SDL directly from Postgres using PostGraphile v5, without HTTP. -export async function buildSchemaSDL(opts: BuildSchemaOptions): Promise { - const database = opts.database ?? 'constructive' - const schemas = Array.isArray(opts.schemas) ? opts.schemas : [] - - // Get pool config for connection string - const pool = getPgPool({ database }) - const poolConfig = (pool as any).options || {} - const connectionString = `postgres://${poolConfig.user || 'postgres'}:${poolConfig.password || ''}@${poolConfig.host || 'localhost'}:${poolConfig.port || 5432}/${database}` - - // Build v5 preset - const preset: GraphileConfig.Preset = { - extends: [ - ConstructivePreset, - ...(opts.graphile?.extends ?? []), - ], - ...(opts.graphile?.disablePlugins && { disablePlugins: opts.graphile.disablePlugins }), - ...(opts.graphile?.plugins && { plugins: opts.graphile.plugins }), - ...(opts.graphile?.schema && { schema: opts.graphile.schema }), - pgServices: [ - makePgService({ - connectionString, - schemas, - }), - ], - } - - const { schema } = await makeSchema(preset) - return printSchema(schema) -} - -// Fetch GraphQL Schema SDL from a running GraphQL endpoint via introspection. -// This centralizes GraphQL client usage in the server package to avoid duplicating deps in the CLI. -export async function fetchEndpointSchemaSDL(endpoint: string, opts?: { headerHost?: string, headers?: Record, auth?: string }): Promise { +export async function fetchEndpointSchemaSDL(endpoint: string, opts?: FetchEndpointSchemaOptions): Promise { const url = new URL(endpoint) const requestUrl = url diff --git a/graphile/graphile-schema/src/index.ts b/graphile/graphile-schema/src/index.ts new file mode 100644 index 000000000..840b76710 --- /dev/null +++ b/graphile/graphile-schema/src/index.ts @@ -0,0 +1,4 @@ +export { buildSchemaSDL } from './build-schema'; +export type { BuildSchemaOptions } from './build-schema'; +export { fetchEndpointSchemaSDL } from './fetch-endpoint-schema'; +export type { FetchEndpointSchemaOptions } from './fetch-endpoint-schema'; diff --git a/graphile/graphile-schema/tsconfig.esm.json b/graphile/graphile-schema/tsconfig.esm.json new file mode 100644 index 000000000..d35ab5318 --- /dev/null +++ b/graphile/graphile-schema/tsconfig.esm.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "dist/esm", + "module": "ESNext", + "moduleResolution": "bundler" + } +} diff --git a/graphile/graphile-schema/tsconfig.json b/graphile/graphile-schema/tsconfig.json new file mode 100644 index 000000000..a54950429 --- /dev/null +++ b/graphile/graphile-schema/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "moduleResolution": "nodenext", + "module": "nodenext" + }, + "include": ["src/**/*"] +} diff --git a/graphile/graphile-search-plugin/package.json b/graphile/graphile-search-plugin/package.json index f28b2ce92..a1ba1cb13 100644 --- a/graphile/graphile-search-plugin/package.json +++ b/graphile/graphile-search-plugin/package.json @@ -14,7 +14,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "publishConfig": { diff --git a/graphile/graphile-settings/package.json b/graphile/graphile-settings/package.json index eed8207da..44a2f19ec 100644 --- a/graphile/graphile-settings/package.json +++ b/graphile/graphile-settings/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/graphile/graphile-test/package.json b/graphile/graphile-test/package.json index 08a8698f0..498bc554b 100644 --- a/graphile/graphile-test/package.json +++ b/graphile/graphile-test/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/graphile/postgraphile-plugin-pgvector/package.json b/graphile/postgraphile-plugin-pgvector/package.json index 055ec5465..08fd62c69 100644 --- a/graphile/postgraphile-plugin-pgvector/package.json +++ b/graphile/postgraphile-plugin-pgvector/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/graphql/codegen/package.json b/graphql/codegen/package.json index d55395a78..bc067f506 100644 --- a/graphql/codegen/package.json +++ b/graphql/codegen/package.json @@ -42,7 +42,7 @@ "lint": "eslint . --fix", "fmt": "oxfmt --write .", "fmt:check": "oxfmt --check .", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch", "example:codegen:sdk": "tsx src/cli/index.ts --config examples/multi-target.config.ts --react-query", "example:codegen:orm": "tsx src/cli/index.ts --config examples/multi-target.config.ts --orm", @@ -56,7 +56,6 @@ "@0no-co/graphql.web": "^1.1.2", "@babel/generator": "^7.28.6", "@babel/types": "^7.28.6", - "@constructive-io/graphql-server": "workspace:^", "@constructive-io/graphql-types": "workspace:^", "@inquirerer/utils": "^3.2.3", "@pgpmjs/core": "workspace:^", @@ -64,6 +63,7 @@ "deepmerge": "^4.3.1", "find-and-require-package-json": "^0.9.0", "gql-ast": "workspace:^", + "graphile-schema": "workspace:^", "graphql": "^16.9.0", "inflekt": "^0.3.1", "inquirerer": "^4.4.0", diff --git a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap index cbdddfa8b..280608677 100644 --- a/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap +++ b/graphql/codegen/src/__tests__/codegen/__snapshots__/cli-generator.test.ts.snap @@ -214,7 +214,10 @@ Common errors: exports[`cli docs generator generates CLI README 1`] = ` "# myapp CLI -> Auto-generated CLI commands from GraphQL schema +

+ +

+ > @generated by @constructive-io/graphql-codegen - DO NOT EDIT ## Setup @@ -345,6 +348,16 @@ All commands output JSON to stdout. Pipe to \`jq\` for formatting: myapp car list | jq '.[]' myapp car get --id | jq '.' \`\`\` + +--- + +Built by the [Constructive](https://constructive.io) team. + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. " `; @@ -1604,7 +1617,10 @@ OUTPUT: UseMutationResult exports[`hooks docs generator generates hooks README 1`] = ` "# React Query Hooks -> Auto-generated React Query hooks from GraphQL schema +

+ +

+ > @generated by @constructive-io/graphql-codegen - DO NOT EDIT ## Setup @@ -1710,6 +1726,16 @@ Authenticate a user |----------|------| | \`email\` | String (required) | | \`password\` | String (required) | + +--- + +Built by the [Constructive](https://constructive.io) team. + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. " `; @@ -2609,7 +2635,10 @@ exports[`multi-target cli docs generates multi-target MCP tools 1`] = ` exports[`multi-target cli docs generates multi-target README 1`] = ` "# myapp CLI -> Auto-generated unified multi-target CLI from GraphQL schemas +

+ +

+ > @generated by @constructive-io/graphql-codegen - DO NOT EDIT ## Setup @@ -2819,6 +2848,16 @@ All commands output JSON to stdout. Pipe to \`jq\` for formatting: myapp auth:user list | jq '.[]' myapp auth:user get --id | jq '.' \`\`\` + +--- + +Built by the [Constructive](https://constructive.io) team. + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. " `; @@ -4238,7 +4277,10 @@ const filtered = await db.modelName.findMany({ select: { id: true }, where: { na exports[`orm docs generator generates ORM README 1`] = ` "# ORM Client -> Auto-generated ORM client from GraphQL schema +

+ +

+ > @generated by @constructive-io/graphql-codegen - DO NOT EDIT ## Setup @@ -4354,6 +4396,16 @@ Authenticate a user \`\`\`typescript const result = await db.mutation.login({ email: '', password: '' }).execute(); \`\`\` + +--- + +Built by the [Constructive](https://constructive.io) team. + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. " `; @@ -5186,6 +5238,10 @@ exports[`target docs generator generates combined MCP config 1`] = ` exports[`target docs generator generates per-target README 1`] = ` "# Generated GraphQL SDK +

+ +

+ > @generated by @constructive-io/graphql-codegen - DO NOT EDIT ## Overview @@ -5230,12 +5286,26 @@ See [hooks/README.md](./hooks/README.md) for full hook reference. inquirerer-based CLI commands for \`myapp\`. See [cli/README.md](./cli/README.md) for command reference. + +--- + +Built by the [Constructive](https://constructive.io) team. + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. " `; exports[`target docs generator generates root-root README for multi-target 1`] = ` "# GraphQL SDK +

+ +

+ > @generated by @constructive-io/graphql-codegen - DO NOT EDIT ## APIs @@ -5244,5 +5314,15 @@ exports[`target docs generator generates root-root README for multi-target 1`] = |-----|----------|------------|------| | auth | http://auth.localhost/graphql | ORM | [./generated/auth/README.md](./generated/auth/README.md) | | app | http://app.localhost/graphql | ORM, React Query, CLI | [./generated/app/README.md](./generated/app/README.md) | + +--- + +Built by the [Constructive](https://constructive.io) team. + +## Disclaimer + +AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND. + +No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value. " `; diff --git a/graphql/codegen/src/__tests__/codegen/expand-targets.test.ts b/graphql/codegen/src/__tests__/codegen/expand-targets.test.ts new file mode 100644 index 000000000..1dc7ee33c --- /dev/null +++ b/graphql/codegen/src/__tests__/codegen/expand-targets.test.ts @@ -0,0 +1,141 @@ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; + +import { expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget } from '../../core/generate'; + +describe('expandApiNamesToMultiTarget', () => { + it('returns null for no apiNames', () => { + expect(expandApiNamesToMultiTarget({})).toBeNull(); + }); + + it('returns null for empty apiNames', () => { + expect(expandApiNamesToMultiTarget({ db: { apiNames: [] } })).toBeNull(); + }); + + it('returns null for single apiName', () => { + expect( + expandApiNamesToMultiTarget({ db: { apiNames: ['app'] } }), + ).toBeNull(); + }); + + it('expands multiple apiNames into separate targets', () => { + const result = expandApiNamesToMultiTarget({ + db: { apiNames: ['app', 'admin'], pgpm: { workspacePath: '/ws', moduleName: 'mod' } }, + output: './generated', + orm: true, + reactQuery: true, + }); + + expect(result).not.toBeNull(); + expect(Object.keys(result!)).toEqual(['app', 'admin']); + + expect(result!.app.db?.apiNames).toEqual(['app']); + expect(result!.app.output).toBe('./generated/app'); + expect(result!.app.orm).toBe(true); + expect(result!.app.reactQuery).toBe(true); + expect(result!.app.db?.pgpm).toEqual({ workspacePath: '/ws', moduleName: 'mod' }); + + expect(result!.admin.db?.apiNames).toEqual(['admin']); + expect(result!.admin.output).toBe('./generated/admin'); + }); + + it('uses default output path when output is not specified', () => { + const result = expandApiNamesToMultiTarget({ + db: { apiNames: ['app', 'admin'] }, + }); + + expect(result!.app.output).toBe('./generated/graphql/app'); + expect(result!.admin.output).toBe('./generated/graphql/admin'); + }); + + it('preserves all other config properties', () => { + const result = expandApiNamesToMultiTarget({ + db: { apiNames: ['app', 'admin'], pgpm: { modulePath: '/mod' } }, + endpoint: 'http://example.com', + tables: { include: ['users'] }, + hooks: { queries: true }, + }); + + expect(result!.app.endpoint).toBe('http://example.com'); + expect(result!.app.tables).toEqual({ include: ['users'] }); + expect(result!.app.hooks).toEqual({ queries: true }); + }); +}); + +describe('expandSchemaDirToMultiTarget', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'expand-schema-dir-')); + }); + + afterEach(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + it('returns null when schemaDir is not set', () => { + expect(expandSchemaDirToMultiTarget({})).toBeNull(); + }); + + it('returns null when directory does not exist', () => { + expect( + expandSchemaDirToMultiTarget({ schemaDir: '/nonexistent/path' }), + ).toBeNull(); + }); + + it('returns null when directory has no .graphql files', () => { + fs.writeFileSync(path.join(tempDir, 'readme.md'), 'hello'); + expect(expandSchemaDirToMultiTarget({ schemaDir: tempDir })).toBeNull(); + }); + + it('expands .graphql files into separate targets named by filename', () => { + fs.writeFileSync(path.join(tempDir, 'app.graphql'), 'type Query { hello: String }'); + fs.writeFileSync(path.join(tempDir, 'admin.graphql'), 'type Query { users: [User] }'); + + const result = expandSchemaDirToMultiTarget({ + schemaDir: tempDir, + output: './out', + orm: true, + }); + + expect(result).not.toBeNull(); + expect(Object.keys(result!).sort()).toEqual(['admin', 'app']); + + expect(result!.app.schemaFile).toBe(path.join(tempDir, 'app.graphql')); + expect(result!.app.output).toBe('./out/app'); + expect(result!.app.orm).toBe(true); + expect(result!.app.schemaDir).toBeUndefined(); + + expect(result!.admin.schemaFile).toBe(path.join(tempDir, 'admin.graphql')); + expect(result!.admin.output).toBe('./out/admin'); + }); + + it('uses default output path when output is not specified', () => { + fs.writeFileSync(path.join(tempDir, 'api.graphql'), 'type Query { ok: Boolean }'); + + const result = expandSchemaDirToMultiTarget({ schemaDir: tempDir }); + + expect(result!.api.output).toBe('./generated/graphql/api'); + }); + + it('ignores non-.graphql files', () => { + fs.writeFileSync(path.join(tempDir, 'app.graphql'), 'type Query { a: String }'); + fs.writeFileSync(path.join(tempDir, 'notes.txt'), 'not a schema'); + fs.writeFileSync(path.join(tempDir, 'data.json'), '{}'); + + const result = expandSchemaDirToMultiTarget({ schemaDir: tempDir }); + + expect(Object.keys(result!)).toEqual(['app']); + }); + + it('sorts targets alphabetically', () => { + fs.writeFileSync(path.join(tempDir, 'zebra.graphql'), 'type Query { z: String }'); + fs.writeFileSync(path.join(tempDir, 'alpha.graphql'), 'type Query { a: String }'); + fs.writeFileSync(path.join(tempDir, 'mid.graphql'), 'type Query { m: String }'); + + const result = expandSchemaDirToMultiTarget({ schemaDir: tempDir }); + + expect(Object.keys(result!)).toEqual(['alpha', 'mid', 'zebra']); + }); +}); diff --git a/graphql/codegen/src/__tests__/codegen/format-output.test.ts b/graphql/codegen/src/__tests__/codegen/format-output.test.ts deleted file mode 100644 index 35169f7fb..000000000 --- a/graphql/codegen/src/__tests__/codegen/format-output.test.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Test for formatOutput function - * Verifies that oxfmt formats generated code correctly - */ -import * as fs from 'node:fs'; -import * as os from 'node:os'; -import * as path from 'node:path'; - -import { formatOutput } from '../../core/output'; - -describe('formatOutput', () => { - let tempDir: string; - - beforeEach(() => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegen-format-test-')); - }); - - afterEach(() => { - fs.rmSync(tempDir, { recursive: true, force: true }); - }); - - it('formats TypeScript files with oxfmt options', async () => { - // Write unformatted code (double quotes, missing semicolons) - const unformatted = `const x = "hello" -const obj = {a: 1,b: 2} -`; - fs.writeFileSync(path.join(tempDir, 'test.ts'), unformatted); - - const result = await formatOutput(tempDir); - - // If oxfmt is not available in test environment, skip the test - if (!result.success && result.error?.includes('oxfmt not available')) { - console.log('Skipping test: oxfmt not available in test environment'); - return; - } - - expect(result.success).toBe(true); - - // Verify formatting applied (single quotes, semicolons added) - const formatted = fs.readFileSync(path.join(tempDir, 'test.ts'), 'utf-8'); - expect(formatted).toContain("'hello'"); - expect(formatted).toContain(';'); - }); -}); diff --git a/graphql/codegen/src/__tests__/codegen/schema-only.test.ts b/graphql/codegen/src/__tests__/codegen/schema-only.test.ts new file mode 100644 index 000000000..7761e9a85 --- /dev/null +++ b/graphql/codegen/src/__tests__/codegen/schema-only.test.ts @@ -0,0 +1,87 @@ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; + +import { generate } from '../../core/generate'; + +const EXAMPLE_SCHEMA = path.resolve( + __dirname, + '../../../examples/example.schema.graphql', +); + +describe('generate() with schemaOnly', () => { + let tempDir: string; + + beforeEach(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'schema-only-test-')); + }); + + afterEach(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + it('writes SDL to file from schemaFile source', async () => { + const result = await generate({ + schemaFile: EXAMPLE_SCHEMA, + schemaOnly: true, + schemaOnlyOutput: tempDir, + }); + + expect(result.success).toBe(true); + expect(result.filesWritten).toHaveLength(1); + + const outFile = path.join(tempDir, 'schema.graphql'); + expect(fs.existsSync(outFile)).toBe(true); + + const sdl = fs.readFileSync(outFile, 'utf8'); + expect(sdl).toContain('type Query'); + expect(sdl).toContain('type User'); + }); + + it('uses custom filename when schemaOnlyFilename is set', async () => { + const result = await generate({ + schemaFile: EXAMPLE_SCHEMA, + schemaOnly: true, + schemaOnlyOutput: tempDir, + schemaOnlyFilename: 'app.graphql', + }); + + expect(result.success).toBe(true); + const outFile = path.join(tempDir, 'app.graphql'); + expect(fs.existsSync(outFile)).toBe(true); + }); + + it('succeeds without any generators enabled', async () => { + const result = await generate({ + schemaFile: EXAMPLE_SCHEMA, + schemaOnly: true, + schemaOnlyOutput: tempDir, + }); + + expect(result.success).toBe(true); + expect(result.message).toContain('Schema exported to'); + }); + + it('fails when no source is specified', async () => { + const result = await generate({ + schemaOnly: true, + schemaOnlyOutput: tempDir, + }); + + expect(result.success).toBe(false); + expect(result.message).toContain('No source specified'); + }); + + it('creates output directory if it does not exist', async () => { + const nestedDir = path.join(tempDir, 'nested', 'output'); + + const result = await generate({ + schemaFile: EXAMPLE_SCHEMA, + schemaOnly: true, + schemaOnlyOutput: nestedDir, + }); + + expect(result.success).toBe(true); + expect(fs.existsSync(path.join(nestedDir, 'schema.graphql'))).toBe(true); + }); +}); diff --git a/graphql/codegen/src/cli/handler.ts b/graphql/codegen/src/cli/handler.ts new file mode 100644 index 000000000..110e5c259 --- /dev/null +++ b/graphql/codegen/src/cli/handler.ts @@ -0,0 +1,122 @@ +/** + * Shared codegen CLI handler + * + * Contains the core logic used by both `graphql-codegen` and `cnc codegen`. + * Both CLIs delegate to runCodegenHandler() after handling their own + * help/version flags. + */ +import type { Question } from 'inquirerer'; + +import { findConfigFile, loadConfigFile } from '../core/config'; +import { expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget, generate, generateMulti } from '../core/generate'; +import type { GraphQLSDKConfigTarget } from '../types/config'; +import { + buildDbConfig, + buildGenerateOptions, + camelizeArgv, + codegenQuestions, + hasResolvedCodegenSource, + normalizeCodegenListOptions, + printResult, + seedArgvFromConfig, +} from './shared'; + +interface Prompter { + prompt(argv: Record, questions: Question[]): Promise>; +} + +export async function runCodegenHandler( + argv: Record, + prompter: Prompter, +): Promise { + const args = camelizeArgv(argv as Record); + + const schemaOnly = Boolean(args.schemaOnly); + + const hasSourceFlags = Boolean( + args.endpoint || args.schemaFile || args.schemaDir || args.schemas || args.apiNames + ); + const configPath = + (args.config as string | undefined) || + (!hasSourceFlags ? findConfigFile() : undefined); + const targetName = args.target as string | undefined; + + let fileConfig: GraphQLSDKConfigTarget = {}; + + if (configPath) { + const loaded = await loadConfigFile(configPath); + if (!loaded.success) { + console.error('x', loaded.error); + process.exit(1); + } + + const config = loaded.config as Record; + const isMulti = !( + 'endpoint' in config || + 'schemaFile' in config || + 'schemaDir' in config || + 'db' in config + ); + + if (isMulti) { + const targets = config as Record; + + if (targetName && !targets[targetName]) { + console.error( + 'x', + `Target "${targetName}" not found. Available: ${Object.keys(targets).join(', ')}`, + ); + process.exit(1); + } + + const cliOptions = buildDbConfig( + normalizeCodegenListOptions(args), + ); + + const selectedTargets = targetName + ? { [targetName]: targets[targetName] } + : targets; + + const { results, hasError } = await generateMulti({ + configs: selectedTargets, + cliOverrides: cliOptions as Partial, + schemaOnly, + }); + + for (const { name, result } of results) { + console.log(`\n[${name}]`); + printResult(result); + } + + if (hasError) process.exit(1); + return; + } + + fileConfig = config as GraphQLSDKConfigTarget; + } + + const seeded = seedArgvFromConfig(args, fileConfig); + const answers = hasResolvedCodegenSource(seeded) + ? seeded + : await prompter.prompt(seeded, codegenQuestions); + const options = buildGenerateOptions(answers, fileConfig); + + const expandedApi = expandApiNamesToMultiTarget(options); + const expandedDir = expandSchemaDirToMultiTarget(options); + const expanded = expandedApi || expandedDir; + if (expanded) { + const { results, hasError } = await generateMulti({ + configs: expanded, + schemaOnly, + }); + for (const { name, result } of results) { + console.log(`\n[${name}]`); + printResult(result); + } + if (hasError) process.exit(1); + return; + } + + const result = await generate({ ...options, schemaOnly }); + printResult(result); +} diff --git a/graphql/codegen/src/cli/index.ts b/graphql/codegen/src/cli/index.ts index dec25073c..6f448a6ed 100644 --- a/graphql/codegen/src/cli/index.ts +++ b/graphql/codegen/src/cli/index.ts @@ -7,19 +7,7 @@ */ import { CLI, CLIOptions, getPackageJson, Inquirerer } from 'inquirerer'; -import { findConfigFile, loadConfigFile } from '../core/config'; -import { generate, generateMulti } from '../core/generate'; -import { mergeConfig, type GraphQLSDKConfigTarget } from '../types/config'; -import { - buildDbConfig, - buildGenerateOptions, - camelizeArgv, - codegenQuestions, - hasResolvedCodegenSource, - normalizeCodegenListOptions, - printResult, - seedArgvFromConfig, -} from './shared'; +import { runCodegenHandler } from './handler'; const usage = ` graphql-codegen - GraphQL SDK generator for Constructive databases @@ -31,10 +19,12 @@ Source Options (choose one): -c, --config Path to config file -e, --endpoint GraphQL endpoint URL -s, --schema-file Path to GraphQL schema file + --schema-dir Directory of .graphql files (auto-expands to multi-target) Database Options: --schemas Comma-separated PostgreSQL schemas --api-names Comma-separated API names (mutually exclusive with --schemas) + Multiple apiNames auto-expand to multi-target (one schema per API). Generator Options: --react-query Generate React Query hooks @@ -45,6 +35,11 @@ Generator Options: --dry-run Preview without writing files -v, --verbose Show detailed output +Schema Export: + --schema-only Export GraphQL SDL instead of running full codegen. + Works with any source (endpoint, file, database, PGPM). + With multiple apiNames, writes one .graphql per API. + -h, --help Show this help message --version Show version number `; @@ -65,81 +60,7 @@ export const commands = async ( process.exit(0); } - const hasSourceFlags = Boolean( - argv.endpoint || - argv.e || - argv['schema-file'] || - argv.s || - argv.schemas || - argv['api-names'], - ); - const explicitConfigPath = (argv.config || argv.c) as string | undefined; - const configPath = - explicitConfigPath || (!hasSourceFlags ? findConfigFile() : undefined); - const targetName = (argv.target || argv.t) as string | undefined; - - let fileConfig: GraphQLSDKConfigTarget = {}; - - if (configPath) { - const loaded = await loadConfigFile(configPath); - if (!loaded.success) { - console.error('x', loaded.error); - process.exit(1); - } - - const config = loaded.config as Record; - const isMulti = !( - 'endpoint' in config || - 'schemaFile' in config || - 'db' in config - ); - - if (isMulti) { - const targets = config as Record; - - if (targetName && !targets[targetName]) { - console.error( - 'x', - `Target "${targetName}" not found. Available: ${Object.keys(targets).join(', ')}`, - ); - process.exit(1); - } - - const cliOptions = buildDbConfig( - normalizeCodegenListOptions( - camelizeArgv(argv as Record), - ), - ); - - const selectedTargets = targetName - ? { [targetName]: targets[targetName] } - : targets; - - const { results, hasError } = await generateMulti({ - configs: selectedTargets, - cliOverrides: cliOptions as Partial, - }); - - for (const { name, result } of results) { - console.log(`\n[${name}]`); - printResult(result); - } - - prompter.close(); - if (hasError) process.exit(1); - return argv; - } - - fileConfig = config as GraphQLSDKConfigTarget; - } - - const seeded = seedArgvFromConfig(argv, fileConfig); - const answers = hasResolvedCodegenSource(seeded) - ? seeded - : await prompter.prompt(seeded, codegenQuestions); - const options = buildGenerateOptions(answers, fileConfig); - const result = await generate(options); - printResult(result); + await runCodegenHandler(argv, prompter); prompter.close(); return argv; }; @@ -156,16 +77,15 @@ export const options: Partial = { a: 'authorization', v: 'verbose', }, + boolean: ['schema-only'], string: [ 'config', 'endpoint', 'schema-file', + 'schema-dir', 'output', 'target', 'authorization', - 'pgpm-module-path', - 'pgpm-workspace-path', - 'pgpm-module-name', 'schemas', 'api-names', ], diff --git a/graphql/codegen/src/core/codegen/cli/docs-generator.ts b/graphql/codegen/src/core/codegen/cli/docs-generator.ts index c039e6c18..88f063d37 100644 --- a/graphql/codegen/src/core/codegen/cli/docs-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/docs-generator.ts @@ -4,6 +4,8 @@ import type { CleanTable, CleanOperation } from '../../../types/schema'; import { formatArgType, getEditableFields, + getReadmeHeader, + getReadmeFooter, gqlTypeToJsonSchemaType, buildSkillFile, } from '../docs-utils'; @@ -24,11 +26,7 @@ export function generateReadme( ): GeneratedDocFile { const lines: string[] = []; - lines.push(`# ${toolName} CLI`); - lines.push(''); - lines.push('> Auto-generated CLI commands from GraphQL schema'); - lines.push('> @generated by @constructive-io/graphql-codegen - DO NOT EDIT'); - lines.push(''); + lines.push(...getReadmeHeader(`${toolName} CLI`)); lines.push('## Setup'); lines.push(''); lines.push('```bash'); @@ -157,6 +155,8 @@ export function generateReadme( lines.push('```'); lines.push(''); + lines.push(...getReadmeFooter()); + return { fileName: 'README.md', content: lines.join('\n'), @@ -737,11 +737,7 @@ export function generateMultiTargetReadme( const { toolName, builtinNames, targets } = input; const lines: string[] = []; - lines.push(`# ${toolName} CLI`); - lines.push(''); - lines.push('> Auto-generated unified multi-target CLI from GraphQL schemas'); - lines.push('> @generated by @constructive-io/graphql-codegen - DO NOT EDIT'); - lines.push(''); + lines.push(...getReadmeHeader(`${toolName} CLI`)); lines.push('## Setup'); lines.push(''); @@ -938,6 +934,8 @@ export function generateMultiTargetReadme( lines.push('```'); lines.push(''); + lines.push(...getReadmeFooter()); + return { fileName: 'README.md', content: lines.join('\n'), diff --git a/graphql/codegen/src/core/codegen/docs-utils.ts b/graphql/codegen/src/core/codegen/docs-utils.ts index 2e5a7d4b6..bab5c1d74 100644 --- a/graphql/codegen/src/core/codegen/docs-utils.ts +++ b/graphql/codegen/src/core/codegen/docs-utils.ts @@ -22,6 +22,39 @@ export interface SkillDefinition { language?: string; } +const CONSTRUCTIVE_LOGO_URL = + 'https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg'; + +const CONSTRUCTIVE_REPO = 'https://github.com/constructive-io/constructive'; + +export function getReadmeHeader(title: string): string[] { + return [ + `# ${title}`, + '', + '

', + ` `, + '

', + '', + `> @generated by @constructive-io/graphql-codegen - DO NOT EDIT`, + '', + ]; +} + +export function getReadmeFooter(): string[] { + return [ + '---', + '', + 'Built by the [Constructive](https://constructive.io) team.', + '', + '## Disclaimer', + '', + 'AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.', + '', + 'No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.', + '', + ]; +} + export function resolveDocsConfig( docs: DocsConfig | boolean | undefined, ): DocsConfig { diff --git a/graphql/codegen/src/core/codegen/hooks-docs-generator.ts b/graphql/codegen/src/core/codegen/hooks-docs-generator.ts index 12e6ffd5a..4a7f93e45 100644 --- a/graphql/codegen/src/core/codegen/hooks-docs-generator.ts +++ b/graphql/codegen/src/core/codegen/hooks-docs-generator.ts @@ -2,6 +2,8 @@ import type { CleanOperation, CleanTable } from '../../types/schema'; import { buildSkillFile, formatArgType, + getReadmeHeader, + getReadmeFooter, gqlTypeToJsonSchemaType, } from './docs-utils'; import type { GeneratedDocFile, McpTool } from './docs-utils'; @@ -33,11 +35,7 @@ export function generateHooksReadme( ): GeneratedDocFile { const lines: string[] = []; - lines.push('# React Query Hooks'); - lines.push(''); - lines.push('> Auto-generated React Query hooks from GraphQL schema'); - lines.push('> @generated by @constructive-io/graphql-codegen - DO NOT EDIT'); - lines.push(''); + lines.push(...getReadmeHeader('React Query Hooks')); lines.push('## Setup'); lines.push(''); lines.push('```typescript'); @@ -170,6 +168,8 @@ export function generateHooksReadme( } } + lines.push(...getReadmeFooter()); + return { fileName: 'README.md', content: lines.join('\n'), diff --git a/graphql/codegen/src/core/codegen/orm/docs-generator.ts b/graphql/codegen/src/core/codegen/orm/docs-generator.ts index 1389d6160..950994c60 100644 --- a/graphql/codegen/src/core/codegen/orm/docs-generator.ts +++ b/graphql/codegen/src/core/codegen/orm/docs-generator.ts @@ -3,6 +3,8 @@ import { buildSkillFile, formatArgType, getEditableFields, + getReadmeHeader, + getReadmeFooter, gqlTypeToJsonSchemaType, } from '../docs-utils'; import type { GeneratedDocFile, McpTool } from '../docs-utils'; @@ -20,11 +22,7 @@ export function generateOrmReadme( ): GeneratedDocFile { const lines: string[] = []; - lines.push('# ORM Client'); - lines.push(''); - lines.push('> Auto-generated ORM client from GraphQL schema'); - lines.push('> @generated by @constructive-io/graphql-codegen - DO NOT EDIT'); - lines.push(''); + lines.push(...getReadmeHeader('ORM Client')); lines.push('## Setup'); lines.push(''); lines.push('```typescript'); @@ -143,6 +141,8 @@ export function generateOrmReadme( } } + lines.push(...getReadmeFooter()); + return { fileName: 'README.md', content: lines.join('\n'), diff --git a/graphql/codegen/src/core/codegen/target-docs-generator.ts b/graphql/codegen/src/core/codegen/target-docs-generator.ts index ab8b5d851..d5215b77d 100644 --- a/graphql/codegen/src/core/codegen/target-docs-generator.ts +++ b/graphql/codegen/src/core/codegen/target-docs-generator.ts @@ -1,4 +1,5 @@ import type { GraphQLSDKConfigTarget } from '../../types/config'; +import { getReadmeHeader, getReadmeFooter } from './docs-utils'; import type { GeneratedDocFile, McpTool } from './docs-utils'; export interface TargetReadmeOptions { @@ -25,10 +26,7 @@ export function generateTargetReadme( } = options; const lines: string[] = []; - lines.push('# Generated GraphQL SDK'); - lines.push(''); - lines.push('> @generated by @constructive-io/graphql-codegen - DO NOT EDIT'); - lines.push(''); + lines.push(...getReadmeHeader('Generated GraphQL SDK')); lines.push('## Overview'); lines.push(''); @@ -110,6 +108,8 @@ export function generateTargetReadme( lines.push(''); } + lines.push(...getReadmeFooter()); + return { fileName: 'README.md', content: lines.join('\n'), @@ -145,10 +145,7 @@ export function generateRootRootReadme( ): GeneratedDocFile { const lines: string[] = []; - lines.push('# GraphQL SDK'); - lines.push(''); - lines.push('> @generated by @constructive-io/graphql-codegen - DO NOT EDIT'); - lines.push(''); + lines.push(...getReadmeHeader('GraphQL SDK')); lines.push('## APIs'); lines.push(''); @@ -163,6 +160,8 @@ export function generateRootRootReadme( } lines.push(''); + lines.push(...getReadmeFooter()); + return { fileName: 'README.md', content: lines.join('\n'), diff --git a/graphql/codegen/src/core/database/index.ts b/graphql/codegen/src/core/database/index.ts index 1b6cfae53..b078602f8 100644 --- a/graphql/codegen/src/core/database/index.ts +++ b/graphql/codegen/src/core/database/index.ts @@ -7,7 +7,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; -import { buildSchemaSDL } from '@constructive-io/graphql-server'; +import { buildSchemaSDL } from 'graphile-schema'; export interface BuildSchemaFromDatabaseOptions { /** Database name */ @@ -56,24 +56,3 @@ export async function buildSchemaFromDatabase( return { schemaPath, sdl }; } - -/** - * Build a GraphQL schema SDL string from a PostgreSQL database without writing to file. - * - * This is a convenience wrapper around buildSchemaSDL from graphql-server. - * - * @param options - Configuration options - * @returns The SDL content as a string - */ -export async function buildSchemaSDLFromDatabase(options: { - database: string; - schemas: string[]; -}): Promise { - const { database, schemas } = options; - - // PostGraphile v5 resolves role/settings via preset configuration. - return buildSchemaSDL({ - database, - schemas, - }); -} diff --git a/graphql/codegen/src/core/generate.ts b/graphql/codegen/src/core/generate.ts index 3dccf5b22..034eb1ee2 100644 --- a/graphql/codegen/src/core/generate.ts +++ b/graphql/codegen/src/core/generate.ts @@ -4,9 +4,16 @@ * This is the primary entry point for programmatic usage. * The CLI is a thin wrapper around this function. */ +import * as fs from 'node:fs'; import path from 'node:path'; -import type { CliConfig, GraphQLSDKConfigTarget } from '../types/config'; +import { buildClientSchema, printSchema } from 'graphql'; + +import { PgpmPackage } from '@pgpmjs/core'; +import { createEphemeralDb, type EphemeralDbResult } from 'pgsql-client'; +import { deployPgpm } from 'pgsql-seed'; + +import type { CliConfig, DbConfig, GraphQLSDKConfigTarget, PgpmConfig } from '../types/config'; import { getConfigOptions } from '../types/config'; import type { CleanOperation, CleanTable } from '../types/schema'; import { generate as generateReactQueryFiles } from './codegen'; @@ -55,6 +62,9 @@ export interface GenerateOptions extends GraphQLSDKConfigTarget { verbose?: boolean; dryRun?: boolean; skipCustomOperations?: boolean; + schemaOnly?: boolean; + schemaOnlyOutput?: string; + schemaOnlyFilename?: string; } export interface GenerateResult { @@ -99,7 +109,7 @@ export async function generate( const runOrm = runReactQuery || !!config.cli || (options.orm !== undefined ? !!options.orm : false); - if (!runReactQuery && !runOrm && !runCli) { + if (!options.schemaOnly && !runReactQuery && !runOrm && !runCli) { return { success: false, message: @@ -130,6 +140,42 @@ export async function generate( headers: config.headers, }); + if (options.schemaOnly) { + try { + console.log(`Fetching schema from ${source.describe()}...`); + const { introspection } = await source.fetch(); + const schema = buildClientSchema(introspection as any); + const sdl = printSchema(schema); + + if (!sdl.trim()) { + return { + success: false, + message: 'Schema introspection returned empty SDL.', + output: outputRoot, + }; + } + + const outDir = path.resolve(options.schemaOnlyOutput || outputRoot || '.'); + await fs.promises.mkdir(outDir, { recursive: true }); + const filename = options.schemaOnlyFilename || 'schema.graphql'; + const filePath = path.join(outDir, filename); + await fs.promises.writeFile(filePath, sdl, 'utf-8'); + + return { + success: true, + message: `Schema exported to ${filePath}`, + output: outDir, + filesWritten: [filePath], + }; + } catch (err) { + return { + success: false, + message: `Failed to export schema: ${err instanceof Error ? err.message : 'Unknown error'}`, + output: outputRoot, + }; + } + } + // Run pipeline let pipelineResult: Awaited>; try { @@ -383,11 +429,66 @@ export async function generate( }; } +export function expandApiNamesToMultiTarget( + config: GraphQLSDKConfigTarget, +): Record | null { + const apiNames = config.db?.apiNames; + if (!apiNames || apiNames.length <= 1) return null; + + const targets: Record = {}; + for (const apiName of apiNames) { + targets[apiName] = { + ...config, + db: { + ...config.db, + apiNames: [apiName], + }, + output: config.output + ? `${config.output}/${apiName}` + : `./generated/graphql/${apiName}`, + }; + } + return targets; +} + +export function expandSchemaDirToMultiTarget( + config: GraphQLSDKConfigTarget, +): Record | null { + const schemaDir = config.schemaDir; + if (!schemaDir) return null; + + const resolvedDir = path.resolve(schemaDir); + if (!fs.existsSync(resolvedDir) || !fs.statSync(resolvedDir).isDirectory()) { + return null; + } + + const graphqlFiles = fs.readdirSync(resolvedDir) + .filter((f) => f.endsWith('.graphql')) + .sort(); + + if (graphqlFiles.length === 0) return null; + + const targets: Record = {}; + for (const file of graphqlFiles) { + const name = path.basename(file, '.graphql'); + targets[name] = { + ...config, + schemaDir: undefined, + schemaFile: path.join(resolvedDir, file), + output: config.output + ? `${config.output}/${name}` + : `./generated/graphql/${name}`, + }; + } + return targets; +} + export interface GenerateMultiOptions { configs: Record; cliOverrides?: Partial; verbose?: boolean; dryRun?: boolean; + schemaOnly?: boolean; unifiedCli?: CliConfig | boolean; } @@ -396,26 +497,143 @@ export interface GenerateMultiResult { hasError: boolean; } +interface SharedPgpmSource { + key: string; + ephemeralDb: EphemeralDbResult; + deployed: boolean; +} + +function getPgpmSourceKey(pgpm: PgpmConfig): string | null { + if (pgpm.modulePath) return `module:${path.resolve(pgpm.modulePath)}`; + if (pgpm.workspacePath && pgpm.moduleName) + return `workspace:${path.resolve(pgpm.workspacePath)}:${pgpm.moduleName}`; + return null; +} + +function getModulePathFromPgpm( + pgpm: PgpmConfig, +): string { + if (pgpm.modulePath) return pgpm.modulePath; + if (pgpm.workspacePath && pgpm.moduleName) { + const workspace = new PgpmPackage(pgpm.workspacePath); + const moduleProject = workspace.getModuleProject(pgpm.moduleName); + const modulePath = moduleProject.getModulePath(); + if (!modulePath) { + throw new Error(`Module "${pgpm.moduleName}" not found in workspace`); + } + return modulePath; + } + throw new Error('Invalid PGPM config: requires modulePath or workspacePath+moduleName'); +} + +async function prepareSharedPgpmSources( + configs: Record, + cliOverrides?: Partial, +): Promise> { + const sharedSources = new Map(); + const pgpmTargetCount = new Map(); + + for (const name of Object.keys(configs)) { + const merged = { ...configs[name], ...(cliOverrides ?? {}) }; + const pgpm = merged.db?.pgpm; + if (!pgpm) continue; + const key = getPgpmSourceKey(pgpm); + if (!key) continue; + pgpmTargetCount.set(key, (pgpmTargetCount.get(key) ?? 0) + 1); + } + + for (const [key, count] of pgpmTargetCount) { + if (count < 2) continue; + + let pgpmConfig: PgpmConfig | undefined; + for (const name of Object.keys(configs)) { + const merged = { ...configs[name], ...(cliOverrides ?? {}) }; + const pgpm = merged.db?.pgpm; + if (pgpm && getPgpmSourceKey(pgpm) === key) { + pgpmConfig = pgpm; + break; + } + } + if (!pgpmConfig) continue; + + const ephemeralDb = createEphemeralDb({ + prefix: 'codegen_pgpm_shared_', + verbose: false, + }); + + const modulePath = getModulePathFromPgpm(pgpmConfig); + await deployPgpm(ephemeralDb.config, modulePath, false); + + sharedSources.set(key, { + key, + ephemeralDb, + deployed: true, + }); + + console.log( + `[multi-target] Shared PGPM source deployed once for ${count} targets: ${key}`, + ); + } + + return sharedSources; +} + +function applySharedPgpmDb( + config: GraphQLSDKConfigTarget, + sharedSources: Map, +): GraphQLSDKConfigTarget { + const pgpm = config.db?.pgpm; + if (!pgpm) return config; + + const key = getPgpmSourceKey(pgpm); + if (!key) return config; + + const shared = sharedSources.get(key); + if (!shared) return config; + + const sharedDbConfig: DbConfig = { + ...config.db, + pgpm: undefined, + config: shared.ephemeralDb.config, + keepDb: true, + }; + + return { + ...config, + db: sharedDbConfig, + }; +} + export async function generateMulti( options: GenerateMultiOptions, ): Promise { - const { configs, cliOverrides, verbose, dryRun, unifiedCli } = options; + const { configs, cliOverrides, verbose, dryRun, schemaOnly, unifiedCli } = options; const names = Object.keys(configs); const results: Array<{ name: string; result: GenerateResult }> = []; let hasError = false; const targetInfos: RootRootReadmeTarget[] = []; - const useUnifiedCli = !!unifiedCli && names.length > 1; + const useUnifiedCli = !schemaOnly && !!unifiedCli && names.length > 1; const cliTargets: MultiTargetCliTarget[] = []; + const sharedSources = await prepareSharedPgpmSources(configs, cliOverrides); + + try { for (const name of names) { - const targetConfig = { + const baseConfig = { ...configs[name], ...(cliOverrides ?? {}), }; + const targetConfig = applySharedPgpmDb(baseConfig, sharedSources); const result = await generate( - { ...targetConfig, verbose, dryRun }, + { + ...targetConfig, + verbose, + dryRun, + schemaOnly, + schemaOnlyFilename: schemaOnly ? `${name}.graphql` : undefined, + }, useUnifiedCli ? { skipCli: true } : undefined, ); results.push({ name, result }); @@ -524,5 +742,12 @@ export async function generateMulti( ); } + } finally { + for (const shared of sharedSources.values()) { + const keepDb = Object.values(configs).some((c) => c.db?.keepDb); + shared.ephemeralDb.teardown({ keepDb }); + } + } + return { results, hasError }; } diff --git a/graphql/codegen/src/core/index.ts b/graphql/codegen/src/core/index.ts index 6b70c545d..3cae1c15c 100644 --- a/graphql/codegen/src/core/index.ts +++ b/graphql/codegen/src/core/index.ts @@ -17,11 +17,9 @@ export * from './ast'; export * from './custom-ast'; // Query builder -/** @deprecated Legacy v4 query builder — use v5 ORM codegen instead */ export { MetaObject, QueryBuilder } from './query-builder'; // Meta object utilities -/** @deprecated Legacy v4 meta-object utilities — v5 uses standard introspection */ export { convertFromMetaSchema, validateMetaObject } from './meta-object'; // Configuration loading and resolution diff --git a/graphql/codegen/src/core/introspect/source/database.ts b/graphql/codegen/src/core/introspect/source/database.ts index 5071fbe0b..851354483 100644 --- a/graphql/codegen/src/core/introspect/source/database.ts +++ b/graphql/codegen/src/core/introspect/source/database.ts @@ -6,8 +6,9 @@ */ import { buildSchema, introspectionFromSchema } from 'graphql'; +import { buildSchemaSDL } from 'graphile-schema'; + import type { IntrospectionQueryResponse } from '../../../types/introspection'; -import { buildSchemaSDLFromDatabase } from '../../database'; import { createDatabasePool, resolveApiSchemas, @@ -80,7 +81,7 @@ export class DatabaseSchemaSource implements SchemaSource { // Build SDL from database let sdl: string; try { - sdl = await buildSchemaSDLFromDatabase({ + sdl = await buildSchemaSDL({ database, schemas, }); diff --git a/graphql/codegen/src/core/introspect/source/pgpm-module.ts b/graphql/codegen/src/core/introspect/source/pgpm-module.ts index 2a02fa70b..e268872a0 100644 --- a/graphql/codegen/src/core/introspect/source/pgpm-module.ts +++ b/graphql/codegen/src/core/introspect/source/pgpm-module.ts @@ -13,8 +13,9 @@ import { getPgPool } from 'pg-cache'; import { createEphemeralDb, type EphemeralDbResult } from 'pgsql-client'; import { deployPgpm } from 'pgsql-seed'; +import { buildSchemaSDL } from 'graphile-schema'; + import type { IntrospectionQueryResponse } from '../../../types/introspection'; -import { buildSchemaSDLFromDatabase } from '../../database'; import { resolveApiSchemas, validateServicesSchemas } from './api-schemas'; import type { SchemaSource, SchemaSourceResult } from './types'; import { SchemaSourceError } from './types'; @@ -199,7 +200,7 @@ export class PgpmModuleSchemaSource implements SchemaSource { // Build SDL from the deployed database let sdl: string; try { - sdl = await buildSchemaSDLFromDatabase({ + sdl = await buildSchemaSDL({ database: dbConfig.database, schemas, }); diff --git a/graphql/codegen/src/core/output/index.ts b/graphql/codegen/src/core/output/index.ts index d5047a2fc..e4cbae29b 100644 --- a/graphql/codegen/src/core/output/index.ts +++ b/graphql/codegen/src/core/output/index.ts @@ -3,7 +3,6 @@ */ export { - formatOutput, type GeneratedFile, writeGeneratedFiles, type WriteOptions, diff --git a/graphql/codegen/src/core/output/writer.ts b/graphql/codegen/src/core/output/writer.ts index 630a047d5..2f6de1a13 100644 --- a/graphql/codegen/src/core/output/writer.ts +++ b/graphql/codegen/src/core/output/writer.ts @@ -225,43 +225,3 @@ function findTsFiles(dir: string): string[] { return files; } - -/** - * Format generated files using oxfmt programmatically - * - * @deprecated Use writeGeneratedFiles with formatFiles option instead. - * This function is kept for backwards compatibility. - */ -export async function formatOutput( - outputDir: string, -): Promise<{ success: boolean; error?: string }> { - const formatFn = await getOxfmtFormat(); - if (!formatFn) { - return { - success: false, - error: 'oxfmt not available. Install it with: npm install oxfmt', - }; - } - - const absoluteOutputDir = path.resolve(outputDir); - - try { - // Find all .ts files in the output directory - const tsFiles = findTsFiles(absoluteOutputDir); - - for (const filePath of tsFiles) { - const content = fs.readFileSync(filePath, 'utf-8'); - const formatted = await formatFileContent( - path.basename(filePath), - content, - formatFn, - ); - fs.writeFileSync(filePath, formatted, 'utf-8'); - } - - return { success: true }; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - return { success: false, error: message }; - } -} diff --git a/graphql/codegen/src/generators/index.ts b/graphql/codegen/src/generators/index.ts index dd6e4a082..d4420dfee 100644 --- a/graphql/codegen/src/generators/index.ts +++ b/graphql/codegen/src/generators/index.ts @@ -1,8 +1,5 @@ /** * Query and mutation generator exports - * - * @deprecated Legacy v4 generators — use v5 ORM codegen pipeline instead. - * These are retained for backward compatibility with existing v4 consumers. */ // Field selector utilities diff --git a/graphql/codegen/src/index.ts b/graphql/codegen/src/index.ts index e1260dc59..56ea02d77 100644 --- a/graphql/codegen/src/index.ts +++ b/graphql/codegen/src/index.ts @@ -23,12 +23,13 @@ export { defineConfig } from './types/config'; // Main generate function (orchestrates the entire pipeline) export type { GenerateOptions, GenerateResult, GenerateMultiOptions, GenerateMultiResult } from './core/generate'; -export { generate, generateMulti } from './core/generate'; +export { generate, generateMulti, expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget } from './core/generate'; // Config utilities export { findConfigFile, loadConfigFile } from './core/config'; // CLI shared utilities (for packages/cli to import) +export { runCodegenHandler } from './cli/handler'; export type { CodegenAnswers } from './cli/shared'; export { buildDbConfig, @@ -50,7 +51,4 @@ export type { BuildSchemaFromDatabaseOptions, BuildSchemaFromDatabaseResult, } from './core/database'; -export { - buildSchemaFromDatabase, - buildSchemaSDLFromDatabase, -} from './core/database'; +export { buildSchemaFromDatabase } from './core/database'; diff --git a/graphql/codegen/src/types/config.ts b/graphql/codegen/src/types/config.ts index 4dc7b946e..0ca13a680 100644 --- a/graphql/codegen/src/types/config.ts +++ b/graphql/codegen/src/types/config.ts @@ -221,6 +221,13 @@ export interface GraphQLSDKConfigTarget { */ schemaFile?: string; + /** + * Path to a directory of .graphql schema files for multi-target generation. + * Each *.graphql file becomes its own target, named by the filename (without extension). + * e.g. schemas/app.graphql + schemas/admin.graphql → targets "app" and "admin" + */ + schemaDir?: string; + /** * Database configuration for direct database introspection or PGPM module * Use db.schemas or db.apiNames to specify which schemas to introspect diff --git a/graphql/env/package.json b/graphql/env/package.json index 81a17e859..fed638503 100644 --- a/graphql/env/package.json +++ b/graphql/env/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/graphql/query/package.json b/graphql/query/package.json index 42db4e178..a9e232670 100644 --- a/graphql/query/package.json +++ b/graphql/query/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/graphql/server-test/__tests__/schema-snapshot.test.ts b/graphql/server-test/__tests__/schema-snapshot.test.ts index 54dd9fb26..a2b9b2c22 100644 --- a/graphql/server-test/__tests__/schema-snapshot.test.ts +++ b/graphql/server-test/__tests__/schema-snapshot.test.ts @@ -23,7 +23,7 @@ */ import path from 'path'; -import { buildSchemaSDL } from '@constructive-io/graphql-server'; +import { buildSchemaSDL } from 'graphile-schema'; import { getConnections, seed } from '../src'; jest.setTimeout(60000); diff --git a/graphql/server-test/package.json b/graphql/server-test/package.json index 4e268df1b..5b2c51924 100644 --- a/graphql/server-test/package.json +++ b/graphql/server-test/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { @@ -40,6 +40,7 @@ "@constructive-io/graphql-types": "workspace:^", "@pgpmjs/types": "workspace:^", "express": "^5.2.1", + "graphile-schema": "workspace:^", "graphql": "15.10.1", "pg": "^8.17.1", "pg-cache": "workspace:^", diff --git a/graphql/server/README.md b/graphql/server/README.md index 3d07f2768..5a39463e5 100644 --- a/graphql/server/README.md +++ b/graphql/server/README.md @@ -118,10 +118,6 @@ Configuration is merged from defaults, config files, and env vars via `@construc Use `supertest` or your HTTP client of choice against `/graphql`. For RLS-aware tests, provide a `Bearer` token and ensure the API's auth function is available. -## Codegen - -For local codegen test, use `PORT=5555 API_ENABLE_META=true PGDATABASE=launchql pnpm dev` - ## Related Packages - `@constructive-io/graphql-env` - env parsing + defaults for GraphQL diff --git a/graphql/server/package.json b/graphql/server/package.json index c799f61a3..6b2aedd38 100644 --- a/graphql/server/package.json +++ b/graphql/server/package.json @@ -29,10 +29,7 @@ "lint": "eslint . --fix", "test": "jest --passWithNoTests", "test:watch": "jest --watch", - "bucket:create": "ts-node src/scripts/create-bucket.ts", - "codegen:schema": "ts-node src/scripts/codegen-schema.ts", - "codegen:clean": "rimraf src/codegen/orm codegen/schema.graphql", - "codegen": "npm run codegen:clean && npm run codegen:schema && graphql-codegen generate-orm --schema codegen/schema.graphql --output src/codegen/orm" + "bucket:create": "ts-node src/scripts/create-bucket.ts" }, "keywords": [ "server", @@ -85,7 +82,6 @@ "graphile-test": "workspace:*", "makage": "^0.1.10", "nodemon": "^3.1.10", - "rimraf": "^6.1.2", "ts-node": "^10.9.2" } } diff --git a/graphql/server/src/index.ts b/graphql/server/src/index.ts index 36b1118d4..803e7fe13 100644 --- a/graphql/server/src/index.ts +++ b/graphql/server/src/index.ts @@ -1,5 +1,4 @@ export * from './server'; -export * from './schema'; // Export options module - types, defaults, type guards, and utility functions export * from './options'; diff --git a/graphql/server/src/scripts/codegen-schema.ts b/graphql/server/src/scripts/codegen-schema.ts deleted file mode 100644 index 24e74a7c6..000000000 --- a/graphql/server/src/scripts/codegen-schema.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { getEnvOptions } from '@constructive-io/graphql-env'; -import { Logger } from '@pgpmjs/logger'; -import { ConstructivePreset, makePgService } from 'graphile-settings'; -import { makeSchema } from 'graphile-build'; -import { buildConnectionString, getPgPool } from 'pg-cache'; -import { printSchema } from 'graphql'; -import { promises as fs } from 'node:fs'; -import path from 'node:path'; -import type { GraphileConfig } from 'graphile-config'; - -const log = new Logger('codegen-schema'); - -const getSchemaOutputPath = () => - process.env.CODEGEN_SCHEMA_OUT ?? - path.resolve(__dirname, '../../codegen/schema.graphql'); - -(async () => { - try { - const opts = getEnvOptions(); - const apiOpts = (opts as any).api || {}; - const metaSchemas = Array.isArray(apiOpts.metaSchemas) - ? apiOpts.metaSchemas - : []; - - let schemas: string[]; - let usingFallback = false; - - const dbName = - process.env.CODEGEN_DATABASE || process.env.PGDATABASE || 'constructive'; - const pgConfig = { ...opts.pg, database: dbName } as typeof opts.pg; - log.info(`Target database for codegen: ${dbName}`); - - if (metaSchemas.length) { - schemas = metaSchemas; - log.info(`Using meta schemas: ${schemas.join(', ')}`); - - const pool = getPgPool(pgConfig); - const checkResult = await pool.query( - `SELECT schema_name FROM information_schema.schemata - WHERE schema_name = ANY($1::text[]) - ORDER BY schema_name`, - [schemas] - ); - - const foundSchemas = checkResult.rows.map((r: any) => r.schema_name); - const missing = schemas.filter((s) => !foundSchemas.includes(s)); - - if (missing.length > 0) { - log.warn( - `Missing schemas: ${missing.join(', ')}. Falling back to 'public'.` - ); - schemas = ['public']; - usingFallback = true; - } - } else { - schemas = ['public']; - log.info('No meta schemas configured, using: public'); - usingFallback = true; - } - - if (usingFallback) { - log.warn( - 'Schema will be generated from public schema only. To generate full meta schema, ensure meta tables exist.' - ); - } - - const pool = getPgPool(pgConfig); - log.debug(`Connecting to database: ${dbName}`); - log.debug(`Connecting to host: ${opts.pg?.host || '(default)'}`); - - const dbInfo = await pool.query( - 'SELECT current_database() AS current_database, current_user AS current_user' - ); - log.info( - `Connected to database: ${dbInfo.rows[0]?.current_database} as user: ${dbInfo.rows[0]?.current_user}` - ); - - const connectionString = buildConnectionString( - pgConfig.user, pgConfig.password, pgConfig.host, pgConfig.port, dbName - ); - - // Create v5 preset with ConstructivePreset - const preset: GraphileConfig.Preset = { - extends: [ConstructivePreset], - pgServices: [ - makePgService({ - connectionString, - schemas, - }), - ], - }; - - const { schema: graphqlSchema } = await makeSchema(preset); - const sdl = printSchema(graphqlSchema); - - const outputPath = getSchemaOutputPath(); - await fs.mkdir(path.dirname(outputPath), { recursive: true }); - await fs.writeFile(outputPath, sdl, 'utf8'); - - log.success(`Schema written to ${outputPath}`); - } catch (error: any) { - log.error('Failed to write schema', error?.stack || error); - process.exitCode = 1; - } -})(); diff --git a/graphql/server/tsconfig.esm.json b/graphql/server/tsconfig.esm.json index b312f70fb..aef23b967 100644 --- a/graphql/server/tsconfig.esm.json +++ b/graphql/server/tsconfig.esm.json @@ -12,9 +12,7 @@ "node_modules", "**/*.spec.*", "**/*.test.*", - "src/codegen/**/*", "src/plugins/**/*", - "src/schema.ts", "src/scripts/**/*", "src/middleware/gql.ts" ] diff --git a/graphql/server/tsconfig.json b/graphql/server/tsconfig.json index 52e1ca50f..8bc5b231b 100644 --- a/graphql/server/tsconfig.json +++ b/graphql/server/tsconfig.json @@ -12,9 +12,7 @@ "node_modules", "**/*.spec.*", "**/*.test.*", - "src/codegen/**/*", "src/plugins/**/*", - "src/schema.ts", "src/scripts/**/*", "src/middleware/gql.ts" ] diff --git a/graphql/test/package.json b/graphql/test/package.json index 8121e6992..6f410339c 100644 --- a/graphql/test/package.json +++ b/graphql/test/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/jobs/knative-job-service/package.json b/jobs/knative-job-service/package.json index 9c70f7004..f0eb42b1c 100644 --- a/jobs/knative-job-service/package.json +++ b/jobs/knative-job-service/package.json @@ -30,7 +30,7 @@ "build:dev": "makage build --dev", "start": "node dist/run.js", "dev": "ts-node --transpile-only src/run.ts", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch", "test:debug": "node --inspect node_modules/.bin/jest --runInBand" }, diff --git a/jobs/knative-job-worker/package.json b/jobs/knative-job-worker/package.json index 744a9f14e..af04c28f1 100644 --- a/jobs/knative-job-worker/package.json +++ b/jobs/knative-job-worker/package.json @@ -25,7 +25,7 @@ "url": "https://github.com/constructive-io/jobs" }, "scripts": { - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch", "test:debug": "node --inspect node_modules/.bin/jest --runInBand", "clean": "makage clean", diff --git a/packages/12factor-env/package.json b/packages/12factor-env/package.json index 8a08ddd98..5c47cb79f 100644 --- a/packages/12factor-env/package.json +++ b/packages/12factor-env/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/packages/cli/AGENTS.md b/packages/cli/AGENTS.md index e22e6c7ea..23d63e7b8 100644 --- a/packages/cli/AGENTS.md +++ b/packages/cli/AGENTS.md @@ -3,7 +3,7 @@ The `@constructive-io/cli` package provides the user-facing CLI for the Constructive ecosystem. - **Binaries:** `constructive` (full) and `cnc` (shorthand) -- **What it covers:** Constructive GraphQL workflows only (server, explorer, schema tools, codegen) +- **What it covers:** Constructive GraphQL workflows only (server, explorer, codegen) **Note:** Database operations (init, add, deploy, revert, etc.) are handled by the separate `pgpm` CLI. Users should install both tools for the complete workflow. @@ -15,19 +15,19 @@ The `@constructive-io/cli` package provides the user-facing CLI for the Construc ## Commands -The CLI provides 4 GraphQL-focused commands: +The CLI provides GraphQL-focused commands: - `packages/cli/src/commands/server.ts` – start the Constructive GraphQL server - `packages/cli/src/commands/explorer.ts` – start the Constructive GraphQL explorer -- `packages/cli/src/commands/get-graphql-schema.ts` – emit schema SDL (DB build or endpoint introspection) -- `packages/cli/src/commands/codegen.ts` – run GraphQL codegen (`@constructive-io/graphql-codegen`) +- `packages/cli/src/commands/codegen.ts` – run GraphQL codegen (`@constructive-io/graphql-codegen`), including `--schema-only` for SDL export ## Debugging Tips - **Command routing:** `packages/cli/src/commands.ts` -- **Schema generation:** `packages/cli/src/commands/get-graphql-schema.ts` delegates schema building to `@constructive-io/graphql-server` +- **Codegen handler:** `packages/cli/src/commands/codegen.ts` delegates to `runCodegenHandler()` from `@constructive-io/graphql-codegen` +- **Schema building:** `graphile-schema` package provides `buildSchemaSDL` (from database) and `fetchEndpointSchemaSDL` (from endpoint) ## Tests -- `packages/cli/__tests__/*` covers the GraphQL commands (codegen, get-graphql-schema, cli) +- `packages/cli/__tests__/*` covers the GraphQL commands (codegen, cli) - Database/PGPM tests are located in `pgpm/cli/__tests__/` diff --git a/packages/cli/README.md b/packages/cli/README.md index 0e5e5e021..fb33e1ee0 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -95,27 +95,33 @@ cnc codegen --api-names my_api --output ./codegen --orm - `--dry-run` - Preview without writing files - `--verbose` - Verbose output -### `cnc get-graphql-schema` +### `cnc codegen --schema-only` -Fetch or build GraphQL schema SDL. +Export GraphQL schema SDL without running full code generation. Works with any source (endpoint, file, database, PGPM). ```bash # From database schemas -cnc get-graphql-schema --database mydb --schemas myapp,public --out ./schema.graphql +cnc codegen --schema-only --schemas myapp,public --output ./schemas # From running server -cnc get-graphql-schema --endpoint http://localhost:3000/graphql --out ./schema.graphql +cnc codegen --schema-only --endpoint http://localhost:3000/graphql --output ./schemas + +# From schema file (useful for converting/validating) +cnc codegen --schema-only --schema-file ./input.graphql --output ./schemas + +# From a directory of .graphql files (multi-target) +cnc codegen --schema-only --schema-dir ./schemas --output ./exported ``` **Options:** -- `--database ` - Database name (for programmatic builder) -- `--schemas ` - Comma-separated schemas to include (required unless using --endpoint) -- `--endpoint ` - GraphQL endpoint to fetch schema via introspection -- `--headerHost ` - Optional Host header for endpoint requests -- `--auth ` - Optional Authorization header value -- `--header "Name: Value"` - Optional HTTP header (repeatable) -- `--out ` - Output file path (prints to stdout if omitted) +- `--endpoint ` - GraphQL endpoint URL +- `--schema-file ` - Path to GraphQL schema file +- `--schemas ` - Comma-separated PostgreSQL schemas +- `--api-names ` - Comma-separated API names (multi-target when >1) +- `--schema-dir ` - Directory of .graphql files (auto-creates one target per file) +- `--output ` - Output directory (default: ./generated/graphql) +- `--authorization ` - Authorization header value ## Configuration diff --git a/packages/cli/__tests__/codegen.test.ts b/packages/cli/__tests__/codegen.test.ts index c61cdc34b..9282a8487 100644 --- a/packages/cli/__tests__/codegen.test.ts +++ b/packages/cli/__tests__/codegen.test.ts @@ -1,86 +1,15 @@ -import type { ParsedArgs, Question } from 'inquirerer' +import type { ParsedArgs } from 'inquirerer' import codegenCommand from '../src/commands/codegen' -const splitCommas = (input: string | undefined): string[] | undefined => { - if (!input) return undefined; - return input.split(',').map((s) => s.trim()).filter(Boolean); -}; +const mockRunCodegenHandler = jest.fn, [Record, unknown]>(); -jest.mock('@constructive-io/graphql-codegen', () => { - const splitCommasMock = (input: string | undefined): string[] | undefined => { - if (!input) return undefined; - return input.split(',').map((s: string) => s.trim()).filter(Boolean); - }; +jest.mock('@constructive-io/graphql-codegen', () => ({ + runCodegenHandler: (argv: Record, prompter: unknown) => mockRunCodegenHandler(argv, prompter), +})); - return { - generate: jest.fn(async () => ({ success: true, message: 'Generated SDK', filesWritten: [] as string[] })), - findConfigFile: jest.fn((): string | undefined => undefined), - loadConfigFile: jest.fn(async () => ({ success: false, error: 'not found' })), - splitCommas: splitCommasMock, - codegenQuestions: [ - { name: 'endpoint', message: 'GraphQL endpoint URL', type: 'text', required: false }, - { name: 'schema-file', message: 'Path to GraphQL schema file', type: 'text', required: false }, - { name: 'output', message: 'Output directory', type: 'text', required: false, default: 'codegen', useDefault: true }, - { name: 'schemas', message: 'PostgreSQL schemas', type: 'text', required: false, sanitize: splitCommasMock }, - { name: 'api-names', message: 'API names', type: 'text', required: false, sanitize: splitCommasMock }, - { name: 'react-query', message: 'Generate React Query hooks?', type: 'confirm', required: false, default: false, useDefault: true }, - { name: 'orm', message: 'Generate ORM client?', type: 'confirm', required: false, default: false, useDefault: true }, - { name: 'authorization', message: 'Authorization header value', type: 'text', required: false }, - { name: 'dry-run', message: 'Preview without writing files?', type: 'confirm', required: false, default: false, useDefault: true }, - { name: 'verbose', message: 'Verbose output?', type: 'confirm', required: false, default: false, useDefault: true }, - ], - printResult: jest.fn((result: any) => { - if (result.success) { - console.log('[ok]', result.message); - } else { - console.error('x', result.message); - process.exit(1); - } - }), - camelizeArgv: jest.fn((argv: Record) => argv), - seedArgvFromConfig: jest.fn((argv: Record, _fileConfig: any) => argv), - hasResolvedCodegenSource: jest.fn((argv: Record) => { - const db = argv.db as Record | undefined; - return Boolean( - argv.endpoint || - argv['schema-file'] || - argv.schemas || - argv['api-names'] || - db?.schemas || - db?.apiNames - ); - }), - buildGenerateOptions: jest.fn((answers: Record, _fileConfig: any) => { - const { schemas, apiNames, ...rest } = answers; - const normalizedSchemas = Array.isArray(schemas) - ? schemas - : splitCommasMock(schemas as string | undefined); - const normalizedApiNames = Array.isArray(apiNames) - ? apiNames - : splitCommasMock(apiNames as string | undefined); - if (schemas || apiNames) { - return { ...rest, db: { schemas: normalizedSchemas, apiNames: normalizedApiNames } }; - } - return rest; - }), - }; -}) - -// Create a mock prompter that returns argv values and applies sanitize functions from questions const createMockPrompter = () => ({ - prompt: jest.fn(async (argv: any, questions: Question[]) => { - const result = { ...argv }; - // Apply sanitize functions from questions to simulate real prompter behavior - if (questions) { - for (const q of questions) { - if (q.sanitize && result[q.name] !== undefined) { - result[q.name] = q.sanitize(result[q.name], result); - } - } - } - return result; - }) -}) + prompt: jest.fn(async (argv: any) => argv), +}); describe('codegen command', () => { beforeEach(() => { @@ -98,14 +27,13 @@ describe('codegen command', () => { expect(spyLog).toHaveBeenCalled() const first = (spyLog.mock.calls[0]?.[0] as string) || '' expect(first).toContain('Constructive GraphQL Codegen') + expect(mockRunCodegenHandler).not.toHaveBeenCalled() spyLog.mockRestore() spyExit.mockRestore() }) - it('calls generate with endpoint flow options', async () => { - const { generate: mockGenerate } = require('@constructive-io/graphql-codegen') - + it('delegates to runCodegenHandler for endpoint flow', async () => { const argv: Partial = { endpoint: 'http://localhost:3000/graphql', authorization: 'Bearer testtoken', @@ -118,22 +46,10 @@ describe('codegen command', () => { await codegenCommand(argv, mockPrompter as any, {} as any) - expect(mockPrompter.prompt).not.toHaveBeenCalled() - expect(mockGenerate).toHaveBeenCalled() - const call = mockGenerate.mock.calls[0][0] - expect(call).toMatchObject({ - endpoint: 'http://localhost:3000/graphql', - output: 'graphql/codegen/dist', - authorization: 'Bearer testtoken', - verbose: true, - dryRun: true, - reactQuery: true - }) + expect(mockRunCodegenHandler).toHaveBeenCalledWith(argv, mockPrompter) }) - it('calls generate with db options when schemas provided', async () => { - const { generate: mockGenerate } = require('@constructive-io/graphql-codegen') - + it('delegates to runCodegenHandler for db options', async () => { const argv: Partial = { schemas: 'public,app', output: 'graphql/codegen/dist', @@ -143,11 +59,6 @@ describe('codegen command', () => { await codegenCommand(argv, mockPrompter as any, {} as any) - expect(mockPrompter.prompt).not.toHaveBeenCalled() - expect(mockGenerate).toHaveBeenCalled() - const call = mockGenerate.mock.calls[0][0] - expect(call.db).toEqual({ schemas: ['public', 'app'], apiNames: undefined }) - expect(call.output).toBe('graphql/codegen/dist') - expect(call.reactQuery).toBe(true) + expect(mockRunCodegenHandler).toHaveBeenCalledWith(argv, mockPrompter) }) }) diff --git a/packages/cli/__tests__/get-graphql-schema.test.ts b/packages/cli/__tests__/get-graphql-schema.test.ts deleted file mode 100644 index 587ddfa27..000000000 --- a/packages/cli/__tests__/get-graphql-schema.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import fs from 'fs'; -import os from 'os'; -import path from 'path'; -import runGetGraphqlSchema from '../src/commands/get-graphql-schema'; - -jest.mock('@constructive-io/graphql-server', () => ({ - buildSchemaSDL: jest.fn(async () => 'type Query { hello: String }\n'), - fetchEndpointSchemaSDL: jest.fn(async () => 'type Query { greeting: String }\n') -})); - -jest.setTimeout(15000); - -describe('cnc get-graphql-schema (mocked)', () => { - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'constructive-cli-test-')); - const outDbFile = path.join(tempDir, 'schema.db.graphql'); - const outEndpointFile = path.join(tempDir, 'schema.endpoint.graphql'); - const outEndpointHeaderHostFile = path.join(tempDir, 'schema.endpoint.headerhost.graphql'); - const outEndpointAuthFile = path.join(tempDir, 'schema.endpoint.auth.graphql'); - const outEndpointHeadersFile = path.join(tempDir, 'schema.endpoint.headers.graphql'); - const outEndpointAuthAndHeaderFile = path.join(tempDir, 'schema.endpoint.auth_and_header.graphql'); - - const prompter: any = { - prompt: async (argv: any) => argv, - close: jest.fn() - }; - - afterAll(() => { - try { - fs.rmSync(tempDir, { recursive: true, force: true }); - } catch (_) { - // ignore - } - }); - - it('writes SDL to file when building from database', async () => { - const argv: any = { - _: ['get-graphql-schema'], - database: 'mockdb', - schemas: 'public', - out: outDbFile - }; - - await runGetGraphqlSchema(argv, prompter, {} as any); - - expect(fs.existsSync(outDbFile)).toBe(true); - const sdl = fs.readFileSync(outDbFile, 'utf8'); - expect(sdl).toContain('type Query'); - expect(sdl).toContain('hello'); - }); - - it('writes SDL to file when fetching from endpoint', async () => { - const argv: any = { - _: ['get-graphql-schema'], - endpoint: 'http://localhost:5555/graphql', - out: outEndpointFile - }; - - await runGetGraphqlSchema(argv, prompter, {} as any); - - expect(fs.existsSync(outEndpointFile)).toBe(true); - const sdl = fs.readFileSync(outEndpointFile, 'utf8'); - expect(sdl).toContain('type Query'); - expect(sdl).toContain('greeting'); - }); - - it('passes headerHost to fetchEndpointSchemaSDL when provided', async () => { - const argv: any = { - _: ['get-graphql-schema'], - endpoint: 'http://localhost:5555/graphql', - headerHost: 'meta8.localhost', - out: outEndpointHeaderHostFile - }; - - await runGetGraphqlSchema(argv, prompter, {} as any); - - // Verify file written - expect(fs.existsSync(outEndpointHeaderHostFile)).toBe(true); - const sdl = fs.readFileSync(outEndpointHeaderHostFile, 'utf8'); - expect(sdl).toContain('type Query'); - expect(sdl).toContain('greeting'); - - // Verify the mocked function received the headerHost argument - const server = jest.requireMock('@constructive-io/graphql-server') as any; - expect(server.fetchEndpointSchemaSDL).toHaveBeenCalled(); - const lastCall = server.fetchEndpointSchemaSDL.mock.calls[server.fetchEndpointSchemaSDL.mock.calls.length - 1]; - expect(lastCall[0]).toBe('http://localhost:5555/graphql'); - expect(lastCall[1]).toEqual({ headerHost: 'meta8.localhost' }); - }); - - it('passes auth to fetchEndpointSchemaSDL when provided', async () => { - const argv: any = { - _: ['get-graphql-schema'], - endpoint: 'http://localhost:5555/graphql', - auth: 'Bearer 123', - out: outEndpointAuthFile - }; - - await runGetGraphqlSchema(argv, prompter, {} as any); - - // Verify file written - expect(fs.existsSync(outEndpointAuthFile)).toBe(true); - const sdl = fs.readFileSync(outEndpointAuthFile, 'utf8'); - expect(sdl).toContain('type Query'); - expect(sdl).toContain('greeting'); - - // Verify the mocked function received the auth argument - const server = jest.requireMock('@constructive-io/graphql-server') as any; - expect(server.fetchEndpointSchemaSDL).toHaveBeenCalled(); - const lastCall = server.fetchEndpointSchemaSDL.mock.calls[server.fetchEndpointSchemaSDL.mock.calls.length - 1]; - expect(lastCall[0]).toBe('http://localhost:5555/graphql'); - expect(lastCall[1]).toEqual({ auth: 'Bearer 123' }); - }); - - it('passes repeated --header values to fetchEndpointSchemaSDL when provided', async () => { - const argv: any = { - _: ['get-graphql-schema'], - endpoint: 'http://localhost:5555/graphql', - header: ['X-Mode: fast', 'Authorization: Bearer ABC'], - out: outEndpointHeadersFile - }; - - await runGetGraphqlSchema(argv, prompter, {} as any); - - // Verify file written - expect(fs.existsSync(outEndpointHeadersFile)).toBe(true); - const sdl = fs.readFileSync(outEndpointHeadersFile, 'utf8'); - expect(sdl).toContain('type Query'); - expect(sdl).toContain('greeting'); - - // Verify the mocked function received the headers argument - const server = jest.requireMock('@constructive-io/graphql-server') as any; - expect(server.fetchEndpointSchemaSDL).toHaveBeenCalled(); - const lastCall = server.fetchEndpointSchemaSDL.mock.calls[server.fetchEndpointSchemaSDL.mock.calls.length - 1]; - expect(lastCall[0]).toBe('http://localhost:5555/graphql'); - expect(lastCall[1]).toEqual({ headers: { 'X-Mode': 'fast', Authorization: 'Bearer ABC' } }); - }); - - it('forwards both auth and header to fetchEndpointSchemaSDL', async () => { - const argv: any = { - _: ['get-graphql-schema'], - endpoint: 'http://localhost:5555/graphql', - auth: 'Bearer 123', - header: ['Authorization: Bearer override', 'X-Mode: fast'], - out: outEndpointAuthAndHeaderFile - }; - - await runGetGraphqlSchema(argv, prompter, {} as any); - - // Verify file written - expect(fs.existsSync(outEndpointAuthAndHeaderFile)).toBe(true); - const sdl = fs.readFileSync(outEndpointAuthAndHeaderFile, 'utf8'); - expect(sdl).toContain('type Query'); - expect(sdl).toContain('greeting'); - - // Verify the mocked function received both auth and headers - const server = jest.requireMock('@constructive-io/graphql-server') as any; - expect(server.fetchEndpointSchemaSDL).toHaveBeenCalled(); - const lastCall = server.fetchEndpointSchemaSDL.mock.calls[server.fetchEndpointSchemaSDL.mock.calls.length - 1]; - expect(lastCall[0]).toBe('http://localhost:5555/graphql'); - expect(lastCall[1]).toEqual({ auth: 'Bearer 123', headers: { Authorization: 'Bearer override', 'X-Mode': 'fast' } }); - }); -}); diff --git a/packages/cli/package.json b/packages/cli/package.json index 79d1b8206..4a694bfc9 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -30,7 +30,7 @@ "build:dev": "makage build --dev", "dev": "ts-node ./src/index.ts", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index f4d8392e7..12444bab0 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -6,7 +6,6 @@ import codegen from './commands/codegen'; import context from './commands/context'; import execute from './commands/execute'; import explorer from './commands/explorer'; -import getGraphqlSchema from './commands/get-graphql-schema'; import jobs from './commands/jobs'; import server from './commands/server'; import { usageText } from './utils'; @@ -15,7 +14,6 @@ const createCommandMap = (): Record => { return { server, explorer, - 'get-graphql-schema': getGraphqlSchema, codegen, jobs, context, diff --git a/packages/cli/src/commands/codegen.ts b/packages/cli/src/commands/codegen.ts index 58d47abe7..a96649050 100644 --- a/packages/cli/src/commands/codegen.ts +++ b/packages/cli/src/commands/codegen.ts @@ -1,15 +1,5 @@ -import { CLIOptions, Inquirerer } from 'inquirerer'; -import { - generate, - findConfigFile, - loadConfigFile, - codegenQuestions, - printResult, - buildGenerateOptions, - seedArgvFromConfig, - hasResolvedCodegenSource, - type GraphQLSDKConfigTarget, -} from '@constructive-io/graphql-codegen'; +import type { CLIOptions, Inquirerer } from 'inquirerer'; +import { runCodegenHandler } from '@constructive-io/graphql-codegen'; const usage = ` Constructive GraphQL Codegen: @@ -20,6 +10,7 @@ Source Options (choose one): --config Path to graphql-codegen config file --endpoint GraphQL endpoint URL --schema-file Path to GraphQL schema file + --schema-dir Directory of .graphql files (auto-expands to multi-target) Database Options: --schemas Comma-separated PostgreSQL schemas @@ -29,10 +20,16 @@ Generator Options: --react-query Generate React Query hooks (default) --orm Generate ORM client --output Output directory (default: codegen) + --target Target name (for multi-target configs) --authorization Authorization header value --dry-run Preview without writing files --verbose Verbose output +Schema Export: + --schema-only Export GraphQL SDL instead of running full codegen. + Works with any source (endpoint, file, database, PGPM). + With multiple apiNames, writes one .graphql per API. + --help, -h Show this help message `; @@ -46,28 +43,5 @@ export default async ( process.exit(0); } - const hasSourceFlags = Boolean( - argv.endpoint || argv['schema-file'] || argv.schemas || argv['api-names'] - ); - const configPath = (argv.config as string | undefined) || - (!hasSourceFlags ? findConfigFile() : undefined); - - let fileConfig: GraphQLSDKConfigTarget = {}; - - if (configPath) { - const loaded = await loadConfigFile(configPath); - if (!loaded.success) { - console.error('x', loaded.error); - process.exit(1); - } - fileConfig = loaded.config as GraphQLSDKConfigTarget; - } - - const seeded = seedArgvFromConfig(argv as Record, fileConfig); - const answers = hasResolvedCodegenSource(seeded) - ? seeded - : await prompter.prompt(seeded, codegenQuestions); - const options = buildGenerateOptions(answers, fileConfig); - const result = await generate(options); - printResult(result); + await runCodegenHandler(argv as Record, prompter); }; diff --git a/packages/cli/src/commands/get-graphql-schema.ts b/packages/cli/src/commands/get-graphql-schema.ts deleted file mode 100644 index f44cf2ee0..000000000 --- a/packages/cli/src/commands/get-graphql-schema.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { CLIOptions, Inquirerer, Question } from 'inquirerer'; -import { promises as fs } from 'fs'; -import { buildSchemaSDL, fetchEndpointSchemaSDL } from '@constructive-io/graphql-server'; - -const usage = ` -Constructive Get GraphQL Schema: - - cnc get-graphql-schema [OPTIONS] - -Source Options (choose one): - --endpoint GraphQL endpoint to fetch schema via introspection - --database Database name (default: constructive) - -Options: - --schemas Comma-separated schemas to include - --headerHost Host header to send with endpoint requests - --auth Authorization header value - --out Output file path (default: print to stdout) - - --help, -h Show this help message -`; - -const questions: Question[] = [ - { - name: 'endpoint', - message: 'GraphQL endpoint URL', - type: 'text', - required: false, - }, - { - name: 'database', - message: 'Database name', - type: 'text', - required: false, - }, - { - name: 'schemas', - message: 'Comma-separated schemas', - type: 'text', - required: false, - }, - { - name: 'out', - message: 'Output file path', - type: 'text', - required: false, - }, -]; - -export default async ( - argv: Partial>, - prompter: Inquirerer, - _options: CLIOptions -) => { - if (argv.help || argv.h) { - console.log(usage); - process.exit(0); - } - - const { - endpoint, - database, - schemas: schemasArg, - out, - } = await prompter.prompt(argv, questions); - - const schemas = String(schemasArg).split(',').map((s) => s.trim()).filter(Boolean); - - // Parse repeated --header values into headers object - const headerArg = argv.header as string | string[] | undefined; - const headerList = Array.isArray(headerArg) ? headerArg : headerArg ? [headerArg] : []; - const headers: Record = {}; - for (const h of headerList) { - const idx = typeof h === 'string' ? h.indexOf(':') : -1; - if (idx <= 0) continue; - const name = h.slice(0, idx).trim(); - const value = h.slice(idx + 1).trim(); - if (!name) continue; - headers[name] = value; - } - - let sdl: string; - if (endpoint) { - const opts: Record = {}; - if (argv.headerHost) opts.headerHost = argv.headerHost; - if (argv.auth) opts.auth = argv.auth; - if (Object.keys(headers).length) opts.headers = headers; - sdl = await (fetchEndpointSchemaSDL as (url: string, opts?: Record) => Promise)( - endpoint as string, - opts - ); - } else { - sdl = await buildSchemaSDL({ database: database as string, schemas }); - } - - if (out) { - await fs.writeFile(out as string, sdl, 'utf8'); - console.log(`Wrote schema SDL to ${out}`); - } else { - process.stdout.write(sdl + '\n'); - } -}; diff --git a/packages/cli/src/utils/display.ts b/packages/cli/src/utils/display.ts index ed2571278..e84b4f988 100644 --- a/packages/cli/src/utils/display.ts +++ b/packages/cli/src/utils/display.ts @@ -10,7 +10,6 @@ export const usageText = ` Code Generation: codegen Generate TypeScript types and SDK from GraphQL schema - get-graphql-schema Fetch or build GraphQL schema SDL Jobs: jobs up Start combined server (jobs runtime) @@ -34,7 +33,7 @@ export const usageText = ` cnc server --port 8080 Start server on custom port cnc explorer Launch GraphiQL explorer cnc codegen --schema schema.graphql Generate types from schema - cnc get-graphql-schema --out schema.graphql Export schema SDL + cnc codegen --schema-only --out schema.graphql Export schema SDL cnc jobs up Start combined server (jobs runtime) # Execution Engine diff --git a/packages/client/package.json b/packages/client/package.json index 16e2628e4..5736d3dbd 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/packages/csrf/package.json b/packages/csrf/package.json index b6fc21623..9c0a976ce 100644 --- a/packages/csrf/package.json +++ b/packages/csrf/package.json @@ -16,7 +16,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/packages/oauth/package.json b/packages/oauth/package.json index 79853417a..c0b790c2f 100644 --- a/packages/oauth/package.json +++ b/packages/oauth/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/packages/orm/package.json b/packages/orm/package.json index bc67ba1eb..38c228d34 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/packages/postmaster/package.json b/packages/postmaster/package.json index 1439340c1..df18c4e7a 100644 --- a/packages/postmaster/package.json +++ b/packages/postmaster/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/packages/query-builder/package.json b/packages/query-builder/package.json index deb410970..1a01757a9 100644 --- a/packages/query-builder/package.json +++ b/packages/query-builder/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/packages/smtppostmaster/package.json b/packages/smtppostmaster/package.json index 167de79cc..774d36518 100644 --- a/packages/smtppostmaster/package.json +++ b/packages/smtppostmaster/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch", "test:send": "ts-node __tests__/test-send.ts", "test:send:dev": "ts-node __tests__/test-send.ts" diff --git a/packages/url-domains/package.json b/packages/url-domains/package.json index 080d496ac..6b8ebad06 100644 --- a/packages/url-domains/package.json +++ b/packages/url-domains/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/pgpm/cli/package.json b/pgpm/cli/package.json index 15c83edd1..1881560e2 100644 --- a/pgpm/cli/package.json +++ b/pgpm/cli/package.json @@ -29,7 +29,7 @@ "build:dev": "makage build --dev", "dev": "ts-node ./src/index.ts", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/pgpm/core/package.json b/pgpm/core/package.json index bf29a02c2..34a7a12bd 100644 --- a/pgpm/core/package.json +++ b/pgpm/core/package.json @@ -28,7 +28,7 @@ "build": "makage build && npm run copy", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/pgpm/env/package.json b/pgpm/env/package.json index 185cd67a7..6d1b47f69 100644 --- a/pgpm/env/package.json +++ b/pgpm/env/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce9912cb8..58af7d00e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -256,6 +256,38 @@ importers: version: link:../../postgres/pgsql-test/dist publishDirectory: dist + graphile/graphile-schema: + dependencies: + deepmerge: + specifier: ^4.3.1 + version: 4.3.1 + graphile-build: + specifier: ^5.0.0-rc.3 + version: 5.0.0-rc.4(grafast@1.0.0-rc.4(graphql@16.12.0))(graphile-config@1.0.0-rc.3)(graphql@16.12.0) + graphile-config: + specifier: 1.0.0-rc.3 + version: 1.0.0-rc.3 + graphile-settings: + specifier: workspace:^ + version: link:../graphile-settings/dist + graphql: + specifier: ^16.9.0 + version: 16.12.0 + pg-cache: + specifier: workspace:^ + version: link:../../postgres/pg-cache/dist + pg-env: + specifier: workspace:^ + version: link:../../postgres/pg-env/dist + devDependencies: + makage: + specifier: ^0.1.10 + version: 0.1.12 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@20.19.27)(typescript@5.9.3) + publishDirectory: dist + graphile/graphile-search-plugin: dependencies: '@dataplan/pg': @@ -488,9 +520,6 @@ importers: '@babel/types': specifier: ^7.28.6 version: 7.28.6 - '@constructive-io/graphql-server': - specifier: workspace:^ - version: link:../server/dist '@constructive-io/graphql-types': specifier: workspace:^ version: link:../types/dist @@ -512,6 +541,9 @@ importers: gql-ast: specifier: workspace:^ version: link:../gql-ast/dist + graphile-schema: + specifier: workspace:^ + version: link:../../graphile/graphile-schema/dist graphql: specifier: ^16.9.0 version: 16.12.0 @@ -908,12 +940,9 @@ importers: nodemon: specifier: ^3.1.10 version: 3.1.11 - rimraf: - specifier: ^6.1.2 - version: 6.1.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.19.27)(typescript@5.9.3) + version: 10.9.2(@types/node@22.19.11)(typescript@5.9.3) publishDirectory: dist graphql/server-test: @@ -933,6 +962,9 @@ importers: express: specifier: ^5.2.1 version: 5.2.1 + graphile-schema: + specifier: workspace:^ + version: link:../../graphile/graphile-schema/dist graphql: specifier: 15.10.1 version: 15.10.1 @@ -8868,11 +8900,6 @@ packages: engines: {node: '>=14'} hasBin: true - rimraf@6.1.2: - resolution: {integrity: sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==} - engines: {node: 20 || >=22} - hasBin: true - robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} @@ -11195,7 +11222,7 @@ snapshots: '@graphiql/plugin-doc-explorer@0.4.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react@19.2.13)(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))': dependencies: - '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@20.19.27)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) '@headlessui/react': 2.2.9(react-dom@19.2.3(react@19.2.3))(react@19.2.3) graphql: 16.12.0 react: 19.2.3 @@ -11209,7 +11236,7 @@ snapshots: '@graphiql/plugin-explorer@5.1.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(graphql@16.12.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@20.19.27)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) graphiql-explorer: 0.9.0(graphql@16.12.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) graphql: 16.12.0 react: 19.2.3 @@ -11217,8 +11244,8 @@ snapshots: '@graphiql/plugin-history@0.4.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/node@22.19.11)(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))': dependencies: - '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@20.19.27)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) - '@graphiql/toolkit': 0.11.3(@types/node@20.19.27)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0) + '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + '@graphiql/toolkit': 0.11.3(@types/node@22.19.11)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0) react: 19.2.3 react-compiler-runtime: 19.1.0-rc.1(react@19.2.3) react-dom: 19.2.3(react@19.2.3) @@ -11231,9 +11258,9 @@ snapshots: - immer - use-sync-external-store - '@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@20.19.27)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))': + '@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))': dependencies: - '@graphiql/toolkit': 0.11.3(@types/node@20.19.27)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0) + '@graphiql/toolkit': 0.11.3(@types/node@22.19.11)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0) '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-dropdown-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@radix-ui/react-tooltip': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -11262,11 +11289,11 @@ snapshots: - immer - use-sync-external-store - '@graphiql/toolkit@0.11.3(@types/node@20.19.27)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)': + '@graphiql/toolkit@0.11.3(@types/node@22.19.11)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)': dependencies: '@n1ru4l/push-pull-async-iterable-iterator': 3.2.0 graphql: 16.12.0 - meros: 1.3.2(@types/node@20.19.27) + meros: 1.3.2(@types/node@22.19.11) optionalDependencies: graphql-ws: 6.0.7(graphql@16.12.0)(ws@8.19.0) transitivePeerDependencies: @@ -15522,7 +15549,7 @@ snapshots: dependencies: '@graphiql/plugin-doc-explorer': 0.4.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react@19.2.13)(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) '@graphiql/plugin-history': 0.4.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/node@22.19.11)(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) - '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@20.19.27)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) graphql: 16.12.0 react: 19.2.3 react-compiler-runtime: 19.1.0-rc.1(react@19.2.3) @@ -16722,9 +16749,9 @@ snapshots: ts-dedent: 2.2.0 uuid: 11.1.0 - meros@1.3.2(@types/node@20.19.27): + meros@1.3.2(@types/node@22.19.11): optionalDependencies: - '@types/node': 20.19.27 + '@types/node': 22.19.11 methods@1.1.2: {} @@ -18071,11 +18098,6 @@ snapshots: dependencies: glob: 9.3.5 - rimraf@6.1.2: - dependencies: - glob: 13.0.0 - package-json-from-dist: 1.0.1 - robust-predicates@3.0.2: {} rollup-plugin-visualizer@6.0.5(rollup@4.57.1): @@ -18146,8 +18168,8 @@ snapshots: '@graphiql/plugin-doc-explorer': 0.4.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react@19.2.13)(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) '@graphiql/plugin-explorer': 5.1.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(graphql@16.12.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@graphiql/plugin-history': 0.4.1(@graphiql/react@0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/node@22.19.11)(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) - '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@20.19.27)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) - '@graphiql/toolkit': 0.11.3(@types/node@20.19.27)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0) + '@graphiql/react': 0.37.3(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-compiler-runtime@19.1.0-rc.1(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) + '@graphiql/toolkit': 0.11.3(@types/node@22.19.11)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0) '@types/node': 22.19.11 grafast: 1.0.0-rc.4(graphql@16.12.0) graphiql: 5.2.2(@emotion/is-prop-valid@1.4.0)(@types/node@22.19.11)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(graphql-ws@6.0.7(graphql@16.12.0)(ws@8.19.0))(graphql@16.12.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) @@ -18660,6 +18682,24 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 + ts-node@10.9.2(@types/node@22.19.11)(typescript@5.9.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.19.11 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.9.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + tsconfig-paths@4.2.0: dependencies: json5: 2.2.3 diff --git a/postgres/drizzle-orm-test/package.json b/postgres/drizzle-orm-test/package.json index ce5316a62..4edb0fc2b 100644 --- a/postgres/drizzle-orm-test/package.json +++ b/postgres/drizzle-orm-test/package.json @@ -42,7 +42,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/postgres/introspectron/package.json b/postgres/introspectron/package.json index f771c47ce..d28bf197f 100644 --- a/postgres/introspectron/package.json +++ b/postgres/introspectron/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/postgres/pg-ast/package.json b/postgres/pg-ast/package.json index f8c067a1c..6bc08ac66 100644 --- a/postgres/pg-ast/package.json +++ b/postgres/pg-ast/package.json @@ -27,7 +27,7 @@ "build:dev": "makage build --dev", "build:proto": "ts-node scripts/pg-proto-parser", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/postgres/pg-codegen/package.json b/postgres/pg-codegen/package.json index 90f01c6cc..051370ca1 100644 --- a/postgres/pg-codegen/package.json +++ b/postgres/pg-codegen/package.json @@ -27,7 +27,7 @@ "build:dev": "makage build --dev", "dev": "ts-node ./src/index.ts", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/postgres/pgsql-test/package.json b/postgres/pgsql-test/package.json index b98969af7..38858957a 100644 --- a/postgres/pgsql-test/package.json +++ b/postgres/pgsql-test/package.json @@ -51,7 +51,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/uploads/content-type-stream/package.json b/uploads/content-type-stream/package.json index f0c10cc00..319cf0b3f 100644 --- a/uploads/content-type-stream/package.json +++ b/uploads/content-type-stream/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "dependencies": { diff --git a/uploads/etag-hash/package.json b/uploads/etag-hash/package.json index ce68aebfd..fcef0319a 100644 --- a/uploads/etag-hash/package.json +++ b/uploads/etag-hash/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/uploads/etag-stream/package.json b/uploads/etag-stream/package.json index 85ed499ee..ea84aaa1e 100644 --- a/uploads/etag-stream/package.json +++ b/uploads/etag-stream/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/uploads/mime-bytes/package.json b/uploads/mime-bytes/package.json index d758bb1fa..ef9751d47 100644 --- a/uploads/mime-bytes/package.json +++ b/uploads/mime-bytes/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [], diff --git a/uploads/s3-streamer/package.json b/uploads/s3-streamer/package.json index 5140b1a45..77d260bdf 100644 --- a/uploads/s3-streamer/package.json +++ b/uploads/s3-streamer/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "devDependencies": { diff --git a/uploads/stream-to-etag/package.json b/uploads/stream-to-etag/package.json index 9b121592e..b2801d994 100644 --- a/uploads/stream-to-etag/package.json +++ b/uploads/stream-to-etag/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/uploads/upload-names/package.json b/uploads/upload-names/package.json index 37f1be57c..d46a33244 100644 --- a/uploads/upload-names/package.json +++ b/uploads/upload-names/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [], diff --git a/uploads/uuid-hash/package.json b/uploads/uuid-hash/package.json index 05b781810..43fa31ba3 100644 --- a/uploads/uuid-hash/package.json +++ b/uploads/uuid-hash/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [ diff --git a/uploads/uuid-stream/package.json b/uploads/uuid-stream/package.json index f8d973b29..bd8351799 100644 --- a/uploads/uuid-stream/package.json +++ b/uploads/uuid-stream/package.json @@ -25,7 +25,7 @@ "build": "makage build", "build:dev": "makage build --dev", "lint": "eslint . --fix", - "test": "jest --passWithNoTests", + "test": "jest", "test:watch": "jest --watch" }, "keywords": [