1
0
mirror of synced 2025-12-19 18:11:23 -05:00

Next.js Fork Migration: Move blitz.config.(js|ts) support into nextjs core (patch) (#2532)

This commit is contained in:
Brandon Bayer
2021-06-24 21:06:55 -06:00
committed by GitHub
parent b2f84f1224
commit 24e51c7ae5
60 changed files with 447 additions and 904 deletions

1
.gitignore vendored
View File

@@ -30,3 +30,4 @@ examples/auth2
db.sqlite-journal
test/integration/**/db.json
test/**/*/out
.blitz**

View File

@@ -12,16 +12,8 @@ import {
} from "blitz"
import getUser from "app/users/queries/getUser"
import logout from "app/auth/mutations/logout"
import path from "path"
export const getServerSideProps: GetServerSideProps = async ({req, res}) => {
// Ensure these files are not eliminated by trace-based tree-shaking (like Vercel)
// https://github.com/blitz-js/blitz/issues/794
path.resolve("next.config.js")
path.resolve("blitz.config.js")
path.resolve(".next/blitz/db.js")
// End anti-tree-shaking
const session = await getSession(req, res)
console.log("Session id:", session.userId)
try {

View File

@@ -35,30 +35,39 @@ export default function NoAnonymousDefaultExport({
switch (def.type) {
case 'ArrowFunctionExpression': {
warn(
[
chalk.yellow.bold(
'Anonymous arrow functions cause Fast Refresh to not preserve local component state.'
),
'Please add a name to your function, for example:',
'',
chalk.bold('Before'),
chalk.cyan('export default () => <div />;'),
'',
chalk.bold('After'),
chalk.cyan('const Named = () => <div />;'),
chalk.cyan('export default Named;'),
'',
`A codemod is available to fix the most common cases: ${chalk.cyan(
'https://nextjs.link/codemod-ndc'
)}`,
].join('\n')
)
if (
!process.env.__NEXT_TEST_MODE ||
!!process.env.__NEXT_TEST_ANON_EXPORT
) {
warn(
[
chalk.yellow.bold(
'Anonymous arrow functions cause Fast Refresh to not preserve local component state.'
),
'Please add a name to your function, for example:',
'',
chalk.bold('Before'),
chalk.cyan('export default () => <div />;'),
'',
chalk.bold('After'),
chalk.cyan('const Named = () => <div />;'),
chalk.cyan('export default Named;'),
'',
`A codemod is available to fix the most common cases: ${chalk.cyan(
'https://nextjs.link/codemod-ndc'
)}`,
].join('\n')
)
}
break
}
case 'FunctionDeclaration': {
const isAnonymous = !Boolean(def.id)
if (isAnonymous) {
if (
isAnonymous &&
(!process.env.__NEXT_TEST_MODE ||
!!process.env.__NEXT_TEST_ANON_EXPORT)
) {
warn(
[
chalk.yellow.bold(

View File

@@ -124,7 +124,7 @@ export default async function build(
const nextBuildSpan = trace('next-build')
return nextBuildSpan.traceAsyncFn(async () => {
// attempt to load global env values so they are available in next.config.js
// attempt to load global env values so they are available in blitz.config.js
const { loadedEnvFiles } = nextBuildSpan
.traceChild('load-dotenv')
.traceFn(() => loadEnvConfig(dir, false, Log))

View File

@@ -52,20 +52,7 @@ import WebpackConformancePlugin, {
} from './webpack/plugins/webpack-conformance-plugin'
import { WellKnownErrorsPlugin } from './webpack/plugins/wellknown-errors-plugin'
import { regexLikeCss } from './webpack/config/blocks/css'
import fs from 'fs'
import { getProjectRoot } from '../server/lib/utils'
/* ------ Blitz.js ------- */
function doesDbModuleExist() {
const projectRoot = getProjectRoot()
return (
fs.existsSync(path.join(projectRoot, 'db/index.js')) ||
fs.existsSync(path.join(projectRoot, 'db/index.ts')) ||
fs.existsSync(path.join(projectRoot, 'db/index.tsx'))
)
}
/* ------ Blitz.js ------- */
import { existsSync } from 'fs'
type ExcludesFalse = <T>(x: T | false) => x is T
@@ -279,6 +266,12 @@ export default async function getBaseWebpackConfig(
const distDir = path.join(dir, config.distDir)
/* ------ Blitz.js ------- */
const hasDbModule =
existsSync(path.join(dir, 'db/index.js')) ||
existsSync(path.join(dir, 'db/index.ts'))
/* ------ Blitz.js ------- */
// Webpack 5 can use the faster babel loader, webpack 5 has built-in caching for loaders
// For webpack 4 the old loader is used as it has external caching
const babelLoader = isWebpack5
@@ -913,9 +906,7 @@ export default async function getBaseWebpackConfig(
entry: async () => {
return {
...(clientEntries ? clientEntries : {}),
...(isServer && doesDbModuleExist()
? { 'blitz-db': './db/index' }
: {}),
...(isServer && hasDbModule ? { 'blitz-db': './db/index' } : {}),
...entrypoints,
}
},
@@ -1373,12 +1364,13 @@ export default async function getBaseWebpackConfig(
type: 'filesystem',
// Includes:
// - Next.js version
// - next.config.js keys that affect compilation
// - blitz.config.js keys that affect compilation
version: `${process.env.__NEXT_VERSION}|${configVars}`,
cacheDirectory: path.join(distDir, 'cache', 'webpack'),
}
// Adds `next.config.js` as a buildDependency when custom webpack config is provided
// TODO - can we remove this?
// Adds `blitz.config.js` as a buildDependency when custom webpack config is provided
if (config.webpack && config.configFile) {
cache.buildDependencies = {
config: [config.configFile],

View File

@@ -7,7 +7,7 @@ export function removePathTrailingSlash(path: string): string {
/**
* Normalizes the trailing slash of a path according to the `trailingSlash` option
* in `next.config.js`.
* in `blitz.config.js`.
*/
export const normalizePathTrailingSlash = process.env.__NEXT_TRAILING_SLASH
? (path: string): string => {

View File

@@ -20,7 +20,6 @@ import {
BUILD_ID_FILE,
CLIENT_PUBLIC_FILES_PATH,
CLIENT_STATIC_FILES_PATH,
CONFIG_FILE,
EXPORT_DETAIL,
EXPORT_MARKER,
PAGES_MANIFEST,
@@ -143,7 +142,7 @@ export default async function exportApp(
return nextExportSpan.traceAsyncFn(async () => {
dir = resolve(dir)
// attempt to load global env values so they are available in next.config.js
// attempt to load global env values so they are available in blitz.config.js
nextExportSpan
.traceChild('load-dotenv')
.traceFn(() => loadEnvConfig(dir, false, Log))
@@ -308,7 +307,7 @@ export default async function exportApp(
if (typeof nextConfig.exportPathMap !== 'function') {
if (!options.silent) {
Log.info(
`No "exportPathMap" found in "${CONFIG_FILE}". Generating map from "./pages"`
`No "exportPathMap" found in "blitz.config.js". Generating map from "./pages"`
)
}
nextConfig.exportPathMap = async (defaultMap: ExportPathMap) => {

View File

@@ -15,7 +15,7 @@ export const REACT_LOADABLE_MANIFEST = 'react-loadable-manifest.json'
export const FONT_MANIFEST = 'font-manifest.json'
export const SERVER_DIRECTORY = 'server'
export const SERVERLESS_DIRECTORY = 'serverless'
export const CONFIG_FILE = 'next.config.js'
export const CONFIG_FILE = '.blitz.config.compiled.js'
export const BUILD_ID_FILE = 'BUILD_ID'
export const BLOCKED_PAGES = ['/_document', '/_app']
export const CLIENT_PUBLIC_FILES_PATH = 'public'

View File

@@ -1,6 +1,13 @@
import { existsSync, readFileSync } from 'fs'
import { build as esbuild } from 'esbuild'
import findUp from 'next/dist/compiled/find-up'
import os from 'os'
import { join } from 'path'
import { Header, Redirect, Rewrite } from '../../lib/load-custom-routes'
import { imageConfigDefault } from './image-config'
import { CONFIG_FILE } from '../lib/constants'
import { copy, remove } from 'fs-extra'
const debug = require('debug')('blitz:config')
export type DomainLocales = Array<{
http?: true
@@ -143,3 +150,116 @@ export function normalizeConfig(phase: string, config: any) {
}
return config
}
export async function getConfigSrcPath(dir: string | null) {
if (!dir) return null
let tsPath = join(dir, 'blitz.config.ts')
let jsPath = join(dir, 'blitz.config.js')
let legacyPath = join(dir, 'next.config.js')
if (existsSync(tsPath)) {
return tsPath
} else if (existsSync(jsPath)) {
return jsPath
} else if (existsSync(legacyPath)) {
const isInternalDevelopment = __dirname.includes(
'packages/next/dist/next-server'
)
if (isInternalDevelopment || process.env.VERCEL_BUILDER) {
// We read from next.config.js that Vercel automatically adds
debug(
'Using next.config.js because isInternalDevelopment or VERCEL_BUILDER...'
)
return legacyPath
} else {
console.log('') // newline
throw new Error(
'Blitz does not support next.config.js. Please rename it to blitz.config.js'
)
}
}
if (process.env.__NEXT_TEST_MODE) {
let tsPath2 = join(dir, '..', 'blitz.config.ts')
let jsPath2 = join(dir, '..', 'blitz.config.js')
let legacyPath2 = join(dir, '..', 'next.config.js')
if (existsSync(tsPath2)) {
return tsPath2
} else if (existsSync(jsPath2)) {
return jsPath2
} else if (existsSync(legacyPath2)) {
return legacyPath2
}
}
return null
}
export function getCompiledConfigPath(dir: string) {
return join(dir, CONFIG_FILE)
}
export async function compileConfig(dir: string | null) {
debug('Starting compileConfig...')
if (!dir) {
debug('compileConfig given empty dir argument')
return
}
const srcPath = await getConfigSrcPath(dir)
debug('srcPath:', srcPath)
const compiledPath = getCompiledConfigPath(dir)
debug('compiledPath:', compiledPath)
// Remove compiled file. This is important for example when user
// had a config file but then removed it
remove(compiledPath)
if (!srcPath) {
debug('Did not find a config file')
return
}
if (readFileSync(srcPath, 'utf8').includes('tsconfig-paths/register')) {
// User is manually handling their own typescript stuff
debug(
"Config contains 'tsconfig-paths/register', so skipping build and just copying the file"
)
await copy(srcPath, compiledPath)
return
}
const pkgJsonPath = await findUp('package.json', { cwd: dir })
if (!pkgJsonPath) {
// This will happen when running blitz no inside a blitz app
debug('Unable to find package directory')
return
}
debug('Building config...')
const pkg = require(pkgJsonPath)
await esbuild({
entryPoints: [srcPath],
outfile: compiledPath,
format: 'cjs',
bundle: true,
platform: 'node',
external: [
'*.json',
'@blitzjs',
'@next',
'@zeit',
'blitz',
'next',
'webpack',
...Object.keys(require('blitz/package').dependencies),
...Object.keys(pkg?.dependencies ?? {}),
...Object.keys(pkg?.devDependencies ?? {}),
],
})
debug('Config built.')
}

View File

@@ -48,7 +48,7 @@ export async function shouldLoadWithWebpack5(
}
}
// Use webpack 5 by default in apps that do not have next.config.js
// Use webpack 5 by default in apps that do not have blitz.config.js
if (!path?.length) {
return {
enabled: true,

View File

@@ -1,14 +1,15 @@
import chalk from 'chalk'
import findUp from 'next/dist/compiled/find-up'
import { basename, extname } from 'path'
import { basename, extname, join } from 'path'
import * as Log from '../../build/output/log'
import { hasNextSupport } from '../../telemetry/ci-info'
import { CONFIG_FILE, PHASE_DEVELOPMENT_SERVER } from '../lib/constants'
import { execOnce } from '../lib/utils'
import { defaultConfig, normalizeConfig } from './config-shared'
import { compileConfig, defaultConfig, normalizeConfig } from './config-shared'
import { loadWebpackHook } from './config-utils'
import { ImageConfig, imageConfigDefault, VALID_LOADERS } from './image-config'
import { loadEnvConfig } from '@next/env'
const debug = require('debug')('blitz:config')
export { DomainLocales, NextConfig, normalizeConfig } from './config-shared'
@@ -412,6 +413,10 @@ export default async function loadConfig(
customConfig?: object | null
) {
await loadEnvConfig(dir, phase === PHASE_DEVELOPMENT_SERVER, Log)
if (!['start', 's'].includes(process.argv[2])) {
// Do not compile config for blitz start because it was already compiled during blitz build
await compileConfig(dir)
}
await loadWebpackHook(phase, dir)
if (customConfig) {
@@ -423,11 +428,20 @@ export default async function loadConfig(
// If config file was found
if (path?.length) {
const userConfigModule = require(path)
const userConfig = normalizeConfig(
let userConfig = normalizeConfig(
phase,
userConfigModule.default || userConfigModule
)
if (process.env.VERCEL_BUILDER) {
debug("Loading Vercel's next.config.js...")
const nextConfig = require(join('dir', 'next.config.js'))
debug("Vercel's next.config.js contents:", nextConfig)
for (const [key, value] of Object.entries(nextConfig)) {
userConfig[key] = value
}
}
if (Object.keys(userConfig).length === 0) {
Log.warn(
'Detected blitz.config.js, no exported configuration found. https://nextjs.org/docs/messages/empty-configuration'
@@ -461,21 +475,23 @@ export default async function loadConfig(
...userConfig,
})
} else {
const configBaseName = basename(CONFIG_FILE, extname(CONFIG_FILE))
const nonJsPath = findUp.sync(
const unsupportedPath = findUp.sync(
[
`${configBaseName}.jsx`,
`${configBaseName}.ts`,
`${configBaseName}.tsx`,
`${configBaseName}.json`,
`blitz.config.jsx`,
`blitz.config.tsx`,
`blitz.config.json`,
`next.config.jsx`,
`next.config.ts`,
`next.config.tsx`,
`next.config.json`,
],
{ cwd: dir }
)
if (nonJsPath?.length) {
if (unsupportedPath?.length) {
throw new Error(
`Configuring Blitz.js via '${basename(
nonJsPath
)}' is not supported. Please replace the file with 'blitz.config.js'.`
unsupportedPath
)}' is not supported. Please replace the file with 'blitz.config.(js|ts)'`
)
}
}

View File

@@ -122,7 +122,7 @@ export type ServerConstructor = {
*/
quiet?: boolean
/**
* Object what you would use in next.config.js - @default {}
* Object what you would use in blitz.config.js - @default {}
*/
conf?: NextConfig | null
dev?: boolean
@@ -606,7 +606,7 @@ export default class Server {
let rewrites: CustomRoutes['rewrites']
// rewrites can be stored as an array when an array is
// returned in next.config.js so massage them into
// returned in blitz.config.js so massage them into
// the expected object format
if (Array.isArray(customRoutes.rewrites)) {
rewrites = {

View File

@@ -83,9 +83,12 @@
"constants-browserify": "1.0.0",
"crypto-browserify": "3.12.0",
"cssnano-simple": "2.0.0",
"debug": "4.3.1",
"domain-browser": "4.19.0",
"encoding": "0.1.13",
"esbuild": "^0.11.12",
"etag": "1.8.1",
"fs-extra": "^9.1.0",
"get-orientation": "1.1.2",
"https-browserify": "1.0.0",
"image-size": "1.0.0",
@@ -203,7 +206,6 @@
"content-type": "1.0.4",
"cookie": "0.4.1",
"css-loader": "4.3.0",
"debug": "4.3.1",
"devalue": "2.0.1",
"escape-string-regexp": "2.0.0",
"file-loader": "6.0.0",

View File

@@ -1,5 +1,5 @@
import fs from 'fs'
import path from 'path'
import findUp from 'next/dist/compiled/find-up'
import { dirname } from 'path'
export function printAndExit(message: string, code = 1) {
if (code === 0) {
@@ -16,16 +16,14 @@ export function getNodeOptionsWithoutInspect() {
return (process.env.NODE_OPTIONS || '').replace(NODE_INSPECT_RE, '')
}
export function getProjectRoot() {
return path.dirname(getConfigSrcPath())
}
export async function getProjectRoot(dir: string) {
const pkgJsonPath = await findUp('package.json', { cwd: dir })
export function getConfigSrcPath() {
const tsPath = path.resolve(path.join(process.cwd(), 'blitz.config.ts'))
if (fs.existsSync(tsPath)) {
return tsPath
} else {
const jsPath = path.resolve(path.join(process.cwd(), 'blitz.config.js'))
return jsPath
if (!pkgJsonPath) {
throw new Error(
'Unable to find project root by looking for your package.json'
)
}
return dirname(pkgJsonPath)
}

View File

@@ -74,6 +74,7 @@ describe('Client Navigation accessibility', () => {
const title = await browser.eval('document.title')
await waitFor(500)
const routeAnnouncerValue = await browser
.waitForElementByCss('#__next-route-announcer__')
.text()
@@ -92,6 +93,7 @@ describe('Client Navigation accessibility', () => {
const pathname = '/page-without-h1-or-title'
await waitFor(500)
const routeAnnouncerValue = await browser
.waitForElementByCss('#__next-route-announcer__')
.text()

View File

@@ -1,6 +0,0 @@
module.exports = {
onDemandEntries: {
// Make sure entries are not getting disposed.
maxInactiveAge: 1000 * 60 * 60,
},
}

View File

@@ -1,3 +0,0 @@
export default function Abc() {
return <div />
}

View File

@@ -1,17 +0,0 @@
/* eslint-env jest */
import { nextBuild } from 'next-test-utils'
import { join } from 'path'
jest.setTimeout(1000 * 60 * 2)
const appDir = join(__dirname, '..')
describe('Empty JSConfig Support', () => {
test('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
})

View File

@@ -14,7 +14,6 @@ let app
let appPort
const appDir = join(__dirname, '..')
const nextConfig = join(appDir, 'next.config.js')
let mode
const specPage = join(appDir, 'app/pages/home.spec.js')
const testPage = join(appDir, 'app/pages/home.test.js')
@@ -32,7 +31,7 @@ afterAll(async () => {
await fs.remove(testApi)
})
const runTests = (dev = false) => {
const runTests = (mode) => {
it('should load the pages', async () => {
const browser = await webdriver(appPort, '/')
let text = await browser.elementByCss('#page-container').text()
@@ -55,7 +54,7 @@ const runTests = (dev = false) => {
expect(html).toContain('This page could not be found')
})
if (!dev) {
if (mode !== 'dev') {
it('should build routes', async () => {
const pagesManifest = JSON.parse(
await fs.readFile(
@@ -87,25 +86,23 @@ describe('dev mode', () => {
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))
runTests(true)
runTests('dev')
})
describe('production mode', () => {
beforeAll(async () => {
await nextBuild(appDir)
mode = 'server'
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
runTests()
runTests('server')
})
describe('Serverless support', () => {
describe('serverless mode', () => {
beforeAll(async () => {
await fs.writeFile(nextConfig, `module.exports = { target: 'serverless' }`)
await nextBuild(appDir)
mode = 'serverless'
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
@@ -114,5 +111,5 @@ describe('Serverless support', () => {
await fs.remove(nextConfig)
})
runTests()
runTests('serverless')
})

View File

@@ -17,13 +17,16 @@ describe('no anonymous default export warning', () => {
beforeEach(async () => {
await fs.remove(join(appDir, '.next'))
})
afterEach(async () => {
await fs.remove(join(appDir, '.next'))
})
it('show correct warnings for page', async () => {
let stderr = ''
const appPort = await findPort()
const app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: true },
env: { __NEXT_TEST_WITH_DEVTOOL: true, __NEXT_TEST_ANON_EXPORT: true },
onStderr(msg) {
stderr += msg || ''
},
@@ -50,7 +53,7 @@ describe('no anonymous default export warning', () => {
const appPort = await findPort()
const app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: true },
env: { __NEXT_TEST_WITH_DEVTOOL: true, __NEXT_TEST_ANON_EXPORT: true },
onStderr(msg) {
stderr += msg || ''
},
@@ -77,7 +80,7 @@ describe('no anonymous default export warning', () => {
const appPort = await findPort()
const app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: true },
env: { __NEXT_TEST_WITH_DEVTOOL: true, __NEXT_TEST_ANON_EXPORT: true },
onStderr(msg) {
stderr += msg || ''
},

View File

@@ -89,12 +89,10 @@ describe('config', () => {
PHASE_DEVELOPMENT_SERVER,
join(__dirname, '_resolvedata', 'typescript-config')
)
).rejects.toThrow(
/Configuring Blitz.js via .+ is not supported. Please replace the file with 'blitz.config.js'/
)
).rejects.toThrow(/Configuring Blitz.js via .* is not supported/)
})
it('Should not throw an error when two versions of next.config.js are present', async () => {
it('Should not throw an error when two versions of blitz.config.js are present', async () => {
const config = await loadConfig(
PHASE_DEVELOPMENT_SERVER,
join(__dirname, '_resolvedata', 'js-ts-config')

View File

@@ -1,4 +1,5 @@
/* eslint-env jest */
import { Console } from 'console'
process.env.BLITZ_TEST_ENVIRONMENT = true
@@ -7,3 +8,6 @@ if (process.env.JEST_RETRY_TIMES) {
console.log(`Configuring jest retries: ${retries}`)
jest.retryTimes(retries)
}
// Reset to default console instead of the verbose Jest one
global.console = new Console({ stdout: process.stdout, stderr: process.stderr })

View File

@@ -118,15 +118,13 @@ export function runNextCommand(argv, options = {}) {
}
let stderrOutput = ''
if (options.stderr) {
instance.stderr.on('data', function (chunk) {
stderrOutput += chunk
instance.stderr.on('data', function (chunk) {
stderrOutput += chunk
if (options.stderr === 'log') {
console.log(chunk.toString())
}
})
}
if (options.stderr === 'log') {
console.log(chunk.toString())
}
})
let stdoutOutput = ''
if (options.stdout) {
@@ -146,6 +144,7 @@ export function runNextCommand(argv, options = {}) {
!options.ignoreFail &&
code !== 0
) {
console.log(stderrOutput)
return reject(new Error(`command failed with code ${code}`))
}

View File

@@ -174,10 +174,10 @@ export default async function webdriver(
const url = `http://${deviceIP}:${appPort}${path}`
browser.initUrl = url
console.log(`\n> Loading browser with ${url}\n`)
console.log(`> Loading browser with ${url}`)
await browser.get(url)
console.log(`\n> Loaded browser with ${url}\n`)
console.log(`> Loaded browser with ${url}`)
// Wait for application to hydrate
if (waitHydration) {

View File

@@ -31,7 +31,7 @@
"dev:nextjs-types": "yarn wait:nextjs && yarn workspace next types && echo 'Finished building nextjs types'",
"dev:blitz": "cross-env BLITZ_PROD_BUILD=true preconstruct watch",
"dev:tsc": "yarn dev:nextjs-types && tsc --watch --pretty --preserveWatchOutput",
"dev:cli": "yarn wait:nextjs && yarn workspace @blitzjs/cli dev",
"dev:cli": "yarn wait:nextjs-types && yarn workspace @blitzjs/cli dev",
"dev:templates": "yarn workspace @blitzjs/generator dev",
"dev": "concurrently --names \"nextjs,blitz,typecheck,cli,templates\" -c \"magenta,cyan,green,yellow,black\" -p \"{name}\" \"npm:dev:nextjs\" \"npm:dev:blitz\" \"npm:dev:tsc\" \"npm:dev:cli\" \"npm:dev:templates\"",
"build:nextjs": "yarn workspace next prepublish",

View File

@@ -85,17 +85,21 @@ const pagesToSkip = ([] as string[]).concat(
);
function isPage(filePath: string) {
if (!filePath.includes('pages' + nodePath.sep)) {
if (!filePath.includes(nodePath.sep + 'pages' + nodePath.sep)) {
return false;
}
if (filePath.includes('pages' + nodePath.sep + 'api')) {
if (
filePath.includes(
nodePath.sep + 'pages' + nodePath.sep + 'api' + nodePath.sep
)
) {
return false;
}
return !pagesToSkip.some((fileToSkip) => filePath.includes(fileToSkip));
}
function isApiRoute(filePath: string) {
if (filePath.includes('pages' + nodePath.sep + 'api')) {
if (filePath.includes(nodePath.sep + 'api' + nodePath.sep)) {
return true;
}
return false;

View File

@@ -2,8 +2,9 @@ require("v8-compile-cache")
const cacheFile = require("path").join(__dirname, ".blitzjs-cli-cache")
const lazyLoad = require("@salesforce/lazy-require").default.create(cacheFile)
lazyLoad.start()
import {buildConfig} from "@blitzjs/config"
import {getProjectRoot} from "@blitzjs/config"
import {run as oclifRun} from "@oclif/command"
import {compileConfig} from "next/dist/next-server/server/config-shared"
// Load the .env environment variable so it's available for all commands
require("dotenv-expand")(require("dotenv-flow").config({silent: true}))
@@ -13,7 +14,7 @@ function buildConfigIfNeeded() {
return Promise.resolve()
}
return buildConfig()
return compileConfig(getProjectRoot())
}
function runOclif() {

View File

@@ -1,9 +1,6 @@
import * as esbuild from "esbuild"
import fs from "fs"
import {existsSync, readJSONSync} from "fs-extra"
import {NextConfig} from "next/dist/next-server/server/config"
import path, {join} from "path"
import pkgDir from "pkg-dir"
const debug = require("debug")("blitz:config")
type NextExperimental = NextConfig["experimental"]
@@ -45,14 +42,10 @@ export interface BlitzConfigNormalized extends BlitzConfig {
}
export function getProjectRoot() {
// TODO consolidate with nextjs/packages/next/server/lib/utils.ts
// IF THIS IS UPDATED, so does the one inside nextjs
return path.dirname(getConfigSrcPath())
}
export function getConfigSrcPath() {
// TODO consolidate with nextjs/packages/next/server/lib/utils.ts
// IF THIS IS UPDATED, so does the one inside nextjs
const tsPath = path.resolve(path.join(process.cwd(), "blitz.config.ts"))
if (existsSync(tsPath)) {
return tsPath
@@ -61,64 +54,6 @@ export function getConfigSrcPath() {
return jsPath
}
}
export function getConfigBuildPath() {
return path.join(getProjectRoot(), ".blitz", "blitz.config.js")
}
interface BuildConfigOptions {
watch?: boolean
}
export async function buildConfig({watch}: BuildConfigOptions = {}) {
debug("Starting buildConfig...")
const dir = pkgDir.sync()
if (!dir) {
// This will happen when running blitz no inside a blitz app
debug("Unable to find package directory")
return
}
const pkg = readJSONSync(path.join(dir, "package.json"))
const srcPath = getConfigSrcPath()
if (fs.readFileSync(srcPath, "utf8").includes("tsconfig-paths/register")) {
// User is manually handling their own typescript stuff
debug("Config contains 'tsconfig-paths/register', so skipping build")
return
}
const esbuildOptions: esbuild.BuildOptions = {
entryPoints: [srcPath],
outfile: getConfigBuildPath(),
format: "cjs",
bundle: true,
platform: "node",
external: [
"blitz",
"next",
...Object.keys(require("blitz/package").dependencies),
...Object.keys(pkg?.dependencies ?? {}),
...Object.keys(pkg?.devDependencies ?? {}),
],
}
if (watch) {
esbuildOptions.watch = {
onRebuild(error) {
if (error) {
console.error("Failed to re-build blitz config")
} else {
console.log("\n> Blitz config changed - restart for changes to take effect\n")
}
},
}
}
debug("Building config...")
debug("Src: ", getConfigSrcPath())
debug("Build: ", getConfigBuildPath())
await esbuild.build(esbuildOptions)
}
declare global {
namespace NodeJS {
interface Global {
@@ -137,11 +72,13 @@ export const getConfig = (reload?: boolean): BlitzConfigNormalized => {
const {PHASE_DEVELOPMENT_SERVER, PHASE_PRODUCTION_SERVER} = require("next/constants")
const projectRoot = getProjectRoot()
let pkgJson: any
const pkgJsonPath = join(getProjectRoot(), "package.json")
if (existsSync(pkgJsonPath)) {
pkgJson = readJSONSync(join(getProjectRoot(), "package.json"))
pkgJson = readJSONSync(pkgJsonPath)
}
let blitzConfig = {
@@ -150,15 +87,8 @@ export const getConfig = (reload?: boolean): BlitzConfigNormalized => {
},
}
const projectRoot = getProjectRoot()
const nextConfigPath = path.join(projectRoot, "next.config.js")
let blitzConfigPath
if (existsSync(path.join(projectRoot, ".blitz"))) {
blitzConfigPath = path.join(projectRoot, ".blitz", "blitz.config.js")
} else {
// projectRoot is inside .blitz/build/
blitzConfigPath = path.join(projectRoot, "..", "blitz.config.js")
}
const blitzConfigPath = path.join(projectRoot, ".blitz.config.compiled.js")
debug("nextConfigPath: " + nextConfigPath)
debug("blitzConfigPath: " + blitzConfigPath)

View File

@@ -31,8 +31,7 @@ export {rpcApiHandler} from "./rpc-server"
export const fixNodeFileTrace = () => {
const path = require("path")
path.resolve("next.config.js")
path.resolve(".blitz/blitz.config.js")
path.resolve(".blitz.config.compiled.js")
path.resolve(".next/server/blitz-db.js")
path.resolve(".next/serverless/blitz-db.js")
}

View File

@@ -13,7 +13,7 @@ web_modules/
*.sqlite
*.sqlite-journal
.now
.blitz-console-history
.blitz**
blitz-log.log
# misc

View File

@@ -1,86 +0,0 @@
import {transform} from "@blitzjs/file-pipeline"
import {Stage} from "@blitzjs/file-pipeline"
import {pathExistsSync} from "fs-extra"
import {resolve} from "path"
import File from "vinyl"
const isNextConfigPath = (p: string) => /next\.config\.(js|ts)/.test(p)
const isNowBuild = () => process.env.NOW_BUILDER || process.env.VERCEL_BUILDER
/**
* Returns a Stage that manages converting from blitz.config.js to next.config.js
*/
export const createStageConfig: Stage = ({config, processNewFile, processNewChildFile}) => {
// Preconditions
const hasNextConfig = pathExistsSync(resolve(config.src, "next.config.js"))
const hasBlitzConfig =
pathExistsSync(resolve(config.src, "blitz.config.js")) ||
pathExistsSync(resolve(config.src, "blitz.config.ts"))
if (hasNextConfig && !isNowBuild()) {
// TODO: Pause the stream and ask the user if they wish to have their configuration file renamed
const err = new Error(
"Blitz does not support next.config.js. Please rename your next.config.js to blitz.config.js",
)
err.name = "NextConfigSupportError"
throw err
}
if (!hasBlitzConfig) {
// Assume a bare blitz config
processNewFile(
new File({
cwd: config.src,
path: resolve(config.src, "blitz.config.js"),
contents: Buffer.from("module.exports = {};"),
}),
)
}
if (!hasNextConfig) {
processNewFile(
new File({
cwd: config.src,
path: resolve(config.src, "next.config.js"),
contents: Buffer.from(`
const config = require('../blitz.config.js');
module.exports = config;
`),
}),
)
}
// No need to filter yet
const stream = transform.file((file) => {
if (!isNextConfigPath(file.path)) return file
// File is next.config.js
// Vercel now adds configuration needed for Now, like serverless target,
// so we need to keep and use that
if (isNowBuild()) {
// Assume we have a next.config.js if NOW_BUILDER is true as the cli creates one
// Divert next.config to next-vercel.config.js
processNewChildFile({
parent: file,
child: new File({
cwd: config.src,
path: resolve(config.src, "next-vercel.config.js"),
contents: file.contents,
}),
stageId: "config",
subfileId: "vercel-config",
})
file.contents = Buffer.from(`
const vercelConfig = require('./next-vercel.config.js');
const config = require('../blitz.config.js');
module.exports = {...config, ...vercelConfig};
`)
}
return file
})
return {stream}
}

View File

@@ -1,5 +1,4 @@
import {ServerEnvironment} from "../config"
import {createStageConfig} from "./config"
import {createStageManifest} from "./manifest"
import {createStageRelative} from "./relative"
import {createStageRewriteImports} from "./rewrite-imports"
@@ -25,7 +24,6 @@ export const configureStages = async (config: StagesConfig) => ({
createStageRpc(config.isTypeScript),
createStageRoutes,
createStageRouteImportManifest,
createStageConfig,
await createStageManifest(config.writeManifestFile, config.buildFolder, config.env),
],
})

View File

@@ -78,8 +78,6 @@ describe("Dev command", () => {
{name: "foo", children: [{name: "api", children: [{name: "foo.ts"}]}]},
],
},
{name: "blitz.config.js"},
{name: "next.config.js"},
],
})
})

View File

@@ -63,14 +63,7 @@ describe("Build command", () => {
expect(directoryTree(rootFolder)).toEqual({
children: [
{
children: [
{name: ".next"},
{name: "_blitz-version.txt"},
{name: "blitz.config.js"},
{name: "next.config.js"},
{name: "one"},
{name: "two"},
],
children: [{name: ".next"}, {name: "_blitz-version.txt"}, {name: "one"}, {name: "two"}],
name: ".blitz-build",
},
{

View File

@@ -83,40 +83,6 @@ describe("Dev command", () => {
})
})
describe.skip("when with next.config", () => {
beforeEach(() => {
rootFolder = resolve("bad")
buildFolder = resolve(rootFolder, ".blitz")
mocks.mockFs({
"bad/next.config.js": "yo",
})
})
afterEach(() => {
mocks.mockFs.restore()
})
it("should fail when passed a next.config.js", async () => {
expect.assertions(2)
await expect(
dev({
rootFolder,
buildFolder,
writeManifestFile: false,
watch: false,
port: 3000,
hostname: "localhost",
env: "dev",
}),
).rejects.toThrowError("Blitz does not support")
expect(
consoleOutput.includes(
"Blitz does not support next.config.js. Please rename your next.config.js to blitz.config.js",
),
).toBeTruthy()
})
})
describe("when run normally", () => {
beforeEach(() => {
rootFolder = resolve("dev")
@@ -146,13 +112,7 @@ describe("Dev command", () => {
expect(directoryTree(rootFolder)).toEqual({
children: [
{
children: [
{name: "_blitz-version.txt"},
{name: "blitz.config.js"},
{name: "next.config.js"},
{name: "one"},
{name: "two"},
],
children: [{name: "_blitz-version.txt"}, {name: "one"}, {name: "two"}],
name: ".blitz-build",
},
{

View File

@@ -68,8 +68,6 @@ describe("Dev command", () => {
name: "app",
children: [{name: "posts", children: [{name: "pages", children: [{name: "foo.tsx"}]}]}],
},
{name: "blitz.config.js"},
{name: "next.config.js"},
{
name: "pages",
children: [{name: "bar.tsx"}],

View File

@@ -1,77 +0,0 @@
/* eslint-disable import/first */
import {resolve} from "path"
import * as blitzVersion from "../src/blitz-version"
import {multiMock} from "./utils/multi-mock"
const mocks = multiMock(
{
"next-utils": {
nextStartDev: jest.fn().mockReturnValue(Promise.resolve()),
customServerExists: jest.fn().mockReturnValue(Boolean),
buildCustomServer: jest.fn().mockReturnValue(Promise.resolve()),
nextBuild: jest.fn().mockReturnValue(Promise.resolve()),
},
"resolve-bin-async": {
resolveBinAsync: jest.fn().mockReturnValue(Promise.resolve("")),
},
"blitz-version": {
getBlitzVersion: jest.fn().mockReturnValue(blitzVersion.getBlitzVersion()),
isVersionMatched: jest.fn().mockImplementation(blitzVersion.isVersionMatched),
saveBlitzVersion: jest.fn().mockImplementation(blitzVersion.saveBlitzVersion),
},
},
resolve(__dirname, "../src"),
)
// Import with mocks applied
import {build} from "../src/build"
import {directoryTree} from "./utils/tree-utils"
jest.mock("@blitzjs/config")
import {getConfig} from "@blitzjs/config"
;(getConfig as any).mockImplementation(() => ({}))
describe("Build command Vercel", () => {
const rootFolder = resolve("")
const buildFolder = resolve(rootFolder, ".blitz-build")
beforeEach(async () => {
process.env.NOW_BUILDER = "1"
mocks.mockFs({
"pages/bar.tsx": "",
".next": "",
"next.config.js": 'module.exports = {target: "experimental-serverless-trace"}',
})
jest.clearAllMocks()
await build({
rootFolder,
buildFolder,
writeManifestFile: false,
port: 3000,
hostname: "localhost",
env: "prod",
})
})
afterEach(() => {
delete process.env.NOW_BUILDER
mocks.mockFs.restore()
})
it("should copy the correct files to the build folder", () => {
expect(directoryTree(buildFolder)).toEqual({
name: ".blitz-build",
children: [
{name: ".next"},
{name: "_blitz-version.txt"},
{name: "blitz.config.js"},
{name: "next-vercel.config.js"},
{name: "next.config.js"},
{
name: "pages",
children: [{name: "bar.tsx"}],
},
],
})
})
})

View File

@@ -0,0 +1,13 @@
#!/bin/sh
set -e
# FROM https://stackoverflow.com/a/56282862
git ls-files -s \*next.config.js \
| sed -r 's,([^ ]* )(.*)next\.config\.js,0 \2next.config.js\n\1\2blitz.config.js,' \
| git update-index --index-info
newtree=`git write-tree`
git read-tree @
git read-tree -u --reset @ $newtree

View File

@@ -1,6 +0,0 @@
import {Ctx} from "blitz"
export default async function login(_: any, ctx: Ctx) {
await ctx.session.$create({userId: 1, role: "user"})
return true
}

View File

@@ -1,6 +0,0 @@
import {Ctx} from "blitz"
export default async function logout(_: any, ctx: Ctx) {
await ctx.session.$revoke()
return true
}

View File

@@ -1,8 +0,0 @@
import {Ctx} from "blitz"
import delay from "delay"
export default async function getAuthenticatedBasic(_: any, ctx: Ctx) {
await delay(10)
ctx.session.$authorize()
return "authenticated-basic-result"
}

View File

@@ -1,7 +0,0 @@
import {Ctx} from "blitz"
import delay from "delay"
export default async function getNoauthBasic(_: any, ctx: Ctx) {
await delay(10)
return "noauth-basic-result"
}

View File

@@ -1,4 +0,0 @@
module.exports = {
presets: ["blitz/babel"],
plugins: [],
}

View File

@@ -1,24 +0,0 @@
import {BlitzConfig, sessionMiddleware, simpleRolesIsAuthorized} from "blitz"
import db from "./db"
const config: BlitzConfig = {
target: "experimental-serverless-trace",
middleware: [
sessionMiddleware({
isAuthorized: simpleRolesIsAuthorized,
getSession: (handle) => db.get("sessions").find({handle}).value(),
getSessions: (userId) => db.get("sessions").filter({userId}).value(),
createSession: (session) => {
return db.get("sessions").push(session).write()
},
updateSession: async (handle, session) => {
return db.get("sessions").find({handle}).assign(session).write()
},
deleteSession: (handle) => db.get("sessions").remove({handle}).write(),
}),
],
eslint: {
ignoreDuringBuilds: true,
},
}
module.exports = config

View File

@@ -1,21 +0,0 @@
const low = require("lowdb")
const FileSync = require("lowdb/adapters/FileSync")
// const Memory = require("lowdb/adapters/Memory")
declare global {
namespace NodeJS {
interface Global {
db: any
}
}
}
let db = global.db || low(new FileSync("db.json"))
global.db = db
db.defaults({
users: [{id: 1}],
sessions: [],
}).write()
export default db

View File

@@ -1,22 +0,0 @@
import {AppProps, ErrorBoundary, ErrorFallbackProps, useQueryErrorResetBoundary} from "blitz"
import {ReactQueryDevtools} from "react-query/devtools"
if (typeof window !== "undefined") {
;(window as any).DEBUG_BLITZ = 1
}
export default function App({Component, pageProps}: AppProps) {
return (
<ErrorBoundary
FallbackComponent={RootErrorFallback}
onReset={useQueryErrorResetBoundary().reset}
>
<Component {...pageProps} />
<ReactQueryDevtools />
</ErrorBoundary>
)
}
function RootErrorFallback({error}: ErrorFallbackProps) {
return <div id="error">{error.name}</div>
}

View File

@@ -1,23 +0,0 @@
import {BlitzScript, Document, DocumentHead, Html, Main} from "blitz"
class MyDocument extends Document {
// Only uncomment if you need to customize this behaviour
// static async getInitialProps(ctx: DocumentContext) {
// const initialProps = await Document.getInitialProps(ctx)
// return {...initialProps}
// }
render() {
return (
<Html lang="en">
<DocumentHead />
<body>
<Main />
<BlitzScript />
</body>
</Html>
)
}
}
export default MyDocument

View File

@@ -1,34 +0,0 @@
import logout from "app/mutations/logout"
import getAuthenticatedBasic from "app/queries/getAuthenticatedBasic"
import {useMutation, useQuery} from "blitz"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getAuthenticatedBasic, undefined)
const [logoutMutation] = useMutation(logout)
return (
<div>
<div id="content">{result}</div>
<button
id="logout"
onClick={async () => {
await logoutMutation()
}}
>
logout
</button>
</div>
)
}
function Page() {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
export default Page

View File

@@ -1,52 +0,0 @@
import login from "app/mutations/login"
import logout from "app/mutations/logout"
import {useMutation, useSession} from "blitz"
import {useState} from "react"
function Content() {
const [error, setError] = useState(null)
const session = useSession({suspense: false})
const [loginMutation] = useMutation(login)
const [logoutMutation] = useMutation(logout)
if (error) return <div id="error">{error}</div>
return (
<div>
<div id="content">{session.userId ? "logged-in" : "logged-out"}</div>
{session.userId ? (
<button
id="logout"
onClick={async () => {
try {
await logoutMutation()
} catch (error) {
setError(error)
}
}}
>
logout
</button>
) : (
<button
id="login"
onClick={async () => {
await loginMutation()
}}
>
login
</button>
)}
</div>
)
}
function Page() {
return (
<div id="page">
<Content />
</div>
)
}
export default Page

View File

@@ -1,20 +0,0 @@
import getNoauthBasic from "app/queries/getNoauthBasic"
import {useQuery} from "blitz"
import {Suspense} from "react"
function Content() {
const [result] = useQuery(getNoauthBasic, undefined)
return <div id="content">{result}</div>
}
function Page() {
return (
<div id="page">
<Suspense fallback={"Loading..."}>
<Content />
</Suspense>
</div>
)
}
export default Page

View File

@@ -1,69 +0,0 @@
/* eslint-env jest */
import {findPort, killApp, launchApp, renderViaHTTP, waitFor} from "lib/blitz-test-utils"
import webdriver from "lib/next-webdriver"
import {join} from "path"
import rimraf from "rimraf"
const context: any = {}
jest.setTimeout(1000 * 60 * 5)
describe("Auth", () => {
beforeAll(async () => {
rimraf.sync(join(__dirname, "../db.json"))
context.appPort = await findPort()
context.server = await launchApp(join(__dirname, "../"), context.appPort, {
env: {__NEXT_TEST_WITH_DEVTOOL: 1},
})
const prerender = [
"/login",
"/noauth-query",
"/authenticated-query",
"/api/queries/getNoauthBasic",
"/api/queries/getAuthenticatedBasic",
"/api/mutations/login",
"/api/mutations/logout",
]
await Promise.all(prerender.map((route) => renderViaHTTP(context.appPort, route)))
})
afterAll(() => killApp(context.server))
describe("unauthenticated", () => {
it("should render result for open query", async () => {
const browser = await webdriver(context.appPort, "/noauth-query")
let text = await browser.elementByCss("#page").text()
await browser.waitForElementByCss("#content")
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/noauth-basic-result/)
if (browser) await browser.close()
})
it("should render error for protected query", async () => {
const browser = await webdriver(context.appPort, "/authenticated-query")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
})
describe("authenticated", () => {
it("should login and out successfully", async () => {
const browser = await webdriver(context.appPort, "/login")
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
await browser.elementByCss("#login").click()
await waitFor(100)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-in/)
await browser.elementByCss("#logout").click()
await waitFor(100)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
if (browser) await browser.close()
})
})
})

View File

@@ -1,25 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": "./",
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"tsBuildInfoFile": ".tsbuildinfo",
"paths": {
"lib/*": ["../../lib/*"]
}
},
"exclude": ["node_modules"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
}

View File

@@ -1,14 +0,0 @@
import {DefaultCtx, SessionContext, SimpleRolesIsAuthorized} from "blitz"
declare module "blitz" {
export interface Ctx extends DefaultCtx {
session: SessionContext
}
export interface Session {
isAuthorized: SimpleRolesIsAuthorized<"user">
PublicData: {
userId: number
role: "user"
}
}
}

View File

@@ -0,0 +1 @@
SESSION_SECRET_KEY=infeiaosievnzoiesntaoikstaoesntaost

View File

@@ -2,6 +2,7 @@ import {BlitzConfig, sessionMiddleware, simpleRolesIsAuthorized} from "blitz"
import db from "./db"
const config: BlitzConfig = {
// replace me
middleware: [
sessionMiddleware({
isAuthorized: simpleRolesIsAuthorized,

View File

@@ -3,6 +3,8 @@ import fs from "fs-extra"
import {
blitzBuild,
blitzExport,
blitzStart,
File,
findPort,
killApp,
launchApp,
@@ -13,18 +15,147 @@ import webdriver from "lib/next-webdriver"
import {join} from "path"
import rimraf from "rimraf"
const context: any = {}
jest.setTimeout(1000 * 60 * 5)
let app: any
let appPort: number
const appDir = join(__dirname, "..")
const outdir = join(appDir, "out")
const blitzConfig = new File(join(appDir, "blitz.config.ts"))
jest.setTimeout(1000 * 60 * 2)
describe("Auth", () => {
beforeAll(async () => {
rimraf.sync(join(__dirname, "../db.json"))
beforeAll(async () => {
rimraf.sync(join(__dirname, "../db.json"))
})
context.appPort = await findPort()
context.server = await launchApp(join(__dirname, "../"), context.appPort, {
env: {__NEXT_TEST_WITH_DEVTOOL: 1},
const runTests = (mode: string) => {
describe("Auth", () => {
describe("unauthenticated", () => {
it("should render result for open query", async () => {
const browser = await webdriver(appPort, "/noauth-query")
let text = await browser.elementByCss("#page").text()
await browser.waitForElementByCss("#content")
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/noauth-basic-result/)
if (browser) await browser.close()
})
it("should render error for protected query", async () => {
const browser = await webdriver(appPort, "/authenticated-query")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
if (mode === "dev") {
// TODO - investigate why failing in production mode
it("should render error for protected page", async () => {
const browser = await webdriver(appPort, "/page-dot-authenticate")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
}
})
describe("authenticated", () => {
it("should login and out successfully", async () => {
const browser = await webdriver(appPort, "/login")
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
await browser.elementByCss("#login").click()
await waitFor(100)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-in/)
await browser.elementByCss("#logout").click()
await waitFor(100)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
if (browser) await browser.close()
})
it("should logout without infinite loop #2233", async () => {
// Login
let browser = await webdriver(appPort, "/login")
await browser.elementByCss("#login").click()
await browser.eval(`window.location = "/authenticated-query"`)
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
await browser.elementByCss("#logout").click()
await waitFor(100)
await browser.waitForElementByCss("#error")
text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
it("Page.authenticate = {redirect} should work ", async () => {
// Login
let browser = await webdriver(appPort, "/login")
await waitFor(100)
await browser.elementByCss("#login").click()
await waitFor(100)
await browser.eval(`window.location = "/page-dot-authenticate-redirect"`)
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
await browser.elementByCss("#logout").click()
await waitFor(500)
expect(await browser.url()).toMatch(/\/login/)
if (browser) await browser.close()
})
})
describe("prefetching", () => {
it("should prefetch from the query cache #2281", async () => {
const browser = await webdriver(appPort, "/prefetching")
await browser.waitForElementByCss("#content")
const text = await browser.elementByCss("#content").text()
expect(text).toMatch(/noauth-basic-result/)
if (browser) await browser.close()
})
})
describe("setting public data for a user", () => {
it("should update all sessions of the user", async () => {
const browser = await webdriver(appPort, "/login")
let text = await browser.elementByCss("#content").text()
if (text.match(/logged-in/)) {
await browser.elementByCss("#logout").click()
}
await browser.eval(`window.location = "/set-public-data"`)
await browser.waitForElementByCss("#change-role")
await browser.elementByCss("#change-role").click()
await waitFor(500)
await browser.waitForElementByCss(".role")
// @ts-ignore
const roleElementsAfter = await browser.elementsByCss(".role")
expect(roleElementsAfter.length).toBe(2)
for (const role of roleElementsAfter) {
// @ts-ignore
const text = await role.getText()
expect(text).toMatch(/role: new role/)
}
if (browser) await browser.close()
})
})
})
}
describe("dev mode", () => {
beforeAll(async () => {
await blitzBuild(appDir)
appPort = await findPort()
app = await launchApp(appDir, appPort)
const prerender = [
"/login",
"/noauth-query",
@@ -37,130 +168,36 @@ describe("Auth", () => {
"/api/mutations/login",
"/api/mutations/logout",
]
await Promise.all(prerender.map((route) => renderViaHTTP(context.appPort, route)))
})
afterAll(() => killApp(context.server))
describe("unauthenticated", () => {
it("should render result for open query", async () => {
const browser = await webdriver(context.appPort, "/noauth-query")
let text = await browser.elementByCss("#page").text()
await browser.waitForElementByCss("#content")
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/noauth-basic-result/)
if (browser) await browser.close()
})
it("should render error for protected query", async () => {
const browser = await webdriver(context.appPort, "/authenticated-query")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
it("should render error for protected page", async () => {
const browser = await webdriver(context.appPort, "/page-dot-authenticate")
await browser.waitForElementByCss("#error")
let text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
})
describe("authenticated", () => {
it("should login and out successfully", async () => {
const browser = await webdriver(context.appPort, "/login")
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
await browser.elementByCss("#login").click()
await waitFor(100)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-in/)
await browser.elementByCss("#logout").click()
await waitFor(100)
text = await browser.elementByCss("#content").text()
expect(text).toMatch(/logged-out/)
if (browser) await browser.close()
})
it("should logout without infinite loop #2233", async () => {
// Login
let browser = await webdriver(context.appPort, "/login")
await browser.elementByCss("#login").click()
await browser.eval(`window.location = "/authenticated-query"`)
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
await browser.elementByCss("#logout").click()
await waitFor(100)
await browser.waitForElementByCss("#error")
text = await browser.elementByCss("#error").text()
expect(text).toMatch(/AuthenticationError/)
if (browser) await browser.close()
})
it("Page.authenticate = {redirect} should work ", async () => {
// Login
let browser = await webdriver(context.appPort, "/login")
await waitFor(100)
await browser.elementByCss("#login").click()
await waitFor(100)
await browser.eval(`window.location = "/page-dot-authenticate-redirect"`)
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()
expect(text).toMatch(/authenticated-basic-result/)
await browser.elementByCss("#logout").click()
await waitFor(500)
expect(await browser.url()).toMatch(/\/login/)
if (browser) await browser.close()
})
})
describe("prefetching", () => {
it("should prefetch from the query cache #2281", async () => {
const browser = await webdriver(context.appPort, "/prefetching")
await browser.waitForElementByCss("#content")
const text = await browser.elementByCss("#content").text()
expect(text).toMatch(/noauth-basic-result/)
if (browser) await browser.close()
})
})
describe("setting public data for a user", () => {
it("should update all sessions of the user", async () => {
const browser = await webdriver(context.appPort, "/login")
let text = await browser.elementByCss("#content").text()
if (text.match(/logged-in/)) {
await browser.elementByCss("#logout").click()
}
await browser.eval(`window.location = "/set-public-data"`)
await browser.waitForElementByCss("#change-role")
await browser.elementByCss("#change-role").click()
await waitFor(500)
await browser.waitForElementByCss(".role")
// @ts-ignore
const roleElementsAfter = await browser.elementsByCss(".role")
expect(roleElementsAfter.length).toBe(2)
for (const role of roleElementsAfter) {
// @ts-ignore
const text = await role.getText()
expect(text).toMatch(/role: new role/)
}
if (browser) await browser.close()
})
await Promise.all(prerender.map((route) => renderViaHTTP(appPort, route)))
})
afterAll(() => killApp(app))
runTests("dev")
})
const appDir = join(__dirname, "../")
const outdir = join(appDir, "out")
describe("production mode", () => {
beforeAll(async () => {
await blitzBuild(appDir)
appPort = await findPort()
app = await blitzStart(appDir, appPort)
})
afterAll(() => killApp(app))
runTests("server")
})
describe("serverless mode", () => {
beforeAll(async () => {
await blitzConfig.replace("// replace me", `target: 'experimental-serverless-trace', `)
await blitzBuild(appDir)
appPort = await findPort()
app = await blitzStart(appDir, appPort)
})
afterAll(async () => {
await killApp(app)
blitzConfig.restore()
})
runTests("serverless")
})
describe("auth - blitz export should not work", () => {
it("should build successfully", async () => {

View File

@@ -74,7 +74,7 @@ describe("Queries", () => {
})
describe("DehydratedState", () => {
it.only("should work", async () => {
it("should work", async () => {
const browser = await webdriver(context.appPort, "/dehydrated-state")
await browser.waitForElementByCss("#content")
let text = await browser.elementByCss("#content").text()

View File

@@ -1,4 +1,5 @@
/* eslint-env jest */
import {Console} from "console"
process.env.BLITZ_TEST_ENVIRONMENT = true
@@ -7,3 +8,6 @@ if (process.env.JEST_RETRY_TIMES) {
console.log(`Configuring jest retries: ${retries}`)
jest.retryTimes(retries)
}
// Reset to default console instead of the verbose Jest one
global.console = new Console({stdout: process.stdout, stderr: process.stderr})

View File

@@ -142,11 +142,9 @@ export function runBlitzCommand(argv: any[], options: RunBlitzCommandOptions = {
}
let stderrOutput = ""
if (options.stderr) {
instance.stderr?.on("data", function (chunk) {
stderrOutput += chunk
})
}
instance.stderr?.on("data", function (chunk) {
stderrOutput += chunk
})
let stdoutOutput = ""
if (options.stdout) {