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

Standardize prettier options across all Blitz code bases (#703)

Co-authored-by: Brandon Bayer <b@bayer.ws> (meta)
This commit is contained in:
Justin Hall
2020-06-18 20:33:57 -06:00
committed by GitHub
parent 09fc2e10d6
commit b3814fc7c0
316 changed files with 3864 additions and 2896 deletions

View File

@@ -1,38 +1,38 @@
module.exports = {
parser: '@typescript-eslint/parser',
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
sourceType: "module",
ecmaFeatures: {
jsx: true,
},
project: `./tsconfig.json`,
},
plugins: ['@typescript-eslint', 'import', 'unicorn'],
extends: ['react-app'],
plugins: ["@typescript-eslint", "import", "unicorn"],
extends: ["react-app"],
rules: {
'react/react-in-jsx-scope': 'off', // React is always in scope with Blitz
'jsx-a11y/anchor-is-valid': 'off', //Doesn't play well with Blitz/Next <Link> usage
'import/first': 'off',
'import/no-default-export': 'error',
'require-await': 'error',
'no-async-promise-executor': 'error',
'unicorn/filename-case': [
'error',
"react/react-in-jsx-scope": "off", // React is always in scope with Blitz
"jsx-a11y/anchor-is-valid": "off", //Doesn't play well with Blitz/Next <Link> usage
"import/first": "off",
"import/no-default-export": "error",
"require-await": "error",
"no-async-promise-executor": "error",
"unicorn/filename-case": [
"error",
{
case: 'kebabCase',
case: "kebabCase",
},
],
'@typescript-eslint/no-floating-promises': 'error',
"@typescript-eslint/no-floating-promises": "error",
},
ignorePatterns: ['packages/cli/', 'packages/generator/templates'],
ignorePatterns: ["packages/cli/", "packages/generator/templates"],
overrides: [
{
files: ['examples/**', 'packages/gui/**'],
files: ["examples/**", "packages/gui/**"],
rules: {
'import/no-default-export': 'off',
'unicorn/filename-case': 'off',
'@typescript-eslint/no-floating-promises': 'off',
"import/no-default-export": "off",
"unicorn/filename-case": "off",
"@typescript-eslint/no-floating-promises": "off",
},
},
],

2
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,4 @@
github: blitz-js
custom: ['https://paypal.me/thebayers']
custom: ["https://paypal.me/thebayers"]
open_collective: blitzjs
patreon: flybayer

View File

@@ -1,9 +1,9 @@
---
name: Feature/change request
about: Something new or better!
title: ''
labels: ''
assignees: ''
title: ""
labels: ""
assignees: ""
---
### What do you want and why?

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
const fs = require('fs')
const yarnOut = fs.readFileSync(0, {encoding: 'utf8'})
const fs = require("fs")
const yarnOut = fs.readFileSync(0, {encoding: "utf8"})
const [installTimeString] = /(?<=^Done in )\d+\.\d+(?=s\.$)/m.exec(yarnOut)
const installTime = Number(installTimeString)

View File

@@ -19,7 +19,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '12.16.1'
node-version: "12.16.1"
- name: Test Install time
run: |
cd ../ && mkdir test && cd test
@@ -37,7 +37,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: '12.16.1'
node-version: "12.16.1"
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"

View File

@@ -1,3 +1,3 @@
const {fs} = require('memfs')
const {fs} = require("memfs")
module.exports = fs

View File

@@ -189,8 +189,8 @@ const Home = () => (
body {
padding: 0;
margin: 0;
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-family: "Libre Franklin", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
* {

View File

@@ -12,7 +12,9 @@
],
"prettier": {
"semi": false,
"printWidth": 110
"printWidth": 100,
"bracketSpacing": false,
"trailingComma": "all"
},
"husky": {
"hooks": {

View File

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

View File

@@ -3,12 +3,13 @@ import { BlitzPage, useInfiniteQuery } from "blitz"
import getProductsInfinite from "app/products/queries/getProductsInfinite"
const Products = () => {
const [
groupedProducts,
{ isFetching, isFetchingMore, fetchMore, canFetchMore },
] = useInfiniteQuery(getProductsInfinite, (page = { take: 3, skip: 0 }) => page, {
const [groupedProducts, {isFetching, isFetchingMore, fetchMore, canFetchMore}] = useInfiniteQuery(
getProductsInfinite,
(page = {take: 3, skip: 0}) => page,
{
getFetchMore: (lastGroup) => lastGroup.nextPage,
})
},
)
return (
<>

View File

@@ -13,7 +13,7 @@ type GetProductsInput = {
export default async function getProducts(
{where, orderBy, skip, cursor, take}: GetProductsInput,
ctx: Record<any, unknown> = {}
ctx: Record<any, unknown> = {},
) {
if (ctx.referer) {
console.log("HTTP referer:", ctx.referer)

View File

@@ -11,7 +11,9 @@
},
"prettier": {
"semi": false,
"printWidth": 100
"printWidth": 100,
"bracketSpacing": false,
"trailingComma": "all"
},
"dependencies": {
"@prisma/cli": "2.0.0",

View File

@@ -11,7 +11,9 @@
],
"prettier": {
"semi": false,
"printWidth": 110
"printWidth": 100,
"bracketSpacing": false,
"trailingComma": "all"
},
"husky": {
"hooks": {

View File

@@ -1 +1 @@
require('@testing-library/jest-dom')
require("@testing-library/jest-dom")

View File

@@ -1,19 +1,19 @@
import pkg from './package.json'
import typescript from '@wessberg/rollup-plugin-ts'
import commonjs from 'rollup-plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import resolve from 'rollup-plugin-node-resolve'
import json from 'rollup-plugin-json'
import pkg from "./package.json"
import typescript from "@wessberg/rollup-plugin-ts"
import commonjs from "rollup-plugin-commonjs"
import external from "rollup-plugin-peer-deps-external"
import resolve from "rollup-plugin-node-resolve"
import json from "rollup-plugin-json"
const common = {
external: [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
'react',
'react-dom',
'next',
'fs',
'path',
"react",
"react-dom",
"next",
"fs",
"path",
],
plugins: [
json(),
@@ -30,23 +30,23 @@ const common = {
}
const lib = {
input: './src/index.ts',
input: "./src/index.ts",
output: {
file: pkg['main'],
exports: 'named',
sourcemap: 'true',
format: 'cjs',
file: pkg["main"],
exports: "named",
sourcemap: "true",
format: "cjs",
},
...common,
}
const cli = {
input: './src/bin/cli.ts',
input: "./src/bin/cli.ts",
output: {
file: './dist/cli.js',
exports: 'named',
sourcemap: 'true',
format: 'cjs',
file: "./dist/cli.js",
exports: "named",
sourcemap: "true",
format: "cjs",
},
...common,
}

View File

@@ -1 +1 @@
export * from '@blitzjs/core'
export * from "@blitzjs/core"

View File

@@ -1 +1 @@
export const isServer = typeof window === 'undefined'
export const isServer = typeof window === "undefined"

View File

@@ -1,3 +1,3 @@
it.skip('todo', () => {
it.skip("todo", () => {
expect(true).toBe(true)
})

View File

@@ -1,12 +1,12 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'js', 'json'],
coverageReporters: ['json', 'lcov', 'text', 'clover'],
preset: "ts-jest",
testEnvironment: "node",
moduleFileExtensions: ["ts", "js", "json"],
coverageReporters: ["json", "lcov", "text", "clover"],
// collectCoverage: !!`Boolean(process.env.CI)`,
collectCoverageFrom: ['src/**/*.ts'],
modulePathIgnorePatterns: ['<rootDir>/tmp', '<rootDir>/lib'],
testPathIgnorePatterns: ['src/commands/test.ts'],
collectCoverageFrom: ["src/**/*.ts"],
modulePathIgnorePatterns: ["<rootDir>/tmp", "<rootDir>/lib"],
testPathIgnorePatterns: ["src/commands/test.ts"],
testTimeout: 30000,
// TODO enable threshold
// coverageThreshold: {
@@ -19,8 +19,8 @@ module.exports = {
// },
globals: {
'ts-jest': {
tsConfig: 'test/tsconfig.json',
"ts-jest": {
tsConfig: "test/tsconfig.json",
isolatedModules: true,
},
},

View File

@@ -1,11 +1,11 @@
import {Hook} from '@oclif/config'
import chalk from 'chalk'
import {Hook} from "@oclif/config"
import chalk from "chalk"
import {isBlitzRoot, IsBlitzRootError} from './utils/is-blitz-root'
import {isBlitzRoot, IsBlitzRootError} from "./utils/is-blitz-root"
const whitelistGlobal = ['-h', '--help', 'help', 'new']
const whitelistGlobal = ["-h", "--help", "help", "new"]
export const hook: Hook<'init'> = async function (options) {
export const hook: Hook<"init"> = async function (options) {
const {id} = options
if (id && whitelistGlobal.includes(id)) return
@@ -16,13 +16,13 @@ export const hook: Hook<'init'> = async function (options) {
case IsBlitzRootError.NotBlitz:
return this.error(
`You are not inside a Blitz project, so this command won't work.\nYou can create a new app with ${chalk.bold(
'blitz new myapp',
)} or see help with ${chalk.bold('blitz help')}`,
"blitz new myapp",
)} or see help with ${chalk.bold("blitz help")}`,
)
case IsBlitzRootError.NotRoot:
const help = depth
? `\nUse ${chalk.bold('cd ' + '../'.repeat(depth))} to get to the root of your project`
: ''
? `\nUse ${chalk.bold("cd " + "../".repeat(depth))} to get to the root of your project`
: ""
return this.error(
`You are currently in a sub-folder of your Blitz app, but this command must be used from the root of your project.${help}`,

View File

@@ -1,5 +1,5 @@
import {Command as OclifCommand} from '@oclif/command'
import Enquirer = require('enquirer')
import {Command as OclifCommand} from "@oclif/command"
import Enquirer = require("enquirer")
export abstract class Command extends OclifCommand {
protected enquirer = new Enquirer()

View File

@@ -1,21 +1,21 @@
import {Command, flags} from '@oclif/command'
import {build} from '@blitzjs/server'
import {runPrismaGeneration} from './db'
import {Command, flags} from "@oclif/command"
import {build} from "@blitzjs/server"
import {runPrismaGeneration} from "./db"
export class Build extends Command {
static description = 'Create a production build'
static aliases = ['b']
static description = "Create a production build"
static aliases = ["b"]
static flags = {
port: flags.integer({
char: 'p',
description: 'Set port number',
char: "p",
description: "Set port number",
default: 3000,
}),
hostname: flags.string({
char: 'H',
description: 'Set server hostname',
default: 'localhost',
char: "H",
description: "Set server hostname",
default: "localhost",
}),
}

View File

@@ -1,33 +1,33 @@
import {runRepl} from '@blitzjs/repl'
import {Command} from '@oclif/command'
import path from 'path'
import fs from 'fs'
import pkgDir from 'pkg-dir'
import {log} from '@blitzjs/display'
import chalk from 'chalk'
import {runRepl} from "@blitzjs/repl"
import {Command} from "@oclif/command"
import path from "path"
import fs from "fs"
import pkgDir from "pkg-dir"
import {log} from "@blitzjs/display"
import chalk from "chalk"
import {setupTsnode} from '../utils/setup-ts-node'
import {runPrismaGeneration} from './db'
import {setupTsnode} from "../utils/setup-ts-node"
import {runPrismaGeneration} from "./db"
const projectRoot = pkgDir.sync() || process.cwd()
const isTypescript = fs.existsSync(path.join(projectRoot, 'tsconfig.json'))
const isTypescript = fs.existsSync(path.join(projectRoot, "tsconfig.json"))
export class Console extends Command {
static description = 'Run the Blitz console REPL'
static aliases = ['c']
static description = "Run the Blitz console REPL"
static aliases = ["c"]
static replOptions = {
prompt: '⚡️ > ',
prompt: "⚡️ > ",
useColors: true,
}
async run() {
log.branded('You have entered the Blitz console')
console.log(chalk.yellow('Tips: - Exit by typing .exit or pressing Ctrl-D'))
console.log(chalk.yellow(' - Use your db like this: await db.project.findMany()'))
console.log(chalk.yellow(' - Use your queries/mutations like this: await getProjects({})'))
log.branded("You have entered the Blitz console")
console.log(chalk.yellow("Tips: - Exit by typing .exit or pressing Ctrl-D"))
console.log(chalk.yellow(" - Use your db like this: await db.project.findMany()"))
console.log(chalk.yellow(" - Use your queries/mutations like this: await getProjects({})"))
const spinner = log.spinner('Loading your code').start()
const spinner = log.spinner("Loading your code").start()
if (isTypescript) {
setupTsnode()
}

View File

@@ -1,17 +1,17 @@
import {resolveBinAsync} from '@blitzjs/server'
import {log} from '@blitzjs/display'
import {Command, flags} from '@oclif/command'
import chalk from 'chalk'
import {spawn} from 'cross-spawn'
import {prompt} from 'enquirer'
import * as fs from 'fs'
import * as path from 'path'
import {promisify} from 'util'
import {projectRoot} from '../utils/get-project-root'
import {resolveBinAsync} from "@blitzjs/server"
import {log} from "@blitzjs/display"
import {Command, flags} from "@oclif/command"
import chalk from "chalk"
import {spawn} from "cross-spawn"
import {prompt} from "enquirer"
import * as fs from "fs"
import * as path from "path"
import {promisify} from "util"
import {projectRoot} from "../utils/get-project-root"
const schemaPath = path.join(process.cwd(), 'db', 'schema.prisma')
const schemaPath = path.join(process.cwd(), "db", "schema.prisma")
const schemaArg = `--schema=${schemaPath}`
const getPrismaBin = () => resolveBinAsync('@prisma/cli', 'prisma')
const getPrismaBin = () => resolveBinAsync("@prisma/cli", "prisma")
// Prisma client generation will fail if no model is defined in the schema.
// So the silent option is here to ignore that failure
@@ -20,7 +20,9 @@ export const runPrismaGeneration = async ({silent = false} = {}) => {
const prismaBin = await getPrismaBin()
return new Promise((resolve) => {
spawn(prismaBin, ['generate', schemaArg], {stdio: silent ? 'ignore' : 'inherit'}).on('exit', (code) => {
spawn(prismaBin, ["generate", schemaArg], {stdio: silent ? "ignore" : "inherit"}).on(
"exit",
(code) => {
if (code === 0) {
resolve()
} else if (silent) {
@@ -28,19 +30,22 @@ export const runPrismaGeneration = async ({silent = false} = {}) => {
} else {
process.exit(1)
}
})
},
)
})
} catch (error) {
if (silent) return
throw new Error("Oops, we can't find Prisma Client. Please make sure it's installed in your project")
throw new Error(
"Oops, we can't find Prisma Client. Please make sure it's installed in your project",
)
}
}
const runMigrateUp = (prismaBin: string, resolve: (value?: unknown) => void) => {
const cp = spawn(prismaBin, ['migrate', 'up', schemaArg, '--create-db', '--experimental'], {
stdio: 'inherit',
const cp = spawn(prismaBin, ["migrate", "up", schemaArg, "--create-db", "--experimental"], {
stdio: "inherit",
})
cp.on('exit', async (code) => {
cp.on("exit", async (code) => {
if (code === 0) {
await runPrismaGeneration()
resolve()
@@ -53,13 +58,13 @@ const runMigrateUp = (prismaBin: string, resolve: (value?: unknown) => void) =>
export const runMigrate = async () => {
const prismaBin = await getPrismaBin()
return new Promise((resolve) => {
if (process.env.NODE_ENV === 'production') {
if (process.env.NODE_ENV === "production") {
runMigrateUp(prismaBin, resolve)
} else {
const cp = spawn(prismaBin, ['migrate', 'save', schemaArg, '--create-db', '--experimental'], {
stdio: 'inherit',
const cp = spawn(prismaBin, ["migrate", "save", schemaArg, "--create-db", "--experimental"], {
stdio: "inherit",
})
cp.on('exit', (code) => {
cp.on("exit", (code) => {
if (code === 0) {
runMigrateUp(prismaBin, resolve)
} else {
@@ -79,13 +84,13 @@ export async function resetPostgres(connectionString: string, db: any): Promise<
)
// currently assuming the public schema is being used
// delete schema and recreate with the appropriate privileges
await db.raw('DROP SCHEMA public cascade;')
await db.raw('CREATE SCHEMA public;')
await db.raw('GRANT ALL ON schema public TO postgres;')
await db.raw('GRANT ALL ON schema public TO public;')
await db.raw("DROP SCHEMA public cascade;")
await db.raw("CREATE SCHEMA public;")
await db.raw("GRANT ALL ON schema public TO postgres;")
await db.raw("GRANT ALL ON schema public TO public;")
// run migration
await runMigrate()
log.success('Your database has been reset.')
log.success("Your database has been reset.")
process.exit(0)
} catch (err) {
log.error(`Resetting the database has failed with an error from the database: `)
@@ -101,7 +106,7 @@ export async function resetMysql(connectionString: string, db: any): Promise<voi
await db.raw(`DROP DATABASE \`${dbName}\``)
// run migration
await runMigrate()
log.success('Your database has been reset.')
log.success("Your database has been reset.")
process.exit(0)
} catch (err) {
log.error(`Resetting the database has failed with an error from the database: `)
@@ -111,14 +116,14 @@ export async function resetMysql(connectionString: string, db: any): Promise<voi
}
export async function resetSqlite(connectionString: string): Promise<void> {
const dbPath: string = connectionString.replace(/^(?:\.\.[\\/])+/, '')
const dbPath: string = connectionString.replace(/^(?:\.\.[\\/])+/, "")
const unlink = promisify(fs.unlink)
try {
// delete database from folder
await unlink(dbPath)
// run migration
await runMigrate()
log.success('Your database has been reset.')
log.success("Your database has been reset.")
process.exit(0)
} catch (err) {
log.error(`Resetting the database has failed with an error from the file system: `)
@@ -128,7 +133,7 @@ export async function resetSqlite(connectionString: string): Promise<void> {
}
export function getDbName(connectionString: string): string {
const dbUrlParts: string[] = connectionString!.split('/')
const dbUrlParts: string[] = connectionString!.split("/")
const dbName: string = dbUrlParts[dbUrlParts.length - 1]
return dbName
}
@@ -136,97 +141,100 @@ export function getDbName(connectionString: string): string {
export class Db extends Command {
static description = `Run database commands
${chalk.bold('migrate')} Run any needed migrations via Prisma 2 and generate Prisma Client.
${chalk.bold("migrate")} Run any needed migrations via Prisma 2 and generate Prisma Client.
${chalk.bold(
'introspect',
"introspect",
)} Will introspect the database defined in db/schema.prisma and automatically generate a complete schema.prisma file for you. Lastly, it'll generate Prisma Client.
${chalk.bold(
'studio',
"studio",
)} Open the Prisma Studio UI at http://localhost:5555 so you can easily see and change data in your database.
${chalk.bold('reset')} Reset the database and run a fresh migration via Prisma 2.
${chalk.bold("reset")} Reset the database and run a fresh migration via Prisma 2.
`
static args = [
{
name: 'command',
description: 'Run specific db command',
name: "command",
description: "Run specific db command",
required: true,
},
]
static flags = {
help: flags.help({char: 'h'}),
help: flags.help({char: "h"}),
}
async run() {
const {args} = this.parse(Db)
const command = args['command']
const command = args["command"]
const prismaBin = await getPrismaBin()
if (command === 'migrate' || command === 'm') {
if (command === "migrate" || command === "m") {
await runMigrate()
} else if (command === 'introspect') {
const cp = spawn(prismaBin, ['introspect', schemaArg], {
stdio: 'inherit',
} else if (command === "introspect") {
const cp = spawn(prismaBin, ["introspect", schemaArg], {
stdio: "inherit",
})
cp.on('exit', (code) => {
cp.on("exit", (code) => {
if (code === 0) {
spawn(prismaBin, ['generate', schemaArg], {stdio: 'inherit'}).on('exit', (code: number) => {
spawn(prismaBin, ["generate", schemaArg], {stdio: "inherit"}).on(
"exit",
(code: number) => {
if (code !== 0) {
process.exit(1)
}
})
},
)
} else {
process.exit(1)
}
})
} else if (command === 'studio') {
const cp = spawn(prismaBin, ['studio', schemaArg, '--experimental'], {
stdio: 'inherit',
} else if (command === "studio") {
const cp = spawn(prismaBin, ["studio", schemaArg, "--experimental"], {
stdio: "inherit",
})
cp.on('exit', (code) => {
cp.on("exit", (code) => {
if (code === 0) {
} else {
process.exit(1)
}
})
} else if (command === 'reset') {
const spinner = log.spinner('Loading your database').start()
} else if (command === "reset") {
const spinner = log.spinner("Loading your database").start()
await runPrismaGeneration({silent: true})
spinner.succeed()
await prompt<{confirm: string}>({
type: 'confirm',
name: 'confirm',
message: 'Are you sure you want to reset your database and erase ALL data?',
type: "confirm",
name: "confirm",
message: "Are you sure you want to reset your database and erase ALL data?",
}).then((res) => {
if (res.confirm) {
const prismaClientPath = require.resolve('@prisma/client', {paths: [projectRoot]})
const prismaClientPath = require.resolve("@prisma/client", {paths: [projectRoot]})
const {PrismaClient} = require(prismaClientPath)
const db = new PrismaClient()
const dataSource: any = db.internalDatasources[0]
const connectorType: string = dataSource.connectorType
const connectionString: string = dataSource.url.value
if (connectorType === 'postgresql') {
if (connectorType === "postgresql") {
resetPostgres(connectionString, db)
} else if (connectorType === 'mysql') {
} else if (connectorType === "mysql") {
resetMysql(connectionString, db)
} else if (connectorType === 'sqlite') {
} else if (connectorType === "sqlite") {
resetSqlite(connectionString)
} else {
this.log('Could not find a valid database configuration')
this.log("Could not find a valid database configuration")
}
}
})
} else {
this.log('\nUh oh, Blitz does not support that command.')
this.log('You can try running a prisma command directly with:')
this.log('\n `npm run prisma COMMAND` or `yarn prisma COMMAND`\n')
this.log('Or you can list available db commands with with:')
this.log('\n `npm run blitz db --help` or `yarn blitz db --help`\n')
this.log("\nUh oh, Blitz does not support that command.")
this.log("You can try running a prisma command directly with:")
this.log("\n `npm run prisma COMMAND` or `yarn prisma COMMAND`\n")
this.log("Or you can list available db commands with with:")
this.log("\n `npm run blitz db --help` or `yarn blitz db --help`\n")
}
}
}

View File

@@ -1,40 +1,40 @@
import {Command} from '../command'
import {flags} from '@oclif/command'
import * as fs from 'fs'
import * as path from 'path'
import enquirer from 'enquirer'
import _pluralize from 'pluralize'
import {Command} from "../command"
import {flags} from "@oclif/command"
import * as fs from "fs"
import * as path from "path"
import enquirer from "enquirer"
import _pluralize from "pluralize"
import {
PageGenerator,
MutationGenerator,
QueryGenerator,
FormGenerator,
ModelGenerator,
} from '@blitzjs/generator'
import {PromptAbortedError} from '../errors/prompt-aborted'
import {log} from '@blitzjs/display'
import camelCase from 'camelcase'
import pkgDir from 'pkg-dir'
const debug = require('debug')('blitz:generate')
} from "@blitzjs/generator"
import {PromptAbortedError} from "../errors/prompt-aborted"
import {log} from "@blitzjs/display"
import camelCase from "camelcase"
import pkgDir from "pkg-dir"
const debug = require("debug")("blitz:generate")
const pascalCase = (str: string) => camelCase(str, {pascalCase: true})
const projectRoot = pkgDir.sync() || process.cwd()
const isTypescript = fs.existsSync(path.join(projectRoot, 'tsconfig.json'))
const isTypescript = fs.existsSync(path.join(projectRoot, "tsconfig.json"))
enum ResourceType {
All = 'all',
Crud = 'crud',
Model = 'model',
Mutations = 'mutations',
Pages = 'pages',
Queries = 'queries',
Resource = 'resource',
All = "all",
Crud = "crud",
Model = "model",
Mutations = "mutations",
Pages = "pages",
Queries = "queries",
Resource = "resource",
}
interface Flags {
context?: string
'dry-run'?: boolean
"dry-run"?: boolean
parent?: string
}
@@ -51,21 +51,27 @@ function singular(input: string): string {
return _pluralize.isSingular(input) ? input : _pluralize.singular(input)
}
function modelName(input: string = '') {
function modelName(input: string = "") {
return camelCase(singular(input))
}
function modelNames(input: string = '') {
function modelNames(input: string = "") {
return camelCase(pluralize(input))
}
function ModelName(input: string = '') {
function ModelName(input: string = "") {
return pascalCase(singular(input))
}
function ModelNames(input: string = '') {
function ModelNames(input: string = "") {
return pascalCase(pluralize(input))
}
const generatorMap = {
[ResourceType.All]: [ModelGenerator, PageGenerator, FormGenerator, QueryGenerator, MutationGenerator],
[ResourceType.All]: [
ModelGenerator,
PageGenerator,
FormGenerator,
QueryGenerator,
MutationGenerator,
],
[ResourceType.Crud]: [MutationGenerator, QueryGenerator],
[ResourceType.Model]: [ModelGenerator],
[ResourceType.Mutations]: [MutationGenerator],
@@ -75,38 +81,38 @@ const generatorMap = {
}
export class Generate extends Command {
static description = 'Generate new files for your Blitz project'
static aliases = ['g']
static description = "Generate new files for your Blitz project"
static aliases = ["g"]
static strict = false
static args = [
{
name: 'type',
name: "type",
required: true,
description: 'What files to generate',
description: "What files to generate",
options: Object.keys(generatorMap).map((s) => s.toLowerCase()),
},
{
name: 'model',
name: "model",
required: true,
description: 'The name of your model, like "user". Can be singular or plural - same result',
},
]
static flags = {
help: flags.help({char: 'h'}),
help: flags.help({char: "h"}),
context: flags.string({
char: 'c',
char: "c",
description:
"Provide a context folder within which we'll place the generated files for better code organization. You can also supply this in the name of the model to be generated (e.g. `blitz generate query admin/projects`). Combining the `--context` flags and supplying context via the model name in the same command is not supported.",
}),
parent: flags.string({
char: 'p',
char: "p",
description:
"Specify a parent model to be used for generating nested routes for dependent data when generating pages, or to create hierarchical validation in queries and mutations. The code will be generated with the nested data model in mind. Most often this should be used in conjunction with 'blitz generate all'",
}),
'dry-run': flags.boolean({
char: 'd',
description: 'Show what files will be created without writing them to disk',
"dry-run": flags.boolean({
char: "d",
description: "Show what files will be created without writing them to disk",
}),
}
@@ -146,9 +152,9 @@ export class Generate extends Command {
async promptForTargetDirectory(paths: string[]): Promise<string> {
return enquirer
.prompt<{directory: string}>({
name: 'directory',
type: 'select',
message: 'Please select a target directory:',
name: "directory",
type: "select",
message: "Please select a target directory:",
choices: paths,
})
.then((resp) => resp.directory)
@@ -157,18 +163,18 @@ export class Generate extends Command {
async genericConfirmPrompt(message: string): Promise<boolean> {
return enquirer
.prompt<{continue: string}>({
name: 'continue',
type: 'select',
name: "continue",
type: "select",
message: message,
choices: ['Yes', 'No'],
choices: ["Yes", "No"],
})
.then((resp) => resp.continue === 'Yes')
.then((resp) => resp.continue === "Yes")
}
async handleNoContext(message: string): Promise<void> {
const shouldCreateNewRoot = await this.genericConfirmPrompt(message)
if (!shouldCreateNewRoot) {
log.error('Could not determine proper location for files. Aborting.')
log.error("Could not determine proper location for files. Aborting.")
this.exit(0)
}
}
@@ -199,8 +205,8 @@ export class Generate extends Command {
async run() {
const {args, argv, flags}: {args: Args; argv: string[]; flags: Flags} = this.parse(Generate)
debug('args: ', args)
debug('flags: ', flags)
debug("args: ", args)
debug("flags: ", flags)
try {
const {model, context} = this.getModelNameAndContext(args.model, flags.context)
@@ -210,7 +216,7 @@ export class Generate extends Command {
for (const GeneratorClass of generators) {
const generator = new GeneratorClass({
destinationRoot: path.resolve(),
extraArgs: argv.slice(2).filter((arg) => !arg.startsWith('-')),
extraArgs: argv.slice(2).filter((arg) => !arg.startsWith("-")),
modelName: singularRootContext,
modelNames: modelNames(singularRootContext),
ModelName: ModelName(singularRootContext),
@@ -219,14 +225,14 @@ export class Generate extends Command {
parentModels: modelNames(flags.parent),
ParentModel: ModelName(flags.parent),
ParentModels: ModelNames(flags.parent),
dryRun: flags['dry-run'],
dryRun: flags["dry-run"],
context: context,
useTs: isTypescript,
})
await generator.run()
}
console.log(' ') // new line
console.log(" ") // new line
} catch (err) {
if (err instanceof PromptAbortedError) this.exit(0)

View File

@@ -1,17 +1,17 @@
import {Command, flags} from '@oclif/command'
import {Command, flags} from "@oclif/command"
import Help from '@oclif/plugin-help'
import Help from "@oclif/plugin-help"
export class HelpCommand extends Command {
static description = 'display help for <%= config.bin %>'
static description = "display help for <%= config.bin %>"
static aliases = ['h']
static aliases = ["h"]
static flags = {
all: flags.boolean({description: 'see all commands in CLI'}),
all: flags.boolean({description: "see all commands in CLI"}),
}
static args = [{name: 'command', required: false, description: 'command to show help for'}]
static args = [{name: "command", required: false, description: "command to show help for"}]
static strict = false

View File

@@ -1,16 +1,16 @@
import {Command} from '../command'
import * as path from 'path'
import {Installer} from '@blitzjs/installer'
import _got from 'got'
import {log} from '@blitzjs/display'
import {dedent} from '../utils/dedent'
import {Stream} from 'stream'
import {promisify} from 'util'
import tar from 'tar'
import {mkdirSync, readFileSync, existsSync} from 'fs-extra'
import rimraf from 'rimraf'
import spawn from 'cross-spawn'
import * as os from 'os'
import {Command} from "../command"
import * as path from "path"
import {Installer} from "@blitzjs/installer"
import _got from "got"
import {log} from "@blitzjs/display"
import {dedent} from "../utils/dedent"
import {Stream} from "stream"
import {promisify} from "util"
import tar from "tar"
import {mkdirSync, readFileSync, existsSync} from "fs-extra"
import rimraf from "rimraf"
import spawn from "cross-spawn"
import * as os from "os"
const pipeline = promisify(Stream.pipeline)
@@ -27,12 +27,12 @@ async function isUrlValid(url: string) {
}
function requireJSON(file: string) {
return JSON.parse(readFileSync(file).toString('utf-8'))
return JSON.parse(readFileSync(file).toString("utf-8"))
}
const GH_ROOT = 'https://github.com/'
const API_ROOT = 'https://api.github.com/repos/'
const CODE_ROOT = 'https://codeload.github.com/'
const GH_ROOT = "https://github.com/"
const API_ROOT = "https://api.github.com/repos/"
const CODE_ROOT = "https://codeload.github.com/"
export enum RecipeLocation {
Local,
@@ -46,21 +46,22 @@ interface RecipeMeta {
}
export class Install extends Command {
static description = 'Install a third-party package into your Blitz app'
static aliases = ['i']
static description = "Install a third-party package into your Blitz app"
static aliases = ["i"]
static strict = false
static hidden = true
static args = [
{
name: 'recipe',
name: "recipe",
required: true,
description:
'Name of a Blitz recipe from @blitzjs/blitz/recipes, or a file path to a local recipe definition',
"Name of a Blitz recipe from @blitzjs/blitz/recipes, or a file path to a local recipe definition",
},
{
name: 'recipe-flags',
description: 'A list of flags to pass to the recipe. Blitz will only parse these in the form key=value',
name: "recipe-flags",
description:
"A list of flags to pass to the recipe. Blitz will only parse these in the form key=value",
},
]
@@ -84,7 +85,9 @@ export class Install extends Command {
repoUrl = `${GH_ROOT}${recipeArg}`
break
default:
throw new Error('should be impossible, the 3 cases are the only way to get into this switch')
throw new Error(
"should be impossible, the 3 cases are the only way to get into this switch",
)
}
return {
path: repoUrl,
@@ -107,18 +110,22 @@ export class Install extends Command {
* @param repoFullName username and repository name in the form {{user}}/{{repo}}
* @param defaultBranch the name of the repository's default branch
*/
async cloneRepo(repoFullName: string, defaultBranch: string, subdirectory?: string): Promise<string> {
const recipeDir = path.join(os.tmpdir(), `blitz-recipe-${repoFullName.replace('/', '-')}`)
async cloneRepo(
repoFullName: string,
defaultBranch: string,
subdirectory?: string,
): Promise<string> {
const recipeDir = path.join(os.tmpdir(), `blitz-recipe-${repoFullName.replace("/", "-")}`)
// clean up from previous run in case of error
rimraf.sync(recipeDir)
mkdirSync(recipeDir)
process.chdir(recipeDir)
const repoName = repoFullName.split('/')[1]
const repoName = repoFullName.split("/")[1]
// `tar` top-level filder is `${repoName}-${defaultBranch}`, and then we want to get our recipe path
// within that folder
const extractPath = subdirectory ? [`${repoName}-${defaultBranch}/${subdirectory}`] : undefined
const depth = subdirectory ? subdirectory.split('/').length + 1 : 1
const depth = subdirectory ? subdirectory.split("/").length + 1 : 1
await pipeline(
_got.stream(`${CODE_ROOT}${repoFullName}/tar.gz/${defaultBranch}`),
tar.extract({strip: depth}, extractPath),
@@ -132,8 +139,8 @@ export class Install extends Command {
const recipeArgs = this.argv.slice(1).reduce(
(acc, arg) => ({
...acc,
[arg.split('=')[0].replace(/--/g, '')]: arg.split('=')[1]
? JSON.parse(`"${arg.split('=')[1]}"`)
[arg.split("=")[0].replace(/--/g, "")]: arg.split("=")[1]
? JSON.parse(`"${arg.split("=")[1]}"`)
: true, // if no value is provided, assume it's a boolean flag
}),
{},
@@ -143,7 +150,7 @@ export class Install extends Command {
async run() {
const {args} = this.parse(Install)
const pkgManager = existsSync(path.resolve('yarn.lock')) ? 'yarn' : 'npm'
const pkgManager = existsSync(path.resolve("yarn.lock")) ? "yarn" : "npm"
const originalCwd = process.cwd()
const recipeInfo = this.normalizeRecipePath(args.recipe)
@@ -168,14 +175,14 @@ export class Install extends Command {
)
spinner.stop()
spinner = log.spinner('Installing package.json dependencies').start()
spinner = log.spinner("Installing package.json dependencies").start()
await new Promise((resolve) => {
const installProcess = spawn(pkgManager, ['install'])
installProcess.on('exit', resolve)
const installProcess = spawn(pkgManager, ["install"])
installProcess.on("exit", resolve)
})
spinner.stop()
const recipePackageMain = requireJSON('./package.json').main
const recipePackageMain = requireJSON("./package.json").main
const recipeEntry = path.resolve(recipePackageMain)
process.chdir(originalCwd)

View File

@@ -1,56 +1,58 @@
import * as path from 'path'
import {flags} from '@oclif/command'
import {Command} from '../command'
import {AppGenerator} from '@blitzjs/generator'
import chalk from 'chalk'
import hasbin from 'hasbin'
import {log} from '@blitzjs/display'
const debug = require('debug')('blitz:new')
import * as path from "path"
import {flags} from "@oclif/command"
import {Command} from "../command"
import {AppGenerator} from "@blitzjs/generator"
import chalk from "chalk"
import hasbin from "hasbin"
import {log} from "@blitzjs/display"
const debug = require("debug")("blitz:new")
import {PromptAbortedError} from '../errors/prompt-aborted'
import {PromptAbortedError} from "../errors/prompt-aborted"
export interface Flags {
ts: boolean
yarn: boolean
'skip-install': boolean
"skip-install": boolean
}
export class New extends Command {
static description = 'Create a new Blitz project'
static description = "Create a new Blitz project"
static args = [
{
name: 'name',
name: "name",
required: true,
description: 'name of your new project',
description: "name of your new project",
},
]
static flags = {
help: flags.help({char: 'h'}),
help: flags.help({char: "h"}),
js: flags.boolean({
description: 'Generates a JS project. TypeScript is the default unless you add this flag.',
description: "Generates a JS project. TypeScript is the default unless you add this flag.",
default: false,
hidden: true,
}),
npm: flags.boolean({
description: 'Use npm as the package manager. Yarn is the default if installed',
default: !hasbin.sync('yarn'),
description: "Use npm as the package manager. Yarn is the default if installed",
default: !hasbin.sync("yarn"),
allowNo: true,
}),
'skip-install': flags.boolean({
description: 'Skip package installation',
"skip-install": flags.boolean({
description: "Skip package installation",
hidden: true,
default: false,
allowNo: true,
}),
'dry-run': flags.boolean({description: 'show what files will be created without writing them to disk'}),
"dry-run": flags.boolean({
description: "show what files will be created without writing them to disk",
}),
}
async run() {
const {args, flags} = this.parse(New)
debug('args: ', args)
debug('flags: ', flags)
debug("args: ", args)
debug("flags: ", flags)
const destinationRoot = path.resolve(args.name)
const appName = path.basename(destinationRoot)
@@ -58,17 +60,17 @@ export class New extends Command {
const generator = new AppGenerator({
destinationRoot,
appName,
dryRun: flags['dry-run'],
dryRun: flags["dry-run"],
useTs: !flags.js,
yarn: !flags.npm,
version: this.config.version,
skipInstall: flags['skip-install'],
skipInstall: flags["skip-install"],
})
try {
this.log('\n' + log.withBrand('Hang tight while we set up your new Blitz app!') + '\n')
this.log("\n" + log.withBrand("Hang tight while we set up your new Blitz app!") + "\n")
await generator.run()
this.log('\n' + log.withBrand('Your new Blitz app is ready! Next steps:') + '\n')
this.log("\n" + log.withBrand("Your new Blitz app is ready! Next steps:") + "\n")
this.log(chalk.yellow(` 1. cd ${args.name}`))
this.log(chalk.yellow(` 2. blitz start`))
this.log(chalk.yellow(` 3. You create new pages by placing components inside app/pages/\n`))

View File

@@ -1,25 +1,25 @@
import {Command, flags} from '@oclif/command'
import {dev, prod} from '@blitzjs/server'
import {Command, flags} from "@oclif/command"
import {dev, prod} from "@blitzjs/server"
import {runPrismaGeneration} from './db'
import {runPrismaGeneration} from "./db"
export class Start extends Command {
static description = 'Start a development server'
static aliases = ['s']
static description = "Start a development server"
static aliases = ["s"]
static flags = {
production: flags.boolean({
description: 'Create and start a production server',
description: "Create and start a production server",
}),
port: flags.integer({
char: 'p',
description: 'Set port number',
char: "p",
description: "Set port number",
default: 3000,
}),
hostname: flags.string({
char: 'H',
description: 'Set server hostname',
default: 'localhost',
char: "H",
description: "Set server hostname",
default: "localhost",
}),
}

View File

@@ -1,28 +1,28 @@
import {spawn} from 'cross-spawn'
import {Command} from '@oclif/command'
import hasYarn from 'has-yarn'
import {spawn} from "cross-spawn"
import {Command} from "@oclif/command"
import hasYarn from "has-yarn"
export class Test extends Command {
static description = 'Run project tests'
static aliases = ['t']
static description = "Run project tests"
static aliases = ["t"]
static args = [
{
name: 'watch',
description: 'Run test:watch',
name: "watch",
description: "Run test:watch",
},
]
async run() {
const {args} = this.parse(Test)
let watchMode: boolean = false
const watch = args['watch']
const watch = args["watch"]
if (watch) {
watchMode = watch === 'watch' || watch === 'w'
watchMode = watch === "watch" || watch === "w"
}
const packageManager = hasYarn() ? 'yarn' : 'npm'
const packageManager = hasYarn() ? "yarn" : "npm"
if (watchMode) spawn(packageManager, ['test:watch'], {stdio: 'inherit'})
else spawn(packageManager, ['test'], {stdio: 'inherit'})
if (watchMode) spawn(packageManager, ["test:watch"], {stdio: "inherit"})
else spawn(packageManager, ["test"], {stdio: "inherit"})
}
}

View File

@@ -1,5 +1,5 @@
export class PromptAbortedError extends Error {
constructor() {
super('Prompt aborted')
super("Prompt aborted")
}
}

View File

@@ -1,11 +1,11 @@
import {run as oclifRun} from '@oclif/command'
import {run as oclifRun} from "@oclif/command"
// Load the .env environment variable so it's available for all commands
require('dotenv').config()
require("dotenv").config()
export function run() {
oclifRun()
.then(require('@oclif/command/flush'))
.then(require("@oclif/command/flush"))
// @ts-ignore (TS complains about using `catch`)
.catch(require('@oclif/errors/handle'))
.catch(require("@oclif/errors/handle"))
}

View File

@@ -1,8 +1,8 @@
export function dedent(strings: TemplateStringsArray, ...args: any[]) {
return strings
.map((str, idx) => str + String(args[idx] || ''))
.join('')
.split('\n')
.map((str, idx) => str + String(args[idx] || ""))
.join("")
.split("\n")
.map((line) => line.trim())
.join('\n')
.join("\n")
}

View File

@@ -1,3 +1,3 @@
import pkgDir from 'pkg-dir'
import pkgDir from "pkg-dir"
export const projectRoot = pkgDir.sync() || process.cwd()

View File

@@ -1,6 +1,6 @@
import {readJSON} from 'fs-extra'
import pkgDir from 'pkg-dir'
import {resolve} from 'path'
import {readJSON} from "fs-extra"
import pkgDir from "pkg-dir"
import {resolve} from "path"
export enum IsBlitzRootError {
NotBlitz,
@@ -9,13 +9,13 @@ export enum IsBlitzRootError {
}
const checkParent = async (): Promise<false | number> => {
const rootDir = await pkgDir('./')
const rootDir = await pkgDir("./")
if (rootDir) {
const file = await readJSON(resolve(rootDir, 'package.json'))
const file = await readJSON(resolve(rootDir, "package.json"))
if (file && Object.keys(file.dependencies || {}).includes('blitz')) {
return process.cwd().slice(rootDir.length).split('/').length - 1
if (file && Object.keys(file.dependencies || {}).includes("blitz")) {
return process.cwd().slice(rootDir.length).split("/").length - 1
}
}
@@ -31,11 +31,15 @@ const checkParent = async (): Promise<false | number> => {
* badPackageJson -> an error occurred while reading local package.json
*/
export const isBlitzRoot = async (): Promise<{err: boolean; message?: IsBlitzRootError; depth?: number}> => {
export const isBlitzRoot = async (): Promise<{
err: boolean
message?: IsBlitzRootError
depth?: number
}> => {
try {
const local = await readJSON('./package.json')
const local = await readJSON("./package.json")
if (local) {
if (local.dependencies['blitz'] || local.devDependencies['blitz']) {
if (local.dependencies["blitz"] || local.devDependencies["blitz"]) {
return {err: false}
} else {
return {
@@ -47,7 +51,7 @@ export const isBlitzRoot = async (): Promise<{err: boolean; message?: IsBlitzRoo
return {err: true, message: IsBlitzRootError.BadPackageJson}
} catch (err) {
// No local package.json
if (err.code === 'ENOENT') {
if (err.code === "ENOENT") {
const out = await checkParent()
if (out === false) {

View File

@@ -1,9 +1,9 @@
import {REGISTER_INSTANCE} from 'ts-node'
import {REGISTER_INSTANCE} from "ts-node"
export const setupTsnode = () => {
if (!process[REGISTER_INSTANCE]) {
// During blitz interal dev, oclif automaticaly sets up ts-node so we have to check
require('ts-node').register({compilerOptions: {module: 'commonjs'}})
require("ts-node").register({compilerOptions: {module: "commonjs"}})
}
require('tsconfig-paths/register')
require("tsconfig-paths/register")
}

View File

@@ -1,12 +1,12 @@
import {Installer} from '@blitzjs/installer'
import {Installer} from "@blitzjs/installer"
// eslint-disable-next-line import/no-default-export
export default new Installer(
{
packageDescription: 'test package',
packageName: 'test',
packageOwner: 'blitz@blitzjs.com',
packageRepoLink: 'https://github.com/blitz-js/blitz',
packageDescription: "test package",
packageName: "test",
packageOwner: "blitz@blitzjs.com",
packageRepoLink: "https://github.com/blitz-js/blitz",
},
[],
)

View File

@@ -1,5 +1,5 @@
const build = jest.fn(() => {})
jest.mock('@blitzjs/server', () => ({build, resolveBinAsync: jest.fn()}))
jest.mock("@blitzjs/server", () => ({build, resolveBinAsync: jest.fn()}))
let onSpy: jest.Mock
const spawn = jest.fn(() => {
@@ -9,23 +9,23 @@ const spawn = jest.fn(() => {
return {on: onSpy}
})
jest.doMock('cross-spawn', () => ({spawn}))
jest.doMock("cross-spawn", () => ({spawn}))
import {Build} from '../../src/commands/build'
import {resolve} from 'path'
import {Build} from "../../src/commands/build"
import {resolve} from "path"
describe('Build command', () => {
describe("Build command", () => {
beforeEach(() => {
jest.clearAllMocks()
})
const options = {
rootFolder: resolve(__dirname, '../../'),
rootFolder: resolve(__dirname, "../../"),
port: 3000,
hostname: 'localhost',
hostname: "localhost",
}
it('runs the build script', async () => {
it("runs the build script", async () => {
await Build.run([])
expect(build).toBeCalledWith(options, Promise.resolve())
})

View File

@@ -1,12 +1,12 @@
import {Console} from '../../src/commands/console'
import {Console} from "../../src/commands/console"
import * as repl from '@blitzjs/repl'
import * as db from '../../src/commands/db'
import * as repl from "@blitzjs/repl"
import * as db from "../../src/commands/db"
jest.spyOn(global.console, 'log').mockImplementation()
jest.spyOn(global.console, "log").mockImplementation()
jest.mock(
'@blitzjs/server',
"@blitzjs/server",
jest.fn(() => {
return {
log: {
@@ -23,12 +23,12 @@ jest.mock(
jest.mock(`${process.cwd()}/package.json`, () => ({
dependencies: {
ramda: '1.0.0',
ramda: "1.0.0",
},
}))
jest.mock(
'@blitzjs/repl',
"@blitzjs/repl",
jest.fn(() => {
return {
runRepl: jest.fn(),
@@ -37,7 +37,7 @@ jest.mock(
)
jest.mock(
'../../src/commands/db',
"../../src/commands/db",
jest.fn(() => {
return {
runPrismaGeneration: jest.fn(),
@@ -45,27 +45,27 @@ jest.mock(
}),
)
describe('Console command', () => {
describe("Console command", () => {
beforeEach(() => {
jest.resetAllMocks()
})
it('runs PrismaGeneration', async () => {
it("runs PrismaGeneration", async () => {
await Console.prototype.run()
expect(db.runPrismaGeneration).toHaveBeenCalled()
})
it('runs PrismaGeneration with silent allowed', async () => {
it("runs PrismaGeneration with silent allowed", async () => {
await Console.prototype.run()
expect(db.runPrismaGeneration).toHaveBeenCalledWith({silent: true})
})
it('runs repl', async () => {
it("runs repl", async () => {
await Console.prototype.run()
expect(repl.runRepl).toHaveBeenCalled()
})
it('runs repl with replOptions', async () => {
it("runs repl with replOptions", async () => {
await Console.prototype.run()
expect(repl.runRepl).toHaveBeenCalledWith(Console.replOptions)
})

View File

@@ -1,5 +1,5 @@
import * as path from 'path'
import {resolveBinAsync} from '@blitzjs/server'
import * as path from "path"
import {resolveBinAsync} from "@blitzjs/server"
let onSpy: jest.Mock
const spawn = jest.fn(() => {
@@ -9,37 +9,37 @@ const spawn = jest.fn(() => {
return {on: onSpy}
})
jest.doMock('cross-spawn', () => ({spawn}))
jest.doMock("cross-spawn", () => ({spawn}))
import {Db} from '../../src/commands/db'
import {Db} from "../../src/commands/db"
let schemaArg: string
let prismaBin: string
let migrateSaveParams: any[]
let migrateUpParams: any[]
beforeAll(async () => {
schemaArg = `--schema=${path.join(process.cwd(), 'db', 'schema.prisma')}`
prismaBin = await resolveBinAsync('@prisma/cli', 'prisma')
schemaArg = `--schema=${path.join(process.cwd(), "db", "schema.prisma")}`
prismaBin = await resolveBinAsync("@prisma/cli", "prisma")
migrateSaveParams = [
prismaBin,
['migrate', 'save', schemaArg, '--create-db', '--experimental'],
{stdio: 'inherit'},
["migrate", "save", schemaArg, "--create-db", "--experimental"],
{stdio: "inherit"},
]
migrateUpParams = [
prismaBin,
['migrate', 'up', schemaArg, '--create-db', '--experimental'],
{stdio: 'inherit'},
["migrate", "up", schemaArg, "--create-db", "--experimental"],
{stdio: "inherit"},
]
})
describe('Db command', () => {
describe("Db command", () => {
beforeEach(() => {
jest.clearAllMocks()
})
afterEach(() => {
process.env.NODE_ENV = 'test'
process.env.NODE_ENV = "test"
})
function expectDbMigrateOutcome() {
@@ -61,42 +61,42 @@ describe('Db command', () => {
expect(spawn).toBeCalledWith(...migrateUpParams)
}
it('runs db migrate', async () => {
await Db.run(['migrate'])
it("runs db migrate", async () => {
await Db.run(["migrate"])
expectDbMigrateOutcome()
})
it('runs db migrate in the production environment.', async () => {
process.env.NODE_ENV = 'production'
await Db.run(['migrate'])
it("runs db migrate in the production environment.", async () => {
process.env.NODE_ENV = "production"
await Db.run(["migrate"])
expectProductionDbMigrateOutcome()
})
it('runs db migrate (alias)', async () => {
await Db.run(['m'])
it("runs db migrate (alias)", async () => {
await Db.run(["m"])
expectDbMigrateOutcome()
})
it('runs db migrate (alias) in the production environment.', async () => {
process.env.NODE_ENV = 'production'
await Db.run(['m'])
it("runs db migrate (alias) in the production environment.", async () => {
process.env.NODE_ENV = "production"
await Db.run(["m"])
expectProductionDbMigrateOutcome()
})
it('runs db introspect', async () => {
await Db.run(['introspect'])
it("runs db introspect", async () => {
await Db.run(["introspect"])
expect(spawn).toHaveBeenCalled()
})
it('runs db studio', async () => {
await Db.run(['studio'])
it("runs db studio", async () => {
await Db.run(["studio"])
expect(spawn).toHaveBeenCalled()
})
it('does not run db in case of invalid command', async () => {
await Db.run(['invalid'])
it("does not run db in case of invalid command", async () => {
await Db.run(["invalid"])
expect(spawn.mock.calls.length).toBe(0)
})

View File

@@ -1,48 +1,48 @@
import {Generate} from '../../src/commands/generate'
import * as path from 'path'
import {Generate} from "../../src/commands/generate"
import * as path from "path"
describe('`generate` command', () => {
describe('#getModelNameAndContext', () => {
it('properly extracts context from arguments', () => {
describe("`generate` command", () => {
describe("#getModelNameAndContext", () => {
it("properly extracts context from arguments", () => {
const getModelNameAndContext = Generate.prototype.getModelNameAndContext
expect(getModelNameAndContext('admin/tasks')).toEqual({
model: 'tasks',
context: 'admin',
expect(getModelNameAndContext("admin/tasks")).toEqual({
model: "tasks",
context: "admin",
})
expect(getModelNameAndContext('admin/projects/tasks')).toEqual({
model: 'tasks',
context: path.join('admin', 'projects'),
expect(getModelNameAndContext("admin/projects/tasks")).toEqual({
model: "tasks",
context: path.join("admin", "projects"),
})
// this should fail on windows if generic filesystem-specific code makes it in
expect(getModelNameAndContext('admin\\projects\\tasks')).toEqual({
model: 'tasks',
context: path.join('admin', 'projects'),
expect(getModelNameAndContext("admin\\projects\\tasks")).toEqual({
model: "tasks",
context: path.join("admin", "projects"),
})
})
describe('when passing context', () => {
it('returns both model and context in their own field', () => {
describe("when passing context", () => {
it("returns both model and context in their own field", () => {
const getModelNameAndContext = Generate.prototype.getModelNameAndContext
expect(getModelNameAndContext('tasks', 'admin')).toEqual({
model: 'tasks',
context: 'admin',
expect(getModelNameAndContext("tasks", "admin")).toEqual({
model: "tasks",
context: "admin",
})
})
it('returns context undefined if not set', () => {
it("returns context undefined if not set", () => {
const getModelNameAndContext = Generate.prototype.getModelNameAndContext
expect(getModelNameAndContext('tasks')).toEqual({
model: 'tasks',
expect(getModelNameAndContext("tasks")).toEqual({
model: "tasks",
context: undefined,
})
})
it('returns context undefined if empty string', () => {
it("returns context undefined if empty string", () => {
const getModelNameAndContext = Generate.prototype.getModelNameAndContext
expect(getModelNameAndContext('tasks', '')).toEqual({
model: 'tasks',
expect(getModelNameAndContext("tasks", "")).toEqual({
model: "tasks",
context: undefined,
})
})

View File

@@ -1,33 +1,33 @@
import {Install, RecipeLocation} from '../../src/commands/install'
import * as path from 'path'
import tempInstaller from '../__fixtures__/installer'
import {Install, RecipeLocation} from "../../src/commands/install"
import * as path from "path"
import tempInstaller from "../__fixtures__/installer"
jest.mock('../__fixtures__/installer')
jest.mock('@blitzjs/installer')
jest.mock("../__fixtures__/installer")
jest.mock("@blitzjs/installer")
describe('`install` command', () => {
describe("`install` command", () => {
afterAll(() => {
jest.resetAllMocks()
})
it('runs local installer', async (done) => {
await Install.run([path.resolve(__dirname, '../__fixtures__/installer')])
it("runs local installer", async (done) => {
await Install.run([path.resolve(__dirname, "../__fixtures__/installer")])
expect(tempInstaller.run).toHaveBeenCalledWith({})
done()
})
it('properly parses remote installer args', () => {
it("properly parses remote installer args", () => {
const normalizePath = Install.prototype.normalizeRecipePath
expect(normalizePath('test-installer')).toEqual({
path: 'https://github.com/blitz-js/blitz',
subdirectory: 'recipes/test-installer',
expect(normalizePath("test-installer")).toEqual({
path: "https://github.com/blitz-js/blitz",
subdirectory: "recipes/test-installer",
location: RecipeLocation.Remote,
})
expect(normalizePath('user/test-installer')).toEqual({
path: 'https://github.com/user/test-installer',
expect(normalizePath("user/test-installer")).toEqual({
path: "https://github.com/user/test-installer",
location: RecipeLocation.Remote,
})
expect(normalizePath('https://github.com/user/test-installer')).toEqual({
path: 'https://github.com/user/test-installer',
expect(normalizePath("https://github.com/user/test-installer")).toEqual({
path: "https://github.com/user/test-installer",
location: RecipeLocation.Remote,
})
})

View File

@@ -1,22 +1,22 @@
import {New} from '../../src/commands/new'
import {getLatestVersion} from '@blitzjs/generator/src/utils/get-latest-version'
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'
import fetch from 'node-fetch'
import nock from 'nock'
import rimraf from 'rimraf'
import {New} from "../../src/commands/new"
import {getLatestVersion} from "@blitzjs/generator/src/utils/get-latest-version"
import * as fs from "fs"
import * as path from "path"
import * as os from "os"
import fetch from "node-fetch"
import nock from "nock"
import rimraf from "rimraf"
jest.setTimeout(120 * 1000)
const blitzCliPackageJson = require('../../package.json')
const blitzCliPackageJson = require("../../package.json")
async function getBlitzDistTags() {
const response = await fetch('https://registry.npmjs.org/-/package/blitz/dist-tags')
const response = await fetch("https://registry.npmjs.org/-/package/blitz/dist-tags")
return await response.json()
}
describe('`new` command', () => {
describe('when scaffolding new project', () => {
describe("`new` command", () => {
describe("when scaffolding new project", () => {
jest.setTimeout(120 * 1000)
async function whileStayingInCWD(task: () => PromiseLike<void>) {
@@ -27,18 +27,18 @@ describe('`new` command', () => {
async function withNewApp(test: (dirName: string, packageJson: any) => Promise<void> | void) {
function makeTempDir() {
const tmpDirPath = path.join(os.tmpdir(), 'blitzjs-test-')
const tmpDirPath = path.join(os.tmpdir(), "blitzjs-test-")
return fs.mkdtempSync(tmpDirPath)
}
const tempDir = makeTempDir()
await whileStayingInCWD(() => New.run([tempDir, '--skip-install']))
await whileStayingInCWD(() => New.run([tempDir, "--skip-install"]))
const packageJsonFile = fs.readFileSync(path.join(tempDir, 'package.json'), {
encoding: 'utf8',
flag: 'r',
const packageJsonFile = fs.readFileSync(path.join(tempDir, "package.json"), {
encoding: "utf8",
flag: "r",
})
const packageJson = JSON.parse(packageJsonFile)
@@ -47,49 +47,52 @@ describe('`new` command', () => {
rimraf.sync(tempDir)
}
it('pins Blitz to the current version', async () =>
it("pins Blitz to the current version", async () =>
await withNewApp(async (_, packageJson) => {
const {
dependencies: {blitz: blitzVersion},
} = packageJson
const {latest, canary} = await getBlitzDistTags()
if (blitzCliPackageJson.version.includes('canary')) {
if (blitzCliPackageJson.version.includes("canary")) {
expect(blitzVersion).toEqual(canary)
} else {
expect(blitzVersion).toEqual(latest)
}
}))
it('fetches latest version from template', async () => {
const expectedVersion = '3.0.0'
const templatePackage = {name: 'eslint-plugin-react-hooks', version: '3.x'}
it("fetches latest version from template", async () => {
const expectedVersion = "3.0.0"
const templatePackage = {name: "eslint-plugin-react-hooks", version: "3.x"}
const scope = nock('https://registry.npmjs.org')
const scope = nock("https://registry.npmjs.org")
scope
.get(`/${templatePackage.name}`)
.reply(200, {versions: {'4.0.0': {}, '3.0.0': {}}})
.reply(200, {versions: {"4.0.0": {}, "3.0.0": {}}})
.persist()
scope
.get(`/-/package/${templatePackage.name}/dist-tags`)
.reply(200, {
latest: '4.0.0',
latest: "4.0.0",
})
.persist()
const {value: latestVersion} = await getLatestVersion(templatePackage.name, templatePackage.version)
const {value: latestVersion} = await getLatestVersion(
templatePackage.name,
templatePackage.version,
)
expect(latestVersion).toBe(expectedVersion)
})
describe('with network trouble', () => {
it('uses template versions', async () => {
nock('https://registry.npmjs.org').get(/.*/).reply(500).persist()
describe("with network trouble", () => {
it("uses template versions", async () => {
nock("https://registry.npmjs.org").get(/.*/).reply(500).persist()
await withNewApp(async (_, packageJson) => {
const {dependencies} = packageJson
expect(dependencies.blitz).toBe('latest')
expect(dependencies.blitz).toBe("latest")
})
nock.restore()

View File

@@ -1,7 +1,7 @@
const dev = jest.fn(() => {})
const prod = jest.fn(() => {})
jest.mock('@blitzjs/server', () => ({dev, prod, resolveBinAsync: jest.fn()}))
jest.mock("@blitzjs/server", () => ({dev, prod, resolveBinAsync: jest.fn()}))
let onSpy: jest.Mock
const spawn = jest.fn(() => {
@@ -11,29 +11,29 @@ const spawn = jest.fn(() => {
return {on: onSpy}
})
jest.doMock('cross-spawn', () => ({spawn}))
jest.doMock("cross-spawn", () => ({spawn}))
import {Start} from '../../src/commands/start'
import {resolve} from 'path'
import {Start} from "../../src/commands/start"
import {resolve} from "path"
describe('Start command', () => {
describe("Start command", () => {
beforeEach(() => {
jest.clearAllMocks()
})
const options = {
rootFolder: resolve(__dirname, '../../'),
rootFolder: resolve(__dirname, "../../"),
port: 3000,
hostname: 'localhost',
hostname: "localhost",
}
it('runs the dev script', async () => {
it("runs the dev script", async () => {
await Start.run([])
expect(dev).toBeCalledWith(options, Promise.resolve())
})
it('runs the prod script when passed the production flag', async () => {
await Start.run(['--production'])
it("runs the prod script when passed the production flag", async () => {
await Start.run(["--production"])
expect(prod).toBeCalledWith(options, Promise.resolve())
})
})

View File

@@ -1,55 +1,55 @@
jest.mock('cross-spawn')
jest.mock('has-yarn')
jest.mock("cross-spawn")
jest.mock("has-yarn")
import crossSpawn from 'cross-spawn'
import hasYarn from 'has-yarn'
import {Test} from '../../src/commands/test'
import crossSpawn from "cross-spawn"
import hasYarn from "has-yarn"
import {Test} from "../../src/commands/test"
const testParams = [['test'], {stdio: 'inherit'}]
const testWatchParams = [['test:watch'], {stdio: 'inherit'}]
const testParams = [["test"], {stdio: "inherit"}]
const testWatchParams = [["test:watch"], {stdio: "inherit"}]
describe('Test command', () => {
describe("Test command", () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('runs yarn test script', async () => {
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
it("runs yarn test script", async () => {
jest.spyOn(hasYarn, "default").mockReturnValue(true)
await Test.run([])
expect(crossSpawn.spawn).toBeCalledWith('yarn', ...testParams)
expect(crossSpawn.spawn).toBeCalledWith("yarn", ...testParams)
})
it('runs npm test script', async () => {
jest.spyOn(hasYarn, 'default').mockReturnValue(false)
it("runs npm test script", async () => {
jest.spyOn(hasYarn, "default").mockReturnValue(false)
await Test.run([])
expect(crossSpawn.spawn).toBeCalledWith('npm', ...testParams)
expect(crossSpawn.spawn).toBeCalledWith("npm", ...testParams)
})
it('runs yarn test:watch script', async () => {
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
it("runs yarn test:watch script", async () => {
jest.spyOn(hasYarn, "default").mockReturnValue(true)
await Test.run(['watch'])
await Test.run(["watch"])
expect(crossSpawn.spawn).toBeCalledWith('yarn', ...testWatchParams)
expect(crossSpawn.spawn).toBeCalledWith("yarn", ...testWatchParams)
})
it('runs yarn test:watch script with alias', async () => {
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
it("runs yarn test:watch script with alias", async () => {
jest.spyOn(hasYarn, "default").mockReturnValue(true)
await Test.run(['w'])
await Test.run(["w"])
expect(crossSpawn.spawn).toBeCalledWith('yarn', ...testWatchParams)
expect(crossSpawn.spawn).toBeCalledWith("yarn", ...testWatchParams)
})
it('runs yarn test and ignores invalid argument', async () => {
jest.spyOn(hasYarn, 'default').mockReturnValue(true)
it("runs yarn test and ignores invalid argument", async () => {
jest.spyOn(hasYarn, "default").mockReturnValue(true)
await Test.run(['invalid'])
await Test.run(["invalid"])
expect(crossSpawn.spawn).toBeCalledWith('yarn', ...testParams)
expect(crossSpawn.spawn).toBeCalledWith("yarn", ...testParams)
})
})

View File

@@ -1,7 +1,7 @@
import {dedent} from '../../src/utils/dedent'
import {dedent} from "../../src/utils/dedent"
describe('dedent', () => {
it('strips leading spaces', () => {
expect(dedent` testing`).toBe('testing')
describe("dedent", () => {
it("strips leading spaces", () => {
expect(dedent` testing`).toBe("testing")
})
})

View File

@@ -1 +1 @@
declare module 'hasbin'
declare module "hasbin"

View File

@@ -1,12 +1,12 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
coverageReporters: ['json', 'lcov', 'text', 'clover'],
preset: "ts-jest",
testEnvironment: "node",
moduleFileExtensions: ["ts", "tsx", "js", "json"],
coverageReporters: ["json", "lcov", "text", "clover"],
// collectCoverage: !!`Boolean(process.env.CI)`,
collectCoverageFrom: ['src/**/*.ts'],
coveragePathIgnorePatterns: ['/templates/'],
modulePathIgnorePatterns: ['<rootDir>/tmp', '<rootDir>/dist'],
collectCoverageFrom: ["src/**/*.ts"],
coveragePathIgnorePatterns: ["/templates/"],
modulePathIgnorePatterns: ["<rootDir>/tmp", "<rootDir>/dist"],
// TODO enable threshold
// coverageThreshold: {
// global: {

View File

@@ -1,3 +1,3 @@
require('@testing-library/jest-dom')
require("@testing-library/jest-dom")
global.fetch = jest.fn(() => Promise.resolve({json: () => ({result: null, error: null})}))

View File

@@ -1,8 +1,8 @@
import pkgDir from 'pkg-dir'
import {join} from 'path'
import {existsSync} from 'fs'
import pkgDir from "pkg-dir"
import {join} from "path"
import {existsSync} from "fs"
const configFiles = ['next.config.js']
const configFiles = ["next.config.js"]
/**
* @param {boolean | undefined} reload - reimport config files to reset global cache
*/
@@ -19,7 +19,7 @@ export const getConfig = (reload?: boolean): Record<string, unknown> => {
const path = join(projectRoot, configFile)
const file = require(path)
let contents
if (typeof file === 'function') {
if (typeof file === "function") {
contents = file()
} else {
contents = file

View File

@@ -1,12 +1,12 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
coverageReporters: ['json', 'lcov', 'text', 'clover'],
preset: "ts-jest",
testEnvironment: "node",
moduleFileExtensions: ["ts", "tsx", "js", "json"],
coverageReporters: ["json", "lcov", "text", "clover"],
// collectCoverage: !!`Boolean(process.env.CI)`,
collectCoverageFrom: ['src/**/*.ts'],
coveragePathIgnorePatterns: ['/templates/'],
modulePathIgnorePatterns: ['<rootDir>/tmp', '<rootDir>/dist'],
collectCoverageFrom: ["src/**/*.ts"],
coveragePathIgnorePatterns: ["/templates/"],
modulePathIgnorePatterns: ["<rootDir>/tmp", "<rootDir>/dist"],
// TODO enable threshold
// coverageThreshold: {
// global: {

View File

@@ -1 +1 @@
require('@testing-library/jest-dom')
require("@testing-library/jest-dom")

View File

@@ -1,14 +1,14 @@
export * from './use-query'
export * from './use-paginated-query'
export * from './use-params'
export * from './use-infinite-query'
export * from './ssr-query'
export * from './rpc'
export * from './with-router'
export * from './use-router'
export * from './use-router-query'
export * from './middleware'
export * from './types'
export * from "./use-query"
export * from "./use-paginated-query"
export * from "./use-params"
export * from "./use-infinite-query"
export * from "./ssr-query"
export * from "./rpc"
export * from "./with-router"
export * from "./use-router"
export * from "./use-router-query"
export * from "./middleware"
export * from "./types"
// --------------------
// Exports from Next.js
@@ -22,15 +22,15 @@ export {
NextPage as BlitzPage,
NextApiRequest as BlitzApiRequest,
NextApiResponse as BlitzApiResponse,
} from 'next'
} from "next"
export {AppProps} from 'next/app'
export {AppProps} from "next/app"
export {default as Head} from 'next/head'
export {default as Head} from "next/head"
export {default as Link} from 'next/link'
export {default as Link} from "next/link"
export {Router} from 'next/router'
export {Router} from "next/router"
export {
default as Document,
@@ -40,8 +40,8 @@ export {
NextScript as BlitzScript,
DocumentContext,
DocumentInitialProps,
} from 'next/document'
} from "next/document"
export {default as dynamic} from 'next/dynamic'
export {default as dynamic} from "next/dynamic"
export {default as Error} from 'next/error'
export {default as Error} from "next/error"

View File

@@ -1,21 +1,21 @@
import {apiResolver} from 'next/dist/next-server/server/api-utils'
import http from 'http'
import listen from 'test-listen'
import fetch from 'isomorphic-unfetch'
import {apiResolver} from "next/dist/next-server/server/api-utils"
import http from "http"
import listen from "test-listen"
import fetch from "isomorphic-unfetch"
import {BlitzApiRequest, BlitzApiResponse} from '.'
import {Middleware, handleRequestWithMiddleware} from './middleware'
import {BlitzApiRequest, BlitzApiResponse} from "."
import {Middleware, handleRequestWithMiddleware} from "./middleware"
describe('handleRequestWithMiddleware', () => {
it('works without await', async () => {
describe("handleRequestWithMiddleware", () => {
it("works without await", async () => {
const middleware: Middleware[] = [
(_req, res, next) => {
res.status(201)
return next()
},
(_req, res, next) => {
res.setHeader('test', 'works')
res.json({a: 'b'})
res.setHeader("test", "works")
res.json({a: "b"})
return next()
},
]
@@ -23,11 +23,11 @@ describe('handleRequestWithMiddleware', () => {
await mockServer(middleware, async (url) => {
const res = await fetch(url)
expect(res.status).toBe(201)
expect(res.headers.get('test')).toBe('works')
expect(res.headers.get("test")).toBe("works")
})
})
it('works with await', async () => {
it("works with await", async () => {
const middleware: Middleware[] = [
async (_req, res, next) => {
res.status(201)
@@ -35,22 +35,22 @@ describe('handleRequestWithMiddleware', () => {
},
async (_req, res, next) => {
await next()
res.setHeader('test', 'works')
res.setHeader("test", "works")
},
]
await mockServer(middleware, async (url) => {
const res = await fetch(url)
expect(res.status).toBe(201)
expect(res.headers.get('test')).toBe('works')
expect(res.headers.get("test")).toBe("works")
})
})
it('works with flipped order', async () => {
it("works with flipped order", async () => {
const middleware: Middleware[] = [
async (_req, res, next) => {
await next()
res.setHeader('test', 'works')
res.setHeader("test", "works")
},
async (_req, res, next) => {
res.status(201)
@@ -61,16 +61,16 @@ describe('handleRequestWithMiddleware', () => {
await mockServer(middleware, async (url) => {
const res = await fetch(url)
expect(res.status).toBe(201)
expect(res.headers.get('test')).toBe('works')
expect(res.headers.get("test")).toBe("works")
})
})
it('middleware can throw', async () => {
it("middleware can throw", async () => {
console.log = jest.fn()
console.error = jest.fn()
const middleware: Middleware[] = [
(_req, _res, _next) => {
throw new Error('test')
throw new Error("test")
},
]
@@ -80,14 +80,14 @@ describe('handleRequestWithMiddleware', () => {
})
})
it('middleware can return error', async () => {
it("middleware can return error", async () => {
console.log = jest.fn()
const middleware: Middleware[] = [
(_req, _res, next) => {
return next(new Error('test'))
return next(new Error("test"))
},
(_req, _res, _next) => {
throw new Error('Remaining middleware should not run if previous has error')
throw new Error("Remaining middleware should not run if previous has error")
},
]
@@ -107,9 +107,9 @@ async function mockServer(middleware: Middleware[], callback: (url: string) => P
let server = http.createServer((req, res) =>
apiResolver(req, res, null, apiEndpoint, {
previewModeId: 'previewModeId',
previewModeEncryptionKey: 'previewModeEncryptionKey',
previewModeSigningKey: 'previewModeSigningKey',
previewModeId: "previewModeId",
previewModeEncryptionKey: "previewModeEncryptionKey",
previewModeSigningKey: "previewModeSigningKey",
}),
)

View File

@@ -1,8 +1,8 @@
import {BlitzApiRequest, BlitzApiResponse} from '.'
import {IncomingMessage, ServerResponse} from 'http'
import {EnhancedResolverModule} from './rpc'
import {getConfig} from '@blitzjs/config'
import {log} from '@blitzjs/display'
import {BlitzApiRequest, BlitzApiResponse} from "."
import {IncomingMessage, ServerResponse} from "http"
import {EnhancedResolverModule} from "./rpc"
import {getConfig} from "@blitzjs/config"
import {log} from "@blitzjs/display"
export interface MiddlewareRequest extends BlitzApiRequest {}
export interface MiddlewareResponse extends BlitzApiResponse {
@@ -71,9 +71,9 @@ export async function handleRequestWithMiddleware(
if (!res.writableFinished) {
res.statusCode = (error as any).code || (error as any).status || 500
res.end(error.message || res.statusCode.toString())
log.error('Error while processing the request:\n')
log.error("Error while processing the request:\n")
} else {
log.error('Error occured in middleware after the response was already sent to the browser:\n')
log.error("Error occured in middleware after the response was already sent to the browser:\n")
}
throw error
}
@@ -85,12 +85,12 @@ export async function handleRequestWithMiddleware(
// -------------------------------------------------------------------------------
export function compose(middleware: Middleware[]) {
if (!Array.isArray(middleware)) {
throw new TypeError('Middleware stack must be an array!')
throw new TypeError("Middleware stack must be an array!")
}
for (const handler of middleware) {
if (typeof handler !== 'function') {
throw new TypeError('Middleware must be composed of functions!')
if (typeof handler !== "function") {
throw new TypeError("Middleware must be composed of functions!")
}
}
@@ -100,7 +100,7 @@ export function compose(middleware: Middleware[]) {
let index = -1
function dispatch(i: number): Promise<void> {
if (i <= index) throw new Error('next() called multiple times')
if (i <= index) throw new Error("next() called multiple times")
index = i
let handler = middleware[i]

View File

@@ -1,21 +1,21 @@
import {deserializeError} from 'serialize-error'
import {queryCache} from 'react-query'
import {getQueryKey} from './utils'
import {ResolverModule, Middleware} from './middleware'
import {deserializeError} from "serialize-error"
import {queryCache} from "react-query"
import {getQueryKey} from "./utils"
import {ResolverModule, Middleware} from "./middleware"
type Options = {
fromQueryHook?: boolean
}
export async function executeRpcCall(url: string, params: any, opts: Options = {}) {
if (typeof window === 'undefined') return
if (typeof window === "undefined") return
const result = await window.fetch(url, {
method: 'POST',
credentials: 'same-origin',
method: "POST",
credentials: "same-origin",
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
redirect: 'follow',
redirect: "follow",
body: JSON.stringify({params}),
})
@@ -38,9 +38,9 @@ export async function executeRpcCall(url: string, params: any, opts: Options = {
}
executeRpcCall.warm = (url: string) => {
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
window.fetch(url, {method: 'HEAD'})
window.fetch(url, {method: "HEAD"})
}
}
@@ -68,7 +68,7 @@ export function getIsomorphicRpcHandler(
resolverName: string,
resolverType: string,
) {
const apiUrl = resolverPath.replace(/^app\/_resolvers/, '/api')
const apiUrl = resolverPath.replace(/^app\/_resolvers/, "/api")
const enhance = <T extends ResolverEnhancement>(fn: T): T => {
fn._meta = {
name: resolverName,
@@ -79,8 +79,9 @@ export function getIsomorphicRpcHandler(
return fn
}
if (typeof window !== 'undefined') {
let rpcFn: EnhancedRpcFunction = ((params: any, opts = {}) => executeRpcCall(apiUrl, params, opts)) as any
if (typeof window !== "undefined") {
let rpcFn: EnhancedRpcFunction = ((params: any, opts = {}) =>
executeRpcCall(apiUrl, params, opts)) as any
rpcFn = enhance(rpcFn)

View File

@@ -1,29 +1,29 @@
import http, {IncomingMessage, ServerResponse} from 'http'
import listen from 'test-listen'
import fetch from 'isomorphic-unfetch'
import delay from 'delay'
import http, {IncomingMessage, ServerResponse} from "http"
import listen from "test-listen"
import fetch from "isomorphic-unfetch"
import delay from "delay"
import {ssrQuery} from './ssr-query'
import {EnhancedResolverModule} from './rpc'
import {ssrQuery} from "./ssr-query"
import {EnhancedResolverModule} from "./rpc"
describe('ssrQuery', () => {
it('works without middleware', async () => {
describe("ssrQuery", () => {
it("works without middleware", async () => {
const resolverModule = (jest.fn().mockImplementation(async (input) => {
await delay(1)
return input
}) as unknown) as EnhancedResolverModule
resolverModule._meta = {
name: 'getTest',
type: 'query',
path: 'some/test/path',
apiUrl: 'some/test/path',
name: "getTest",
type: "query",
path: "some/test/path",
apiUrl: "some/test/path",
}
await mockServer(
async (req, res) => {
const result = await ssrQuery(resolverModule as any, 'test', {req, res})
const result = await ssrQuery(resolverModule as any, "test", {req, res})
expect(result).toBe('test')
expect(result).toBe("test")
},
async (url) => {
const res = await fetch(url)
@@ -32,16 +32,16 @@ describe('ssrQuery', () => {
)
})
it('works with middleware', async () => {
it("works with middleware", async () => {
const resolverModule = (jest.fn().mockImplementation(async (input) => {
await delay(1)
return input
}) as unknown) as EnhancedResolverModule
resolverModule._meta = {
name: 'getTest',
type: 'query',
path: 'some/test/path',
apiUrl: 'some/test/path',
name: "getTest",
type: "query",
path: "some/test/path",
apiUrl: "some/test/path",
}
resolverModule.middleware = [
(_req, res, next) => {
@@ -49,21 +49,21 @@ describe('ssrQuery', () => {
return next()
},
(_req, res, next) => {
res.setHeader('test', 'works')
res.setHeader("test", "works")
return next()
},
]
await mockServer(
async (req, res) => {
const result = await ssrQuery(resolverModule as any, 'test', {req, res})
const result = await ssrQuery(resolverModule as any, "test", {req, res})
expect(result).toBe('test')
expect(result).toBe("test")
},
async (url) => {
const res = await fetch(url)
expect(res.status).toBe(201)
expect(res.headers.get('test')).toBe('works')
expect(res.headers.get("test")).toBe("works")
},
)
})

View File

@@ -1,8 +1,12 @@
import {IncomingMessage, ServerResponse} from 'http'
import {log} from '@blitzjs/display'
import {InferUnaryParam} from './types'
import {getAllMiddlewareForModule, handleRequestWithMiddleware, MiddlewareResponse} from './middleware'
import {EnhancedResolverModule} from './rpc'
import {IncomingMessage, ServerResponse} from "http"
import {log} from "@blitzjs/display"
import {InferUnaryParam} from "./types"
import {
getAllMiddlewareForModule,
handleRequestWithMiddleware,
MiddlewareResponse,
} from "./middleware"
import {EnhancedResolverModule} from "./rpc"
type QueryFn = (...args: any) => Promise<any>

View File

@@ -2,14 +2,14 @@ import {
useInfiniteQuery as useInfiniteReactQuery,
InfiniteQueryResult,
InfiniteQueryOptions,
} from 'react-query'
import {PromiseReturnType, InferUnaryParam, QueryFn} from './types'
import {getQueryCacheFunctions, QueryCacheFunctions} from './utils/query-cache'
import {EnhancedRpcFunction} from './rpc'
} from "react-query"
import {PromiseReturnType, InferUnaryParam, QueryFn} from "./types"
import {getQueryCacheFunctions, QueryCacheFunctions} from "./utils/query-cache"
import {EnhancedRpcFunction} from "./rpc"
type RestQueryResult<T extends QueryFn> = Omit<
InfiniteQueryResult<PromiseReturnType<T>, any>,
'resolvedData'
"resolvedData"
> &
QueryCacheFunctions<PromiseReturnType<T>[]>
@@ -18,11 +18,11 @@ export function useInfiniteQuery<T extends QueryFn>(
params: InferUnaryParam<T> | (() => InferUnaryParam<T>),
options: InfiniteQueryOptions<PromiseReturnType<T>, any>,
): [PromiseReturnType<T>[], RestQueryResult<T>] {
if (typeof queryFn === 'undefined') {
throw new Error('useInfiniteQuery is missing the first argument - it must be a query function')
if (typeof queryFn === "undefined") {
throw new Error("useInfiniteQuery is missing the first argument - it must be a query function")
}
if (typeof params === 'undefined') {
if (typeof params === "undefined") {
throw new Error(
"useInfiniteQuery is missing the second argument. This will be the input to your query function on the server. Pass `null` if the query function doesn't take any arguments",
)
@@ -31,11 +31,14 @@ export function useInfiniteQuery<T extends QueryFn>(
const queryRpcFn = (queryFn as unknown) as EnhancedRpcFunction
const {data, ...queryRest} = useInfiniteReactQuery({
queryKey: () => [queryRpcFn._meta.apiUrl, typeof params === 'function' ? (params as Function)() : params],
queryKey: () => [
queryRpcFn._meta.apiUrl,
typeof params === "function" ? (params as Function)() : params,
],
queryFn: (_: string, params, more?) => queryRpcFn({...params, ...more}, {fromQueryHook: true}),
config: {
suspense: true,
retry: process.env.NODE_ENV === 'production' ? 3 : false,
retry: process.env.NODE_ENV === "production" ? 3 : false,
...options,
},
})

View File

@@ -1,9 +1,16 @@
import {usePaginatedQuery as usePaginatedReactQuery, PaginatedQueryResult, QueryOptions} from 'react-query'
import {PromiseReturnType, InferUnaryParam, QueryFn} from './types'
import {QueryCacheFunctions, getQueryCacheFunctions} from './utils/query-cache'
import {EnhancedRpcFunction} from './rpc'
import {
usePaginatedQuery as usePaginatedReactQuery,
PaginatedQueryResult,
QueryOptions,
} from "react-query"
import {PromiseReturnType, InferUnaryParam, QueryFn} from "./types"
import {QueryCacheFunctions, getQueryCacheFunctions} from "./utils/query-cache"
import {EnhancedRpcFunction} from "./rpc"
type RestQueryResult<T extends QueryFn> = Omit<PaginatedQueryResult<PromiseReturnType<T>>, 'resolvedData'> &
type RestQueryResult<T extends QueryFn> = Omit<
PaginatedQueryResult<PromiseReturnType<T>>,
"resolvedData"
> &
QueryCacheFunctions<PromiseReturnType<T>>
export function usePaginatedQuery<T extends QueryFn>(
@@ -11,11 +18,11 @@ export function usePaginatedQuery<T extends QueryFn>(
params: InferUnaryParam<T> | (() => InferUnaryParam<T>),
options?: QueryOptions<PaginatedQueryResult<PromiseReturnType<T>>>,
): [PromiseReturnType<T>, RestQueryResult<T>] {
if (typeof queryFn === 'undefined') {
throw new Error('usePaginatedQuery is missing the first argument - it must be a query function')
if (typeof queryFn === "undefined") {
throw new Error("usePaginatedQuery is missing the first argument - it must be a query function")
}
if (typeof params === 'undefined') {
if (typeof params === "undefined") {
throw new Error(
"usePaginatedQuery is missing the second argument. This will be the input to your query function on the server. Pass `null` if the query function doesn't take any arguments",
)
@@ -24,11 +31,14 @@ export function usePaginatedQuery<T extends QueryFn>(
const queryRpcFn = (queryFn as unknown) as EnhancedRpcFunction
const {resolvedData, ...queryRest} = usePaginatedReactQuery({
queryKey: () => [queryRpcFn._meta.apiUrl, typeof params === 'function' ? (params as Function)() : params],
queryKey: () => [
queryRpcFn._meta.apiUrl,
typeof params === "function" ? (params as Function)() : params,
],
queryFn: (_: string, params) => queryRpcFn(params, {fromQueryHook: true}),
config: {
suspense: true,
retry: process.env.NODE_ENV === 'production' ? 3 : false,
retry: process.env.NODE_ENV === "production" ? 3 : false,
...options,
},
})

View File

@@ -1,5 +1,5 @@
import {useRouter} from 'next/router'
import {useRouterQuery} from './use-router-query'
import {useRouter} from "next/router"
import {useRouterQuery} from "./use-router-query"
type ParsedUrlQueryValue = string | string[] | undefined
@@ -33,7 +33,8 @@ function areQueryValuesEqual(value1: ParsedUrlQueryValue, value2: ParsedUrlQuery
export function extractRouterParams(routerQuery: ParsedUrlQuery, query: ParsedUrlQuery) {
return Object.fromEntries(
Object.entries(routerQuery).filter(
([key, value]) => typeof query[key] === 'undefined' || !areQueryValuesEqual(value, query[key]),
([key, value]) =>
typeof query[key] === "undefined" || !areQueryValuesEqual(value, query[key]),
),
)
}
@@ -46,34 +47,34 @@ export function useParams() {
}
export function useParam(key: string): undefined | string | string[]
export function useParam(key: string, returnType: 'string'): string
export function useParam(key: string, returnType: 'number'): number
export function useParam(key: string, returnType: 'array'): string[]
export function useParam(key: string, returnType: "string"): string
export function useParam(key: string, returnType: "number"): number
export function useParam(key: string, returnType: "array"): string[]
export function useParam(
key: string,
returnType?: 'string' | 'number' | 'array',
returnType?: "string" | "number" | "array",
): undefined | number | string | string[] {
const params = useParams()
const rawValue = params[key]
if (returnType === 'number') {
if (returnType === "number") {
return Number(rawValue)
}
if (returnType === 'string') {
if (typeof rawValue === 'undefined') {
return ''
if (returnType === "string") {
if (typeof rawValue === "undefined") {
return ""
}
return rawValue
}
if (returnType === 'array') {
if (typeof rawValue === 'undefined') {
if (returnType === "array") {
if (typeof rawValue === "undefined") {
return []
}
if (typeof rawValue === 'string') {
if (typeof rawValue === "string") {
return [rawValue]
}

View File

@@ -1,9 +1,9 @@
import {useQuery as useReactQuery, QueryResult, QueryOptions} from 'react-query'
import {PromiseReturnType, InferUnaryParam, QueryFn} from './types'
import {QueryCacheFunctions, getQueryCacheFunctions} from './utils/query-cache'
import {EnhancedRpcFunction} from './rpc'
import {useQuery as useReactQuery, QueryResult, QueryOptions} from "react-query"
import {PromiseReturnType, InferUnaryParam, QueryFn} from "./types"
import {QueryCacheFunctions, getQueryCacheFunctions} from "./utils/query-cache"
import {EnhancedRpcFunction} from "./rpc"
type RestQueryResult<T extends QueryFn> = Omit<QueryResult<PromiseReturnType<T>>, 'data'> &
type RestQueryResult<T extends QueryFn> = Omit<QueryResult<PromiseReturnType<T>>, "data"> &
QueryCacheFunctions<PromiseReturnType<T>>
export function useQuery<T extends QueryFn>(
@@ -11,11 +11,11 @@ export function useQuery<T extends QueryFn>(
params: InferUnaryParam<T> | (() => InferUnaryParam<T>),
options?: QueryOptions<QueryResult<PromiseReturnType<T>>>,
): [PromiseReturnType<T>, RestQueryResult<T>] {
if (typeof queryFn === 'undefined') {
throw new Error('useQuery is missing the first argument - it must be a query function')
if (typeof queryFn === "undefined") {
throw new Error("useQuery is missing the first argument - it must be a query function")
}
if (typeof params === 'undefined') {
if (typeof params === "undefined") {
throw new Error(
"useQuery is missing the second argument. This will be the input to your query function on the server. Pass `null` if the query function doesn't take any arguments",
)
@@ -24,11 +24,14 @@ export function useQuery<T extends QueryFn>(
const queryRpcFn = (queryFn as unknown) as EnhancedRpcFunction
const {data, ...queryRest} = useReactQuery({
queryKey: () => [queryRpcFn._meta.apiUrl, typeof params === 'function' ? (params as Function)() : params],
queryKey: () => [
queryRpcFn._meta.apiUrl,
typeof params === "function" ? (params as Function)() : params,
],
queryFn: (_: string, params) => queryRpcFn(params, {fromQueryHook: true}),
config: {
suspense: true,
retry: process.env.NODE_ENV === 'production' ? 3 : false,
retry: process.env.NODE_ENV === "production" ? 3 : false,
...options,
},
})

View File

@@ -1,5 +1,5 @@
import {useRouter} from 'next/router'
import {parse} from 'url'
import {useRouter} from "next/router"
import {parse} from "url"
export function useRouterQuery() {
const router = useRouter()

View File

@@ -1,6 +1,6 @@
import {useRouter as useNextRouter} from 'next/router'
import {useParams} from './use-params'
import {useRouterQuery} from './use-router-query'
import {useRouter as useNextRouter} from "next/router"
import {useParams} from "./use-params"
import {useRouterQuery} from "./use-router-query"
export function useRouter() {
const router = useNextRouter()

View File

@@ -1,7 +1,7 @@
import {QueryKeyPart} from 'react-query'
import {QueryKeyPart} from "react-query"
export const isServer = typeof window === 'undefined'
export const isServer = typeof window === "undefined"
export function getQueryKey(cacheKey: string, params: any): readonly [string, ...QueryKeyPart[]] {
return [cacheKey, typeof params === 'function' ? (params as Function)() : params]
return [cacheKey, typeof params === "function" ? (params as Function)() : params]
}

View File

@@ -1,4 +1,4 @@
import {queryCache} from 'react-query'
import {queryCache} from "react-query"
export interface QueryCacheFunctions<T> {
mutate: (newData: T | ((oldData: T | undefined) => T)) => void

View File

@@ -1,8 +1,8 @@
import React from 'react'
import {withRouter as withNextRouter, NextRouter} from 'next/router'
import {WithRouterProps as WithNextRouterProps} from 'next/dist/client/with-router'
import {useParams, extractRouterParams} from './use-params'
import {useRouterQuery} from './use-router-query'
import React from "react"
import {withRouter as withNextRouter, NextRouter} from "next/router"
import {WithRouterProps as WithNextRouterProps} from "next/dist/client/with-router"
import {useParams, extractRouterParams} from "./use-params"
import {useRouterQuery} from "./use-router-query"
export interface BlitzRouter extends NextRouter {
params: ReturnType<typeof extractRouterParams>

View File

@@ -1,4 +1,4 @@
import {executeRpcCall, getIsomorphicRpcHandler} from '@blitzjs/core'
import {executeRpcCall, getIsomorphicRpcHandler} from "@blitzjs/core"
global.fetch = jest.fn(() => Promise.resolve({json: () => ({result: null, error: null})}))
@@ -10,48 +10,50 @@ declare global {
}
}
describe('RPC', () => {
describe('HEAD', () => {
it('warms the endpoint', () => {
describe("RPC", () => {
describe("HEAD", () => {
it("warms the endpoint", () => {
expect.assertions(1)
executeRpcCall.warm('/api/endpoint')
executeRpcCall.warm("/api/endpoint")
expect(global.fetch).toBeCalled()
})
})
describe('POST', () => {
it('makes the request', async () => {
describe("POST", () => {
it("makes the request", async () => {
expect.assertions(2)
const fetchMock = jest
.spyOn(global, 'fetch')
.spyOn(global, "fetch")
.mockImplementationOnce(() => Promise.resolve())
.mockImplementationOnce(() => Promise.resolve({json: () => ({result: 'result', error: null})}))
.mockImplementationOnce(() =>
Promise.resolve({json: () => ({result: "result", error: null})}),
)
const resolverModule = {
default: jest.fn(),
}
const rpcFn = getIsomorphicRpcHandler(
resolverModule,
'app/_resolvers/queries/getProduct',
'testResolver',
'query',
"app/_resolvers/queries/getProduct",
"testResolver",
"query",
)
try {
const result = await rpcFn('/api/endpoint', {paramOne: 1234})
expect(result).toBe('result')
const result = await rpcFn("/api/endpoint", {paramOne: 1234})
expect(result).toBe("result")
expect(fetchMock).toBeCalled()
} finally {
fetchMock.mockRestore()
}
})
it('handles errors', async () => {
it("handles errors", async () => {
expect.assertions(1)
const fetchMock = jest
.spyOn(global, 'fetch')
.mockImplementation(() =>
Promise.resolve({json: () => ({result: null, error: {name: 'Error', message: 'something broke'}})}),
const fetchMock = jest.spyOn(global, "fetch").mockImplementation(() =>
Promise.resolve({
json: () => ({result: null, error: {name: "Error", message: "something broke"}}),
}),
)
const resolverModule = {
@@ -59,13 +61,15 @@ describe('RPC', () => {
}
const rpcFn = getIsomorphicRpcHandler(
resolverModule,
'app/_resolvers/queries/getProduct',
'testResolver',
'query',
"app/_resolvers/queries/getProduct",
"testResolver",
"query",
)
try {
await expect(rpcFn('/api/endpoint', {paramOne: 1234})).rejects.toThrowError(/something broke/)
await expect(rpcFn("/api/endpoint", {paramOne: 1234})).rejects.toThrowError(
/something broke/,
)
} finally {
fetchMock.mockRestore()
}

View File

@@ -1,29 +1,29 @@
import {extractRouterParams} from '@blitzjs/core'
import {extractRouterParams} from "@blitzjs/core"
describe('useParams', () => {
describe('extractRouterParams', () => {
it('returns proper params', () => {
describe("useParams", () => {
describe("extractRouterParams", () => {
it("returns proper params", () => {
const routerQuery = {
id: '1',
cat: 'category',
slug: ['example', 'multiple', 'slugs'],
empty: '',
queryArray: ['1', '123', ''],
id: "1",
cat: "category",
slug: ["example", "multiple", "slugs"],
empty: "",
queryArray: ["1", "123", ""],
}
const query = {
cat: 'somethingelse',
slug: ['query-slug'],
queryArray: ['1', '123', ''],
onlyInQuery: 'onlyInQuery',
cat: "somethingelse",
slug: ["query-slug"],
queryArray: ["1", "123", ""],
onlyInQuery: "onlyInQuery",
}
const params = extractRouterParams(routerQuery, query)
expect(params).toEqual({
id: '1',
cat: 'category',
slug: ['example', 'multiple', 'slugs'],
empty: '',
id: "1",
cat: "category",
slug: ["example", "multiple", "slugs"],
empty: "",
})
})
})

View File

@@ -1,16 +1,19 @@
import React from 'react'
import {act, render, waitForElementToBeRemoved, screen} from '@testing-library/react'
import {useQuery} from '../src/use-query'
import React from "react"
import {act, render, waitForElementToBeRemoved, screen} from "@testing-library/react"
import {useQuery} from "../src/use-query"
describe('useQuery', () => {
const setupHook = (params: any, queryFn: (...args: any) => Promise<any>): [{data?: any}, Function] => {
describe("useQuery", () => {
const setupHook = (
params: any,
queryFn: (...args: any) => Promise<any>,
): [{data?: any}, Function] => {
// This enhance fn does what getIsomorphicRpcHandler does during build time
const enhance = (fn: any) => {
fn._meta = {
name: 'testResolver',
type: 'query',
path: 'app/test',
apiUrl: 'test/url',
name: "testResolver",
type: "query",
path: "app/test",
apiUrl: "test/url",
}
return fn
}
@@ -22,7 +25,7 @@ describe('useQuery', () => {
)
const [data] = useQuery(enhance(queryFn), params)
Object.assign(res, {data})
return <div id="harness">{data ? 'Ready' : 'Missing Dependency'}</div>
return <div id="harness">{data ? "Ready" : "Missing Dependency"}</div>
}
const ui = () => (
@@ -40,34 +43,34 @@ describe('useQuery', () => {
const upcase = async (args: string): Promise<string> => {
return args.toUpperCase()
}
it('should work', async () => {
const [res] = setupHook('test', upcase)
await waitForElementToBeRemoved(() => screen.getByText('Loading...'))
it("should work", async () => {
const [res] = setupHook("test", upcase)
await waitForElementToBeRemoved(() => screen.getByText("Loading..."))
await act(async () => {
await screen.findByText('Ready')
expect(res.data).toBe('TEST')
await screen.findByText("Ready")
expect(res.data).toBe("TEST")
})
})
it('should work on dependent query', async () => {
it("should work on dependent query", async () => {
// dependent queries are canceled if the params function throws
let params: Function = () => {
throw new Error('not ready yet')
throw new Error("not ready yet")
}
const [res, rerender] = setupHook(() => params(), upcase)
await screen.findByText('Missing Dependency')
await screen.findByText("Missing Dependency")
// eslint-disable-next-line require-await
await act(async () => {
// simulates the dependency becoming available
params = () => 'test'
params = () => "test"
rerender()
})
await act(async () => {
await screen.findByText('Ready')
expect(res.data).toBe('TEST')
await screen.findByText("Ready")
expect(res.data).toBe("TEST")
})
})
})

View File

@@ -4,4 +4,4 @@
// }
// }
export * from '../src/types'
export * from "../src/types"

View File

@@ -1,3 +1,3 @@
it('should do nothing', () => {
it("should do nothing", () => {
expect(true).toBe(true)
})

View File

@@ -1,9 +1,9 @@
import chalk from 'chalk'
import ora from 'ora'
import readline from 'readline'
import chalk from "chalk"
import ora from "ora"
import readline from "readline"
// const blitzTrueBrandColor = '6700AB'
const blitzBrightBrandColor = '8a3df0'
const blitzBrightBrandColor = "8a3df0"
// Using brigh brand color so it's better for dark terminals
const brandColor = blitzBrightBrandColor
@@ -17,15 +17,15 @@ const withWarning = (str: string) => {
}
const withCaret = (str: string) => {
return `${chalk.gray('>')} ${str}`
return `${chalk.gray(">")} ${str}`
}
const withCheck = (str: string) => {
return `${chalk.green('✔')} ${str}`
return `${chalk.green("✔")} ${str}`
}
const withX = (str: string) => {
return `${chalk.red.bold('✕')} ${str}`
return `${chalk.red.bold("✕")} ${str}`
}
/**
@@ -91,10 +91,10 @@ const info = (msg: string) => {
const spinner = (str: string) => {
return ora({
text: str,
color: 'blue',
color: "blue",
spinner: {
interval: 120,
frames: ['◢', '◣', '◤', '◥'],
frames: ["◢", "◣", "◤", "◥"],
},
})
}
@@ -109,7 +109,7 @@ const success = (msg: string) => {
}
const newline = () => {
console.log(' ')
console.log(" ")
}
/**

View File

@@ -98,7 +98,7 @@ function myStage({
// Ready - is an object that will be merged with all other
// Stages and returned in a promise by transformFiles()
const ready = {foo: 'This will appear in the object returned by transformation promise'}
const ready = {foo: "This will appear in the object returned by transformation promise"}
// Export the stream and the ready info
return {stream, ready}
@@ -175,21 +175,21 @@ If you push an Error to the transform stream `next(new Error)` an Error Event wi
Evented Vinyl Files are [Vinyl Files](https://github.com/gulpjs/vinyl) with events attached to them
```ts
const isDelete = (file) => file.isNull() && file.event === 'unlink'
const isDelete = (file) => file.isNull() && file.event === "unlink"
// The input file at '/path/to/foo' was deleted
// This can be transformed during the process phase
return new Vinyl({
path: '/path/to/foo',
path: "/path/to/foo",
content: null,
event: 'unlink',
event: "unlink",
})
```
```ts
// Add file at '/path/to/foo'
new Vinyl({
path: '/path/to/foo',
path: "/path/to/foo",
content: someContentStream,
})
```

View File

@@ -1,8 +1,8 @@
import {through} from './streams'
import File from 'vinyl'
import {log} from '@blitzjs/display'
import chalk from 'chalk'
import {Event, FILE_WRITTEN, INIT, ERROR_THROWN, READY} from './events'
import {through} from "./streams"
import File from "vinyl"
import {log} from "@blitzjs/display"
import chalk from "chalk"
import {Event, FILE_WRITTEN, INIT, ERROR_THROWN, READY} from "./events"
/**
* Display is a stream that converts build status events and prepares them for the console.
@@ -11,12 +11,12 @@ import {Event, FILE_WRITTEN, INIT, ERROR_THROWN, READY} from './events'
export function createDisplay() {
let lastEvent: Event<any> = {type: INIT, payload: null}
let spinner = log.spinner('Preparing for launch').start()
let spinner = log.spinner("Preparing for launch").start()
const stream = through({objectMode: true}, (event: Event<File>, _, next) => {
switch (event.type) {
case FILE_WRITTEN: {
const filePath = event.payload.history[0].replace(process.cwd(), '')
const filePath = event.payload.history[0].replace(process.cwd(), "")
spinner.text = filePath
break
}
@@ -24,13 +24,13 @@ export function createDisplay() {
case ERROR_THROWN: {
// Tidy up if operational error is encountered
if (lastEvent.type === FILE_WRITTEN) {
spinner.fail('Uh oh something broke')
spinner.fail("Uh oh something broke")
}
break
}
case READY: {
spinner.succeed(chalk.green.bold('Prepped for launch'))
spinner.succeed(chalk.green.bold("Prepped for launch"))
break
}
}

View File

@@ -1,8 +1,8 @@
export type Event<T> = {type: string; payload: T}
export const IDLE = 'IDLE'
export const INIT = 'INIT'
export const FILE_WRITTEN = 'FILE_WRITTEN'
export const FILE_DELETED = 'FILE_DELETED'
export const ERROR_THROWN = 'ERROR_THROWN'
export const READY = 'READY'
export const IDLE = "IDLE"
export const INIT = "INIT"
export const FILE_WRITTEN = "FILE_WRITTEN"
export const FILE_DELETED = "FILE_DELETED"
export const ERROR_THROWN = "ERROR_THROWN"
export const READY = "READY"

View File

@@ -1,30 +1,30 @@
import fs from 'fs'
import {resolve} from 'path'
import {agnosticSource} from '.'
import {testStreamItems} from '../../test-utils'
import fs from "fs"
import {resolve} from "path"
import {agnosticSource} from "."
import {testStreamItems} from "../../test-utils"
const cwd = resolve(__dirname, 'fixtures')
const cwd = resolve(__dirname, "fixtures")
function logItem(fileOrString: {path: string} | string) {
if (typeof fileOrString === 'string') {
if (typeof fileOrString === "string") {
return fileOrString
}
return fileOrString.path
}
describe('agnosticSource', () => {
describe("agnosticSource", () => {
afterEach(() => {
if (fs.existsSync(resolve(cwd, 'three'))) {
fs.unlinkSync(resolve(cwd, 'three'))
if (fs.existsSync(resolve(cwd, "three"))) {
fs.unlinkSync(resolve(cwd, "three"))
}
})
test('when watching = false', (done) => {
const expected = [resolve(cwd, 'one'), resolve(cwd, 'two')]
const {stream} = agnosticSource({ignore: [], include: ['**/*'], cwd, watch: false})
test("when watching = false", (done) => {
const expected = [resolve(cwd, "one"), resolve(cwd, "two")]
const {stream} = agnosticSource({ignore: [], include: ["**/*"], cwd, watch: false})
const log: any[] = []
stream.on('data', (data) => {
if (data === 'ready') {
stream.on("data", (data) => {
if (data === "ready") {
stream.end()
expect(log).toEqual(expected)
return done()
@@ -33,14 +33,14 @@ describe('agnosticSource', () => {
})
})
test('when watching = true', async () => {
const expected = [resolve(cwd, 'one'), resolve(cwd, 'two'), 'ready', resolve(cwd, 'three')]
const {stream, close} = agnosticSource({ignore: [], include: ['**/*'], cwd, watch: true})
test("when watching = true", async () => {
const expected = [resolve(cwd, "one"), resolve(cwd, "two"), "ready", resolve(cwd, "three")]
const {stream, close} = agnosticSource({ignore: [], include: ["**/*"], cwd, watch: true})
setTimeout(() => {
// Wait
const filename = resolve(cwd, 'three')
fs.writeFile(filename, Buffer.from('three'), () => {})
const filename = resolve(cwd, "three")
fs.writeFile(filename, Buffer.from("three"), () => {})
}, 800)
await testStreamItems(stream, expected, logItem)

View File

@@ -1,12 +1,12 @@
import {through} from '../../streams'
import vfs from 'vinyl-fs'
import mergeStream from 'merge-stream'
import {through} from "../../streams"
import vfs from "vinyl-fs"
import mergeStream from "merge-stream"
import File from 'vinyl'
import chokidar from 'chokidar'
import vinyl from 'vinyl-file'
import {Stats} from 'fs'
import {normalize, resolve, isAbsolute} from 'path'
import File from "vinyl"
import chokidar from "chokidar"
import vinyl from "vinyl-file"
import {Stats} from "fs"
import {normalize, resolve, isAbsolute} from "path"
export const watch = (includePaths: string[] | string, options: chokidar.WatchOptions) => {
function resolveFilepath(filepath: string) {
@@ -26,7 +26,9 @@ export const watch = (includePaths: string[] | string, options: chokidar.WatchOp
const fileOpts = Object.assign({}, options, {path: filepath})
const file =
evt === 'unlink' || evt === 'unlinkDir' ? new File(fileOpts) : await vinyl.read(filepath, fileOpts)
evt === "unlink" || evt === "unlinkDir"
? new File(fileOpts)
: await vinyl.read(filepath, fileOpts)
file.event = evt
stream.push(file)
@@ -34,9 +36,9 @@ export const watch = (includePaths: string[] | string, options: chokidar.WatchOp
}
const fswatcher = chokidar.watch(includePaths, options)
fswatcher.on('add', processEvent('add'))
fswatcher.on('change', processEvent('change'))
fswatcher.on('unlink', processEvent('unlink'))
fswatcher.on("add", processEvent("add"))
fswatcher.on("change", processEvent("change"))
fswatcher.on("unlink", processEvent("unlink"))
return {stream, fswatcher}
}
@@ -67,7 +69,7 @@ function getWatcher(watching: boolean, cwd: string, include: string[], ignore: s
* @param config Config object
*/
export function agnosticSource({ignore, include, cwd, watch: watching = false}: SourceConfig) {
const allGlobs = [...include, ...ignore.map((a) => '!' + a)]
const allGlobs = [...include, ...ignore.map((a) => "!" + a)]
const vinylFsStream = vfs.src(allGlobs, {
buffer: true,
@@ -80,13 +82,13 @@ export function agnosticSource({ignore, include, cwd, watch: watching = false}:
const stream = mergeStream(vinylFsStream, watcher.stream) as NodeJS.ReadWriteStream
vinylFsStream.on('end', () => {
vinylFsStream.on("end", () => {
// Send ready event when our initial scan of the folder is done
stream.write('ready')
stream.write("ready")
})
const close = () => watcher.fswatcher.close()
stream.on('end', async () => {
stream.on("end", async () => {
await close()
})

View File

@@ -1,5 +1,5 @@
import crypto from 'crypto'
import {transform} from '../transform'
import crypto from "crypto"
import {transform} from "../transform"
/**
* Returns a stage that prepares files coming into the stream
* with correct event information as well as hash information
@@ -15,14 +15,14 @@ export function createEnrichFiles() {
}
if (!file.event) {
file.event = 'add'
file.event = "add"
}
if (!file.hash) {
const hash = crypto
.createHash('md5')
.createHash("md5")
.update(JSON.stringify({path: file.path, s: file.stat?.mtime}))
.digest('hex')
.digest("hex")
file.hash = hash
}

View File

@@ -1,6 +1,6 @@
import {transform} from '../transform'
import File from 'vinyl'
import {isEvent} from '../utils'
import {transform} from "../transform"
import File from "vinyl"
import {isEvent} from "../utils"
type FileCacheEntry = {path: string}
@@ -64,7 +64,7 @@ export function createFileCache(filter: (a: File) => boolean = () => true) {
return next(null, file)
}
if (file.event === 'unlink') {
if (file.event === "unlink") {
cache.delete(file)
} else {
cache.add(file)

View File

@@ -1,12 +1,12 @@
import {createIdleHandler} from '.'
import {pipeline, through} from '../../streams'
import {testStreamItems} from '../../test-utils'
import {IDLE, READY} from '../../events'
import {createIdleHandler} from "."
import {pipeline, through} from "../../streams"
import {testStreamItems} from "../../test-utils"
import {IDLE, READY} from "../../events"
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
describe('idlehander', () => {
it('should fire the idle event', async () => {
describe("idlehander", () => {
it("should fire the idle event", async () => {
// Setup an input stream
const input = through.obj()
@@ -24,7 +24,7 @@ describe('idlehander', () => {
for (const item of arr) {
input.write(item)
}
input.write('ready')
input.write("ready")
await sleep(150)
for (const item of arr) {
input.write(item)

View File

@@ -1,7 +1,7 @@
import {through} from '../../streams'
import {READY, IDLE} from '../../events'
import {Writable} from 'stream'
import {isEvent} from '../../utils'
import {through} from "../../streams"
import {READY, IDLE} from "../../events"
import {Writable} from "stream"
import {isEvent} from "../../utils"
/**
* Idle handler will fire events when the stream is idle
@@ -26,14 +26,14 @@ export const createIdleHandler = (bus: Writable, delay: number = 500) => {
}
const stream = through({objectMode: true}, function (f, _, next) {
if (isEvent(f) && f === 'ready') {
if (isEvent(f) && f === "ready") {
bus.write({type: READY})
}
resetTimeout()
next(null, f)
})
stream.on('end', () => {
stream.on("end", () => {
destroyTimeout()
handler()
})

View File

@@ -1,8 +1,8 @@
import {unlink as unlinkFile, pathExists} from 'fs-extra'
import {unlink as unlinkFile, pathExists} from "fs-extra"
import {relative, resolve} from 'path'
import {transform} from '../transform'
import {EventedFile} from 'types'
import {relative, resolve} from "path"
import {transform} from "../transform"
import {EventedFile} from "types"
function getDestPath(folder: string, file: EventedFile) {
const {history, cwd} = file
@@ -16,7 +16,7 @@ function getDestPath(folder: string, file: EventedFile) {
*/
export function unlink(folder: string) {
return transform.file(async (file) => {
if (file.event === 'unlink' || file.event === 'unlinkDir') {
if (file.event === "unlink" || file.event === "unlinkDir") {
if (await pathExists(getDestPath(folder, file))) await unlinkFile(getDestPath(folder, file))
}

View File

@@ -8,9 +8,9 @@ The following is a rough plan for how to do this. (Likely to change/improve at a
```ts
const hash = crypto
.createHash('md5')
.createHash("md5")
.update(file.path + file.stats.mtime)
.digest('hex')
.digest("hex")
file.hash = hash
```
@@ -21,21 +21,21 @@ Following
```ts
// reduced to as the first step during input
const input = {abc123def456: '/foo/bar/baz', def456abc123: '/foo/bar/bop'}
const input = {abc123def456: "/foo/bar/baz", def456abc123: "/foo/bar/bop"}
// reduced to as the last step just before file write
const complete = {
abc123def456: {
input: '/foo/bar/baz',
output: ['/bas/boop/blop', '/bas/boop/ding', '/bas/boop/bar'],
input: "/foo/bar/baz",
output: ["/bas/boop/blop", "/bas/boop/ding", "/bas/boop/bar"],
},
def456abc123: {
input: '/foo/bar/bing',
output: ['/bas/boop/ping', '/bas/boop/foo', '/bas/boop/fawn'],
input: "/foo/bar/bing",
output: ["/bas/boop/ping", "/bas/boop/foo", "/bas/boop/fawn"],
},
cbd123aef456: {
input: '/foo/bar/bop',
output: ['/bas/boop/thing'],
input: "/foo/bar/bop",
output: ["/bas/boop/thing"],
},
}
```

View File

@@ -1,6 +1,6 @@
// Mostly concerned with solving the Dirty Sync problem
import {log} from '@blitzjs/display'
import {transform} from '../../transform'
import {log} from "@blitzjs/display"
import {transform} from "../../transform"
/**
* Returns streams that help handling work optimisation in the file transform stream.
@@ -22,12 +22,12 @@ export function createWorkOptimizer() {
const triage = transform.file((file, {push, next}) => {
if (!file.hash) {
log.debug('File does not have hash! ' + file.path)
log.debug("File does not have hash! " + file.path)
return next()
}
// Dont send files that have already been done or have already been added
if (done.includes(file.hash) || todo.includes(file.hash)) {
log.debug('Rejecting because this job has been done before: ' + file.path)
log.debug("Rejecting because this job has been done before: " + file.path)
return next()
}

View File

@@ -1,75 +1,75 @@
import {createWorkOptimizer} from '.'
import {testStreamItems} from '../../test-utils'
import {pipeline} from '../../streams'
import {normalize} from 'path'
import File from 'vinyl'
import {createWorkOptimizer} from "."
import {testStreamItems} from "../../test-utils"
import {pipeline} from "../../streams"
import {normalize} from "path"
import File from "vinyl"
function logItem(fileOrString: {path: string} | string) {
if (typeof fileOrString === 'string') {
if (typeof fileOrString === "string") {
return fileOrString
}
return fileOrString.path
}
describe('agnosticSource', () => {
test('basic throughput', async () => {
describe("agnosticSource", () => {
test("basic throughput", async () => {
const {triage, reportComplete} = createWorkOptimizer()
triage.write(
new File({
hash: 'one',
path: normalize('/path/to/one'),
content: Buffer.from('one'),
hash: "one",
path: normalize("/path/to/one"),
content: Buffer.from("one"),
}),
)
triage.write(
new File({
hash: 'two',
path: normalize('/path/to/two'),
content: Buffer.from('two'),
hash: "two",
path: normalize("/path/to/two"),
content: Buffer.from("two"),
}),
)
triage.write(
new File({
hash: 'three',
path: normalize('/path/to/three'),
content: Buffer.from('three'),
hash: "three",
path: normalize("/path/to/three"),
content: Buffer.from("three"),
}),
)
const expected = ['/path/to/one', '/path/to/two', '/path/to/three'].map(normalize)
const expected = ["/path/to/one", "/path/to/two", "/path/to/three"].map(normalize)
const stream = pipeline(triage, reportComplete)
await testStreamItems(stream, expected, logItem)
})
test('same file is rejected', async () => {
test("same file is rejected", async () => {
const {triage, reportComplete} = createWorkOptimizer()
triage.write(
new File({
hash: 'one',
path: normalize('/path/to/one'),
content: Buffer.from('one'),
hash: "one",
path: normalize("/path/to/one"),
content: Buffer.from("one"),
}),
)
triage.write(
new File({
hash: 'one',
path: normalize('/path/to/one'),
content: Buffer.from('one'),
hash: "one",
path: normalize("/path/to/one"),
content: Buffer.from("one"),
}),
)
triage.write(
new File({
hash: 'two',
path: normalize('/path/to/two'),
content: Buffer.from('two'),
hash: "two",
path: normalize("/path/to/two"),
content: Buffer.from("two"),
}),
)
const expected = ['/path/to/one', '/path/to/two'].map(normalize)
const expected = ["/path/to/one", "/path/to/two"].map(normalize)
const stream = pipeline(triage, reportComplete)
await testStreamItems(stream, expected, logItem)
})

View File

@@ -1,12 +1,12 @@
import {pipeline} from '../../streams'
import gulpIf from 'gulp-if'
import {unlink} from '../unlink'
import {dest} from 'vinyl-fs'
import File from 'vinyl'
import {FILE_WRITTEN, FILE_DELETED} from '../../events'
import {Writable} from 'stream'
import {isFile} from '../../utils'
import {transform} from '../../transform'
import {pipeline} from "../../streams"
import gulpIf from "gulp-if"
import {unlink} from "../unlink"
import {dest} from "vinyl-fs"
import File from "vinyl"
import {FILE_WRITTEN, FILE_DELETED} from "../../events"
import {Writable} from "stream"
import {isFile} from "../../utils"
import {transform} from "../../transform"
/**
* Returns a Stage that writes files to the destination path
*/
@@ -30,4 +30,4 @@ export const createWrite = (
return {stream}
}
const isUnlinkFile = (file: File) => file.event === 'unlink' || file.event === 'unlinkDir'
const isUnlinkFile = (file: File) => file.event === "unlink" || file.event === "unlinkDir"

View File

@@ -1,33 +1,34 @@
import {createWrite} from '.'
import {through} from '../../streams'
import {testStreamItems} from '../../test-utils'
import {normalize} from 'path'
import File from 'vinyl'
import {FILE_DELETED, FILE_WRITTEN} from '../../events'
import {createWrite} from "."
import {through} from "../../streams"
import {testStreamItems} from "../../test-utils"
import {normalize} from "path"
import File from "vinyl"
import {FILE_DELETED, FILE_WRITTEN} from "../../events"
describe('writer', () => {
it('should write files', async () => {
describe("writer", () => {
it("should write files", async () => {
// Setup an input stream
const bus = through.obj()
const destinationWriter = through({objectMode: true}, (f, _, next) => {
if (typeof f === 'string') throw new Error('This should not happen because the writer wont allow it!')
if (typeof f === "string")
throw new Error("This should not happen because the writer wont allow it!")
next(null, f)
})
// setup the test pipeline
const writer = createWrite(normalize('/foo'), bus, destinationWriter).stream
writer.write(new File({path: normalize('/bar'), event: 'unlink'}))
writer.write(new File({path: normalize('/thing'), event: 'add'}))
writer.write('foo')
const writer = createWrite(normalize("/foo"), bus, destinationWriter).stream
writer.write(new File({path: normalize("/bar"), event: "unlink"}))
writer.write(new File({path: normalize("/thing"), event: "add"}))
writer.write("foo")
await testStreamItems(
bus,
[
// Note order is not necessarily deterministic
// If that is the case we can change the way we test this
{type: FILE_WRITTEN, file: normalize('/thing')},
{type: FILE_DELETED, file: normalize('/bar')},
{type: FILE_WRITTEN, file: normalize("/thing")},
{type: FILE_DELETED, file: normalize("/bar")},
],
({type, payload}) => ({type, file: payload.path}),
)

View File

@@ -1,5 +1,5 @@
export {transformFiles} from './transform-files'
export {Stage, PipelineItem} from './types'
export * from './events'
export {transform} from './transform'
export {FileCache} from './helpers/file-cache'
export {transformFiles} from "./transform-files"
export {Stage, PipelineItem} from "./types"
export * from "./events"
export {transform} from "./transform"
export {FileCache} from "./helpers/file-cache"

View File

@@ -1,16 +1,16 @@
import {Writable} from 'stream'
import File from 'vinyl'
import {pipeline, through} from './streams'
import {Stage, StageArgs, StageConfig} from './types'
import {agnosticSource} from './helpers/agnostic-source'
import {createEnrichFiles} from './helpers/enrich-files'
import {createFileCache} from './helpers/file-cache'
import {createIdleHandler} from './helpers/idle-handler'
import {createWorkOptimizer} from './helpers/work-optimizer'
import {createWrite} from './helpers/writer'
import {Writable} from "stream"
import File from "vinyl"
import {pipeline, through} from "./streams"
import {Stage, StageArgs, StageConfig} from "./types"
import {agnosticSource} from "./helpers/agnostic-source"
import {createEnrichFiles} from "./helpers/enrich-files"
import {createFileCache} from "./helpers/file-cache"
import {createIdleHandler} from "./helpers/idle-handler"
import {createWorkOptimizer} from "./helpers/work-optimizer"
import {createWrite} from "./helpers/writer"
export function isSourceFile(file: File) {
return file.hash.indexOf(':') === -1
return file.hash.indexOf(":") === -1
}
/**

View File

@@ -1,29 +1,29 @@
// The following are a loose collaction of stream
// helpers based on the missisippi library
import {Stream} from 'stream'
import {Stream} from "stream"
import pipe from 'pump'
import pipe from "pump"
export {pipe}
import through from 'through2'
import through from "through2"
export {through}
export {default as parallel} from 'parallel-transform'
export {default as parallel} from "parallel-transform"
// Fix issues with interop
import from2 from 'from2'
import from2 from "from2"
type From2 = typeof from2
const from: From2 = require('from2')
const from: From2 = require("from2")
export {from}
// Fix issues with interop
import flushWriteStream from 'flush-write-stream'
import flushWriteStream from "flush-write-stream"
type FlushWriteStream = typeof flushWriteStream
const to: FlushWriteStream = require('flush-write-stream')
const to: FlushWriteStream = require("flush-write-stream")
export {to}
import pumpify from 'pumpify'
import pumpify from "pumpify"
// Bad types
type PumpifyFn = (...streams: Stream[]) => pumpify

View File

@@ -1,6 +1,6 @@
import {through, pipeline} from './streams'
import {through, pipeline} from "./streams"
const defaultLogger = (file: any) => (typeof file === 'string' ? file : file.path)
const defaultLogger = (file: any) => (typeof file === "string" ? file : file.path)
export function testStreamItems(
stream: NodeJS.ReadWriteStream,
expected: any[],

View File

@@ -1,11 +1,11 @@
import through2 from 'through2'
import {testStreamItems} from '../test-utils'
import File from 'vinyl'
import {transformFiles} from '.'
import {normalize} from 'path'
import through2 from "through2"
import {testStreamItems} from "../test-utils"
import File from "vinyl"
import {transformFiles} from "."
import {normalize} from "path"
function logFile(file: File | string) {
const out = typeof file === 'string' ? file : file.path
const out = typeof file === "string" ? file : file.path
return out
}
@@ -16,8 +16,8 @@ function generateFile(num: number) {
content: Buffer.from(`${num}`),
})
}
describe('transformFiles', () => {
test('With massive numbers of files', async () => {
describe("transformFiles", () => {
test("With massive numbers of files", async () => {
const source = {
stream: through2.obj((f, _, next) => {
next(null, f)
@@ -39,7 +39,7 @@ describe('transformFiles', () => {
source.stream.write(f)
})
source.stream.write('ready')
source.stream.write("ready")
// Close the test when these are both done
await Promise.all([
@@ -49,10 +49,10 @@ describe('transformFiles', () => {
.map((f) => {
return normalize(f.path)
})
.concat(['ready']),
.concat(["ready"]),
logFile,
),
transformFiles(normalize('/foo'), [], normalize('/bar'), {source, writer, noclean: true}),
transformFiles(normalize("/foo"), [], normalize("/bar"), {source, writer, noclean: true}),
])
}, 10000)
})

View File

@@ -1,11 +1,11 @@
import {pipe} from '../streams'
import {createPipeline} from '../pipeline'
import {pathExists, ensureDir, remove} from 'fs-extra'
import {through} from '../streams'
import {createDisplay} from '../display'
import {READY, ERROR_THROWN} from '../events'
import {Stage} from '../types'
import {Transform} from 'stream'
import {pipe} from "../streams"
import {createPipeline} from "../pipeline"
import {pathExists, ensureDir, remove} from "fs-extra"
import {through} from "../streams"
import {createDisplay} from "../display"
import {READY, ERROR_THROWN} from "../events"
import {Stage} from "../types"
import {Transform} from "stream"
type FSStreamer = {stream: NodeJS.ReadWriteStream}
@@ -59,7 +59,7 @@ export async function transformFiles(
watch,
}
bus.on('data', ({type}) => {
bus.on("data", ({type}) => {
if (type === READY) {
resolve(fileTransformPipeline.ready)
}
@@ -68,7 +68,7 @@ export async function transformFiles(
const fileTransformPipeline = createPipeline(config, stages, bus, source, writer)
// Send source to fileTransformPipeline
fileTransformPipeline.stream.on('error', (err) => {
fileTransformPipeline.stream.on("error", (err) => {
bus.write({type: ERROR_THROWN, payload: err})
if (err) reject(err)
})

View File

@@ -1,17 +1,17 @@
import through2 from 'through2'
import {testStreamItems} from '../test-utils'
import File from 'vinyl'
import {transformFiles} from '.'
import {normalize} from 'path'
import through2 from "through2"
import {testStreamItems} from "../test-utils"
import File from "vinyl"
import {transformFiles} from "."
import {normalize} from "path"
function logFile(file: File | string) {
if (typeof file === 'string') {
if (typeof file === "string") {
return file
}
return file.path
}
describe('transformFiles', () => {
test('Files and events are sent from one end of the pipeline to the other', async () => {
describe("transformFiles", () => {
test("Files and events are sent from one end of the pipeline to the other", async () => {
const source = {
stream: through2.obj((f, _, next) => {
next(null, f)
@@ -26,34 +26,34 @@ describe('transformFiles', () => {
source.stream.write(
new File({
path: normalize('/foo/one'),
content: Buffer.from('one'),
path: normalize("/foo/one"),
content: Buffer.from("one"),
}),
)
source.stream.write(
new File({
path: normalize('/foo/two'),
content: Buffer.from('two'),
path: normalize("/foo/two"),
content: Buffer.from("two"),
}),
)
source.stream.write(
new File({
path: normalize('/foo/three'),
content: Buffer.from('three'),
path: normalize("/foo/three"),
content: Buffer.from("three"),
}),
)
source.stream.write('ready')
source.stream.write("ready")
// Close the test when these are both done
await Promise.all([
testStreamItems(
writer.stream,
['/foo/one', '/foo/two', '/foo/three'].map(normalize).concat(['ready']),
["/foo/one", "/foo/two", "/foo/three"].map(normalize).concat(["ready"]),
logFile,
),
transformFiles(normalize('/foo'), [], normalize('/bar'), {source, writer, noclean: true}),
transformFiles(normalize("/foo"), [], normalize("/bar"), {source, writer, noclean: true}),
])
})
})

View File

@@ -1,14 +1,14 @@
import {transform} from './transform'
import {testStreamItems} from './test-utils'
import {PipelineItem} from 'types'
import {isFile, isEvent} from './utils'
import File from 'vinyl'
import through2 from 'through2'
import {normalize} from 'path'
import {transform} from "./transform"
import {testStreamItems} from "./test-utils"
import {PipelineItem} from "types"
import {isFile, isEvent} from "./utils"
import File from "vinyl"
import through2 from "through2"
import {normalize} from "path"
describe('transform', () => {
describe('when it uses the files filter', () => {
const newFile = (path: string) => new File({event: 'add', path})
describe("transform", () => {
describe("when it uses the files filter", () => {
const newFile = (path: string) => new File({event: "add", path})
const logger = (i: PipelineItem) => {
if (isEvent(i)) {
return i
@@ -20,33 +20,33 @@ describe('transform', () => {
return i
}
it('should pass events', async () => {
it("should pass events", async () => {
const s = transform.file((f) => f)
s.write('one')
s.write('two')
s.write('three')
await testStreamItems(s, ['one', 'two', 'three'], logger)
s.write("one")
s.write("two")
s.write("three")
await testStreamItems(s, ["one", "two", "three"], logger)
})
it('should pass files', async () => {
it("should pass files", async () => {
const s = transform.file((f) => f)
s.write(newFile('/one'))
s.write(newFile('/two'))
s.write(newFile('/three'))
s.write(newFile("/one"))
s.write(newFile("/two"))
s.write(newFile("/three"))
await testStreamItems(
s,
[
{path: '/one', event: 'add'},
{path: '/two', event: 'add'},
{path: '/three', event: 'add'},
{path: "/one", event: "add"},
{path: "/two", event: "add"},
{path: "/three", event: "add"},
].map((obj) => ({...obj, path: normalize(obj.path)})), // vinyl on windoze...
logger,
)
})
it('should swallow stuff when it doesnt return anything', () => {
it("should swallow stuff when it doesnt return anything", () => {
const s = transform.file((_, {next}) => next())
const l: any[] = []
s.pipe(
@@ -55,81 +55,87 @@ describe('transform', () => {
next(f)
}),
)
s.write(newFile('/one'))
s.write(newFile('/two'))
s.write(newFile('/three'))
s.write(newFile("/one"))
s.write(newFile("/two"))
s.write(newFile("/three"))
expect(l).toEqual([])
})
it('should allow events through even when it is swallowing files', async () => {
it("should allow events through even when it is swallowing files", async () => {
const s = transform.file((_, {next}) => next())
s.write('one')
s.write('two')
s.write('three')
await testStreamItems(s, ['one', 'two', 'three'], logger)
s.write("one")
s.write("two")
s.write("three")
await testStreamItems(s, ["one", "two", "three"], logger)
})
it('can push multiple events to the queue', async () => {
it("can push multiple events to the queue", async () => {
const s = transform.file((_, {push}) => {
push('a')
push('b')
return 'c'
push("a")
push("b")
return "c"
})
s.write(newFile('one'))
s.write('foo')
await testStreamItems(s, ['a', 'b', 'c', 'foo'], logger)
s.write(newFile("one"))
s.write("foo")
await testStreamItems(s, ["a", "b", "c", "foo"], logger)
})
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms))
it('can handle a promise fn', async () => {
it("can handle a promise fn", async () => {
const s = transform.file(async (f, {push}) => {
push(f.path)
push('b')
push("b")
await sleep(30)
return 'c'
return "c"
})
s.write('foo')
s.write(newFile('a'))
await testStreamItems(s, ['foo', 'a', 'b', 'c'], logger)
s.write("foo")
s.write(newFile("a"))
await testStreamItems(s, ["foo", "a", "b", "c"], logger)
})
it('will only run the fn if the input is a file', async () => {
it("will only run the fn if the input is a file", async () => {
const fn = jest.fn((f) => f)
const s = transform.file(fn)
s.write(newFile('one'))
s.write(newFile('two'))
s.write(newFile('three'))
s.write('a')
s.write('b')
s.write(newFile("one"))
s.write(newFile("two"))
s.write(newFile("three"))
s.write("a")
s.write("b")
await testStreamItems(
s,
[{path: 'one', event: 'add'}, {path: 'two', event: 'add'}, {path: 'three', event: 'add'}, 'a', 'b'],
[
{path: "one", event: "add"},
{path: "two", event: "add"},
{path: "three", event: "add"},
"a",
"b",
],
logger,
)
expect(fn.mock.calls.length).toBe(3)
})
})
it('should throw errors when they are returned', (done) => {
it("should throw errors when they are returned", (done) => {
const s = transform((f) => {
if (isEvent(f) && f === 'throw') {
return new Error('boop')
if (isEvent(f) && f === "throw") {
return new Error("boop")
}
return f
})
s.on('error', (err) => {
expect(err.message).toBe('boop')
s.on("error", (err) => {
expect(err.message).toBe("boop")
done()
})
s.write('one')
s.write('throw')
s.write("one")
s.write("throw")
})
it('should have reasonable defaults', () => {
it("should have reasonable defaults", () => {
const s = transform()
expect(s.readableHighWaterMark).toBe(16)
expect(s.writableHighWaterMark).toBe(16)

View File

@@ -1,8 +1,8 @@
import {through} from './streams'
import {through} from "./streams"
import {isEvent} from './utils'
import {EventedFile, PipelineItem} from './types'
import {DuplexOptions, Transform} from 'stream'
import {isEvent} from "./utils"
import {EventedFile, PipelineItem} from "./types"
import {DuplexOptions, Transform} from "stream"
/**
* Stream API for utilizing stream functions
@@ -26,7 +26,10 @@ type PossiblePromise<T> = T | Promise<T>
* @argument api StreamApi to manage the stream
* @returns A promise with either a Pipelineitem, an error or undefined. If a PipelineItem is returned it will be sent on. If an Error is returned it will throw an Error on the stream. If undefined is returned nothing will happen and you need to remember to call next() to process the next chunk.
*/
export type TransformFn = (file: PipelineItem, api: StreamApi) => PossiblePromise<PossibleTransformFnReturn>
export type TransformFn = (
file: PipelineItem,
api: StreamApi,
) => PossiblePromise<PossibleTransformFnReturn>
/**
* TransformFn
@@ -53,7 +56,7 @@ export function transform(
options: DuplexOptions = defaultStreamOptions,
) {
const mergedOpts = Object.assign({}, defaultStreamOptions, options)
return through(mergedOpts, async function (item: PipelineItem, _, next: StreamApi['next']) {
return through(mergedOpts, async function (item: PipelineItem, _, next: StreamApi["next"]) {
await processInput({transformFn, next, self: this, item})
})
}
@@ -63,7 +66,7 @@ transform.file = function transformFiles(
options: DuplexOptions = defaultStreamOptions,
) {
const mergedOpts = Object.assign({}, defaultStreamOptions, options)
return through(mergedOpts, async function (item: PipelineItem, _, next: StreamApi['next']) {
return through(mergedOpts, async function (item: PipelineItem, _, next: StreamApi["next"]) {
await processInput({transformFn, next, self: this, filesOnly: true, item})
})
}
@@ -76,7 +79,7 @@ async function processInput({
item,
}: {
transformFn: TransformFilesFn | TransformFn
next: StreamApi['next']
next: StreamApi["next"]
self: Transform
filesOnly?: boolean
item: PipelineItem

View File

@@ -1,9 +1,9 @@
import {Writable} from 'stream'
import {FileCache} from './helpers/file-cache'
import File from 'vinyl'
import {Writable} from "stream"
import {FileCache} from "./helpers/file-cache"
import File from "vinyl"
export type EventedFile = {
event: 'add' | 'change' | 'unlink' | 'unlinkDir'
event: "add" | "change" | "unlink" | "unlinkDir"
hash: string
} & File

View File

@@ -1,10 +1,10 @@
import File from 'vinyl'
import {PipelineEvent, EventedFile} from './types'
import File from "vinyl"
import {PipelineEvent, EventedFile} from "./types"
export function isFile(file: any): file is EventedFile {
return File.isVinyl(file)
}
export function isEvent(file: any): file is PipelineEvent {
return typeof file === 'string'
return typeof file === "string"
}

View File

@@ -1,6 +1,6 @@
import File from 'vinyl'
import File from "vinyl"
declare module 'vinyl-file' {
declare module "vinyl-file" {
type Options = {
cwd?: string
base?: string

Some files were not shown because too many files have changed in this diff Show More