diff --git a/sources/@roots/bud-esbuild/src/features/esbuild-ts.ts b/sources/@roots/bud-esbuild/src/features/esbuild-ts.ts index 66459053fe..9638a10d38 100644 --- a/sources/@roots/bud-esbuild/src/features/esbuild-ts.ts +++ b/sources/@roots/bud-esbuild/src/features/esbuild-ts.ts @@ -9,8 +9,8 @@ export const tsFeature: Extension.Module = { options: async ({path}) => ({ loader: 'tsx', target: 'es2015', - tsconfigRaw: (await pathExists(path('@project', 'tsconfig.json'))) - ? await readJson(path('@project', 'tsconfig.json')) + tsconfigRaw: (await pathExists(path('tsconfig.json'))) + ? await readJson(path('tsconfig.json')) : { compilerOptions: { importsNotUsedAsValues: 'remove', diff --git a/sources/@roots/bud-eslint/src/eslint.options.ts b/sources/@roots/bud-eslint/src/eslint.options.ts index e34a6e4fdb..e33315ce6e 100644 --- a/sources/@roots/bud-eslint/src/eslint.options.ts +++ b/sources/@roots/bud-eslint/src/eslint.options.ts @@ -8,10 +8,10 @@ export interface options { export const options: options = ({path}) => ({ extensions: ['js', 'jsx', 'ts', 'tsx', 'vue'], - cacheLocation: path(`@storage/cache/eslint.json`), + cacheLocation: path('@storage/cache/eslint.json'), cacheStrategy: 'content', - cwd: path('@project'), + cwd: path(), eslintPath: require.resolve('eslint'), - resolvePluginsRelativeTo: path('@project'), + resolvePluginsRelativeTo: path(), threads: cpus.length / 2, }) diff --git a/sources/@roots/bud-framework/src/Framework/methods/make.ts b/sources/@roots/bud-framework/src/Framework/methods/make.ts index 65494fb56f..c961d39c96 100644 --- a/sources/@roots/bud-framework/src/Framework/methods/make.ts +++ b/sources/@roots/bud-framework/src/Framework/methods/make.ts @@ -42,23 +42,25 @@ export async function make( name: string, tap?: (app: Framework) => any, ): Promise { - this as Framework + const app = this as Framework - handleChildNestingError.bind(this)() - this.logger.instance.fav(`new instance:`, name) + handleChildNestingError.bind(app)() + app.logger.instance.fav(`new instance:`, name) - const instance = new this.implementation({ - childOf: this, - config: {...this.options.config, name}, - context: this.context, - services: this.options.services, + const instance = new app.implementation({ + name, + mode: app.mode, + childOf: app, + config: {...app.options.config, name}, + context: app.context, + services: app.options.services, }) await instance.lifecycle() if (tap) await tap(instance) - this.children?.set(name, instance) + app.children?.set(name, instance) - return this + return app } diff --git a/sources/@roots/bud-framework/src/Framework/methods/path.ts b/sources/@roots/bud-framework/src/Framework/methods/path.ts index feee59a4fc..80658221fc 100644 --- a/sources/@roots/bud-framework/src/Framework/methods/path.ts +++ b/sources/@roots/bud-framework/src/Framework/methods/path.ts @@ -1,4 +1,4 @@ -import {join} from 'path' +import {join, resolve, sep as slash} from 'node:path' import {Framework} from '../..' @@ -6,25 +6,65 @@ export interface path { (base?: string, ...segments: Array): string } +/** + * Transform `@alias` path + * + * @param app - Framework instance + * @param base - Path segment + * @returns string + * + * @public + */ const transformShorthandBase = (app: Framework, base: string): string => { - const parts = base.includes('/') ? base.split('/') : [base] - parts[0] = app.hooks.filter(`location.${parts[0]}`) - return parts.join('/') + /** + * If path contains multiple segments, explode into an array + * If path contains one segment, insert it into a blank array + */ + const [ident, ...parts] = base.includes(slash) + ? base.split(slash) + : [base] + + !app.hooks.has(`location.${ident}`) && + app.error( + `\`${ident}\` is not a registered path. It must be defined with bud.setPath`, + ) + + /** + * Replace + */ + const value = app.hooks.filter(`location.${ident}`) + + return join(value, ...(parts ?? []).filter(Boolean)) } export const path: path = function ( base?: string, ...segments: Array ): string { - const ctx = this as Framework + const app = this as Framework + /** + * If no base path was provided return the project directory + */ + if (!base) return app.context.projectDir + + /** + * If base path starts with `/` return the joined path and segments (if any) + */ + if (base.startsWith('/')) + return segments.length ? join(base, ...segments) : base + + /** + * Replace any `@alias` aliases with their corresponding entry + */ + const normalized = base.startsWith(`@`) + ? transformShorthandBase(app, base) + : base - if (!base || base.startsWith(`/`)) return ctx.context.projectDir + const absolutePath = base.startsWith(`/`) + ? normalized + : resolve(app.context.projectDir, normalized) - return join( - ctx.context.projectDir, - ...[ - base.startsWith(`@`) ? transformShorthandBase(ctx, base) : base, - ...(segments ?? []), - ].filter(Boolean), - ) + return segments.length + ? resolve(absolutePath, ...(segments ?? []).filter(Boolean)) + : absolutePath } diff --git a/sources/@roots/bud-framework/src/Framework/methods/setPath.ts b/sources/@roots/bud-framework/src/Framework/methods/setPath.ts index 367d74ae58..12ae513904 100644 --- a/sources/@roots/bud-framework/src/Framework/methods/setPath.ts +++ b/sources/@roots/bud-framework/src/Framework/methods/setPath.ts @@ -10,13 +10,7 @@ const {isString} = lodash * @internal */ export interface setPath { - < - T extends `${Omit< - '@project', - `${keyof Framework.Locations & string}` - > & - string}`, - >( + ( arg1: T | Record, arg2?: string, ): Framework.Framework diff --git a/sources/@roots/bud-react/package.json b/sources/@roots/bud-react/package.json index 7cb5fd1cc8..ef64f51c0e 100644 --- a/sources/@roots/bud-react/package.json +++ b/sources/@roots/bud-react/package.json @@ -97,10 +97,6 @@ "@babel/plugin-syntax-jsx": "^7.16.0", "@babel/preset-react": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.4", - "autobind-decorator": "2.4.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.27.1", - "eslint-plugin-react-hooks": "^4.3.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hot-loader": "^4.13.0", diff --git a/sources/@roots/bud/src/seed.ts b/sources/@roots/bud/src/seed.ts index 65ee266737..0e5695a657 100644 --- a/sources/@roots/bud/src/seed.ts +++ b/sources/@roots/bud/src/seed.ts @@ -188,7 +188,6 @@ export const seed: Partial = { [`build.parallelism`]: app => Math.max(cpus().length - 1, 1), [`build.performance`]: app => ({hints: false}), [`build.resolve.alias`]: app => ({ - '@project': app.path(), '@src': app.path('@src'), '@dist': app.path('@dist'), }), diff --git a/sources/@roots/sage/package.json b/sources/@roots/sage/package.json index 2a697c110c..2d9c2e46e6 100644 --- a/sources/@roots/sage/package.json +++ b/sources/@roots/sage/package.json @@ -45,7 +45,6 @@ ".": "./lib/cjs/index.js", "./client": "./lib/cjs/client/index.js", "./client/dom-ready": "./lib/cjs/client/domReady.js", - "./eslint-config": "./eslint-config/index.js", "./stylelint-config": "./stylelint-config/index.js", "./theme/tailwind": "./lib/cjs/theme/tailwind.adapter.js", "./tsconfig": "./tsconfig/tsconfig.json" @@ -60,14 +59,12 @@ "@roots/bud-api": "workspace:sources/@roots/bud-api", "@roots/bud-framework": "workspace:sources/@roots/bud-framework", "@skypack/package-check": "0.2.2", - "@types/lodash": "4.14.179", "@types/node": "16.11.26", "webpack": "5.68.0" }, "dependencies": { "@roots/bud-preset-wordpress": "workspace:sources/@roots/bud-preset-wordpress", "@roots/bud-support": "workspace:sources/@roots/bud-support", - "helpful-decorators": "^2.1.0", "tslib": "^2.3.1" } } diff --git a/sources/@roots/sage/src/theme/api/themeJson.ts b/sources/@roots/sage/src/theme/api/themeJson.ts index 6bbc6e78e6..695e178349 100644 --- a/sources/@roots/sage/src/theme/api/themeJson.ts +++ b/sources/@roots/sage/src/theme/api/themeJson.ts @@ -28,25 +28,32 @@ export interface facade { (input?: Mutator | WPThemeJson['settings'], raw?: boolean): Framework } +const getContainerOptions = (app: Framework) => + app.extensions.get('wp-theme-json').options + +const getRawOptions = (app: Framework) => + app.extensions.get('wp-theme-json').options.all() + export const method: method = async function ( input?: Mutator | WPThemeJson['settings'], raw?: boolean, ) { - const ctx = this as Framework + const app = this as Framework + + if (!app.extensions.has('wp-theme-json')) return - ctx.extensions.get('wp-theme-json').set('when', true) + app.extensions.get('wp-theme-json').set('when', true) - if (!input) return ctx + if (!input) return app isFunction(input) - ? ctx.extensions + ? app.extensions .get('wp-theme-json') - .setOptions( - raw === true - ? input(ctx.extensions.get('wp-theme-json').options.all()) - : input(ctx.extensions.get('wp-theme-json').options), + .setOption( + 'settings', + raw === true ? getRawOptions(app) : getContainerOptions(app), ) - : ctx.extensions.get('wp-theme-json').setOptions(input) + : app.extensions.get('wp-theme-json').setOption('settings', input) - return ctx + return app } diff --git a/sources/@roots/sage/src/theme/api/useTailwindColors.ts b/sources/@roots/sage/src/theme/api/useTailwindColors.ts index 813caba3fa..7ee20f80dd 100644 --- a/sources/@roots/sage/src/theme/api/useTailwindColors.ts +++ b/sources/@roots/sage/src/theme/api/useTailwindColors.ts @@ -18,7 +18,7 @@ export const method: method = async function () { ctx.extensions .get('wp-theme-json') - .setOption('color.palette', transformPalette(palette)) + .setOption('settings.color.palette', transformPalette(palette)) return ctx } diff --git a/sources/@roots/sage/src/theme/extension.ts b/sources/@roots/sage/src/theme/extension.ts index ebeac4c54d..76dd487f4b 100644 --- a/sources/@roots/sage/src/theme/extension.ts +++ b/sources/@roots/sage/src/theme/extension.ts @@ -5,7 +5,7 @@ import * as useTailwindColors from './api/useTailwindColors' import {Options, ThemeJsonWebpackPlugin} from './plugin' /** - * Extension for managing WordPress `theme.json` values + * Extension for managing WordPress `theme.json` * * @public */ @@ -15,6 +15,7 @@ export interface ThemeExtension Options > { name: 'wp-theme-json' + options: (app: Framework.Framework) => Options api: { themeJson: themeJson.method useTailwindColors: useTailwindColors.method @@ -35,7 +36,7 @@ export const name: ThemeExtension['name'] = 'wp-theme-json' */ export const options: ThemeExtension['options'] = app => ({ path: app.path('theme.json'), - json: { + settings: { color: { custom: false, customGradient: false, @@ -73,7 +74,12 @@ export const api: ThemeExtension['api'] = { export const make: ThemeExtension['make'] = options => new ThemeJsonWebpackPlugin({ path: options.get('path'), - json: options.get('json'), + settings: options.get('settings'), }) +/** + * Extension when + * + * @public + */ export const when: ThemeExtension['when'] = false diff --git a/sources/@roots/sage/src/theme/plugin.ts b/sources/@roots/sage/src/theme/plugin.ts index efd4916fea..d793cba358 100644 --- a/sources/@roots/sage/src/theme/plugin.ts +++ b/sources/@roots/sage/src/theme/plugin.ts @@ -1,5 +1,5 @@ import {ThemeJSON} from '@roots/bud-preset-wordpress' -import {fs} from '@roots/bud-support' +import {bind, fs} from '@roots/bud-support' import {Compiler, WebpackPluginInstance} from 'webpack' /** @@ -11,7 +11,7 @@ export interface Options { /** * JSON contents */ - json: ThemeJSON.JSONSchemaForWordPressBlockThemeGlobalSettingsAndStyles['settings'] + settings: ThemeJSON.JSONSchemaForWordPressBlockThemeGlobalSettingsAndStyles['settings'] /** * Emit path @@ -21,17 +21,74 @@ export interface Options { /** * ThemeJSONWebpackPlugin + * + * @public */ export class ThemeJsonWebpackPlugin implements WebpackPluginInstance { + /** + * theme.json path + * + * @public + */ + public get path(): string { + return this.options.path + } + + /** + * theme.json settings + * + * @public + */ + public get settings(): string { + return JSON.stringify( + { + __generated__: '⚠️ This file is generated. Do not edit.', + $schema: 'https://schemas.wp.org/trunk/theme.json', + version: 2, + settings: this.options.settings, + }, + null, + 2, + ) + } + + /** + * Class constructor + * + * @param options - Plugin options + * + * @public + */ public constructor(public options: Options) {} + /** + * Apply plugin + * + * @param compiler - Webpack compiler + * @returns void + * + * @public + * @decorator `@bind` + */ + @bind public apply(compiler: Compiler) { - compiler.hooks.done.tapPromise(this.constructor.name, async () => { - try { - await fs.writeFile(this.options.path, this.options.json, 'utf8') - } catch (err) { - throw new Error(err) - } - }) + compiler.hooks.done.tapPromise(this.constructor.name, this.done) + } + + /** + * Compiler done + * + * @returns Promise + * + * @public + * @decorator `@bind` + */ + @bind + public async done() { + try { + await fs.writeFile(this.path, this.settings, 'utf8') + } catch (err) { + throw new Error(err) + } } } diff --git a/tests/unit/bud-api/repository/alias.test.ts b/tests/unit/bud-api/repository/alias.test.ts index 54338c6cee..62e4ecd503 100644 --- a/tests/unit/bud-api/repository/alias.test.ts +++ b/tests/unit/bud-api/repository/alias.test.ts @@ -18,7 +18,6 @@ describe('bud.alias', function () { expect(alias).toEqual({ '@dist': bud.path('@dist'), '@src': bud.path('@src'), - '@project': bud.path(), '@foo': bud.path('bar'), }) }) diff --git a/tests/unit/bud-build/config.test.ts b/tests/unit/bud-build/config.test.ts index 1c9b33de3c..2434c91348 100644 --- a/tests/unit/bud-build/config.test.ts +++ b/tests/unit/bud-build/config.test.ts @@ -103,7 +103,6 @@ describe('bud.build.config', function () { it('has expected resolve.alias default', () => { expect(bud.build.config.resolve.alias).toEqual({ '@dist': bud.path('@dist'), - '@project': bud.path(), '@src': bud.path('@src'), }) }) diff --git a/tests/unit/bud-framework/path.test.ts b/tests/unit/bud-framework/path.test.ts index be60d01c59..027b3fb612 100644 --- a/tests/unit/bud-framework/path.test.ts +++ b/tests/unit/bud-framework/path.test.ts @@ -16,19 +16,11 @@ describe('bud.path', function () { expect(bud.path()).toEqual(mockProject.path) }) - it('returns projectDir when passed `project`', () => { - expect(bud.path()).toEqual(mockProject.path) - }) - - it('returns expected projectDir when passed `@project`', () => { - expect(bud.path()).toEqual(mockProject.path) - }) - it('returns expected project relative path', () => { expect(bud.path('foo')).toEqual(join(mockProject.path, 'foo')) }) it('returns expected multipart path', () => { - expect(bud.path('@project', 'foo', 'bar')).toEqual( + expect(bud.path('foo', 'bar')).toEqual( join(mockProject.path, 'foo', 'bar'), ) }) @@ -44,6 +36,6 @@ describe('bud.path', function () { }) it('path: returns correct paths with @ shorthand', () => { - expect(bud.path('@project/foo')).toContain(`util/project/foo`) + expect(bud.path('foo')).toContain(`util/project/foo`) }) }) diff --git a/tests/unit/sage/extension.ts b/tests/unit/sage/extension.ts index 19b6bd8406..001f9b5f05 100644 --- a/tests/unit/sage/extension.ts +++ b/tests/unit/sage/extension.ts @@ -25,7 +25,6 @@ describe('@roots/sage', () => { it(`sets aliases`, () => { expect(bud.hooks.filter('build.resolve.alias')).toBe({ - '@project': bud.path(), '@scripts': bud.path('@src', 'scripts'), '@styles': bud.path('@src', 'styles'), '@images': bud.path('@src', 'images'), diff --git a/tests/util/project/.eslintrc.js b/tests/util/project/.eslintrc.js index cc6568d611..a8047dd8ee 100644 --- a/tests/util/project/.eslintrc.js +++ b/tests/util/project/.eslintrc.js @@ -1,3 +1,3 @@ module.exports = { - extends: [require.resolve('@roots/bud-preset-recommend/eslint-config')], + extends: ['@roots/eslint-config'], } diff --git a/yarn.lock b/yarn.lock index df8f092f81..263f8efcdf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3797,10 +3797,6 @@ __metadata: "@types/node": 16.11.26 "@types/react": 17.0.39 "@types/react-dom": 17.0.13 - autobind-decorator: 2.4.0 - eslint-plugin-jsx-a11y: ^6.5.1 - eslint-plugin-react: ^7.27.1 - eslint-plugin-react-hooks: ^4.3.0 react: ^17.0.2 react-dom: ^17.0.2 react-hot-loader: ^4.13.0 @@ -4343,9 +4339,7 @@ __metadata: "@roots/bud-preset-wordpress": "workspace:sources/@roots/bud-preset-wordpress" "@roots/bud-support": "workspace:sources/@roots/bud-support" "@skypack/package-check": 0.2.2 - "@types/lodash": 4.14.179 "@types/node": 16.11.26 - helpful-decorators: ^2.1.0 tslib: ^2.3.1 webpack: 5.68.0 languageName: unknown