polen
Version:
A framework for delightful GraphQL developer portals
181 lines (157 loc) • 4.85 kB
text/typescript
// @ts-nocheck
/*
* Examples
*
* polen open --introspect https://api.graphql-hive.com/graphql
* polen open --introspect https://api.github.com/graphql --introspection-headers '{"Authorization": "Bearer $GITHUB_TOKEN"}'
* polen open --sdl https://docs.github.com/public/fpt/schema.docs.graphql
*/
import { Api } from '#api/index'
import { Vite } from '#dep/vite/index'
import { Grafaid } from '#lib/grafaid/index'
import { GraphqlSchemaLoader } from '#lib/graphql-schema-loader/index'
import { Command } from '@molt/command'
import type { Fn } from '@wollybeard/kit'
import { Err, Fs, Json, Path, Rec } from '@wollybeard/kit'
import { homedir } from 'node:os'
import { z } from 'zod'
const args = Command.create()
.parameter(
`--introspection-headers --inh`,
z
.string()
.optional()
.describe(
`Include headers in the introspection query request sent when using --introspection-url. Format is JSON Object.`,
),
)
.parametersExclusive(`source`, ($) =>
$.parameter(`--introspect --in -i`, {
schema: z
.string()
.url()
.describe(
`Get the schema by sending a GraphQL introspection query to this URL.`,
),
})
.parameter(`--sdl -s`, {
schema: z
.string()
.describe(
`Get the schema from a GraphQL Schema Definition Language file. Can be a path to a local file or an HTTP URL to a remote one.`,
),
})
.parameter(`--name -n`, {
schema: z
.enum([`github`, `hive`])
.describe(
`Pick from a well known public API. Polen already knows how to fetch the schema for these APIs.`,
),
}))
.parameter(
`--cache`,
z
.boolean()
.default(true)
.describe(
`Enable or disable caching. By default this command caches fetched schemas for re-use.`,
),
)
.settings({
parameters: {
environment: {
$default: {
// todo prfix seting doesn't seem to work with Molt!
prefix: `POLEN_CREATE_`,
enabled: false,
},
},
},
})
.parse()
// cache
const cacheDir = Path.join(homedir(), `.polen`, `cache`, `cli`, `open`)
const bsae64Codec = {
encode: (str: string) => Buffer.from(str).toString(`base64`),
decode: (str: string) => Buffer.from(str, `base64`).toString(`utf-8`),
}
const cacheWrite = async (source: string, schema: Grafaid.Schema.Schema) => {
if (!args.cache) return
await Err.tryCatchIgnore(async () => {
const fileName = bsae64Codec.encode(source)
const filePath = Path.join(cacheDir, fileName)
const sdl = Grafaid.Schema.print(schema)
await Fs.write({ path: filePath, content: sdl })
})
}
const cacheRead = async (source: string) => {
if (!args.cache) return null
return Err.tryCatchIgnore(async () => {
const fileName = bsae64Codec.encode(source)
const filePath = Path.join(cacheDir, fileName)
const sdl = await Fs.read(filePath)
if (!sdl) return null
return Grafaid.Schema.fromAST(Grafaid.Schema.AST.parse(sdl))
})
}
const cache = {
read: cacheRead,
write: cacheWrite,
}
const wrapCache = <fn extends Fn.AnyAnyAsync>(fn: fn): fn => {
const wrapped = async (...args: Parameters<fn>) => {
const cacheKey = JSON.stringify(args)
const cachedSchema = await cache.read(cacheKey)
if (cachedSchema) {
return cachedSchema
}
const value = await fn(...args)
await cache.write(cacheKey, value)
return value
}
return wrapped as any
}
// cache end
// todo: use a proper validation, e.g. zod, better yet: allow to specify the validation in molt itself
const parseHeaders = (headersJsonEncoded: string): Record<string, string> => {
const headersJson = Json.codec.deserialize(headersJsonEncoded)
if (!Rec.is(headersJson)) {
console.log(`--introspection-headers must be a JSON object.`)
process.exit(1)
}
return headersJson as any
}
const load = wrapCache(GraphqlSchemaLoader.load)
const schema = await load(
args.source._tag === `name`
? { type: `name`, name: args.source.value }
: args.source._tag === `sdl`
? { type: `sdl`, pathOrUrl: args.source.value }
: {
type: `introspect`,
url: args.source.value,
headers: args.introspectionHeaders
? parseHeaders(args.introspectionHeaders)
: undefined,
},
)
const config = await Api.ConfigResolver.fromMemory({
root: await Fs.makeTemporaryDirectory(),
schema: {
dataSources: {
data: {
versions: [
{
before: Grafaid.Schema.empty,
after: schema,
changes: [],
date: new Date(),
},
],
},
},
},
})
const viteDevServer = await Vite.createServer(config)
await viteDevServer.listen()
viteDevServer.printUrls()