Skip to content
Open
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
112 changes: 91 additions & 21 deletions packages/cli/src/commands/cache-graphql.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { saveFile } from '../utils/file'
import { format } from 'prettier'
import { Args, Command, Flags } from '@oclif/core'
import chalk from 'chalk'
import { getBasePath, withBasePath } from '../utils/directory'
import { logger } from '../utils/logger'
import graphql from 'graphql'
import path from 'path'
import fsExtra from 'fs-extra'
import prettier from 'prettier'

const { Kind, OperationTypeNode, parse: parseGraphql } = graphql

Expand Down Expand Up @@ -77,28 +77,16 @@ export default class CacheGraphql extends Command {
{ with: { type: 'json' } }
)

const discoveryConfig = await import(configPath)
const cachedQueries = getQueries(persistedDocuments)

saveConfigFile(
await format(
`module.exports = ${JSON.stringify(
{
...(discoveryConfig?.default ?? discoveryConfig),
experimental: {
...(discoveryConfig?.default ?? discoveryConfig).experimental,
cachedOperations: cachedQueries ?? [],
},
},
undefined,
2
)}`,
{
parser: 'typescript',
quoteProps: 'as-needed',
}
)
)
const source = fsExtra.readFileSync(configPath, 'utf8')
const patched = updateCachedOperations(source, cachedQueries)
const prettierConfig = await prettier.resolveConfig(configPath)
const formatted = await prettier.format(patched, {
...prettierConfig,
filepath: configPath,
})
saveConfigFile(formatted)

logger.info(
`${chalk.green('[Success]')} - GraphQL queries cached with success: 🎉
Expand Down Expand Up @@ -184,3 +172,85 @@ const getQueries = (persistedDocuments: Record<string, string>) => {

return operationNames
}

/**
* Updates only the `experimental.cachedOperations` property inside the
* `module.exports = { ... }` object literal of a discovery config file.
*
* Works as a targeted string edit (no AST): it locates the `experimental: { ... }`
* block by counting curly braces and either replaces an existing
* `cachedOperations: [...]` array or inserts a new one before the closing `}`.
* The rest of the file (comments, `process.env.*`, formatting, ...) is left
* untouched. A final pass through prettier normalizes the inserted snippet.
*/
function updateCachedOperations(
source: string,
cachedQueries: string[]
): string {
const block = findExperimentalBlock(source)
if (!block) {
throw new Error(
`Couldn't find \`experimental: { ... }\` block in ${configFileName}`
)
}

const arraySnippet = `[\n${cachedQueries.map((q) => ` '${q}',`).join('\n')}\n ]`
const propSnippet = `cachedOperations: ${arraySnippet},`

const inner = source.slice(block.innerStart, block.innerEnd)
const existing = findCachedOperationsRange(inner)

if (existing) {
const absStart = block.innerStart + existing.start
const absEnd = block.innerStart + existing.end
return source.slice(0, absStart) + propSnippet + source.slice(absEnd)
}

const insertAt = block.innerEnd
const head = source.slice(0, insertAt).replace(/\s*$/, '')
const tail = source.slice(insertAt)
return `${head}\n ${propSnippet}\n${tail}`
}

/** Find the `experimental: { ... }` block, returning the inner range. */
function findExperimentalBlock(source: string) {
const re = /(^|\n)[ \t]*experimental\s*:\s*\{/
const match = re.exec(source)
if (!match) return null

const innerStart = match.index + match[0].length
let depth = 1
for (let i = innerStart; i < source.length; i++) {
const ch = source[i]
if (ch === '{') depth++
else if (ch === '}') {
depth--
if (depth === 0) return { innerStart, innerEnd: i }
}
}
return null
}

/** Find the `cachedOperations: [...]` property range inside a string. */
function findCachedOperationsRange(inner: string) {
const re = /(^|\n)[ \t]*cachedOperations\s*:\s*\[/
const match = re.exec(inner)
if (!match) return null

const start = match.index + (match[1] === '\n' ? 1 : 0)
const arrayStart = match.index + match[0].length
let depth = 1
for (let i = arrayStart; i < inner.length; i++) {
const ch = inner[i]
if (ch === '[') depth++
else if (ch === ']') {
depth--
if (depth === 0) {
let end = i + 1
if (inner[end] === ',') end++
return { start, end }
}
}
}
return null
}
Loading