diff --git a/packages/eslint-config/README.md b/packages/eslint-config/README.md index b9ab397..c786ca7 100644 --- a/packages/eslint-config/README.md +++ b/packages/eslint-config/README.md @@ -31,9 +31,14 @@ const eslintSettings = merge(require('@yardinternet/eslint-config'), [ module.exports = eslintSettings; ``` -## brave-root vs theme-root +## Import aliases -The `@sage/scripts` import alias auto-detects where ESLint runs: +Every theme gets an `@/scripts` alias pointing at its own +`resources/scripts`, in both layouts: -- **brave-root** — cwd has `web/app/themes/`. `@sage/scripts` → `web/app/themes/sage/resources/scripts`. -- **theme-root** — cwd is a single theme (has `style.css`, no `web/app/themes/`). `@sage/scripts` and `@/scripts` → `./resources/scripts`. +- **brave-root** — cwd is the project root; aliases resolve per theme, e.g. `@sage/scripts` → `web/app/themes/sage/resources/scripts`. +- **theme-root** — cwd is a single theme (has `style.css`, no themes directory); `@/scripts` → `./resources/scripts`. + +`@sage/scripts` is always available: in projects without a theme named `sage` it +falls back to the default theme, so shared starter code keeps resolving. See +[project layout detection](../shared-utils/README.md#project-layout-detection). diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index fa84151..d9df8e9 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -19,6 +19,7 @@ "license": "ISC", "dependencies": { "@wordpress/eslint-plugin": "^26.0.0", + "@yardinternet/shared-utils": "workspace:*", "eslint-import-resolver-alias": "^1.1.2", "globals": "^17.12.0", "typescript-eslint": "^8.70.0" diff --git a/packages/eslint-config/src/index.js b/packages/eslint-config/src/index.js index aa040ca..404fd25 100644 --- a/packages/eslint-config/src/index.js +++ b/packages/eslint-config/src/index.js @@ -33,7 +33,7 @@ const sharedRules = { module.exports = [ ...wordpress.configs.recommended, { - files: [ '**/*.js', '**/*.jsx' ], + files: [ '**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs' ], languageOptions: { globals: sharedGlobals, }, diff --git a/packages/eslint-config/src/utils/resolve-import-aliases.js b/packages/eslint-config/src/utils/resolve-import-aliases.js index 5adbe8c..ead8571 100644 --- a/packages/eslint-config/src/utils/resolve-import-aliases.js +++ b/packages/eslint-config/src/utils/resolve-import-aliases.js @@ -1,32 +1,51 @@ -const fs = require( 'fs' ); const path = require( 'path' ); +const { tryResolveThemeContext } = require( '@yardinternet/shared-utils' ); /** * Resolves the `import/resolver` aliases for the current project layout. * - * - brave-root (default): the `@sage/scripts` alias, unchanged. - * - theme-root (cwd is a theme — no `web/app/themes`, has `style.css`): point - * both `@/scripts` and the `@sage/scripts` at the local - * `./resources/scripts` so theme imports resolve. + * - brave-root: `@/scripts` for every theme in the themes directory. + * - theme-root (cwd is a theme): `@/scripts` points at the local + * `./resources/scripts`. + * + * `@sage/scripts` is kept in both layouts, aliased to the default theme, so + * starter code importing it keeps resolving in projects that do not name their + * parent theme `sage`. */ const resolveImportAliases = () => { - const cwd = process.cwd(); - const isBraveRoot = fs.existsSync( path.resolve( cwd, 'web/app/themes' ) ); + const context = tryResolveThemeContext(); + + if ( ! context ) { + // Not a WordPress project layout — assume the cwd is the theme. + const themeName = path.basename( process.cwd() ); - if ( isBraveRoot ) { return [ - [ '@sage/scripts', './web/app/themes/sage/resources/scripts' ], + [ `@${ themeName }/scripts`, './resources/scripts' ], + [ '@sage/scripts', './resources/scripts' ], ]; } - const themeName = path.basename( cwd ); + const scriptsPath = ( theme ) => + context.mode === 'theme-root' + ? './resources/scripts' + : `./${ theme.relDirPosix }/resources/scripts`; + + const aliases = context.themes.map( ( theme ) => [ + `@${ theme.name }/scripts`, + scriptsPath( theme ), + ] ); + + if ( ! aliases.some( ( [ alias ] ) => alias === '@sage/scripts' ) ) { + const defaultTheme = context.themes.find( + ( theme ) => theme.name === context.defaultTheme + ); + + if ( defaultTheme ) { + aliases.push( [ '@sage/scripts', scriptsPath( defaultTheme ) ] ); + } + } - return [ - [ `@${ themeName }/scripts`, './resources/scripts' ], - // Brave-root alias kept so shared/starter code importing `@sage/scripts` - // keeps resolving in a theme-root build. - [ '@sage/scripts', './resources/scripts' ], - ]; + return aliases; }; module.exports = resolveImportAliases; diff --git a/packages/eslint-config/tests/__snapshots__/config.snapshot.test.js.snap b/packages/eslint-config/tests/__snapshots__/config.snapshot.test.js.snap index 8f0dd4b..8d9317b 100644 --- a/packages/eslint-config/tests/__snapshots__/config.snapshot.test.js.snap +++ b/packages/eslint-config/tests/__snapshots__/config.snapshot.test.js.snap @@ -2522,6 +2522,8 @@ exports[`eslint config files patterns match snapshot 1`] = ` [ "**/*.js", "**/*.jsx", + "**/*.cjs", + "**/*.mjs", ], [ "**/*.ts", diff --git a/packages/eslint-config/tests/import-aliases.test.js b/packages/eslint-config/tests/import-aliases.test.js new file mode 100644 index 0000000..a43ab05 --- /dev/null +++ b/packages/eslint-config/tests/import-aliases.test.js @@ -0,0 +1,59 @@ +'use strict'; + +const path = require( 'path' ); +const resolveImportAliases = require( '../src/utils/resolve-import-aliases' ); +const { clearThemeContextCache } = require( '@yardinternet/shared-utils' ); + +const FIXTURES = path.resolve( __dirname, '../../shared-utils/tests/fixtures' ); + +const originalCwd = process.cwd(); + +const useProject = ( ...segments ) => { + process.chdir( path.join( FIXTURES, ...segments ) ); + clearThemeContextCache(); +}; + +afterEach( () => { + process.chdir( originalCwd ); + clearThemeContextCache(); +} ); + +test( 'brave-root aliases every theme and keeps @sage/scripts', () => { + useProject( 'bedrock' ); + + expect( resolveImportAliases() ).toEqual( [ + [ '@sage/scripts', './web/app/themes/sage/resources/scripts' ], + [ + '@sage-child/scripts', + './web/app/themes/sage-child/resources/scripts', + ], + ] ); +} ); + +test( '@sage/scripts falls back to the default theme when no sage exists', () => { + useProject( 'configured' ); + + expect( resolveImportAliases() ).toEqual( [ + [ '@alpha/scripts', './src/themes/alpha/resources/scripts' ], + [ '@zulu/scripts', './src/themes/zulu/resources/scripts' ], + [ '@sage/scripts', './src/themes/zulu/resources/scripts' ], + ] ); +} ); + +test( 'theme-root points every alias at the local resources', () => { + useProject( 'theme-root', 'basis' ); + + expect( resolveImportAliases() ).toEqual( [ + [ '@basis/scripts', './resources/scripts' ], + [ '@sage/scripts', './resources/scripts' ], + ] ); +} ); + +test( 'unresolvable layouts keep the historical theme-root guess', () => { + useProject( 'empty' ); + + expect( resolveImportAliases() ).toEqual( [ + [ '@empty/scripts', './resources/scripts' ], + [ '@sage/scripts', './resources/scripts' ], + ] ); +} ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af99b9f..9078357 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -43,6 +43,9 @@ importers: '@wordpress/eslint-plugin': specifier: ^26.0.0 version: 26.0.0(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@10.11.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.11.0(jiti@2.7.0))(typescript@5.9.3))(@typescript-eslint/parser@8.70.0(eslint@10.11.0(jiti@2.7.0))(typescript@5.9.3))(esbuild@0.28.1)(eslint@10.11.0(jiti@2.7.0))(jest@30.5.2(@types/node@26.0.0)(babel-plugin-macros@3.1.0))(postcss@8.5.28)(prettier@3.8.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(stylelint@17.15.0(typescript@5.9.3))(typescript@5.9.3)(vite@8.1.0(@types/node@26.0.0)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.104.1)(sugarss@5.0.1(postcss@8.5.28))(terser@5.48.0)(yaml@2.9.0)) + '@yardinternet/shared-utils': + specifier: workspace:* + version: link:../shared-utils eslint-import-resolver-alias: specifier: ^1.1.2 version: 1.1.2(eslint-plugin-import@2.32.0)