Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extension): generator extensions #1196

Merged
merged 8 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export default tsEslint.config({
'vitest*.config.ts',
'**/generated/**/*',
'tests/_/schemas/*/graffle/**/*',
'**/tests/fixture/graffle/**/*',
'src/layers/1_Schema/Hybrid/types/Scalar/Scalar.ts', // There is an ESLint error that goes away when ignored leading to a circular issue of either lint error or unused lint disable.
'**/$/**/*',
'legacy/**/*',
'build/**/*',
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"scripts": {
"serve:pokemon": "tsx tests/_/services/pokemonManual.ts",
"gen:graffle": "pnpm gen:graffle:tests && pnpm build && cd website && pnpm gen:graffle",
"gen:graffle:tests": "tsx tests/_/schemas/generate.ts",
"gen:graffle:tests": "tsx tests/_/schemas/generate.ts && pnpm graffle --project src/extensions/SchemaErrors/tests/fixture",
"gen:graffle:tests2": "tsx tests/_/schemas/generate.ts",
"graffle": "tsx ./src/cli/generate.ts",
"gen:examples": "tsx scripts/generate-examples-derivatives/generate.ts && pnpm format",
"dev": "rm -rf dist && tsc --watch",
Expand Down
128 changes: 57 additions & 71 deletions src/cli/generate.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#!/usr/bin/env node

import { Command } from '@molt/command'
import * as Path from 'node:path'
import { z } from 'zod'
import { Generator } from '../layers/4_generator/__.js'
import { urlParseSafe } from '../lib/prelude.js'
import { toAbsolutePath } from '../lib/fs.js'
import { isError, urlParseSafe } from '../lib/prelude.js'

const args = Command.create().description(`Generate a type safe GraphQL client.`)
.parameter(
Expand All @@ -14,25 +16,15 @@ const args = Command.create().description(`Generate a type safe GraphQL client.`
)
.parameter(
`schema`,
z.string().min(1).describe(
`Path to where your GraphQL schema is. If a URL is given it will be introspected. Otherwise assumed to be a path to your GraphQL SDL file. If a directory path is given, then will look for a "schema.graphql" within that directory. Otherwise will attempt to load the exact file path given.`,
z.string().min(1).optional().describe(
`Path to where your GraphQL schema is. If a URL is given it will be introspected. Otherwise assumed to be a path to your GraphQL SDL file. If a directory path is given, then will look for a "schema.graphql" within that directory. Otherwise will attempt to load the exact file path given. If omitted, then your project must have a configuration file which supplies the schema source.`,
),
)
.parametersExclusive(
`schemaErrorType`,
$ =>
$.parameter(
`schemaErrorTypes`,
z.boolean().describe(
`Use the schema error types pattern. All object types whose name starts with "Error" will be considered to be error types. If you want to specify a custom name pattern then use the other parameter "schemaErrorTypePattern".`,
),
)
.parameter(
`schemaErrorTypePattern`,
z.string().min(1).describe(
`Designate objects whose name matches this JS regular expression as being error types in your schema.`,
),
).default(`schemaErrorTypes`, true),
.parameter(
`project`,
z.string().optional().describe(
`Path to your configuration file. By default will look for "graffle.config.{ts,js,mjs,mts}" in the current working directory. If a directory path is given, then will look for "graffle.config.{ts,js,mjs,mts}" in that directory.`,
),
)
.parameter(
`defaultSchemaUrl`,
Expand All @@ -43,44 +35,20 @@ const args = Command.create().description(`Generate a type safe GraphQL client.`
z.string().min(1).describe(
`A GraphQL endpoint to be used as the default URL in the generated client for requests.`,
),
]).default(true),
]).optional(),
)
.parameter(
`output`,
z.string().min(1).default(`./graffle`).describe(
`Directory path for where to output the generated TypeScript files.`,
z.string().min(1).optional().describe(
`Directory path for where to output the generated TypeScript files. By default will be './graffle' in the project root.`,
),
)
.parameter(
`format`,
z.boolean().describe(
`Try to format the generated files. At attempt to use dprint will be made. You need to have these dependencies installed in your project: @dprint/formatter, @dprint/typescript.`,
)
.default(true),
)
.parameter(
`libraryPathClient`,
z.string().optional().describe(
`Custom location for where the generated code should import the Graffle "client" module from.`,
),
)
.parameter(
`libraryPathSchema`,
z.string().optional().describe(
`Custom location for where the generated code should import the Graffle "schema" module from.`,
),
)
.parameter(
`libraryPathScalars`,
z.string().optional().describe(
`Custom location for where the generated code should import the Graffle "scalars" module from.`,
),
)
.parameter(
`libraryPathUtilitiesForGenerated`,
z.string().optional().describe(
`Custom location for where the generated code should import the Graffle "utilities-for-generated" module from.`,
),
.optional(),
)
.settings({
parameters: {
Expand All @@ -89,33 +57,51 @@ const args = Command.create().description(`Generate a type safe GraphQL client.`
})
.parse()

const url = urlParseSafe(args.schema)
// --- Resolve Config File ---

const configModule = await Generator.Config.load({ filePath: args.project })
if (isError(configModule)) throw configModule
if (!configModule.builder && args.project) {
throw new Error(`Could not find a configuration file at "${configModule.paths.join(`, `)}".`)
}

// --- Resolve Default Schema URL ---

const defaultSchemaUrl = typeof args.defaultSchemaUrl === `string`
? new URL(args.defaultSchemaUrl)
: args.defaultSchemaUrl

const format = args.format

const schemaSource = url
? { type: `url` as const, url }
: { type: `sdl` as const, dirOrFilePath: args.schema }

await Generator.generate({
format,
schemaSource,
defaultSchemaUrl,
name: args.name,
outputDirPath: args.output,
errorTypeNamePattern: args.schemaErrorType._tag === `schemaErrorTypePattern`
? new RegExp(args.schemaErrorType.value)
: args.schemaErrorType.value
? /^Error.+/
: undefined,
libraryPaths: {
client: args.libraryPathClient,
schema: args.libraryPathSchema,
scalars: args.libraryPathScalars,
utilitiesForGenerated: args.libraryPathUtilitiesForGenerated,
},
})
// --- Resolve Schema ---

const url = args.schema ? urlParseSafe(args.schema) : null

const schemaViaCLI = args.schema
? url
? { type: `url` as const, url }
: { type: `sdl` as const, dirOrFilePath: Path.join(process.cwd(), args.schema) }
: undefined

const schema = schemaViaCLI ?? configModule.builder?._.input.schema

if (!schema) {
throw new Error(`No schema source provided. Either specify a schema source in the config file or via the CLI.`)
}

const currentWorkingDirectory = configModule.path ? Path.dirname(configModule.path) : process.cwd()

// --- Merge Inputs ---

const input = {
...configModule.builder?._.input,
currentWorkingDirectory,
schema,
}

if (defaultSchemaUrl !== undefined) input.defaultSchemaUrl = defaultSchemaUrl
if (args.format !== undefined) input.format = args.format
if (args.name !== undefined) input.name = args.name
if (args.output !== undefined) input.outputDirPath = toAbsolutePath(process.cwd(), args.output)

// --- Generate ---

await Generator.generate(input)
2 changes: 2 additions & 0 deletions src/entrypoints/_Generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { generate } from '../layers/4_generator/_.js'
export { create } from '../layers/4_generator/configFile/builder.js'
1 change: 1 addition & 0 deletions src/entrypoints/__Generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * as Generator from './_Generator.js'
2 changes: 2 additions & 0 deletions src/entrypoints/extensionkit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { createExtension as createGeneratorExtension } from '../layers/4_generator/extension/create.js'
export { createExtension } from '../layers/6_client/extension/extension.js'
2 changes: 1 addition & 1 deletion src/entrypoints/generator.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { generate } from '../layers/4_generator/_.js'
export * from './__Generator.js'
5 changes: 3 additions & 2 deletions src/entrypoints/utilities-for-generated.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export { type Simplify } from 'type-fest'
export { type SchemaDrivenDataMap } from '../extensions/CustomScalars/schemaDrivenDataMap/types.js'
export { type SchemaDrivenDataMap } from '../extensions/CustomScalars/schemaDrivenDataMap/__.js'
export * from '../layers/2_Select/__.js'
export { type SchemaIndex as SchemaIndexBase } from '../layers/4_generator/generators/SchemaIndex.js'
export { type Schema as SchemaIndexBase } from '../layers/4_generator/generators/Schema.js'
export { type GlobalRegistry } from '../layers/4_generator/globalRegistry.js'
export type {
ConfigGetOutputError,
HandleOutput,
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/CustomScalars/decode.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Kind } from 'graphql'
import { applyCodec } from '../../layers/1_Schema/Hybrid/types/Scalar/Scalar.js'
import type { Grafaid } from '../../lib/grafaid/__.js'
import { SchemaDrivenDataMap } from './schemaDrivenDataMap/types.js'
import { SchemaDrivenDataMap } from './schemaDrivenDataMap/__.js'

/**
* If a document is given then aliases will be decoded as well.
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/CustomScalars/encode.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { applyCodec } from '../../layers/1_Schema/Hybrid/types/Scalar/Scalar.js'
import { Grafaid } from '../../lib/grafaid/__.js'
import { SchemaDrivenDataMap } from './schemaDrivenDataMap/types.js'
import { SchemaDrivenDataMap } from './schemaDrivenDataMap/__.js'

export const encodeRequestVariables = ({ sddm, request }: {
sddm: SchemaDrivenDataMap
Expand Down
5 changes: 5 additions & 0 deletions src/extensions/CustomScalars/schemaDrivenDataMap/__.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * as SchemaDrivenDataMap from './types.js'

import { type SchemaDrivenDataMap as SchemaDrivenDataMap_ } from './types.js'

export type SchemaDrivenDataMap = SchemaDrivenDataMap_
Loading