Next.js Fork Migration: Move blitz.config.(js|ts) support into nextjs core (patch) (#2532)
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -30,3 +30,4 @@ examples/auth2
|
||||
db.sqlite-journal
|
||||
test/integration/**/db.json
|
||||
test/**/*/out
|
||||
.blitz**
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.')
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)'`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
onDemandEntries: {
|
||||
// Make sure entries are not getting disposed.
|
||||
maxInactiveAge: 1000 * 60 * 60,
|
||||
},
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function Abc() {
|
||||
return <div />
|
||||
}
|
||||
@@ -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/)
|
||||
})
|
||||
})
|
||||
@@ -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')
|
||||
})
|
||||
|
||||
@@ -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 || ''
|
||||
},
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 })
|
||||
|
||||
@@ -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}`))
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ web_modules/
|
||||
*.sqlite
|
||||
*.sqlite-journal
|
||||
.now
|
||||
.blitz-console-history
|
||||
.blitz**
|
||||
blitz-log.log
|
||||
|
||||
# misc
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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),
|
||||
],
|
||||
})
|
||||
|
||||
@@ -78,8 +78,6 @@ describe("Dev command", () => {
|
||||
{name: "foo", children: [{name: "api", children: [{name: "foo.ts"}]}]},
|
||||
],
|
||||
},
|
||||
{name: "blitz.config.js"},
|
||||
{name: "next.config.js"},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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"}],
|
||||
|
||||
@@ -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"}],
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
13
scripts/renameNextConfigToBlitzConfig.sh
Executable file
13
scripts/renameNextConfigToBlitzConfig.sh
Executable 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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import {Ctx} from "blitz"
|
||||
|
||||
export default async function logout(_: any, ctx: Ctx) {
|
||||
await ctx.session.$revoke()
|
||||
return true
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
presets: ["blitz/babel"],
|
||||
plugins: [],
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
1
test/integration/auth/.env.production
Normal file
1
test/integration/auth/.env.production
Normal file
@@ -0,0 +1 @@
|
||||
SESSION_SECRET_KEY=infeiaosievnzoiesntaoikstaoesntaost
|
||||
@@ -2,6 +2,7 @@ import {BlitzConfig, sessionMiddleware, simpleRolesIsAuthorized} from "blitz"
|
||||
import db from "./db"
|
||||
|
||||
const config: BlitzConfig = {
|
||||
// replace me
|
||||
middleware: [
|
||||
sessionMiddleware({
|
||||
isAuthorized: simpleRolesIsAuthorized,
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user