From e7bf5d4b5492476aaa9c3b32c7d21c327f0af52e Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Sat, 2 Sep 2023 17:17:56 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20improve(patch):=20improve=20yar?= =?UTF-8?q?n=20berry=20and=20pnpm=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@roots/bud-support/src/which-pm/index.ts | 11 +-- .../bud/src/cli/commands/upgrade/index.tsx | 77 +++++++------------ sources/create-bud-app/src/commands/create.ts | 65 +++++++++++----- .../src/flags/package-manager.ts | 7 +- .../src/prompts/packageManager.ts | 3 + sources/create-bud-app/src/tasks/build.ts | 8 ++ .../create-bud-app/src/tasks/install.dev.ts | 8 ++ .../create-bud-app/src/tasks/install.prod.ts | 8 ++ .../src/tasks/write.bud.config.ts | 1 + .../create-bud-app/src/tasks/write.readme.ts | 5 +- .../src/tasks/write.tsconfig.ts | 3 +- .../src/tasks/write.yarn.lock.ts | 19 +++++ .../create-bud-app/src/tasks/write.yarnrc.ts | 34 ++++++++ .../create-bud-app/src/tasks/yarn-version.ts | 20 +++++ .../create-bud-app/templates/.yarnrc.yml.hbs | 1 + .../create-bud-app/templates/README.md.hbs | 22 +++++- 16 files changed, 213 insertions(+), 79 deletions(-) create mode 100644 sources/create-bud-app/src/tasks/write.yarn.lock.ts create mode 100644 sources/create-bud-app/src/tasks/write.yarnrc.ts create mode 100644 sources/create-bud-app/src/tasks/yarn-version.ts create mode 100644 sources/create-bud-app/templates/.yarnrc.yml.hbs diff --git a/sources/@roots/bud-support/src/which-pm/index.ts b/sources/@roots/bud-support/src/which-pm/index.ts index 1a9e42e352..e6d1a50966 100644 --- a/sources/@roots/bud-support/src/which-pm/index.ts +++ b/sources/@roots/bud-support/src/which-pm/index.ts @@ -9,8 +9,8 @@ export default async function (basedir: string = cwd()) { if (packageField) { if (packageField.includes(`yarn`)) { - if (await hasYarn3Rc(basedir)) return `yarn3` - return `yarn` + if (await hasYarnBerryConfig(basedir)) return `yarn` + return `yarn-classic` } if (packageField.includes(`npm`)) return `npm` @@ -18,9 +18,10 @@ export default async function (basedir: string = cwd()) { } if (await hasYarnLockfile(basedir)) { - if (await hasYarn3Rc(basedir)) return `yarn3` - return `yarn` + if (await hasYarnBerryConfig(basedir)) return `yarn` + return `yarn-classic` } + if (await hasNpmLockfile(basedir)) return `npm` if (await hasPnpmLockfile(basedir)) return `pnpm` } @@ -35,7 +36,7 @@ export const hasYarnLockfile = async ( } } -export const hasYarn3Rc = async (basedir: string): Promise => { +export const hasYarnBerryConfig = async (basedir: string): Promise => { try { return await fileExists(join(basedir, `.yarnrc.yml`)) } catch (error) { diff --git a/sources/@roots/bud/src/cli/commands/upgrade/index.tsx b/sources/@roots/bud/src/cli/commands/upgrade/index.tsx index 807d26cc6a..4cdabb084f 100644 --- a/sources/@roots/bud/src/cli/commands/upgrade/index.tsx +++ b/sources/@roots/bud/src/cli/commands/upgrade/index.tsx @@ -1,7 +1,7 @@ import {Command, Option} from '@roots/bud-support/clipanion' import {bind} from '@roots/bud-support/decorators/bind' import {BudError} from '@roots/bud-support/errors' -import isString from '@roots/bud-support/lodash/isString' +import {isLiteral, isOneOf} from '@roots/bud-support/typanion' import whichPm from '@roots/bud-support/which-pm' import BudCommand from '@roots/bud/cli/commands' @@ -42,7 +42,13 @@ export default class BudUpgradeCommand extends BudCommand { * Package manager option */ public pm = Option.String(`--pm`, undefined, { - description: `Package manager to use. One of: npm, yarn, yarn3, pnpm`, + description: `Package manager to use. One of: npm, yarn, yarn-classic, pnpm`, + validator: isOneOf([ + isLiteral(`npm`), + isLiteral(`pnpm`), + isLiteral(`yarn`), + isLiteral(`yarn-classic`), + ]), }) /** @@ -57,11 +63,6 @@ export default class BudUpgradeCommand extends BudCommand { */ public version = Option.String({required: false}) - public get binary() { - if (this.pm === `yarn3`) return `yarn` - return this.pm - } - /** * Catch fetch errors */ @@ -79,30 +80,21 @@ export default class BudUpgradeCommand extends BudCommand { await this.makeBud() if (!this.pm) { - const pacman = await whichPm(this.bud.context.basedir).catch( - error => { - const normalError = BudError.normalize(error) - normalError.details = normalError.message - normalError.thrownBy = `@roots/bud-support/which-pm` - throw error - }, - ) - - if (!this.isSupportedPackageManager(pacman)) { - const error = new BudError( - `bud upgrade only supports yarn classic and npm`, - ) - error.details = `You are using ${ - pacman ?? `an unknown package manager` - }` - error.thrownBy = `@roots/bud/cli/commands/upgrade` + this.pm = await whichPm(this.bud.context.basedir).catch(error => { + const normalError = BudError.normalize(error) + normalError.details = normalError.message + normalError.thrownBy = `@roots/bud-support/which-pm` throw error - } + }) + } - this.pm = pacman + if (this.pm === `yarn` && this.registry) { + throw new BudError( + `Yarn berry does not support custom registries set via --registry. Add your desired registry to .yarnrc.yml as per yarn's documentation.`, + ) } - this.command = this.pm === `npm` ? `install` : `add` + this.command = [`npm`, `pnpm`].includes(this.pm) ? `install` : `add` if (!this.version) { const get = await import(`@roots/bud-support/axios`) @@ -121,17 +113,8 @@ export default class BudUpgradeCommand extends BudCommand { return this.catchFetchError(new Error(`No version found`)) } - if (this.pm === `yarn3` && this.registry) { - const error = new BudError( - `yarn 3 does not support custom registries via the CLI`, - ) - error.details = `Set the registry in your .yarnrc.yml` - error.thrownBy = `@roots/bud/cli/commands/upgrade` - throw error - } - if (this.hasUpgradeableDependencies(`devDependencies`)) { - await this.$(this.binary, [ + await this.$(this.pm, [ this.command, ...this.getUpgradeableDependencies(`devDependencies`), ...this.getFlags(`devDependencies`), @@ -139,7 +122,7 @@ export default class BudUpgradeCommand extends BudCommand { } if (this.hasUpgradeableDependencies(`dependencies`)) { - await this.$(this.binary, [ + await this.$(this.pm, [ this.command, ...this.getUpgradeableDependencies(`dependencies`), ...this.getFlags(`dependencies`), @@ -167,13 +150,16 @@ export default class BudUpgradeCommand extends BudCommand { case `npm`: flags.push(`--save-dev`) break + case `pnpm`: - flags.push(`--save-dev`) + flags.push(`--save-dev`, `--public-hoist-pattern="*"`) break + case `yarn`: flags.push(`--dev`) break - case `yarn3`: + + case `yarn-classic`: flags.push(`--dev`) break } @@ -184,8 +170,9 @@ export default class BudUpgradeCommand extends BudCommand { case `npm`: flags.push(`--save`) break + case `pnpm`: - flags.push(`--save-prod`) + flags.push(`--save-prod`, `--public-hoist-pattern="*"`) break } @@ -208,12 +195,4 @@ export default class BudUpgradeCommand extends BudCommand { ): boolean { return this.getUpgradeableDependencies(type)?.length > 0 } - - public isSupportedPackageManager( - pacman: false | string, - ): pacman is `npm` | `pnpm` | `yarn` | `yarn3` { - return ( - isString(pacman) && [`npm`, `pnpm`, `yarn`, `yarn3`].includes(pacman) - ) - } } diff --git a/sources/create-bud-app/src/commands/create.ts b/sources/create-bud-app/src/commands/create.ts index 1874be6fa8..8f0c56429a 100644 --- a/sources/create-bud-app/src/commands/create.ts +++ b/sources/create-bud-app/src/commands/create.ts @@ -55,6 +55,9 @@ import writeSrcTask from '../tasks/write.src.js' import writeStylelintConfigTask from '../tasks/write.stylelint.config.js' import writeTailwindConfigTask from '../tasks/write.tailwind.config.js' import writeTsConfigTask from '../tasks/write.tsconfig.js' +import writeYarnLockfile from '../tasks/write.yarn.lock.js' +import writeYarnRCTask from '../tasks/write.yarnrc.js' +import yarnVersionTask from '../tasks/yarn-version.js' import getGitUser from '../utilities/getGitUser.js' import getLatestVersion from '../utilities/getLatestVersion.js' @@ -165,9 +168,6 @@ export default class CreateCommand extends Command { public extensions = extensionsMap - /** - * Project files - */ public files: Array = [] public fs: Filesystem @@ -202,24 +202,48 @@ export default class CreateCommand extends Command { * CLI after */ public async after() { - const pm = this.packageManager === `yarn` ? `yarn` : `npx` + const messages = [`🎉 Project ready for you to start building!`] - this.context.stdout.write( - [ - `🎉 Project ready for you to start building!`, - `${ - this.relativePath - ? `Navigate to ${chalk.blueBright( - `./${relative(this.cwd, this.directory)}`, - )} and run` - : `Run` - } ${chalk.blueBright(`${pm} bud dev`)} to get started.`, - `When you are ready to deploy, run ${chalk.blueBright( - `${pm} bud build`, - )} to compile your project for production.`, - `Happy hacking!`, - ].join(`\n\n`), + let devMessage = this.relativePath + ? `Navigate to ${chalk.blueBright( + `./${relative(this.cwd, this.directory)}`, + )} and run` + : `Run` + + if ([`yarn classic`, `yarn`].includes(this.packageManager)) { + devMessage = `${devMessage} ${chalk.blueBright(`yarn bud dev`)}` + } + if (this.packageManager === `pnpm`) { + devMessage = `${devMessage} ${chalk.blueBright(`pnpm bud dev`)}` + } + if (this.packageManager === `npm`) { + devMessage = `${devMessage} ${chalk.blueBright(`npx bud dev`)}` + } + + messages.push(`${devMessage} to get started.`) + + let buildMessage = `When you are ready to deploy, run` + + if ([`yarn classic`, `yarn`].includes(this.packageManager)) { + buildMessage = `${buildMessage} ${chalk.blueBright(`yarn bud build`)}` + } + if (this.packageManager === `pnpm`) { + buildMessage = `${buildMessage} ${chalk.blueBright( + `pnpm bud build`, + )}` + } + if (this.packageManager === `npm`) { + buildMessage = `${buildMessage} ${chalk.blueBright( + `npx bud build`, + )}` + } + + messages.push( + `${buildMessage} to compile your project for production.`, + `Happy hacking!`, ) + + this.context.stdout.write(messages.join(`\n\n`)) } /** @@ -442,6 +466,9 @@ export default class CreateCommand extends Command { await writePackageJSONTask(this) await writeReadmeTask(this) + await yarnVersionTask(this) + await writeYarnRCTask(this) + await writeYarnLockfile(this) await writeGitignoreConfigTask(this) await writeTsConfigTask(this) await writeConfigTask(this) diff --git a/sources/create-bud-app/src/flags/package-manager.ts b/sources/create-bud-app/src/flags/package-manager.ts index 3185bdb0e2..08a5d62e66 100644 --- a/sources/create-bud-app/src/flags/package-manager.ts +++ b/sources/create-bud-app/src/flags/package-manager.ts @@ -3,5 +3,10 @@ import {isLiteral, isOneOf} from 'typanion' export default Option.String(`--package-manager,-p`, `npm`, { description: `Package manager`, - validator: isOneOf([isLiteral(`npm`), isLiteral(`yarn`)]), + validator: isOneOf([ + isLiteral(`npm`), + isLiteral(`pnpm`), + isLiteral(`yarn classic`), + isLiteral(`yarn`), + ]), }) diff --git a/sources/create-bud-app/src/prompts/packageManager.ts b/sources/create-bud-app/src/prompts/packageManager.ts index 18e7b29334..234bdb7b02 100644 --- a/sources/create-bud-app/src/prompts/packageManager.ts +++ b/sources/create-bud-app/src/prompts/packageManager.ts @@ -6,7 +6,10 @@ import type CreateCommand from '../commands/create.js' const choices = { npm: {name: `npm`, value: `npm`}, + pnpm: {name: `pnpm`, value: `pnpm`}, yarn: {name: `yarn`, value: `yarn`}, + // eslint-disable-next-line perfectionist/sort-objects + [`yarn-classic`]: {name: `yarn classic`, value: `yarn classic`}, } export default (command: CreateCommand) => diff --git a/sources/create-bud-app/src/tasks/build.ts b/sources/create-bud-app/src/tasks/build.ts index 5d82a5a249..0291a5d3c2 100644 --- a/sources/create-bud-app/src/tasks/build.ts +++ b/sources/create-bud-app/src/tasks/build.ts @@ -6,10 +6,18 @@ export default async function buildTask(command: CreateCommand) { try { switch (command.packageManager) { + case `pnpm`: + await command.sh(`pnpm`, [`bud`, `build`, `production`]) + break + case `npm`: await command.sh(`npx`, [`bud`, `build`, `production`]) break + case `yarn classic`: + await command.sh(`yarn`, [`bud`, `build`, `production`]) + break + case `yarn`: await command.sh(`yarn`, [`bud`, `build`, `production`]) break diff --git a/sources/create-bud-app/src/tasks/install.dev.ts b/sources/create-bud-app/src/tasks/install.dev.ts index 24c269ea23..57294778b2 100644 --- a/sources/create-bud-app/src/tasks/install.dev.ts +++ b/sources/create-bud-app/src/tasks/install.dev.ts @@ -15,10 +15,18 @@ export default async function installTask(command: CreateCommand) { ]) switch (command.packageManager) { + case `pnpm`: + await command.sh(`pnpm`, [`install`, ...dependencies, `--save-dev`, `--public-hoist-pattern="*"`]) + break + case `npm`: await command.sh(`npm`, [`install`, ...dependencies, `--save-dev`]) break + case `yarn classic`: + await command.sh(`yarn`, [`add`, ...dependencies, `--dev`]) + break + case `yarn`: await command.sh(`yarn`, [`add`, ...dependencies, `--dev`]) break diff --git a/sources/create-bud-app/src/tasks/install.prod.ts b/sources/create-bud-app/src/tasks/install.prod.ts index 565f59708f..7cce8d56cf 100644 --- a/sources/create-bud-app/src/tasks/install.prod.ts +++ b/sources/create-bud-app/src/tasks/install.prod.ts @@ -11,10 +11,18 @@ export default async function installTask(command: CreateCommand) { try { switch (command.packageManager) { + case `pnpm`: + await command.sh(`pnpm`, [`install`, ...command.dependencies, `--public-hoist-pattern="*"`]) + break + case `npm`: await command.sh(`npm`, [`install`, ...command.dependencies]) break + case `yarn classic`: + await command.sh(`yarn`, [`add`, ...command.dependencies]) + break + case `yarn`: await command.sh(`yarn`, [`add`, ...command.dependencies]) break diff --git a/sources/create-bud-app/src/tasks/write.bud.config.ts b/sources/create-bud-app/src/tasks/write.bud.config.ts index bb03e9b924..3f71f873d0 100644 --- a/sources/create-bud-app/src/tasks/write.bud.config.ts +++ b/sources/create-bud-app/src/tasks/write.bud.config.ts @@ -17,6 +17,7 @@ export default async function writeConfigTask(command: CreateCommand) { const isTs = command.support.includes(`typescript`) || command.support.includes(`swc`) + const isReact = command.support.includes(`react`) const scriptExtension = isReact && isTs ? `tsx` : isTs ? `ts` : `js` diff --git a/sources/create-bud-app/src/tasks/write.readme.ts b/sources/create-bud-app/src/tasks/write.readme.ts index e84c5e4121..e4efd2497e 100644 --- a/sources/create-bud-app/src/tasks/write.readme.ts +++ b/sources/create-bud-app/src/tasks/write.readme.ts @@ -8,7 +8,7 @@ export default async function writePackageManifest( command: CreateCommand, ) { const spinner = command.createSpinner() - spinner.start(`writing readme...`) + spinner.start(`Writing README.md...`) if (!command.overwrite && command.exists(`readme`)) { return spinner.warn(`readme already exists. skipping write task.`) @@ -26,8 +26,11 @@ export default async function writePackageManifest( description: command.description, license: command.license, name: command.name, + npm: command.packageManager === `npm`, + pnpm: command.packageManager === `pnpm`, username: command.username, version: command.version, + yarn: [`yarn classic`, `yarn`].includes(command.packageManager), }) await command.fs.write(`README.md`, result) diff --git a/sources/create-bud-app/src/tasks/write.tsconfig.ts b/sources/create-bud-app/src/tasks/write.tsconfig.ts index 30210885bc..01fa357176 100644 --- a/sources/create-bud-app/src/tasks/write.tsconfig.ts +++ b/sources/create-bud-app/src/tasks/write.tsconfig.ts @@ -44,7 +44,8 @@ export default async function writeTsConfig(command: CreateCommand) { }, "types": [${types.reduce((all, type) => `${all} "${type}",`, ``)}] }, - "include": ["./bud.config.ts", "./src"], + "files": ["./bud.config.ts"], + "include": ["./src"], "exclude": ["./node_modules", "./dist"] } ` diff --git a/sources/create-bud-app/src/tasks/write.yarn.lock.ts b/sources/create-bud-app/src/tasks/write.yarn.lock.ts new file mode 100644 index 0000000000..ae9d62a1ea --- /dev/null +++ b/sources/create-bud-app/src/tasks/write.yarn.lock.ts @@ -0,0 +1,19 @@ +import type CreateCommand from '../commands/create.js' + +export default async function writeYarnLockfile(command: CreateCommand) { + if (![`yarn classic`, `yarn`].includes(command.packageManager)) return + + const spinner = command.createSpinner() + spinner.start(`Writing yarn.lock...`) + + if (!command.overwrite && command.exists(`yarn.lock`)) { + return spinner.warn(`yarn.lock already exists. skipping write task.`) + } + + await command.fs.write(`yarn.lock`, ``).catch(error => { + spinner.fail() + throw error + }) + + spinner.succeed() +} diff --git a/sources/create-bud-app/src/tasks/write.yarnrc.ts b/sources/create-bud-app/src/tasks/write.yarnrc.ts new file mode 100644 index 0000000000..498f7faa34 --- /dev/null +++ b/sources/create-bud-app/src/tasks/write.yarnrc.ts @@ -0,0 +1,34 @@ +import {join} from 'node:path' + +import type CreateCommand from '../commands/create.js' + +import hbs from '../utilities/templateEngine.js' + +export default async function writeYarnRC( + command: CreateCommand, +) { + if (command.packageManager !== `yarn`) return + + const spinner = command.createSpinner() + spinner.start(`Writing .yarnrc.yml...`) + + if (!command.overwrite && command.exists(`.yarnrc`)) { + return spinner.warn( + `.yarnrc.yml already exists. skipping write task.`, + ) + } + + try { + const source = await command.fs.read( + join(command.createRoot, `templates`, `.yarnrc.yml.hbs`), + `utf8`, + ) + + await command.fs.write(`.yarnrc.yml`, hbs.compile(source)({})) + } catch (error) { + spinner.fail() + throw error + } + + spinner.succeed() +} diff --git a/sources/create-bud-app/src/tasks/yarn-version.ts b/sources/create-bud-app/src/tasks/yarn-version.ts new file mode 100644 index 0000000000..4222efb024 --- /dev/null +++ b/sources/create-bud-app/src/tasks/yarn-version.ts @@ -0,0 +1,20 @@ +/* eslint-disable no-console */ +import type CreateCommand from '../commands/create.js' + +export default async function yarnVersionTask(command: CreateCommand) { + if (![`yarn classic`, `yarn`].includes(command.packageManager)) return + + const spinner = command.createSpinner() + spinner.start(`Setting yarn version`) + + await command.sh(`yarn`, [`set`, `version`, command.packageManager === `yarn` ? `berry` : `classic`]) + + try { + + } catch (error) { + spinner.fail() + throw error + } + + spinner.succeed() +} diff --git a/sources/create-bud-app/templates/.yarnrc.yml.hbs b/sources/create-bud-app/templates/.yarnrc.yml.hbs new file mode 100644 index 0000000000..3186f3f079 --- /dev/null +++ b/sources/create-bud-app/templates/.yarnrc.yml.hbs @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/sources/create-bud-app/templates/README.md.hbs b/sources/create-bud-app/templates/README.md.hbs index 1174531fdd..efefc6add5 100644 --- a/sources/create-bud-app/templates/README.md.hbs +++ b/sources/create-bud-app/templates/README.md.hbs @@ -2,13 +2,21 @@ This project was bootstrapped with [Create Bud App](https://github.com/roots/bud/tree/main/sources/@roots/create-bud-app). -Consult [the bud documentation](https://bud.js.org) for information on configuring and using bud.js. +Consult [the bud.js documentation](https://bud.js.org) for information on configuring and using bud.js. ## Available Scripts In the project directory, you can run: -### `npm dev` +{{#if npm}} +### `npx bud dev` +{{/if}} +{{#if yarn}} +### `yarn bud dev` +{{/if}} +{{#if pnpm}} +### `pnpm bud dev` +{{/if}} Runs the app in the development mode. @@ -16,7 +24,15 @@ Open [http://localhost:3000](http://localhost:3000) to view it in your browser. The page will reload when you make changes. -### `npm run build` +{{#if npm}} +### `npx bud build` +{{/if}} +{{#if yarn}} +### `yarn bud build` +{{/if}} +{{#if pnpm}} +### `pnpm bud build` +{{/if}} Builds the app for production to the `dist` folder. The build will be optimized for performance. From 9895ae9db580ec7d69e5f08ce326290758351a4a Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Sun, 3 Sep 2023 17:00:26 -0400 Subject: [PATCH 2/4] --- config/eslint.config.cjs | 25 +- config/vitest/config.e2e.ts | 2 +- config/vitest/config.integration.ts | 2 +- config/vitest/config.unit.ts | 2 +- package.json | 3 + .../docs/src/theme/BlogTagsPostsPage/index.js | 2 +- sources/@roots/bud-cache/src/service.ts | 118 +++++----- .../indicator/indicator.component.ts | 91 ++++--- .../components/overlay/overlay.component.ts | 7 +- sources/@roots/bud-client/src/hot/events.ts | 29 ++- .../@roots/bud-compiler/src/service/index.tsx | 86 ++++--- sources/@roots/bud-eslint/src/api.ts | 90 +++---- .../src/bud/commands/bud.eslint.command.tsx | 2 +- .../bud-framework/src/extension/index.ts | 221 ++++++++--------- .../src/bud/commands/bud.prettier.command.tsx | 2 +- sources/@roots/bud-server/src/server/base.ts | 65 +++-- sources/@roots/bud-stylelint/src/api.ts | 82 +++---- .../bud/commands/bud.stylelint.command.tsx | 2 +- .../@roots/bud-support/src/errors/index.ts | 29 ++- sources/@roots/bud-support/src/value/index.ts | 46 ++-- .../@roots/bud-support/src/which-pm/index.ts | 4 +- .../src/bud/commands/tailwindcss/index.tsx | 2 +- .../src/bud/commands/bud.ts.check.command.tsx | 2 +- .../src/bud/commands/bud.ts.command.tsx | 2 +- sources/@roots/bud/src/cli/app.tsx | 14 +- .../bud/src/cli/commands/build/index.tsx | 2 +- .../cli/commands/build/production/index.tsx | 2 +- .../bud/src/cli/commands/clean/index.tsx | 2 +- .../bud/src/cli/commands/doctor/index.tsx | 2 +- sources/@roots/bud/src/cli/commands/index.tsx | 41 ++-- .../bud/src/cli/commands/repl/index.tsx | 4 +- .../bud/src/cli/commands/upgrade/index.tsx | 69 ++++-- .../bud/src/cli/commands/view/index.tsx | 5 +- .../bud/src/cli/commands/webpack/index.tsx | 9 +- .../entrypoints-webpack-plugin/src/plugin.ts | 43 ++-- .../src/plugin.ts | 31 ++- sources/create-bud-app/src/commands/create.ts | 222 ++++++++++++++---- sources/create-bud-app/src/flags/support.ts | 16 +- .../src/prompts/packageManager.ts | 4 +- sources/create-bud-app/src/tasks/build.ts | 12 +- .../create-bud-app/src/tasks/install.dev.ts | 34 +-- .../create-bud-app/src/tasks/install.prod.ts | 12 +- .../src/tasks/write.tsconfig.ts | 2 +- .../create-bud-app/src/tasks/write.types.ts | 34 +++ .../templates/tsconfig.json.hbs | 18 -- sources/create-bud-app/templates/types.d.hbs | 4 + tests/reproductions/issue-1886.test.ts | 40 ++-- tests/reproductions/issue-1890.test.ts | 24 +- tests/reproductions/issue-2088.test.ts | 12 +- yarn.lock | 10 + 50 files changed, 830 insertions(+), 754 deletions(-) create mode 100644 sources/create-bud-app/src/tasks/write.types.ts delete mode 100644 sources/create-bud-app/templates/tsconfig.json.hbs create mode 100644 sources/create-bud-app/templates/types.d.hbs diff --git a/config/eslint.config.cjs b/config/eslint.config.cjs index 6bd3623df5..375cf4154f 100644 --- a/config/eslint.config.cjs +++ b/config/eslint.config.cjs @@ -7,6 +7,7 @@ module.exports = { `plugin:react/recommended`, `plugin:n/recommended-module`, `plugin:perfectionist/recommended-alphabetical`, + `plugin:sort-class-members/recommended`, ], ignorePatterns: [ `**/*.d.ts`, @@ -54,6 +55,7 @@ module.exports = { `react-hooks`, `eslint-plugin-n`, `perfectionist`, + `sort-class-members`, ], rules: { [`@typescript-eslint/explicit-member-accessibility`]: ERROR, @@ -109,6 +111,7 @@ module.exports = { [`n/shebang`]: OFF, [`no-console`]: ERROR, [`no-extra-semi`]: OFF, + [`perfectionist/sort-classes`]: OFF, [`perfectionist/sort-imports`]: [ ERROR, { @@ -133,13 +136,33 @@ module.exports = { `unknown`, ], order: `asc`, - type: `alphabetical`, + type: `natural`, }, ], [`react-hooks/exhaustive-deps`]: WARN, [`react-hooks/rules-of-hooks`]: ERROR, [`react/prop-types`]: OFF, [`react/react-in-jsx-scope`]: OFF, + [`sort-class-members/sort-class-members`]: [ + ERROR, + { + accessorPairPositioning: `getThenSet`, + groups: { + [`event-handlers`]: [{name: `/on.+/`, type: `method`}], + }, + order: [ + `[static-properties]`, + `[static-methods]`, + `[conventional-private-properties]`, + `[properties]`, + `[accessor-pairs]`, + `constructor`, + `[event-handlers]`, + `[methods]`, + `[conventional-private-methods]`, + ], + }, + ], }, settings: { n: { diff --git a/config/vitest/config.e2e.ts b/config/vitest/config.e2e.ts index 637efef7b4..9693f378c8 100644 --- a/config/vitest/config.e2e.ts +++ b/config/vitest/config.e2e.ts @@ -1,7 +1,7 @@ import {env} from 'node:process' -import GithubActionsReporter from 'vitest-github-actions-reporter' import {defineConfig} from 'vitest/config' +import GithubActionsReporter from 'vitest-github-actions-reporter' import alias from './alias.js' diff --git a/config/vitest/config.integration.ts b/config/vitest/config.integration.ts index 9f72d74d2b..fdc6a7006c 100644 --- a/config/vitest/config.integration.ts +++ b/config/vitest/config.integration.ts @@ -1,7 +1,7 @@ import {env} from 'node:process' -import GithubActionsReporter from 'vitest-github-actions-reporter' import {defineConfig} from 'vitest/config' +import GithubActionsReporter from 'vitest-github-actions-reporter' import alias from './alias.js' diff --git a/config/vitest/config.unit.ts b/config/vitest/config.unit.ts index 4b42f20b0f..a1ca7a211e 100644 --- a/config/vitest/config.unit.ts +++ b/config/vitest/config.unit.ts @@ -1,7 +1,7 @@ import {env} from 'process' -import GithubActionsReporter from 'vitest-github-actions-reporter' import {defineConfig} from 'vitest/config' +import GithubActionsReporter from 'vitest-github-actions-reporter' import alias from './alias.js' diff --git a/package.json b/package.json index 07c54e24d8..a46059915b 100644 --- a/package.json +++ b/package.json @@ -73,5 +73,8 @@ }, "resolutions": { "@yarnpkg/builder@4.0.0-rc.49": "patch:@yarnpkg/builder@npm%3A4.0.0-rc.49#./.yarn/patches/@yarnpkg-builder-npm-4.0.0-rc.49-3bed67fd94.patch" + }, + "devDependencies": { + "eslint-plugin-sort-class-members": "1.18.0" } } diff --git a/sources/@repo/docs/src/theme/BlogTagsPostsPage/index.js b/sources/@repo/docs/src/theme/BlogTagsPostsPage/index.js index bb38c5a24c..c4ad6f5d5f 100644 --- a/sources/@repo/docs/src/theme/BlogTagsPostsPage/index.js +++ b/sources/@repo/docs/src/theme/BlogTagsPostsPage/index.js @@ -1,11 +1,11 @@ import Link from '@docusaurus/Link' +import {translate} from '@docusaurus/Translate' import { HtmlClassNameProvider, PageMetadata, ThemeClassNames, usePluralForm, } from '@docusaurus/theme-common' -import {translate} from '@docusaurus/Translate' import {Hero} from '@site/src/components/hero' import BlogPostItems from '@theme/BlogPostItems' import Layout from '@theme/Layout' diff --git a/sources/@roots/bud-cache/src/service.ts b/sources/@roots/bud-cache/src/service.ts index 0f55bb10cb..7d31e9f342 100644 --- a/sources/@roots/bud-cache/src/service.ts +++ b/sources/@roots/bud-cache/src/service.ts @@ -23,12 +23,6 @@ export default class Cache extends Service implements BudCache { * {@link BudCache.enabled} */ public declare enabled: boolean - public set allowCollectingMemory( - value: Callback, - ) { - this.app.hooks.on(`build.cache.allowCollectingMemory`, value) - } - /** * {@link BudCache.allowCollectingMemory} */ @@ -41,15 +35,10 @@ export default class Cache extends Service implements BudCache { ) return typeof value === `boolean` ? value : fallback } - - /** - * {@link Service.boot} - */ - public override async boot?(bud: Bud) { - if (bud.context.force === true) { - await this.flush() - } - this.enabled = bud.context.cache !== false + public set allowCollectingMemory( + value: Callback, + ) { + this.app.hooks.on(`build.cache.allowCollectingMemory`, value) } /** @@ -89,13 +78,65 @@ export default class Cache extends Service implements BudCache { fallback ) } - public set cacheDirectory( directory: Callback, ) { this.app.hooks.on(`build.cache.cacheDirectory`, directory) } + /** + * {@link BudCache.name} + */ + public get name(): string { + const fallback = join( + this.app.mode, + ...Object.values(this.app.context._ ?? {}), + ) + return ( + this.app.hooks.filter( + `build.cache.name`, + this.app.hooks.filter(`build.name`, fallback), + ) ?? fallback + ) + } + public set name(name: string) { + this.app.hooks.on(`build.cache.name`, name) + } + + /** + * {@link BudCache.type} + */ + public get type(): 'filesystem' | 'memory' { + const fallback = isString(this.app.context.cache) + ? this.app.context.cache + : `filesystem` + + return this.app.hooks.filter(`build.cache.type`) ?? fallback + } + public set type(type: Callback) { + this.app.hooks.on(`build.cache.type`, type) + } + + /** + * {@link BudCache.version} + */ + public get version(): string | undefined { + return this.app.hooks.filter(`build.cache.version`, undefined) + } + public set version(version: string) { + this.app.hooks.on(`build.cache.version`, version) + } + + /** + * {@link Service.boot} + */ + public override async boot?(bud: Bud) { + if (bud.context.force === true) { + await this.flush() + } + this.enabled = bud.context.cache !== false + } + /** * {@link BudCache.configuration} * @readonly @@ -153,26 +194,6 @@ export default class Cache extends Service implements BudCache { return this.type } - /** - * {@link BudCache.name} - */ - public get name(): string { - const fallback = join( - this.app.mode, - ...Object.values(this.app.context._ ?? {}), - ) - return ( - this.app.hooks.filter( - `build.cache.name`, - this.app.hooks.filter(`build.name`, fallback), - ) ?? fallback - ) - } - - public set name(name: string) { - this.app.hooks.on(`build.cache.name`, name) - } - /** * {@link BudCache.register} */ @@ -221,29 +242,4 @@ export default class Cache extends Service implements BudCache { this.type = type return this } - - public set type(type: Callback) { - this.app.hooks.on(`build.cache.type`, type) - } - - /** - * {@link BudCache.type} - */ - public get type(): 'filesystem' | 'memory' { - const fallback = isString(this.app.context.cache) - ? this.app.context.cache - : `filesystem` - - return this.app.hooks.filter(`build.cache.type`) ?? fallback - } - - public set version(version: string) { - this.app.hooks.on(`build.cache.version`, version) - } - /** - * {@link BudCache.version} - */ - public get version(): string | undefined { - return this.app.hooks.filter(`build.cache.version`, undefined) - } } diff --git a/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts b/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts index 6d88aa919f..f94b6103d1 100644 --- a/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts +++ b/sources/@roots/bud-client/src/hot/components/indicator/indicator.component.ts @@ -4,6 +4,9 @@ import {pulse} from './indicator.pulse.js' * Indicator web component */ export class Component extends HTMLElement { + public static get observedAttributes() { + return [`has-errors`, `has-warnings`, `action`] + } /** * Status indicator colors */ @@ -37,51 +40,6 @@ export class Component extends HTMLElement { this.renderShadow() } - public static get observedAttributes() { - return [`has-errors`, `has-warnings`, `action`] - } - - public attributeChangedCallback() { - if (this.hasAttribute(`has-errors`)) return this.onError() - if (this.hasAttribute(`has-warnings`)) return this.onWarning() - - if ( - !this.hasAttribute(`has-errors`) && - !this.hasAttribute(`has-warnings`) && - this.getAttribute(`action`) === `built` - ) - return this.onSuccess() - - if ( - this.getAttribute(`action`) == `building` || - this.getAttribute(`action`) == `sync` - ) - return this.onPending() - } - - /** - * Get accessor: has errors - */ - public get hasErrors(): boolean { - return this.getAttribute(`has-errors`) == `true` - } - - /** - * Get accessor: has warnings - */ - public get hasWarnings(): boolean { - return this.getAttribute(`has-warnings`) == `true` - } - - /** - * Hide status indicator - */ - public hide() { - this.hideTimeout = setTimeout(() => { - this.shadowRoot.querySelector(this.selector).classList.remove(`show`) - }, 2000) - } - /** * Status is error */ @@ -93,7 +51,6 @@ export class Component extends HTMLElement { .classList.remove(`warning`, `success`, `pending`) this.shadowRoot.querySelector(this.selector).classList.add(`error`) } - /** * Status is pending */ @@ -108,7 +65,6 @@ export class Component extends HTMLElement { this.hide() } - /** * Status is success */ @@ -123,7 +79,6 @@ export class Component extends HTMLElement { this.hide() } - /** * Status is warning */ @@ -136,6 +91,46 @@ export class Component extends HTMLElement { this.shadowRoot.querySelector(this.selector).classList.add(`warning`) } + public attributeChangedCallback() { + if (this.hasAttribute(`has-errors`)) return this.onError() + if (this.hasAttribute(`has-warnings`)) return this.onWarning() + + if ( + !this.hasAttribute(`has-errors`) && + !this.hasAttribute(`has-warnings`) && + this.getAttribute(`action`) === `built` + ) + return this.onSuccess() + + if ( + this.getAttribute(`action`) == `building` || + this.getAttribute(`action`) == `sync` + ) + return this.onPending() + } + + /** + * Get accessor: has errors + */ + public get hasErrors(): boolean { + return this.getAttribute(`has-errors`) == `true` + } + + /** + * Get accessor: has warnings + */ + public get hasWarnings(): boolean { + return this.getAttribute(`has-warnings`) == `true` + } + + /** + * Hide status indicator + */ + public hide() { + this.hideTimeout = setTimeout(() => { + this.shadowRoot.querySelector(this.selector).classList.remove(`show`) + }, 2000) + } /** * Render status indicator diff --git a/sources/@roots/bud-client/src/hot/components/overlay/overlay.component.ts b/sources/@roots/bud-client/src/hot/components/overlay/overlay.component.ts index a9d4fc689c..66d9ee4dc9 100644 --- a/sources/@roots/bud-client/src/hot/components/overlay/overlay.component.ts +++ b/sources/@roots/bud-client/src/hot/components/overlay/overlay.component.ts @@ -2,6 +2,9 @@ * Component container */ export class Component extends HTMLElement { + public static get observedAttributes() { + return [`message`] + } public documentBodyStyle: any public name: string = `bud-overlay` @@ -16,10 +19,6 @@ export class Component extends HTMLElement { this.renderShadow() } - public static get observedAttributes() { - return [`message`] - } - public attributeChangedCallback() { if (!this.documentBodyStyle) this.documentBodyStyle = document.body?.style diff --git a/sources/@roots/bud-client/src/hot/events.ts b/sources/@roots/bud-client/src/hot/events.ts index a4682a6f00..267dfd305d 100644 --- a/sources/@roots/bud-client/src/hot/events.ts +++ b/sources/@roots/bud-client/src/hot/events.ts @@ -11,6 +11,20 @@ export const injectEvents = (eventSource: EventSourceFactory) => { * mocking in tests */ return class Events extends eventSource { + /** + * Singleton constructor + * + */ + public static make( + options: Partial & {name: string; path: string}, + ): Events { + if (typeof window.bud.hmr[options.name] === `undefined`) + Object.assign(window.bud.hmr, { + [options.name]: new Events(options), + }) + + return window.bud.hmr[options.name] + } /** * Registered listeners */ @@ -58,21 +72,6 @@ export const injectEvents = (eventSource: EventSourceFactory) => { this.addListener = this.addListener.bind(this) } - /** - * Singleton constructor - * - */ - public static make( - options: Partial & {name: string; path: string}, - ): Events { - if (typeof window.bud.hmr[options.name] === `undefined`) - Object.assign(window.bud.hmr, { - [options.name]: new Events(options), - }) - - return window.bud.hmr[options.name] - } - /** * EventSource `addMessageListener` handler */ diff --git a/sources/@roots/bud-compiler/src/service/index.tsx b/sources/@roots/bud-compiler/src/service/index.tsx index d9db64261d..09716dda20 100644 --- a/sources/@roots/bud-compiler/src/service/index.tsx +++ b/sources/@roots/bud-compiler/src/service/index.tsx @@ -56,49 +56,6 @@ class Compiler extends Service implements BudCompiler { */ public declare stats: BudCompiler[`stats`] - /** - * {@link BudCompiler.compile} - */ - @bind - public async compile(bud: Bud): Promise { - const config = !bud.hasChildren - ? [await bud.build.make()] - : await Promise.all( - Object.values(bud.children).map(async (child: Bud) => - child.build.make().catch(error => { - throw error - }), - ), - ) - - this.config = config?.filter(Boolean) - - this.config.parallelism = Math.max(cpus().length - 1, 1) - this.logger.info(`parallel compilations: ${this.config.parallelism}`) - - await bud.hooks.fire(`compiler.before`, bud).catch(error => { - throw error - }) - - this.logger.timeEnd(`initialize`) - this.app.dashboard.updateStatus(`compiling`) - - try { - this.instance = this.implementation(this.config) - } catch (error: unknown) { - const normalError = - error instanceof Error ? error : BudError.normalize(error) - this.onError(normalError) - } - - this.instance.hooks.done.tap(bud.label, (stats: any) => { - this.onStats(stats) - bud.hooks.fire(`compiler.done`, bud, this.stats).catch(this.onError) - }) - - return this.instance - } - /** * {@link BudCompiler.onError} */ @@ -121,7 +78,6 @@ class Compiler extends Service implements BudCompiler { render() } } - /** * {@link BudCompiler.onStats} */ @@ -196,6 +152,48 @@ class Compiler extends Service implements BudCompiler { } }) } + /** + * {@link BudCompiler.compile} + */ + @bind + public async compile(bud: Bud): Promise { + const config = !bud.hasChildren + ? [await bud.build.make()] + : await Promise.all( + Object.values(bud.children).map(async (child: Bud) => + child.build.make().catch(error => { + throw error + }), + ), + ) + + this.config = config?.filter(Boolean) + + this.config.parallelism = Math.max(cpus().length - 1, 1) + this.logger.info(`parallel compilations: ${this.config.parallelism}`) + + await bud.hooks.fire(`compiler.before`, bud).catch(error => { + throw error + }) + + this.logger.timeEnd(`initialize`) + this.app.dashboard.updateStatus(`compiling`) + + try { + this.instance = this.implementation(this.config) + } catch (error: unknown) { + const normalError = + error instanceof Error ? error : BudError.normalize(error) + this.onError(normalError) + } + + this.instance.hooks.done.tap(bud.label, (stats: any) => { + this.onStats(stats) + bud.hooks.fire(`compiler.done`, bud, this.stats).catch(this.onError) + }) + + return this.instance + } /** * {@link Service.register} diff --git a/sources/@roots/bud-eslint/src/api.ts b/sources/@roots/bud-eslint/src/api.ts index 68f0e40681..a108344fec 100644 --- a/sources/@roots/bud-eslint/src/api.ts +++ b/sources/@roots/bud-eslint/src/api.ts @@ -260,6 +260,40 @@ export class BudEslintPublicApi extends Extension { */ public declare useEslintrc: Api['useEslintrc'] + /** + * Eslint plugins + * + * @example + * ```js + * console.log(bud.eslint.plugins) + * ``` + */ + public get plugins(): Api[`overrideConfig`][`plugins`] { + return this.overrideConfig.plugins + } + public set plugins(plugins: Api[`overrideConfig`][`plugins`]) { + this.setOverrideConfig((config = {}) => { + return {...config, plugins: [...(config?.plugins ?? []), ...plugins]} + }) + } + + /** + * Eslint rules + * + * @example + * ```js + * console.log(bud.eslint.rules) + * ``` + */ + public get rules(): Api[`overrideConfig`][`rules`] { + return this.overrideConfig.rules + } + public set rules(rules: Api[`overrideConfig`][`rules`]) { + this.setOverrideConfig((config = {}) => { + return {...config, rules: {...config.rules, ...rules}} + }) + } + /** * {@link Options.overrideConfig} */ @@ -329,62 +363,6 @@ export class BudEslintPublicApi extends Extension { return this.rules } - /** - * Get eslint plugins - * - * @example - * ```js - * console.log(bud.eslint.plugins) - * ``` - */ - public get plugins(): Api[`overrideConfig`][`plugins`] { - return this.overrideConfig.plugins - } - - /** - * Set eslint plugins - * - * @example - * ```js - * bud.eslint.plugins = { - * 'no-console': 'off', - * } - * ``` - */ - public set plugins(plugins: Api[`overrideConfig`][`plugins`]) { - this.setOverrideConfig((config = {}) => { - return {...config, plugins: [...(config?.plugins ?? []), ...plugins]} - }) - } - - /** - * Get eslint rules - * - * @example - * ```js - * console.log(bud.eslint.rules) - * ``` - */ - public get rules(): Api[`overrideConfig`][`rules`] { - return this.overrideConfig.rules - } - - /** - * Set eslint rules - * - * @example - * ```js - * bud.eslint.rules = { - * 'no-console': 'off', - * } - * ``` - */ - public set rules(rules: Api[`overrideConfig`][`rules`]) { - this.setOverrideConfig((config = {}) => { - return {...config, rules: {...config.rules, ...rules}} - }) - } - /** * {@link Options.overrideConfig} */ diff --git a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx index fd444e4cac..4db86e3096 100644 --- a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx +++ b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx @@ -1,5 +1,5 @@ -import {Option} from '@roots/bud-support/clipanion' import BudCommand from '@roots/bud/cli/commands' +import {Option} from '@roots/bud-support/clipanion' /** * Bud eslint command diff --git a/sources/@roots/bud-framework/src/extension/index.ts b/sources/@roots/bud-framework/src/extension/index.ts index c31d8e608c..83503a2e34 100644 --- a/sources/@roots/bud-framework/src/extension/index.ts +++ b/sources/@roots/bud-framework/src/extension/index.ts @@ -136,22 +136,18 @@ export class Extension< * Application */ public _app: () => Bud - /** * Extension options */ public _options: Partial> - /** * Depends on */ public dependsOn?: Set - /** * Depends on (optional) */ public dependsOnOptional?: Set<`${keyof Modules & string}`> - /** * Is extension enabled * @@ -161,14 +157,11 @@ export class Extension< * - {@link Extension.make} */ public enabled: boolean = true - public get = this.getOption - /** * The module name */ public label: `${keyof Modules & string}` - /** * Extension meta */ @@ -179,19 +172,16 @@ export class Extension< configAfter: false, register: false, } - /** * Extension options * * @readonly */ public options: ExtensionOptions - /** * Plugin constructor */ public plugin?: ApplyPluginConstructor - public set = this.setOption /** @@ -208,130 +198,28 @@ export class Extension< }) } - /** - * `boot` callback handler - */ - @bind - public async _boot() { - if (isUndefined(this.boot)) return - - if (this.meta[`boot`] === true) return - this.meta[`boot`] = true - - await this.boot(this.app).catch(this.catch) - } - - /** - * `buildAfter` callback handler - */ - @bind - public async _buildAfter() { - if (isUndefined(this.buildAfter)) return - if (!this.isEnabled()) return - - if (this.meta[`buildAfter`] === true) return - this.meta[`buildAfter`] = true - - await this.buildAfter(this.app).catch(this.catch) - } - - /** - * `buildBefore` callback handler - */ - @bind - public async _buildBefore() { - if (isUndefined(this.buildBefore)) return - if (!this.isEnabled()) return - - if (this.meta[`buildBefore`] === true) return - this.meta[`buildBefore`] = true - - await this.buildBefore(this.app).catch(this.catch) - } - - /** - * `configAfter` callback handler - */ - @bind - public async _configAfter() { - if (isUndefined(this.configAfter)) return - - if (this.meta[`configAfter`] === true) return - this.meta[`configAfter`] = true - - await this.configAfter(this.app).catch(this.catch) - } - - /** - * `make` callback handler - */ - @bind - public async _make() { - if (isUndefined(this.make) && isUndefined(this.plugin)) return false - if (this.isEnabled() === false) return false - - try { - if (!isUndefined(this.apply)) { - this.logger.info(`apply prop found. return extension instance`) - return this - } - - if (!isUndefined(this.plugin)) { - const plugin = new this.plugin({...this.options}) - this.logger.success(`produced webpack plugin`) - return plugin - } - - if (!isUndefined(this.make)) { - const plugin = await this.make(this.app, {...this.options}) - this.logger.success(`produced webpack plugin`) - return plugin - } - } catch (error) { - this.catch(error) - } - } - - /** - * `register` callback handler - */ - @bind - public async _register() { - if (isUndefined(this.register)) return - - if (this.meta[`register`] === true) return - this.meta[`register`] = true - - await this.register(this.app).catch(this.catch) - } - /** * Application accessor */ public get app(): Bud { return this._app() } - /** * {@link ApplyPlugin.apply} */ public apply?(compiler: Compiler): unknown | void - /** * `boot` callback */ public async boot?(app: Bud): Promise - /** * `buildAfter` callback */ public async buildAfter?(app: Bud): Promise - /** * `buildBefore` callback */ public async buildBefore?(app: Bud): Promise - @bind public catch(error: Error | string): never { const thrownBy = @@ -355,12 +243,10 @@ export class Extension< }, ) } - /** * `configAfter` callback */ public async configAfter?(app: Bud): Promise - /** * Disable extension * @deprecated pass `false` to {@link Extension.enable} @@ -369,7 +255,6 @@ export class Extension< public disable() { this.enabled = false } - /** * Return to bud instance from extension */ @@ -377,7 +262,6 @@ export class Extension< public done(): Bud { return this.app } - /** * Enable extension */ @@ -386,7 +270,6 @@ export class Extension< this.enabled = enabled return this } - /** * Get extension option */ @@ -394,7 +277,6 @@ export class Extension< public getOption(key: K): ExtensionOptions[K] { return get(this.options, key) } - public getOptions(): ExtensionOptions { return Object.entries(this._options).reduce((acc, [key, value]) => { if (isUndefined(value)) return acc @@ -433,7 +315,6 @@ export class Extension< .import(signifier, context, options) .catch(this.catch) } - /** * Is extension enabled? */ @@ -447,17 +328,14 @@ export class Extension< public get logger(): any { return logger.scope(...[this.app.label, this.label].filter(Boolean)) } - /** * `make` callback */ public async make?(app: Bud, options?: ExtensionOptions): Promise - /** * {@link Extension.register} */ public async register?(app: Bud): Promise - /** * Resolve module using `import.meta.resolve` api */ @@ -477,7 +355,6 @@ export class Extension< ) } } - /** * Set extension option */ @@ -494,7 +371,6 @@ export class Extension< set(this._options, key, valueOrCallback) return this } - /** * Set extension options */ @@ -504,7 +380,6 @@ export class Extension< this._options = value return this } - /** * Function returning a boolean indicating if the {@link Extension} should be utilized. * @@ -514,6 +389,102 @@ export class Extension< public when(bud: Bud, options?: ExtensionOptions): boolean { return this.enabled } + /** + * `boot` callback handler + */ + @bind + public async _boot() { + if (isUndefined(this.boot)) return + + if (this.meta[`boot`] === true) return + this.meta[`boot`] = true + + await this.boot(this.app).catch(this.catch) + } + + /** + * `buildAfter` callback handler + */ + @bind + public async _buildAfter() { + if (isUndefined(this.buildAfter)) return + if (!this.isEnabled()) return + + if (this.meta[`buildAfter`] === true) return + this.meta[`buildAfter`] = true + + await this.buildAfter(this.app).catch(this.catch) + } + + /** + * `buildBefore` callback handler + */ + @bind + public async _buildBefore() { + if (isUndefined(this.buildBefore)) return + if (!this.isEnabled()) return + + if (this.meta[`buildBefore`] === true) return + this.meta[`buildBefore`] = true + + await this.buildBefore(this.app).catch(this.catch) + } + + /** + * `configAfter` callback handler + */ + @bind + public async _configAfter() { + if (isUndefined(this.configAfter)) return + + if (this.meta[`configAfter`] === true) return + this.meta[`configAfter`] = true + + await this.configAfter(this.app).catch(this.catch) + } + + /** + * `make` callback handler + */ + @bind + public async _make() { + if (isUndefined(this.make) && isUndefined(this.plugin)) return false + if (this.isEnabled() === false) return false + + try { + if (!isUndefined(this.apply)) { + this.logger.info(`apply prop found. return extension instance`) + return this + } + + if (!isUndefined(this.plugin)) { + const plugin = new this.plugin({...this.options}) + this.logger.success(`produced webpack plugin`) + return plugin + } + + if (!isUndefined(this.make)) { + const plugin = await this.make(this.app, {...this.options}) + this.logger.success(`produced webpack plugin`) + return plugin + } + } catch (error) { + this.catch(error) + } + } + + /** + * `register` callback handler + */ + @bind + public async _register() { + if (isUndefined(this.register)) return + + if (this.meta[`register`] === true) return + this.meta[`register`] = true + + await this.register(this.app).catch(this.catch) + } } export {DynamicOption} diff --git a/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx b/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx index ca2b95010a..3861f0584d 100644 --- a/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx +++ b/sources/@roots/bud-prettier/src/bud/commands/bud.prettier.command.tsx @@ -1,5 +1,5 @@ -import {Command, Option} from '@roots/bud-support/clipanion' import BudCommand from '@roots/bud/cli/commands' +import {Command, Option} from '@roots/bud-support/clipanion' /** * `bud prettier` command diff --git a/sources/@roots/bud-server/src/server/base.ts b/sources/@roots/bud-server/src/server/base.ts index c175167d98..8358cae161 100644 --- a/sources/@roots/bud-server/src/server/base.ts +++ b/sources/@roots/bud-server/src/server/base.ts @@ -34,6 +34,37 @@ export abstract class BaseServer implements Connection { */ public constructor(public app: Bud) {} + /** + * Server error event + */ + @bind + public onError(error: Error) { + const cause = BudError.normalize(error) + throw new ServerError(cause.message, {cause}) + } + /** + * Server listen event + */ + @bind + public onListening(...param: any[]) { + this.logger.info(`listening`, ...param) + } + /** + * Server request event + */ + @bind + public async onRequest( + request: IncomingMessage, + response: ServerResponse, + ) { + this.logger.log( + `[${response.statusCode}]`, + request.url, + response.statusMessage ?? ``, + ) + + return response + } /** * Listen */ @@ -68,40 +99,6 @@ export abstract class BaseServer implements Connection { return logger.scope(`server`, this.constructor.name.toLowerCase()) } - /** - * Server error event - */ - @bind - public onError(error: Error) { - const cause = BudError.normalize(error) - throw new ServerError(cause.message, {cause}) - } - - /** - * Server listen event - */ - @bind - public onListening(...param: any[]) { - this.logger.info(`listening`, ...param) - } - - /** - * Server request event - */ - @bind - public async onRequest( - request: IncomingMessage, - response: ServerResponse, - ) { - this.logger.log( - `[${response.statusCode}]`, - request.url, - response.statusMessage ?? ``, - ) - - return response - } - /** * Options */ diff --git a/sources/@roots/bud-stylelint/src/api.ts b/sources/@roots/bud-stylelint/src/api.ts index 9801a32cc1..a4a2147972 100644 --- a/sources/@roots/bud-stylelint/src/api.ts +++ b/sources/@roots/bud-stylelint/src/api.ts @@ -227,6 +227,38 @@ export class BudStylelintPublicApi extends Extension { */ public declare threads: Api['threads'] + /** + * Stylelint plugins + * + * @example + * ```js + * console.log(bud.stylelint.plugins) + * ``` + */ + public get plugins(): Api[`config`][`plugins`] { + return this.config.plugins + } + public set plugins(plugins: Api[`config`][`plugins`]) { + this.setConfig((config = {}) => ({...config, plugins})) + } + + /** + * Stylelint rules + * + * @example + * ```js + * console.log(bud.stylelint.rules) + * ``` + */ + public get rules(): Api[`config`][`rules`] { + return this.config.rules + } + public set rules(rules: Api[`config`][`rules`]) { + this.setConfig((config = {}) => { + return {...config, rules: {...config.rules, ...rules}} + }) + } + /** * Extend config * @@ -280,56 +312,6 @@ export class BudStylelintPublicApi extends Extension { return this.rules } - /** - * Get stylelint plugins - * - * @example - * ```js - * console.log(bud.stylelint.plugins) - * ``` - */ - public get plugins(): Api[`config`][`plugins`] { - return this.config.plugins - } - - /** - * Set stylelint plugins - * - * @example - * ```js - * bud.stylelint.plugins = [] - * ``` - */ - public set plugins(plugins: Api[`config`][`plugins`]) { - this.setConfig((config = {}) => ({...config, plugins})) - } - - /** - * Get stylelint rules - * - * @example - * ```js - * console.log(bud.stylelint.rules) - * ``` - */ - public get rules(): Api[`config`][`rules`] { - return this.config.rules - } - - /** - * Set stylelint rules - * - * @example - * ```js - * bud.stylelint.rules = {'no-descending-specificity': null} - * ``` - */ - public set rules(rules: Api[`config`][`rules`]) { - this.setConfig((config = {}) => { - return {...config, rules: {...config.rules, ...rules}} - }) - } - /** * Set stylelint config plugins * diff --git a/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx b/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx index 6eeb4cbf24..e29a03d031 100644 --- a/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx +++ b/sources/@roots/bud-stylelint/src/bud/commands/bud.stylelint.command.tsx @@ -1,5 +1,5 @@ -import {Command, Option} from '@roots/bud-support/clipanion' import BudCommand from '@roots/bud/cli/commands' +import {Command, Option} from '@roots/bud-support/clipanion' /** * Bud stylelint command diff --git a/sources/@roots/bud-support/src/errors/index.ts b/sources/@roots/bud-support/src/errors/index.ts index 13ef88d675..e3927ee1eb 100644 --- a/sources/@roots/bud-support/src/errors/index.ts +++ b/sources/@roots/bud-support/src/errors/index.ts @@ -24,6 +24,20 @@ interface BudErrorProps extends Error { * Error base class */ class BudError extends Error { + public static normalize(error: unknown) { + if (error instanceof BudError) return error + + if (error instanceof Error) { + const {message, ...rest} = error + return new BudError(message, rest) + } + + if (typeof error === `string`) { + return new BudError(error) + } + + return new BudError(`unknown error`) + } /** * Details */ @@ -108,21 +122,6 @@ class BudError extends Error { this.isBudError = true } - - public static normalize(error: unknown) { - if (error instanceof BudError) return error - - if (error instanceof Error) { - const {message, ...rest} = error - return new BudError(message, rest) - } - - if (typeof error === `string`) { - return new BudError(error) - } - - return new BudError(`unknown error`) - } } /** diff --git a/sources/@roots/bud-support/src/value/index.ts b/sources/@roots/bud-support/src/value/index.ts index 69f863e7a5..fba5a520a7 100644 --- a/sources/@roots/bud-support/src/value/index.ts +++ b/sources/@roots/bud-support/src/value/index.ts @@ -5,27 +5,6 @@ interface Value { class Value { public static isBudValue: true = true - /** - * For type checking - * - * @remarks - * Some functions like _.set() will mutate the class instance. - * This property is used to check if the instance is a {@link Value} - * and should work even after mutation. - */ - public isBudValue: true = true - - /** - * Class constructor - * - * @remarks - * Singleton pattern. Use {@link Value.make} to create a {@link Value} instance. - * - * @param identity - Value - * @private - */ - public constructor(public identity: T) {} - /** * Get {@link Value.identity} */ @@ -37,20 +16,17 @@ class Value { ? Value.get(value)(...args) : Value.get(value) } - /** * Get {@link Value.identity} */ public static get(value: T | Value): T { return Value.isValue(value) ? value.identity : (value as T) } - public static isCallable( value: T | Value, ): value is T & CallableFunction { return Value.typeOf(value) === `function` } - /** * Check {@link Value.identity} type */ @@ -65,20 +41,38 @@ class Value { value.isBudValue ) } - /** * Make {@link Value} instance */ public static make(value: T, ..._args: any[]): Value { return Value.isValue(value) ? value : new Value(value) } - /** * Check {@link Value.identity} type */ public static typeOf(value: T | Value): string { return Value.isValue(value) ? typeof value.identity : typeof value } + /** + * For type checking + * + * @remarks + * Some functions like _.set() will mutate the class instance. + * This property is used to check if the instance is a {@link Value} + * and should work even after mutation. + */ + public isBudValue: true = true + + /** + * Class constructor + * + * @remarks + * Singleton pattern. Use {@link Value.make} to create a {@link Value} instance. + * + * @param identity - Value + * @private + */ + public constructor(public identity: T) {} /** * Get value of {@link Value.identity} diff --git a/sources/@roots/bud-support/src/which-pm/index.ts b/sources/@roots/bud-support/src/which-pm/index.ts index e6d1a50966..03a991e171 100644 --- a/sources/@roots/bud-support/src/which-pm/index.ts +++ b/sources/@roots/bud-support/src/which-pm/index.ts @@ -36,7 +36,9 @@ export const hasYarnLockfile = async ( } } -export const hasYarnBerryConfig = async (basedir: string): Promise => { +export const hasYarnBerryConfig = async ( + basedir: string, +): Promise => { try { return await fileExists(join(basedir, `.yarnrc.yml`)) } catch (error) { diff --git a/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx b/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx index 94ceb11670..457e1dc789 100644 --- a/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx +++ b/sources/@roots/bud-tailwindcss/src/bud/commands/tailwindcss/index.tsx @@ -1,7 +1,7 @@ import {relative} from 'node:path' -import {Option} from '@roots/bud-support/clipanion' import BudCommand from '@roots/bud/cli/commands' +import {Option} from '@roots/bud-support/clipanion' export class BudTailwindCommand extends BudCommand { /** diff --git a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx index 85c40a3f42..8914f23a32 100644 --- a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx +++ b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.check.command.tsx @@ -1,5 +1,5 @@ -import {bind} from '@roots/bud-support/decorators/bind' import BudCommand from '@roots/bud/cli/commands' +import {bind} from '@roots/bud-support/decorators/bind' /** * Bud ts check command diff --git a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx index e607ef1a7d..4b3a1b7f6d 100644 --- a/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx +++ b/sources/@roots/bud-typescript/src/bud/commands/bud.ts.command.tsx @@ -1,5 +1,5 @@ -import {Command, Option} from '@roots/bud-support/clipanion' import BudCommand from '@roots/bud/cli/commands' +import {Command, Option} from '@roots/bud-support/clipanion' /** * Bud tsc command diff --git a/sources/@roots/bud/src/cli/app.tsx b/sources/@roots/bud/src/cli/app.tsx index 269a0a88a4..2b51c99729 100644 --- a/sources/@roots/bud/src/cli/app.tsx +++ b/sources/@roots/bud/src/cli/app.tsx @@ -2,13 +2,6 @@ import type {BaseContext, CommandClass} from '@roots/bud-support/clipanion' import {exit, stderr, stdin, stdout} from 'node:process' -import {Error} from '@roots/bud-dashboard/components/error' -import {Builtins, Cli} from '@roots/bud-support/clipanion' -import {BudError} from '@roots/bud-support/errors' -import {render} from '@roots/bud-support/ink' -import isFunction from '@roots/bud-support/lodash/isFunction' -import isUndefined from '@roots/bud-support/lodash/isUndefined' -import * as args from '@roots/bud-support/utilities/args' import BudCommand from '@roots/bud/cli/commands' import BudBuildCommand from '@roots/bud/cli/commands/build' import BudBuildDevelopmentCommand from '@roots/bud/cli/commands/build/development' @@ -21,6 +14,13 @@ import BudViewCommand from '@roots/bud/cli/commands/view' import BudWebpackCommand from '@roots/bud/cli/commands/webpack' import {Finder} from '@roots/bud/cli/finder' import getContext, {type Context} from '@roots/bud/context' +import {Error} from '@roots/bud-dashboard/components/error' +import {Builtins, Cli} from '@roots/bud-support/clipanion' +import {BudError} from '@roots/bud-support/errors' +import {render} from '@roots/bud-support/ink' +import isFunction from '@roots/bud-support/lodash/isFunction' +import isUndefined from '@roots/bud-support/lodash/isUndefined' +import * as args from '@roots/bud-support/utilities/args' /** * Error handler diff --git a/sources/@roots/bud/src/cli/commands/build/index.tsx b/sources/@roots/bud/src/cli/commands/build/index.tsx index 28abc3b7ac..a160a14119 100644 --- a/sources/@roots/bud/src/cli/commands/build/index.tsx +++ b/sources/@roots/bud/src/cli/commands/build/index.tsx @@ -1,4 +1,3 @@ -import isBoolean from '@roots/bud-support/lodash/isBoolean' import BudCommand from '@roots/bud/cli/commands' import ci from '@roots/bud/cli/flags/ci' import clean from '@roots/bud/cli/flags/clean' @@ -26,6 +25,7 @@ import silent from '@roots/bud/cli/flags/silent' import splitChunks from '@roots/bud/cli/flags/splitChunks' import storage from '@roots/bud/cli/flags/storage' import verbose from '@roots/bud/cli/flags/verbose' +import isBoolean from '@roots/bud-support/lodash/isBoolean' /** * `bud build` command diff --git a/sources/@roots/bud/src/cli/commands/build/production/index.tsx b/sources/@roots/bud/src/cli/commands/build/production/index.tsx index 56d59fe88b..fcc92ff5d1 100644 --- a/sources/@roots/bud/src/cli/commands/build/production/index.tsx +++ b/sources/@roots/bud/src/cli/commands/build/production/index.tsx @@ -1,7 +1,7 @@ import type mode from '@roots/bud/cli/flags/mode' -import {Command} from '@roots/bud-support/clipanion' import BuildCommand from '@roots/bud/cli/commands/build' +import {Command} from '@roots/bud-support/clipanion' /** * `bud build production` command diff --git a/sources/@roots/bud/src/cli/commands/clean/index.tsx b/sources/@roots/bud/src/cli/commands/clean/index.tsx index 2d33fef2f3..f889c5522c 100644 --- a/sources/@roots/bud/src/cli/commands/clean/index.tsx +++ b/sources/@roots/bud/src/cli/commands/clean/index.tsx @@ -1,9 +1,9 @@ import type {Bud} from '@roots/bud' +import BudCommand from '@roots/bud/cli/commands' import {Command, Option} from '@roots/bud-support/clipanion' import {bind} from '@roots/bud-support/decorators/bind' import {Box, Text} from '@roots/bud-support/ink' -import BudCommand from '@roots/bud/cli/commands' /** * `bud clean` diff --git a/sources/@roots/bud/src/cli/commands/doctor/index.tsx b/sources/@roots/bud/src/cli/commands/doctor/index.tsx index 41e62e734f..83ab83f5c9 100644 --- a/sources/@roots/bud/src/cli/commands/doctor/index.tsx +++ b/sources/@roots/bud/src/cli/commands/doctor/index.tsx @@ -5,6 +5,7 @@ import type {InspectTreeResult} from 'fs-jetpack/types.js' import {platform} from 'node:os' +import BudCommand from '@roots/bud/cli/commands' import {Error} from '@roots/bud-dashboard/components/error' import {Command} from '@roots/bud-support/clipanion' import {bind} from '@roots/bud-support/decorators/bind' @@ -13,7 +14,6 @@ import figures from '@roots/bud-support/figures' import {Box, Text} from '@roots/bud-support/ink' import prettyFormat from '@roots/bud-support/pretty-format' import webpack from '@roots/bud-support/webpack' -import BudCommand from '@roots/bud/cli/commands' import {WinError} from './WinError.js' diff --git a/sources/@roots/bud/src/cli/commands/index.tsx b/sources/@roots/bud/src/cli/commands/index.tsx index 5526c2d18c..dcb35a8957 100644 --- a/sources/@roots/bud/src/cli/commands/index.tsx +++ b/sources/@roots/bud/src/cli/commands/index.tsx @@ -5,16 +5,6 @@ import type {ExecaReturnValue} from '@roots/bud-support/execa' import {join, parse} from 'node:path' import {env, exit} from 'node:process' -import {Bud} from '@roots/bud-framework' -import chalk from '@roots/bud-support/chalk' -import {Command, Option} from '@roots/bud-support/clipanion' -import {bind} from '@roots/bud-support/decorators/bind' -import {BudError} from '@roots/bud-support/errors' -import figures from '@roots/bud-support/figures' -import {Box, render, Static} from '@roots/bud-support/ink' -import isNumber from '@roots/bud-support/lodash/isNumber' -import logger from '@roots/bud-support/logger' -import args from '@roots/bud-support/utilities/args' import basedir from '@roots/bud/cli/flags/basedir' import cache from '@roots/bud/cli/flags/cache' import color from '@roots/bud/cli/flags/color' @@ -31,6 +21,16 @@ import use from '@roots/bud/cli/flags/use' import verbose from '@roots/bud/cli/flags/verbose' import {isset} from '@roots/bud/cli/helpers/isset' import * as instance from '@roots/bud/instance' +import {Bud} from '@roots/bud-framework' +import chalk from '@roots/bud-support/chalk' +import {Command, Option} from '@roots/bud-support/clipanion' +import {bind} from '@roots/bud-support/decorators/bind' +import {BudError} from '@roots/bud-support/errors' +import figures from '@roots/bud-support/figures' +import {Box, render, Static} from '@roots/bud-support/ink' +import isNumber from '@roots/bud-support/lodash/isNumber' +import logger from '@roots/bud-support/logger' +import args from '@roots/bud-support/utilities/args' import * as Fallback from '../components/Error.js' import {Menu} from '../components/Menu.js' @@ -78,6 +78,16 @@ export default class BudCommand extends Command { examples: [[`Interactive menu of available subcommands`, `$0`]], }) + /** + * Render static + */ + public static renderStatic(...children: Array) { + return render( + + {(child, id) => {child}} + , + ).unmount() + } public basedir = basedir public declare bud?: Bud | undefined @@ -108,17 +118,6 @@ export default class BudCommand extends Command { public verbose: typeof verbose = false - /** - * Render static - */ - public static renderStatic(...children: Array) { - return render( - - {(child, id) => {child}} - , - ).unmount() - } - /** * Execute arbitrary sh command with inherited stdio */ diff --git a/sources/@roots/bud/src/cli/commands/repl/index.tsx b/sources/@roots/bud/src/cli/commands/repl/index.tsx index 495deaa148..0e91922f58 100644 --- a/sources/@roots/bud/src/cli/commands/repl/index.tsx +++ b/sources/@roots/bud/src/cli/commands/repl/index.tsx @@ -1,9 +1,9 @@ +import BudCommand from '@roots/bud/cli/commands' +import indent from '@roots/bud/cli/flags/indent' import {bind} from '@roots/bud-framework/extension/decorators' import {Command, Option} from '@roots/bud-support/clipanion' import {render} from '@roots/bud-support/ink' import logger from '@roots/bud-support/logger' -import BudCommand from '@roots/bud/cli/commands' -import indent from '@roots/bud/cli/flags/indent' /** * {@link BudCommand} diff --git a/sources/@roots/bud/src/cli/commands/upgrade/index.tsx b/sources/@roots/bud/src/cli/commands/upgrade/index.tsx index 4cdabb084f..c51a6c906d 100644 --- a/sources/@roots/bud/src/cli/commands/upgrade/index.tsx +++ b/sources/@roots/bud/src/cli/commands/upgrade/index.tsx @@ -1,9 +1,10 @@ +import BudCommand from '@roots/bud/cli/commands' import {Command, Option} from '@roots/bud-support/clipanion' import {bind} from '@roots/bud-support/decorators/bind' import {BudError} from '@roots/bud-support/errors' +import logger from '@roots/bud-support/logger' import {isLiteral, isOneOf} from '@roots/bud-support/typanion' import whichPm from '@roots/bud-support/which-pm' -import BudCommand from '@roots/bud/cli/commands' /** * `bud upgrade` command @@ -32,7 +33,9 @@ export default class BudUpgradeCommand extends BudCommand { This command is a passthrough to the package manager you are using. `, examples: [ - [`Upgrade all bud dependencies to latest version`, `$0 upgrade`], + [`Upgrade all bud dependencies to latest`, `$0 upgrade`], + [`Upgrade all bud dependencies to version 6.15.2`, `$0 upgrade 6.15.2`], + [`Upgrade all bud dependencies to version 6.15.2 using yarn-classic`, `$0 upgrade 6.15.2 --pm yarn-classic`], ], }) @@ -42,7 +45,7 @@ export default class BudUpgradeCommand extends BudCommand { * Package manager option */ public pm = Option.String(`--pm`, undefined, { - description: `Package manager to use. One of: npm, yarn, yarn-classic, pnpm`, + description: `Package manager to use. One of: \`npm\`, \`yarn-classic\`, \`pnpm\` (experimental), or \`yarn\` (experimental)`, validator: isOneOf([ isLiteral(`npm`), isLiteral(`pnpm`), @@ -54,9 +57,13 @@ export default class BudUpgradeCommand extends BudCommand { /** * Registry option */ - public registry = Option.String(`--registry`, undefined, { - description: `custom registry`, - }) + public registry = Option.String( + `--registry`, + `https://registry.npmjs.org`, + { + description: `custom registry`, + }, + ) /** * Version option @@ -88,13 +95,23 @@ export default class BudUpgradeCommand extends BudCommand { }) } - if (this.pm === `yarn` && this.registry) { - throw new BudError( - `Yarn berry does not support custom registries set via --registry. Add your desired registry to .yarnrc.yml as per yarn's documentation.`, + if (this.pm === `yarn`) { + if (this.registry !== `https://registry.npmjs.org`) { + logger.warn( + `Yarn berry does not support custom registries set by CLI. Ignoring registry flag. Set your custom registry in \`.yarnrc.yml\``, + ) + this.registry = `https://registry.npmjs.org` + } + + const yarnrc = await this.bud.fs.yml.read( + this.bud.path(`.yarnrc.yml`), ) + if (yarnrc?.[`npmRegistryServer`]) { + this.registry = yarnrc[`npmRegistryServer`] + } } - this.command = [`npm`, `pnpm`].includes(this.pm) ? `install` : `add` + this.command = this.pm === `npm` ? `install` : `add` if (!this.version) { const get = await import(`@roots/bud-support/axios`) @@ -128,6 +145,15 @@ export default class BudUpgradeCommand extends BudCommand { ...this.getFlags(`dependencies`), ]).catch(this.catch) } + + if (this.pm === `pnpm`) { + await this.$(`pnpm`, [`install`, `--shamefully-hoist`]) + } + + /** + * Clean old caches and distributables + */ + await this.$(this.pm, [`bud`, `clean`]) } @bind @@ -143,23 +169,17 @@ export default class BudUpgradeCommand extends BudCommand { public getFlags(type: `dependencies` | `devDependencies`) { const flags = [] - if (this.registry) flags.push(`--registry`, this.registry) - if (type === `devDependencies`) { switch (this.pm) { case `npm`: - flags.push(`--save-dev`) + flags.push(`--save-dev`, `--registry`, this.registry) break case `pnpm`: - flags.push(`--save-dev`, `--public-hoist-pattern="*"`) + flags.push(`--save-dev`, `--registry`, this.registry) break - case `yarn`: - flags.push(`--dev`) - break - - case `yarn-classic`: + default: flags.push(`--dev`) break } @@ -168,11 +188,18 @@ export default class BudUpgradeCommand extends BudCommand { if (type === `dependencies`) switch (this.pm) { case `npm`: - flags.push(`--save`) + flags.push(`--save`, `--registry`, this.registry) break case `pnpm`: - flags.push(`--save-prod`, `--public-hoist-pattern="*"`) + flags.push(`--save-prod`, `--registry`, this.registry) + break + + case `yarn-classic`: + flags.push(`--registry`, this.registry) + break + + case `yarn`: break } diff --git a/sources/@roots/bud/src/cli/commands/view/index.tsx b/sources/@roots/bud/src/cli/commands/view/index.tsx index 1a2a69197a..eac4248950 100644 --- a/sources/@roots/bud/src/cli/commands/view/index.tsx +++ b/sources/@roots/bud/src/cli/commands/view/index.tsx @@ -1,17 +1,16 @@ +import BudCommand from '@roots/bud/cli/commands' +import indent from '@roots/bud/cli/flags/indent' import {Command, Option} from '@roots/bud-support/clipanion' import {highlight} from '@roots/bud-support/highlight' import {Box, Text} from '@roots/bud-support/ink' import get from '@roots/bud-support/lodash/get' import format from '@roots/bud-support/pretty-format' -import BudCommand from '@roots/bud/cli/commands' -import indent from '@roots/bud/cli/flags/indent' /** * `bud view` command */ export default class BudViewCommand extends BudCommand { public static override paths = [[`view`]] - public static override usage = Command.Usage({ category: `debug`, description: `Explore bud object`, diff --git a/sources/@roots/bud/src/cli/commands/webpack/index.tsx b/sources/@roots/bud/src/cli/commands/webpack/index.tsx index 55ceee6a67..7a6ee78a5e 100644 --- a/sources/@roots/bud/src/cli/commands/webpack/index.tsx +++ b/sources/@roots/bud/src/cli/commands/webpack/index.tsx @@ -1,18 +1,11 @@ -import {Command, Option} from '@roots/bud-support/clipanion' import BudCommand from '@roots/bud/cli/commands' +import {Command, Option} from '@roots/bud-support/clipanion' /** * `bud webpack` command */ export default class BudWebpackCommand extends BudCommand { - /** - * {@link Command.paths} - */ public static override paths = [[`webpack`]] - - /** - * {@link Command.usage} - */ public static override usage = Command.Usage({ category: `tool`, description: `Webpack CLI passthrough`, diff --git a/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts b/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts index 211e224d80..f1e3c54750 100644 --- a/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts +++ b/sources/@roots/entrypoints-webpack-plugin/src/plugin.ts @@ -28,6 +28,27 @@ const hookMap = new WeakMap() * ``` */ export class EntrypointsWebpackPlugin { + /** + * Compilation hooks + * + * @param compilation + * @returns + */ + public static getCompilationHooks( + compilation: Webpack.Compilation, + ): CompilationHooks { + let hooks = hookMap.get(compilation) + + if (hooks === undefined) { + hooks = { + compilation: new SyncHook([`compilation`]), + entrypoints: new SyncWaterfallHook([`entrypoints`]), + } + hookMap.set(compilation, hooks) + } + + return hooks + } /** * Collected assets */ @@ -64,28 +85,6 @@ export class EntrypointsWebpackPlugin { this.apply = this.apply.bind(this) } - /** - * Compilation hooks - * - * @param compilation - * @returns - */ - public static getCompilationHooks( - compilation: Webpack.Compilation, - ): CompilationHooks { - let hooks = hookMap.get(compilation) - - if (hooks === undefined) { - hooks = { - compilation: new SyncHook([`compilation`]), - entrypoints: new SyncWaterfallHook([`entrypoints`]), - } - hookMap.set(compilation, hooks) - } - - return hooks - } - public addToManifest({ ident, path, diff --git a/sources/@roots/wordpress-theme-json-webpack-plugin/src/plugin.ts b/sources/@roots/wordpress-theme-json-webpack-plugin/src/plugin.ts index 583572dafb..6c876cbada 100644 --- a/sources/@roots/wordpress-theme-json-webpack-plugin/src/plugin.ts +++ b/sources/@roots/wordpress-theme-json-webpack-plugin/src/plugin.ts @@ -19,22 +19,6 @@ const hookMap = new WeakMap() * ThemeJSONWebpackPlugin */ export class ThemeJsonWebpackPlugin implements WebpackPluginInstance { - /** - * Plugin data - */ - public data: Record = { - __generated__: `⚠️ This file is generated. Do not edit.`, - $schema: `https://schemas.wp.org/trunk/theme.json`, - version: 2, - } - - /** - * Class constructor - * - * @param options - Plugin options - */ - public constructor(public options: Options) {} - /** * {@see https://webpack.js.org/api/compilation-hooks/} */ @@ -53,6 +37,21 @@ export class ThemeJsonWebpackPlugin implements WebpackPluginInstance { return hooks } + /** + * Plugin data + */ + public data: Record = { + __generated__: `⚠️ This file is generated. Do not edit.`, + $schema: `https://schemas.wp.org/trunk/theme.json`, + version: 2, + } + + /** + * Class constructor + * + * @param options - Plugin options + */ + public constructor(public options: Options) {} /** * {@link WebpackPluginInstance.apply} diff --git a/sources/create-bud-app/src/commands/create.ts b/sources/create-bud-app/src/commands/create.ts index 8f0c56429a..060fdf950d 100644 --- a/sources/create-bud-app/src/commands/create.ts +++ b/sources/create-bud-app/src/commands/create.ts @@ -32,7 +32,7 @@ import supportFlag from '../flags/support.js' import usernameFlag from '../flags/username.js' import versionFlag from '../flags/version.js' import wordpressPresetFlag from '../flags/wordpress.js' -import extensionsMap from '../mappedExtensions.js' +import supportKeyMap from '../mappedExtensions.js' import createConfirmPrompt from '../prompts/confirmExisting.js' import createHtmlPrompt from '../prompts/html.js' import createPackageManagerPrompt from '../prompts/packageManager.js' @@ -55,6 +55,7 @@ import writeSrcTask from '../tasks/write.src.js' import writeStylelintConfigTask from '../tasks/write.stylelint.config.js' import writeTailwindConfigTask from '../tasks/write.tailwind.config.js' import writeTsConfigTask from '../tasks/write.tsconfig.js' +import writeTypes from '../tasks/write.types.js' import writeYarnLockfile from '../tasks/write.yarn.lock.js' import writeYarnRCTask from '../tasks/write.yarnrc.js' import yarnVersionTask from '../tasks/yarn-version.js' @@ -154,50 +155,169 @@ export default class CreateCommand extends Command { ], }) - public confirmExisting = confirmExistingFlag - - public customize = customizeFlag - - public cwd = cwdFlag + private _confirmExisting = confirmExistingFlag + private _customize = customizeFlag + private _cwd = cwdFlag + private _dependencies = dependenciesFlag + private _description = descriptionFlag + private _devDependencies = devDependenciesFlag + private _html = htmlFlag + private _interactive = interactiveFlag + private _license = licenseFlag + private _name = nameFlag + private _overwrite = overwriteFlag + private _packageManager = packageManagerFlag + private _react = reactPresetFlag + private _recommended = recommendedPresetFlag + private _relativePath = Option.String({required: false}) + private _support = supportFlag + private _username? = usernameFlag + private _version? = versionFlag + private _wordpress = wordpressPresetFlag + + public declare fs: Filesystem + public files: string[] = [] + + public get customize() { + return this._customize + } + public set customize(customize: boolean) { + this._customize = customize + } - public dependencies = dependenciesFlag + public get cwd() { + return this._cwd + } + public set cwd(cwd: string) { + this._cwd = cwd + } - public description = descriptionFlag + public get dependencies() { + return this._dependencies + } + public set dependencies(dependencies: Array) { + this._dependencies = dependencies + } - public devDependencies = devDependenciesFlag + public get description() { + return this._description + } + public set description(description: string) { + this._description = description + } - public extensions = extensionsMap + public get devDependencies() { + return [...this._devDependencies, ...this.support.map(k => supportKeyMap[k])] + } + public set devDependencies(devDependencies: Array) { + this._devDependencies = devDependencies + } - public files: Array = [] + public get html() { + return this._html + } + public set html(html: typeof this._html) { + this._html = html + } - public fs: Filesystem + public get interactive() { + return this._interactive + } + public set interactive(interactive: boolean) { + this._interactive = interactive + } - public html = htmlFlag + public get license() { + return this._license + } + public set license(license: string) { + this._license = license + } - public interactive = interactiveFlag + public get name() { + return this._name + } + public set name(name: string) { + this._name = name + } - public license = licenseFlag + public get overwrite() { + return this._overwrite + } + public set overwrite(overwrite: boolean) { + this._overwrite = overwrite + } - public name = nameFlag + public get packageManager() { + return this._packageManager + } + public set packageManager(packageManager: typeof this._packageManager) { + this._packageManager = packageManager + } - public overwrite = overwriteFlag + public get react() { + return this._react + } + public set react(react: boolean) { + this._react = react + } - public packageManager = packageManagerFlag + public get recommended() { + return this._recommended + } + public set recommended(recommended: boolean) { + this._recommended = recommended + } - public react = reactPresetFlag + public get relativePath() { + return this._relativePath + } + public set relativePath(relativePath: string) { + this._relativePath = relativePath + } - public recommended = recommendedPresetFlag + public get support() { + return this._support + } + public set support(support: typeof this._support) { + this._support = support + } - public relativePath = Option.String({required: false}) + public get username() { + return this._username + } + public set username(username: string) { + this._username = username + } - public support = supportFlag + public get version() { + return this._version + } + public set version(version: string) { + this._version = version + } - public username? = usernameFlag + public get wordpress() { + return this._wordpress + } + public set wordpress(wordpress: boolean) { + this._wordpress = wordpress + } - public version? = versionFlag + public addDevDependencies(...devDependencies: Array): this { + this._devDependencies.push(...devDependencies) + return this + } - public wordpress = wordpressPresetFlag + public addDependencies(...dependencies: Array): this { + this._dependencies.push(...dependencies) + return this + } + public addSupport(...support: Array): this { + this._support.push(...support) + return this + } /** * CLI after */ @@ -225,7 +345,9 @@ export default class CreateCommand extends Command { let buildMessage = `When you are ready to deploy, run` if ([`yarn classic`, `yarn`].includes(this.packageManager)) { - buildMessage = `${buildMessage} ${chalk.blueBright(`yarn bud build`)}` + buildMessage = `${buildMessage} ${chalk.blueBright( + `yarn bud build`, + )}` } if (this.packageManager === `pnpm`) { buildMessage = `${buildMessage} ${chalk.blueBright( @@ -233,9 +355,7 @@ export default class CreateCommand extends Command { )}` } if (this.packageManager === `npm`) { - buildMessage = `${buildMessage} ${chalk.blueBright( - `npx bud build`, - )}` + buildMessage = `${buildMessage} ${chalk.blueBright(`npx bud build`)}` } messages.push( @@ -273,18 +393,18 @@ export default class CreateCommand extends Command { } if (this.wordpress) { - this.support.push(`wordpress`, `swc`, `postcss`) + this.addSupport(`wordpress`, `swc`, `postcss`) this.html = false if (!this.customize) this.interactive = false } if (this.recommended) { - this.support.push(`swc`, `postcss`) + this.addSupport(`swc`, `postcss`) if (!this.customize) this.interactive = false } if (this.react) { - this.support.push(`swc`, `postcss`, `react`) + this.addSupport(`swc`, `postcss`, `react`) if (!this.customize) this.interactive = false } @@ -329,7 +449,9 @@ export default class CreateCommand extends Command { throw new Error() } } - + public get confirmExisting() { + return this._confirmExisting + } /** * Path to root of `create-bud-app` */ @@ -339,7 +461,6 @@ export default class CreateCommand extends Command { join(dirname(fileURLToPath(import.meta.url)), `..`, `..`), ) } - /** * Create spinner instance */ @@ -351,7 +472,7 @@ export default class CreateCommand extends Command { } /** - * Directory path + * @readonly */ public get directory() { return normalize(join(this.cwd, this.relativePath ?? ``)) @@ -383,7 +504,6 @@ export default class CreateCommand extends Command { public exists(...args: Array) { return args.some(arg => this.files.some(file => file.includes(arg))) } - /** * Project has an emotion compatible compiler selected for install */ @@ -408,11 +528,11 @@ export default class CreateCommand extends Command { this.packageManager = await createPackageManagerPrompt(this).run() - this.support.push(...(await createEnvPrompt(this).run())) - this.support.push(...(await createJsSupportPrompt(this).run())) - this.support.push(...(await createCssSupportPrompt(this).run())) - this.support.push(...(await createComponentsSupportPrompt(this).run())) - this.support.push(...(await createTestSupportPrompt(this).run())) + this.addSupport(...(await createEnvPrompt(this).run())) + this.addSupport(...(await createJsSupportPrompt(this).run())) + this.addSupport(...(await createCssSupportPrompt(this).run())) + this.addSupport(...(await createComponentsSupportPrompt(this).run())) + this.addSupport(...(await createTestSupportPrompt(this).run())) this.html = await createHtmlPrompt(this).run() } @@ -421,9 +541,10 @@ export default class CreateCommand extends Command { */ public async runTasks() { if (this.support.includes(`react`)) { - this.dependencies.push(`react`, `react-dom`) + this.addDependencies(`react`, `react-dom`) + if (!this.hasReactCompatibleCompiler) { - this.support.push(`swc`) + this.addSupport(`swc`) this.createSpinner().warn( `A compatible JS compiler is required for React. Adding swc (@roots/bud-swc).\n`, ) @@ -431,9 +552,10 @@ export default class CreateCommand extends Command { } if (this.support.includes(`tailwindcss`)) { - this.dependencies.push(`tailwindcss`) + this.addDependencies(`tailwindcss`) + if (!this.support.includes(`postcss`)) { - this.support.push(`postcss`) + this.addSupport(`postcss`) this.createSpinner().warn( `PostCSS is required for TailwindCSS. Adding postcss (@roots/bud-postcss).\n`, ) @@ -442,7 +564,8 @@ export default class CreateCommand extends Command { if (this.support.includes(`emotion`)) { if (!this.hasEmotionCompatibleCompiler) { - this.support.push(`swc`) + this.addSupport(`swc`) + this.createSpinner().warn( `A compatible JS compiler is required for Emotion. Adding swc (@roots/bud-swc).\n`, ) @@ -450,17 +573,17 @@ export default class CreateCommand extends Command { } if (this.support.includes(`vue`)) { - this.dependencies.push(`vue`) + this.addDependencies(`vue`) } if (this.support.includes(`eslint`)) { - this.devDependencies.push(`@roots/eslint-config`) + this.addDevDependencies(`@roots/eslint-config`) if ( this.support.includes(`swc`) && !this.support.includes(`typescript`) ) { - this.devDependencies.push(`typescript`) + this.addDevDependencies(`typescript`) } } @@ -471,6 +594,7 @@ export default class CreateCommand extends Command { await writeYarnLockfile(this) await writeGitignoreConfigTask(this) await writeTsConfigTask(this) + await writeTypes(this) await writeConfigTask(this) await writeSrcTask(this) await writeStylelintConfigTask(this) diff --git a/sources/create-bud-app/src/flags/support.ts b/sources/create-bud-app/src/flags/support.ts index 4fcdb6a609..5eeb3ca732 100644 --- a/sources/create-bud-app/src/flags/support.ts +++ b/sources/create-bud-app/src/flags/support.ts @@ -5,19 +5,19 @@ export default Option.Array(`--support,-s`, [], { description: `Support for various components`, validator: isArray( isOneOf([ - isLiteral(`swc`), - isLiteral(`typescript`), isLiteral(`babel`), isLiteral(`emotion`), - isLiteral(`sass`), + isLiteral(`eslint`), isLiteral(`postcss`), - isLiteral(`tailwindcss`), - isLiteral(`wordpress`), + isLiteral(`prettier`), isLiteral(`react`), - isLiteral(`vue`), - isLiteral(`eslint`), + isLiteral(`sass`), isLiteral(`stylelint`), - isLiteral(`prettier`), + isLiteral(`swc`), + isLiteral(`tailwindcss`), + isLiteral(`typescript`), + isLiteral(`vue`), + isLiteral(`wordpress`), ]), ), }) diff --git a/sources/create-bud-app/src/prompts/packageManager.ts b/sources/create-bud-app/src/prompts/packageManager.ts index 234bdb7b02..bf849af27c 100644 --- a/sources/create-bud-app/src/prompts/packageManager.ts +++ b/sources/create-bud-app/src/prompts/packageManager.ts @@ -6,10 +6,10 @@ import type CreateCommand from '../commands/create.js' const choices = { npm: {name: `npm`, value: `npm`}, - pnpm: {name: `pnpm`, value: `pnpm`}, - yarn: {name: `yarn`, value: `yarn`}, // eslint-disable-next-line perfectionist/sort-objects [`yarn-classic`]: {name: `yarn classic`, value: `yarn classic`}, + pnpm: {name: `pnpm (experimental)`, value: `pnpm`}, + yarn: {name: `yarn (experimental)`, value: `yarn`}, } export default (command: CreateCommand) => diff --git a/sources/create-bud-app/src/tasks/build.ts b/sources/create-bud-app/src/tasks/build.ts index 0291a5d3c2..e240d9a79a 100644 --- a/sources/create-bud-app/src/tasks/build.ts +++ b/sources/create-bud-app/src/tasks/build.ts @@ -7,19 +7,15 @@ export default async function buildTask(command: CreateCommand) { try { switch (command.packageManager) { case `pnpm`: - await command.sh(`pnpm`, [`bud`, `build`, `production`]) + await command.sh(`pnpm`, [`bud`, `build`, `--force`, `--no-cache`]) break case `npm`: - await command.sh(`npx`, [`bud`, `build`, `production`]) + await command.sh(`npx`, [`bud`, `build`, `--force`, `--no-cache`]) break - case `yarn classic`: - await command.sh(`yarn`, [`bud`, `build`, `production`]) - break - - case `yarn`: - await command.sh(`yarn`, [`bud`, `build`, `production`]) + default: + await command.sh(`yarn`, [`bud`, `build`, `--force`, `--no-cache`]) break } } catch (error) { diff --git a/sources/create-bud-app/src/tasks/install.dev.ts b/sources/create-bud-app/src/tasks/install.dev.ts index 57294778b2..26282dd8b7 100644 --- a/sources/create-bud-app/src/tasks/install.dev.ts +++ b/sources/create-bud-app/src/tasks/install.dev.ts @@ -6,28 +6,25 @@ export default async function installTask(command: CreateCommand) { spinner.start(`Installing development dependencies...`) try { - const formatSignifier = createSignifierFormatter(command) - const dependencies = new Set([ `@roots/bud`, - ...command.support.map(formatSignifier).filter(Boolean), - ...command.devDependencies.map(formatSignifier).filter(Boolean), + ...command.devDependencies.filter(Boolean), ]) switch (command.packageManager) { case `pnpm`: - await command.sh(`pnpm`, [`install`, ...dependencies, `--save-dev`, `--public-hoist-pattern="*"`]) + await command.sh(`pnpm`, [`add`, ...dependencies, `--save-dev`]) + await command.sh(`pnpm`, [ + `install`, + `--shamefully-hoist`, + ]) break case `npm`: await command.sh(`npm`, [`install`, ...dependencies, `--save-dev`]) break - case `yarn classic`: - await command.sh(`yarn`, [`add`, ...dependencies, `--dev`]) - break - - case `yarn`: + default: await command.sh(`yarn`, [`add`, ...dependencies, `--dev`]) break } @@ -38,20 +35,3 @@ export default async function installTask(command: CreateCommand) { throw error } } - -function createSignifierFormatter( - command: CreateCommand, -): (key: string) => string { - return (key: string) => { - if ( - key?.startsWith(`@roots`) && - !key.split(`/`).pop()?.includes(`@`) - ) { - return `${key}@${command.version}` - } - if (command.extensions[key]) { - return `${command.extensions[key]}@${command.version}` - } - return key - } -} diff --git a/sources/create-bud-app/src/tasks/install.prod.ts b/sources/create-bud-app/src/tasks/install.prod.ts index 7cce8d56cf..bf4c1386b3 100644 --- a/sources/create-bud-app/src/tasks/install.prod.ts +++ b/sources/create-bud-app/src/tasks/install.prod.ts @@ -12,18 +12,18 @@ export default async function installTask(command: CreateCommand) { try { switch (command.packageManager) { case `pnpm`: - await command.sh(`pnpm`, [`install`, ...command.dependencies, `--public-hoist-pattern="*"`]) + await command.sh(`pnpm`, [ + `add`, + ...command.dependencies, + ]) + await command.sh(`pnpm`, [`install`, `--shamefully-hoist`]) break case `npm`: await command.sh(`npm`, [`install`, ...command.dependencies]) break - case `yarn classic`: - await command.sh(`yarn`, [`add`, ...command.dependencies]) - break - - case `yarn`: + default: await command.sh(`yarn`, [`add`, ...command.dependencies]) break } diff --git a/sources/create-bud-app/src/tasks/write.tsconfig.ts b/sources/create-bud-app/src/tasks/write.tsconfig.ts index 01fa357176..78c72b3ed7 100644 --- a/sources/create-bud-app/src/tasks/write.tsconfig.ts +++ b/sources/create-bud-app/src/tasks/write.tsconfig.ts @@ -44,7 +44,7 @@ export default async function writeTsConfig(command: CreateCommand) { }, "types": [${types.reduce((all, type) => `${all} "${type}",`, ``)}] }, - "files": ["./bud.config.ts"], + "files": ["./bud.config.ts", "./types.d.ts"], "include": ["./src"], "exclude": ["./node_modules", "./dist"] } diff --git a/sources/create-bud-app/src/tasks/write.types.ts b/sources/create-bud-app/src/tasks/write.types.ts new file mode 100644 index 0000000000..320451cb33 --- /dev/null +++ b/sources/create-bud-app/src/tasks/write.types.ts @@ -0,0 +1,34 @@ +import {join} from 'node:path' + +import type CreateCommand from '../commands/create.js' + +import templateEngine from '../utilities/templateEngine.js' + +export default async function writeTypes( + command: CreateCommand, +) { + const spinner = command.createSpinner() + spinner.start(`Writing types.d.ts...`) + + if (!command.overwrite && command.exists(`types.d.ts`)) { + return spinner.warn(`types.d.ts already exists. skipping write task.`) + } + + try { + const source = await command.fs.read( + join(command.createRoot, `templates`, `types.d.hbs`), + `utf8`, + ) + + const template = templateEngine.compile(source) + + const result = template({}) + + await command.fs.write(`types.d.ts`, result) + } catch (error) { + spinner.fail() + throw error + } + + spinner.succeed() +} diff --git a/sources/create-bud-app/templates/tsconfig.json.hbs b/sources/create-bud-app/templates/tsconfig.json.hbs deleted file mode 100644 index 26801fe87b..0000000000 --- a/sources/create-bud-app/templates/tsconfig.json.hbs +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extends": "@roots/bud/config/tsconfig.json", - "compilerOptions": { - "allowJs": true, - "paths": { - "@src/*": ["./src/*"] - }, - {{#if types}} - "types": [ - {{#each types}} - "{{this}}", - {{/each}} - ] - {{/if} - }, - "include": ["./bud.config.ts", "./src"], - "exclude": ["./node_modules", "./dist"] -} diff --git a/sources/create-bud-app/templates/types.d.hbs b/sources/create-bud-app/templates/types.d.hbs new file mode 100644 index 0000000000..e2efa8bb12 --- /dev/null +++ b/sources/create-bud-app/templates/types.d.hbs @@ -0,0 +1,4 @@ +declare module '*.@(css|svg|jpg|jpeg|png|gif|webp|avif|woff|woff2|ttf|eot|otf)' { + const content: string; + export default content; +} diff --git a/tests/reproductions/issue-1886.test.ts b/tests/reproductions/issue-1886.test.ts index 21c21e4d51..78e96a5699 100644 --- a/tests/reproductions/issue-1886.test.ts +++ b/tests/reproductions/issue-1886.test.ts @@ -1,6 +1,4 @@ -import {join} from 'node:path' - -import {paths} from '@repo/constants' +import {path} from '@repo/constants' import execa from '@roots/bud-support/execa' import {Filesystem} from '@roots/bud-support/filesystem' import {beforeAll, describe, expect, it} from 'vitest' @@ -11,26 +9,26 @@ describe(`issue-1886`, () => { beforeAll(async () => { fs = new Filesystem() await execa(`yarn`, [`bud`, `clean`], { - cwd: join(paths.tests, `reproductions`, `issue-1886`), + cwd: path(`tests`, `reproductions`, `issue-1886`), }) await execa(`yarn`, [`bud`, `build`], { - cwd: join(paths.tests, `reproductions`, `issue-1886`), + cwd: path(`tests`, `reproductions`, `issue-1886`), }) }) it(`should generate webp from png included in js source`, async () => { const manifest = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1886`, `dist`, `manifest.json`, ), ) - const path = manifest[`images/bud.png?as=webp`] + const targetPath = manifest[`images/bud.png?as=webp`] const image = await fs.read( - join(paths.tests, `reproductions`, `issue-1886`, `dist`, path), + path(`tests`, `reproductions`, `issue-1886`, `dist`, targetPath), `utf8`, ) expect(image.length).toMatchInlineSnapshot(`8377`) @@ -38,8 +36,8 @@ describe(`issue-1886`, () => { it(`should generate webp from png included in css source`, async () => { const manifest = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1886`, `dist`, @@ -47,11 +45,11 @@ describe(`issue-1886`, () => { ), ) - const path = + const targetPath = manifest[`images/bud-css.png?as=webp&width=1200&height=630`] const image = await fs.read( - join(paths.tests, `reproductions`, `issue-1886`, `dist`, path), + path(`tests`, `reproductions`, `issue-1886`, `dist`, targetPath), `utf8`, ) expect(image.length).toMatchInlineSnapshot(`8377`) @@ -59,8 +57,8 @@ describe(`issue-1886`, () => { it(`should inline svg when url appended with ?inline in css source`, async () => { const css = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1886`, `dist`, @@ -76,8 +74,8 @@ describe(`issue-1886`, () => { it(`should inline svg when url appended with ?inline in js source`, async () => { const js = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1886`, `dist`, @@ -93,17 +91,17 @@ describe(`issue-1886`, () => { it.skip(`should work with disk caching`, async () => { await execa(`yarn`, [`bud`, `clean`], { - cwd: join(paths.tests, `reproductions`, `issue-1886`), + cwd: path(`tests`, `reproductions`, `issue-1886`), }) const res1 = await execa(`yarn`, [`bud`, `build`, `--no-clean`], { - cwd: join(paths.tests, `reproductions`, `issue-1886`), + cwd: path(`tests`, `reproductions`, `issue-1886`), }) const res2 = await execa(`yarn`, [`bud`, `build`, `--no-clean`], { - cwd: join(paths.tests, `reproductions`, `issue-1886`), + cwd: path(`tests`, `reproductions`, `issue-1886`), }) const res3 = await execa(`yarn`, [`bud`, `build`, `--no-clean`], { - cwd: join(paths.tests, `reproductions`, `issue-1886`), + cwd: path(`tests`, `reproductions`, `issue-1886`), }) expect([res1.exitCode, res2.exitCode, res3.exitCode]).toEqual( diff --git a/tests/reproductions/issue-1890.test.ts b/tests/reproductions/issue-1890.test.ts index 8aa0415fdf..c1c99a6d67 100644 --- a/tests/reproductions/issue-1890.test.ts +++ b/tests/reproductions/issue-1890.test.ts @@ -1,6 +1,4 @@ -import {join} from 'node:path' - -import {paths} from '@repo/constants' +import {path} from '@repo/constants' import execa from '@roots/bud-support/execa' import {Filesystem} from '@roots/bud-support/filesystem' import {beforeAll, describe, expect, it} from 'vitest' @@ -13,16 +11,16 @@ describe(`issue-1890`, () => { it(`should generate scripts`, async () => { await execa(`yarn`, [`bud`, `clean`, `--silent`], { - cwd: join(paths.tests, `reproductions`, `issue-1890`), + cwd: path(`tests`, `reproductions`, `issue-1890`), }) await execa(`yarn`, [`bud`, `build`, `--silent`], { - cwd: join(paths.tests, `reproductions`, `issue-1890`), + cwd: path(`tests`, `reproductions`, `issue-1890`), }) const normalJs = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1890`, `dist`, @@ -32,8 +30,8 @@ describe(`issue-1890`, () => { `utf8`, ) const simpleJs = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1890`, `dist`, @@ -43,8 +41,8 @@ describe(`issue-1890`, () => { `utf8`, ) const mixedNormalJs = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1890`, `dist`, @@ -54,8 +52,8 @@ describe(`issue-1890`, () => { `utf8`, ) const mixedSimpleJs = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-1890`, `dist`, diff --git a/tests/reproductions/issue-2088.test.ts b/tests/reproductions/issue-2088.test.ts index a8a3e0af5f..c3cf6bc8c8 100644 --- a/tests/reproductions/issue-2088.test.ts +++ b/tests/reproductions/issue-2088.test.ts @@ -1,6 +1,4 @@ -import {join} from 'node:path' - -import {paths} from '@repo/constants' +import {path} from '@repo/constants' import execa from '@roots/bud-support/execa' import {Filesystem} from '@roots/bud-support/filesystem' import {beforeAll, describe, expect, it} from 'vitest' @@ -13,16 +11,16 @@ describe(`issue-2088`, () => { }) it(`should generate app.js`, async () => { await execa(`yarn`, [`bud`, `clean`, `dist`, `storage`], { - cwd: join(paths.tests, `reproductions`, `issue-2088`), + cwd: path(`tests`, `reproductions`, `issue-2088`), }) await execa(`yarn`, [`bud`, `build`, `--minimize`], { - cwd: join(paths.tests, `reproductions`, `issue-2088`), + cwd: path(`tests`, `reproductions`, `issue-2088`), }) const file = await fs.read( - join( - paths.tests, + path( + `tests`, `reproductions`, `issue-2088`, `dist`, diff --git a/yarn.lock b/yarn.lock index c7e1948d2b..43a311e74b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15812,6 +15812,7 @@ __metadata: eslint-plugin-perfectionist: 1.5.1 eslint-plugin-react: 7.33.2 eslint-plugin-react-hooks: 4.6.0 + eslint-plugin-sort-class-members: 1.18.0 execa: 7.2.0 highlight.js: 11.8.0 html-loader: 4.2.0 @@ -20144,6 +20145,15 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-sort-class-members@npm:1.18.0": + version: 1.18.0 + resolution: "eslint-plugin-sort-class-members@npm:1.18.0" + peerDependencies: + eslint: ">=0.8.0" + checksum: aeeccef0c7c68936225558258a3270cafecad9fbed01551d7140bf725512b98dddf881824693457ca602ada07697307de66e63b851aebdc9709c19a721449403 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" From d9f6eb9f08e8cf58304849b73708412c960da53b Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Mon, 4 Sep 2023 13:22:47 -0400 Subject: [PATCH 3/4] --- .../src/bud/commands/bud.eslint.command.tsx | 12 +++- sources/create-bud-app/src/commands/create.ts | 2 +- ...ite.eslint.config.ts => write.eslintrc.ts} | 6 +- .../src/tasks/write.tsconfig.ts | 4 +- ...eslint.config.js.hbs => .eslintrc.cjs.hbs} | 2 +- sources/create-bud-app/templates/types.d.hbs | 66 ++++++++++++++++++- 6 files changed, 80 insertions(+), 12 deletions(-) rename sources/create-bud-app/src/tasks/{write.eslint.config.ts => write.eslintrc.ts} (87%) rename sources/create-bud-app/templates/{eslint.config.js.hbs => .eslintrc.cjs.hbs} (85%) diff --git a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx index 4db86e3096..1b553d26bd 100644 --- a/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx +++ b/sources/@roots/bud-eslint/src/bud/commands/bud.eslint.command.tsx @@ -2,7 +2,7 @@ import BudCommand from '@roots/bud/cli/commands' import {Option} from '@roots/bud-support/clipanion' /** - * Bud eslint command + * {@link BudCommand} */ export class BudEslintCommand extends BudCommand { /** @@ -19,6 +19,7 @@ export class BudEslintCommand extends BudCommand { examples: [[`Run eslint on source files`, `$0 eslint`]], }) + public options = Option.Proxy({name: `eslint passthrough options`}) /** @@ -28,8 +29,13 @@ export class BudEslintCommand extends BudCommand { await this.makeBud() await this.bud.run() - return await this.run([`eslint`, `bin`, `eslint.js`], this.options, [ - this.bud.path(`@src`, `**`, `*.{ts,tsx,js,jsx}`), + const eslintrc = Object.values(this.bud.context.files).find((file) => file.name.includes(`eslintrc`) || file.name.includes(`eslint.config`))?.path + + await this.run([`eslint`, `bin`, `eslint.js`], this.options, [ + `--ext`, + `.js,.jsx,.ts,.tsx`, + ...(eslintrc ? [`--config`, eslintrc]: []), + this.bud.relPath(`@src`), ]) } } diff --git a/sources/create-bud-app/src/commands/create.ts b/sources/create-bud-app/src/commands/create.ts index 060fdf950d..9e3166d601 100644 --- a/sources/create-bud-app/src/commands/create.ts +++ b/sources/create-bud-app/src/commands/create.ts @@ -46,7 +46,7 @@ import buildTask from '../tasks/build.js' import installDevDependenciesTask from '../tasks/install.dev.js' import installProductionDependenciesTask from '../tasks/install.prod.js' import writeConfigTask from '../tasks/write.bud.config.js' -import writeEslintConfigTask from '../tasks/write.eslint.config.js' +import writeEslintConfigTask from '../tasks/write.eslintrc.js' import writeGitignoreConfigTask from '../tasks/write.gitignore.js' import writePackageJSONTask from '../tasks/write.package.js' import writePrettierConfigTask from '../tasks/write.prettier.config.js' diff --git a/sources/create-bud-app/src/tasks/write.eslint.config.ts b/sources/create-bud-app/src/tasks/write.eslintrc.ts similarity index 87% rename from sources/create-bud-app/src/tasks/write.eslint.config.ts rename to sources/create-bud-app/src/tasks/write.eslintrc.ts index 5682433e1d..d491b8b250 100644 --- a/sources/create-bud-app/src/tasks/write.eslint.config.ts +++ b/sources/create-bud-app/src/tasks/write.eslintrc.ts @@ -5,7 +5,7 @@ import type CreateCommand from '../commands/create.js' import formatSource from '../utilities/formatSource.js' import templateEngine from '../utilities/templateEngine.js' -export default async function writeStylelintConfigTask( +export default async function writeEslintConfigTask( command: CreateCommand, ) { if (!command.support.includes(`eslint`)) return @@ -39,7 +39,7 @@ export default async function writeStylelintConfigTask( configExtends.push(`@roots/eslint-config/wordpress`) const source = await command.fs.read( - join(command.createRoot, `templates`, `eslint.config.js.hbs`), + join(command.createRoot, `templates`, `.eslintrc.cjs.hbs`), `utf8`, ) @@ -48,7 +48,7 @@ export default async function writeStylelintConfigTask( const result = template({extends: configExtends}) - await command.fs.write(`eslint.config.js`, await formatSource(result)) + await command.fs.write(`.eslintrc.cjs`, await formatSource(result)) } catch (error) { spinner.fail() throw error diff --git a/sources/create-bud-app/src/tasks/write.tsconfig.ts b/sources/create-bud-app/src/tasks/write.tsconfig.ts index 78c72b3ed7..f612ac7eaa 100644 --- a/sources/create-bud-app/src/tasks/write.tsconfig.ts +++ b/sources/create-bud-app/src/tasks/write.tsconfig.ts @@ -35,8 +35,10 @@ export default async function writeTsConfig(command: CreateCommand) { `@roots/bud-wordpress-manifests`, ) + types.push(`webpack/module`) + const source = `{ - "extends": "@roots/bud/config/jsconfig.json", + "extends": ["@roots/bud/config/tsconfig.json"], "compilerOptions": { "allowJs": true, "paths": { diff --git a/sources/create-bud-app/templates/eslint.config.js.hbs b/sources/create-bud-app/templates/.eslintrc.cjs.hbs similarity index 85% rename from sources/create-bud-app/templates/eslint.config.js.hbs rename to sources/create-bud-app/templates/.eslintrc.cjs.hbs index 6523a88420..6e3ca46bff 100644 --- a/sources/create-bud-app/templates/eslint.config.js.hbs +++ b/sources/create-bud-app/templates/.eslintrc.cjs.hbs @@ -1,4 +1,4 @@ -export default { +module.exports = { root: true, {{#if extends}} extends: [ diff --git a/sources/create-bud-app/templates/types.d.hbs b/sources/create-bud-app/templates/types.d.hbs index e2efa8bb12..55ecfa39eb 100644 --- a/sources/create-bud-app/templates/types.d.hbs +++ b/sources/create-bud-app/templates/types.d.hbs @@ -1,4 +1,64 @@ -declare module '*.@(css|svg|jpg|jpeg|png|gif|webp|avif|woff|woff2|ttf|eot|otf)' { - const content: string; - export default content; +declare module '*.htm' { + const value: string + export default value +} +declare module '*.html' { + const value: string + export default value +} +declare module '*.json' { + const value: Record + export default value +} +declare module '*.yml' { + const value: Record + export default value +} +declare module '*.css' { + const content: string + export default content +} +declare module '*.sass' { + const content: string + export default content +} +declare module '*.scss' { + const content: string + export default content +} +declare module '*.gif' { + const value: string + export default value +} +declare module '*.jpg' { + const value: string + export default value +} +declare module '*.jpeg' { + const value: string + export default value +} +declare module '*.png' { + const value: string + export default value +} +declare module '*.svg' { + const value: string + export default value +} +declare module '*.webp' { + const value: string + export default value +} +declare module '*.mp4' { + const value: string + export default value +} +declare module '*.webm' { + const value: string + export default value +} +declare module '*.woff2' { + const value: string + export default value } From 7fcce39e0686bf2633fc6f7ef21de01c1c806e78 Mon Sep 17 00:00:00 2001 From: Kelly Mears Date: Tue, 5 Sep 2023 01:16:41 -0400 Subject: [PATCH 4/4] --- sources/@roots/bud/src/cli/components/Menu.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/@roots/bud/src/cli/components/Menu.tsx b/sources/@roots/bud/src/cli/components/Menu.tsx index 596742fed7..cb5c6a1303 100644 --- a/sources/@roots/bud/src/cli/components/Menu.tsx +++ b/sources/@roots/bud/src/cli/components/Menu.tsx @@ -24,10 +24,10 @@ export const Menu = ({cli}: {cli: BudCommand[`cli`]}) => { .reduce((acc, cmd, id) => { return [ ...acc, - ...(cmd.examples ?? []).map(([description, path]) => { + cmd.examples?.map(([description, path]) => { return {cmd, description, id, path} - }), - ] + }).shift(), + ].filter(Boolean) }, []), ) }, [defined])