diff --git a/docker-compose.yml b/docker-compose.yml index e19cef8094..ba6b0a260b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,6 +40,6 @@ volumes: driver: local verdaccio: driver: local - - mocks: storage: + driver: local + mocks: diff --git a/sources/@repo/docs/content/guides/paths.mdx b/sources/@repo/docs/content/guides/paths.mdx new file mode 100644 index 0000000000..7c60ea8bf6 --- /dev/null +++ b/sources/@repo/docs/content/guides/paths.mdx @@ -0,0 +1,7 @@ +--- +title: Pathing +description: Working with paths +slug: installation +sidebar_label: Paths +sidebar_position: 3 +--- diff --git a/sources/@repo/docs/content/guides/requirements.mdx b/sources/@repo/docs/content/guides/requirements.mdx index 80da9e6432..28bf616826 100644 --- a/sources/@repo/docs/content/guides/requirements.mdx +++ b/sources/@repo/docs/content/guides/requirements.mdx @@ -3,7 +3,7 @@ title: Requirements description: bud.js requirements slug: requirements sidebar_label: Requirements -sidebar_position: 3 +sidebar_position: 4 --- :::danger WSL required for use in Windows diff --git a/sources/@roots/bud-api/src/api/methods/proxy/index.ts b/sources/@roots/bud-api/src/api/methods/proxy/index.ts index bca903bc7c..5e3fd5da1a 100644 --- a/sources/@roots/bud-api/src/api/methods/proxy/index.ts +++ b/sources/@roots/bud-api/src/api/methods/proxy/index.ts @@ -23,13 +23,16 @@ export const enableMiddleware = ( middleware: Array, ): Array => [ ...(disableMiddleware(middleware) ?? []), + 'cookie', 'proxy', ] export const disableMiddleware = ( middleware: Array, ): Array => - middleware?.filter(middleware => middleware !== 'proxy') ?? [] + middleware?.filter( + middleware => middleware !== 'proxy' && middleware !== 'cookie', + ) ?? [] export const method: method = function (input) { const ctx = this as Framework diff --git a/sources/@roots/bud-api/src/api/methods/serve/index.ts b/sources/@roots/bud-api/src/api/methods/serve/index.ts index dc27a1d6a7..cba0f4562a 100644 --- a/sources/@roots/bud-api/src/api/methods/serve/index.ts +++ b/sources/@roots/bud-api/src/api/methods/serve/index.ts @@ -1,117 +1,42 @@ import type {Framework} from '@roots/bud-framework' -import {chokidar} from '@roots/bud-support' +import {Connection} from '@roots/bud-framework/src/Server' +import {lodash} from '@roots/bud-support' -/** - * Input object - * - * @public - */ -export interface UserRecordInput { - /** - * URL object or url string - * - * @defaultValue URL('http://localhost:3000') - */ - url?: URL | string - /** - * Path to ssl certificate - * - * @defaultValue undefined - */ - cert?: string - /** - * Path to ssl key - * - * @defaultValue undefined - */ - key?: string - /** - * Client options - */ - client?: { - /** - * Scripts to be injected before application scripts - */ - scripts: Set<(app: Framework) => string> - } - - /** - * FSWatcher - */ - watch?: { - /** - * Watched files - */ - files: Array - /** - * Watcher options - */ - options?: chokidar.WatchOptions - } -} - -export type UserInput = URL | string | number | UserRecordInput +const {isFunction} = lodash export interface method { - (input?: UserInput): Framework + ( + url: URL | string, + options?: + | Connection.Options + | ((options: Connection.Options) => Connection.Options), + ): Framework } export type facade = method -export const method: method = function (input) { - const ctx = this as Framework +export const method: method = function (input, options?) { + const app = this as Framework + + if (!app.isDevelopment) return app - if (!ctx.isDevelopment) return ctx + if (options) { + const unwrapped = isFunction(options) + ? options(app.hooks.filter('dev.options') ?? {}) + : options - if (typeof input === 'number') { - return ctx.hooks.on('dev.url', url => { - url.port = `${input}` - return url - }) + app.hooks.on('dev.options', value => + value ? {...value, ...unwrapped} : unwrapped, + ) } if (typeof input === 'string') { - return ctx.hooks.on('dev.url', new URL(input)) + return app.hooks.on('dev.url', new URL(input)) } if (input instanceof URL) { - return ctx.hooks.on('dev.url', input) + return app.hooks.on('dev.url', input) } - input.url && - ctx.hooks.on( - 'dev.url', - input.url instanceof URL ? input.url : new URL(input.url), - ) - - input.key && ctx.hooks.on('dev.ssl.key', input.key) - input.cert && ctx.hooks.on('dev.ssl.cert', input.cert) - - ctx.hooks.filter('dev.ssl.key') && - ctx.hooks.filter('dev.ssl.cert') && - ctx.hooks.on('dev.url', url => { - url.protocol = `https:` - url.port = url.port ?? `443` - return url - }) - - input.watch?.files && - ctx.hooks.on('dev.watch.files', files => { - input.watch.files.forEach(file => files.add(file)) - return files - }) - - input.watch?.options && - ctx.hooks.on('dev.watch.options', options => ({ - ...options, - ...input.watch.options, - })) - - input.client?.scripts && - ctx.hooks.on('dev.client.scripts', scripts => { - input.client.scripts.forEach(script => scripts.add(script)) - return scripts - }) - - return ctx + return app } diff --git a/sources/@roots/bud-api/src/api/methods/watch/index.ts b/sources/@roots/bud-api/src/api/methods/watch/index.ts index 5a89791299..e523f29f5f 100644 --- a/sources/@roots/bud-api/src/api/methods/watch/index.ts +++ b/sources/@roots/bud-api/src/api/methods/watch/index.ts @@ -1,33 +1,21 @@ import type {Framework} from '@roots/bud-framework' -import {chokidar} from '@roots/bud-support' export interface watch { ( /** * Watched files */ - files: Array, - /** - * Watcher options - */ - options?: chokidar.WatchOptions, + ...files: Array ): Framework } -export const watch: watch = function (files, options = {}) { - const ctx = this as Framework - - files = Array.isArray(files) ? files : [files] +export const watch: watch = function (...input) { + const app = this as Framework - ctx.hooks - .on('dev.watch.files', fileSet => { - files.forEach(file => fileSet.add(file)) - return fileSet - }) - .hooks.on('dev.watch.options', hookValue => ({ - ...(hookValue ?? {}), - ...(options ?? {}), - })) + input = Array.isArray(input) ? input : [input] + app.hooks.on('dev.watch.files', files => + input.reduce((files, file) => files.add(file), files), + ) - return ctx + return app } diff --git a/sources/@roots/bud-build/src/Build/index.ts b/sources/@roots/bud-build/src/Build/index.ts index 950df562a8..159fe3366b 100644 --- a/sources/@roots/bud-build/src/Build/index.ts +++ b/sources/@roots/bud-build/src/Build/index.ts @@ -116,7 +116,7 @@ export class Build extends Framework.Service implements Framework.Build { if (!this.app.hooks.has(`build.${key}`)) return false - const type = rest.length && rest.shift() ? 'async' : 'sync' + const type = rest.length && rest.shift() ? true : false const count = this.app.hooks.count(`build.${key}`) return [key, type, count] @@ -124,13 +124,13 @@ export class Build extends Framework.Service implements Framework.Build { @bind @memo() - public async memoMapValue([propKey, type, _count]: [ + public async memoMapValue([propKey, isAsync, _count]: [ keyof Configuration, - 'async' | 'sync', + boolean, number, ]) { const propValue = - type == 'async' + isAsync === true ? await this.app.hooks.filterAsync(`build.${propKey}` as any) : this.app.hooks.filter(`build.${propKey}` as any) diff --git a/sources/@roots/bud-extensions/src/Controller/controller.service.ts b/sources/@roots/bud-extensions/src/Controller/controller.service.ts index 80ae5c05a8..9c352f3218 100644 --- a/sources/@roots/bud-extensions/src/Controller/controller.service.ts +++ b/sources/@roots/bud-extensions/src/Controller/controller.service.ts @@ -371,13 +371,16 @@ export class Controller { await this.mixin() await this.api() - if (isFunction(this._module.register)) + if (isFunction(this._module.register)) { await this._module.register(this.app, this.moduleLogger) - this.moduleLogger.success({ - message: `register called`, - suffix: chalk.dim`${this.name}`, - }) + await this.app.api.processQueue() + + this.moduleLogger.success({ + message: `register called`, + suffix: chalk.dim`${this.name}`, + }) + } return this } @@ -505,6 +508,8 @@ export class Controller { if (isFunction(this._module.boot)) { await this._module.boot(this.app, this.moduleLogger) + await this.app.api.processQueue() + this.moduleLogger.success({ message: `${this.name} booted`, }) diff --git a/sources/@roots/bud-framework/src/Framework/framework.process.ts b/sources/@roots/bud-framework/src/Framework/framework.process.ts index ea5fc7e84c..356e50406d 100644 --- a/sources/@roots/bud-framework/src/Framework/framework.process.ts +++ b/sources/@roots/bud-framework/src/Framework/framework.process.ts @@ -8,11 +8,13 @@ const {removeSync} = fs * Render error */ const renderError = (msg: string, name?: string) => { - global.process.stderr.write( - boxen(msg, { + process.stderr.write( + boxen(`\n${msg}\n`, { title: name ?? 'error', borderStyle: 'bold', borderColor: 'red', + padding: 1, + margin: 1, }), ) } @@ -24,24 +26,26 @@ const curryHandler = function (code: number) { const ERROR = code !== 0 const close = () => { - if (ERROR) removeSync(this.path('@storage/cache')) + process.stdout.write(`\n`) - global.process.exitCode = code - global.process.exit() + try { + ERROR && removeSync(this.path('@storage/cache')) + } catch (err) {} + + process.exitCode = code + setTimeout(() => process.exit(), 100) } return (exitMessage: string | Error) => { - const exit = () => setTimeout(close, 100).unref() - - if (!ERROR) return exit() + if (!ERROR) return close() if (exitMessage instanceof Error) { renderError(exitMessage.message, exitMessage.name) } else { - renderError(exitMessage, 'error') + renderError(`\n${exitMessage}\n`, 'error') } - return exit() + return close() } } @@ -59,12 +63,12 @@ const curryHandler = function (code: number) { export const initialize = (app: Framework) => { const makeHandler = curryHandler.bind(app) - global.process + process // only works when there is no task running // because we have a server always listening port, this handler will NEVER execute .on('beforeExit', makeHandler(0)) - // only works when the global.process normally exits + // only works when the process normally exits // on windows, ctrl-c will not trigger this handler (it is unnormal) // unless you listen on 'SIGINT' .on('exit', makeHandler(0)) diff --git a/sources/@roots/bud-framework/src/Framework/index.ts b/sources/@roots/bud-framework/src/Framework/index.ts index e1f1920d04..fb3c6da920 100644 --- a/sources/@roots/bud-framework/src/Framework/index.ts +++ b/sources/@roots/bud-framework/src/Framework/index.ts @@ -730,6 +730,7 @@ export abstract class Framework { @bind public error(...messages: any[]) { this.logger.instance.error(...messages) + if (this.isProduction) { process.exitCode = 1 process.exit() diff --git a/sources/@roots/bud-framework/src/Hooks.ts b/sources/@roots/bud-framework/src/Hooks.ts index fb0655649d..8b8f2c4daa 100644 --- a/sources/@roots/bud-framework/src/Hooks.ts +++ b/sources/@roots/bud-framework/src/Hooks.ts @@ -202,19 +202,18 @@ export namespace Hooks { export interface Map extends Server.Middleware.Middleware<`options`>, Server.Middleware.Middleware<`factory`>, + Server.Connection.OptionsMap, LocationKeyMap, ConfigMap { [`extension`]: ValueOf | ValueOf - [`dev.ssl.enabled`]: boolean - [`dev.ssl.cert`]: string - [`dev.ssl.key`]: string - [`dev.ssl.port`]: number + [`dev.options`]: Server.Connection.Options [`dev.url`]: URL [`dev.watch.files`]: Set [`dev.watch.options`]: WatchOptions [`dev.client.scripts`]: Set<(app: Framework) => string> [`middleware.enabled`]: Array [`middleware.proxy.target`]: URL + [`middleware.proxy.replacements`]: Array<[string, string]> // here down is wack [key: Server.Middleware.OptionsKey]: any diff --git a/sources/@roots/bud-framework/src/Server/Connection.ts b/sources/@roots/bud-framework/src/Server/Connection.ts index 1c5fb47bb8..2ed78229e8 100644 --- a/sources/@roots/bud-framework/src/Server/Connection.ts +++ b/sources/@roots/bud-framework/src/Server/Connection.ts @@ -1,45 +1,47 @@ -import {Server as HttpServer} from 'http' -import {Server as HttpsServer} from 'https' +import { + Server as HttpServer, + ServerOptions as HttpServerOptions, +} from 'http' +import { + Server as HttpsServer, + ServerOptions as HttpsServerOptions, +} from 'https' import {IncomingMessage, ServerResponse} from 'webpack-dev-middleware' +export {HttpsServerOptions} +export {HttpServerOptions} + +export interface Options extends HttpsServerOptions, HttpServerOptions {} +export type OptionsMap = { + [K in keyof Options as `dev.options.${K & string}`]: Options[K] +} + /** * Connection */ -export interface Connection { +export interface Connection { /** * Node server - * * @public */ - instance: T + instance: HttpServer | HttpsServer /** * Server URL - * * @public */ url: URL - /** - * Server port - * - * @public - */ - get port(): number - /** * Create server - * * @remarks * Returns Node server - * * @public */ - createServer: (app: any) => Promise + createServer(app: any): Promise /** * Setup - * * @public */ setup(): Promise @@ -95,39 +97,35 @@ export interface Connection { * * @public */ -export interface Http extends Connection {} +export interface Http extends Connection {} /** * Https Connection * * @public */ -export interface Https extends Connection { +export interface Https extends Connection { /** * Has SSL key - * * @public */ hasKey(): boolean /** * Get SSL key - * * @returns */ - getKey(): Promise + getKey(): Promise /** * Has SSL certificate - * * @public */ hasCert(): boolean /** * Get SSL certificate - * * @public */ - getCert(): Promise + getCert(): Promise } diff --git a/sources/@roots/bud-framework/src/Server/Middleware.ts b/sources/@roots/bud-framework/src/Server/Middleware.ts index 69b99a9cac..4af8c1afc2 100644 --- a/sources/@roots/bud-framework/src/Server/Middleware.ts +++ b/sources/@roots/bud-framework/src/Server/Middleware.ts @@ -26,6 +26,7 @@ export type Middleware = { export interface Available { dev?: Definition> hot?: Definition + cookie?: Definition proxy?: Definition } diff --git a/sources/@roots/bud-hooks/src/Hooks/index.ts b/sources/@roots/bud-hooks/src/Hooks/index.ts index e6cec941f2..0271331948 100644 --- a/sources/@roots/bud-hooks/src/Hooks/index.ts +++ b/sources/@roots/bud-hooks/src/Hooks/index.ts @@ -188,7 +188,10 @@ export class Hooks extends Service implements Contract { | ((value: Contract.Map[T]) => Contract.Map[T]) | Contract.Map[T], ) => { - return isFunction(current) ? current(accumulated) : current + const next = isFunction(current) ? current(accumulated) : current + if (this.app.context.args.debug) + this.app.info(`hooks.filter`, id, `=>`, next) + return next }, value, ) diff --git a/sources/@roots/bud-preset-wordpress/src/index.ts b/sources/@roots/bud-preset-wordpress/src/index.ts index 0efbee1107..88f9a76f7c 100644 --- a/sources/@roots/bud-preset-wordpress/src/index.ts +++ b/sources/@roots/bud-preset-wordpress/src/index.ts @@ -10,7 +10,7 @@ * @packageDocumentation */ -import type {Extension} from '@roots/bud-framework' +import type {Extension, Framework} from '@roots/bud-framework' declare module '@roots/bud-framework' { interface Modules { @@ -36,4 +36,26 @@ type BudWordPressPreset = Extension.Module export const name: BudWordPressPreset['name'] = '@roots/bud-preset-wordpress' +export const boot = async (app: Framework) => { + app.hooks.on( + 'middleware.proxy.replacements', + (replacements): Array<[string, string]> => { + const proxy = app.hooks.filter('middleware.proxy.target').origin + const dev = app.server.connection.url.origin + + return [ + ...(replacements ?? []), + [ + ` void +} + +interface BaseOptions { + autoConnect: boolean + timeout: number + overlay: boolean + reload: boolean + log: boolean + warn: boolean + name: string + overlayWarnings: boolean + path: string +} + +interface Options extends BaseOptions { + 'bud.overlay': boolean + 'bud.indicator': boolean +} + ;(async (query: string) => { const querystring = await import('querystring') const hmr = await import('./bridge') - const {IndicatorController} = await import( - './indicator/indicator.controller' - ) - const {OverlayController} = await import('./overlay/overlay.controller') - const indicator = new IndicatorController() - const overlay = new OverlayController() + const controllers: Array = [] - const instance = { - path: '/__bud/hmr', + const FALLBACK_OPTS: Options = { + ['bud.overlay']: true, + ['bud.indicator']: true, + autoConnect: false, timeout: 20 * 1000, - overlay: true, + overlay: false, + reload: false, log: false, - warn: true, + warn: false, name: '', - autoConnect: false, overlayWarnings: false, - ...querystring.parse(query.slice(1)), + path: '/__bud/hmr', } - hmr.setOptionsAndConnect(instance) + const options: Options = Object.entries( + querystring.parse(query.slice(1)), + ).reduce((a: Options, [k, v]: [keyof Options, any]) => { + if (v === 'true') v = true + if (v === 'false') v = false + return {...a, [k]: v} + }, FALLBACK_OPTS) + + hmr.setOptionsAndConnect(options) + + const registerIndicator = async () => { + if (!options['bud.indicator']) return + const controllerModule = await import('./indicator/index.js') + const controller = await controllerModule.make() + controller?.update && controllers.push(controller) + } + + const registerOverlay = async () => { + if (!options['bud.overlay']) return + const controllerModule = await import('./overlay/index.js') + const controller = await controllerModule.make() + controller?.update && controllers.push(controller) + } + + await registerIndicator() + await registerOverlay() + hmr.subscribeAll(payload => { - if (payload.action === 'reload') window.location.reload() + console.table({ + name: payload.name, + action: payload.action, + hash: payload.hash, + }) - indicator.update(payload) - overlay.update(payload) + payload.warnings.map(console.warn) + payload.errors.map(console.error) - // eslint-disable-next-line no-console - console.log( - `%c[bud]%c %c${payload.action}`, - 'background: #525ddc; color: #ffffff;', - 'background: transparent;', - 'background: white; color: #343a40;', - ) + controllers.map(controller => controller.update(payload)) + + if (payload.action === 'reload') window.location.reload() }) })( // @ts-ignore diff --git a/sources/@roots/bud-server/src/client/indicator/index.ts b/sources/@roots/bud-server/src/client/indicator/index.ts new file mode 100644 index 0000000000..2c74c72a53 --- /dev/null +++ b/sources/@roots/bud-server/src/client/indicator/index.ts @@ -0,0 +1,10 @@ +export const make = async (): Promise<{update: (data) => void}> => { + const {Controller} = await import('./indicator.controller') + const {Component} = await import('./indicator.component') + + if (customElements.get('bud-activity-indicator')) return + + customElements.define('bud-activity-indicator', Component) + + return new Controller() +} diff --git a/sources/@roots/bud-server/src/client/indicator/indicator.component.ts b/sources/@roots/bud-server/src/client/indicator/indicator.component.ts index 96071b3dca..4d481d8192 100644 --- a/sources/@roots/bud-server/src/client/indicator/indicator.component.ts +++ b/sources/@roots/bud-server/src/client/indicator/indicator.component.ts @@ -4,7 +4,7 @@ import {pulse} from './indicator.pulse' * Indicator web component * @public */ -export class IndicatorComponent extends HTMLElement { +export class Component extends HTMLElement { /** * Has component rendered * @public diff --git a/sources/@roots/bud-server/src/client/indicator/indicator.controller.ts b/sources/@roots/bud-server/src/client/indicator/indicator.controller.ts index 8f8dde0205..3c92160a05 100644 --- a/sources/@roots/bud-server/src/client/indicator/indicator.controller.ts +++ b/sources/@roots/bud-server/src/client/indicator/indicator.controller.ts @@ -1,10 +1,8 @@ -import {IndicatorComponent} from './indicator.component' - /** * Activity indicator controller * @public */ -export class IndicatorController { +export class Controller { /** * DOM node * @public @@ -24,7 +22,6 @@ export class IndicatorController { public constructor() { this.node = document.createElement('bud-activity-indicator') document.body && document.body.appendChild(this.node) - customElements.define('bud-activity-indicator', IndicatorComponent) } /** diff --git a/sources/@roots/bud-server/src/client/inject.ts b/sources/@roots/bud-server/src/client/inject.ts index 1db7b9c2bd..90a5a0e6c8 100644 --- a/sources/@roots/bud-server/src/client/inject.ts +++ b/sources/@roots/bud-server/src/client/inject.ts @@ -30,6 +30,7 @@ export const inject: inject = async (instance: Framework, injection) => { `${instance.name} entrypoints are malformed`, `skipping inject`, ) + return entrypoints } diff --git a/sources/@roots/bud-server/src/client/overlay/index.ts b/sources/@roots/bud-server/src/client/overlay/index.ts new file mode 100644 index 0000000000..541eb285a6 --- /dev/null +++ b/sources/@roots/bud-server/src/client/overlay/index.ts @@ -0,0 +1,10 @@ +export const make = async (): Promise<{update: (data) => void}> => { + const {Controller} = await import('./overlay.controller') + const {Component} = await import('./overlay.component') + + if (customElements.get('bud-error')) return + + customElements.define('bud-error', Component) + + return new Controller() +} diff --git a/sources/@roots/bud-server/src/client/overlay/overlay.component.ts b/sources/@roots/bud-server/src/client/overlay/overlay.component.ts index a9b23e7c50..f7131b8185 100644 --- a/sources/@roots/bud-server/src/client/overlay/overlay.component.ts +++ b/sources/@roots/bud-server/src/client/overlay/overlay.component.ts @@ -55,6 +55,7 @@ export class Component extends HTMLElement { } .${this.name}__visible { backdrop-filter: blur(10px); + background: rgba(255, 255, 255, 0.75); border-top: 3px solid red; display: flex; align-items: center; diff --git a/sources/@roots/bud-server/src/client/overlay/overlay.controller.ts b/sources/@roots/bud-server/src/client/overlay/overlay.controller.ts index cb5364ef43..eb3282ba8f 100644 --- a/sources/@roots/bud-server/src/client/overlay/overlay.controller.ts +++ b/sources/@roots/bud-server/src/client/overlay/overlay.controller.ts @@ -1,8 +1,6 @@ import stripAnsi from 'strip-ansi' import {StatsError} from 'webpack' -import {Component} from './overlay.component' - interface Payload { hash: string errors: Array @@ -12,7 +10,7 @@ interface Payload { * Overlay controller * @public */ -export class OverlayController { +export class Controller { /** * Element * @public @@ -45,9 +43,6 @@ export class OverlayController { * @public */ public constructor() { - !customElements.get('bud-error') && - customElements.define('bud-error', Component) - this.element = document.createElement('bud-error') document.body && document.body.appendChild(this.element) } diff --git a/sources/@roots/bud-server/src/client/proxy-click-interceptor.js b/sources/@roots/bud-server/src/client/proxy-click-interceptor.js deleted file mode 100644 index 04a893a5f1..0000000000 --- a/sources/@roots/bud-server/src/client/proxy-click-interceptor.js +++ /dev/null @@ -1,36 +0,0 @@ -/* global __resourceQuery */ -/* eslint-disable no-console, no-extra-semi */ -// @ts-check - -;(async () => { - const main = async () => { - const {headers} = await fetch(window.location.origin, { - method: 'GET', - }) - - const origin = { - proxy: headers.get('x-bud-proxy-origin'), - dev: headers.get('x-bud-dev-origin'), - } - - if (!origin.proxy || !origin.dev) return - - document.addEventListener('click', event => { - // @ts-ignore - const el = event.target.closest('a') - const href = el?.getAttribute('href') - - if (!href || href.includes(origin.dev)) return - - Object.assign(el, { - href: href.replace(origin.proxy, origin.dev), - }) - }) - } - - window.requestAnimationFrame(async function ready() { - return document.body - ? await main() - : window.requestAnimationFrame(ready) - }) -})() diff --git a/sources/@roots/bud-server/src/client/proxy-click-interceptor.ts b/sources/@roots/bud-server/src/client/proxy-click-interceptor.ts new file mode 100644 index 0000000000..27d040f5b7 --- /dev/null +++ b/sources/@roots/bud-server/src/client/proxy-click-interceptor.ts @@ -0,0 +1,54 @@ +/* global __resourceQuery */ +/* eslint-disable no-console, no-extra-semi */ +// @ts-check + +type Target = HTMLAnchorElement | HTMLLinkElement | HTMLFormElement +type ElementTuple = [HTMLCollectionOf, any] +type Collection = Array + +type Task = [Array, any] + +const collect = (): Collection => [ + [document.getElementsByTagName('a'), 'href'], + [document.getElementsByTagName('link'), 'href'], + [document.getElementsByTagName('form'), 'action'], +] + +const main = async () => { + const {headers} = await fetch(window.location.origin, { + method: 'GET', + }) + + const origin = { + proxy: headers.get('x-bud-proxy-origin'), + dev: headers.get('x-bud-dev-origin'), + } + + const normalize = ([elements, attribute]): [Array, any] => [ + Array.from(elements), + attribute, + ] + + const filter = ([elements, attribute]: [Array, any]) => + elements.filter(el => + el.getAttribute(attribute)?.includes(origin.proxy), + ).length + + const mapElements = ([elements, attribute]: [Array, any]) => { + elements.map(el => { + const value = el.getAttribute(attribute) + if (!value) return + + el.setAttribute(attribute, value.replace(origin.proxy, origin.dev)) + }) + } + + collect().map(normalize).filter(filter).map(mapElements) +} + +;(async () => + window.requestAnimationFrame(async function ready() { + return document.body + ? await main() + : window.requestAnimationFrame(ready) + }))() diff --git a/sources/@roots/bud-server/src/middleware/cookie/index.ts b/sources/@roots/bud-server/src/middleware/cookie/index.ts new file mode 100644 index 0000000000..188b292566 --- /dev/null +++ b/sources/@roots/bud-server/src/middleware/cookie/index.ts @@ -0,0 +1,13 @@ +import {Framework} from '@roots/bud-framework/src' +import cookieParserMiddleware from 'cookie-parser' + +/** + * cookie middleware factory + * + * @public + */ +export interface cookie { + (app: Framework): any +} + +export const cookie = (app: Framework) => cookieParserMiddleware() diff --git a/sources/@roots/bud-server/src/middleware/index.ts b/sources/@roots/bud-server/src/middleware/index.ts index 81b93746da..8c5cbae0b5 100644 --- a/sources/@roots/bud-server/src/middleware/index.ts +++ b/sources/@roots/bud-server/src/middleware/index.ts @@ -1,3 +1,4 @@ export {dev} from './dev' export {hot} from './hot' +export {cookie} from './cookie' export {proxy} from './proxy' diff --git a/sources/@roots/bud-server/src/middleware/proxy/index.ts b/sources/@roots/bud-server/src/middleware/proxy/index.ts index 939c0030ee..56daebf810 100644 --- a/sources/@roots/bud-server/src/middleware/proxy/index.ts +++ b/sources/@roots/bud-server/src/middleware/proxy/index.ts @@ -1,5 +1,5 @@ import type {Framework} from '@roots/bud-framework' -import {createProxyMiddleware} from 'http-proxy-middleware' +import {createProxyMiddleware, Options} from 'http-proxy-middleware' import {RequestInterceptorFactory} from './req.interceptor' import {ResponseInterceptorFactory} from './res.interceptor' @@ -12,102 +12,122 @@ import {ApplicationURL} from './url' */ export const proxy = (app: Framework) => { const url = new ApplicationURL(() => app) - const interceptor = new ResponseInterceptorFactory(() => app, url) + const response = new ResponseInterceptorFactory(() => app, url) const request = new RequestInterceptorFactory(() => app, url) - return createProxyMiddleware( - app.hooks.filter(`middleware.proxy.options`, { - /** - * Change origin - */ - changeOrigin: app.hooks.filter( - `middleware.proxy.options.changeOrigin`, - true, - ), - - /** - * Cookie domain rewrite - */ - cookieDomainRewrite: app.hooks.filter( - `middleware.proxy.options.cookieDomainRewrite`, - { - [url.proxy.host]: url.dev.host, - }, - ), - - /** - * Headers - */ - headers: app.hooks.filter(`middleware.proxy.options.headers`, { - [`X-Proxy-By`]: `@roots/bud`, - [`Connection`]: `keep-alive`, - [`Access-Control-Allow-Origin`]: `*`, - [`Access-Control-Allow-Credentials`]: `*`, - [`Access-Control-Allow-Methods`]: `*`, + const options: Options = { + autoRewrite: app.hooks.filter( + 'middleware.proxy.options.autoRewrite', + true, + ), + + /** + * Change origin + */ + changeOrigin: app.hooks.filter( + `middleware.proxy.options.changeOrigin`, + true, + ), + + /** + * Cookie domain rewrite + */ + cookieDomainRewrite: app.hooks.filter( + `middleware.proxy.options.cookieDomainRewrite`, + url.dev.host, + ), + + /** + * Follow redirects + */ + followRedirects: app.hooks.filter( + `middleware.proxy.options.followRedirects`, + false, + ), + + /** + * Headers + */ + headers: { + ...app.hooks.filter(`middleware.proxy.options.headers`, { + connection: 'keep-alive', + 'access-control-allow-origin': `*`, + 'access-control-allow-credentials': `*`, + 'access-control-allow-methods': `*`, }), + 'x-proxy-by': '@roots/bud', + 'x-bud-dev-origin': url.dev.origin, + 'x-bud-dev-protocol': url.dev.protocol, + 'x-bud-dev-hostname': url.dev.hostname, + 'x-bud-proxy-origin': url.proxy.origin, + }, + + /** + * Host rewrite + */ + hostRewrite: app.hooks.filter( + `middleware.proxy.options.hostRewrite`, + url.dev.host, + ), + + /** + * Log level + */ + logLevel: app.hooks.filter( + `middleware.proxy.options.logLevel`, + `info`, + ), + + /** + * Log provider + */ + logProvider: () => app.logger.instance.scope('proxy'), + + /** + * Proxy request handler + */ + onProxyReq: app.hooks.filter( + `middleware.proxy.options.onProxyReq`, + request.make, + ), + + /** + * Proxy response handler + */ + onProxyRes: app.hooks.filter( + `middleware.proxy.options.onProxyRes`, + response.make, + ), + + /** + * Protocol rewrite + */ + protocolRewrite: app.hooks.filter( + `middleware.proxy.options.protocolRewrite`, + url.dev.protocol?.startsWith('https') ? 'https' : undefined, + ), + + /** + * Secure + */ + secure: app.hooks.filter(`middleware.proxy.options.secure`, false), + + /** + * Self handle response + */ + selfHandleResponse: app.hooks.filter( + `middleware.proxy.options.selfHandleResponse`, + true, + ), + + /** + * Target + */ + target: app.hooks.filter('middleware.proxy.target', url.proxy), + } + + const {log} = app.logger.instance.scope('proxy') + Object.entries(options).map(v => log(...v)) - /** - * Host rewrite - */ - hostRewrite: app.hooks.filter( - `middleware.proxy.options.hostRewrite`, - url.dev.host, - ), - - /** - * Log level - */ - logLevel: app.hooks.filter( - `middleware.proxy.options.logLevel`, - `info`, - ), - - /** - * Log provider - */ - logProvider: () => app.logger.instance.scope('proxy'), - - /** - * Proxy request handler - */ - onProxyReq: app.hooks.filter( - `middleware.proxy.options.onProxyReq`, - request.make, - ), - - /** - * Proxy response handler - */ - onProxyRes: app.hooks.filter( - `middleware.proxy.options.onProxyRes`, - interceptor.make, - ), - - /** - * Protocol rewrite - */ - protocolRewrite: app.hooks.filter( - `middleware.proxy.options.protocolRewrite`, - url.dev.protocol?.startsWith('https') ? 'https' : undefined, - ), - - /** - * Secure - */ - secure: app.hooks.filter(`middleware.proxy.options.secure`, false), - - /** - * Self handle response - */ - selfHandleResponse: app.hooks.filter( - `middleware.proxy.options.selfHandleResponse`, - true, - ), - - /** - * Target - */ - target: app.hooks.filter('middleware.proxy.target'), - }), - ) + return createProxyMiddleware(options) } diff --git a/sources/@roots/bud-server/src/middleware/proxy/res.interceptor.ts b/sources/@roots/bud-server/src/middleware/proxy/res.interceptor.ts index f02beb5f18..94a4a49aba 100644 --- a/sources/@roots/bud-server/src/middleware/proxy/res.interceptor.ts +++ b/sources/@roots/bud-server/src/middleware/proxy/res.interceptor.ts @@ -1,10 +1,26 @@ import {Framework} from '@roots/bud-framework' import {bind} from '@roots/bud-support' -import {IncomingMessage, ServerResponse} from 'http' +import * as http from 'http' import {responseInterceptor} from 'http-proxy-middleware' import {ApplicationURL} from './url' +interface IncomingMessage extends http.IncomingMessage { + cookies: any +} +interface ServerResponse extends http.ServerResponse { + cookie: any +} + +export interface ResponseInterceptorFactory { + interceptor( + buffer: Buffer, + proxyRes: IncomingMessage, + request: IncomingMessage, + response: ServerResponse, + ): Promise +} + /** * Proxy response interceptor * @@ -36,30 +52,39 @@ export class ResponseInterceptorFactory { * @remarks * This is the callback for `http-proxy-middleware`s `responseInterceptor`. * It is called after the response has been received from the target server. - * It is passed the response body, the response object, and the request object. + * It is passed the response body, and the req and res objects. * It can be used to modify the response body or the response object. * - * @param buffer - Buffered response body + * @param buffer - Buffered response * @param proxyRes - Response from the proxy * @param req - Request from the client * @param res - Response from the server - * @returns client response body * * @public * @decorator `@bind` */ @bind - public async _interceptor( + public async interceptor( buffer: Buffer, - _proxyRes: IncomingMessage, - _req: IncomingMessage, - res: ServerResponse, - ): Promise { - res.setHeader('x-proxy-by', '@roots/bud') - res.setHeader('x-bud-proxy-origin', this.url.proxy.origin) - res.setHeader('x-bud-dev-origin', this.url.dev.origin) + proxyRes: IncomingMessage, + request: IncomingMessage, + response: ServerResponse, + ): Promise { + response.setHeader('x-proxy-by', '@roots/bud') + response.setHeader('x-bud-proxy-origin', this.url.proxy.origin) + response.setHeader('x-bud-dev-origin', this.url.dev.origin) + response.removeHeader('x-http-method-override') + + Object.entries(request.cookies).map(([k, v]) => + response.cookie(k, v, {domain: null}), + ) - return buffer + return this.app.hooks + .filter('middleware.proxy.replacements') + .reduce( + (buffer, [find, replace]) => buffer.replaceAll(find, replace), + buffer.toString(), + ) } /** @@ -70,6 +95,6 @@ export class ResponseInterceptorFactory { */ @bind public make() { - return responseInterceptor(this._interceptor) + return responseInterceptor(this.interceptor) } } diff --git a/sources/@roots/bud-server/src/seed.ts b/sources/@roots/bud-server/src/seed.ts index be3a209c52..43a978b418 100644 --- a/sources/@roots/bud-server/src/seed.ts +++ b/sources/@roots/bud-server/src/seed.ts @@ -41,12 +41,13 @@ export const seed = (app: Framework) => { ) .hooks.on(`middleware.hot.options.heartbeat`, 2000) - .hooks.on(`middleware.proxy.target`, new URL(`http://localhost`)) - .hooks.on( `dev.client.scripts`, new Set([ - app => src(`index.js?name=${app.name}&path=/__bud/hmr`), + app => + src( + `index.js?name=${app.name}&bud.overlay=${app.context.args.overlay}&bud.indicator=${app.context.args.indicator}&path=/__bud/hmr`, + ), () => src(`proxy-click-interceptor.js`), ]), ) diff --git a/sources/@roots/bud-server/src/server/index.ts b/sources/@roots/bud-server/src/server/index.ts index a8681b6836..d6568882d6 100644 --- a/sources/@roots/bud-server/src/server/index.ts +++ b/sources/@roots/bud-server/src/server/index.ts @@ -122,8 +122,9 @@ export class Server @once public async setConnection() { this.connection = - this.app.hooks.filter('dev.ssl.cert') && - this.app.hooks.filter('dev.ssl.key') + this.app.hooks.filter('dev.options') && + this.app.hooks.filter('dev.options').cert && + this.app.hooks.filter('dev.options').key ? new Https(this.app, this.app.hooks.filter('dev.url')) : new Http(this.app, this.app.hooks.filter('dev.url')) diff --git a/sources/@roots/bud-server/src/server/server.base.ts b/sources/@roots/bud-server/src/server/server.base.ts index 58673af405..3e56d19b1f 100644 --- a/sources/@roots/bud-server/src/server/server.base.ts +++ b/sources/@roots/bud-server/src/server/server.base.ts @@ -1,4 +1,4 @@ -import {Framework} from '@roots/bud-framework' +import {Framework, Server} from '@roots/bud-framework' import {Connection} from '@roots/bud-framework/src/Server/Connection' import {bind, getPort, Signale} from '@roots/bud-support' import {IncomingMessage, Server as HttpServer} from 'http' @@ -7,60 +7,57 @@ import {ServerResponse} from 'webpack-dev-middleware' /** * HTTP Server - * * @public */ -export abstract class BaseServer implements Connection { - public abstract createServer: Connection['createServer'] +export abstract class BaseServer implements Connection { + public abstract createServer(app: any): Promise /** * Server instance - * * @public */ - public instance: T & (HttpServer | HttpsServer) + public instance: Connection['instance'] /** - * Port number - * + * Logger * @public */ - public port: number + public logger: Signale /** - * Logger - * + * Options * @public */ - public logger: Signale + public get options(): Server.Connection.Options { + return this.app.hooks.filter(`dev.options`) + } /** * Constructor - * * @param app - Framework + * @public */ public constructor(public app: Framework, public url: URL) { - this.logger = this.app.logger.instance + this.logger = this.app.logger.instance.scope(this.url.host) } /** * setup - * * @public + * @decorator `@bind` */ @bind public async setup() { const port = await getPort({port: Number(this.url.port)}) this.url.port = `${port}` this.app.hooks.on('dev.url', this.url) - - this.logger.log(this.url.toString()) + this.logger.log('url', this.url) } /** * Listen - * * @public + * @decorator `@bind` */ @bind public async listen() { @@ -73,17 +70,16 @@ export abstract class BaseServer implements Connection { /** * Server listen event - * * @public + * @decorator `@bind` */ @bind - public onListening() { - this.logger.info(`listening`) + public onListening(...param: any[]) { + this.logger.info(`listening`, ...param) } /** * Server request - * * @public * @decorator `@bind` */ @@ -92,26 +88,17 @@ export abstract class BaseServer implements Connection { request: IncomingMessage, response: ServerResponse, ) { - if (request.headers['bud-healthcheck']) return response - - if (response.statusCode === 200) { - this.logger.success([response.statusCode], request.url) - return response - } - - if (response.statusCode === 500) { - this.logger.error([response.statusCode], request.url) - return response - } + this.logger.log( + `[${response.statusCode}]`, + request.url, + response.statusMessage ?? '', + ) return response } /** * Server error - * - * @param error - error - * * @public * @decorator `@bind` */ diff --git a/sources/@roots/bud-server/src/server/server.http.ts b/sources/@roots/bud-server/src/server/server.http.ts index 572a7339e1..f13f47df80 100644 --- a/sources/@roots/bud-server/src/server/server.http.ts +++ b/sources/@roots/bud-server/src/server/server.http.ts @@ -1,27 +1,33 @@ import {Server} from '@roots/bud-framework' -import {createServer, Server as HttpServer} from 'http' +import {bind} from '@roots/bud-support' +import {createServer, RequestListener, Server as HttpServer} from 'http' import {BaseServer} from './server.base' /** * HTTP Server - * * @public */ -export class Http - extends BaseServer - implements Server.Connection.Http -{ +export class Http extends BaseServer implements Server.Connection.Http { + /** + * Server instance + * @public + */ + public instance: HttpServer + /** * createServer * - * @param app - Express application + * @param express - Express application * @returns http.Server + * @public + * @decorator `@bind` */ - public createServer = function ( - app: Express.Application, + @bind + public async createServer( + express: RequestListener, ): Promise { - this.instance = createServer(app) + this.instance = createServer(this.options, express) return this.instance } } diff --git a/sources/@roots/bud-server/src/server/server.https.ts b/sources/@roots/bud-server/src/server/server.https.ts index d252c77953..433b1674b5 100644 --- a/sources/@roots/bud-server/src/server/server.https.ts +++ b/sources/@roots/bud-server/src/server/server.https.ts @@ -1,19 +1,18 @@ import {Server} from '@roots/bud-framework' -import {fs} from '@roots/bud-support' +import {bind, fs, lodash} from '@roots/bud-support' +import {RequestListener} from 'http' import {createServer, Server as HttpsServer} from 'https' import {BaseServer} from './server.base' const {readFile} = fs +const {isUndefined} = lodash /** * HTTPS Server * @public */ -export class Https - extends BaseServer - implements Server.Connection.Https -{ +export class Https extends BaseServer implements Server.Connection.Https { /** * Server instance * @public @@ -21,49 +20,90 @@ export class Https public instance: HttpsServer /** - * Has SSL key + * Has options + * @returns boolean * @public + * @decorator `@bind` */ - public hasKey(): boolean { - return this.app.hooks.filter('dev.ssl.key') ? true : false + @bind + public hasOptions(): boolean { + return Object.keys(this.options).length > 0 } /** - * Get SSL key - * @returns + * Has SSL key + * @public */ - public async getKey(): Promise { - !this.hasKey() && this.app.error('Server key is not defined') - return await readFile(this.app.hooks.filter('dev.ssl.key'), 'utf8') + @bind + public hasKey(): boolean { + return this.hasOptions() && !isUndefined(this.options.key) } /** * Has SSL certificate * @public */ + @bind public hasCert(): boolean { - return this.app.hooks.filter('dev.ssl.cert') ? true : false + return this.hasOptions() && !isUndefined(this.options.cert) + } + + /** + * Get SSL key + * @returns + */ + @bind + public async getKey(): Promise { + if (!this.hasKey()) { + this.app.warn('Server key is not defined') + return + } + + try { + return await readFile(this.options.key as string, 'utf8') + } catch (err) { + this.app.error(err) + } } /** * Get SSL certificate * @public + * @decorator `@bind` */ - public async getCert(): Promise { - !this.hasCert() && this.app.error('Server cert is not defined') - return await readFile(this.app.hooks.filter('dev.ssl.cert'), 'utf8') + @bind + public async getCert(): Promise { + if (!this.hasKey()) { + this.app.warn('Server key is not defined') + return + } + + try { + return await readFile(this.options.cert as string, 'utf8') + } catch (err) { + this.app.error(err) + } } /** * Create HTTPS server * @public + * @decorator `@bind` */ - public createServer = async function (app: any): Promise { + @bind + public async createServer( + express: RequestListener & Express.Application, + ): Promise { + if (!this.hasOptions()) { + this.instance = createServer(express) + return this.instance + } + const key = await this.getKey() const cert = await this.getCert() + const options = {...this.options, ...({key} ?? {}), ...({cert} ?? {})} - this.instance = createServer({key, cert}, app) - + this.instance = createServer(options, express) return this.instance } } diff --git a/sources/@roots/bud/src/cli/commands/build.ts b/sources/@roots/bud/src/cli/commands/build.ts index a51489f2b5..03b3f9a24c 100644 --- a/sources/@roots/bud/src/cli/commands/build.ts +++ b/sources/@roots/bud/src/cli/commands/build.ts @@ -90,6 +90,11 @@ export class BuildCommand extends BaseCommand { hidden: true, }) + public debug = Option.Boolean(`--debug`, false, { + description: + 'Enable debugging mode. Very verbose logging. Writes output files to `@storage` directory', + }) + /** * --devtool */ @@ -148,17 +153,17 @@ export class BuildCommand extends BaseCommand { }) /** - * --log + * --indicator */ - public log = Option.Boolean(`--log`, undefined, { - description: 'Enable logging', + public indicator = Option.Boolean(`--indicator`, true, { + description: 'Enable development status indicator', }) /** - * --verbose + * --log */ - public verbose = Option.Boolean(`--verbose`, false, { - description: 'Set logging level', + public log = Option.Boolean(`--log`, undefined, { + description: 'Enable logging', }) /** @@ -175,12 +180,25 @@ export class BuildCommand extends BaseCommand { description: 'Minimize compiled assets', }) + /** + * --modules + */ public modules = Option.String(`--modules`, undefined, { description: 'Module resolution path', }) + /** + * --notify + */ public notify = Option.Boolean(`--notify`, true, { - description: 'Allow OS notifications', + description: 'Enable notfication center messages', + }) + + /** + * --overlay + */ + public overlay = Option.Boolean(`--overlay`, true, { + description: 'Enable error overlay in development mode', }) /** @@ -208,6 +226,13 @@ export class BuildCommand extends BaseCommand { description: 'Limit compilation to particular compilers', }) + /** + * --verbose + */ + public verbose = Option.Boolean(`--verbose`, false, { + description: 'Set logging level', + }) + /** * Execute command */ @@ -216,28 +241,32 @@ export class BuildCommand extends BaseCommand { this.context.stdout.write( `the --dashboard and --no-dashboard flags are deprecated and will be removed in a future release.\n`, ) - - this.context.args = { - cache: this.cache ?? null, - clean: this.clean ?? null, - devtool: this.devtool ?? null, - dist: this.dist ?? null, - flush: this.flush ?? null, - hash: this.hash ?? null, - html: this.html ?? null, - inject: this.inject ?? null, - log: this.log ?? null, - verbose: this.verbose ?? null, - manifest: this.manifest ?? null, - minimize: this.minimize ?? null, - mode: this.mode ?? null, - modules: this.modules ?? null, - notify: this.notify ?? null, - publicPath: this.publicPath ?? null, - src: this.src ?? null, - splitChunks: this.splitChunks ?? null, - target: this.target ?? null, - } + ;[ + 'cache', + 'ci', + 'clean', + 'debug', + 'devtool', + 'flush', + 'hash', + 'html', + 'indicator', + 'inject', + 'log', + 'manifest', + 'minimize', + 'mode', + 'modules', + 'notify', + 'overlay', + 'publicPath', + 'src', + 'splitChunks', + 'target', + 'verbose', + ].map(arg => { + this.context.args[arg] = isUndefined(arg) ? null : this[arg] + }) this.app = await factory({ name: 'bud', diff --git a/sources/@roots/bud/src/context/env.ts b/sources/@roots/bud/src/context/env.ts index 12172a1481..9a8ca2f1a2 100644 --- a/sources/@roots/bud/src/context/env.ts +++ b/sources/@roots/bud/src/context/env.ts @@ -11,18 +11,22 @@ export class Env { * @returns */ public constructor(baseDirectory: string) { - const {parsed, error} = dotenv.config({ - path: join(baseDirectory, '.env'), - }) - if (error || !parsed) return + const paths = baseDirectory.split('/') - const expanded = dotenvExpand({ - ...parsed, - }) - if (!expanded) return + Object.entries(process.env).map(([k, v]) => (this[k] = v)) - Object.entries(expanded).map(([k, v]) => { - this[k] = v - }) + const get = (path: string) => { + const {parsed, error} = dotenv.config({path}) + if (error || !parsed) return + const expanded = dotenvExpand(parsed) + if (!expanded) return + Object.entries(expanded).map(([k, v]) => (this[k] = v)) + } + + paths.reduce((a, c) => { + const next = join(a, c) + get(join(next, '.env')) + return next + }, `/`) } } diff --git a/sources/@roots/bud/src/services/Env/index.ts b/sources/@roots/bud/src/services/Env/index.ts index 925a265180..5d1ca5515f 100644 --- a/sources/@roots/bud/src/services/Env/index.ts +++ b/sources/@roots/bud/src/services/Env/index.ts @@ -10,13 +10,6 @@ const {isString} = lodash * @public */ export class Env extends Service implements Base { - /** - * Service ident - * - * @internal - */ - public ident = 'env' - /** * Bootstrap event callback * diff --git a/tests/unit/bud-api/repository/__snapshots__/watch.test.ts.snap b/tests/unit/bud-api/repository/__snapshots__/watch.test.ts.snap index 137121e2f0..b27d4f5128 100644 --- a/tests/unit/bud-api/repository/__snapshots__/watch.test.ts.snap +++ b/tests/unit/bud-api/repository/__snapshots__/watch.test.ts.snap @@ -1,27 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`bud.watch merges watch files 1`] = ` +exports[`bud.watch sets watch files: **/*.js 1`] = ` Array [ - "**/*.js", - "foo/*.js", -] -`; - -exports[`bud.watch merges watch options 1`] = ` -Object { - "cwd": "/srv/www", - "depth": 1, -} -`; - -exports[`bud.watch set watch options 1`] = ` -Object { - "depth": 1, -} -`; - -exports[`bud.watch sets watch files 1`] = ` -Array [ - "**/*.js", + Array [ + "**/*.js", + ], ] `; diff --git a/tests/unit/bud-api/repository/server.test.ts b/tests/unit/bud-api/repository/server.test.ts index fede6be936..3889d8b0d2 100644 --- a/tests/unit/bud-api/repository/server.test.ts +++ b/tests/unit/bud-api/repository/server.test.ts @@ -14,6 +14,7 @@ describe('bud.serve', function () { it('sets URL from string', async () => { bud.serve('http://example.com') + await bud.api.processQueue() expect(bud.hooks.filter('dev.url')).toStrictEqual( @@ -23,47 +24,20 @@ describe('bud.serve', function () { it('sets URL from URL', async () => { const testUrl = new URL('http://test-url.com') + bud.serve(testUrl) + await bud.api.processQueue() expect(bud.hooks.filter('dev.url')).toStrictEqual(testUrl) }) - it('sets URL from port number', async () => { - const port = 3000 - await bud.api.call('serve', port) - - expect(bud.hooks.filter('dev.url').origin).toStrictEqual( - `http://localhost:${port}`, - ) - }) - - it('sets URL from url prop', async () => { + it('sets options', async () => { const url = new URL('http://test-url.com') - await bud.api.call('serve', {url}) - expect(bud.hooks.filter('dev.url')).toStrictEqual(url) - }) - - it('registers cert with prop', async () => { - await bud.api.call('serve', {cert: 'foo'}) - expect(bud.hooks.filter('dev.ssl.cert')).toStrictEqual('foo') - }) + const options = {cert: 'foo', key: 'bar'} + await bud.api.call('serve', url, options) - it('registers key with prop', async () => { - await bud.api.call('serve', {key: 'foo'}) - expect(bud.hooks.filter('dev.ssl.key')).toStrictEqual('foo') - }) - - it('registers key with prop', async () => { - const props = { - watch: { - files: ['foo', 'bar'], - }, - } - await bud.api.call('serve', props) - expect(bud.hooks.filter('dev.watch.files')).toStrictEqual( - new Set(props.watch.files), - ) + expect(bud.hooks.filter('dev.options')).toStrictEqual(options) }) }) diff --git a/tests/unit/bud-api/repository/watch.test.ts b/tests/unit/bud-api/repository/watch.test.ts index b5bbe632b3..f7c8dafe7a 100644 --- a/tests/unit/bud-api/repository/watch.test.ts +++ b/tests/unit/bud-api/repository/watch.test.ts @@ -18,37 +18,16 @@ describe('bud.watch', function () { expect( Array.from(bud.hooks.filter('dev.watch.files')), - ).toMatchSnapshot(['**/*.js']) + ).toMatchSnapshot('**/*.js') }) it('merges watch files', async () => { - bud.watch(['foo/*.js']) + bud.watch('foo/*.js') await bud.api.processQueue() - expect( - Array.from(bud.hooks.filter('dev.watch.files')), - ).toMatchSnapshot(['**/*.js', 'foo/*.js']) - }) - - it('set watch options', async () => { - bud.watch([], {depth: 1}) - - await bud.api.processQueue() - - expect(bud.hooks.filter('dev.watch.options')).toMatchSnapshot({ - depth: 1, - }) - }) - - it('merges watch options', async () => { - bud.watch([], {cwd: '/srv/www'}) - - await bud.api.processQueue() - - expect(bud.hooks.filter('dev.watch.options')).toMatchSnapshot({ - cwd: '/srv/www', - depth: 1, - }) + expect(Array.from(bud.hooks.filter('dev.watch.files')).length).toEqual( + 2, + ) }) }) diff --git a/tests/unit/bud/services/project/project.test.ts b/tests/unit/bud/services/project/project.test.ts index c21b22bcf0..c51bd4617d 100644 --- a/tests/unit/bud/services/project/project.test.ts +++ b/tests/unit/bud/services/project/project.test.ts @@ -1,5 +1,7 @@ import {Bud, factory} from '@repo/test-kit/bud' +jest.setTimeout(15000) + describe('bud.project', function () { let bud: Bud diff --git a/tests/util/project/src/styles/app.css b/tests/util/project/src/styles/app.css index 2ecc701ba7..667b731299 100644 --- a/tests/util/project/src/styles/app.css +++ b/tests/util/project/src/styles/app.css @@ -1 +1,4 @@ @import './common/global'; +body { + backgorun +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 5f686197ab..fc020a2e71 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3691,12 +3691,16 @@ __metadata: "@roots/bud-support": "workspace:sources/@roots/bud-support" "@roots/container": "workspace:sources/@roots/container" "@skypack/package-check": 0.2.2 + "@types/cookie-parser": ^1 + "@types/cors": ^2 "@types/express": 4.17.13 "@types/lodash": 4.14.180 "@types/node": 16.11.26 "@types/webpack-dev-middleware": 5.0.2 "@types/webpack-hot-middleware": 2.25.6 ansi-html-community: 0.0.8 + cookie-parser: 1.4.6 + cors: 2.8.5 express: ^4.17.1 html-entities: ^2.1.0 http-proxy-middleware: 2.0.4 @@ -4629,6 +4633,22 @@ __metadata: languageName: node linkType: hard +"@types/cookie-parser@npm:^1": + version: 1.4.2 + resolution: "@types/cookie-parser@npm:1.4.2" + dependencies: + "@types/express": "*" + checksum: d5b3c0e193cc95289444306ad396b27b92c9ff1ffd0769268b374d09315c8df1ae94de5fe2a81a79c0629560646763136e15c8a4069d3dc5ba5cb6bc185eaccd + languageName: node + linkType: hard + +"@types/cors@npm:^2": + version: 2.8.12 + resolution: "@types/cors@npm:2.8.12" + checksum: 8c45f112c7d1d2d831b4b266f2e6ed33a1887a35dcbfe2a18b28370751fababb7cd045e745ef84a523c33a25932678097bf79afaa367c6cb3fa0daa7a6438257 + languageName: node + linkType: hard + "@types/debug@npm:^4.0.0": version: 4.1.7 resolution: "@types/debug@npm:4.1.7" @@ -8330,6 +8350,16 @@ __metadata: languageName: node linkType: hard +"cookie-parser@npm:1.4.6": + version: 1.4.6 + resolution: "cookie-parser@npm:1.4.6" + dependencies: + cookie: 0.4.1 + cookie-signature: 1.0.6 + checksum: 1e5a63aa82e8eb4e02d2977c6902983dee87b02e87ec5ec43ac3cb1e72da354003716570cd5190c0ad9e8a454c9d3237f4ad6e2f16d0902205a96a1c72b77ba5 + languageName: node + linkType: hard + "cookie-signature@npm:1.0.6": version: 1.0.6 resolution: "cookie-signature@npm:1.0.6" @@ -8344,6 +8374,13 @@ __metadata: languageName: node linkType: hard +"cookie@npm:0.4.1": + version: 0.4.1 + resolution: "cookie@npm:0.4.1" + checksum: bd7c47f5d94ab70ccdfe8210cde7d725880d2fcda06d8e375afbdd82de0c8d3b73541996e9ce57d35f67f672c4ee6d60208adec06b3c5fc94cebb85196084cf8 + languageName: node + linkType: hard + "copy-text-to-clipboard@npm:^3.0.1": version: 3.0.1 resolution: "copy-text-to-clipboard@npm:3.0.1" @@ -8398,6 +8435,16 @@ __metadata: languageName: node linkType: hard +"cors@npm:2.8.5": + version: 2.8.5 + resolution: "cors@npm:2.8.5" + dependencies: + object-assign: ^4 + vary: ^1 + checksum: ced838404ccd184f61ab4fdc5847035b681c90db7ac17e428f3d81d69e2989d2b680cc254da0e2554f5ed4f8a341820a1ce3d1c16b499f6e2f47a1b9b07b5006 + languageName: node + linkType: hard + "cosmiconfig@npm:7.0.1, cosmiconfig@npm:^7.0.0, cosmiconfig@npm:^7.0.1": version: 7.0.1 resolution: "cosmiconfig@npm:7.0.1" @@ -15726,7 +15773,7 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": +"object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" checksum: fcc6e4ea8c7fe48abfbb552578b1c53e0d194086e2e6bbbf59e0a536381a292f39943c6e9628af05b5528aa5e3318bb30d6b2e53cadaf5b8fe9e12c4b69af23f @@ -21490,7 +21537,7 @@ __metadata: languageName: node linkType: hard -"vary@npm:~1.1.2": +"vary@npm:^1, vary@npm:~1.1.2": version: 1.1.2 resolution: "vary@npm:1.1.2" checksum: ae0123222c6df65b437669d63dfa8c36cee20a504101b2fcd97b8bf76f91259c17f9f2b4d70a1e3c6bbcee7f51b28392833adb6b2770b23b01abec84e369660b