From c270e2abe616326b08ae0f9d55412776ccbfb965 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Thu, 18 Aug 2022 09:54:01 +1000 Subject: [PATCH 1/7] change configuration loading to use loadConfig --- .../{require-source => load-config}/package.json | 4 ++-- packages/core/package.json | 2 +- .../load-config.ts | 1 + .../require-source.ts | 1 - packages/core/src/lib/config/loadConfig.ts | 8 ++++++++ packages/core/src/scripts/build/build.ts | 5 ++--- packages/core/src/scripts/postinstall.ts | 6 ++---- packages/core/src/scripts/prisma.ts | 7 +++---- packages/core/src/scripts/run/dev.ts | 6 +++--- scripts/generate-artifacts-for-projects/src/index.ts | 6 +++--- 10 files changed, 25 insertions(+), 21 deletions(-) rename packages/core/___internal-do-not-use-will-break-in-patch/{require-source => load-config}/package.json (65%) create mode 100644 packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts delete mode 100644 packages/core/src/___internal-do-not-use-will-break-in-patch/require-source.ts create mode 100644 packages/core/src/lib/config/loadConfig.ts diff --git a/packages/core/___internal-do-not-use-will-break-in-patch/require-source/package.json b/packages/core/___internal-do-not-use-will-break-in-patch/load-config/package.json similarity index 65% rename from packages/core/___internal-do-not-use-will-break-in-patch/require-source/package.json rename to packages/core/___internal-do-not-use-will-break-in-patch/load-config/package.json index e5777beeb70..7590e4cca83 100644 --- a/packages/core/___internal-do-not-use-will-break-in-patch/require-source/package.json +++ b/packages/core/___internal-do-not-use-will-break-in-patch/load-config/package.json @@ -1,4 +1,4 @@ { - "main": "dist/keystone-6-core-___internal-do-not-use-will-break-in-patch-require-source.cjs.js", - "module": "dist/keystone-6-core-___internal-do-not-use-will-break-in-patch-require-source.esm.js" + "main": "dist/keystone-6-core-___internal-do-not-use-will-break-in-patch-load-config.cjs.js", + "module": "dist/keystone-6-core-___internal-do-not-use-will-break-in-patch-load-config.esm.js" } diff --git a/packages/core/package.json b/packages/core/package.json index 0f9e2c4c33f..da7e7f53243 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -134,7 +134,7 @@ "index.ts", "system.ts", "next.ts", - "___internal-do-not-use-will-break-in-patch/{node-api,next-graphql,require-source}.ts", + "___internal-do-not-use-will-break-in-patch/{node-api,next-graphql,load-config}.ts", "___internal-do-not-use-will-break-in-patch/admin-ui/pages/*/index.tsx", "___internal-do-not-use-will-break-in-patch/admin-ui/{next-config.ts,id-field-view.tsx}", "artifacts.ts", diff --git a/packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts b/packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts new file mode 100644 index 00000000000..d81128e1fce --- /dev/null +++ b/packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts @@ -0,0 +1 @@ +export { loadConfig } from '../lib/config/loadConfig'; diff --git a/packages/core/src/___internal-do-not-use-will-break-in-patch/require-source.ts b/packages/core/src/___internal-do-not-use-will-break-in-patch/require-source.ts deleted file mode 100644 index bfd5f740551..00000000000 --- a/packages/core/src/___internal-do-not-use-will-break-in-patch/require-source.ts +++ /dev/null @@ -1 +0,0 @@ -export { requireSource } from '../lib/config/requireSource'; diff --git a/packages/core/src/lib/config/loadConfig.ts b/packages/core/src/lib/config/loadConfig.ts new file mode 100644 index 00000000000..525367fa842 --- /dev/null +++ b/packages/core/src/lib/config/loadConfig.ts @@ -0,0 +1,8 @@ +import { getConfigPath } from '../../scripts/utils'; +import { KeystoneConfig } from '../../types'; +import { initConfig } from './initConfig'; +import { requireSource } from './requireSource'; + +export async function loadConfig(cwd: string): Promise { + return initConfig(requireSource(getConfigPath(cwd)).default); +} diff --git a/packages/core/src/scripts/build/build.ts b/packages/core/src/scripts/build/build.ts index f9a9840bb62..b9ed031bc26 100644 --- a/packages/core/src/scripts/build/build.ts +++ b/packages/core/src/scripts/build/build.ts @@ -3,12 +3,11 @@ import fs from 'fs-extra'; import { AdminFileToWrite } from '../../types'; import { buildAdminUI, generateAdminUI } from '../../admin-ui/system'; import { createSystem } from '../../lib/createSystem'; -import { initConfig } from '../../lib/config/initConfig'; -import { requireSource } from '../../lib/config/requireSource'; import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../../artifacts'; import { getAdminPath, getConfigPath } from '../utils'; import { serializePathForImport } from '../../admin-ui/utils/serializePathForImport'; import { writeAdminFile } from '../../admin-ui/system/generateAdminUI'; +import { loadConfig } from '../../lib/config/loadConfig'; const reexportKeystoneConfig = async (cwd: string, isDisabled?: boolean) => { const projectAdminPath = getAdminPath(cwd); @@ -55,7 +54,7 @@ const reexportKeystoneConfig = async (cwd: string, isDisabled?: boolean) => { }; export async function build(cwd: string) { - const config = initConfig(requireSource(getConfigPath(cwd)).default); + const config = await loadConfig(cwd); const { graphQLSchema, adminMeta } = createSystem(config); diff --git a/packages/core/src/scripts/postinstall.ts b/packages/core/src/scripts/postinstall.ts index 3bbf118642e..55104963801 100644 --- a/packages/core/src/scripts/postinstall.ts +++ b/packages/core/src/scripts/postinstall.ts @@ -4,9 +4,7 @@ import { generateNodeModulesArtifacts, validateCommittedArtifacts, } from '../artifacts'; -import { requireSource } from '../lib/config/requireSource'; -import { initConfig } from '../lib/config/initConfig'; -import { getConfigPath } from './utils'; +import { loadConfig } from '../lib/config/loadConfig'; // The postinstall step serves two purposes: @@ -40,7 +38,7 @@ import { getConfigPath } from './utils'; // * only generated with generateNodeAPI option export async function postinstall(cwd: string, shouldFix: boolean) { - const config = initConfig(requireSource(getConfigPath(cwd)).default); + const config = await loadConfig(cwd); const { graphQLSchema } = createSystem(config); diff --git a/packages/core/src/scripts/prisma.ts b/packages/core/src/scripts/prisma.ts index 5d00ec956e5..a9500861e50 100644 --- a/packages/core/src/scripts/prisma.ts +++ b/packages/core/src/scripts/prisma.ts @@ -1,12 +1,11 @@ import execa from 'execa'; import { createSystem } from '../lib/createSystem'; import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../artifacts'; -import { requireSource } from '../lib/config/requireSource'; -import { initConfig } from '../lib/config/initConfig'; -import { ExitError, getConfigPath } from './utils'; +import { loadConfig } from '../lib/config/loadConfig'; +import { ExitError } from './utils'; export async function prisma(cwd: string, args: string[]) { - const config = initConfig(requireSource(getConfigPath(cwd)).default); + const config = await loadConfig(cwd); const { graphQLSchema } = createSystem(config); diff --git a/packages/core/src/scripts/run/dev.ts b/packages/core/src/scripts/run/dev.ts index 11ede9023bd..4e5d0bfaa12 100644 --- a/packages/core/src/scripts/run/dev.ts +++ b/packages/core/src/scripts/run/dev.ts @@ -11,7 +11,7 @@ import { generateAdminUI } from '../../admin-ui/system'; import { devMigrations, pushPrismaSchemaToDatabase } from '../../lib/migrations'; import { createSystem } from '../../lib/createSystem'; import { initConfig } from '../../lib/config/initConfig'; -import { requireSource } from '../../lib/config/requireSource'; +import { loadConfig } from '../../lib/config/loadConfig'; import { defaults } from '../../lib/config/defaults'; import { createExpressServer } from '../../lib/server/createExpressServer'; import { createAdminUIMiddleware } from '../../lib/server/createAdminUIMiddleware'; @@ -23,7 +23,7 @@ import { getSchemaPaths, requirePrismaClient, } from '../../artifacts'; -import { getAdminPath, getConfigPath } from '../utils'; +import { getAdminPath } from '../utils'; import { createSessionContext } from '../../session'; import { AdminMetaRootVal, CreateContext, KeystoneConfig } from '../../types'; import { serializePathForImport } from '../../admin-ui/utils/serializePathForImport'; @@ -63,7 +63,7 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { // - you have an error in your config after startup -> will keep the last working version until importing the config succeeds // also, if you're thinking "why not always use the Next api route to get the config"? // this will get the GraphQL API up earlier - const configWithHTTP = initConfig(requireSource(getConfigPath(cwd)).default); + const configWithHTTP = await loadConfig(cwd); const config = cleanConfig(configWithHTTP); const isReady = () => diff --git a/scripts/generate-artifacts-for-projects/src/index.ts b/scripts/generate-artifacts-for-projects/src/index.ts index 91867a7da1a..082fc2c1d92 100644 --- a/scripts/generate-artifacts-for-projects/src/index.ts +++ b/scripts/generate-artifacts-for-projects/src/index.ts @@ -1,19 +1,19 @@ import path from 'path'; import fs from 'fs/promises'; import { format } from 'util'; -import { createSystem, initConfig } from '@keystone-6/core/system'; +import { createSystem } from '@keystone-6/core/system'; import { validateCommittedArtifacts, generateNodeModulesArtifacts, generateCommittedArtifacts, } from '@keystone-6/core/artifacts'; -import { requireSource } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/require-source'; +import { loadConfig } from '@keystone-6/core/___internal-do-not-use-will-break-in-patch/load-config'; const mode = process.env.UPDATE_SCHEMAS ? 'generate' : 'validate'; async function generateArtifactsForProjectDir(projectDir: string) { try { - const config = initConfig(requireSource(path.join(projectDir, 'keystone')).default); + const config = await loadConfig(projectDir); const { graphQLSchema } = createSystem(config, false); if (mode === 'validate') { await validateCommittedArtifacts(graphQLSchema, config, projectDir); From b6cf71971b352456d9a4b60e261f7ece17839484 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Thu, 18 Aug 2022 14:00:02 +1000 Subject: [PATCH 2/7] use esbuild --- packages/core/package.json | 7 +- .../load-config.ts | 2 +- packages/core/src/lib/config/loadConfig.ts | 44 +++- packages/core/src/lib/config/requireSource.ts | 62 ----- packages/core/src/scripts/build/build.ts | 63 +---- packages/core/src/scripts/postinstall.ts | 4 +- packages/core/src/scripts/prisma.ts | 4 +- packages/core/src/scripts/run/dev.ts | 222 +++++++++--------- packages/core/src/scripts/run/start.ts | 12 +- packages/core/src/scripts/tests/build.test.ts | 2 - packages/core/src/scripts/utils.ts | 4 + tests/admin-ui-tests/live-reloading.test.ts | 2 +- .../live-reloading/schemas/runtime-error.ts | 3 +- yarn.lock | 146 +++++++++++- 14 files changed, 301 insertions(+), 276 deletions(-) delete mode 100644 packages/core/src/lib/config/requireSource.ts diff --git a/packages/core/package.json b/packages/core/package.json index da7e7f53243..4f1619f7f33 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -31,7 +31,6 @@ "@aws-sdk/client-s3": "^3.83.0", "@aws-sdk/lib-storage": "^3.83.0", "@aws-sdk/s3-request-presigner": "^3.83.0", - "@babel/core": "^7.16.0", "@babel/runtime": "^7.16.3", "@emotion/hash": "^0.9.0", "@emotion/weak-memoize": "^0.3.0", @@ -65,11 +64,9 @@ "@types/express": "^4.17.13", "@types/fs-extra": "^9.0.13", "@types/inflection": "^1.13.0", - "@types/node-fetch": "^2.5.12", "@types/pluralize": "^0.0.29", "@types/prompts": "^2.0.14", "@types/react": "^18.0.9", - "@types/source-map-support": "^0.5.4", "@types/supertest": "^2.0.11", "@types/uid-safe": "^2.1.2", "@types/uuid": "^8.3.1", @@ -89,6 +86,7 @@ "date-fns": "^2.26.0", "decimal.js": "10.4.0", "dumb-passwords": "^0.2.1", + "esbuild": "^0.15.5", "execa": "^5.1.1", "express": "^4.17.1", "fast-deep-equal": "^3.1.3", @@ -105,16 +103,13 @@ "meow": "^9.0.0", "micro": "^9.3.4", "next": "^12.2.4", - "node-fetch": "^2.6.7", "p-limit": "^2.3.0", - "pirates": "4.0.4", "pluralize": "^8.0.0", "prisma": "4.3.1", "prompts": "^2.4.2", "react": "^18.1.0", "react-dom": "^18.1.0", "resolve": "^1.20.0", - "source-map-support": "^0.5.20", "supertest": "^6.1.6", "ts-toolbelt": "^9.6.0", "uid-safe": "^2.1.5", diff --git a/packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts b/packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts index d81128e1fce..416c50a1111 100644 --- a/packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts +++ b/packages/core/src/___internal-do-not-use-will-break-in-patch/load-config.ts @@ -1 +1 @@ -export { loadConfig } from '../lib/config/loadConfig'; +export { loadConfigOnce as loadConfig } from '../lib/config/loadConfig'; diff --git a/packages/core/src/lib/config/loadConfig.ts b/packages/core/src/lib/config/loadConfig.ts index 525367fa842..f8bb2d756c6 100644 --- a/packages/core/src/lib/config/loadConfig.ts +++ b/packages/core/src/lib/config/loadConfig.ts @@ -1,8 +1,44 @@ -import { getConfigPath } from '../../scripts/utils'; +import esbuild, { BuildOptions } from 'esbuild'; import { KeystoneConfig } from '../../types'; +import { getBuiltConfigPath } from '../../scripts/utils'; import { initConfig } from './initConfig'; -import { requireSource } from './requireSource'; -export async function loadConfig(cwd: string): Promise { - return initConfig(requireSource(getConfigPath(cwd)).default); +export function getEsbuildConfig(cwd: string): BuildOptions { + return { + entryPoints: ['./keystone'], + absWorkingDir: cwd, + bundle: true, + outfile: '.keystone/config.js', + format: 'cjs', + plugins: [ + { + name: 'external-node_modules', + setup(build) { + build.onResolve( + { + // this regex is intended to be the opposite of /^\.\.?(?:\/|$)/ + // so it matches anything that isn't a relative import + // so this means that we're only going to bundle relative imports + // we can't use a negative lookahead/lookbehind because this regex is executed + // by Go's regex package which doesn't support them + // this regex could have less duplication with nested groups but this is probably easier to read + filter: /(?:^[^.])|(?:^\.[^/.])|(?:^\.\.[^/])/, + }, + args => { + return { external: true, path: args.path }; + } + ); + }, + }, + ], + }; +} + +export function loadBuiltConfig(cwd: string): KeystoneConfig { + return initConfig(require(getBuiltConfigPath(cwd)).default); +} + +export async function loadConfigOnce(cwd: string): Promise { + await esbuild.build(getEsbuildConfig(cwd)); + return loadBuiltConfig(cwd); } diff --git a/packages/core/src/lib/config/requireSource.ts b/packages/core/src/lib/config/requireSource.ts deleted file mode 100644 index a2a62619a0d..00000000000 --- a/packages/core/src/lib/config/requireSource.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* -This is a slightly-modified version of preconstruct's hook for use with -keystone project files in the monorepo. Importantly it doesn't accept a cwd and -sets rootMode: "upward-optional" -*/ - -import { addHook } from 'pirates'; -import * as babel from '@babel/core'; -import sourceMapSupport from 'source-map-support'; - -const EXTENSIONS = ['.js', '.jsx', '.ts', '.tsx']; - -const hook = () => { - let compiling = false; - let sourceMaps: Record = {}; - let needsToInstallSourceMapSupport = true; - function compileHook(code: string, filename: string) { - if (compiling) return code; - // we do this lazily because jest has its own require implementation - // which means preconstruct's require hook won't be run - // so we don't want to install source map support because that will mess up - // jest's source map support - if (needsToInstallSourceMapSupport) { - sourceMapSupport.install({ - environment: 'node', - retrieveSourceMap(source) { - let map = sourceMaps[source]; - if (map !== undefined) { - return { url: source, map }; - } else { - return null; - } - }, - }); - needsToInstallSourceMapSupport = false; - } - try { - compiling = true; - const output = babel.transformSync(code, { - filename, - presets: [require.resolve('next/babel')], - configFile: false, - babelrc: false, - sourceMaps: 'both', - })!; - sourceMaps[filename] = output.map; - return output.code!; - } finally { - compiling = false; - } - } - return addHook(compileHook, { - exts: EXTENSIONS, - }); -}; - -export const requireSource = (filePath: string) => { - const unregister = hook(); - const result = require(filePath); - unregister(); - return result; -}; diff --git a/packages/core/src/scripts/build/build.ts b/packages/core/src/scripts/build/build.ts index b9ed031bc26..c59921c1930 100644 --- a/packages/core/src/scripts/build/build.ts +++ b/packages/core/src/scripts/build/build.ts @@ -1,60 +1,11 @@ -import Path from 'path'; -import fs from 'fs-extra'; -import { AdminFileToWrite } from '../../types'; import { buildAdminUI, generateAdminUI } from '../../admin-ui/system'; import { createSystem } from '../../lib/createSystem'; import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../../artifacts'; -import { getAdminPath, getConfigPath } from '../utils'; -import { serializePathForImport } from '../../admin-ui/utils/serializePathForImport'; -import { writeAdminFile } from '../../admin-ui/system/generateAdminUI'; -import { loadConfig } from '../../lib/config/loadConfig'; - -const reexportKeystoneConfig = async (cwd: string, isDisabled?: boolean) => { - const projectAdminPath = getAdminPath(cwd); - const configPath = getConfigPath(cwd); - if (isDisabled) { - // Nuke any existing files in our target directory - await fs.remove(projectAdminPath); - } - - // We re-export the Keystone config file into the Admin UI project folder - // so that when we run the build step, we will end up with a compiled version - // of the configuration file in the .next/ directory. Even if we're not building - // an Admin UI, we still need to run the `build()` function so that this config - // file is correctly compiled. - const pkgDir = Path.dirname(require.resolve('@keystone-6/core/package.json')); - const p = serializePathForImport( - Path.relative(Path.join(projectAdminPath, 'pages', 'api'), configPath) - ); - const files: AdminFileToWrite[] = [ - { - mode: 'write', - src: `export { default as config } from ${p}; - export default function (req, res) { return res.status(500) }`, - outputPath: Path.join('pages', 'api', '__keystone_api_build.js'), - }, - ]; - if (isDisabled) { - // These are the basic files required to have a valid Next.js project. If the - // Admin UI is disabled then we need to do this ourselves here. - files.push( - { - mode: 'copy' as const, - inputPath: Path.join(pkgDir, 'static', 'next.config.js'), - outputPath: 'next.config.js', - }, - { - mode: 'copy' as const, - inputPath: Path.join(pkgDir, 'static', 'tsconfig.json'), - outputPath: 'tsconfig.json', - } - ); - } - await Promise.all(files.map(file => writeAdminFile(file, projectAdminPath))); -}; +import { getAdminPath } from '../utils'; +import { loadConfigOnce } from '../../lib/config/loadConfig'; export async function build(cwd: string) { - const config = await loadConfig(cwd); + const config = await loadConfigOnce(cwd); const { graphQLSchema, adminMeta } = createSystem(config); @@ -70,11 +21,7 @@ export async function build(cwd: string) { } else { console.log('✨ Generating Admin UI code'); await generateAdminUI(config, graphQLSchema, adminMeta, getAdminPath(cwd), false); + console.log('✨ Building Admin UI'); + await buildAdminUI(getAdminPath(cwd)); } - - console.log('✨ Generating Keystone config code'); - await reexportKeystoneConfig(cwd, config.ui?.isDisabled); - - console.log('✨ Building Admin UI'); - await buildAdminUI(getAdminPath(cwd)); } diff --git a/packages/core/src/scripts/postinstall.ts b/packages/core/src/scripts/postinstall.ts index 55104963801..5395d42cde8 100644 --- a/packages/core/src/scripts/postinstall.ts +++ b/packages/core/src/scripts/postinstall.ts @@ -4,7 +4,7 @@ import { generateNodeModulesArtifacts, validateCommittedArtifacts, } from '../artifacts'; -import { loadConfig } from '../lib/config/loadConfig'; +import { loadConfigOnce } from '../lib/config/loadConfig'; // The postinstall step serves two purposes: @@ -38,7 +38,7 @@ import { loadConfig } from '../lib/config/loadConfig'; // * only generated with generateNodeAPI option export async function postinstall(cwd: string, shouldFix: boolean) { - const config = await loadConfig(cwd); + const config = await loadConfigOnce(cwd); const { graphQLSchema } = createSystem(config); diff --git a/packages/core/src/scripts/prisma.ts b/packages/core/src/scripts/prisma.ts index a9500861e50..6433f5d5f9b 100644 --- a/packages/core/src/scripts/prisma.ts +++ b/packages/core/src/scripts/prisma.ts @@ -1,11 +1,11 @@ import execa from 'execa'; import { createSystem } from '../lib/createSystem'; import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../artifacts'; -import { loadConfig } from '../lib/config/loadConfig'; +import { loadConfigOnce } from '../lib/config/loadConfig'; import { ExitError } from './utils'; export async function prisma(cwd: string, args: string[]) { - const config = await loadConfig(cwd); + const config = await loadConfigOnce(cwd); const { graphQLSchema } = createSystem(config); diff --git a/packages/core/src/scripts/run/dev.ts b/packages/core/src/scripts/run/dev.ts index 4e5d0bfaa12..db5e6dbbe9a 100644 --- a/packages/core/src/scripts/run/dev.ts +++ b/packages/core/src/scripts/run/dev.ts @@ -1,17 +1,16 @@ import path from 'path'; import type { ListenOptions } from 'net'; import url from 'url'; -import util from 'util'; import { createServer, IncomingMessage, ServerResponse } from 'http'; import express from 'express'; import { GraphQLSchema, printSchema } from 'graphql'; import fs from 'fs-extra'; import chalk from 'chalk'; +import esbuild, { BuildFailure, BuildResult } from 'esbuild'; import { generateAdminUI } from '../../admin-ui/system'; import { devMigrations, pushPrismaSchemaToDatabase } from '../../lib/migrations'; import { createSystem } from '../../lib/createSystem'; -import { initConfig } from '../../lib/config/initConfig'; -import { loadConfig } from '../../lib/config/loadConfig'; +import { getEsbuildConfig, loadBuiltConfig } from '../../lib/config/loadConfig'; import { defaults } from '../../lib/config/defaults'; import { createExpressServer } from '../../lib/server/createExpressServer'; import { createAdminUIMiddleware } from '../../lib/server/createAdminUIMiddleware'; @@ -23,10 +22,9 @@ import { getSchemaPaths, requirePrismaClient, } from '../../artifacts'; -import { getAdminPath } from '../utils'; +import { ExitError, getAdminPath, getBuiltConfigPath } from '../utils'; import { createSessionContext } from '../../session'; import { AdminMetaRootVal, CreateContext, KeystoneConfig } from '../../types'; -import { serializePathForImport } from '../../admin-ui/utils/serializePathForImport'; import { initialiseLists } from '../../lib/core/types-for-lists'; import { printPrismaSchema } from '../../lib/core/prisma-schema'; @@ -36,8 +34,6 @@ const devLoadingHTMLFilepath = path.join( 'dev-loading.html' ); -const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - const cleanConfig = (config: KeystoneConfig): KeystoneConfig => { const { server, ...rest } = config; if (server) { @@ -47,6 +43,19 @@ const cleanConfig = (config: KeystoneConfig): KeystoneConfig => { return rest; }; +function resolvablePromise(): Promise & { resolve: (value: T) => void } { + let _resolve!: (value: T) => void; + const promise: any = new Promise(resolve => { + _resolve = resolve; + }); + promise.resolve = _resolve; + return promise; +} + +function isBuildFailure(err: unknown): err is BuildFailure { + return err instanceof Error && Array.isArray((err as any).errors); +} + export const dev = async (cwd: string, shouldDropDatabase: boolean) => { console.log('✨ Starting Keystone'); @@ -63,7 +72,31 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { // - you have an error in your config after startup -> will keep the last working version until importing the config succeeds // also, if you're thinking "why not always use the Next api route to get the config"? // this will get the GraphQL API up earlier - const configWithHTTP = await loadConfig(cwd); + type WatchBuildResult = { error: BuildFailure | null; result: BuildResult | null }; + + let lastPromise = resolvablePromise>(); + const builds: AsyncIterable = { + [Symbol.asyncIterator]: () => ({ next: () => lastPromise }), + }; + const initialBuildResult = await esbuild + .build({ + ...getEsbuildConfig(cwd), + watch: { + onRebuild(error, result) { + let prev = lastPromise; + lastPromise = resolvablePromise(); + prev.resolve({ value: { error, result }, done: false }); + }, + }, + }) + .catch(async err => { + if (isBuildFailure(err)) { + // when a build failure happens, esbuild will have printed the error already + throw new ExitError(1); + } + throw err; + }); + const configWithHTTP = loadBuiltConfig(cwd); const config = cleanConfig(configWithHTTP); const isReady = () => @@ -71,10 +104,6 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { const initKeystone = async () => { await fs.remove(getAdminPath(cwd)); - const p = serializePathForImport( - path.relative(path.join(getAdminPath(cwd), 'pages', 'api'), `${cwd}/keystone`) - ); - const { adminMeta, graphQLSchema, @@ -115,21 +144,6 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { expressServer.use(adminUIMiddleware); hasAddedAdminUIMiddleware = true; initKeystonePromiseResolve(); - - // this exports a function which dynamically requires the config rather than directly importing it. - // this allows us to control exactly _when_ the gets evaluated so that we can handle errors ourselves. - // note this is intentionally using CommonJS and not ESM because by doing a dynamic import, webpack - // will generate a separate chunk for that whereas a require will be in the same bundle but the execution can still be delayed. - // dynamic importing didn't work on windows because it couldn't get the path to require the chunk for some reason - await fs.outputFile( - `${getAdminPath(cwd)}/pages/api/__keystone_api_build.js`, - `exports.getConfig = () => require(${p}); -const x = Math.random(); -exports.default = function (req, res) { return res.send(x.toString()) } -` - ); - let lastVersion = ''; - let lastError = undefined; const originalPrismaSchema = printPrismaSchema( initialiseLists(config), config.db.provider, @@ -139,99 +153,71 @@ exports.default = function (req, res) { return res.send(x.toString()) } let lastPrintedGraphQLSchema = printSchema(graphQLSchema); let lastApolloServer = apolloServer; - while (true) { - await wait(500); - try { - // this fetching essentially does two things: - // - keeps the api route built, if we don't fetch it, Next will stop compiling it - // - returns a random number which when it changes indicates that the config _might_ have changed - // note that it can go off randomly so the version changing doesn't necessarily - // mean that the config has changed, Next might have just reloaded for some random reason - // so we shouldn't log something like "hey, we reloaded your config" - // because it would go off at times when the user didn't change their config - - const version = await fetch( - `http://localhost:${httpOptions.port}/api/__keystone_api_build` - ).then(x => x.text()); - if (lastVersion !== version) { - lastVersion = version; - const resolved = require.resolve( - `${getAdminPath(cwd)}/.next/server/pages/api/__keystone_api_build` - ); - delete require.cache[resolved]; - // webpack will make modules that import Node ESM externals(which must be loaded with dynamic import) - // export a promise that resolves to the actual export so yeah, we need to await a require call - // technically, the await for requiring the api route module isn't necessary since there are no imports there - // but just in case webpack decides to make it async in the future, this'll still work - const apiRouteModule = await require(resolved); - const uninitializedConfig = (await apiRouteModule.getConfig()).default; - const newConfigWithHttp = initConfig(uninitializedConfig); - const newConfig = cleanConfig(newConfigWithHttp); - const newPrismaSchema = printPrismaSchema( - initialiseLists(newConfig), - newConfig.db.provider, - newConfig.db.prismaPreviewFeatures, - newConfig.db.additionalPrismaDatasourceProperties - ); - if (originalPrismaSchema !== newPrismaSchema) { - console.log('🔄 Your prisma schema has changed, please restart Keystone'); - process.exit(1); - } - // we only need to test for the things which influence the prisma client creation - // and aren't written into the prisma schema since we check whether the prisma schema has changed above - if ( - newConfig.db.enableLogging !== config.db.enableLogging || - newConfig.db.url !== config.db.url || - newConfig.db.useMigrations !== config.db.useMigrations - ) { - console.log('Your db config has changed, please restart Keystone'); - process.exit(1); - } - const { graphQLSchema, getKeystone, adminMeta } = createSystem(newConfig, true); - // we're not using generateCommittedArtifacts or any of the similar functions - // because we will never need to write a new prisma schema here - // and formatting the prisma schema leaves some listeners on the process - // which means you get a "there's probably a memory leak" warning from node - const newPrintedGraphQLSchema = printSchema(graphQLSchema); - if (newPrintedGraphQLSchema !== lastPrintedGraphQLSchema) { - await fs.writeFile( - getSchemaPaths(cwd).graphql, - getFormattedGraphQLSchema(newPrintedGraphQLSchema) - ); - lastPrintedGraphQLSchema = newPrintedGraphQLSchema; - } + for await (const buildResult of builds) { + if (buildResult.error) { + // esbuild will have printed the error already + continue; + } + console.log('compiled successfully'); - await generateNodeModulesArtifactsWithoutPrismaClient(graphQLSchema, newConfig, cwd); - await generateAdminUI(newConfig, graphQLSchema, adminMeta, getAdminPath(cwd), true); - const keystone = getKeystone({ - PrismaClient: function fakePrismaClientClass() { - return prismaClient; - } as unknown as new (args: unknown) => any, - Prisma: prismaClientModule.Prisma, - }); - await keystone.connect(); - const servers = await createExpressServer( - newConfig, - graphQLSchema, - keystone.createContext + try { + const resolved = require.resolve(getBuiltConfigPath(cwd)); + delete require.cache[resolved]; + const newConfigWithHttp = loadBuiltConfig(cwd); + const newConfig = cleanConfig(newConfigWithHttp); + const newPrismaSchema = printPrismaSchema( + initialiseLists(newConfig), + newConfig.db.provider, + newConfig.db.prismaPreviewFeatures, + newConfig.db.additionalPrismaDatasourceProperties + ); + if (originalPrismaSchema !== newPrismaSchema) { + console.log('🔄 Your prisma schema has changed, please restart Keystone'); + process.exit(1); + } + // we only need to test for the things which influence the prisma client creation + // and aren't written into the prisma schema since we check whether the prisma schema has changed above + if ( + newConfig.db.enableLogging !== config.db.enableLogging || + newConfig.db.url !== config.db.url || + newConfig.db.useMigrations !== config.db.useMigrations + ) { + console.log('Your db config has changed, please restart Keystone'); + process.exit(1); + } + const { graphQLSchema, getKeystone, adminMeta } = createSystem(newConfig, true); + // we're not using generateCommittedArtifacts or any of the similar functions + // because we will never need to write a new prisma schema here + // and formatting the prisma schema leaves some listeners on the process + // which means you get a "there's probably a memory leak" warning from node + const newPrintedGraphQLSchema = printSchema(graphQLSchema); + if (newPrintedGraphQLSchema !== lastPrintedGraphQLSchema) { + await fs.writeFile( + getSchemaPaths(cwd).graphql, + getFormattedGraphQLSchema(newPrintedGraphQLSchema) ); - - servers.expressServer.use(adminUIMiddleware); - expressServer = servers.expressServer; - let prevApolloServer = lastApolloServer; - lastApolloServer = servers.apolloServer; - await prevApolloServer.stop(); - lastError = undefined; + lastPrintedGraphQLSchema = newPrintedGraphQLSchema; } + + await generateNodeModulesArtifactsWithoutPrismaClient(graphQLSchema, newConfig, cwd); + await generateAdminUI(newConfig, graphQLSchema, adminMeta, getAdminPath(cwd), true); + const keystone = getKeystone({ + PrismaClient: function fakePrismaClientClass() { + return prismaClient; + } as unknown as new (args: unknown) => any, + Prisma: prismaClientModule.Prisma, + }); + await keystone.connect(); + const servers = await createExpressServer(newConfig, graphQLSchema, keystone.createContext); + + servers.expressServer.use(adminUIMiddleware); + expressServer = servers.expressServer; + let prevApolloServer = lastApolloServer; + lastApolloServer = servers.apolloServer; + await prevApolloServer.stop(); } catch (err) { - // since Next will sometimes randomly refresh the api route even though it hasn't changed - // we want to avoid showing the same error again - const printed = util.inspect(err); - if (printed !== lastError) { - console.log('🚨', chalk.red('There was an error loading your Keystone config')); - console.log(printed); - lastError = printed; - } + console.log('🚨', chalk.red('There was an error loading your Keystone config')); + console.log(err); } } }; @@ -328,6 +314,7 @@ exports.default = function (req, res) { return res.send(x.toString()) } return () => new Promise((resolve, reject) => { server.close(async err => { + initialBuildResult.stop!(); try { await disconnect?.(); } catch (disconnectionError: any) { @@ -425,6 +412,9 @@ async function initAdminUI( cwd: string, createContext: CreateContext ) { + if (config.ui?.isDisabled) { + return express(); + } console.log('✨ Generating Admin UI code'); await generateAdminUI(config, graphQLSchema, adminMeta, getAdminPath(cwd), false); diff --git a/packages/core/src/scripts/run/start.ts b/packages/core/src/scripts/run/start.ts index bf6b96f3ff2..0e17f5c8ba7 100644 --- a/packages/core/src/scripts/run/start.ts +++ b/packages/core/src/scripts/run/start.ts @@ -1,26 +1,22 @@ -import path from 'path'; import type { ListenOptions } from 'net'; import * as fs from 'fs-extra'; import { createSystem } from '../../lib/createSystem'; -import { initConfig } from '../../lib/config/initConfig'; import { createExpressServer } from '../../lib/server/createExpressServer'; import { createAdminUIMiddleware } from '../../lib/server/createAdminUIMiddleware'; import { requirePrismaClient } from '../../artifacts'; -import { ExitError, getAdminPath } from '../utils'; +import { ExitError, getAdminPath, getBuiltConfigPath } from '../utils'; +import { loadBuiltConfig } from '../../lib/config/loadConfig'; export const start = async (cwd: string) => { console.log('✨ Starting Keystone'); // This is the compiled version of the configuration which was generated during the build step. - // See reexportKeystoneConfig(). - const apiFile = path.join(getAdminPath(cwd), '.next/server/pages/api/__keystone_api_build.js'); + const apiFile = getBuiltConfigPath(cwd); if (!fs.existsSync(apiFile)) { console.log('🚨 keystone build must be run before running keystone start'); throw new ExitError(1); } - // webpack will make modules that import Node ESM externals(which must be loaded with dynamic import) - // export a promise that resolves to the actual export so yeah, we need to await a require call - const config = initConfig((await require(apiFile)).config); + const config = loadBuiltConfig(cwd); const { getKeystone, graphQLSchema } = createSystem(config); const prismaClient = requirePrismaClient(cwd); diff --git a/packages/core/src/scripts/tests/build.test.ts b/packages/core/src/scripts/tests/build.test.ts index 32d94295d2f..1d869693392 100644 --- a/packages/core/src/scripts/tests/build.test.ts +++ b/packages/core/src/scripts/tests/build.test.ts @@ -76,7 +76,6 @@ test('build works with typescript without the user defining a babel config', asy ).toMatchInlineSnapshot(` "✨ Building Keystone ✨ Generating Admin UI code - ✨ Generating Keystone config code ✨ Building Admin UI info - Skipping validation of types info - Skipping linting @@ -89,7 +88,6 @@ test('build works with typescript without the user defining a babel config', asy info - Generating static pages (5/7) info - Generating static pages (7/7) next build size report - λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps) ○ (Static) automatically rendered as static HTML (uses no initial props) " `); diff --git a/packages/core/src/scripts/utils.ts b/packages/core/src/scripts/utils.ts index 94946b6f2aa..8e19d404557 100644 --- a/packages/core/src/scripts/utils.ts +++ b/packages/core/src/scripts/utils.ts @@ -8,6 +8,10 @@ export function getAdminPath(cwd: string) { return path.join(cwd, '.keystone/admin'); } +export function getBuiltConfigPath(cwd: string) { + return path.join(cwd, '.keystone/config.js'); +} + export class ExitError extends Error { code: number; constructor(code: number) { diff --git a/tests/admin-ui-tests/live-reloading.test.ts b/tests/admin-ui-tests/live-reloading.test.ts index 911d0193d26..ca3685cbc5e 100644 --- a/tests/admin-ui-tests/live-reloading.test.ts +++ b/tests/admin-ui-tests/live-reloading.test.ts @@ -108,7 +108,7 @@ test('the generated schema includes schema updates', async () => { test("a syntax error is shown and doesn't crash the process", async () => { await replaceSchema('syntax-error'); - await expectContentInStdio(process, 'error - ../../schemas/syntax-error.js'); + await expectContentInStdio(process, '✘ [ERROR] Expected ";" but found "const"'); }); test("a runtime error is shown and doesn't crash the process", async () => { diff --git a/tests/test-projects/live-reloading/schemas/runtime-error.ts b/tests/test-projects/live-reloading/schemas/runtime-error.ts index 33cfb003795..ae52398488f 100644 --- a/tests/test-projects/live-reloading/schemas/runtime-error.ts +++ b/tests/test-projects/live-reloading/schemas/runtime-error.ts @@ -1,4 +1,5 @@ // @ts-ignore doesNotExist(); -export {}; +export const lists = {}; +export const extendGraphqlSchema = {}; diff --git a/yarn.lock b/yarn.lock index 7ee92a3b81a..fdeb52e4705 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2688,6 +2688,11 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== +"@esbuild/linux-loong64@0.15.5": + version "0.15.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.5.tgz#91aef76d332cdc7c8942b600fa2307f3387e6f82" + integrity sha512-UHkDFCfSGTuXq08oQltXxSZmH1TXyWsL+4QhZDWvvLl6mEJQqk3u7/wq1LjhrrAXYIllaTtRSzUXl4Olkf2J8A== + "@eslint/eslintrc@^1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" @@ -4661,13 +4666,6 @@ "@types/mime" "*" "@types/node" "*" -"@types/source-map-support@^0.5.4": - version "0.5.4" - resolved "https://registry.yarnpkg.com/@types/source-map-support/-/source-map-support-0.5.4.tgz#574ff6a8636bc0ebae78a8014136f749b3177d58" - integrity sha512-9zGujX1sOPg32XLyfgEB/0G9ZnrjthL/Iv1ZfuAjj8LEilHZEpQSQs1scpRXPhHzGYgWiLz9ldF1cI8JhL+yMw== - dependencies: - source-map "^0.6.0" - "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -7016,6 +7014,133 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +esbuild-android-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.5.tgz#3c7b2f2a59017dab3f2c0356188a8dd9cbdc91c8" + integrity sha512-dYPPkiGNskvZqmIK29OPxolyY3tp+c47+Fsc2WYSOVjEPWNCHNyqhtFqQadcXMJDQt8eN0NMDukbyQgFcHquXg== + +esbuild-android-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.5.tgz#e301db818c5a67b786bf3bb7320e414ac0fcf193" + integrity sha512-YyEkaQl08ze3cBzI/4Cm1S+rVh8HMOpCdq8B78JLbNFHhzi4NixVN93xDrHZLztlocEYqi45rHHCgA8kZFidFg== + +esbuild-darwin-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.5.tgz#11726de5d0bf5960b92421ef433e35871c091f8d" + integrity sha512-Cr0iIqnWKx3ZTvDUAzG0H/u9dWjLE4c2gTtRLz4pqOBGjfjqdcZSfAObFzKTInLLSmD0ZV1I/mshhPoYSBMMCQ== + +esbuild-darwin-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.5.tgz#ad89dafebb3613fd374f5a245bb0ce4132413997" + integrity sha512-WIfQkocGtFrz7vCu44ypY5YmiFXpsxvz2xqwe688jFfSVCnUsCn2qkEVDo7gT8EpsLOz1J/OmqjExePL1dr1Kg== + +esbuild-freebsd-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.5.tgz#6bfb52b4a0d29c965aa833e04126e95173289c8a" + integrity sha512-M5/EfzV2RsMd/wqwR18CELcenZ8+fFxQAAEO7TJKDmP3knhWSbD72ILzrXFMMwshlPAS1ShCZ90jsxkm+8FlaA== + +esbuild-freebsd-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.5.tgz#38a3fed8c6398072f9914856c7c3e3444f9ef4dd" + integrity sha512-2JQQ5Qs9J0440F/n/aUBNvY6lTo4XP/4lt1TwDfHuo0DY3w5++anw+jTjfouLzbJmFFiwmX7SmUhMnysocx96w== + +esbuild-linux-32@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.5.tgz#942dc70127f0c0a7ea91111baf2806e61fc81b32" + integrity sha512-gO9vNnIN0FTUGjvTFucIXtBSr1Woymmx/aHQtuU+2OllGU6YFLs99960UD4Dib1kFovVgs59MTXwpFdVoSMZoQ== + +esbuild-linux-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.5.tgz#6d748564492d5daaa7e62420862c31ac3a44aed9" + integrity sha512-ne0GFdNLsm4veXbTnYAWjbx3shpNKZJUd6XpNbKNUZaNllDZfYQt0/zRqOg0sc7O8GQ+PjSMv9IpIEULXVTVmg== + +esbuild-linux-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.5.tgz#28cd899beb2d2b0a3870fd44f4526835089a318d" + integrity sha512-7EgFyP2zjO065XTfdCxiXVEk+f83RQ1JsryN1X/VSX2li9rnHAt2swRbpoz5Vlrl6qjHrCmq5b6yxD13z6RheA== + +esbuild-linux-arm@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.5.tgz#6441c256225564d8794fdef5b0a69bc1a43051b5" + integrity sha512-wvAoHEN+gJ/22gnvhZnS/+2H14HyAxM07m59RSLn3iXrQsdS518jnEWRBnJz3fR6BJa+VUTo0NxYjGaNt7RA7Q== + +esbuild-linux-mips64le@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.5.tgz#d4927f817290eaffc062446896b2a553f0e11981" + integrity sha512-KdnSkHxWrJ6Y40ABu+ipTZeRhFtc8dowGyFsZY5prsmMSr1ZTG9zQawguN4/tunJ0wy3+kD54GaGwdcpwWAvZQ== + +esbuild-linux-ppc64le@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.5.tgz#b6d660dc6d5295f89ac51c675f1a2f639e2fb474" + integrity sha512-QdRHGeZ2ykl5P0KRmfGBZIHmqcwIsUKWmmpZTOq573jRWwmpfRmS7xOhmDHBj9pxv+6qRMH8tLr2fe+ZKQvCYw== + +esbuild-linux-riscv64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.5.tgz#2801bf18414dc3d3ad58d1ea83084f00d9d84896" + integrity sha512-p+WE6RX+jNILsf+exR29DwgV6B73khEQV0qWUbzxaycxawZ8NE0wA6HnnTxbiw5f4Gx9sJDUBemh9v49lKOORA== + +esbuild-linux-s390x@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.5.tgz#12a634ae6d3384cacc2b8f4201047deafe596eae" + integrity sha512-J2ngOB4cNzmqLHh6TYMM/ips8aoZIuzxJnDdWutBw5482jGXiOzsPoEF4j2WJ2mGnm7FBCO4StGcwzOgic70JQ== + +esbuild-netbsd-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.5.tgz#951bbf87600512dfcfbe3b8d9d117d684d26c1b8" + integrity sha512-MmKUYGDizYjFia0Rwt8oOgmiFH7zaYlsoQ3tIOfPxOqLssAsEgG0MUdRDm5lliqjiuoog8LyDu9srQk5YwWF3w== + +esbuild-openbsd-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.5.tgz#26705b61961d525d79a772232e8b8f211fdbb035" + integrity sha512-2mMFfkLk3oPWfopA9Plj4hyhqHNuGyp5KQyTT9Rc8hFd8wAn5ZrbJg+gNcLMo2yzf8Uiu0RT6G9B15YN9WQyMA== + +esbuild-sunos-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.5.tgz#d794da1ae60e6e2f6194c44d7b3c66bf66c7a141" + integrity sha512-2sIzhMUfLNoD+rdmV6AacilCHSxZIoGAU2oT7XmJ0lXcZWnCvCtObvO6D4puxX9YRE97GodciRGDLBaiC6x1SA== + +esbuild-windows-32@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.5.tgz#0670326903f421424be86bc03b7f7b3ff86a9db7" + integrity sha512-e+duNED9UBop7Vnlap6XKedA/53lIi12xv2ebeNS4gFmu7aKyTrok7DPIZyU5w/ftHD4MUDs5PJUkQPP9xJRzg== + +esbuild-windows-64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.5.tgz#64f32acb7341f3f0a4d10e8ff1998c2d1ebfc0a9" + integrity sha512-v+PjvNtSASHOjPDMIai9Yi+aP+Vwox+3WVdg2JB8N9aivJ7lyhp4NVU+J0MV2OkWFPnVO8AE/7xH+72ibUUEnw== + +esbuild-windows-arm64@0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.5.tgz#4fe7f333ce22a922906b10233c62171673a3854b" + integrity sha512-Yz8w/D8CUPYstvVQujByu6mlf48lKmXkq6bkeSZZxTA626efQOJb26aDGLzmFWx6eg/FwrXgt6SZs9V8Pwy/aA== + +esbuild@^0.15.5: + version "0.15.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.5.tgz#5effd05666f621d4ff2fe2c76a67c198292193ff" + integrity sha512-VSf6S1QVqvxfIsSKb3UKr3VhUCis7wgDbtF4Vd9z84UJr05/Sp2fRKmzC+CSPG/dNAPPJZ0BTBLTT1Fhd6N9Gg== + optionalDependencies: + "@esbuild/linux-loong64" "0.15.5" + esbuild-android-64 "0.15.5" + esbuild-android-arm64 "0.15.5" + esbuild-darwin-64 "0.15.5" + esbuild-darwin-arm64 "0.15.5" + esbuild-freebsd-64 "0.15.5" + esbuild-freebsd-arm64 "0.15.5" + esbuild-linux-32 "0.15.5" + esbuild-linux-64 "0.15.5" + esbuild-linux-arm "0.15.5" + esbuild-linux-arm64 "0.15.5" + esbuild-linux-mips64le "0.15.5" + esbuild-linux-ppc64le "0.15.5" + esbuild-linux-riscv64 "0.15.5" + esbuild-linux-s390x "0.15.5" + esbuild-netbsd-64 "0.15.5" + esbuild-openbsd-64 "0.15.5" + esbuild-sunos-64 "0.15.5" + esbuild-windows-32 "0.15.5" + esbuild-windows-64 "0.15.5" + esbuild-windows-arm64 "0.15.5" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -11759,11 +11884,6 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== -pirates@4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" - integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw== - pirates@^4.0.1, pirates@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" @@ -13005,7 +13125,7 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@^0.5.16, source-map-support@^0.5.20, source-map-support@~0.5.20: +source-map-support@^0.5.16, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== From 1220259d117a4db7dc1222b3c15e075a48e15d3e Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 9 Sep 2022 12:17:47 +1000 Subject: [PATCH 3/7] Remove some hacks from the cli tests so they work with esbuild --- .eslintrc.js | 27 ++---- package.json | 1 - packages/core/package.json | 1 - .../core/src/scripts/tests/artifacts.test.ts | 51 ++++------- packages/core/src/scripts/tests/build.test.ts | 24 +----- .../tests/fixtures/basic-project/keystone.ts | 18 ++++ .../tests/fixtures/basic-with-no-ui.ts | 19 ++++ .../fixtures/generate-next-graphql-api.ts | 21 +++++ .../tests/fixtures/generate-node-api.ts | 21 +++++ .../fixtures/no-fields-with-migrations.ts | 17 ++++ .../src/scripts/tests/fixtures/no-fields.ts | 16 ++++ .../fixtures/one-field-with-migrations.ts | 20 +++++ .../fixtures/two-fields-with-migrations.ts | 21 +++++ .../src/scripts/tests/fixtures/with-ts.ts | 17 ++++ .../core/src/scripts/tests/migrations.test.ts | 86 +++++-------------- packages/core/src/scripts/tests/utils.tsx | 25 +----- .../src/index.ts | 5 +- yarn.lock | 19 ---- 18 files changed, 226 insertions(+), 183 deletions(-) create mode 100644 packages/core/src/scripts/tests/fixtures/basic-project/keystone.ts create mode 100644 packages/core/src/scripts/tests/fixtures/basic-with-no-ui.ts create mode 100644 packages/core/src/scripts/tests/fixtures/generate-next-graphql-api.ts create mode 100644 packages/core/src/scripts/tests/fixtures/generate-node-api.ts create mode 100644 packages/core/src/scripts/tests/fixtures/no-fields-with-migrations.ts create mode 100644 packages/core/src/scripts/tests/fixtures/no-fields.ts create mode 100644 packages/core/src/scripts/tests/fixtures/one-field-with-migrations.ts create mode 100644 packages/core/src/scripts/tests/fixtures/two-fields-with-migrations.ts create mode 100644 packages/core/src/scripts/tests/fixtures/with-ts.ts diff --git a/.eslintrc.js b/.eslintrc.js index 0aca56515a0..051de2bbf9e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -11,14 +11,7 @@ module.exports = { node: true, jest: true, }, - plugins: [ - 'react', - 'react-hooks', - 'jest', - 'import', - '@typescript-eslint', - '@preconstruct/format-js-tag', - ], + plugins: ['react', 'react-hooks', 'jest', 'import', '@typescript-eslint'], settings: { react: { version: 'detect', @@ -103,31 +96,23 @@ module.exports = { }, }, ], - '@preconstruct/format-js-tag/format': 'error', }, extends: ['plugin:jest/recommended'], // Disable some rules for (code blocks within) Markdown docs overrides: [ { - files: ['**/*.md'], + files: ['**/*.{ts,tsx}'], rules: { - 'no-unused-vars': 'off', + // TypeScript already checks for the following things and they conflict with TypeScript + 'import/no-unresolved': 'off', 'no-undef': 'off', }, }, { - files: ['packages/fields/src/**/*.{js,ts,tsx}'], - rules: { - 'import/no-commonjs': 'error', - }, - }, - { - files: ['**/*.{ts,tsx}'], + files: ['packages/core/src/scripts/tests/fixtures/**/*.{ts,tsx}'], rules: { - // TypeScript already checks for the following things and they conflict with TypeScript - 'import/no-unresolved': 'off', - 'no-undef': 'off', + 'import/no-extraneous-dependencies': 'off', }, }, ], diff --git a/package.json b/package.json index 8535cbdbd05..74485bc9f62 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@jest/test-sequencer": "^29.0.0", "@manypkg/cli": "^0.19.1", "@preconstruct/cli": "2.2.1", - "@preconstruct/eslint-plugin-format-js-tag": "^0.2.0", "@testing-library/jest-dom": "^5.15.0", "@types/babel__core": "^7.1.16", "@types/jest": "^29.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 4f1619f7f33..d1bad8cd2ee 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -120,7 +120,6 @@ "fast-glob": "^3.2.7", "fixturez": "^1.1.0", "mime": "^3.0.0", - "outdent": "^0.8.0", "string-argv": "^0.3.1", "strip-ansi": "^6.0.1" }, diff --git a/packages/core/src/scripts/tests/artifacts.test.ts b/packages/core/src/scripts/tests/artifacts.test.ts index 4899908490d..343b436aaa9 100644 --- a/packages/core/src/scripts/tests/artifacts.test.ts +++ b/packages/core/src/scripts/tests/artifacts.test.ts @@ -1,8 +1,7 @@ -import { text } from '../../fields'; -import { config, list } from '../..'; +import fs from 'fs/promises'; import { ExitError } from '../utils'; -import { allowAll } from '../../access'; import { + basicKeystoneConfig, getFiles, recordConsole, runCommand, @@ -11,21 +10,6 @@ import { testdir, } from './utils'; -const basicKeystoneConfig = { - kind: 'config' as const, - config: config({ - db: { provider: 'sqlite', url: 'file:./app.db' }, - lists: { - Todo: list({ - access: allowAll, - fields: { - title: text(), - }, - }), - }, - }), -}; - describe.each(['postinstall', 'build', 'prisma migrate status'])('%s', command => { test('logs an error and exits with 1 when the schemas do not exist and the terminal is non-interactive', async () => { const tmp = await testdir({ @@ -41,6 +25,8 @@ describe.each(['postinstall', 'build', 'prisma migrate status'])('%s', command = }); }); +const schemasMatch = ['schema.prisma', 'schema.graphql']; + // a lot of these cases are also the same for prisma and build commands but we don't include them here // because when they're slow and then run the same code as the postinstall command // (and in the case of the build command we need to spawn a child process which would make each case take a _very_ long time) @@ -54,12 +40,8 @@ describe('postinstall', () => { 'Would you like to update your Prisma and GraphQL schemas?': true, }); await runCommand(tmp, 'postinstall'); - const files = await getFiles(tmp, ['schema.prisma', 'schema.graphql']); - // to update them - // for (const [file, content] of Object.entries(files)) { - // require('fs').writeFileSync(`${__dirname}/fixtures/basic-project/${file}`, content); - // } - expect(files).toEqual(await getFiles(`${__dirname}/fixtures/basic-project`)); + const files = await getFiles(tmp, schemasMatch); + expect(files).toEqual(await getFiles(`${__dirname}/fixtures/basic-project`, schemasMatch)); expect(recording()).toMatchInlineSnapshot(` "Your Prisma and GraphQL schemas are not up to date Prompt: Would you like to update your Prisma and GraphQL schemas? true @@ -73,8 +55,8 @@ describe('postinstall', () => { }); const recording = recordConsole(); await runCommand(tmp, 'postinstall --fix'); - const files = await getFiles(tmp, ['schema.prisma', 'schema.graphql']); - expect(files).toEqual(await getFiles(`${__dirname}/fixtures/basic-project`)); + const files = await getFiles(tmp, schemasMatch); + expect(files).toEqual(await getFiles(`${__dirname}/fixtures/basic-project`, schemasMatch)); expect(recording()).toMatchInlineSnapshot(`"✨ Generated GraphQL and Prisma schemas"`); }); test("does not prompt, error or modify the schemas if they're already up to date", async () => { @@ -85,8 +67,8 @@ describe('postinstall', () => { }); const recording = recordConsole(); await runCommand(tmp, 'postinstall'); - const files = await getFiles(tmp, ['schema.prisma', 'schema.graphql']); - expect(files).toEqual(await getFiles(`${__dirname}/fixtures/basic-project`)); + const files = await getFiles(tmp, schemasMatch); + expect(files).toEqual(await getFiles(`${__dirname}/fixtures/basic-project`, schemasMatch)); expect(recording()).toMatchInlineSnapshot(`"✨ GraphQL and Prisma schemas are up to date"`); }); test('writes the correct node_modules files', async () => { @@ -104,10 +86,7 @@ describe('postinstall', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...schemas, - 'keystone.js': { - kind: 'config', - config: { ...basicKeystoneConfig.config, experimental: { generateNodeAPI: true } }, - }, + 'keystone.js': await fs.readFile(`${__dirname}/fixtures/generate-node-api.ts`, 'utf8'), }); const recording = recordConsole(); await runCommand(tmp, 'postinstall'); @@ -136,10 +115,10 @@ describe('postinstall', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...schemas, - 'keystone.js': { - kind: 'config', - config: { ...basicKeystoneConfig.config, experimental: { generateNextGraphqlAPI: true } }, - }, + 'keystone.js': await fs.readFile( + `${__dirname}/fixtures/generate-next-graphql-api.ts`, + 'utf8' + ), }); const recording = recordConsole(); await runCommand(tmp, 'postinstall'); diff --git a/packages/core/src/scripts/tests/build.test.ts b/packages/core/src/scripts/tests/build.test.ts index 1d869693392..53e88d55727 100644 --- a/packages/core/src/scripts/tests/build.test.ts +++ b/packages/core/src/scripts/tests/build.test.ts @@ -1,10 +1,10 @@ import execa from 'execa'; import * as fs from 'fs-extra'; +import stripAnsi from 'strip-ansi'; import { ExitError } from '../utils'; import { basicKeystoneConfig, cliBinPath, - js, recordConsole, runCommand, schemas, @@ -36,23 +36,7 @@ test('build works with typescript without the user defining a babel config', asy const tmp = await testdir({ ...symlinkKeystoneDeps, ...schemas, - 'keystone.ts': js` - import { config, list } from "@keystone-6/core"; - import { text } from "@keystone-6/core/fields"; - - type x = string; - - export default config({ - db: { provider: "sqlite", url: "file:./app.db" }, - lists: { - Todo: list({ - fields: { - title: text(), - }, - }), - }, - }); - `, + 'keystone.ts': await fs.readFile(`${__dirname}/fixtures/with-ts.ts`, 'utf8'), }); const result = await execa('node', [cliBinPath, 'build'], { reject: false, @@ -64,8 +48,8 @@ test('build works with typescript without the user defining a babel config', asy }); expect(await fs.readFile(`${tmp}/node_modules/.keystone/types.js`, 'utf8')).toBe(''); expect( - result - .all!.replace( + stripAnsi(result.all!) + .replace( '\nwarn - No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache', '' ) diff --git a/packages/core/src/scripts/tests/fixtures/basic-project/keystone.ts b/packages/core/src/scripts/tests/fixtures/basic-project/keystone.ts new file mode 100644 index 00000000000..84e79ecc89d --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/basic-project/keystone.ts @@ -0,0 +1,18 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; +import { text } from '@keystone-6/core/fields'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + }, + lists: { + Todo: list({ + access: allowAll, + fields: { + title: text(), + }, + }), + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/basic-with-no-ui.ts b/packages/core/src/scripts/tests/fixtures/basic-with-no-ui.ts new file mode 100644 index 00000000000..f45db094008 --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/basic-with-no-ui.ts @@ -0,0 +1,19 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; +import { text } from '@keystone-6/core/fields'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + }, + ui: { isDisabled: true }, + lists: { + Todo: list({ + access: allowAll, + fields: { + title: text(), + }, + }), + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/generate-next-graphql-api.ts b/packages/core/src/scripts/tests/fixtures/generate-next-graphql-api.ts new file mode 100644 index 00000000000..5218815d12f --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/generate-next-graphql-api.ts @@ -0,0 +1,21 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; +import { text } from '@keystone-6/core/fields'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + }, + lists: { + Todo: list({ + access: allowAll, + fields: { + title: text(), + }, + }), + }, + experimental: { + generateNextGraphqlAPI: true, + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/generate-node-api.ts b/packages/core/src/scripts/tests/fixtures/generate-node-api.ts new file mode 100644 index 00000000000..149710e8fb2 --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/generate-node-api.ts @@ -0,0 +1,21 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; +import { text } from '@keystone-6/core/fields'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + }, + lists: { + Todo: list({ + access: allowAll, + fields: { + title: text(), + }, + }), + }, + experimental: { + generateNodeAPI: true, + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/no-fields-with-migrations.ts b/packages/core/src/scripts/tests/fixtures/no-fields-with-migrations.ts new file mode 100644 index 00000000000..956fe3e2833 --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/no-fields-with-migrations.ts @@ -0,0 +1,17 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + useMigrations: true, + }, + ui: { isDisabled: true }, + lists: { + Todo: list({ + access: allowAll, + fields: {}, + }), + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/no-fields.ts b/packages/core/src/scripts/tests/fixtures/no-fields.ts new file mode 100644 index 00000000000..700231dfa44 --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/no-fields.ts @@ -0,0 +1,16 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + }, + ui: { isDisabled: true }, + lists: { + Todo: list({ + access: allowAll, + fields: {}, + }), + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/one-field-with-migrations.ts b/packages/core/src/scripts/tests/fixtures/one-field-with-migrations.ts new file mode 100644 index 00000000000..8089cd3bac7 --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/one-field-with-migrations.ts @@ -0,0 +1,20 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; +import { text } from '@keystone-6/core/fields'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + useMigrations: true, + }, + ui: { isDisabled: true }, + lists: { + Todo: list({ + access: allowAll, + fields: { + title: text(), + }, + }), + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/two-fields-with-migrations.ts b/packages/core/src/scripts/tests/fixtures/two-fields-with-migrations.ts new file mode 100644 index 00000000000..edb5c3d23f1 --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/two-fields-with-migrations.ts @@ -0,0 +1,21 @@ +import { list, config } from '@keystone-6/core'; +import { allowAll } from '@keystone-6/core/access'; +import { checkbox, text } from '@keystone-6/core/fields'; + +export default config({ + db: { + provider: 'sqlite', + url: 'file:./app.db', + useMigrations: true, + }, + ui: { isDisabled: true }, + lists: { + Todo: list({ + access: allowAll, + fields: { + title: text(), + isComplete: checkbox(), + }, + }), + }, +}); diff --git a/packages/core/src/scripts/tests/fixtures/with-ts.ts b/packages/core/src/scripts/tests/fixtures/with-ts.ts new file mode 100644 index 00000000000..4b4da154764 --- /dev/null +++ b/packages/core/src/scripts/tests/fixtures/with-ts.ts @@ -0,0 +1,17 @@ +import { config, list } from '@keystone-6/core'; +import { text } from '@keystone-6/core/fields'; +import { allowAll } from '@keystone-6/core/access'; + +export type something = string; + +export default config({ + db: { provider: 'sqlite', url: 'file:./app.db' }, + lists: { + Todo: list({ + access: allowAll, + fields: { + title: text(), + }, + }), + }, +}); diff --git a/packages/core/src/scripts/tests/migrations.test.ts b/packages/core/src/scripts/tests/migrations.test.ts index 7e6f4e27d33..f54d1eeacd1 100644 --- a/packages/core/src/scripts/tests/migrations.test.ts +++ b/packages/core/src/scripts/tests/migrations.test.ts @@ -1,10 +1,6 @@ import fs from 'fs-extra'; -import { ListSchemaConfig } from '../../types'; -import { checkbox, text } from '../../fields'; import { requirePrismaClient } from '../../artifacts'; -import { config, list } from '../..'; import { ExitError } from '../utils'; -import { allowAll } from '../../access'; import { getFiles, introspectDb, @@ -14,26 +10,8 @@ import { testdir, } from './utils'; -const basicLists = { - Todo: list({ - access: allowAll, - fields: { - title: text(), - }, - }), -}; - const dbUrl = 'file:./app.db'; -const basicKeystoneConfig = (useMigrations: boolean, lists: ListSchemaConfig = basicLists) => ({ - kind: 'config' as const, - config: config({ - db: { provider: 'sqlite', url: dbUrl, useMigrations }, - ui: { isDisabled: true }, - lists, - }), -}); - async function setupAndStopDevServerForMigrations(cwd: string, resetDb: boolean = false) { let stopServer = (await runCommand( cwd, @@ -81,10 +59,12 @@ function cleanOutputForApplyingMigration(output: string, generatedMigrationName: .replace('✅ The migration has been applied\n', ''); } +const basicKeystoneConfig = fs.readFileSync(`${__dirname}/fixtures/basic-with-no-ui.ts`, 'utf8'); + async function setupInitialProjectWithoutMigrations() { const tmp = await testdir({ ...symlinkKeystoneDeps, - 'keystone.js': basicKeystoneConfig(false), + 'keystone.js': basicKeystoneConfig, }); const recording = recordConsole(); await setupAndStopDevServerForMigrations(tmp); @@ -141,12 +121,7 @@ describe('useMigrations: false', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...(await getDatabaseFiles(prevCwd)), - 'keystone.js': basicKeystoneConfig(false, { - Todo: { - access: allowAll, - fields: {}, - }, - }), + 'keystone.js': await fs.readFile(`${__dirname}/fixtures/no-fields.ts`, 'utf8'), }); const recording = recordConsole({ 'Do you want to continue? Some data will be lost.': true, @@ -188,12 +163,7 @@ describe('useMigrations: false', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...(await getDatabaseFiles(prevCwd)), - 'keystone.js': basicKeystoneConfig(false, { - Todo: { - access: allowAll, - fields: {}, - }, - }), + 'keystone.js': await fs.readFile(`${__dirname}/fixtures/no-fields.ts`, 'utf8'), }); const recording = recordConsole({ 'Do you want to continue? Some data will be lost.': false, @@ -254,10 +224,15 @@ describe('useMigrations: false', () => { }); }); +const basicWithMigrations = fs.readFileSync( + `${__dirname}/fixtures/one-field-with-migrations.ts`, + 'utf8' +); + async function setupInitialProjectWithMigrations() { const tmp = await testdir({ ...symlinkKeystoneDeps, - 'keystone.js': basicKeystoneConfig(true), + 'keystone.js': basicWithMigrations, }); const recording = recordConsole({ 'Name of migration': 'init', @@ -314,15 +289,10 @@ describe('useMigrations: true', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...(await getDatabaseFiles(prevCwd)), - 'keystone.js': basicKeystoneConfig(true, { - Todo: { - access: allowAll, - fields: { - title: text(), - isComplete: checkbox(), - }, - }, - }), + 'keystone.js': await fs.readFile( + `${__dirname}/fixtures/two-fields-with-migrations.ts`, + 'utf8' + ), }); const recording = recordConsole({ 'Name of migration': 'add-is-complete', @@ -386,12 +356,10 @@ describe('useMigrations: true', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...(await getDatabaseFiles(prevCwd)), - 'keystone.js': basicKeystoneConfig(true, { - Todo: { - access: allowAll, - fields: {}, - }, - }), + 'keystone.js': await fs.readFile( + `${__dirname}/fixtures/no-fields-with-migrations.ts`, + 'utf8' + ), }); const recording = recordConsole({ 'Name of migration': 'remove all fields except id', @@ -463,7 +431,7 @@ describe('useMigrations: true', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, 'app.db': await fs.readFile(`${prevCwd}/app.db`), - 'keystone.js': basicKeystoneConfig(true), + 'keystone.js': await fs.readFile(`${__dirname}/fixtures/one-field-with-migrations.ts`), }); const recording = recordConsole({ 'Do you want to continue? All data will be lost.': true, @@ -539,7 +507,7 @@ describe('useMigrations: true', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, 'app.db': dbBuffer, - 'keystone.js': basicKeystoneConfig(true), + 'keystone.js': await fs.readFile(`${__dirname}/fixtures/no-fields-with-migrations.ts`), }); const recording = recordConsole({ 'Do you want to continue? All data will be lost.': false, @@ -578,15 +546,7 @@ describe('useMigrations: true', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...dbFiles, - 'keystone.js': basicKeystoneConfig(true, { - Todo: { - access: allowAll, - fields: { - title: text(), - isComplete: checkbox(), - }, - }, - }), + 'keystone.js': await fs.readFile(`${__dirname}/fixtures/two-fields-with-migrations.ts`), }); const recording = recordConsole({ 'Name of migration': 'add-is-complete', @@ -648,7 +608,7 @@ describe('useMigrations: true', () => { const tmp = await testdir({ ...symlinkKeystoneDeps, ...migrations, - 'keystone.js': basicKeystoneConfig(true), + 'keystone.js': basicWithMigrations, }); const recording = recordConsole(); await setupAndStopDevServerForMigrations(tmp); diff --git a/packages/core/src/scripts/tests/utils.tsx b/packages/core/src/scripts/tests/utils.tsx index a62101d7ddf..65bf5cd3662 100644 --- a/packages/core/src/scripts/tests/utils.tsx +++ b/packages/core/src/scripts/tests/utils.tsx @@ -6,7 +6,6 @@ import * as fs from 'fs-extra'; import fastGlob from 'fast-glob'; // @ts-ignore import fixturez from 'fixturez'; -import outdent from 'outdent'; import { parseArgsStringToArgv } from 'string-argv'; import { IntrospectionEngine, uriToCredentials } from '@prisma/internals'; import { KeystoneConfig } from '../../types'; @@ -15,26 +14,10 @@ import { mockPrompts } from '../../lib/prompts'; export const cliBinPath = require.resolve('@keystone-6/core/bin/cli.js'); -export const js = outdent; -export const ts = outdent; -export const tsx = outdent; -export const graphql = outdent; - -export const basicKeystoneConfig = js` - import { config, list } from "@keystone-6/core"; - import { text } from "@keystone-6/core/fields"; - - export default config({ - db: { provider: "sqlite", url: "file:./app.db" }, - lists: { - Todo: list({ - fields: { - title: text(), - }, - }), - }, - }); - `; +export const basicKeystoneConfig = fs.readFileSync( + `${__dirname}/fixtures/basic-project/keystone.ts`, + 'utf8' +); export const schemas = { 'schema.graphql': fs.readFileSync(`${__dirname}/fixtures/basic-project/schema.graphql`, 'utf8'), diff --git a/scripts/generate-artifacts-for-projects/src/index.ts b/scripts/generate-artifacts-for-projects/src/index.ts index 082fc2c1d92..0090074946c 100644 --- a/scripts/generate-artifacts-for-projects/src/index.ts +++ b/scripts/generate-artifacts-for-projects/src/index.ts @@ -73,7 +73,10 @@ async function main() { ) ) .flat() - .concat(path.join(repoRoot, 'tests/sandbox')); + .concat( + path.join(repoRoot, 'tests/sandbox'), + path.join(repoRoot, 'packages/core/src/scripts/tests/fixtures/basic-project') + ); // this breaks if we do this entirely in parallel (it only seemed to consistently fail on Vercel though) // because of Prisma's loading native libraries and child processes stuff and it seems racey diff --git a/yarn.lock b/yarn.lock index fdeb52e4705..380c2dea991 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3490,13 +3490,6 @@ terser "^5.2.1" v8-compile-cache "^2.1.1" -"@preconstruct/eslint-plugin-format-js-tag@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@preconstruct/eslint-plugin-format-js-tag/-/eslint-plugin-format-js-tag-0.2.0.tgz#6727039301087469fc74fa5ddcdc56fc0187e234" - integrity sha512-/yXgIoBp8h0MOYOeI55pT5uLCalOLRyM+FSY55HqnbH1ALkr8ttmf7hu/EI3Z6KY8zRso3sXaf6OeFM4tuHIDg== - dependencies: - "@typescript-eslint/experimental-utils" "^5.4.0" - "@preconstruct/hook@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@preconstruct/hook/-/hook-0.4.0.tgz#c15dfacfc6e60652a6837209c2fd87f0240b099e" @@ -4775,13 +4768,6 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/experimental-utils@^5.4.0": - version "5.33.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.33.1.tgz#5fa908addffb82ea8fb0e62cb47c387de1bff536" - integrity sha512-wk2o+4wojvKz/x3UCbsgjgXl0lyLPYQsfKP0MdRzj4jtsQr4bVtgWUWck6+N3GzThUTbUFyyKLduWPwePhh0xQ== - dependencies: - "@typescript-eslint/utils" "5.33.1" - "@typescript-eslint/parser@^5.7.0": version "5.33.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.33.1.tgz#e4b253105b4d2a4362cfaa4e184e2d226c440ff3" @@ -11491,11 +11477,6 @@ outdent@^0.5.0: resolved "https://registry.yarnpkg.com/outdent/-/outdent-0.5.0.tgz#9e10982fdc41492bb473ad13840d22f9655be2ff" integrity sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q== -outdent@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/outdent/-/outdent-0.8.0.tgz#2ebc3e77bf49912543f1008100ff8e7f44428eb0" - integrity sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A== - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" From 8822d6b54765ba886693e48c74974ca469f906d1 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 9 Sep 2022 13:35:46 +1000 Subject: [PATCH 4/7] Changeset --- .changeset/giant-avocados-swim.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/giant-avocados-swim.md diff --git a/.changeset/giant-avocados-swim.md b/.changeset/giant-avocados-swim.md new file mode 100644 index 00000000000..db4a25658f8 --- /dev/null +++ b/.changeset/giant-avocados-swim.md @@ -0,0 +1,5 @@ +--- +'@keystone-6/core': patch +--- + +Replaced Next.js for server-side build with [esbuild](https://esbuild.github.io/) From 681a1457254159eb2de5a2faf4b48ced739970e8 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 9 Sep 2022 13:41:01 +1000 Subject: [PATCH 5/7] Remove @types/babel__core --- package.json | 1 - yarn.lock | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 74485bc9f62..dc7634a03de 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "@manypkg/cli": "^0.19.1", "@preconstruct/cli": "2.2.1", "@testing-library/jest-dom": "^5.15.0", - "@types/babel__core": "^7.1.16", "@types/jest": "^29.0.0", "@types/node-fetch": "^2.5.12", "@typescript-eslint/eslint-plugin": "^5.7.0", diff --git a/yarn.lock b/yarn.lock index 380c2dea991..2ba20aca8f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4181,7 +4181,7 @@ resolved "https://registry.yarnpkg.com/@types/babel__code-frame/-/babel__code-frame-7.0.3.tgz#eda94e1b7c9326700a4b69c485ebbc9498a0b63f" integrity sha512-2TN6oiwtNjOezilFVl77zwdNPwQWaDBBCCWWxyo1ctiO3vAtd7H/aB/CBJdw9+kqq3+latD0SXoedIuHySSZWw== -"@types/babel__core@^7.1.14", "@types/babel__core@^7.1.16": +"@types/babel__core@^7.1.14": version "7.1.19" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== From 787d448853e59c3c6b30df455ad1a1d709b0f488 Mon Sep 17 00:00:00 2001 From: mitchellhamilton Date: Fri, 9 Sep 2022 15:40:23 +1000 Subject: [PATCH 6/7] Fix tests and make live reloading work when `ui.isDisabled: true` --- packages/core/src/scripts/run/dev.ts | 37 +++++++++---------- .../core/src/scripts/tests/migrations.test.ts | 5 +++ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/core/src/scripts/run/dev.ts b/packages/core/src/scripts/run/dev.ts index db5e6dbbe9a..28d35409af3 100644 --- a/packages/core/src/scripts/run/dev.ts +++ b/packages/core/src/scripts/run/dev.ts @@ -56,6 +56,12 @@ function isBuildFailure(err: unknown): err is BuildFailure { return err instanceof Error && Array.isArray((err as any).errors); } +let shouldWatch = true; + +export function setSkipWatching() { + shouldWatch = false; +} + export const dev = async (cwd: string, shouldDropDatabase: boolean) => { console.log('✨ Starting Keystone'); @@ -81,13 +87,15 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { const initialBuildResult = await esbuild .build({ ...getEsbuildConfig(cwd), - watch: { - onRebuild(error, result) { - let prev = lastPromise; - lastPromise = resolvablePromise(); - prev.resolve({ value: { error, result }, done: false }); - }, - }, + watch: shouldWatch + ? { + onRebuild(error, result) { + let prev = lastPromise; + lastPromise = resolvablePromise(); + prev.resolve({ value: { error, result }, done: false }); + }, + } + : undefined, }) .catch(async err => { if (isBuildFailure(err)) { @@ -99,8 +107,7 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { const configWithHTTP = loadBuiltConfig(cwd); const config = cleanConfig(configWithHTTP); - const isReady = () => - expressServer !== null && (hasAddedAdminUIMiddleware || config.ui?.isDisabled === true); + const isReady = () => expressServer !== null && hasAddedAdminUIMiddleware; const initKeystone = async () => { await fs.remove(getAdminPath(cwd)); @@ -127,13 +134,6 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { const prismaClient = createContext().prisma; ({ disconnect, expressServer } = rest); - // if you've disabled the Admin UI, sorry, no live reloading - // the chance that someone is actually using this is probably quite low - // and starting Next in tests where we don't care about it would slow things down quite a bit - if (config.ui?.isDisabled) { - initKeystonePromiseResolve(); - return; - } const adminUIMiddleware = await initAdminUI( config, graphQLSchema, @@ -244,7 +244,7 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { // Pass the request the express server, or serve the loading page app.use((req, res, next) => { // If both the express server and Admin UI Middleware are ready, we're go! - if (expressServer && (hasAddedAdminUIMiddleware || config.ui?.isDisabled === true)) { + if (expressServer && hasAddedAdminUIMiddleware) { return expressServer(req, res, next); } // Otherwise, we may be able to serve the GraphQL API @@ -308,13 +308,12 @@ export const dev = async (cwd: string, shouldDropDatabase: boolean) => { }); }); }); - await initKeystonePromise; return () => new Promise((resolve, reject) => { server.close(async err => { - initialBuildResult.stop!(); + initialBuildResult.stop?.(); try { await disconnect?.(); } catch (disconnectionError: any) { diff --git a/packages/core/src/scripts/tests/migrations.test.ts b/packages/core/src/scripts/tests/migrations.test.ts index f54d1eeacd1..8daabe8953a 100644 --- a/packages/core/src/scripts/tests/migrations.test.ts +++ b/packages/core/src/scripts/tests/migrations.test.ts @@ -1,5 +1,6 @@ import fs from 'fs-extra'; import { requirePrismaClient } from '../../artifacts'; +import { setSkipWatching } from '../run/dev'; import { ExitError } from '../utils'; import { getFiles, @@ -10,6 +11,10 @@ import { testdir, } from './utils'; +// watching with esbuild inside of jest goes _weird_ +// esbuild doesn't seem to let you wait for the cleanup +setSkipWatching(); + const dbUrl = 'file:./app.db'; async function setupAndStopDevServerForMigrations(cwd: string, resetDb: boolean = false) { From 8cdf849ed0379bcd0785b6d83eef99e244d64656 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Fri, 9 Sep 2022 21:37:58 +1000 Subject: [PATCH 7/7] update changeset --- .changeset/giant-avocados-swim.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/giant-avocados-swim.md b/.changeset/giant-avocados-swim.md index db4a25658f8..f0645aad219 100644 --- a/.changeset/giant-avocados-swim.md +++ b/.changeset/giant-avocados-swim.md @@ -2,4 +2,4 @@ '@keystone-6/core': patch --- -Replaced Next.js for server-side build with [esbuild](https://esbuild.github.io/) +Changed platform compilation to use [esbuild](https://esbuild.github.io/), previously used next.js