Skip to content

Commit

Permalink
feat(graph): open-ended codegen (#4896)
Browse files Browse the repository at this point in the history
* feat: interim commit

* fix: pass config around

* feat: open codegen working

* fix: various type errors

* feat: track operations doc id in memory

* fix: various bugs in graph flow

* chore: update dep

* fix: refactor Graph codegen module singleton and call sites

* fix: commit unnecessary eslint suggestion

* fix: remove bad warnings

* fix: update docs

* feat: detect included codegen modules

* fix: commit unnecessary eslint suggestion

* fix: formatting

* chore: update dep

* fix: update snapshots

* fix: commit unnecessary eslint suggestion

* fix: update tests

* fix: update dep

* fix: update snapshots

* fix: support windows filename in tests

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
sgrove and kodiakhq[bot] committed Aug 9, 2022
1 parent f02d08e commit 64e48dc
Show file tree
Hide file tree
Showing 15 changed files with 3,069 additions and 34,325 deletions.
1 change: 1 addition & 0 deletions docs/commands/graph.md
Expand Up @@ -89,6 +89,7 @@ netlify graph:handler

**Flags**

- `codegen` (*string*) - The id of the specific code generator to use
- `debug` (*boolean*) - Print debugging information
- `httpProxy` (*string*) - Proxy server address to route requests through.
- `httpProxyCertificateFilename` (*string*) - Certificate file to use when connecting using a proxy server
Expand Down
46 changes: 22 additions & 24 deletions npm-shrinkwrap.json

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

2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -291,7 +291,7 @@
"multiparty": "^4.2.1",
"netlify": "^12.0.0",
"netlify-headers-parser": "^6.0.2",
"netlify-onegraph-internal": "0.4.2",
"netlify-onegraph-internal": "0.6.1",
"netlify-redirect-parser": "^13.0.5",
"netlify-redirector": "^0.2.1",
"node-fetch": "^2.6.0",
Expand Down
2 changes: 2 additions & 0 deletions src/commands/dev/dev.js
Expand Up @@ -524,6 +524,7 @@ const dev = async (options, command) => {
}

stopWatchingCLISessions = await startOneGraphCLISession({
config,
netlifyGraphConfig,
netlifyToken,
site,
Expand All @@ -535,6 +536,7 @@ const dev = async (options, command) => {
const oneGraphSessionId = loadCLISession(state)

await persistNewOperationsDocForSession({
config,
netlifyGraphConfig,
netlifyToken,
oneGraphSessionId,
Expand Down
5 changes: 3 additions & 2 deletions src/commands/graph/graph-edit.js
Expand Up @@ -21,7 +21,7 @@ const { ensureAppForSite, executeCreatePersistedQueryMutation } = OneGraphCliCli
* @returns
*/
const graphEdit = async (options, command) => {
const { site, state } = command.netlify
const { config, site, state } = command.netlify
const siteId = site.id

if (!site.id) {
Expand All @@ -44,6 +44,7 @@ const graphEdit = async (options, command) => {
await ensureAppForSite(netlifyToken, siteId)

const oneGraphSessionId = await ensureCLISession({
config,
metadata: {},
netlifyToken,
site,
Expand Down Expand Up @@ -77,7 +78,7 @@ const graphEdit = async (options, command) => {

const { jwt } = await OneGraphClient.getGraphJwtForSite({ siteId, nfToken: netlifyToken })
await upsertMergeCLISessionMetadata({
netlifyGraphConfig,
config,
jwt,
siteId,
siteRoot: site.root,
Expand Down
104 changes: 41 additions & 63 deletions src/commands/graph/graph-handler.js
@@ -1,29 +1,27 @@
/* eslint-disable eslint-comments/disable-enable-pair */
// @ts-check
const inquirer = require('inquirer')
const { GraphQL } = require('netlify-onegraph-internal')

const {
autocompleteCodegenModules,
autocompleteOperationNames,
buildSchema,
defaultExampleOperationsDoc,
extractFunctionsFromOperationDoc,
generateHandlerByOperationName,
getCodegenFunctionById,
getCodegenModule,
getNetlifyGraphConfig,
readGraphQLOperationsSourceFile,
readGraphQLSchemaFile,
} = require('../../lib/one-graph/cli-netlify-graph')
const { error, log } = require('../../utils')

const { parse } = GraphQL

/**
* Creates the `netlify graph:handler` command
* @param {string} userOperationName
* @param {import('commander').OptionValues} options
* @param {import('../base-command').BaseCommand} command
* @returns
*/
const graphHandler = async (userOperationName, options, command) => {
const graphHandler = async (args, options, command) => {
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
const { config } = command.netlify

const schemaString = readGraphQLSchemaFile(netlifyGraphConfig)

Expand All @@ -35,69 +33,48 @@ const graphHandler = async (userOperationName, options, command) => {
error(`Error parsing schema: ${buildSchemaError}`)
}

if (!schema) {
error(`Failed to parse Netlify GraphQL schema`)
}
const userOperationName = args.operationName
const userCodegenId = options.codegen

let operationName = userOperationName
if (!operationName) {
try {
let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
if (currentOperationsDoc.trim().length === 0) {
currentOperationsDoc = defaultExampleOperationsDoc
}

const parsedDoc = parse(currentOperationsDoc)
const { functions } = extractFunctionsFromOperationDoc(parsedDoc)

const sorted = Object.values(functions).sort((aItem, bItem) =>
aItem.operationName.localeCompare(bItem.operationName),
)

const perPage = 50

const allOperationChoices = sorted.map((operation) => ({
name: `${operation.operationName} (${operation.kind})`,
value: operation.operationName,
}))

const filterOperationNames = (operationChoices, input) =>
operationChoices.filter((operation) => operation.value.toLowerCase().match(input.toLowerCase()))

// eslint-disable-next-line n/global-require
const inquirerAutocompletePrompt = require('inquirer-autocomplete-prompt')
/** multiple matching detectors, make the user choose */
inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt)
operationName = await autocompleteOperationNames({ netlifyGraphConfig })
}

const { selectedOperationName } = await inquirer.prompt({
name: 'selectedOperationName',
message: `For which operation would you like to generate a handler?`,
type: 'autocomplete',
pageSize: perPage,
source(_, input) {
if (!input || input === '') {
return allOperationChoices
}
if (!operationName) {
error(`No operation name provided`)
}

const filteredChoices = filterOperationNames(allOperationChoices, input)
// only show filtered results
return filteredChoices
},
})
const codegenModule = await getCodegenModule({ config })
if (!codegenModule) {
error(
`No Netlify Graph codegen module specified in netlify.toml under the [graph] header. Please specify 'codeGenerator' field and try again.`,
)
return
}

if (selectedOperationName) {
operationName = selectedOperationName
}
} catch (parseError) {
parseError(`Error parsing operations library: ${parseError}`)
}
let codeGenerator = userCodegenId ? await getCodegenFunctionById({ config, id: userCodegenId }) : null
if (!codeGenerator) {
codeGenerator = await autocompleteCodegenModules({ config })
}

if (!operationName) {
error(`No operation name provided`)
if (!codeGenerator) {
error(`Unable to select appropriate Netlify Graph code generator`)
return
}

generateHandlerByOperationName({ logger: log, netlifyGraphConfig, schema, operationName, handlerOptions: {} })
if (schema) {
generateHandlerByOperationName({
generate: codeGenerator.generateHandler,
logger: log,
netlifyGraphConfig,
schema,
operationName,
handlerOptions: {},
})
} else {
error(`Failed to parse Netlify GraphQL schema`)
}
}

/**
Expand All @@ -109,11 +86,12 @@ const createGraphHandlerCommand = (program) =>
program
.command('graph:handler')
.argument('[name]', 'Operation name')
.option('-c, --codegen <id>', 'The id of the specific code generator to use')
.description(
'Generate a handler for a Graph operation given its name. See `graph:operations` for a list of operations.',
)
.action(async (operationName, options, command) => {
await graphHandler(operationName, options, command)
await graphHandler({ operationName }, options, command)
})

module.exports = { createGraphHandlerCommand }
3 changes: 2 additions & 1 deletion src/commands/graph/graph-init.js
Expand Up @@ -18,7 +18,7 @@ const { ensureAppForSite, executeCreateApiTokenMutation } = OneGraphCliClient
* @returns
*/
const graphInit = async (options, command) => {
const { api, site, state } = command.netlify
const { api, config, site, state } = command.netlify
const siteId = site.id

if (!siteId) {
Expand Down Expand Up @@ -66,6 +66,7 @@ const graphInit = async (options, command) => {

const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })
await ensureCLISession({
config,
metadata: {},
netlifyToken,
site,
Expand Down
32 changes: 20 additions & 12 deletions src/commands/graph/graph-library.js
@@ -1,4 +1,6 @@
// @ts-check
const { GraphQL } = require('netlify-onegraph-internal')

const { readLockfile } = require('../../lib/one-graph/cli-client')
const {
buildSchema,
Expand All @@ -19,10 +21,19 @@ const { NETLIFYDEVERR, chalk, error, log } = require('../../utils')
* @returns
*/
const graphLibrary = async (options, command) => {
const { config } = command.netlify
const netlifyGraphConfig = await getNetlifyGraphConfig({ command, options })

const schemaString = readGraphQLSchemaFile(netlifyGraphConfig)

let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
if (currentOperationsDoc.trim().length === 0) {
currentOperationsDoc = defaultExampleOperationsDoc
}

const parsedDoc = parse(currentOperationsDoc)
const { fragments, functions } = extractFunctionsFromOperationDoc(GraphQL, parsedDoc)

let schema

try {
Expand All @@ -33,37 +44,34 @@ const graphLibrary = async (options, command) => {

if (!schema) {
error(`Failed to parse Netlify GraphQL schema`)
return
}

let currentOperationsDoc = readGraphQLOperationsSourceFile(netlifyGraphConfig)
if (currentOperationsDoc.trim().length === 0) {
currentOperationsDoc = defaultExampleOperationsDoc
}

const parsedDoc = parse(currentOperationsDoc)
const { fragments, functions } = extractFunctionsFromOperationDoc(parsedDoc)

const lockfile = readLockfile({ siteRoot: command.netlify.site.root })

if (lockfile == null) {
if (lockfile === undefined) {
error(
`${NETLIFYDEVERR} Error: no lockfile found, unable to run \`netlify graph:library\`. To pull a remote schema (and create a lockfile), run ${chalk.yellow(
'netlify graph:pull',
)} `,
)
return
}

const schemaId = lockfile && lockfile.locked.schemaId
const { schemaId } = lockfile.locked

generateFunctionsFile({
const payload = {
config,
logger: log,
netlifyGraphConfig,
schema,
schemaId,
operationsDoc: currentOperationsDoc,
functions,
fragments,
})
}

generateFunctionsFile(payload)
}

/**
Expand Down

1 comment on commit 64e48dc

@github-actions
Copy link

Choose a reason for hiding this comment

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

📊 Benchmark results

Package size: 228 MB

Please sign in to comment.