fix: get auto type-gen to work on turbo, by running type gen in a child process outside turbo/webpack (#6714)

Before on turbo: https://github.com/vercel/next.js/issues/66723
This commit is contained in:
Alessio Gravili
2024-06-10 18:03:12 -04:00
committed by GitHub
parent ba513d5a97
commit 7b7dc71845
8 changed files with 69 additions and 37 deletions

View File

@@ -2,7 +2,6 @@ import type { GeneratedTypes, Payload } from 'payload'
import type { InitOptions, SanitizedConfig } from 'payload/config' import type { InitOptions, SanitizedConfig } from 'payload/config'
import { BasePayload } from 'payload' import { BasePayload } from 'payload'
import { generateTypes } from 'payload/bin'
import WebSocket from 'ws' import WebSocket from 'ws'
let cached: { let cached: {
@@ -39,7 +38,12 @@ export const reload = async (config: SanitizedConfig, payload: Payload): Promise
// Generate types // Generate types
if (config.typescript.autoGenerate !== false) { if (config.typescript.autoGenerate !== false) {
void generateTypes(config, { log: false }) // We cannot run it directly here, as generate-types imports json-schema-to-typescript, which breaks on turbopack.
// see: https://github.com/vercel/next.js/issues/66723
void payload.bin({
args: ['generate:types'],
log: false,
})
} }
await payload.db.init() await payload.db.init()

View File

@@ -68,7 +68,6 @@ export const withPayload = (nextConfig = {}) => {
'pino', 'pino',
'pino-pretty', 'pino-pretty',
'graphql', 'graphql',
'json-schema-to-typescript',
], ],
webpack: (webpackConfig, webpackOptions) => { webpack: (webpackConfig, webpackOptions) => {
const incomingWebpackConfig = const incomingWebpackConfig =
@@ -84,7 +83,6 @@ export const withPayload = (nextConfig = {}) => {
'drizzle-kit/payload', 'drizzle-kit/payload',
'sharp', 'sharp',
'libsql', 'libsql',
'json-schema-to-typescript',
], ],
ignoreWarnings: [ ignoreWarnings: [
...(incomingWebpackConfig?.ignoreWarnings || []), ...(incomingWebpackConfig?.ignoreWarnings || []),

View File

@@ -18,7 +18,6 @@ const componentWebpackConfig = {
'payload/config', 'payload/config',
'react-image-crop', 'react-image-crop',
'payload/operations', 'payload/operations',
'payload/bin',
], ],
mode: 'production', mode: 'production',
module: { module: {

View File

@@ -4,8 +4,6 @@ import { register } from 'node:module'
import path from 'node:path' import path from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url' import { fileURLToPath, pathToFileURL } from 'node:url'
import { bin } from './dist/bin/index.js'
// Allow disabling SWC for debugging // Allow disabling SWC for debugging
if (process.env.DISABLE_SWC !== 'true') { if (process.env.DISABLE_SWC !== 'true') {
const filename = fileURLToPath(import.meta.url) const filename = fileURLToPath(import.meta.url)
@@ -15,4 +13,9 @@ if (process.env.DISABLE_SWC !== 'true') {
register('./dist/bin/loader/index.js', url) register('./dist/bin/loader/index.js', url)
} }
bin() const start = async () => {
const { bin } = await import('./dist/bin/index.js')
bin()
}
void start()

View File

@@ -1,5 +1,5 @@
import { findUpSync, pathExistsSync } from 'find-up' import { findUpSync, pathExistsSync } from 'find-up'
import fs from 'fs' import { getTsconfig } from 'get-tsconfig'
import path from 'path' import path from 'path'
/** /**
@@ -12,37 +12,30 @@ const getTSConfigPaths = (): {
outPath?: string outPath?: string
rootPath?: string rootPath?: string
srcPath?: string srcPath?: string
tsConfigPath?: string
} => { } => {
const tsConfigPath = findUpSync('tsconfig.json') const tsConfigResult = getTsconfig()
const tsConfig = tsConfigResult.config
if (!tsConfigPath) { const tsConfigDir = path.dirname(tsConfigResult.path)
return {
rootPath: process.cwd(),
}
}
try { try {
// Read the file as a string and remove trailing commas const rootConfigDir = path.resolve(tsConfigDir, tsConfig.compilerOptions.baseUrl || '')
const rawTsConfig = fs
.readFileSync(tsConfigPath, 'utf-8')
.replace(/,\s*\]/g, ']')
.replace(/,\s*\}/g, '}')
const tsConfig = JSON.parse(rawTsConfig)
const rootPath = process.cwd()
const srcPath = tsConfig.compilerOptions?.rootDir || path.resolve(process.cwd(), 'src') const srcPath = tsConfig.compilerOptions?.rootDir || path.resolve(process.cwd(), 'src')
const outPath = tsConfig.compilerOptions?.outDir || path.resolve(process.cwd(), 'dist') const outPath = tsConfig.compilerOptions?.outDir || path.resolve(process.cwd(), 'dist')
const tsConfigDir = path.dirname(tsConfigPath) let configPath = path.resolve(
let configPath = tsConfig.compilerOptions?.paths?.['@payload-config']?.[0] rootConfigDir,
tsConfig.compilerOptions?.paths?.['@payload-config']?.[0],
)
if (configPath) { if (configPath) {
configPath = path.resolve(tsConfigDir, configPath) configPath = path.resolve(rootConfigDir, configPath)
} }
return { return {
configPath, configPath,
outPath, outPath,
rootPath, rootPath: rootConfigDir,
srcPath, srcPath,
tsConfigPath: tsConfigResult.path,
} }
} catch (error) { } catch (error) {
console.error(`Error parsing tsconfig.json: ${error}`) // Do not throw the error, as we can still continue with the other config path finding methods console.error(`Error parsing tsconfig.json: ${error}`) // Do not throw the error, as we can still continue with the other config path finding methods
@@ -70,6 +63,11 @@ export const findConfig = (): string => {
const { configPath, outPath, rootPath, srcPath } = getTSConfigPaths() const { configPath, outPath, rootPath, srcPath } = getTSConfigPaths()
// if configPath is absolute file, not folder, return it
if (path.extname(configPath) === '.js' || path.extname(configPath) === '.ts') {
return configPath
}
const searchPaths = const searchPaths =
process.env.NODE_ENV === 'production' process.env.NODE_ENV === 'production'
? [configPath, outPath, srcPath, rootPath] ? [configPath, outPath, srcPath, rootPath]

View File

@@ -1,5 +0,0 @@
/**
* WARNING: This file contains exports that can only be safely used on the server.
*/
export { generateTypes } from '../bin/generateTypes.js'

View File

@@ -2,7 +2,10 @@ import type { ExecutionResult, GraphQLSchema, ValidationRule } from 'graphql'
import type { OperationArgs, Request as graphQLRequest } from 'graphql-http' import type { OperationArgs, Request as graphQLRequest } from 'graphql-http'
import type pino from 'pino' import type pino from 'pino'
import { spawn } from 'child_process'
import crypto from 'crypto' import crypto from 'crypto'
import { fileURLToPath } from 'node:url'
import path from 'path'
import type { AuthArgs } from './auth/operations/auth.js' import type { AuthArgs } from './auth/operations/auth.js'
import type { Result as ForgotPasswordResult } from './auth/operations/forgotPassword.js' import type { Result as ForgotPasswordResult } from './auth/operations/forgotPassword.js'
@@ -47,7 +50,6 @@ import type { TypeWithVersion } from './versions/types.js'
import { decrypt, encrypt } from './auth/crypto.js' import { decrypt, encrypt } from './auth/crypto.js'
import { APIKeyAuthentication } from './auth/strategies/apiKey.js' import { APIKeyAuthentication } from './auth/strategies/apiKey.js'
import { JWTAuthentication } from './auth/strategies/jwt.js' import { JWTAuthentication } from './auth/strategies/jwt.js'
import { generateTypes } from './bin/generateTypes.js'
import localOperations from './collections/operations/local/index.js' import localOperations from './collections/operations/local/index.js'
import { validateSchema } from './config/validate.js' import { validateSchema } from './config/validate.js'
import { consoleEmailAdapter } from './email/consoleEmailAdapter.js' import { consoleEmailAdapter } from './email/consoleEmailAdapter.js'
@@ -57,6 +59,9 @@ import flattenFields from './utilities/flattenTopLevelFields.js'
import Logger from './utilities/logger.js' import Logger from './utilities/logger.js'
import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js' import { serverInit as serverInitTelemetry } from './utilities/telemetry/events/serverInit.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
/** /**
* @description Payload * @description Payload
*/ */
@@ -302,6 +307,31 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
[slug: string]: any // TODO: Type this [slug: string]: any // TODO: Type this
} = {} } = {}
async bin({
args,
cwd,
log,
}: {
args: string[]
cwd?: string
log?: boolean
}): Promise<{ code: number }> {
return new Promise((resolve, reject) => {
const spawned = spawn('node', [path.resolve(dirname, '../bin.js'), ...args], {
cwd,
stdio: log || log === undefined ? 'inherit' : 'ignore',
})
spawned.on('exit', (code) => {
resolve({ code })
})
spawned.on('error', (error) => {
reject(error)
})
})
}
/** /**
* @description delete one or more documents * @description delete one or more documents
* @param options * @param options
@@ -366,7 +396,12 @@ export class BasePayload<TGeneratedTypes extends GeneratedTypes> {
// Generate types on startup // Generate types on startup
if (process.env.NODE_ENV !== 'production' && this.config.typescript.autoGenerate !== false) { if (process.env.NODE_ENV !== 'production' && this.config.typescript.autoGenerate !== false) {
void generateTypes(this.config, { log: false }) // We cannot run it directly here, as generate-types imports json-schema-to-typescript, which breaks on turbopack.
// see: https://github.com/vercel/next.js/issues/66723
void this.bin({
args: ['generate:types'],
log: false,
})
} }
this.db = this.config.db.init({ payload: this }) this.db = this.config.db.init({ payload: this })

View File

@@ -32,7 +32,7 @@ export const createTestHooks = async (testSuiteName = '_community') => {
tsConfig.compilerOptions.paths['@payload-config'] = [`./test/${testSuiteName}/config.ts`] tsConfig.compilerOptions.paths['@payload-config'] = [`./test/${testSuiteName}/config.ts`]
await writeFile(tsConfigPath, stringify(tsConfig, null, 2) + '\n') await writeFile(tsConfigPath, stringify(tsConfig, null, 2) + '\n')
process.env.PAYLOAD_CONFIG_PATH = path.resolve(testSuiteName, 'config') process.env.PAYLOAD_CONFIG_PATH = path.resolve(dirname, testSuiteName, 'config.ts')
}, },
} }
} }