From 2ecef9bf9fa9630c12f675bba26703bea86b66d2 Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Mon, 31 Jul 2023 01:39:34 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20improve:=20stricter=20typings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/tsconfig.json | 2 +- config/vitest/alias.ts | 70 +++-------- .../readme/renderer/handlebars.ts | 41 +++--- sources/@repo/markdown-kit/releases/data.ts | 37 +++--- .../bud-api/src/methods/experiments/index.ts | 12 +- .../bud-api/src/methods/proxy/helpers.ts | 4 +- .../bud-api/src/methods/proxy/proxy.types.ts | 30 +++-- sources/@roots/bud-babel/src/extension.ts | 12 +- sources/@roots/bud-build/src/config/index.ts | 2 +- .../bud-build/src/config/output/index.ts | 8 +- .../@roots/bud-build/src/config/plugins.ts | 6 +- sources/@roots/bud-build/src/config/stats.ts | 23 +--- sources/@roots/bud-build/src/service.ts | 2 +- sources/@roots/bud-cache/src/service/index.ts | 45 ++++--- sources/@roots/bud-compiler/src/service.tsx | 33 +++-- .../@types/critical/index.d.ts | 15 +++ sources/@roots/bud-criticalcss/package.json | 19 ++- .../src/{api/extract.ts => bud.extractCss.ts} | 41 +++--- .../@roots/bud-criticalcss/src/extension.ts | 3 +- sources/@roots/bud-criticalcss/src/index.ts | 29 ++++- sources/@roots/bud-criticalcss/src/types.ts | 27 ---- sources/@roots/bud-criticalcss/tsconfig.json | 7 +- sources/@roots/bud-dashboard/package.json | 4 +- .../@roots/bud-dashboard/src/application.tsx | 30 ++--- .../bud-dashboard/src/components/asset.tsx | 8 +- .../bud-dashboard/src/components/error.tsx | 83 ++++++++----- .../bud-dashboard/src/components/messages.tsx | 14 +-- .../bud-dashboard/src/helpers/formatErrors.ts | 17 ++- .../src/hooks/useCompilationColor.ts | 13 +- .../src/hooks/useLongestNamedObjectLength.ts | 4 +- sources/@roots/bud-dashboard/src/service.tsx | 17 +-- .../bud-dashboard/src/views/compilation.tsx | 47 +++---- .../@roots/bud-dashboard/src/views/debug.tsx | 67 +++++----- .../bud-dashboard/src/views/entrypoints.tsx | 8 +- .../@roots/bud-dashboard/src/views/server.tsx | 99 +++++++++------ .../@roots/bud-dashboard/test/app.test.tsx | 30 ++--- sources/@roots/bud-dashboard/tsconfig.json | 3 +- .../@roots/bud-extensions/src/cdn/index.ts | 6 +- .../src/copy-webpack-plugin/index.ts | 2 +- .../bud-extensions/src/import-map/index.ts | 11 +- sources/@roots/bud-extensions/src/service.ts | 20 +-- .../src/tsconfig-values/index.ts | 59 +++++---- .../test/extensions/esm.test.ts | 10 +- sources/@roots/bud-framework/package.json | 8 ++ sources/@roots/bud-framework/src/bootstrap.ts | 35 ++++-- sources/@roots/bud-framework/src/bud.ts | 2 +- .../src/configuration/configuration.ts | 2 +- sources/@roots/bud-framework/src/context.ts | 2 - .../src/services => bud-framework/src}/env.ts | 2 +- sources/@roots/bud-framework/src/fs.ts | 48 ++++--- sources/@roots/bud-framework/src/index.ts | 1 + .../@roots/bud-framework/src/methods/close.ts | 33 +++-- .../@roots/bud-framework/src/methods/glob.ts | 2 + .../@roots/bud-framework/src/methods/path.ts | 18 +-- .../@roots/bud-framework/src/methods/pipe.ts | 19 ++- .../bud-framework/src/methods/publicPath.ts | 6 +- .../@roots/bud-framework/src/methods/run.ts | 19 ++- .../bud-framework/src/methods/sequence.ts | 6 +- .../src/methods/setPublicPath.ts | 18 ++- .../@roots/bud-framework/src/methods/sh.ts | 7 +- sources/@roots/bud-framework/src/module.ts | 3 +- .../services => bud-framework/src}/project.ts | 4 +- .../@roots/bud-framework/src/registry/dev.ts | 2 +- .../bud-framework/src/services/build/index.ts | 2 +- .../src/services/compiler/index.ts | 2 +- .../src/services/dashboard/index.ts | 4 +- .../bud-framework/src/services/hooks/index.ts | 8 +- .../src/services/server/middleware.ts | 8 +- .../src/services/server/watcher.ts | 2 +- .../{bud => bud-framework}/test/env.test.ts | 5 +- .../test/project.test.ts | 5 +- sources/@roots/bud-minify/src/extension.ts | 12 +- .../@roots/bud-minify/src/minify-css/index.ts | 4 +- .../src/minify-js/extension.config.ts | 37 +++--- .../@roots/bud-minify/src/minify-js/index.ts | 4 +- .../@roots/bud-minify/test/extension.test.ts | 11 +- sources/@roots/bud-postcss/src/options.ts | 2 + .../src/hooks/dev.client.scripts.ts | 8 +- sources/@roots/bud-server/src/inject.ts | 4 +- .../bud-server/src/middleware/dev/factory.ts | 7 +- .../bud-server/src/middleware/hot/factory.ts | 25 ++-- .../@roots/bud-server/src/middleware/index.ts | 2 +- .../src/middleware/proxy/factory.ts | 6 +- .../middleware/proxy/responseInterceptor.ts | 1 + .../bud-server/src/middleware/proxy/url.ts | 4 +- sources/@roots/bud-server/src/server/base.ts | 4 +- .../@roots/bud-server/src/server/watcher.ts | 4 +- .../@roots/bud-server/src/service/index.ts | 47 +++++-- sources/@roots/bud-support/src/value/index.ts | 4 +- .../bud-wordpress-externals/src/extension.ts | 8 +- .../bud-wordpress-externals/tsconfig.json | 1 + .../@roots/bud/src/cli/commands/repl/Repl.tsx | 19 +-- .../bud/src/cli/commands/repl/index.tsx | 4 +- sources/@roots/bud/src/context/services.ts | 4 +- sources/@roots/bud/test/context.test.ts | 4 +- sources/@roots/container/src/container.ts | 2 +- .../@types/critical/index.d.ts | 15 +++ .../critical-css-webpack-plugin/src/index.ts | 57 ++++++++- .../src/interface.ts | 53 -------- .../critical-css-webpack-plugin/src/plugin.ts | 117 +++++++----------- .../{src => test}/plugin.test.ts | 3 +- .../critical-css-webpack-plugin/tsconfig.json | 1 + .../dependencies/src/command/base.command.ts | 4 +- .../dependencies/src/command/npm.command.ts | 3 + .../dependencies/src/command/yarn.command.ts | 3 + .../entrypoints-webpack-plugin/src/index.ts | 3 +- sources/@roots/filesystem/src/filesystem.ts | 12 +- sources/@roots/filesystem/src/json.ts | 11 +- sources/@roots/filesystem/src/s3/index.ts | 18 +-- sources/@roots/filesystem/src/yml.ts | 3 + .../src/plugin.ts | 56 ++++++--- .../src/plugin.ts | 3 + sources/@roots/wordpress-hmr/src/editor.ts | 22 ++-- sources/@roots/wordpress-hmr/src/index.ts | 4 + sources/@roots/wordpress-hmr/src/utility.ts | 12 +- .../scripts/build-types.js | 2 + .../src/theme.ts | 2 + .../wordpress-transforms/src/wordpress.ts | 15 ++- sources/create-bud-app/src/commands/create.ts | 4 +- sources/create-bud-app/src/flags/name.ts | 2 +- sources/create-bud-app/src/flags/overwrite.ts | 2 +- sources/create-bud-app/src/flags/username.ts | 2 +- sources/create-bud-app/src/flags/version.ts | 2 +- .../src/prompts/support.components.ts | 9 +- .../create-bud-app/src/prompts/support.css.ts | 5 +- .../create-bud-app/src/prompts/support.env.ts | 5 +- .../create-bud-app/src/prompts/support.js.ts | 5 +- .../create-bud-app/src/prompts/support.qa.ts | 5 +- .../create-bud-app/src/tasks/install.dev.ts | 5 +- .../src/utilities/getGitUser.ts | 4 +- 130 files changed, 1083 insertions(+), 907 deletions(-) create mode 100644 sources/@roots/bud-criticalcss/@types/critical/index.d.ts rename sources/@roots/bud-criticalcss/src/{api/extract.ts => bud.extractCss.ts} (52%) delete mode 100644 sources/@roots/bud-criticalcss/src/types.ts rename sources/@roots/{bud/src/services => bud-framework/src}/env.ts (95%) rename sources/@roots/{bud/src/services => bud-framework/src}/project.ts (96%) rename sources/@roots/{bud => bud-framework}/test/env.test.ts (90%) rename sources/@roots/{bud => bud-framework}/test/project.test.ts (91%) create mode 100644 sources/@roots/critical-css-webpack-plugin/@types/critical/index.d.ts delete mode 100644 sources/@roots/critical-css-webpack-plugin/src/interface.ts rename sources/@roots/critical-css-webpack-plugin/{src => test}/plugin.test.ts (89%) diff --git a/config/tsconfig.json b/config/tsconfig.json index 394a7717b1..470a3a140e 100644 --- a/config/tsconfig.json +++ b/config/tsconfig.json @@ -5,7 +5,7 @@ "composite": true, "declaration": true, "declarationMap": false, - "emitDecoratorMetadata": true, + "emitDecoratorMetadata": false, "esModuleInterop": true, "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, diff --git a/config/vitest/alias.ts b/config/vitest/alias.ts index 6fc6f64b68..e7c8b64d09 100644 --- a/config/vitest/alias.ts +++ b/config/vitest/alias.ts @@ -1,60 +1,20 @@ +import {join, relative} from 'node:path' + import {path} from '@repo/constants' +import globby from '@roots/bud-support/globby' + +const packages = await globby(path(`sources/@roots/*`), { + absolute: true, + onlyDirectories: true, +}) export default { - '@roots/bud': path(`sources/@roots/bud/src`), - '@roots/bud-api': path(`sources/@roots/bud-api/src`), - '@roots/bud-api/methods': path(`sources/@roots/bud-api/src/methods`), - '@roots/bud-api/service': path(`sources/@roots/bud-api/src/service`), - '@roots/bud-babel': path(`sources/@roots/bud-babel/src`), - '@roots/bud-build': path(`sources/@roots/bud-build/src`), - '@roots/bud-build/handlers': path( - `sources/@roots/bud-build/src/handlers`, - ), - '@roots/bud-cache': path(`sources/@roots/bud-cache/src`), - '@roots/bud-compiler': path(`sources/@roots/bud-compiler/src`), - '@roots/bud-compress': path(`sources/@roots/bud-compress/src`), - '@roots/bud-dashboard': path(`sources/@roots/bud-dashboard/src`), - '@roots/bud-extensions': path(`sources/@roots/bud-extensions/src`), - '@roots/bud-framework': path(`sources/@roots/bud-framework/src`), - '@roots/bud-framework/extension': path( - `sources/@roots/bud-framework/src/extension`, - ), - '@roots/bud-framework/extension/decorators': path( - `sources/@roots/bud-framework/src/extension/decorators`, - ), - '@roots/bud-hooks': path(`sources/@roots/bud-hooks/src`), - '@roots/bud-preset-react': path(`sources/@roots/bud-preset-react/src`), - '@roots/bud-preset-recommend': path( - `sources/@roots/bud-preset-recommend/src`, - ), - '@roots/bud-react': path(`sources/@roots/bud-react/src`), - '@roots/bud-sass': path(`sources/@roots/bud-sass/src`), - '@roots/bud-server': path(`sources/@roots/bud-server/src`), - '@roots/bud-server/middleware': path( - `sources/@roots/bud-server/src/middleware`, - ), - '@roots/bud-support': path(`sources/@roots/bud-support/src`), - '@roots/bud-swc': path(`sources/@roots/bud-swc/src`), - '@roots/bud-tailwindcss-theme-json': path( - `sources/@roots/bud-tailwindcss-theme-json/src`, - ), - '@roots/bud-wordpress-externals': path( - `sources/@roots/bud-wordpress-externals/src`, - ), - '@roots/bud/services': path(`sources/@roots/bud/src/services`), - '@roots/container': path(`sources/@roots/container/src`), - '@roots/entrypoints-webpack-plugin': path( - `sources/@roots/entrypoints-webpack-plugin/src`, - ), - '@roots/sage': path(`sources/@roots/sage/src`), - '@roots/wordpress-dependencies-webpack-plugin': path( - `sources/@roots/wordpress-dependencies-webpack-plugin/src`, - ), - '@roots/wordpress-externals-webpack-plugin': path( - `sources/@roots/wordpress-externals-webpack-plugin/src`, - ), - '@roots/wordpress-hmr': path(`sources/@roots/wordpress-hmr/src`), - '@roots/wordpress-hmr/loader': path( - `sources/@roots/wordpress-hmr/src/loader`, + ...packages.reduce((aliases, packageRoot) => { + const signifier = relative(path(), packageRoot) + aliases[signifier] = join(packageRoot, `src`) + return aliases + }, {}), + '@roots/filesystem/src/vendor/sdk': path( + `sources/@roots/filesystem/vendor/sdk`, ), } diff --git a/sources/@repo/markdown-kit/readme/renderer/handlebars.ts b/sources/@repo/markdown-kit/readme/renderer/handlebars.ts index d341321125..6893033e25 100644 --- a/sources/@repo/markdown-kit/readme/renderer/handlebars.ts +++ b/sources/@repo/markdown-kit/readme/renderer/handlebars.ts @@ -1,35 +1,38 @@ -import type {TemplateDelegate} from 'handlebars' +import type { TemplateDelegate } from "handlebars"; -import {path} from '@repo/constants' -import fs from 'fs-jetpack' -import {globby} from 'globby' -import Handlebars from 'handlebars' +import { path } from "@repo/constants"; +import fs from "fs-jetpack"; +import { globby } from "globby"; +import Handlebars from "handlebars"; -let handlebars = Handlebars +let handlebars = Handlebars; const sources = await globby([ path(`sources/@repo/markdown-kit/readme/partials/*.md`), -]) +]); const partials = await sources.reduce(async (promised, filePath) => { - const dictionary = await promised - const templateSource = await fs.readAsync(filePath).then(String) + const dictionary = await promised; + const templateSource = await fs.readAsync(filePath).then(String); + + const lastSegment = filePath.split(`/`).pop(); + if (!lastSegment) return dictionary; return { ...dictionary, - [`${filePath.split(`/`).pop().split(`.`).shift()}`]: templateSource, - } -}, Promise.resolve({})) + [`${lastSegment.split(`.`).shift()}`]: templateSource, + }; +}, Promise.resolve({})); -handlebars.registerPartial(partials) +partials && handlebars.registerPartial(partials); handlebars.registerHelper(`dotPath`, function (context, options) { - return `${options.fn(this).replace(/\./, options.data.root.name)}` -}) + return `${options.fn(this).replace(/\./, options.data.root.name)}`; +}); handlebars.registerHelper(`raw`, function (context) { - return context.fn(this) -}) + return context.fn(this); +}); -export {handlebars, Handlebars} -export type {TemplateDelegate} +export { handlebars, Handlebars }; +export type { TemplateDelegate }; diff --git a/sources/@repo/markdown-kit/releases/data.ts b/sources/@repo/markdown-kit/releases/data.ts index 613653c0f4..87e14d293d 100644 --- a/sources/@repo/markdown-kit/releases/data.ts +++ b/sources/@repo/markdown-kit/releases/data.ts @@ -32,11 +32,12 @@ type releases = Array /** * Request cache */ -let request: request +let request: request = {} + /** * Release cache */ -let releases: Array +const releases: Array = [] // eslint-disable-next-line const octokit = new Octokit({auth: process.env.GITHUB_TOKEN}) @@ -62,7 +63,7 @@ const parse = (release: ghRelease): release => { return { ...release, body: release.body.split(`\n`).slice(1).join(`\n`).trim(), - intro: release.body.split(`\n`).shift().trim().trim(), + intro: release.body.split(`\n`).shift()?.trim() ?? ``, major: parseVersion(major), minor: parseVersion(minor), patch: parseVersion(patch), @@ -85,20 +86,22 @@ if (!request?.data) { } } -if (!releases) { - releases = request?.data - ?.filter(filter) - .map(parse) - .sort((a, b) => { - if (a.major > b.major) return -1 - if (a.major < b.major) return 1 - return 0 - }) - .sort((a, b) => { - if (a.minor > b.minor) return -1 - if (a.minor < b.minor) return 1 - return 0 - }) +if (request.data) { + releases.push( + ...request.data + ?.filter(filter) + .map(parse) + .sort((a, b) => { + if (a.major > b.major) return -1 + if (a.major < b.major) return 1 + return 0 + }) + .sort((a, b) => { + if (a.minor > b.minor) return -1 + if (a.minor < b.minor) return 1 + return 0 + }), + ) await fs.writeAsync( path(`sources/@repo/docs/generated/releases/data.json`), diff --git a/sources/@roots/bud-api/src/methods/experiments/index.ts b/sources/@roots/bud-api/src/methods/experiments/index.ts index f4e2b3d1ba..476a1ed8ae 100644 --- a/sources/@roots/bud-api/src/methods/experiments/index.ts +++ b/sources/@roots/bud-api/src/methods/experiments/index.ts @@ -2,8 +2,8 @@ import type {Bud} from '@roots/bud-framework' import type {Configuration} from '@roots/bud-framework/config' export type Parameters< - T extends - `${keyof Configuration[`experiments`]}` = `${keyof Configuration[`experiments`]}`, + T extends `${keyof Configuration[`experiments`] & + string}` = `${keyof Configuration[`experiments`] & string}`, > = [ Partial | T, Configuration[`experiments`][T]?, @@ -17,12 +17,14 @@ export const experiments: experiments = function ( this: Bud, ...params ): Bud { - if (params.length === 1) { + if (typeof params[0] === `object`) { + const experimentsObject = params[0] as Configuration[`experiments`] + this.hooks.on(`build.experiments`, (experiments = {}) => ({ ...experiments, - ...params[0], + ...experimentsObject, })) - } else if (typeof params[0] === `string`) { + } else { this.hooks.on(`build.experiments`, (experiments = {}) => ({ ...experiments, [`${params[0]}`]: params[1], diff --git a/sources/@roots/bud-api/src/methods/proxy/helpers.ts b/sources/@roots/bud-api/src/methods/proxy/helpers.ts index 835470debf..bbeac5c7c0 100644 --- a/sources/@roots/bud-api/src/methods/proxy/helpers.ts +++ b/sources/@roots/bud-api/src/methods/proxy/helpers.ts @@ -51,9 +51,9 @@ export const assignUrl = (bud: Bud, maybeURL: string | URL) => { */ export const assignOptionsCallback = ( bud: Bud, - callback: (options?: Options) => Options, + value: ((options: Options | undefined) => Options) | Options, ) => { - bud.hooks.on(`dev.middleware.proxy.options`, callback) + bud.hooks.on(`dev.middleware.proxy.options`, value) } /** diff --git a/sources/@roots/bud-api/src/methods/proxy/proxy.types.ts b/sources/@roots/bud-api/src/methods/proxy/proxy.types.ts index 31fdb32d4f..d63411aa73 100644 --- a/sources/@roots/bud-api/src/methods/proxy/proxy.types.ts +++ b/sources/@roots/bud-api/src/methods/proxy/proxy.types.ts @@ -13,25 +13,33 @@ export interface Options extends HttpProxy.Options { autoRewrite?: boolean buffer?: Stream changeOrigin?: boolean - cookieDomainRewrite?: Record - cookiePathRewrite?: Record - ejectPlugins?: boolean + cookieDomainRewrite?: + | {[oldDomain: string]: string} + | false + | string + | undefined + cookiePathRewrite?: + | {[oldDomain: string]: string} + | false + | string + | undefined + ejectPlugins: boolean followRedirects?: boolean forward?: ProxyOptions[`forward`] headers?: Record hostRewrite?: string ignorePath?: boolean localAddress?: string - logger?: Pick - on?: ProxyOptions[`on`] + logger: Pick + on: ProxyOptions[`on`] onProxyReq?: any onProxyRes?: any - pathFilter?: Array - pathRewrite?: Record - plugins?: ProxyOptions[`plugins`] + pathFilter: Array + pathRewrite?: HttpProxy.Options[`pathRewrite`] + plugins: ProxyOptions[`plugins`] prependPath?: boolean preserveHeaderKeyCase?: boolean - protocolRewrite?: `http` | `https` + protocolRewrite?: HttpProxy.Options[`protocolRewrite`] proxyTimeout?: number /** * Not a proxy-party option @@ -39,11 +47,11 @@ export interface Options extends HttpProxy.Options { * Used by default `onProxyRes` handler to rewrite the page body */ replacements?: ReplacementCallback | ReplacementTuples - router?: Record + router?: HttpProxy.Options[`router`] secure?: boolean selfHandleResponse?: boolean ssl?: HttpsServerOptions - target?: URL + target?: HttpProxy.Options[`target`] timeout?: number toProxy?: boolean ws?: boolean diff --git a/sources/@roots/bud-babel/src/extension.ts b/sources/@roots/bud-babel/src/extension.ts index 7376413e28..04ffb543db 100644 --- a/sources/@roots/bud-babel/src/extension.ts +++ b/sources/@roots/bud-babel/src/extension.ts @@ -44,7 +44,7 @@ export default class BabelExtension extends Extension { * Cache directory */ public get cacheDirectory(): LoaderOptions[`cacheDirectory`] { - return this.app.cache.enabled + return this.app.cache.enabled && this.app.cache.cacheDirectory ? this.app.path(this.app.cache.cacheDirectory, `babel`) : false } @@ -52,7 +52,7 @@ export default class BabelExtension extends Extension { /** * Config file accessor */ - public get configFile(): Record { + public get configFile(): Record | undefined { return Object.values(this.app.context.files).find( file => file.name === `.babelrc` || @@ -116,7 +116,7 @@ export default class BabelExtension extends Extension { this.configFileOptions = this.configFile.module?.default ?? this.configFile.module - hooks.on(`build.cache.buildDependencies`, paths => { + hooks.on(`build.cache.buildDependencies`, (paths = {}) => { if (isString(this.configFile)) { paths.babel = [this.configFile] this.logger.success( @@ -294,8 +294,7 @@ export default class BabelExtension extends Extension { */ @bind public unsetPlugin(plugin: string) { - if (!isUndefined(this.plugins[plugin])) - this.plugins[plugin] = undefined + if (!isUndefined(this.plugins[plugin])) delete this.plugins[plugin] return this } @@ -305,8 +304,7 @@ export default class BabelExtension extends Extension { */ @bind public unsetPreset(preset: string) { - if (!isUndefined(this.presets[preset])) - this.presets[preset] = undefined + if (!isUndefined(this.presets[preset])) delete this.presets[preset] return this } diff --git a/sources/@roots/bud-build/src/config/index.ts b/sources/@roots/bud-build/src/config/index.ts index 48467d9631..1f3decd2b3 100644 --- a/sources/@roots/bud-build/src/config/index.ts +++ b/sources/@roots/bud-build/src/config/index.ts @@ -63,7 +63,7 @@ export interface Factory< Key extends keyof Config, Config = Configuration, > { - (app: Bud): Promise + (app: Bud): Promise } export type Records = { diff --git a/sources/@roots/bud-build/src/config/output/index.ts b/sources/@roots/bud-build/src/config/output/index.ts index 250d9f6194..dcd2bafab1 100644 --- a/sources/@roots/bud-build/src/config/output/index.ts +++ b/sources/@roots/bud-build/src/config/output/index.ts @@ -1,6 +1,5 @@ import type {Factory} from '../index.js' -import {isMjs} from '../../helpers/isMjs.js' import {assetModuleFilename} from './assetModuleFilename.js' import {filename} from './filename.js' @@ -27,12 +26,11 @@ export const output: Factory<`output`> = async ({ /** * Path info is not necessary unless the user * really knows what's going on. + * + * @see {@link https://medium.com/@kenneth_chau/speeding-up-webpack-typescript-incremental-builds-by-7x-3912ba4c1d15} */ pathinfo: filter(`build.output.pathinfo`, false), publicPath: filter(`build.output.publicPath`, `auto`), - scriptType: filter( - `build.output.scriptType`, - isMjs(filter) ? `module` : `text/javascript`, - ), + scriptType: filter(`build.output.scriptType`, undefined), uniqueName: filter(`build.output.uniqueName`, `@roots/bud`), }) diff --git a/sources/@roots/bud-build/src/config/plugins.ts b/sources/@roots/bud-build/src/config/plugins.ts index c8df650a50..951814b2f3 100644 --- a/sources/@roots/bud-build/src/config/plugins.ts +++ b/sources/@roots/bud-build/src/config/plugins.ts @@ -1,4 +1,6 @@ import type {Factory} from './index.js' -export const plugins: Factory<`plugins`> = async app => - await app.hooks.filterAsync(`build.plugins`, await app.extensions.make()) +export const plugins: Factory<`plugins`> = async app => { + const plugins = await app.extensions.make() + return await app.hooks.filterAsync(`build.plugins`, plugins) +} diff --git a/sources/@roots/bud-build/src/config/stats.ts b/sources/@roots/bud-build/src/config/stats.ts index 3914d99dda..50b7d8235d 100644 --- a/sources/@roots/bud-build/src/config/stats.ts +++ b/sources/@roots/bud-build/src/config/stats.ts @@ -1,25 +1,4 @@ import type {Factory} from './index.js' export const stats: Factory<`stats`> = async app => - app.hooks.filter( - `build.stats`, - app.isProduction - ? { - all: false, - assets: true, - assetsSort: `size`, - builtAt: false, - children: false, - chunks: false, - entrypoints: true, - errors: true, - errorsCount: true, - hash: true, - modules: true, - outputPath: true, - timings: true, - warnings: true, - warningsCount: true, - } - : {preset: `none`}, - ) + app.hooks.filter(`build.stats`, {preset: `none`}) diff --git a/sources/@roots/bud-build/src/service.ts b/sources/@roots/bud-build/src/service.ts index 7ab0a305be..9e0e6b2e07 100644 --- a/sources/@roots/bud-build/src/service.ts +++ b/sources/@roots/bud-build/src/service.ts @@ -89,7 +89,7 @@ export class Build extends Service implements BudBuild { * {@link BudBuild.make} */ @bind - public async make(): Promise { + public async make(): Promise> { this.logger.log(`bud.build.make called`) await this.app.hooks.fire(`build.before`, this.app) diff --git a/sources/@roots/bud-cache/src/service/index.ts b/sources/@roots/bud-cache/src/service/index.ts index 2d45c4328e..17c5836ceb 100644 --- a/sources/@roots/bud-cache/src/service/index.ts +++ b/sources/@roots/bud-cache/src/service/index.ts @@ -31,14 +31,21 @@ export default class Cache extends Service implements BudCache { *{@link BudCache.buildDependencies} */ public get buildDependencies(): Record> { - return this.app.hooks.filter(`build.cache.buildDependencies`, { + const baseDependencies = { bud: [ this.app.context.files[`package`]?.path, ...Object.values(this.app.context.files) .filter(({bud}) => bud) .map(({path}) => path), ].filter(Boolean), - }) + } + + return ( + this.app.hooks.filter( + `build.cache.buildDependencies`, + baseDependencies, + ) ?? baseDependencies + ) } public set buildDependencies( dependencies: Record>, @@ -50,9 +57,11 @@ export default class Cache extends Service implements BudCache { * {@link BudCache.cacheDirectory} */ public get cacheDirectory(): string { - return this.app.hooks.filter( - `build.cache.cacheDirectory`, - this.app.path(`@storage`, this.app.label, `cache`), + return ( + this.app.hooks.filter( + `build.cache.cacheDirectory`, + this.app.path(`@storage`, this.app.label, `cache`), + ) ?? this.app.path(`@storage`, this.app.label, `cache`) ) } public set cacheDirectory(directory: string) { @@ -95,12 +104,14 @@ export default class Cache extends Service implements BudCache { * {@link BudCache.name} */ public get name(): string { - return this.app.hooks.filter( - `build.cache.name`, + return ( this.app.hooks.filter( - `build.name`, - join(this.app.mode, ...Object.values(this.app.context._ ?? {})), - ), + `build.cache.name`, + this.app.hooks.filter( + `build.name`, + join(this.app.mode, ...Object.values(this.app.context._ ?? {})), + ), + ) ?? join(this.app.mode, ...Object.values(this.app.context._ ?? {})) ) } public set name(name: string) { @@ -119,11 +130,13 @@ export default class Cache extends Service implements BudCache { * {@link BudCache.type} */ public get type(): 'filesystem' | 'memory' { - return this.app.hooks.filter( - `build.cache.type`, - isString(this.app.context.cache) - ? this.app.context.cache - : `filesystem`, + return ( + this.app.hooks.filter( + `build.cache.type`, + isString(this.app.context.cache) + ? this.app.context.cache + : `filesystem`, + ) ?? `filesystem` ) } public set type(type: 'filesystem' | 'memory') { @@ -133,7 +146,7 @@ export default class Cache extends Service implements BudCache { /** * {@link BudCache.version} */ - public get version(): string { + public get version(): string | undefined { return this.app.hooks.filter(`build.cache.version`, undefined) } public set version(version: string) { diff --git a/sources/@roots/bud-compiler/src/service.tsx b/sources/@roots/bud-compiler/src/service.tsx index af05c56c83..5943187823 100644 --- a/sources/@roots/bud-compiler/src/service.tsx +++ b/sources/@roots/bud-compiler/src/service.tsx @@ -1,5 +1,5 @@ -import type {Compiler as BudCompiler} from '@roots/bud-framework' import type {Bud} from '@roots/bud-framework' +import type {Compiler as BudCompiler} from '@roots/bud-framework' import type { MultiCompiler, MultiStats, @@ -21,9 +21,9 @@ import {Error} from '@roots/bud-dashboard/components/error' import {Service} from '@roots/bud-framework/service' import {bind} from '@roots/bud-support/decorators/bind' import {BudError} from '@roots/bud-support/errors' -import {duration} from '@roots/bud-support/human-readable' import {render} from '@roots/bud-support/ink' import isNull from '@roots/bud-support/lodash/isNull' +import isNumber from '@roots/bud-support/lodash/isNumber' import isString from '@roots/bud-support/lodash/isString' import stripAnsi from '@roots/bud-support/strip-ansi' import webpack from '@roots/bud-support/webpack' @@ -62,7 +62,7 @@ export class Compiler extends Service implements BudCompiler { */ @bind public async compile(bud: Bud): Promise { - this.config = !bud.hasChildren + const config = !bud.hasChildren ? [await bud.build.make()] : await Promise.all( Object.values(bud.children).map(async (child: Bud) => @@ -72,6 +72,8 @@ export class Compiler extends Service implements BudCompiler { ), ) + this.config = config?.filter(Boolean) + this.config.parallelism = Math.max(cpus().length - 1, 1) this.logger.info(`parallel compilations: ${this.config.parallelism}`) @@ -143,12 +145,17 @@ export class Compiler extends Service implements BudCompiler { this.compilationStats.children = this.compilationStats.children?.map( child => ({ ...child, - errors: this.sourceErrors(child.errors), + errors: + child.errors && this.sourceErrors + ? this.sourceErrors(child.errors) + : child.errors ?? [], }), ) this.compilationStats.children - ?.filter(child => child.errorsCount > 0) + ?.filter( + child => isNumber(child.errorsCount) && child.errorsCount > 0, + ) .forEach(child => { try { const error = child.errors?.shift() @@ -162,7 +169,7 @@ export class Compiler extends Service implements BudCompiler { title: makeNoticeTitle(child), }) - this.app.notifier.openEditor(error.file) + error.file && this.app.notifier.openEditor(error.file) } catch (error) { this.logger.error(error) } @@ -176,16 +183,15 @@ export class Compiler extends Service implements BudCompiler { this.app.notifier.notify({ group: `${this.app.label}-${child.name}`, message: child.modules - ? `${child.modules.length} modules compiled in ${duration( - child.time, - )}` - : `Compiled in ${duration(child.time)}`, + ? `${child.modules.length} modules compiled` + : `Modules compiled successfully`, open: this.app.server?.publicUrl.href, subtitle: `Build successful`, title: makeNoticeTitle(child), }) - this.app.notifier.openBrowser(this.app.server?.publicUrl.href) + this.app.server?.publicUrl.href && + this.app.notifier.openBrowser(this.app.server?.publicUrl.href) } catch (error) { this.logger.error(error) } @@ -206,7 +212,7 @@ export class Compiler extends Service implements BudCompiler { @bind public sourceErrors?( - errors: Array, + errors: Array | undefined, ): Array { if (!errors || !errors.length) return [] @@ -222,7 +228,7 @@ export class Compiler extends Service implements BudCompiler { * In a perfect world webpack plugins would use the * `nameForCondition` property to identify the module. */ - if (ident) { + if (ident && this.compilationStats?.children) { module = this.compilationStats.children .flatMap(child => child?.modules) .find(module => [module?.id, module?.name].includes(ident)) @@ -290,6 +296,7 @@ const statsOptions = { cachedAssets: true, cachedModules: true, entrypoints: true, + errorDetails: false, errors: true, errorsCount: true, hash: true, diff --git a/sources/@roots/bud-criticalcss/@types/critical/index.d.ts b/sources/@roots/bud-criticalcss/@types/critical/index.d.ts new file mode 100644 index 0000000000..0a5e9c0558 --- /dev/null +++ b/sources/@roots/bud-criticalcss/@types/critical/index.d.ts @@ -0,0 +1,15 @@ +declare module 'critical' { + interface Stream { + (params: any): any + } + interface Generate { + (params: any, cb?: any): any + } + interface Generate { + stream: typeof stream + } + + declare const generate: Generate + declare const stream: Stream + export {generate, stream} +} diff --git a/sources/@roots/bud-criticalcss/package.json b/sources/@roots/bud-criticalcss/package.json index d4c58fdb5e..1ffd7c3b44 100644 --- a/sources/@roots/bud-criticalcss/package.json +++ b/sources/@roots/bud-criticalcss/package.json @@ -45,22 +45,21 @@ ], "type": "module", "exports": { - ".": { - "import": "./lib/index.js", - "default": "./lib/index.js" - }, - "./types": { - "import": "./lib/types.js", - "default": "./lib/types.js" - } + ".": "./lib/index.js", + "./bud.extractCss": "./lib/bud.extractCss.js", + "./extension": "./lib/extension.js" + }, "typesVersions": { "*": { ".": [ "./lib/index.d.ts" ], - "types": [ - "./lib/types.d.ts" + "bud.extractCss": [ + "./lib/bud.extractCss.d.ts" + ], + "extension": [ + "./lib/extension.d.ts" ] } }, diff --git a/sources/@roots/bud-criticalcss/src/api/extract.ts b/sources/@roots/bud-criticalcss/src/bud.extractCss.ts similarity index 52% rename from sources/@roots/bud-criticalcss/src/api/extract.ts rename to sources/@roots/bud-criticalcss/src/bud.extractCss.ts index 7da9dfa492..1293e89732 100644 --- a/sources/@roots/bud-criticalcss/src/api/extract.ts +++ b/sources/@roots/bud-criticalcss/src/bud.extractCss.ts @@ -1,7 +1,7 @@ import type {Bud} from '@roots/bud-framework' import type {Options} from '@roots/critical-css-webpack-plugin' -import * as critical from 'critical' +import {generate} from 'critical' import vinyl from 'vinyl' export interface extractCss { @@ -9,21 +9,21 @@ export interface extractCss { } export const extractCss = function ( + this: Bud, assets: Array, - options?: Options, + options: Options = {}, ): Bud { - const bud = this as Bud - - bud.hooks.action(`compiler.done`, async () => { + this.hooks.action(`compiler.done`, async () => { options = { - ...(bud.extensions.get(`@roots/bud-criticalcss`).getOptions() ?? {}), - ...(options ?? {}), + ...(this.extensions.get(`@roots/bud-criticalcss`).getOptions() ?? + {}), + ...options, } try { await Promise.all( assets.map(async from => { - const contents = await bud.fs.read(from, `buffer`) + const contents = await this.fs.read(from, `buffer`) const vfile = new vinyl({ base: options.base, @@ -43,20 +43,23 @@ export const extractCss = function ( .join(``) .concat(`.uncritical.css`) - await critical - .generate({...options, css: [vfile]}) - .then(async ({css, uncritical}) => { - await bud.fs.write(criticalPath, css) - options.extract - ? await bud.fs.write(from, uncritical) - : await bud.fs.write(uncriticalPath, uncritical) - }) + const result = await generate({...options, css: [vfile]}) + if (!result) return + + await this.fs.write(criticalPath, result.css) + + if (options.extract && result.uncritical) { + await this.fs.write(from, result.uncritical) + } + if (!options.extract && result.uncritical) { + await this.fs.write(uncriticalPath, result.uncritical) + } }), ) - } catch (e) { - throw new Error(e) + } catch (error: unknown) { + throw error } }) - return bud + return this } diff --git a/sources/@roots/bud-criticalcss/src/extension.ts b/sources/@roots/bud-criticalcss/src/extension.ts index f99d714e36..7d216c3ebc 100644 --- a/sources/@roots/bud-criticalcss/src/extension.ts +++ b/sources/@roots/bud-criticalcss/src/extension.ts @@ -1,6 +1,7 @@ import type {Bud} from '@roots/bud-framework' import type {Options} from '@roots/critical-css-webpack-plugin' +import {extractCss} from '@roots/bud-criticalcss/bud.extractCss' import {Extension} from '@roots/bud-framework/extension' import { bind, @@ -14,8 +15,6 @@ import {deprecated} from '@roots/bud-support/decorators' import Value from '@roots/bud-support/value' import CriticalCssWebpackPlugin from '@roots/critical-css-webpack-plugin' -import {extractCss} from './api/extract.js' - /** * Critical css configuration */ diff --git a/sources/@roots/bud-criticalcss/src/index.ts b/sources/@roots/bud-criticalcss/src/index.ts index 613b6eb08e..e2fbb3c2e1 100644 --- a/sources/@roots/bud-criticalcss/src/index.ts +++ b/sources/@roots/bud-criticalcss/src/index.ts @@ -5,7 +5,32 @@ * @see {@link https://npmjs.com/package/@roots/critical-css-webpack-plugin} */ -import BudCriticalCssExtension from './extension.js' -import './types.js' +import type {extractCss} from '@roots/bud-criticalcss/bud.extractCss' +import type {PublicExtensionApi} from '@roots/bud-framework/extension' + +import BudCriticalCssExtension from '@roots/bud-criticalcss/extension' + +interface CriticalPublicAPI + extends PublicExtensionApi { + base: BudCriticalCssExtension[`base`] + extract: BudCriticalCssExtension[`extract`] + height: BudCriticalCssExtension[`height`] + html: BudCriticalCssExtension[`html`] + ignore: BudCriticalCssExtension[`ignore`] + request: BudCriticalCssExtension[`request`] + src: BudCriticalCssExtension[`src`] + width: BudCriticalCssExtension[`width`] +} + +declare module '@roots/bud-framework' { + interface Bud { + critical: CriticalPublicAPI + extractCss: extractCss + } + + interface Modules { + '@roots/bud-criticalcss': CriticalPublicAPI + } +} export default BudCriticalCssExtension diff --git a/sources/@roots/bud-criticalcss/src/types.ts b/sources/@roots/bud-criticalcss/src/types.ts deleted file mode 100644 index bf660ba898..0000000000 --- a/sources/@roots/bud-criticalcss/src/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type {PublicExtensionApi} from '@roots/bud-framework/extension' - -import type {extractCss} from './api/extract.js' -import type BudCriticalCssExtension from './extension.js' - -interface CriticalPublicAPI - extends PublicExtensionApi { - base: BudCriticalCssExtension[`base`] - extract: BudCriticalCssExtension[`extract`] - height: BudCriticalCssExtension[`height`] - html: BudCriticalCssExtension[`html`] - ignore: BudCriticalCssExtension[`ignore`] - request: BudCriticalCssExtension[`request`] - src: BudCriticalCssExtension[`src`] - width: BudCriticalCssExtension[`width`] -} - -declare module '@roots/bud-framework' { - interface Bud { - critical: CriticalPublicAPI - extractCss: extractCss - } - - interface Modules { - '@roots/bud-criticalcss': CriticalPublicAPI - } -} diff --git a/sources/@roots/bud-criticalcss/tsconfig.json b/sources/@roots/bud-criticalcss/tsconfig.json index 8271ea7eec..ca2e6d69ef 100644 --- a/sources/@roots/bud-criticalcss/tsconfig.json +++ b/sources/@roots/bud-criticalcss/tsconfig.json @@ -2,12 +2,11 @@ "extends": "../../../config/tsconfig.json", "compilerOptions": { "rootDir": "./src", - "outDir": "./lib" + "outDir": "./lib", + "strict": true, }, - "include": ["./src"], - "exclude": ["./lib", "./node_modules", "**/*.test.ts", "**/__fixtures__"], + "include": ["./src", "./@types"], "references": [ - {"path": "./../bud-api/tsconfig.json"}, {"path": "./../bud-framework/tsconfig.json"}, {"path": "./../critical-css-webpack-plugin/tsconfig.json"} diff --git a/sources/@roots/bud-dashboard/package.json b/sources/@roots/bud-dashboard/package.json index 7870b69da2..11dcad908c 100644 --- a/sources/@roots/bud-dashboard/package.json +++ b/sources/@roots/bud-dashboard/package.json @@ -88,7 +88,9 @@ "@skypack/package-check": "0.2.2", "@types/node": "18.16.19", "@types/react": "18.2.15", - "ink-testing-library": "3.0.0" + "ink-testing-library": "3.0.0", + "vitest": "0.33.0", + "webpack": "5.88.2" }, "dependencies": { "@roots/bud-framework": "workspace:*", diff --git a/sources/@roots/bud-dashboard/src/application.tsx b/sources/@roots/bud-dashboard/src/application.tsx index 5073eacb8a..ed6902a1a1 100644 --- a/sources/@roots/bud-dashboard/src/application.tsx +++ b/sources/@roots/bud-dashboard/src/application.tsx @@ -1,6 +1,5 @@ import type {Bud} from '@roots/bud-framework' import type {StatsCompilation} from '@roots/bud-framework/config' -import type {BudError} from '@roots/bud-support/errors' import {exit} from 'node:process' @@ -28,7 +27,7 @@ export interface Props { displayAssets?: boolean displayEntrypoints?: boolean displayServerInfo?: boolean - error?: BudError + error?: Error errors?: StatsCompilation[`errors`] isolated?: number mode: Bud['mode'] @@ -87,7 +86,7 @@ export const Application = ({ displayAssets={displayAssets} displayEntrypoints={displayEntrypoints} id={id + 1} - total={compilations.length} + total={compilations?.length} /> @@ -149,21 +148,22 @@ export const TeletypeApplication = ({ break } - new Array(9) - .fill(0) - .forEach( - (_, i) => - key === `${i + 1}` && - isolated !== i + 1 && - setIsolated(Math.min(i + 1, props.compilations.length)), - ) + new Array(9).fill(0).forEach((_, i) => { + if (!props.compilations) return + key === `${i + 1}` && + isolated !== i + 1 && + setIsolated(Math.min(i + 1, props.compilations.length)) + }) if (input.escape) { setClosed(true) - close((error?) => { - app.exit(error) - exit(error ? 1 : 0) - }) + if (close) + close((error?) => { + if (error) app.exit(error) + else app.exit() + + exit(error ? 1 : 0) + }) } }) diff --git a/sources/@roots/bud-dashboard/src/components/asset.tsx b/sources/@roots/bud-dashboard/src/components/asset.tsx index e4ea7c4bf0..5904120485 100644 --- a/sources/@roots/bud-dashboard/src/components/asset.tsx +++ b/sources/@roots/bud-dashboard/src/components/asset.tsx @@ -3,6 +3,7 @@ import type {StatsAsset} from '@roots/bud-framework/config' import figures from '@roots/bud-support/figures' import {size as formatSize} from '@roots/bud-support/human-readable' import {Box, Text} from '@roots/bud-support/ink' +import isNumber from '@roots/bud-support/lodash/isNumber' interface Props extends Partial { name?: string @@ -38,8 +39,13 @@ const Asset = (asset: Props) => { )} + - {`${asset.size > 0 ? formatSize(asset.size) : `ø`}`.trim()} + {`${ + isNumber(asset?.size) && asset.size > 0 + ? formatSize(asset.size) + : `ø` + }`.trim()} diff --git a/sources/@roots/bud-dashboard/src/components/error.tsx b/sources/@roots/bud-dashboard/src/components/error.tsx index 680c1ee82e..adb2762ab8 100644 --- a/sources/@roots/bud-dashboard/src/components/error.tsx +++ b/sources/@roots/bud-dashboard/src/components/error.tsx @@ -7,9 +7,11 @@ import isString from '@roots/bud-support/lodash/isString' const basePath = process.env.PROJECT_CWD ?? process.env.INIT_CWD ?? process.cwd() -export const Error = ({error}: {error: BudError}) => { +export const Error = ({error}: {error: BudError | Error}) => { + let normalError: BudError + if (!error) { - error = BudError.normalize(`Unknown error`) + normalError = BudError.normalize(`Unknown error`) } if (isString(error)) { @@ -29,38 +31,44 @@ export const Error = ({error}: {error: BudError}) => { ) } + normalError = + error instanceof BudError ? error : BudError.normalize(error) + return ( {` `} - {error.name ?? `Error`} + {normalError.name ?? `Error`} {` `} - {error.message && ( + {normalError.message && ( {figures.cross} {` `} - {error.message.replace(basePath, `.`).replace(`Error: `, ``)} + {normalError.message + .replace(basePath, `.`) + .replace(`Error: `, ``)} )} - {error.details && !error.details.startsWith(`resolve`) && ( - - - - {figures.ellipsis} - {` `}Details{` `} - + {normalError.details && + !normalError.details.startsWith(`resolve`) && ( + + + + {figures.ellipsis} + {` `}Details{` `} + - {error.details.replace(basePath, `.`)} - - - )} + {normalError.details.replace(basePath, `.`)} + + + )} - {error.thrownBy && ( + {normalError.thrownBy && ( @@ -68,12 +76,12 @@ export const Error = ({error}: {error: BudError}) => { {` `}Thrown by{` `} - {error.thrownBy} + {normalError.thrownBy} )} - {error.docs && ( + {normalError.docs && ( @@ -81,12 +89,12 @@ export const Error = ({error}: {error: BudError}) => { {` `}Documentation {` `} - {error.docs.href} + {normalError.docs.href} )} - {error.issues && ( + {normalError.issues && ( @@ -95,24 +103,39 @@ export const Error = ({error}: {error: BudError}) => { Issues {` `} - {error.issues.href} + {normalError.issues.href} )} - {error.origin && ( - - - - )} - - {error.file && ( + {normalError.file && ( {figures.info} {` `}See file{` `} - {error.file.path} + {normalError.file.path} + + )} + + {normalError.origin && ( + + + {figures.home} + {` `}Originating error{` `} + + + + + )} diff --git a/sources/@roots/bud-dashboard/src/components/messages.tsx b/sources/@roots/bud-dashboard/src/components/messages.tsx index 3d4139c8bf..ba47176c9e 100644 --- a/sources/@roots/bud-dashboard/src/components/messages.tsx +++ b/sources/@roots/bud-dashboard/src/components/messages.tsx @@ -4,22 +4,22 @@ export default function Messages({ messages, ...props }: { - color: string - messages: Array<{message: string}> + color?: string + messages?: Array<{message: string}> }) { if (!messages?.length) return null return ( - {messages.map((error, id: number) => ( - + {messages.map((message, id: number) => ( + ))} ) } -const Message = ({color, error}) => - error?.message && ( +const Message = ({color, message}: {color?: string; message?: string}) => + !message ? null : ( overflowX="hidden" paddingLeft={1} > - {error.message} + {message} ) diff --git a/sources/@roots/bud-dashboard/src/helpers/formatErrors.ts b/sources/@roots/bud-dashboard/src/helpers/formatErrors.ts index eea17d1e3e..7d151a9e3c 100644 --- a/sources/@roots/bud-dashboard/src/helpers/formatErrors.ts +++ b/sources/@roots/bud-dashboard/src/helpers/formatErrors.ts @@ -2,6 +2,7 @@ import type {Bud} from '@roots/bud-framework' import type {StatsError} from '@roots/bud-framework/config' import cleanStack from '@roots/bud-support/clean-stack' +import isString from '@roots/bud-support/lodash/isString' export const makeErrorFormatter = (bud: Bud) => (errors?: StatsError[]) => errors @@ -62,18 +63,22 @@ const filterInternalErrors = (error: StatsError) => * Prettify errors */ const makePrettifier = (bud: Bud) => (error: StatsError) => { - error.message = error.message + let message = error.message ?? error.details + if (!message || !isString(message)) return error + + const segments = message .replace(`Module parse failed:`, ``) .replace(/Module build failed \(.*\):?/, ``) .replace(/ at .*?\/(webpack|tapable)\/?.*/gm, ``) .trim() .split(`Error:`) - .pop() - .split(` at`) - .shift() - .trim() + const lastSegment = segments.pop() + if (!lastSegment) return error + + const beforeStack = lastSegment.split(` at`).shift() + if (!beforeStack) return error - error.message = cleanStack(error.message, { + error.message = cleanStack(error.message.trim(), { basePath: process.cwd(), pretty: true, }) diff --git a/sources/@roots/bud-dashboard/src/hooks/useCompilationColor.ts b/sources/@roots/bud-dashboard/src/hooks/useCompilationColor.ts index 1ebc16473a..c18694b866 100644 --- a/sources/@roots/bud-dashboard/src/hooks/useCompilationColor.ts +++ b/sources/@roots/bud-dashboard/src/hooks/useCompilationColor.ts @@ -1,12 +1,19 @@ +import isNumber from '@roots/bud-support/lodash/isNumber' + export const useCompilationColor = ( - compilation: { + compilation?: { errorsCount?: number warningsCount?: number }, successColor: string = `green`, ) => { if (!compilation) return `dim` - if (compilation.errorsCount > 0) return `red` - if (compilation.warningsCount > 0) return `yellow` + + if (isNumber(compilation.errorsCount) && compilation.errorsCount > 0) + return `red` + + if (isNumber(compilation.warningsCount) && compilation.warningsCount > 0) + return `yellow` + return successColor } diff --git a/sources/@roots/bud-dashboard/src/hooks/useLongestNamedObjectLength.ts b/sources/@roots/bud-dashboard/src/hooks/useLongestNamedObjectLength.ts index 657dae27c0..54880f51dc 100644 --- a/sources/@roots/bud-dashboard/src/hooks/useLongestNamedObjectLength.ts +++ b/sources/@roots/bud-dashboard/src/hooks/useLongestNamedObjectLength.ts @@ -6,5 +6,7 @@ export const longestNamedObjectLength = ( items: Array<{name?: string}> = [], ) => items?.reduce((longest: number, item: {name?: string}) => { - return Math.max(item?.name?.length, longest) + const length = item?.name?.length + if (!length) return longest + return Math.max(length, longest) }, 0) + 1 diff --git a/sources/@roots/bud-dashboard/src/service.tsx b/sources/@roots/bud-dashboard/src/service.tsx index 91cea9f50b..2e442f2f35 100644 --- a/sources/@roots/bud-dashboard/src/service.tsx +++ b/sources/@roots/bud-dashboard/src/service.tsx @@ -4,7 +4,6 @@ import type { StatsError, } from '@roots/bud-framework/config' import type {Dashboard as BudDashboard} from '@roots/bud-framework/services' -import type {BudError} from '@roots/bud-support/errors' import {stderr, stdin, stdout} from 'node:process' @@ -27,7 +26,9 @@ export class Dashboard extends Service implements BudDashboard { /** * {@link BudDashboard.formatStatsErrors} */ - public declare formatStatsErrors: (errors: StatsError[]) => StatsError[] + public declare formatStatsErrors: ( + errors: StatsError[] | undefined, + ) => StatsError[] | undefined /** * {@link BudDashboard.instance} @@ -103,7 +104,7 @@ export class Dashboard extends Service implements BudDashboard { * {@link BudDashboard.render} */ @bind - public render(error?: BudError) { + public render(error?: Error) { /** * Do not render if: * - dashboard is disabled @@ -192,14 +193,14 @@ export class Dashboard extends Service implements BudDashboard { /** * Get dashboard.assets option */ - const assets = !isUndefined(this.app.context.dashboard?.assets) + const assets = !isUndefined(this.app.context.dashboard) ? this.app.context.dashboard.assets : true /** * Get dashboard.compact option */ - const compact = !isUndefined(this.app.context.dashboard?.compact) + const compact = !isUndefined(this.app.context.dashboard) ? this.app.context.dashboard.compact : compilations.length > 2 @@ -209,14 +210,14 @@ export class Dashboard extends Service implements BudDashboard { const entrypoints = !isUndefined( this.app.context.dashboard?.entrypoints, ) - ? this.app.context.dashboard.entrypoints + ? this.app.context.dashboard?.entrypoints : true /** * Get dashboard.server option */ const server = !isUndefined(this.app.context.dashboard?.server) - ? this.app.context.dashboard.server + ? this.app.context.dashboard?.server : true /** @@ -285,6 +286,8 @@ export class Dashboard extends Service implements BudDashboard { */ @bind public updateStats(stats: StatsCompilation): BudDashboard { + if (!this.app.compiler) return this + if (stats) this.stats = stats if (this.app.context.silent === true) return this diff --git a/sources/@roots/bud-dashboard/src/views/compilation.tsx b/sources/@roots/bud-dashboard/src/views/compilation.tsx index 5f581e2f47..fe2ed93cd8 100644 --- a/sources/@roots/bud-dashboard/src/views/compilation.tsx +++ b/sources/@roots/bud-dashboard/src/views/compilation.tsx @@ -10,12 +10,13 @@ import View from '@roots/bud-dashboard/components/view' import {useCompilationColor} from '@roots/bud-dashboard/hooks/useCompilationColor' import {duration} from '@roots/bud-support/human-readable' import {Box, Text, useEffect, useState} from '@roots/bud-support/ink' +import isNumber from '@roots/bud-support/lodash/isNumber' import Assets from './assets.js' import Entrypoints from './entrypoints.js' export interface Props { - basedir: string + basedir?: string borderColor?: string compact?: boolean compilation: StatsCompilation @@ -23,7 +24,7 @@ export interface Props { displayAssets?: boolean displayEntrypoints?: boolean id: number - total: number + total?: number } export interface Asset extends Partial {} @@ -110,7 +111,7 @@ const Head = ({basedir, compilation, id, total}: Props) => { {compilation.name?.split(`/`).pop() ?? `compilation`} - {total > 1 && ( + {total && total > 1 && ( {` `}[{id}/{total}] @@ -136,10 +137,11 @@ const Footer = ({compilation}: Partial) => { if (!compilation || !compilation?.assets) return ... + const errorsCount = isNumber(compilation.errorsCount) + ? compilation.errorsCount + : 0 const formattedErrorCount = - compilation.errorsCount > 1 - ? `${compilation.errorsCount} errors` - : `${compilation.errorsCount} error` + errorsCount > 1 ? `${errorsCount} errors` : `${errorsCount} error` const cachedModuleCount = compilation.modules?.filter(mod => mod?.cached)?.length ?? 0 @@ -147,14 +149,13 @@ const Footer = ({compilation}: Partial) => { compilation.modules?.filter(mod => mod && mod.hasOwnProperty(`cached`)) ?.length ?? 0 - const formattedModuleCount = - cachedModuleCount > 0 - ? `${cachedModuleCount}/${totalModuleCount} modules cached` - : `${totalModuleCount} modules` + const formattedModuleCount = `${cachedModuleCount}/${totalModuleCount} modules cached` - const formattedTime = `${duration(compilation.time)}` + const formattedTime = compilation.time + ? `${duration(compilation.time)} ` + : `` - if (compilation.errorsCount > 0) { + if (errorsCount > 0) { return ( {formattedErrorCount} @@ -162,30 +163,12 @@ const Footer = ({compilation}: Partial) => { ) } - if (totalModuleCount === 0) { - return ( - - {formattedTime} - - ) - } - - if (cachedModuleCount === 0) { - return ( - - - {formattedTime} - {` ${totalModuleCount} modules`} - - - ) - } - return ( {formattedTime} - {{` [${formattedModuleCount}]`}} + {`${totalModuleCount} modules`} + {` [${formattedModuleCount}]`} ) diff --git a/sources/@roots/bud-dashboard/src/views/debug.tsx b/sources/@roots/bud-dashboard/src/views/debug.tsx index 44bc2aa9ac..d2fd3bbae7 100644 --- a/sources/@roots/bud-dashboard/src/views/debug.tsx +++ b/sources/@roots/bud-dashboard/src/views/debug.tsx @@ -1,11 +1,11 @@ import View from '@roots/bud-dashboard/components/view' import {Box, Text} from '@roots/bud-support/ink' -type Fields = Array | number | Record | string +type Data = Array | number | Record | string export interface Props { - compilation?: Record - config?: Record + compilation?: Record + config?: Record debug?: boolean } @@ -27,39 +27,36 @@ export default function Debug({compilation, debug}: Props) { return ( <> - {format(compilation).map(([key, value]: [string, Fields], id) => ( - - debug config: {`${id + 1}`} /{` `} - {`${format(compilation).length}`} - - } - head={stats: {key}} - key={id} - > - - - - - ))} + {format(compilation).map(([key, fields], id) => { + if (typeof key !== `string`) return null + + return ( + + debug config: {`${id + 1}`} /{` `} + {`${format(compilation).length}`} + + } + head={stats: {key}} + key={id} + > + + + + + ) + })} ) } -const isPrimitive = ( - field: unknown, -): field is [] | boolean | number | string => { - return ( - typeof field === `boolean` || - typeof field === `string` || - typeof field === `number` || - (Array.isArray(field) && field.length === 0) - ) +const isPrimitive = (field: unknown): field is number | string => { + return typeof field === `string` || typeof field === `number` } -const Fields = ({fields}: {fields: Fields}) => { - if (typeof fields === `undefined`) { +const Fields = ({fields}: {fields: unknown}) => { + if (fields === undefined) { return undefined } @@ -76,16 +73,18 @@ const Fields = ({fields}: {fields: Fields}) => { } if (isPrimitive(fields)) { + const preLoaderSplit = `${fields}`.split(`!`).pop() + if (!preLoaderSplit) return null return ( - - {`${fields}`.split(`!`).pop().split(`?`).pop()} - + {preLoaderSplit.split(`?`).pop()} ) } return Object.entries(fields) .filter(([k, v]) => v !== undefined && v !== null) - .map(([key, fields]: [string, Fields], id) => { + .map(([key, fields], id) => { + if (!fields) return null + return ( {assets.length} modules {`${formatSize( - assets.reduce((acc, asset) => acc + asset.size, 0), + assets.reduce( + (acc, asset) => + acc + + (asset?.size && isNumber(asset.size) ? asset.size : 0), + 0, + ), )}`} diff --git a/sources/@roots/bud-dashboard/src/views/server.tsx b/sources/@roots/bud-dashboard/src/views/server.tsx index aab27f8317..df64353d29 100644 --- a/sources/@roots/bud-dashboard/src/views/server.tsx +++ b/sources/@roots/bud-dashboard/src/views/server.tsx @@ -1,15 +1,15 @@ -import figures from '@roots/bud-support/figures' -import {Box, Text} from '@roots/bud-support/ink' -import {externalNetworkInterface} from '@roots/bud-support/os' +import figures from "@roots/bud-support/figures"; +import { Box, Text } from "@roots/bud-support/ink"; +import { externalNetworkInterface } from "@roots/bud-support/os"; interface Props { - devUrl?: URL - displayServerInfo?: boolean - mode?: `development` | `production` - proxy?: unknown - proxyUrl?: URL - publicDevUrl?: URL - publicProxyUrl?: URL + devUrl?: URL; + displayServerInfo?: boolean; + mode?: `development` | `production`; + proxy?: unknown; + proxyUrl?: URL; + publicDevUrl?: URL; + publicProxyUrl?: URL; } /** @@ -24,36 +24,27 @@ export const Server = ({ publicDevUrl, publicProxyUrl, }: Props) => { - if (!displayServerInfo) return null - if (mode !== `development`) return null - if (!devUrl || !(devUrl instanceof URL)) return null + if ( + !displayServerInfo || + mode !== `development` || + !devUrl || + !(devUrl instanceof URL) + ) + return null; - const ipv4 = externalNetworkInterface.ipv4Url(devUrl.protocol) - ipv4.port = devUrl.port + const ipv4 = externalNetworkInterface.ipv4Url(devUrl.protocol); + ipv4.port = devUrl.port; return ( Network - - {proxy && proxyUrl?.href && ( - - - {figures.pointerSmall} proxy - + {` `} - - - {figures.lineDashed0} {proxyUrl.href} - - - {publicProxyUrl?.href !== proxyUrl.href && ( - - {figures.lineDashed0} {publicProxyUrl.href} - - )} - - - )} + {devUrl?.href && ( @@ -72,7 +63,7 @@ export const Server = ({ )} - {publicDevUrl?.href !== devUrl.href && ( + {publicDevUrl && publicDevUrl?.href !== devUrl.href && ( {figures.lineDashed0} {publicDevUrl.href} @@ -81,7 +72,39 @@ export const Server = ({ )} - ) -} + ); +}; + +const Proxy = ({ + proxy, + proxyUrl, + publicProxyUrl, +}: { + proxy?: Props[`proxy`]; + proxyUrl?: Props[`proxyUrl`]; + publicProxyUrl?: Props[`publicProxyUrl`]; +}) => { + if (!proxy || !proxyUrl || !(proxyUrl instanceof URL)) return null; + + return ( + + + {figures.pointerSmall} Proxy + + + + + {figures.lineDashed0} {proxyUrl.href} + + + {publicProxyUrl?.href && publicProxyUrl?.href !== proxyUrl.href && ( + + {figures.lineDashed0} {publicProxyUrl.href} + + )} + + + ); +}; -export {Server as default} +export { Server as default }; diff --git a/sources/@roots/bud-dashboard/test/app.test.tsx b/sources/@roots/bud-dashboard/test/app.test.tsx index 369a042978..e4053c992b 100644 --- a/sources/@roots/bud-dashboard/test/app.test.tsx +++ b/sources/@roots/bud-dashboard/test/app.test.tsx @@ -179,7 +179,7 @@ describe(`@roots/bud-dashboard app component`, () => { expect(lines[5]).toBe(Char.Vertical) expect(startsWith(lines[6], Char.BottomLeft)).toBe(true) - expect(lines[6]).toMatch(/0ms \[2\/2 modules cached\]$/) + expect(lines[6]).toMatch(/2 modules \[2\/2 modules cached\]$/) expect(lines[7]).toBe(Char.Empty) expect(lines[8]).toBeUndefined() @@ -203,7 +203,7 @@ describe(`@roots/bud-dashboard app component`, () => { expect(lines[3]).toBe(Char.Vertical) expect(startsWith(lines[4], Char.BottomLeft)).toBe(true) - expect(lines[4]).toMatch(/0ms \[2\/2 modules cached\]$/) + expect(lines[4]).toMatch(/2 modules \[2\/2 modules cached\]$/) expect(lines[5]).toBe(Char.Empty) }) @@ -310,7 +310,7 @@ describe(`@roots/bud-dashboard app component`, () => { expect(lines[5]).toBe(Char.Vertical) expect(startsWith(lines[6], Char.BottomLeft)).toBe(true) - expect(lines[6]).toMatch(/0ms \[2\/2 modules cached\]$/) + expect(lines[6]).toMatch(/2 modules \[2\/2 modules cached\]$/) expect(lines[7]).toBe(Char.Empty) expect(startsWith(lines[8], Char.TopLeft)).toBe(true) @@ -320,7 +320,7 @@ describe(`@roots/bud-dashboard app component`, () => { expect(lines[10]).toMatch(/│ foo/) expect(lines[11]).toMatch(/│ › foo.js /) expect(lines[12]).toBe(Char.Vertical) - expect(lines[13]).toMatch(/╰ 0ms \[2\/2 modules cached\]/) + expect(lines[13]).toMatch(/╰ 2 modules \[2\/2 modules cached\]/) expect(lines[14]).toBe(Char.Empty) }) @@ -350,7 +350,7 @@ describe(`@roots/bud-dashboard app component`, () => { expect(lines[6]).toBe(Char.Vertical) expect(startsWith(lines[7], Char.BottomLeft)).toBe(true) - expect(lines[7]).toMatch(/0ms \[2\/2 modules cached\]$/) + expect(lines[7]).toMatch(/2 modules \[2\/2 modules cached\]$/) expect(lines[8]).toBe(Char.Empty) }) @@ -372,7 +372,7 @@ describe(`@roots/bud-dashboard app component`, () => { expect(lines[4]).toBe(Char.Vertical) expect(startsWith(lines[5], Char.BottomLeft)).toBe(true) - expect(lines[5]).toMatch(/0ms \[2\/2 modules cached\]$/) + expect(lines[5]).toMatch(/2 modules \[2\/2 modules cached\]$/) expect(lines[6]).toBe(Char.Empty) }) @@ -418,19 +418,19 @@ describe(`@roots/bud-dashboard app component`, () => { expect(lines[9]).toBe(Char.Vertical) expect(startsWith(lines[10], Char.BottomLeft)).toBe(true) - expect(lines[10]).toMatch(/0ms \[2\/2 modules cached\]$/) + expect(lines[10]).toMatch(/2 modules \[2\/2 modules cached\]$/) expect(lines[11]).toBe(Char.Empty) expect(lines[12]).toMatch(/Network/) - expect(lines[13]).toMatch(/ › proxy ┄ http:\/\/localhost:\d+\//) - expect(lines[14]).toBe(Char.Empty) - expect(lines[15]).toMatch(/ ┄ http:\/\/example\.test\//) - expect(lines[16]).toMatch(/ › dev ┄ http:\/\/localhost:\d+\//) - expect(lines[17]).toMatch( + expect(lines[14]).toMatch(/ › Proxy ┄ http:\/\/.+:\d+\//) + expect(lines[15]).toBe(Char.Empty) + expect(lines[16]).toMatch(/ ┄ http:\/\/.+/) + expect(lines[17]).toMatch(/ › dev ┄ http:\/\/.+\//) + expect(lines[18]).toMatch( / ┄ http:\/\/\d+\.\d+\.\d+\.\d+:\d+\//, ) - expect(lines[18]).toMatch(/ ┄ http:\/\/example\.test:\d+\//) - expect(lines[19]).toBe(Char.Empty) + expect(lines[19]).toMatch(/ ┄ http:\/\/.+:\d+\//) + expect(lines[20]).toBe(Char.Empty) }) it(`should not render proxy info when proxy not set`, () => { @@ -450,7 +450,7 @@ describe(`@roots/bud-dashboard app component`, () => { />, ) const lines = stripAnsi(frames.pop()).split(Char.NewLine) - expect(lines[13]).toMatch(/ › dev ┄ http:\/\/localhost:\d+\//) + expect(lines[14]).toMatch(/ › dev ┄ http:\/\/.+:\d+\//) }) it(`should not throw when crazy input happens`, () => { diff --git a/sources/@roots/bud-dashboard/tsconfig.json b/sources/@roots/bud-dashboard/tsconfig.json index 936b523eca..fe5e53f7c8 100644 --- a/sources/@roots/bud-dashboard/tsconfig.json +++ b/sources/@roots/bud-dashboard/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../../../config/tsconfig.json", "compilerOptions": { "rootDir": "./src", - "outDir": "./lib" + "outDir": "./lib", + "strict": true }, "include": ["./src/**/*.tsx", "./src/**/*.ts"], "exclude": ["**/*.test.ts", "**/*.test.tsx"], diff --git a/sources/@roots/bud-extensions/src/cdn/index.ts b/sources/@roots/bud-extensions/src/cdn/index.ts index 3ffb74eac6..c577750ba6 100644 --- a/sources/@roots/bud-extensions/src/cdn/index.ts +++ b/sources/@roots/bud-extensions/src/cdn/index.ts @@ -23,7 +23,7 @@ export type Options = { cacheLocation: false | string frozen: boolean lockfileLocation: string - proxy: string + proxy: false | string upgrade: boolean } @@ -96,7 +96,7 @@ export default class Cdn extends Extension implements Api { bud.hooks.on(`build.experiments`, experiments => ({ ...(experiments ?? {}), buildHttp: { - allowedUris: Array.from(this.allowedUris), + allowedUris: Array.from(this.allowedUris ?? []), cacheLocation: this.cacheEnabled ? this.cacheLocation : false, frozen: this.frozen, lockfileLocation: this.lockfileLocation, @@ -114,7 +114,7 @@ export default class Cdn extends Extension implements Api { : Array.isArray(rule.include) ? rule.include : []), - ...Array.from(this.allowedUris), + ...Array.from(this.allowedUris ?? []), ]) }) diff --git a/sources/@roots/bud-extensions/src/copy-webpack-plugin/index.ts b/sources/@roots/bud-extensions/src/copy-webpack-plugin/index.ts index 50ecd8cd1d..c347aa0d51 100644 --- a/sources/@roots/bud-extensions/src/copy-webpack-plugin/index.ts +++ b/sources/@roots/bud-extensions/src/copy-webpack-plugin/index.ts @@ -16,7 +16,7 @@ import {Plugin} from '@roots/bud-support/copy-webpack-plugin' @label(`@roots/bud-extensions/copy-webpack-plugin`) @plugin(Plugin) @options({patterns: []}) -@when((_app, options) => options.patterns?.length > 0) +@when((_app, options) => options?.patterns?.length > 0) class BudCopyPlugin extends Extension { public getPatterns: () => Options[`patterns`] diff --git a/sources/@roots/bud-extensions/src/import-map/index.ts b/sources/@roots/bud-extensions/src/import-map/index.ts index 04c8e0dfc6..9c85e5b56a 100644 --- a/sources/@roots/bud-extensions/src/import-map/index.ts +++ b/sources/@roots/bud-extensions/src/import-map/index.ts @@ -1,5 +1,6 @@ import {Extension} from '@roots/bud-framework/extension' import {bind, label} from '@roots/bud-framework/extension/decorators' +import isString from '@roots/bud-support/lodash/isString' /** * Import map extension @@ -13,10 +14,10 @@ export default class BudImportMapExtension extends Extension { public override async register(bud) { if (!bud.context.manifest?.imports) return - Object.entries(bud.context.manifest.imports).map( - ([k, v]: [string, string]) => { - if (!v.match(/https?:^/)) bud.alias(k, bud.path(v)) - }, - ) + Object.entries(bud.context.manifest.imports) + .filter(([k, v]) => isString(k) && isString(v)) + .map(([k, v]) => { + if (v && !(v as any).match(/https?:^/)) bud.alias(k, bud.path(v)) + }) } } diff --git a/sources/@roots/bud-extensions/src/service.ts b/sources/@roots/bud-extensions/src/service.ts index 9908798e61..66b9aba477 100644 --- a/sources/@roots/bud-extensions/src/service.ts +++ b/sources/@roots/bud-extensions/src/service.ts @@ -12,6 +12,7 @@ import {isConstructor} from '@roots/bud-extensions/helpers/isConstructor' import {Extension} from '@roots/bud-framework/extension' import {Service} from '@roots/bud-framework/service' import {bind} from '@roots/bud-support/decorators/bind' +import {ExtensionError} from '@roots/bud-support/errors' import isFunction from '@roots/bud-support/lodash/isFunction' import isUndefined from '@roots/bud-support/lodash/isUndefined' import Container from '@roots/container' @@ -246,7 +247,7 @@ export class Extensions extends Service implements BudExtensions { if (this.has(signifier)) { this.logger.info(signifier, `already imported`) - return this.get(signifier) + return this.get(signifier) as Extension } const extension: Extension = await this.app.module @@ -255,8 +256,13 @@ export class Extensions extends Service implements BudExtensions { this.unresolvable.add(signifier) if (required) throw error }) + if (!extension) { - if (required) throw `Extension ${signifier} not found but required` + if (required) + throw new ExtensionError( + `Extension ${signifier} not found but required`, + ) + return } @@ -434,8 +440,8 @@ export class Extensions extends Service implements BudExtensions { if (instance.dependsOn) { await Array.from(instance.dependsOn) .filter(this.isAllowed) - .filter((signifier: K) => !this.unresolvable.has(signifier)) - .reduce(async (promised, signifier: K) => { + .filter((signifier: string) => !this.unresolvable.has(signifier)) + .reduce(async (promised, signifier: any) => { await promised if (!this.has(signifier)) await this.import(signifier, true) @@ -450,8 +456,8 @@ export class Extensions extends Service implements BudExtensions { if (this.options.is(`discover`, true) && instance.dependsOnOptional) await Array.from(instance.dependsOnOptional) .filter(this.isAllowed) - .filter((signifier: K) => !this.unresolvable.has(signifier)) - .reduce(async (promised, signifier: K) => { + .filter((signifier: string) => !this.unresolvable.has(signifier)) + .reduce(async (promised, signifier: any) => { await promised if (!this.has(signifier)) await this.import(signifier, false) if (!this.has(signifier)) { @@ -472,7 +478,7 @@ export class Extensions extends Service implements BudExtensions { */ @bind public set(value: Extension): this { - const key = value.label ?? randomUUID() + const key = (value.label ?? randomUUID()) as any this.repository[key] = value this.logger.success(`set`, key) diff --git a/sources/@roots/bud-extensions/src/tsconfig-values/index.ts b/sources/@roots/bud-extensions/src/tsconfig-values/index.ts index bf544460b7..8b83965aaa 100644 --- a/sources/@roots/bud-extensions/src/tsconfig-values/index.ts +++ b/sources/@roots/bud-extensions/src/tsconfig-values/index.ts @@ -16,10 +16,10 @@ import { import isString from '@roots/bud-support/lodash/isString' type CompilerOptions = { - baseUrl?: string - outDir?: string - paths?: Record> - rootDir?: string + baseUrl: string | undefined + outDir: string | undefined + paths: Record> | undefined + rootDir: string | undefined } type BudOptions = { @@ -27,13 +27,23 @@ type BudOptions = { } type Options = { - bud?: BudOptions - compilerOptions?: CompilerOptions - exclude?: Array - include?: Array + bud: BudOptions | undefined + compilerOptions: CompilerOptions | undefined + exclude: Array | undefined + include: Array | undefined } -type Api = PublicExtensionApi +type Api = PublicExtensionApi & { + getBud: () => Api['bud'] + getCompilerOptions: () => Api['compilerOptions'] + getExclude: () => Api['exclude'] + getInclude: () => Api['include'] + + setBud: (bud: BudOptions) => Api + setCompilerOptions: (options: CompilerOptions) => Api + setExclude: (exclude: Array) => Api + setInclude: (include: Array) => Api +} /** * The BudTsConfigValues class configures the bud.js application using settings @@ -164,18 +174,19 @@ export default class BudTsConfigValues // and then set as paths w/ aliases in the bud.js application. if (this.compilerOptions?.paths) { const normalPaths = this.normalizePaths(this.compilerOptions.paths) - bud - .setPath(normalPaths) - // @ts-ignore - .alias( - Object.entries(normalPaths).reduce( - (a, [k, v]) => ({ - ...a, - [k]: this.makeAbsolute(v), - }), - {}, - ), - ) + if (normalPaths) + bud + .setPath(normalPaths) + // @ts-ignore + .alias( + Object.entries(normalPaths).reduce( + (a, [k, v]) => ({ + ...a, + [k]: this.makeAbsolute(v), + }), + {}, + ), + ) } // If specific directories have been defined to be included in the tsconfig.json, @@ -235,12 +246,14 @@ export default class BudTsConfigValues */ @bind public normalizePaths( - paths: Options['compilerOptions']['paths'], + paths: Record>, ): Record | undefined { if (!paths) return const normalPaths = Object.entries(paths) - .map(([k, v]: [string, Array]) => [k, v[0]]) + .filter(([k, v]) => Array.isArray(v)) + .map(([k, v]) => [k, v[0]]) + .filter(Boolean) .map(tuple => tuple.map((str: string) => str.replace(`/*`, ``))) .reduce( (acc, [key, value]) => ({ diff --git a/sources/@roots/bud-extensions/test/extensions/esm.test.ts b/sources/@roots/bud-extensions/test/extensions/esm.test.ts index fd3418da5f..baf9970881 100644 --- a/sources/@roots/bud-extensions/test/extensions/esm.test.ts +++ b/sources/@roots/bud-extensions/test/extensions/esm.test.ts @@ -1,9 +1,7 @@ -import '@roots/bud-extensions' - import {factory} from '@repo/test-kit' -import {describe, expect, it, vi} from 'vitest' - +import '@roots/bud-extensions' import extensionConstructor from '@roots/bud-extensions/esm' +import {describe, expect, it, vi} from 'vitest' describe(`@roots/bud-extensions/esm`, () => { it(`is constructable`, () => { @@ -16,10 +14,6 @@ describe(`@roots/bud-extensions/esm`, () => { expect(new extensionConstructor(bud)).toBeInstanceOf( extensionConstructor, ) - - expect(new extensionConstructor(bud).buildBefore).not.toBe( - bud.extensions.get(`@roots/bud-extensions/esm`).buildBefore, - ) }) it(`should be exposed via bud.esm`, async () => { diff --git a/sources/@roots/bud-framework/package.json b/sources/@roots/bud-framework/package.json index 128d64377f..87e57837eb 100644 --- a/sources/@roots/bud-framework/package.json +++ b/sources/@roots/bud-framework/package.json @@ -56,6 +56,7 @@ "./configuration": "./lib/configuration/index.js", "./config": "./lib/config/index.js", "./context": "./lib/context.js", + "./env": "./lib/env.js", "./extension/decorators/*": "./lib/extension/decorators/*.js", "./extension/decorators": "./lib/extension/decorators/index.js", "./extension": "./lib/extension/index.js", @@ -66,6 +67,7 @@ "./module": "./lib/module.js", "./options": "./lib/options/index.js", "./options/*": "./lib/options/*.js", + "./project": "./lib/project.js", "./registry": "./lib/registry/index.js", "./registry/*": "./lib/registry/*.js", "./service": "./lib/service.js", @@ -97,6 +99,9 @@ "context": [ "./lib/context.d.ts" ], + "env": [ + "./lib/env.d.ts" + ], "fs": [ "./lib/fs.d.ts" ], @@ -109,6 +114,9 @@ "extension/decorators/*": [ "./lib/extension/decorators/*.d.ts" ], + "project": [ + "./lib/project.d.ts" + ], "methods/setPath": [ "./lib/methods/setPath/index.d.ts" ], diff --git a/sources/@roots/bud-framework/src/bootstrap.ts b/sources/@roots/bud-framework/src/bootstrap.ts index f1a937e2a7..d5448d7afe 100644 --- a/sources/@roots/bud-framework/src/bootstrap.ts +++ b/sources/@roots/bud-framework/src/bootstrap.ts @@ -171,16 +171,16 @@ export const bootstrap = async function (bud: Bud) { 'location.@modules': isString(bud.context.modules) ? bud.context.modules : `node_modules`, - 'location.@os-cache': bud.context.paths[`os-cache`], - 'location.@os-config': bud.context.paths[`os-config`], - 'location.@os-data': bud.context.paths[`os-data`], - 'location.@os-log': bud.context.paths[`os-log`], - 'location.@os-temp': bud.context.paths[`os-temp`], + 'location.@os-cache': bud.context.paths?.[`os-cache`], + 'location.@os-config': bud.context.paths?.[`os-config`], + 'location.@os-data': bud.context.paths?.[`os-data`], + 'location.@os-log': bud.context.paths?.[`os-log`], + 'location.@os-temp': bud.context.paths?.[`os-temp`], 'location.@src': isString(bud.context.input) && bud.context.input !== `` ? bud.context.input : `src`, - 'location.@storage': bud.context.paths.storage, + 'location.@storage': bud.context.paths?.storage ?? `.bud`, 'pattern.css': /(?!.*\.module)\.css$/, 'pattern.cssModule': /\.module\.css$/, 'pattern.csv': /\.(csv|tsv)$/, @@ -223,12 +223,23 @@ export const bootstrap = async function (bud: Bud) { bud.hooks.action(`compiler.before`, bud.module.compilerBefore) - await [`bootstrap`, `register`, `boot`] - .reduce(async (promised, event: keyof Registry.EventsStore) => { - await promised - await bud.executeServiceCallbacks(event).catch(bud.catch) - }, Promise.resolve()) - .catch(bud.catch) + const initializeEvents: Array<`${keyof Registry.EventsStore}`> = [ + `bootstrap`, + `register`, + `boot`, + ] + + await initializeEvents.reduce( + async (promised: Promise, event: any) => { + try { + await promised + await bud.executeServiceCallbacks(event).catch(bud.catch) + } catch (error) { + throw error + } + }, + Promise.resolve({}), + ) bud.after(bud.module.after) } diff --git a/sources/@roots/bud-framework/src/bud.ts b/sources/@roots/bud-framework/src/bud.ts index c2fea1946e..bfede80def 100644 --- a/sources/@roots/bud-framework/src/bud.ts +++ b/sources/@roots/bud-framework/src/bud.ts @@ -256,7 +256,7 @@ export class Bud { * @readonly */ public get label() { - return this.context?.label + return this.context?.label ?? `bud` } /** diff --git a/sources/@roots/bud-framework/src/configuration/configuration.ts b/sources/@roots/bud-framework/src/configuration/configuration.ts index 66f30de1bf..2eeae4a702 100644 --- a/sources/@roots/bud-framework/src/configuration/configuration.ts +++ b/sources/@roots/bud-framework/src/configuration/configuration.ts @@ -68,7 +68,7 @@ class Configuration { if (isObject(request)) await Promise.all( - Object.entries(value).map(async ([key, value]) => { + Object.entries(parsedValue).map(async ([key, value]) => { return await Promise.resolve( this.handleConfigEntry(request, [key, value]), ) diff --git a/sources/@roots/bud-framework/src/context.ts b/sources/@roots/bud-framework/src/context.ts index afc5ec6aa9..bb50221571 100644 --- a/sources/@roots/bud-framework/src/context.ts +++ b/sources/@roots/bud-framework/src/context.ts @@ -9,8 +9,6 @@ import type {parse} from 'node:path' * The context object is constructing during bootstrapping. You shouldn't modify * or reference this object unless you know what you're doing and are okay with updating * your code when the context object changes between releases. - * - * @internal */ export interface Context { /** diff --git a/sources/@roots/bud/src/services/env.ts b/sources/@roots/bud-framework/src/env.ts similarity index 95% rename from sources/@roots/bud/src/services/env.ts rename to sources/@roots/bud-framework/src/env.ts index 7ae382aee0..680af9446c 100644 --- a/sources/@roots/bud/src/services/env.ts +++ b/sources/@roots/bud-framework/src/env.ts @@ -1,4 +1,4 @@ -import type {Bud} from '@roots/bud' +import type {Bud} from '@roots/bud-framework' import {ServiceContainer} from '@roots/bud-framework/service' import {bind} from '@roots/bud-support/decorators/bind' diff --git a/sources/@roots/bud-framework/src/fs.ts b/sources/@roots/bud-framework/src/fs.ts index 2828fe4ccc..fe09034a36 100644 --- a/sources/@roots/bud-framework/src/fs.ts +++ b/sources/@roots/bud-framework/src/fs.ts @@ -7,7 +7,9 @@ import {bind} from '@roots/bud-support/decorators/bind' import {BudError} from '@roots/bud-support/errors' import {Filesystem, json, yml} from '@roots/bud-support/filesystem' import globby from '@roots/bud-support/globby' -import isUndefined from '@roots/bud-support/lodash/isUndefined' +import isBoolean from '@roots/bud-support/lodash/isBoolean' +import isNumber from '@roots/bud-support/lodash/isNumber' +import isString from '@roots/bud-support/lodash/isString' import logger from '@roots/bud-support/logger' import {S3} from '@roots/filesystem' @@ -18,7 +20,7 @@ export class FS extends Filesystem implements Contract { /** * JSON * - * @see {@link https://bud.js.org/docs/bud.fs/json} + * @see {@link https://bud.js.org/reference/bud.fs/json} */ public json: typeof json = json @@ -30,14 +32,14 @@ export class FS extends Filesystem implements Contract { /** * S3 * - * @see {@link https://bud.js.org/docs/bud.fs/s3} + * @see {@link https://bud.js.org/reference/bud.fs/s3} */ - public s3?: S3 + public s3: S3 /** * YML * - * @see {@link https://bud.js.org/docs/bud.fs/yml} + * @see {@link https://bud.js.org/reference/bud.fs/yml} */ public yml: typeof yml = yml @@ -83,7 +85,7 @@ export class FS extends Filesystem implements Contract { * * @param bucket - {@link S3.bucket} * - * @see {@link https://bud.js.org/docs/bud.fs/s3#setup} + * @see {@link https://bud.js.org/reference/bud.fs/s3#setup} */ @bind public setBucket(bucket: string) { @@ -99,7 +101,7 @@ export class FS extends Filesystem implements Contract { * * @param credentials - {@link S3.credentials} * - * @see {@link https://bud.js.org/docs/bud.fs/s3#setup} + * @see {@link https://bud.js.org/reference/bud.fs/s3#setup} */ @bind public setCredentials(credentials: S3[`config`][`credentials`]) { @@ -115,7 +117,7 @@ export class FS extends Filesystem implements Contract { * * @param endpoint - S3 endpoint * - * @see {@link https://bud.js.org/docs/bud.fs/s3#setup} + * @see {@link https://bud.js.org/reference/bud.fs/s3#setup} */ @bind public setEndpoint(endpoint: S3[`config`][`endpoint`]) { @@ -131,7 +133,7 @@ export class FS extends Filesystem implements Contract { * * @param region - S3 region * - * @see {@link https://bud.js.org/docs/bud.fs/s3#setup} + * @see {@link https://bud.js.org/reference/bud.fs/s3#setup} */ @bind public setRegion(region: S3[`config`][`region`]) { @@ -147,7 +149,7 @@ export class FS extends Filesystem implements Contract { * * @param options - upload options * - * @see {@link https://bud.js.org/docs/bud.fs/s3#uploading-files} + * @see {@link https://bud.js.org/reference/bud.fs/s3#uploading-files} */ @bind public upload(options?: { @@ -156,13 +158,20 @@ export class FS extends Filesystem implements Contract { keep?: false | number source?: string }): this { + if (!this.s3) { + throw new BudError( + `S3 is not configured. See https://budjs.dev/reference/bud.fs/s3`, + ) + } + const {destination, files, keep, source} = { destination: options?.destination, - files: isUndefined(options?.files) ? `**/*` : options.files, - keep: isUndefined(options?.keep) ? 5 : options.keep, - source: isUndefined(options?.source) - ? this.app.path(`@dist`) - : options.source, + files: options?.files ?? `**/*`, + keep: + isNumber(options?.keep) || isBoolean(options?.keep) + ? options?.keep + : 5, + source: options?.source ?? this.app.path(`@dist`), } const s3Path = (path: string) => @@ -211,14 +220,17 @@ export class FS extends Filesystem implements Contract { await Promise.all( [...new Set(stale)] - .flatMap(([key, value]: [string, Array]) => value) + .flatMap(([, value]) => value) .filter( key => - !entries.some(([_, value]: [string, Array]) => - value.includes(key), + !entries.some( + ([_, value]) => + Array.isArray(value) && value.includes(key), ), ) .map(async key => { + if (!isString(key)) return + const fileExists = await this.s3.exists(key) if (!fileExists) return diff --git a/sources/@roots/bud-framework/src/index.ts b/sources/@roots/bud-framework/src/index.ts index 16180d180f..87d40e2aec 100644 --- a/sources/@roots/bud-framework/src/index.ts +++ b/sources/@roots/bud-framework/src/index.ts @@ -14,6 +14,7 @@ export { default as Service, ServiceContainer, } from './service.js' + export type {Context} from '@roots/bud-framework/context' export type * from '@roots/bud-framework/config' export type * as Registry from './registry/index.js' diff --git a/sources/@roots/bud-framework/src/methods/close.ts b/sources/@roots/bud-framework/src/methods/close.ts index 27b33b3f42..0c6665c0a0 100644 --- a/sources/@roots/bud-framework/src/methods/close.ts +++ b/sources/@roots/bud-framework/src/methods/close.ts @@ -1,6 +1,6 @@ -import type {Bud} from '@roots/bud-framework' +import type { Bud } from "@roots/bud-framework"; -import isFunction from '@roots/bud-support/lodash/isFunction' +import isFunction from "@roots/bud-support/lodash/isFunction"; /** * Close interface @@ -9,7 +9,7 @@ import isFunction from '@roots/bud-support/lodash/isFunction' * @param done - Callback function to be called before end of run */ export interface close { - (done?: () => unknown): Promise + (done?: () => unknown): Promise; } /** @@ -24,34 +24,33 @@ export function close(onComplete?: () => unknown) { try { if (this.compiler?.instance?.running) { this.compiler.instance.close(() => { - closeDevelopmentServer(this) - }) + closeDevelopmentServer(this); + }); } else { - closeDevelopmentServer(this) + closeDevelopmentServer(this); } } catch (error) { - throw error + throw error; } - if (onComplete) return onComplete() + if (onComplete) return onComplete(); } const closeDevelopmentServer = (bud: Bud) => { + if (!bud.server) return; + try { - if ( - bud.isDevelopment && - isFunction(bud.server?.watcher?.instance?.close) - ) { - bud.server.watcher.instance.close() + if (bud.isDevelopment && isFunction(bud.server.watcher?.instance?.close)) { + bud.server.watcher.instance.close(); } if ( bud.isDevelopment && - isFunction(bud.server?.connection?.instance?.close) + isFunction(bud.server.connection?.instance?.close) ) { - bud.server.connection.instance.close() + bud.server.connection.instance.close(); } } catch (error) { - throw error + throw error; } -} +}; diff --git a/sources/@roots/bud-framework/src/methods/glob.ts b/sources/@roots/bud-framework/src/methods/glob.ts index b46081ed97..7052f803bc 100644 --- a/sources/@roots/bud-framework/src/methods/glob.ts +++ b/sources/@roots/bud-framework/src/methods/glob.ts @@ -28,6 +28,7 @@ export const globSync: globSync = function (...searches) { return results } catch (error) { this.catch(error) + throw error // this should never happen } } @@ -48,6 +49,7 @@ export const glob: glob = async function (...searches) { return results } catch (error) { this.catch(error) + throw error // this should never happen } } diff --git a/sources/@roots/bud-framework/src/methods/path.ts b/sources/@roots/bud-framework/src/methods/path.ts index 8a337b326f..b6655192cb 100644 --- a/sources/@roots/bud-framework/src/methods/path.ts +++ b/sources/@roots/bud-framework/src/methods/path.ts @@ -13,10 +13,8 @@ export interface path { (...values: Array): string } export const path: path = function (...values) { - const app = this as Bud - /* Exit early with context.basedir if no path was passed */ - if (!values?.length) return app.context.basedir + if (!values?.length) return this.context.basedir if (isAbsolute(join(...values))) { return join(...values) @@ -24,19 +22,23 @@ export const path: path = function (...values) { values = values.flatMap(value => value.split(sep)) - const hash = app.hooks.filter(`value.hashFormat`, `[contenthash:6]`) - const name = app.hooks.filter(`value.fileFormat`, `[name]`) + const hash = + this.hooks.filter(`value.hashFormat`, `[contenthash:6]`) ?? + `[contenthash:6]` + const name = this.hooks.filter(`value.fileFormat`, `[name]`) ?? `[name]` const transformMagicString = makeParseMagicString( - app.context.hash ? `${name}.${hash}` : name, + this.context.hash ? `${name}.${hash}` : name, hash, ) - const parseAlias = makeParseAlias(app) + const parseAlias = makeParseAlias(this) const result = join(...values.map(transformMagicString).map(parseAlias)) - return isAbsolute(result) ? result : resolve(app.context.basedir, result) + return isAbsolute(result) + ? result + : resolve(this.context.basedir, result) } /** diff --git a/sources/@roots/bud-framework/src/methods/pipe.ts b/sources/@roots/bud-framework/src/methods/pipe.ts index 828c3709f7..26cd5b25e9 100644 --- a/sources/@roots/bud-framework/src/methods/pipe.ts +++ b/sources/@roots/bud-framework/src/methods/pipe.ts @@ -35,17 +35,14 @@ export interface pipe { * ) // resulting value is 3 * ``` */ -export const pipe: pipe = async function ( - this: Bud, - functions: Array>, - maybeInitialValue?: T, -) { - const initialValue = Promise.resolve( - !isUndefined(maybeInitialValue) ? maybeInitialValue : (this as T), - ) - +export const pipe: pipe = async function (functions, maybeInitialValue) { return await functions.reduce( - async (value, fn) => await fn(await value), - initialValue, + async (value, fn) => { + const nextValue = await value + return await fn(nextValue) + }, + Promise.resolve( + !isUndefined(maybeInitialValue) ? maybeInitialValue : this, + ), ) } diff --git a/sources/@roots/bud-framework/src/methods/publicPath.ts b/sources/@roots/bud-framework/src/methods/publicPath.ts index 234eecae84..91b1b5b373 100644 --- a/sources/@roots/bud-framework/src/methods/publicPath.ts +++ b/sources/@roots/bud-framework/src/methods/publicPath.ts @@ -1,7 +1,5 @@ import {join} from 'node:path' -import type {Bud} from '../index.js' - export interface publicPath { (...parts: Array): string } @@ -13,9 +11,7 @@ export interface publicPath { * Path from web root to assets */ export const publicPath: publicPath = function (...parts) { - const ctx = this as Bud - - const basePath = ctx.hooks.filter(`build.output.publicPath`, `auto`) + const basePath = this.hooks.filter(`build.output.publicPath`, `auto`) if (!parts.length) return basePath if (basePath !== `auto`) return join(basePath, ...parts) diff --git a/sources/@roots/bud-framework/src/methods/run.ts b/sources/@roots/bud-framework/src/methods/run.ts index dce86fb1be..b0271e0be7 100644 --- a/sources/@roots/bud-framework/src/methods/run.ts +++ b/sources/@roots/bud-framework/src/methods/run.ts @@ -1,5 +1,4 @@ -import type {Bud} from '@roots/bud-framework' -import type {WebpackError} from '@roots/bud-framework/config' +import type {Bud, Compiler} from '@roots/bud-framework' /** * Run the build @@ -9,15 +8,21 @@ export interface run { } export const run: run = async function (this: Bud) { - if (this.isProduction && this.compiler) { + if (!hasCompiler(this)) { + throw new Error(`Compiiler not found`) + } + + if (this.isProduction) { const compilation = await this.compiler ?.compile(this) .catch(this.catch) - compilation?.run((error: WebpackError) => { + compilation?.run((error?: Error | null | undefined) => { + if (!hasCompiler(this)) return if (error) this.compiler.onError(error) - compilation.close((error: WebpackError) => { + compilation.close((error?: Error | null | undefined) => { + if (!hasCompiler(this)) return if (error) this.compiler.onError(error) }) }) @@ -26,3 +31,7 @@ export const run: run = async function (this: Bud) { if (this.isDevelopment && this.server) await this.server.run().catch(this.server.catch) } + +function hasCompiler(bud: Bud): bud is Bud & {compiler: Compiler} { + return bud.compiler !== undefined +} diff --git a/sources/@roots/bud-framework/src/methods/sequence.ts b/sources/@roots/bud-framework/src/methods/sequence.ts index c51a81e15a..7c6cbc26ca 100644 --- a/sources/@roots/bud-framework/src/methods/sequence.ts +++ b/sources/@roots/bud-framework/src/methods/sequence.ts @@ -44,9 +44,7 @@ export interface sequenceSync { export const sequenceSync: sequenceSync = ( fns: Array, ): Bud => { - const app = this as Bud - - fns.map(fn => fn.call(this, app)) + fns.map(fn => fn.call(this, this)) - return app + return this as unknown as Bud } diff --git a/sources/@roots/bud-framework/src/methods/setPublicPath.ts b/sources/@roots/bud-framework/src/methods/setPublicPath.ts index 0d6482cfc1..1065911bdf 100644 --- a/sources/@roots/bud-framework/src/methods/setPublicPath.ts +++ b/sources/@roots/bud-framework/src/methods/setPublicPath.ts @@ -1,9 +1,15 @@ import {sep} from 'node:path' +import isString from '@roots/bud-support/lodash/isString' + import type {Bud} from '../index.js' export interface setPublicPath { - (publicPath: ((publicPath: string) => string) | string): Bud + ( + publicPath: + | ((publicPath: string | undefined) => string | undefined) + | string, + ): Bud } /** @@ -31,15 +37,15 @@ export interface setPublicPath { * @see {@link https://bud.js.org/docs/bud.setPublicPath} */ export const setPublicPath: setPublicPath = function (publicPath) { - const app = this as Bud - - app.hooks.on(`build.output.publicPath`, publicPath) + this.hooks.on(`build.output.publicPath`, publicPath) // Normalize the publicPath in case the user did not end it with a slash. - app.hooks.on(`build.output.publicPath`, value => { + this.hooks.on(`build.output.publicPath`, (value = `auto`) => { if (value === `` || value === `auto`) return value + if (!isString(value)) return value + return !value.endsWith(sep) ? `${value}${sep}` : value }) - return app + return this } diff --git a/sources/@roots/bud-framework/src/methods/sh.ts b/sources/@roots/bud-framework/src/methods/sh.ts index 810b54a2f6..ecc639b25a 100644 --- a/sources/@roots/bud-framework/src/methods/sh.ts +++ b/sources/@roots/bud-framework/src/methods/sh.ts @@ -25,7 +25,12 @@ export const sh: sh = function ( .map(str => str.split(` `)) .flat() - const child = execa(commandInput.shift(), commandInput.filter(Boolean), { + const bin = commandInput.shift() + if (!bin) { + throw new Error(`No command provided`) + } + + const child = execa(bin, commandInput.filter(Boolean), { cwd: bud.context.basedir, ...options, }) diff --git a/sources/@roots/bud-framework/src/module.ts b/sources/@roots/bud-framework/src/module.ts index 5b9accd1e6..fbd914f743 100644 --- a/sources/@roots/bud-framework/src/module.ts +++ b/sources/@roots/bud-framework/src/module.ts @@ -133,6 +133,7 @@ export class Module extends Service { const path = options.bustCache ? `${this.resolved[signifier]}?v=${Date.now()}` : this.resolved[signifier] + const result = await import(path).catch(error => { logger .scope(`module`) @@ -141,7 +142,7 @@ export class Module extends Service { error, ) - this.resolved[signifier] = undefined + delete this.resolved[signifier] }) return options.raw ? result : result?.default ?? result diff --git a/sources/@roots/bud/src/services/project.ts b/sources/@roots/bud-framework/src/project.ts similarity index 96% rename from sources/@roots/bud/src/services/project.ts rename to sources/@roots/bud-framework/src/project.ts index 4c223e744b..cb324e5715 100644 --- a/sources/@roots/bud/src/services/project.ts +++ b/sources/@roots/bud-framework/src/project.ts @@ -1,4 +1,4 @@ -import type {Bud} from '@roots/bud' +import type {Bud} from '@roots/bud-framework' import type {Stats} from '@roots/bud-framework/config' import {Service} from '@roots/bud-framework/service' @@ -79,7 +79,7 @@ export default class Project extends Service { await bud.fs.write( bud.path(`@storage`, bud.label, `debug`, `stats.yml`), { - compilation: bud.compiler?.stats.toJson({all: true}), + compilation: bud.compiler?.stats.toJson(`verbose`), message: stats.toString(), }, ) diff --git a/sources/@roots/bud-framework/src/registry/dev.ts b/sources/@roots/bud-framework/src/registry/dev.ts index 23987cf4e9..2befa31e24 100644 --- a/sources/@roots/bud-framework/src/registry/dev.ts +++ b/sources/@roots/bud-framework/src/registry/dev.ts @@ -16,7 +16,7 @@ export interface Sync { /** * Scripts included in dev builds */ - 'client.scripts': Set<(app: Bud) => string> + 'client.scripts': Set<(app: Bud) => false | string> 'client.standalone': boolean diff --git a/sources/@roots/bud-framework/src/services/build/index.ts b/sources/@roots/bud-framework/src/services/build/index.ts index 688f9c1c65..1d11874f95 100644 --- a/sources/@roots/bud-framework/src/services/build/index.ts +++ b/sources/@roots/bud-framework/src/services/build/index.ts @@ -46,7 +46,7 @@ export interface Build { /** * Compiler configuration */ - config: Configuration + config: Partial /** * Get a {@link Item} instance diff --git a/sources/@roots/bud-framework/src/services/compiler/index.ts b/sources/@roots/bud-framework/src/services/compiler/index.ts index 97c7f6f72e..260ec2d751 100644 --- a/sources/@roots/bud-framework/src/services/compiler/index.ts +++ b/sources/@roots/bud-framework/src/services/compiler/index.ts @@ -29,7 +29,7 @@ export interface Compiler { /** * The compiler configuration */ - config: Array & {parallelism?: number} + config: Array> & {parallelism?: number} /** * Compiler implementation diff --git a/sources/@roots/bud-framework/src/services/dashboard/index.ts b/sources/@roots/bud-framework/src/services/dashboard/index.ts index 9f534640f6..a427c3056f 100644 --- a/sources/@roots/bud-framework/src/services/dashboard/index.ts +++ b/sources/@roots/bud-framework/src/services/dashboard/index.ts @@ -15,7 +15,9 @@ export interface Dashboard { /** * Format stats errors */ - formatStatsErrors: (stats: StatsError[]) => StatsError[] + formatStatsErrors: ( + errors: StatsError[] | undefined, + ) => StatsError[] | undefined /** * CLI instance diff --git a/sources/@roots/bud-framework/src/services/hooks/index.ts b/sources/@roots/bud-framework/src/services/hooks/index.ts index 991b624030..c1da364c28 100644 --- a/sources/@roots/bud-framework/src/services/hooks/index.ts +++ b/sources/@roots/bud-framework/src/services/hooks/index.ts @@ -48,10 +48,10 @@ export interface Hooks { * ) * ``` */ - filter: ( - id: T, - callback?: Registry.SyncCallback[T] | Registry.SyncRegistry[T], - ) => Registry.SyncRegistry[T] + filter: ( + id: K, + callback?: Registry.SyncRegistry[K], + ) => Registry.SyncRegistry[K] /** * Async version of hook.filter diff --git a/sources/@roots/bud-framework/src/services/server/middleware.ts b/sources/@roots/bud-framework/src/services/server/middleware.ts index b0561515c8..483b0c7b87 100644 --- a/sources/@roots/bud-framework/src/services/server/middleware.ts +++ b/sources/@roots/bud-framework/src/services/server/middleware.ts @@ -39,10 +39,10 @@ export type Middleware = { * Key mapped middleware */ export interface Available { - cookie?: Definition - dev?: Definition - hot?: Definition - proxy?: Definition + cookie: Definition + dev: Definition + hot: Definition + proxy: Definition } /** diff --git a/sources/@roots/bud-framework/src/services/server/watcher.ts b/sources/@roots/bud-framework/src/services/server/watcher.ts index f43f79ff58..e69bac10fa 100644 --- a/sources/@roots/bud-framework/src/services/server/watcher.ts +++ b/sources/@roots/bud-framework/src/services/server/watcher.ts @@ -33,7 +33,7 @@ export interface Watcher { /** * Initialize watch files */ - watch(): Promise + watch(): Promise /** * Watcher callback diff --git a/sources/@roots/bud/test/env.test.ts b/sources/@roots/bud-framework/test/env.test.ts similarity index 90% rename from sources/@roots/bud/test/env.test.ts rename to sources/@roots/bud-framework/test/env.test.ts index fa694fe92a..0105f289de 100644 --- a/sources/@roots/bud/test/env.test.ts +++ b/sources/@roots/bud-framework/test/env.test.ts @@ -1,9 +1,8 @@ -import {Bud} from '@roots/bud' import {factory} from '@repo/test-kit' +import {Bud} from '@roots/bud' +import Env from '@roots/bud-framework/env' import {beforeEach, describe, expect, it} from 'vitest' -import Env from '@roots/bud/services/env' - describe(`@roots/bud/services/env`, () => { let bud: Bud diff --git a/sources/@roots/bud/test/project.test.ts b/sources/@roots/bud-framework/test/project.test.ts similarity index 91% rename from sources/@roots/bud/test/project.test.ts rename to sources/@roots/bud-framework/test/project.test.ts index 2e69bbab1c..c6c34a15e3 100644 --- a/sources/@roots/bud/test/project.test.ts +++ b/sources/@roots/bud-framework/test/project.test.ts @@ -1,9 +1,8 @@ -import {Bud} from '@roots/bud' import {factory} from '@repo/test-kit' +import {Bud} from '@roots/bud' +import Project from '@roots/bud-framework/project' import {beforeEach, describe, expect, it, vi} from 'vitest' -import Project from '@roots/bud/services/project' - describe(`@roots/bud/services/project`, () => { let bud: Bud let project: Project diff --git a/sources/@roots/bud-minify/src/extension.ts b/sources/@roots/bud-minify/src/extension.ts index 373b600361..b675d69707 100644 --- a/sources/@roots/bud-minify/src/extension.ts +++ b/sources/@roots/bud-minify/src/extension.ts @@ -40,8 +40,16 @@ class BudMinimize extends Extension { */ @bind public override async register(bud: Bud) { - this.js = bud.extensions.get(`@roots/bud-minify/minify-js`) - bud.set(`terser`, bud.extensions.get(`@roots/bud-minify/minify-js`)) + this.js = bud.extensions.get( + `@roots/bud-minify/minify-js`, + ) as BudMinimize[`js`] + + bud.set( + `terser`, + bud.extensions.get( + `@roots/bud-minify/minify-js`, + ) as BudMinimize[`js`], + ) this.css = bud.extensions.get(`@roots/bud-minify/minify-css`) bud.set( diff --git a/sources/@roots/bud-minify/src/minify-css/index.ts b/sources/@roots/bud-minify/src/minify-css/index.ts index d76f17514e..1b4bdeea25 100644 --- a/sources/@roots/bud-minify/src/minify-css/index.ts +++ b/sources/@roots/bud-minify/src/minify-css/index.ts @@ -1,3 +1,5 @@ +import type {Bud} from '@roots/bud-framework' + import {Extension} from '@roots/bud-framework/extension' import {label} from '@roots/bud-framework/extension/decorators/label' import {production} from '@roots/bud-framework/extension/decorators/production' @@ -19,7 +21,7 @@ class BudMinimizeCSS extends BudMinimizeCSSPublicApi { * {@link Extension.buildBefore} */ @bind - public override async buildBefore({extensions, hooks}) { + public override async buildBefore({extensions, hooks}: Bud) { const { default: Minimizer, esbuildMinify, diff --git a/sources/@roots/bud-minify/src/minify-js/extension.config.ts b/sources/@roots/bud-minify/src/minify-js/extension.config.ts index 8083381332..d8c584a196 100644 --- a/sources/@roots/bud-minify/src/minify-js/extension.config.ts +++ b/sources/@roots/bud-minify/src/minify-js/extension.config.ts @@ -44,15 +44,15 @@ type BudMinimizeJSPublicInterface = StrictPublicExtensionApi< BudMinimizeJSPublicApi, BudMinimizeJSOptions > & { - dropComments: (enable?: boolean) => BudMinimizeJSPublicInterface - dropConsole: (enable?: boolean) => BudMinimizeJSPublicInterface - dropDebugger: (enable?: boolean) => BudMinimizeJSPublicInterface + dropComments: (enable?: boolean) => BudMinimizeJSPublicApi + dropConsole: (enable?: boolean) => BudMinimizeJSPublicApi + dropDebugger: (enable?: boolean) => BudMinimizeJSPublicApi mangle: ( mangle: OptionCallback< - BudMinimizeJSPublicInterface['terserOptions'], + BudMinimizeJSPublicApi['terserOptions'], `mangle` >, - ) => BudMinimizeJSPublicInterface + ) => BudMinimizeJSPublicApi } /** @@ -71,24 +71,22 @@ type BudMinimizeJSPublicInterface = StrictPublicExtensionApi< drop_debugger: true, unused: true, }, - ecma: undefined, - enclose: undefined, + ecma: `2020` as any, + enclose: false, format: { ascii_only: true, comments: false, }, - ie8: undefined, - keep_classnames: undefined, - keep_fnames: undefined, - mangle: { - safari10: true, - }, - module: undefined, - nameCache: undefined, - parse: undefined, - safari10: undefined, - sourceMap: undefined, - toplevel: undefined, + ie8: false, + keep_classnames: false, + keep_fnames: false, + mangle: {}, + module: false, + nameCache: {}, + parse: {}, + safari10: false, + sourceMap: false, + toplevel: true, }, }) class BudMinimizeJSPublicApi @@ -280,6 +278,7 @@ class BudMinimizeJSPublicApi * ``` */ @bind + // @ts-ignore public mangle( mangle: BudMinimizeJSPublicInterface['terserOptions']['mangle'], ): this { diff --git a/sources/@roots/bud-minify/src/minify-js/index.ts b/sources/@roots/bud-minify/src/minify-js/index.ts index feafe075ef..2881c9275d 100644 --- a/sources/@roots/bud-minify/src/minify-js/index.ts +++ b/sources/@roots/bud-minify/src/minify-js/index.ts @@ -1,3 +1,5 @@ +import type {Bud} from '@roots/bud-framework' + import {Extension} from '@roots/bud-framework/extension' import {label} from '@roots/bud-framework/extension/decorators/label' import {production} from '@roots/bud-framework/extension/decorators/production' @@ -18,7 +20,7 @@ class BudMinimizeJS extends BudMinimizeJSPublicApi { * {@link Extension.buildBefore} */ @bind - public override async buildBefore({extensions, hooks}) { + public override async buildBefore({extensions, hooks}: Bud) { const { default: Minimizer, esbuildMinify, diff --git a/sources/@roots/bud-minify/test/extension.test.ts b/sources/@roots/bud-minify/test/extension.test.ts index aebbfb0966..fb2a0323b5 100644 --- a/sources/@roots/bud-minify/test/extension.test.ts +++ b/sources/@roots/bud-minify/test/extension.test.ts @@ -1,12 +1,9 @@ -import '@roots/bud-minify/types' - import {type Bud, factory} from '@repo/test-kit' -import {Extension} from '@roots/bud-framework/extension' -import {beforeAll, describe, expect, it} from 'vitest' - import BudMinimize from '@roots/bud-minify' import BudMinimizeCss from '@roots/bud-minify/minify-css' import BudMinimizeJs from '@roots/bud-minify/minify-js' +import '@roots/bud-minify/types' +import {beforeAll, describe, expect, it} from 'vitest' describe(`@roots/bud-minify`, () => { let bud: Bud @@ -54,9 +51,7 @@ describe(`@roots/bud-minify`, () => { }) it(`instance.js.mangle`, async () => { - expect(instance.js.options.terserOptions.mangle).toStrictEqual({ - safari10: true, - }) + expect(instance.js.options.terserOptions.mangle).toStrictEqual({}) instance.js.mangle({toplevel: true}) expect(instance.js.options.terserOptions.mangle).toStrictEqual({ toplevel: true, diff --git a/sources/@roots/bud-postcss/src/options.ts b/sources/@roots/bud-postcss/src/options.ts index 34a6ded2ef..3750425502 100644 --- a/sources/@roots/bud-postcss/src/options.ts +++ b/sources/@roots/bud-postcss/src/options.ts @@ -35,9 +35,11 @@ type BudPostCssPublicInterface = StrictPublicExtensionApi< BudPostCssOptionsApi, Options > & { + getConfig(): boolean getPlugin(name: string): PluginReference getPluginOptions(name: string): Record getPluginPath(name: string): string + setConfig(config: boolean): BudPostCssPublicInterface setPlugin(name: string, plugin?: PluginInput): BudPostCssPublicInterface setPluginOptions( name: string, diff --git a/sources/@roots/bud-server/src/hooks/dev.client.scripts.ts b/sources/@roots/bud-server/src/hooks/dev.client.scripts.ts index f361e7026b..358efbb42d 100644 --- a/sources/@roots/bud-server/src/hooks/dev.client.scripts.ts +++ b/sources/@roots/bud-server/src/hooks/dev.client.scripts.ts @@ -8,7 +8,9 @@ import isUndefined from '@roots/bud-support/lodash/isUndefined' * * @returns Set of client script callbacks */ -export const callback = () => new Set([hmrClient, proxyClickInterceptor]) +export const callback = (): Set<(app: Bud) => false | string> => { + return new Set([hmrClient, proxyClickInterceptor]) +} /** * Proxy click interceptor @@ -18,7 +20,7 @@ export const callback = () => new Set([hmrClient, proxyClickInterceptor]) */ export const proxyClickInterceptor = (app: Bud) => { if (!app.hooks.filter(`dev.middleware.enabled`, []).includes(`proxy`)) - return + return false const params = new URLSearchParams({ replace: `/`, @@ -38,7 +40,7 @@ export const proxyClickInterceptor = (app: Bud) => { * @returns string */ export const hmrClient = (app: Bud) => { - if (app.context.hot === false) return + if (app.context.hot === false) return false const params = new URLSearchParams({ indicator: diff --git a/sources/@roots/bud-server/src/inject.ts b/sources/@roots/bud-server/src/inject.ts index fc13356642..b5d80de819 100644 --- a/sources/@roots/bud-server/src/inject.ts +++ b/sources/@roots/bud-server/src/inject.ts @@ -5,9 +5,9 @@ import type {Bud} from '@roots/bud-framework' */ export const inject = async ( app: Bud, - injection: Array<(app: Bud) => string>, + injection: Array<(app: Bud) => false | string>, ) => { - app.hooks.on(`build.entry`, entrypoints => { + app.hooks.on(`build.entry`, (entrypoints = {}) => { if (!injection) return entrypoints return Object.entries(entrypoints ?? {}).reduce( diff --git a/sources/@roots/bud-server/src/middleware/dev/factory.ts b/sources/@roots/bud-server/src/middleware/dev/factory.ts index 1d522441bd..8089897f48 100644 --- a/sources/@roots/bud-server/src/middleware/dev/factory.ts +++ b/sources/@roots/bud-server/src/middleware/dev/factory.ts @@ -7,8 +7,10 @@ import type { import WebpackDevMiddleware from '@roots/bud-support/webpack-dev-middleware' -export const factory: MiddlewareFactory = (app: Bud) => - WebpackDevMiddleware( +export const factory: MiddlewareFactory = (app: Bud) => { + if (!app.compiler) return undefined + + return WebpackDevMiddleware( app.compiler.instance as any, app.hooks.filter(`dev.middleware.dev.options`, { headers: app.hooks.filter(`dev.middleware.dev.options.headers`, [ @@ -30,3 +32,4 @@ export const factory: MiddlewareFactory = (app: Bud) => ), }), ) +} diff --git a/sources/@roots/bud-server/src/middleware/hot/factory.ts b/sources/@roots/bud-server/src/middleware/hot/factory.ts index 593906b4aa..5d41b0d909 100644 --- a/sources/@roots/bud-server/src/middleware/hot/factory.ts +++ b/sources/@roots/bud-server/src/middleware/hot/factory.ts @@ -16,11 +16,13 @@ import loggerInstance from '@roots/bud-support/logger' const middlewarePath = `/bud/hot` -let latestStats = null +let latestStats: null | StatsCompilation = null let closed = false let logger: typeof loggerInstance export const factory: MiddlewareFactory = (app: Bud) => { + if (!app.compiler) return + logger = loggerInstance.scope(app.label, `hmr`) as typeof logger return makeHandler(app.compiler.instance) } @@ -80,12 +82,12 @@ export const publish = ( const compilations = collectCompilations( statsCompilation.toJson({ all: false, - cached: true, - children: true, + assets: true, + errorDetails: false, errors: true, hash: true, - modules: true, timings: true, + warnings: true, }), ) @@ -107,16 +109,19 @@ export const publish = ( }) } -export const collectModules = (modules: Array) => - modules?.reduce( - (modules, module) => ({...modules, [module.id]: module.name}), - {}, - ) +export const collectModules = (modules?: Array) => { + if (!modules) return {} + + return modules?.reduce((modules, module) => { + if (!module.id || !module.name) return modules + return {...modules, [module.id]: module.name} + }, {}) +} export const collectCompilations = ( stats: StatsCompilation, ): Array => { - let collection = [] + let collection: Array = [] // Stats has modules, single bundle if (stats.modules) collection.push(stats) diff --git a/sources/@roots/bud-server/src/middleware/index.ts b/sources/@roots/bud-server/src/middleware/index.ts index 83961e9541..da850db1cc 100644 --- a/sources/@roots/bud-server/src/middleware/index.ts +++ b/sources/@roots/bud-server/src/middleware/index.ts @@ -2,7 +2,7 @@ import type {Bud} from '@roots/bud-framework' import type {RequestHandler} from '@roots/bud-support/express' export interface MiddlewareFactory { - (app: Bud): RequestHandler + (app: Bud): RequestHandler | undefined } export * as cookie from '@roots/bud-server/middleware/cookie' diff --git a/sources/@roots/bud-server/src/middleware/proxy/factory.ts b/sources/@roots/bud-server/src/middleware/proxy/factory.ts index addbb4c4a3..aafc977963 100644 --- a/sources/@roots/bud-server/src/middleware/proxy/factory.ts +++ b/sources/@roots/bud-server/src/middleware/proxy/factory.ts @@ -37,7 +37,7 @@ export const makeOptions = (app: Bud): Options => { ), cookieDomainRewrite: app.hooks.filter( `dev.middleware.proxy.options.cookieDomainRewrite`, - url.dev.host, + url.dev?.host, ), cookiePathRewrite: app.hooks.filter( `dev.middleware.proxy.options.cookiePathRewrite`, @@ -57,7 +57,7 @@ export const makeOptions = (app: Bud): Options => { ), hostRewrite: app.hooks.filter( `dev.middleware.proxy.options.hostRewrite`, - url.dev.host, + url.dev?.host, ), ignorePath: app.hooks.filter( `dev.middleware.proxy.options.ignorePath`, @@ -69,7 +69,7 @@ export const makeOptions = (app: Bud): Options => { ), logger: app.hooks.filter( `dev.middleware.proxy.options.logger`, - app.context.log + app.context.log && app.server?.logger ? app.server.logger.scope(app.label, `proxy`) : app.context.ci ? console diff --git a/sources/@roots/bud-server/src/middleware/proxy/responseInterceptor.ts b/sources/@roots/bud-server/src/middleware/proxy/responseInterceptor.ts index f1997a3b65..7610239887 100644 --- a/sources/@roots/bud-server/src/middleware/proxy/responseInterceptor.ts +++ b/sources/@roots/bud-server/src/middleware/proxy/responseInterceptor.ts @@ -56,6 +56,7 @@ const transformResponseBuffer = ( buffer: Buffer, ) => { if (!isTransformable(proxy)) return buffer + if (!url.dev?.origin || !url.publicProxy.origin) return buffer return bud.hooks .filter(`dev.middleware.proxy.replacements`, [ diff --git a/sources/@roots/bud-server/src/middleware/proxy/url.ts b/sources/@roots/bud-server/src/middleware/proxy/url.ts index 2103a9b3d4..51df234790 100644 --- a/sources/@roots/bud-server/src/middleware/proxy/url.ts +++ b/sources/@roots/bud-server/src/middleware/proxy/url.ts @@ -19,8 +19,8 @@ export class ApplicationURL { /** * Node URL for dev */ - public get dev(): URL { - return this.app.server.publicUrl + public get dev(): undefined | URL { + return this.app.server?.publicUrl } /** diff --git a/sources/@roots/bud-server/src/server/base.ts b/sources/@roots/bud-server/src/server/base.ts index 20a501aa9b..64a5953bfa 100644 --- a/sources/@roots/bud-server/src/server/base.ts +++ b/sources/@roots/bud-server/src/server/base.ts @@ -42,8 +42,8 @@ export abstract class BaseServer implements Connection { this.instance .listen( this.app.hooks.filter(`dev.listenOptions`, { - host: this.app.server.url.hostname, - port: Number(this.app.server.url.port), + host: this.app.server?.url.hostname, + port: Number(this.app.server?.url.port), }), ) .on( diff --git a/sources/@roots/bud-server/src/server/watcher.ts b/sources/@roots/bud-server/src/server/watcher.ts index 815f82f093..3e130140ce 100644 --- a/sources/@roots/bud-server/src/server/watcher.ts +++ b/sources/@roots/bud-server/src/server/watcher.ts @@ -51,7 +51,7 @@ export class Watcher implements BudWatcher { * Initialize watch files */ @bind - public async watch(): Promise { + public async watch(): Promise { if (this.app.context.dry) return this.files = this.app.hooks.filter(`dev.watch.files`, new Set([])) @@ -84,7 +84,7 @@ export class Watcher implements BudWatcher { `triggered reload`, ) - this.app.server.appliedMiddleware?.hot?.publish({ + this.app.server?.appliedMiddleware?.hot?.publish({ action: `reload`, message: `Detected file change: ${path}. Reloading window.`, }) diff --git a/sources/@roots/bud-server/src/service/index.ts b/sources/@roots/bud-server/src/service/index.ts index 8cfcd034ca..fbceb883bd 100644 --- a/sources/@roots/bud-server/src/service/index.ts +++ b/sources/@roots/bud-server/src/service/index.ts @@ -51,17 +51,33 @@ export class Server extends Service implements BudServer { await Promise.all( Object.entries(this.enabledMiddleware).map( async ([key, signifier]) => { - if (this.app.context.hot === false && key === `hot`) return + if (this.app.context.hot === false && key === `hot`) { + this.logger + .scope(this.app.label, `server`, `middleware`, key) + .log(`disabled`, `bud.context.hot is false`) + return + } /** import middleware */ const {factory} = await this.app.module .import(signifier, import.meta.url) - .catch(error => { - throw error - }) + .catch(this.catch) /** save reference to middleware instance */ this.appliedMiddleware[key] = factory(this.app) + + if (typeof this.appliedMiddleware[key] !== `function`) { + this.logger + .scope(this.app.label, `server`, `middleware`, key) + .log(`unused`) + return + } + + this.logger + .scope(this.app.label, `server`, `middleware`, key) + .log(`applied`) + .info(this.appliedMiddleware[key]) + /** apply middleware */ this.application.use(this.appliedMiddleware[key]) }, @@ -74,6 +90,18 @@ export class Server extends Service implements BudServer { }) } + /** + * {@link Contract.catch} + */ + @bind + public override catch(error: BudError | string): never { + if (typeof error === `string`) { + throw ServerError.normalize(error) + } + + throw error + } + /** * {@link BudServer.compilerBefore} */ @@ -165,12 +193,11 @@ export class Server extends Service implements BudServer { this.logger.log(`server.watcher`, this.watcher?.constructor.name) - bud.hooks.on( - `dev.client.scripts`, - await import(`@roots/bud-server/hooks`).then( - ({devClientScripts}) => devClientScripts.callback, - ), - ) + const scripts = await import(`@roots/bud-server/hooks`) + .catch(this.catch) + .then(result => result?.devClientScripts.callback) + + bud.hooks.on(`dev.client.scripts`, scripts) this.application.set(`x-powered-by`, false) } diff --git a/sources/@roots/bud-support/src/value/index.ts b/sources/@roots/bud-support/src/value/index.ts index 130c022088..69f863e7a5 100644 --- a/sources/@roots/bud-support/src/value/index.ts +++ b/sources/@roots/bud-support/src/value/index.ts @@ -54,7 +54,9 @@ class Value { /** * Check {@link Value.identity} type */ - public static isValue(value: T | Value): value is Value { + public static isValue( + value: T | Value, + ): value is Value { return ( typeof value === `object` && value !== null && diff --git a/sources/@roots/bud-wordpress-externals/src/extension.ts b/sources/@roots/bud-wordpress-externals/src/extension.ts index 1bd45ca2e0..81bf29e343 100644 --- a/sources/@roots/bud-wordpress-externals/src/extension.ts +++ b/sources/@roots/bud-wordpress-externals/src/extension.ts @@ -4,15 +4,17 @@ import { options, plugin, } from '@roots/bud-framework/extension/decorators' -import WordPressExternals from '@roots/wordpress-externals-webpack-plugin' +import WordPressExternals, { + type Options, +} from '@roots/wordpress-externals-webpack-plugin' @label(`@roots/bud-wordpress-externals`) @plugin(WordPressExternals) -@options({ +@options({ exclude: [], }) export default class BudWordPressExternals extends Extension< - null, + Options, WordPressExternals > { /** diff --git a/sources/@roots/bud-wordpress-externals/tsconfig.json b/sources/@roots/bud-wordpress-externals/tsconfig.json index 4102eab398..19408de1ec 100644 --- a/sources/@roots/bud-wordpress-externals/tsconfig.json +++ b/sources/@roots/bud-wordpress-externals/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "rootDir": "./src", "outDir": "./lib", + "strict": true, "types": ["node", "@roots/bud-framework"] }, "include": ["./src"], diff --git a/sources/@roots/bud/src/cli/commands/repl/Repl.tsx b/sources/@roots/bud/src/cli/commands/repl/Repl.tsx index a36f1b8971..41f1a1f691 100644 --- a/sources/@roots/bud/src/cli/commands/repl/Repl.tsx +++ b/sources/@roots/bud/src/cli/commands/repl/Repl.tsx @@ -1,5 +1,6 @@ import type {Bud} from '@roots/bud-framework' +import {BudError} from '@roots/bud-support/errors' import {highlight} from '@roots/bud-support/highlight' import { Box, @@ -21,8 +22,8 @@ interface ReplProps { export const Repl = ({app, depth, indent}: ReplProps) => { const [search, setSearch] = useState(``) const [result, setResult] = useState(``) - const [paged, setPaged] = useState([]) - const [page, setPage] = useState(0) + const [paged, setPaged] = useState>([]) + const [page, setPage] = useState(0) const [action, setAction] = useState(``) const pageSize = 10 @@ -75,11 +76,10 @@ export const Repl = ({app, depth, indent}: ReplProps) => { useEffect(() => { if (result) { - setPaged( - chunk(result.split(`\n`), pageSize).map(page => - page.join(`\n`), - ), + const page = chunk(result.split(`\n`), pageSize).map(page => + page.join(`\n`), ) + setPaged(page) } }, [result, pageSize]) @@ -106,7 +106,8 @@ export const Repl = ({app, depth, indent}: ReplProps) => { ) setResult(result) } catch (e) { - setResult(e.message) + const error = BudError.normalize(e) + setResult(error.message) } } @@ -117,8 +118,8 @@ export const Repl = ({app, depth, indent}: ReplProps) => { const results = await fn(app) processResults(results) await app.promise() - } catch (err) { - setResult(err.message) + } catch (error) { + setResult(BudError.normalize(error).message) } })() } diff --git a/sources/@roots/bud/src/cli/commands/repl/index.tsx b/sources/@roots/bud/src/cli/commands/repl/index.tsx index 1bb323260e..6d01180608 100644 --- a/sources/@roots/bud/src/cli/commands/repl/index.tsx +++ b/sources/@roots/bud/src/cli/commands/repl/index.tsx @@ -44,7 +44,9 @@ export default class BudReplCommand extends BudCommand { public override async execute() { await this.makeBud().catch(logger.warn) - await this.bud.compiler?.compile(this.bud).catch(error => { + if (!this.bud) return + + await this.bud?.compiler?.compile(this.bud).catch(error => { throw error }) diff --git a/sources/@roots/bud/src/context/services.ts b/sources/@roots/bud/src/context/services.ts index 2b570fb43b..85169dd7fd 100644 --- a/sources/@roots/bud/src/context/services.ts +++ b/sources/@roots/bud/src/context/services.ts @@ -1,5 +1,5 @@ export default [ - `@roots/bud/services/env`, + `@roots/bud-framework/env`, `@roots/bud-hooks`, `@roots/bud-api`, `@roots/bud-build`, @@ -8,5 +8,5 @@ export default [ `@roots/bud-dashboard`, `@roots/bud-extensions`, `@roots/bud-server`, - `@roots/bud/services/project`, + `@roots/bud-framework/project`, ] diff --git a/sources/@roots/bud/test/context.test.ts b/sources/@roots/bud/test/context.test.ts index a18a824257..88c9a60353 100644 --- a/sources/@roots/bud/test/context.test.ts +++ b/sources/@roots/bud/test/context.test.ts @@ -45,7 +45,7 @@ describe(`context.get`, () => { expect(context.mode).toBe(`production`) expect(context.services).toEqual( expect.arrayContaining([ - `@roots/bud/services/env`, + `@roots/bud-framework/env`, `@roots/bud-hooks`, `@roots/bud-api`, `@roots/bud-build`, @@ -54,7 +54,7 @@ describe(`context.get`, () => { `@roots/bud-dashboard`, `@roots/bud-extensions`, `@roots/bud-server`, - `@roots/bud/services/project`, + `@roots/bud-framework/project`, ]), ) }) diff --git a/sources/@roots/container/src/container.ts b/sources/@roots/container/src/container.ts index 6569de86eb..d6eb61c5b3 100644 --- a/sources/@roots/container/src/container.ts +++ b/sources/@roots/container/src/container.ts @@ -236,7 +236,7 @@ export default class Container { new Map(), ] - return this.getEntries(key ?? null).reduce(...reducer) + return this.getEntries(key).reduce(...reducer) } /** diff --git a/sources/@roots/critical-css-webpack-plugin/@types/critical/index.d.ts b/sources/@roots/critical-css-webpack-plugin/@types/critical/index.d.ts new file mode 100644 index 0000000000..cf1e7f063e --- /dev/null +++ b/sources/@roots/critical-css-webpack-plugin/@types/critical/index.d.ts @@ -0,0 +1,15 @@ +declare module 'critical' { + interface Stream { + (params: any): any + } + interface Generate { + (params: any, cb?: any): any + } + interface Generate { + stream: typeof stream + } + + const generate: Generate + const stream: Stream + export {generate, stream} +} diff --git a/sources/@roots/critical-css-webpack-plugin/src/index.ts b/sources/@roots/critical-css-webpack-plugin/src/index.ts index 254764744b..4c2631294e 100644 --- a/sources/@roots/critical-css-webpack-plugin/src/index.ts +++ b/sources/@roots/critical-css-webpack-plugin/src/index.ts @@ -2,9 +2,60 @@ * CriticalCssWebpackPlugin */ -import type {Options} from './interface.js' +import CriticalCssWebpackPlugin from '@roots/critical-css-webpack-plugin/plugin' -import CriticalCssWebpackPlugin from './plugin.js' +/** + * Plugin constructor options + */ +export interface Options { + /** + * Base directory + */ + base?: string + + /** + * Extract critical + */ + extract?: boolean + + /** + * Viewport height + */ + height?: number + + /** + * Html source string + */ + html?: string + + /** + * Ignore CSS rules + */ + ignore?: { + atrule: string[] + decl: (node: any, value: any) => boolean + rule: RegExp[] + } + + /** + * Node server request options + * + * @remarks + * Uses sindresorhus/got for request parsing + * + * @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md + */ + request?: any + + /** + * Html source string + */ + src?: string + + /** + * Viewport width + */ + width?: number +} export default CriticalCssWebpackPlugin -export type {Options} diff --git a/sources/@roots/critical-css-webpack-plugin/src/interface.ts b/sources/@roots/critical-css-webpack-plugin/src/interface.ts deleted file mode 100644 index 22c296bf14..0000000000 --- a/sources/@roots/critical-css-webpack-plugin/src/interface.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Plugin constructor options - */ -export interface Options { - /** - * Base directory - */ - base?: string - - /** - * Extract critical - */ - extract?: boolean - - /** - * Viewport height - */ - height?: number - - /** - * Html source string - */ - html?: string - - /** - * Ignore CSS rules - */ - ignore?: { - atrule: string[] - decl: (node: any, value: any) => boolean - rule: RegExp[] - } - - /** - * Node server request options - * - * @remarks - * Uses sindresorhus/got for request parsing - * - * @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md - */ - request?: any - - /** - * Html source string - */ - src?: string - - /** - * Viewport width - */ - width?: number -} diff --git a/sources/@roots/critical-css-webpack-plugin/src/plugin.ts b/sources/@roots/critical-css-webpack-plugin/src/plugin.ts index cea5c6c33e..00613cbd60 100644 --- a/sources/@roots/critical-css-webpack-plugin/src/plugin.ts +++ b/sources/@roots/critical-css-webpack-plugin/src/plugin.ts @@ -1,4 +1,6 @@ /* eslint-disable no-console */ +import type {Options} from '@roots/critical-css-webpack-plugin' + import {join} from 'node:path' import * as critical from 'critical' @@ -6,10 +8,6 @@ import {bind} from 'helpful-decorators' import vinyl from 'vinyl' import Webpack from 'webpack' -import type {Options} from './interface.js' - -export type {Options} - /** * CriticalCSSWebpackPlugin */ @@ -36,17 +34,6 @@ export default class CriticalCssWebpackPlugin { stage: Webpack.Compilation.PROCESS_ASSETS_STAGE_DERIVED, } - /** - * Webpack lifecycle events - */ - public webpack: { - compilation: Webpack.Compilation - compiler: Webpack.Compiler - } = { - compilation: null, - compiler: null, - } - /** * Class constructor * @@ -66,76 +53,66 @@ export default class CriticalCssWebpackPlugin { */ @bind public async apply(compiler: Webpack.Compiler): Promise { - this.webpack.compiler = compiler - - this.webpack.compiler.hooks.thisCompilation.tap( - this.plugin, - this.compilation, - ) - } - - /** - * Compilation hook - */ - @bind - public compilation(compilation: Webpack.Compilation): void { - this.webpack.compilation = compilation - - this.webpack.compilation.hooks.processAssets.tapPromise( - this.plugin, - this.processAssets, - ) + compiler.hooks.thisCompilation.tap(this.plugin, compilation => { + compilation.hooks.processAssets.tapPromise( + this.plugin, + this.makeProcessAssetsHook(compiler, compilation), + ) + }) } /** * Process assets */ @bind - public async processAssets(assets: Webpack.Compilation['assets']) { - const base = - this.options.base ?? this.webpack.compilation.outputOptions.path + public makeProcessAssetsHook( + compiler: Webpack.Compiler, + compilation: Webpack.Compilation, + ) { + return async (assets: Webpack.Compilation['assets']) => { + const base = this.options.base ?? compilation.outputOptions.path + + await Promise.all( + Object.keys(assets).map(async ident => { + if (!ident.endsWith(`.css`)) return - await Promise.all( - Object.keys(assets).map(async ident => { - if (!ident.endsWith(`.css`)) return + const asset = compilation.getAsset(ident) + if (!asset) return - const asset = this.webpack.compilation.getAsset(ident) + compilation.buildDependencies.add(asset.name) - const vfile = new vinyl({ - base, - contents: asset.source.buffer(), - path: asset.name, - }) + const vfile = new vinyl({ + base, + contents: asset.source.buffer(), + path: asset.name, + }) - try { - await critical + const result = await critical .generate({ ...this.options, base, css: [vfile], }) - .then(({css, uncritical}) => { - if (this.options.extract) { - this.webpack.compilation.updateAsset( - asset.name, - new Webpack.sources.RawSource(uncritical), - ) - } - - this.webpack.compilation.emitAsset( - join( - `critical`, - asset.name.split(`.`).shift().concat(`.css`), - ), - new Webpack.sources.RawSource(css), - asset.info, - ) + .catch((error: Error) => { + throw error }) - } catch (error) { - console.error(error) - throw error - } - }), - ) + + if (!result?.css) return // nothing to do here + + if (this.options.extract && result.uncritical) { + compilation.updateAsset( + asset.name, + new Webpack.sources.RawSource(result.uncritical), + ) + } + + compilation.emitAsset( + join(`critical`, asset.name), + new Webpack.sources.RawSource(result.css), + asset.info, + ) + }), + ) + } } } diff --git a/sources/@roots/critical-css-webpack-plugin/src/plugin.test.ts b/sources/@roots/critical-css-webpack-plugin/test/plugin.test.ts similarity index 89% rename from sources/@roots/critical-css-webpack-plugin/src/plugin.test.ts rename to sources/@roots/critical-css-webpack-plugin/test/plugin.test.ts index 0dff4c3c88..68e33548f1 100644 --- a/sources/@roots/critical-css-webpack-plugin/src/plugin.test.ts +++ b/sources/@roots/critical-css-webpack-plugin/test/plugin.test.ts @@ -1,7 +1,6 @@ +import Plugin from '@roots/critical-css-webpack-plugin' import {describe, expect, it} from 'vitest' -import Plugin from './index.js' - describe(`@roots/critical-css-webpack-plugin`, () => { it(`should be constructable`, () => { expect(Plugin).toBeInstanceOf(Function) diff --git a/sources/@roots/critical-css-webpack-plugin/tsconfig.json b/sources/@roots/critical-css-webpack-plugin/tsconfig.json index 4aab95cd11..0262231402 100644 --- a/sources/@roots/critical-css-webpack-plugin/tsconfig.json +++ b/sources/@roots/critical-css-webpack-plugin/tsconfig.json @@ -4,6 +4,7 @@ "rootDir": "src", "outDir": "lib", }, + "files": ["./@types/critical/index.d.ts"], "include": ["src"], "exclude": ["lib", "node_modules"] } diff --git a/sources/@roots/dependencies/src/command/base.command.ts b/sources/@roots/dependencies/src/command/base.command.ts index 48e1579efc..997e1df962 100644 --- a/sources/@roots/dependencies/src/command/base.command.ts +++ b/sources/@roots/dependencies/src/command/base.command.ts @@ -13,7 +13,7 @@ export abstract class Command { public async execute(commandArgs: Array): Promise { const [bin, ...args] = commandArgs return new Promise((resolve, reject) => { - const message = [] + const message: Array = [] const command = spawn(bin, args) @@ -36,7 +36,7 @@ export abstract class Command { dependencies: Array<[string, string] | string>, ): Array { return dependencies - .reduce((acc, dependency) => { + .reduce((acc: Array, dependency) => { if (Array.isArray(dependency)) { acc.push(`${dependency[0]}@${dependency[1]}`) } else { diff --git a/sources/@roots/dependencies/src/command/npm.command.ts b/sources/@roots/dependencies/src/command/npm.command.ts index 10636d4240..ef9db195d1 100644 --- a/sources/@roots/dependencies/src/command/npm.command.ts +++ b/sources/@roots/dependencies/src/command/npm.command.ts @@ -19,7 +19,10 @@ export class Npm extends Command implements IDependencyManager { signifier, `version`, ]) + if (result?.shift) return result.shift().trim() + // #todo: this is bad + return `latest` // fallback } /** diff --git a/sources/@roots/dependencies/src/command/yarn.command.ts b/sources/@roots/dependencies/src/command/yarn.command.ts index d5ad9a6b1e..ac88bbf1a2 100644 --- a/sources/@roots/dependencies/src/command/yarn.command.ts +++ b/sources/@roots/dependencies/src/command/yarn.command.ts @@ -21,6 +21,9 @@ export class Yarn extends Command implements IDependencyManager { ]) if (result?.shift) return result.shift().trim() + + // #todo: this is bad + return `latest` // fallback } /** diff --git a/sources/@roots/entrypoints-webpack-plugin/src/index.ts b/sources/@roots/entrypoints-webpack-plugin/src/index.ts index b514cc7a64..b1161fb137 100644 --- a/sources/@roots/entrypoints-webpack-plugin/src/index.ts +++ b/sources/@roots/entrypoints-webpack-plugin/src/index.ts @@ -6,9 +6,10 @@ */ import type {SyncHook, SyncWaterfallHook} from 'tapable' +import type {Compilation} from 'webpack' export interface CompilationHooks { - compilation: SyncHook> + compilation: SyncHook entrypoints: SyncWaterfallHook } diff --git a/sources/@roots/filesystem/src/filesystem.ts b/sources/@roots/filesystem/src/filesystem.ts index 85365c2a05..ce87fce5d4 100644 --- a/sources/@roots/filesystem/src/filesystem.ts +++ b/sources/@roots/filesystem/src/filesystem.ts @@ -14,7 +14,6 @@ import type { WritableData, WriteOptions, } from 'fs-jetpack/types.js' -import type {CreateWriteStreamOptions} from 'node:fs/promises' import type {PathLike, ReadStream, WriteStream} from 'node:fs' import {join} from 'node:path' @@ -94,10 +93,7 @@ export default class Filesystem { * Create a {@link WriteStream} */ @bind - public createWriteStream( - path: PathLike, - options?: BufferEncoding | CreateWriteStreamOptions, - ): WriteStream { + public createWriteStream(path: PathLike, options?: any): WriteStream { return this.fs.createWriteStream(path, options) } @@ -112,7 +108,7 @@ export default class Filesystem { path: string, criteria?: DirCriteria, ): Promise { - this.fs.dirAsync(path, criteria) + await this.fs.dirAsync(path, criteria) return this } @@ -144,7 +140,7 @@ export default class Filesystem { options?: Array | FindOptions | string, ): Promise { if (typeof options === `string` || Array.isArray(options)) { - return this.fs.findAsync({matching: options}) + return await this.fs.findAsync({matching: options}) } return await this.fs.findAsync(options) @@ -200,7 +196,6 @@ export default class Filesystem { options?: MoveOptions, ): Promise { await this.fs.moveAsync(from, to, options) // returns void - return this } @@ -244,7 +239,6 @@ export default class Filesystem { @bind public async remove(path?: string): Promise { await this.fs.removeAsync(path) // returns void - return this } diff --git a/sources/@roots/filesystem/src/json.ts b/sources/@roots/filesystem/src/json.ts index 526162b46c..afc0ea093c 100644 --- a/sources/@roots/filesystem/src/json.ts +++ b/sources/@roots/filesystem/src/json.ts @@ -8,12 +8,13 @@ export interface WriteOptions { } export const read = async (path: string): Promise => { - let source: string + const source: string | undefined = await fs + .readAsync(path, `utf8`) + .catch((error: Error) => { + error.name = `json read error: ${path}` + throw error + }) - source = await fs.readAsync(path, `utf8`).catch((error: Error) => { - error.name = `json read error: ${path}` - throw error - }) if (!source) { const error = new Error(`File read returned no value to parse`) error.name = `json read error: ${path}` diff --git a/sources/@roots/filesystem/src/s3/index.ts b/sources/@roots/filesystem/src/s3/index.ts index cad17c540a..e80d4e0849 100644 --- a/sources/@roots/filesystem/src/s3/index.ts +++ b/sources/@roots/filesystem/src/s3/index.ts @@ -1,5 +1,4 @@ import type { - GetObjectOutput, ListObjectsCommandInput, PutObjectCommandInput, S3Client, @@ -158,13 +157,10 @@ export class S3 { * @throws Error - If the file does not exist */ @bind - public async read( - key: string, - raw = false, - ): Promise { + public async read(key: string, raw = false): Promise { const streamToString = ({Body: stream}: {Body: Readable}) => new Promise((resolve, reject) => { - const chunks = [] + const chunks: Array = [] stream .on(`data`, chunk => chunks.push(chunk)) @@ -180,12 +176,8 @@ export class S3 { }) try { - // @ts-ignore - const request = (await client.send(GetObjectCommandOutput)) as { - Body: Readable - } - - return raw ? request : streamToString(request) + const request: any = await client.send(GetObjectCommandOutput) + if (!request) return raw ? request : streamToString(request) } catch (error) { throw error } @@ -208,7 +200,7 @@ export class S3 { Bucket: this.config.bucket, ContentType: null, Key: null, - } + } as any if (typeof params[0] !== `string`) Object.assign(putProps, params[0]) else Object.assign(putProps, {Body: params[1], Key: params[0]}) diff --git a/sources/@roots/filesystem/src/yml.ts b/sources/@roots/filesystem/src/yml.ts index 64ad0c293e..7147815714 100644 --- a/sources/@roots/filesystem/src/yml.ts +++ b/sources/@roots/filesystem/src/yml.ts @@ -3,6 +3,9 @@ import yaml from 'js-yaml' export const read = async (file: string): Promise => { const source = await fs.readAsync(file, `utf8`) + if (!source) + throw new Error(`File read of ${file} returned no value to parse`) + return yaml.load(source) } diff --git a/sources/@roots/wordpress-dependencies-webpack-plugin/src/plugin.ts b/sources/@roots/wordpress-dependencies-webpack-plugin/src/plugin.ts index 99566318d1..d0da0d5baf 100644 --- a/sources/@roots/wordpress-dependencies-webpack-plugin/src/plugin.ts +++ b/sources/@roots/wordpress-dependencies-webpack-plugin/src/plugin.ts @@ -17,6 +17,8 @@ export interface Options { export default class WordPressDependenciesWebpackPlugin { public dependencies: Map> + public outputPath: string + public plugin = { name: `WordPressDependenciesWebpackPlugin`, stage: Infinity, @@ -24,9 +26,8 @@ export default class WordPressDependenciesWebpackPlugin { public requested: Map> - public constructor(public options?: Options) { - if (!this.options.outputPath) - this.options.outputPath = `wordpress.json` + public constructor(public options: Options = {emitWordPressJson: true}) { + this.outputPath = this.options.outputPath ?? `wordpress.json` if ( typeof this.options.emitWordPressJson === `undefined` && @@ -49,12 +50,13 @@ export default class WordPressDependenciesWebpackPlugin { key: string, item: string, ) { - if (!obj.has(key)) { + const items = obj.get(key) + if (!items) { obj.set(key, new Set([item])) return } - obj.set(key, obj.get(key).add(item)) + obj.set(key, items.add(item)) } /** @@ -72,6 +74,8 @@ export default class WordPressDependenciesWebpackPlugin { }) compiler.hooks.thisCompilation.tap(this.plugin, compilation => { + if (!this.options) return + if (this.options.entrypointsPlugin) { const hooks = this.options.entrypointsPlugin.getCompilationHooks(compilation) @@ -86,14 +90,14 @@ export default class WordPressDependenciesWebpackPlugin { ) } - if (this.options.emitWordPressJson) { + if (this.options?.emitWordPressJson) { compilation.hooks.processAssets.tapPromise( this.plugin, async () => { this.extractDependenciesFromCompilation(compilation) compilation.emitAsset( - this.options.outputPath, + this.outputPath, new compiler.webpack.sources.RawSource( JSON.stringify(this.dependencies, null, 2), ), @@ -107,6 +111,8 @@ export default class WordPressDependenciesWebpackPlugin { @bind public extractDependenciesFromCompilation(compilation: Compilation) { for (const {chunks, name} of compilation.entrypoints.values()) { + if (!name) continue + for (const chunk of chunks) { const records: any = compilation.chunkGraph.getChunkModules(chunk) @@ -130,12 +136,17 @@ export default class WordPressDependenciesWebpackPlugin { */ @bind public processChunkRequest(name: string, userRequest: string) { - if (!name || !userRequest || !this.requested.has(userRequest)) return + if (!name || !userRequest) return - for (const request of this.requested.get(userRequest)) { + const requested = this.requested.get(userRequest) + if (!requested) return + + for (const request of requested) { if (!wordpress.isProvided(request)) continue - this.addItemToMap(this.dependencies, name, handle.transform(request)) + const wordPressHandle = handle.transform(request) + if (!wordPressHandle) continue + this.addItemToMap(this.dependencies, name, wordPressHandle) } } @@ -144,13 +155,24 @@ export default class WordPressDependenciesWebpackPlugin { */ @bind public tapEntrypointsManifestObject(entrypoints: Entrypoints) { - for (const [ident, entrypoint] of entrypoints.entries()) - if (this.dependencies.has(ident)) - for (const dependency of this.dependencies.get(ident)) - entrypoint.has(`dependencies`) - ? entrypoint.get(`dependencies`).add(dependency) - : entrypoint.set(`dependencies`, new Set([dependency])) - else entrypoint.set(`dependencies`, new Set()) + for (const [ident, entrypoint] of entrypoints.entries()) { + const dependencies = this.dependencies.get(ident) + + if (!dependencies) { + entrypoint.set(`dependencies`, new Set()) + continue + } + + for (const dependency of dependencies) { + const entrypointDependencies = entrypoint.get(`dependencies`) + if (!entrypointDependencies) { + entrypoint.set(`dependencies`, new Set([dependency])) + continue + } + + entrypointDependencies.add(dependency) + } + } return entrypoints } diff --git a/sources/@roots/wordpress-externals-webpack-plugin/src/plugin.ts b/sources/@roots/wordpress-externals-webpack-plugin/src/plugin.ts index 7ce46e06a4..75c6526521 100644 --- a/sources/@roots/wordpress-externals-webpack-plugin/src/plugin.ts +++ b/sources/@roots/wordpress-externals-webpack-plugin/src/plugin.ts @@ -19,6 +19,9 @@ export class WordPressExternalsWebpackPlugin */ public apply(compiler: Webpack.Compiler) { new Webpack.ExternalsPlugin(`window`, ({request}, callback) => { + // evidently this can be undefined? OK. + if (!request) return callback() + // bail on excluded signifiers if (this.options?.exclude?.includes(request)) return callback() diff --git a/sources/@roots/wordpress-hmr/src/editor.ts b/sources/@roots/wordpress-hmr/src/editor.ts index 86b6d31d17..dfd20ef0cc 100644 --- a/sources/@roots/wordpress-hmr/src/editor.ts +++ b/sources/@roots/wordpress-hmr/src/editor.ts @@ -1,12 +1,16 @@ import {dispatch} from '@wordpress/data' -import type {AcceptCallback, ContextFactory} from './index.js' +import type { + AcceptCallback, + AfterCallback, + ContextFactory, +} from './index.js' import {Cache} from './cache.js' export interface Props { accept: AcceptCallback - after?: (changed?: Array<{name: string}>) => unknown + after?: AfterCallback api: { register: (...args: Array) => void unregister: (...args: Array) => void @@ -21,19 +25,13 @@ export const setNotify = (value: boolean) => { notify = value } -export const load = ({ - accept, - after = () => null, - api, - before = () => null, - getContext, -}: Props) => { +export const load = ({accept, after, api, before, getContext}: Props) => { const cache = new Cache() const handler = () => { - const changed = [] + const changed: Array<{name: string}> = [] - before() + before && before() const context = getContext() @@ -49,7 +47,7 @@ export const load = ({ cache.set(key, source) }) - after(changed) + after && after(changed) if (notify && import.meta.webpackHot) { if (!initial) { diff --git a/sources/@roots/wordpress-hmr/src/index.ts b/sources/@roots/wordpress-hmr/src/index.ts index 5035ae693a..9f70afd7e8 100644 --- a/sources/@roots/wordpress-hmr/src/index.ts +++ b/sources/@roots/wordpress-hmr/src/index.ts @@ -8,6 +8,10 @@ export * as plugins from './plugins.js' export * as styles from './styles.js' export * as variations from './variations.js' +export interface AfterCallback { + (changed?: Array<{name: string}>): unknown +} + export interface ContextFactory { (): __WebpackModuleApi.RequireContext } diff --git a/sources/@roots/wordpress-hmr/src/utility.ts b/sources/@roots/wordpress-hmr/src/utility.ts index 1326d02265..7b70e2e16a 100644 --- a/sources/@roots/wordpress-hmr/src/utility.ts +++ b/sources/@roots/wordpress-hmr/src/utility.ts @@ -10,14 +10,16 @@ export const enforceNamespace = (id: string, name: string) => id.startsWith(name) ? id : `${name}/${id}` export const filterCallback = ( - filters: KeyedFilters, - namespace: string, - handle: (filter: RegisterProps, register: RegistrationModule) => void, -) => - filters && + filters?: KeyedFilters, + namespace?: string, + handle?: (filter: RegisterProps, register: RegistrationModule) => void, +) => { + if (!filters || !namespace || !handle) return + Object.entries(filters).map(([hook, records]) => Object.entries(records).map(([name, callback]) => { name = enforceNamespace(name, namespace) handle({callback, hook, name}, api) }), ) +} diff --git a/sources/@roots/wordpress-theme-json-webpack-plugin/scripts/build-types.js b/sources/@roots/wordpress-theme-json-webpack-plugin/scripts/build-types.js index 3b2b9aed60..1a86c49524 100644 --- a/sources/@roots/wordpress-theme-json-webpack-plugin/scripts/build-types.js +++ b/sources/@roots/wordpress-theme-json-webpack-plugin/scripts/build-types.js @@ -52,6 +52,8 @@ try { * To update typings run: * \`yarn workspace @roots/wordpress-theme-json-webpack-plugin exec node ./scripts/build-types.js\` */ + +// @ts-nocheck `, }) } catch (err) { diff --git a/sources/@roots/wordpress-theme-json-webpack-plugin/src/theme.ts b/sources/@roots/wordpress-theme-json-webpack-plugin/src/theme.ts index caae8b5aac..101ebb9d71 100644 --- a/sources/@roots/wordpress-theme-json-webpack-plugin/src/theme.ts +++ b/sources/@roots/wordpress-theme-json-webpack-plugin/src/theme.ts @@ -6,6 +6,8 @@ * `yarn workspace @roots/wordpress-theme-json-webpack-plugin exec node ./scripts/build-types.js` */ +// @ts-nocheck + export type SettingsProperties = SettingsPropertiesAppearanceTools & SettingsPropertiesBorder & SettingsPropertiesColor & diff --git a/sources/@roots/wordpress-transforms/src/wordpress.ts b/sources/@roots/wordpress-transforms/src/wordpress.ts index 5f0bf802d1..0e6827dd82 100644 --- a/sources/@roots/wordpress-transforms/src/wordpress.ts +++ b/sources/@roots/wordpress-transforms/src/wordpress.ts @@ -26,11 +26,18 @@ export const isLibrary = (request: string): boolean => export const isOmitted = (request: string): boolean => omitted.includes(normalize(request)) -export const getGlobal = (request: string): string => - requestMap.get(normalize(request))[0] +export const getGlobal = (request: string): string => { + const result = requestMap.get(normalize(request)) + if (!result?.[0]) throw new Error(`No global found for ${request}`) + return result[0] +} -export const getScriptDependencyHandle = (request: string): string => - requestMap.get(normalize(request))[1] +export const getScriptDependencyHandle = (request: string): string => { + const result = requestMap.get(normalize(request)) + if (!result?.[1]) + throw new Error(`No script dependency handle found for ${request}`) + return result[1] +} export const isProvided = (request: string): boolean => { if (isOmitted(request)) return false diff --git a/sources/create-bud-app/src/commands/create.ts b/sources/create-bud-app/src/commands/create.ts index 374b71edb6..1874be6fa8 100644 --- a/sources/create-bud-app/src/commands/create.ts +++ b/sources/create-bud-app/src/commands/create.ts @@ -192,9 +192,9 @@ export default class CreateCommand extends Command { public support = supportFlag - public username = usernameFlag + public username? = usernameFlag - public version = versionFlag + public version? = versionFlag public wordpress = wordpressPresetFlag diff --git a/sources/create-bud-app/src/flags/name.ts b/sources/create-bud-app/src/flags/name.ts index 8caad874f3..d89c842aa7 100644 --- a/sources/create-bud-app/src/flags/name.ts +++ b/sources/create-bud-app/src/flags/name.ts @@ -1,5 +1,5 @@ import {Option} from 'clipanion' -export default Option.String(`--name,-n`, undefined, { +export default Option.String(`--name,-n`, { description: `Project name`, }) diff --git a/sources/create-bud-app/src/flags/overwrite.ts b/sources/create-bud-app/src/flags/overwrite.ts index 088bd7a6c9..67ec56cad0 100644 --- a/sources/create-bud-app/src/flags/overwrite.ts +++ b/sources/create-bud-app/src/flags/overwrite.ts @@ -1,5 +1,5 @@ import {Option} from 'clipanion' -export default Option.Boolean(`--overwrite,-o`, undefined, { +export default Option.Boolean(`--overwrite,-o`, { description: `Overwrite existing files`, }) diff --git a/sources/create-bud-app/src/flags/username.ts b/sources/create-bud-app/src/flags/username.ts index 1e267e0fea..cbfdd2392d 100644 --- a/sources/create-bud-app/src/flags/username.ts +++ b/sources/create-bud-app/src/flags/username.ts @@ -1,5 +1,5 @@ import {Option} from 'clipanion' -export default Option.String(`--username,-u`, undefined, { +export default Option.String(`--username,-u`, { description: `Github username`, }) diff --git a/sources/create-bud-app/src/flags/version.ts b/sources/create-bud-app/src/flags/version.ts index aef3ad65b3..84edcbb770 100644 --- a/sources/create-bud-app/src/flags/version.ts +++ b/sources/create-bud-app/src/flags/version.ts @@ -1,5 +1,5 @@ import {Option} from 'clipanion' -export default Option.String(`--version,-v`, undefined, { +export default Option.String(`--version,-v`, { description: `Target bud.js version`, }) diff --git a/sources/create-bud-app/src/prompts/support.components.ts b/sources/create-bud-app/src/prompts/support.components.ts index 8357cd83c2..db43fabcac 100644 --- a/sources/create-bud-app/src/prompts/support.components.ts +++ b/sources/create-bud-app/src/prompts/support.components.ts @@ -20,10 +20,9 @@ export default (command: CreateCommand) => result( this: typeof MultiSelect, answer: Array>, - ) { - return Object.keys(this.map(answer)).reduce( - (all, support) => [...all, support], - [], - ) + ): Array { + const records = this.map(answer) as Record + const answers = Object.keys(records) + return answers }, }) diff --git a/sources/create-bud-app/src/prompts/support.css.ts b/sources/create-bud-app/src/prompts/support.css.ts index 1e3c415de7..64d5237535 100644 --- a/sources/create-bud-app/src/prompts/support.css.ts +++ b/sources/create-bud-app/src/prompts/support.css.ts @@ -19,9 +19,6 @@ export default (command: CreateCommand) => this: typeof MultiSelect, answer: Array>, ) { - return Object.keys(this.map(answer)).reduce( - (all, support) => [...all, support], - [], - ) + return [...Object.keys(this.map(answer))] }, }) diff --git a/sources/create-bud-app/src/prompts/support.env.ts b/sources/create-bud-app/src/prompts/support.env.ts index 4808eba5ac..222aa9e437 100644 --- a/sources/create-bud-app/src/prompts/support.env.ts +++ b/sources/create-bud-app/src/prompts/support.env.ts @@ -18,9 +18,6 @@ export default (command: CreateCommand) => this: typeof MultiSelect, answer: Array>, ) { - return Object.keys(this.map(answer)).reduce( - (all, support) => [...all, support], - [], - ) + return [...Object.keys(this.map(answer))] }, }) diff --git a/sources/create-bud-app/src/prompts/support.js.ts b/sources/create-bud-app/src/prompts/support.js.ts index 62b7df4b21..841b1f1f14 100644 --- a/sources/create-bud-app/src/prompts/support.js.ts +++ b/sources/create-bud-app/src/prompts/support.js.ts @@ -20,9 +20,6 @@ export default (command: CreateCommand) => this: typeof MultiSelect, answer: Array>, ) { - return Object.keys(this.map(answer)).reduce( - (all, support) => [...all, support], - [], - ) + return [...Object.keys(this.map(answer))] }, }) diff --git a/sources/create-bud-app/src/prompts/support.qa.ts b/sources/create-bud-app/src/prompts/support.qa.ts index 9b41b15d83..9075175fd2 100644 --- a/sources/create-bud-app/src/prompts/support.qa.ts +++ b/sources/create-bud-app/src/prompts/support.qa.ts @@ -20,9 +20,6 @@ export default (command: CreateCommand) => this: typeof MultiSelect, answer: Array>, ) { - return Object.keys(this.map(answer)).reduce( - (all, support) => [...all, support], - [], - ) + return [...Object.keys(this.map(answer))] }, }) diff --git a/sources/create-bud-app/src/tasks/install.dev.ts b/sources/create-bud-app/src/tasks/install.dev.ts index 51e70f8dc5..5d2d008209 100644 --- a/sources/create-bud-app/src/tasks/install.dev.ts +++ b/sources/create-bud-app/src/tasks/install.dev.ts @@ -34,7 +34,10 @@ function createSignifierFormatter( command: CreateCommand, ): (key: string) => string { return (key: string) => { - if (key.startsWith(`@roots`) && !key.split(`/`).pop().includes(`@`)) { + if ( + key?.startsWith(`@roots`) && + !key.split(`/`).pop()?.includes(`@`) + ) { return `${key}@${command.version}` } if (command.extensions[key]) { diff --git a/sources/create-bud-app/src/utilities/getGitUser.ts b/sources/create-bud-app/src/utilities/getGitUser.ts index e768025bf8..760d328f17 100644 --- a/sources/create-bud-app/src/utilities/getGitUser.ts +++ b/sources/create-bud-app/src/utilities/getGitUser.ts @@ -8,8 +8,8 @@ export default async function getGitUser(command: CreateCommand) { const stdio = result.stdout.concat(result.stderr) if (stdio) { - const [, match] = stdio.split(`\n`)[1].match(/as (.*) /) - return match + const result = stdio.split(`\n`)[1].match(/as (.*) /) + return result?.[1] } } catch (err) { // fallthrough From 253f5aff014b3a5a8cdb3a58cb35f26c15b9181e Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Mon, 31 Jul 2023 01:41:19 -0400 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20prettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@roots/bud-dashboard/src/views/server.tsx | 55 ++++++++++--------- .../@roots/bud-framework/src/methods/close.ts | 31 ++++++----- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/sources/@roots/bud-dashboard/src/views/server.tsx b/sources/@roots/bud-dashboard/src/views/server.tsx index df64353d29..dd8738707f 100644 --- a/sources/@roots/bud-dashboard/src/views/server.tsx +++ b/sources/@roots/bud-dashboard/src/views/server.tsx @@ -1,15 +1,15 @@ -import figures from "@roots/bud-support/figures"; -import { Box, Text } from "@roots/bud-support/ink"; -import { externalNetworkInterface } from "@roots/bud-support/os"; +import figures from '@roots/bud-support/figures' +import {Box, Text} from '@roots/bud-support/ink' +import {externalNetworkInterface} from '@roots/bud-support/os' interface Props { - devUrl?: URL; - displayServerInfo?: boolean; - mode?: `development` | `production`; - proxy?: unknown; - proxyUrl?: URL; - publicDevUrl?: URL; - publicProxyUrl?: URL; + devUrl?: URL + displayServerInfo?: boolean + mode?: `development` | `production` + proxy?: unknown + proxyUrl?: URL + publicDevUrl?: URL + publicProxyUrl?: URL } /** @@ -30,10 +30,10 @@ export const Server = ({ !devUrl || !(devUrl instanceof URL) ) - return null; + return null - const ipv4 = externalNetworkInterface.ipv4Url(devUrl.protocol); - ipv4.port = devUrl.port; + const ipv4 = externalNetworkInterface.ipv4Url(devUrl.protocol) + ipv4.port = devUrl.port return ( @@ -72,19 +72,19 @@ export const Server = ({ )} - ); -}; + ) +} const Proxy = ({ proxy, proxyUrl, publicProxyUrl, }: { - proxy?: Props[`proxy`]; - proxyUrl?: Props[`proxyUrl`]; - publicProxyUrl?: Props[`publicProxyUrl`]; + proxy?: Props[`proxy`] + proxyUrl?: Props[`proxyUrl`] + publicProxyUrl?: Props[`publicProxyUrl`] }) => { - if (!proxy || !proxyUrl || !(proxyUrl instanceof URL)) return null; + if (!proxy || !proxyUrl || !(proxyUrl instanceof URL)) return null return ( @@ -97,14 +97,15 @@ const Proxy = ({ {figures.lineDashed0} {proxyUrl.href} - {publicProxyUrl?.href && publicProxyUrl?.href !== proxyUrl.href && ( - - {figures.lineDashed0} {publicProxyUrl.href} - - )} + {publicProxyUrl?.href && + publicProxyUrl?.href !== proxyUrl.href && ( + + {figures.lineDashed0} {publicProxyUrl.href} + + )} - ); -}; + ) +} -export { Server as default }; +export {Server as default} diff --git a/sources/@roots/bud-framework/src/methods/close.ts b/sources/@roots/bud-framework/src/methods/close.ts index 0c6665c0a0..baf94c7c1e 100644 --- a/sources/@roots/bud-framework/src/methods/close.ts +++ b/sources/@roots/bud-framework/src/methods/close.ts @@ -1,6 +1,6 @@ -import type { Bud } from "@roots/bud-framework"; +import type {Bud} from '@roots/bud-framework' -import isFunction from "@roots/bud-support/lodash/isFunction"; +import isFunction from '@roots/bud-support/lodash/isFunction' /** * Close interface @@ -9,7 +9,7 @@ import isFunction from "@roots/bud-support/lodash/isFunction"; * @param done - Callback function to be called before end of run */ export interface close { - (done?: () => unknown): Promise; + (done?: () => unknown): Promise } /** @@ -24,33 +24,36 @@ export function close(onComplete?: () => unknown) { try { if (this.compiler?.instance?.running) { this.compiler.instance.close(() => { - closeDevelopmentServer(this); - }); + closeDevelopmentServer(this) + }) } else { - closeDevelopmentServer(this); + closeDevelopmentServer(this) } } catch (error) { - throw error; + throw error } - if (onComplete) return onComplete(); + if (onComplete) return onComplete() } const closeDevelopmentServer = (bud: Bud) => { - if (!bud.server) return; + if (!bud.server) return try { - if (bud.isDevelopment && isFunction(bud.server.watcher?.instance?.close)) { - bud.server.watcher.instance.close(); + if ( + bud.isDevelopment && + isFunction(bud.server.watcher?.instance?.close) + ) { + bud.server.watcher.instance.close() } if ( bud.isDevelopment && isFunction(bud.server.connection?.instance?.close) ) { - bud.server.connection.instance.close(); + bud.server.connection.instance.close() } } catch (error) { - throw error; + throw error } -}; +} From 55893b65482a9719014d48e9dd81d577b5a72cfb Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Mon, 31 Jul 2023 01:44:01 -0400 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=A6=20yarn.lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sources/@roots/bud-criticalcss/package.json | 1 - yarn.lock | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sources/@roots/bud-criticalcss/package.json b/sources/@roots/bud-criticalcss/package.json index 1ffd7c3b44..df546f613e 100644 --- a/sources/@roots/bud-criticalcss/package.json +++ b/sources/@roots/bud-criticalcss/package.json @@ -48,7 +48,6 @@ ".": "./lib/index.js", "./bud.extractCss": "./lib/bud.extractCss.js", "./extension": "./lib/extension.js" - }, "typesVersions": { "*": { diff --git a/yarn.lock b/yarn.lock index b84b06c8d0..37d013b33b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7941,6 +7941,8 @@ __metadata: "@types/react": 18.2.15 ink-testing-library: 3.0.0 tslib: 2.6.0 + vitest: 0.33.0 + webpack: 5.88.2 languageName: unknown linkType: soft From 89c8516fb98770a513f012a961a337a43a5dec27 Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Mon, 31 Jul 2023 11:52:38 -0400 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=A7=B9=20chore:=20eslint=20--fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sources/@roots/bud-dashboard/src/views/server.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/@roots/bud-dashboard/src/views/server.tsx b/sources/@roots/bud-dashboard/src/views/server.tsx index dd8738707f..0035814f73 100644 --- a/sources/@roots/bud-dashboard/src/views/server.tsx +++ b/sources/@roots/bud-dashboard/src/views/server.tsx @@ -41,9 +41,9 @@ export const Server = ({ {` `} {devUrl?.href && (