@emulsify/core 3.5.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cli/init.js +40 -31
- package/.storybook/_drupal.js +129 -8
- package/.storybook/css-components.js +13 -0
- package/.storybook/css-dist.js +5 -0
- package/.storybook/emulsifyTheme.js +9 -6
- package/.storybook/main.js +397 -106
- package/.storybook/manager.js +9 -16
- package/.storybook/preview.js +88 -110
- package/.storybook/utils.js +69 -74
- package/README.md +110 -59
- package/config/.stylelintrc.json +2 -6
- package/config/a11y.config.js +9 -5
- package/config/babel.config.js +6 -11
- package/config/eslint.config.js +31 -3
- package/config/postcss.config.js +5 -0
- package/config/vite/entries.js +227 -0
- package/config/vite/environment.js +39 -0
- package/config/vite/platforms.js +70 -0
- package/config/vite/plugins/copy-src-assets.js +76 -0
- package/config/vite/plugins/copy-twig-files.js +84 -0
- package/config/vite/plugins/css-asset-relativizer.js +40 -0
- package/config/vite/plugins/index.js +105 -0
- package/config/vite/plugins/mirror-components.js +358 -0
- package/config/vite/plugins/require-context.js +311 -0
- package/config/vite/plugins/source-file-index.js +184 -0
- package/config/vite/plugins/svg-sprite.js +117 -0
- package/config/vite/plugins/twig-extension-installers.js +36 -0
- package/config/vite/plugins/twig-module.js +1251 -0
- package/config/vite/plugins/virtual-twig-asset-sources.js +404 -0
- package/config/vite/plugins/virtual-twig-globs.js +136 -0
- package/config/vite/plugins/vituum-patch.js +167 -0
- package/config/vite/plugins/yaml-module.js +133 -0
- package/config/vite/plugins.js +12 -0
- package/config/vite/project-config.js +192 -0
- package/config/vite/project-extensions.js +177 -0
- package/config/vite/project-structure.js +447 -0
- package/config/vite/twig-extensions.js +109 -0
- package/config/vite/utils/fs-safe.js +66 -0
- package/config/vite/utils/paths.js +40 -0
- package/config/vite/utils/react-singleton.js +85 -0
- package/config/vite/utils/unique.js +36 -0
- package/config/vite/vite.config.js +161 -0
- package/package.json +164 -75
- package/scripts/a11y.js +70 -16
- package/scripts/audit-twig-stories.js +378 -0
- package/scripts/audit.js +1602 -0
- package/scripts/check-node-version.js +18 -0
- package/scripts/loadYaml.js +5 -1
- package/src/extensions/index.js +8 -0
- package/src/extensions/react/index.js +12 -0
- package/src/extensions/react/register.js +45 -0
- package/src/extensions/shared/attributes.js +308 -0
- package/src/extensions/shared/html.js +41 -0
- package/src/extensions/shared/lists.js +38 -0
- package/src/extensions/shared/object.js +22 -0
- package/src/extensions/twig/function-map.js +20 -0
- package/src/extensions/twig/functions/add-attributes.js +39 -0
- package/src/extensions/twig/functions/bem.js +166 -0
- package/src/extensions/twig/index.js +13 -0
- package/src/extensions/twig/register.js +52 -0
- package/src/extensions/twig/tag-map.js +16 -0
- package/src/extensions/twig/tags/switch.js +266 -0
- package/src/storybook/index.js +14 -0
- package/src/storybook/main-config.js +132 -0
- package/src/storybook/platform-behaviors.js +60 -0
- package/src/storybook/preview-parameters.js +81 -0
- package/src/storybook/render-twig.js +295 -0
- package/src/storybook/twig/drupal-filters.js +7 -0
- package/src/storybook/twig/include-function.js +109 -0
- package/src/storybook/twig/include.js +28 -0
- package/src/storybook/twig/reference-paths.js +294 -0
- package/src/storybook/twig/resolver.js +318 -0
- package/src/storybook/twig/setup.js +39 -0
- package/src/storybook/twig/source-events.js +5 -0
- package/src/storybook/twig/source-extensions.js +24 -0
- package/src/storybook/twig/source-function.js +239 -0
- package/src/storybook/twig/source.js +39 -0
- package/.all-contributorsrc +0 -45
- package/.editorconfig +0 -5
- package/.github/ISSUE_TEMPLATE/BUG_REPORT_TEMPLATE.md +0 -18
- package/.github/ISSUE_TEMPLATE/FEATURE_REQUEST_TEMPLATE.md +0 -11
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -19
- package/.github/dependabot.yml +0 -6
- package/.github/workflows/addtoprojects.yml +0 -21
- package/.github/workflows/contributors.yml +0 -37
- package/.github/workflows/lint.yml +0 -22
- package/.github/workflows/semantic-release.yml +0 -24
- package/.husky/commit-msg +0 -2
- package/.husky/pre-commit +0 -2
- package/.nvmrc +0 -1
- package/.prettierignore +0 -4
- package/.storybook/polyfills/twig-include.js +0 -40
- package/.storybook/polyfills/twig-resolver.js +0 -70
- package/.storybook/polyfills/twig-source.js +0 -65
- package/.storybook/webpack.config.js +0 -269
- package/CODE_OF_CONDUCT.md +0 -56
- package/commitlint.config.js +0 -5
- package/config/jest.config.js +0 -19
- package/config/webpack/app.js +0 -1
- package/config/webpack/loaders.js +0 -167
- package/config/webpack/optimizers.js +0 -26
- package/config/webpack/plugins.js +0 -283
- package/config/webpack/resolves.js +0 -157
- package/config/webpack/sdc-loader.js +0 -16
- package/config/webpack/webpack.common.js +0 -272
- package/config/webpack/webpack.dev.js +0 -41
- package/config/webpack/webpack.prod.js +0 -6
- package/release.config.cjs +0 -30
- package/scripts/a11y.test.js +0 -172
- package/scripts/loadYaml.test.js +0 -30
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import { resolve, dirname, relative } from 'path';
|
|
2
|
-
import webpack from 'webpack';
|
|
3
|
-
import { CleanWebpackPlugin } from 'clean-webpack-plugin';
|
|
4
|
-
import RemoveEmptyScriptsPlugin from 'webpack-remove-empty-scripts';
|
|
5
|
-
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
|
|
6
|
-
import SVGSpritemapPlugin from 'svg-spritemap-webpack-plugin';
|
|
7
|
-
import CopyPlugin from 'copy-webpack-plugin';
|
|
8
|
-
import { sync as globSync } from 'glob';
|
|
9
|
-
import fs from 'fs-extra';
|
|
10
|
-
import emulsifyConfig from '../../../../../project.emulsify.json' with { type: 'json' };
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Resolve the directory of this file (without fileURLToPath).
|
|
14
|
-
* @type {string}
|
|
15
|
-
*/
|
|
16
|
-
let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
|
|
17
|
-
if (process.platform === 'win32' && _filename.startsWith('/')) {
|
|
18
|
-
_filename = _filename.slice(1);
|
|
19
|
-
}
|
|
20
|
-
const _dirname = dirname(_filename);
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Project root (five levels up).
|
|
24
|
-
* @type {string}
|
|
25
|
-
*/
|
|
26
|
-
const projectDir = resolve(_dirname, '../../../../..');
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Where source files live.
|
|
30
|
-
* Prefer `<project>/src`; fall back to `<project>/components` (legacy layout).
|
|
31
|
-
* @type {string}
|
|
32
|
-
*/
|
|
33
|
-
const srcPath = resolve(projectDir, 'src');
|
|
34
|
-
const isSrcExists = fs.pathExistsSync(srcPath);
|
|
35
|
-
const srcDir = isSrcExists ? srcPath : resolve(projectDir, 'components');
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Where built assets live.
|
|
39
|
-
* If `src/` exists, use `<project>/dist`; else write into `<project>/components`.
|
|
40
|
-
* @type {string}
|
|
41
|
-
*/
|
|
42
|
-
const distPath = isSrcExists
|
|
43
|
-
? resolve(projectDir, 'dist')
|
|
44
|
-
: resolve(projectDir, 'components');
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Platform switch (affects component output roots).
|
|
48
|
-
* @type {boolean}
|
|
49
|
-
*/
|
|
50
|
-
const isDrupal = emulsifyConfig?.project?.platform === 'drupal';
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Component source root:
|
|
54
|
-
* - with src/: `<project>/src/components`
|
|
55
|
-
* - without src/: `<project>/components`
|
|
56
|
-
* @type {string}
|
|
57
|
-
*/
|
|
58
|
-
const componentsSrcRoot = isSrcExists ? resolve(srcDir, 'components') : srcDir;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Component output root (where compiled component assets go):
|
|
62
|
-
* - Drupal + src/: `components/…`
|
|
63
|
-
* - Otherwise: `dist/components/…`
|
|
64
|
-
* (Relative to `projectDir`; used by CopyPlugins `to:` path.)
|
|
65
|
-
* @type {string}
|
|
66
|
-
*/
|
|
67
|
-
const componentsOutRoot =
|
|
68
|
-
isDrupal && isSrcExists ? 'components' : 'dist/components';
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Glob pattern for Twig & component meta files. These are copied as-is so
|
|
72
|
-
* Drupal/WordPress themes can consume them alongside compiled assets.
|
|
73
|
-
* @type {string}
|
|
74
|
-
*/
|
|
75
|
-
const componentFilesPattern = resolve(
|
|
76
|
-
srcDir,
|
|
77
|
-
'**/*.{twig,component.yml,component.json}',
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Build CopyPlugin patterns from a glob matcher, preserving source structure.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} filesMatcher - Glob for files to mirror.
|
|
84
|
-
* @returns {Array<{from:string,to:string}>} Copy patterns for CopyPlugin.
|
|
85
|
-
*/
|
|
86
|
-
function getPatterns(filesMatcher) {
|
|
87
|
-
return globSync(filesMatcher).map((file) => {
|
|
88
|
-
const projectPath = file.split('/src/')[0]; // base path before /src/
|
|
89
|
-
const srcStructure = file.split(`${srcDir}/`)[1];
|
|
90
|
-
const parentDir = srcStructure.split('/')[0];
|
|
91
|
-
|
|
92
|
-
// Consolidate foundation/layout under "components" for Drupal.
|
|
93
|
-
const consolidateDirs =
|
|
94
|
-
parentDir === 'layout' || parentDir === 'foundation'
|
|
95
|
-
? '/components/'
|
|
96
|
-
: '/';
|
|
97
|
-
|
|
98
|
-
const filePath = file.split(/(foundation\/|components\/|layout\/)/)[2];
|
|
99
|
-
|
|
100
|
-
const to = isDrupal
|
|
101
|
-
? `${projectPath}${consolidateDirs}${parentDir}/${filePath}`
|
|
102
|
-
: `${projectPath}/dist/${parentDir}/${filePath}`;
|
|
103
|
-
|
|
104
|
-
return { from: file, to };
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* CopyPlugin instance (only when `src/` exists):
|
|
110
|
-
* copies Twig and component meta files 1:1 into their expected destinations.
|
|
111
|
-
* @type {CopyPlugin|false}
|
|
112
|
-
*/
|
|
113
|
-
const CopyTwigPlugin = isSrcExists
|
|
114
|
-
? new CopyPlugin({ patterns: getPatterns(componentFilesPattern) })
|
|
115
|
-
: false;
|
|
116
|
-
|
|
117
|
-
/* -------------------------------------------------------------------------- */
|
|
118
|
-
/* COMPONENT & GLOBAL ASSETS */
|
|
119
|
-
/* -------------------------------------------------------------------------- */
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Asset allow-list (extensions we consider "static assets" to mirror).
|
|
123
|
-
* Extend to suit your project (e.g., add `pdf`, `txt`, `xml`, etc.).
|
|
124
|
-
* NOTE: We purposefully exclude code-like files via the filter below.
|
|
125
|
-
* @type {RegExp}
|
|
126
|
-
*/
|
|
127
|
-
const ASSET_EXT_RE =
|
|
128
|
-
/\.(?:png|jpe?g|gif|svg|webp|avif|ico|bmp|heic|heif|mp4|webm|mp3|ogg|wav|aac|woff2?|ttf|otf|eot|json|webmanifest|manifest|pdf)$/i;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Exclude code & tooling files (don’t mirror these).
|
|
132
|
-
* @type {RegExp}
|
|
133
|
-
*/
|
|
134
|
-
const EXCLUDE_CODE_RE =
|
|
135
|
-
/\.(?:jsx?|tsx?|mjs|cjs|vue|svelte|scss|sass|less|styl|css|map|twig|php|yml|yaml|md|markdown|story(?:book)?\.[jt]sx?|stories\.[jt]sx?|test\.[jt]sx?)$/i;
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Shared filter for CopyPlugin patterns.
|
|
139
|
-
* Decides whether a file should be copied as a "static asset".
|
|
140
|
-
*
|
|
141
|
-
* @param {string} resourcePath - Absolute file path on disk.
|
|
142
|
-
* @param {string} base - The context directory for the pattern.
|
|
143
|
-
* @returns {boolean} True if we should copy the file.
|
|
144
|
-
*/
|
|
145
|
-
const assetFilter = (resourcePath, base) => {
|
|
146
|
-
const rel = relative(base, resourcePath);
|
|
147
|
-
// Guard: stay inside context
|
|
148
|
-
if (rel.startsWith('..')) return false;
|
|
149
|
-
// Exclude typical code/tooling files
|
|
150
|
-
if (EXCLUDE_CODE_RE.test(rel)) return false;
|
|
151
|
-
// Include known asset extensions
|
|
152
|
-
return ASSET_EXT_RE.test(rel);
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Copy **all static assets inside components**, regardless of folder labels.
|
|
157
|
-
*
|
|
158
|
-
* Examples (all preserved under the component’s output root):
|
|
159
|
-
* src/components/accordion/assets/dropdown-icon.svg
|
|
160
|
-
* src/components/accordion/images/icons/chevron.svg
|
|
161
|
-
* src/components/accordion/icon.svg (root-level asset)
|
|
162
|
-
*
|
|
163
|
-
* @type {CopyPlugin}
|
|
164
|
-
*/
|
|
165
|
-
const CopyComponentAssetsPlugin = new CopyPlugin({
|
|
166
|
-
patterns: [
|
|
167
|
-
{
|
|
168
|
-
// Start at the components root and evaluate every file
|
|
169
|
-
from: '**/*',
|
|
170
|
-
context: componentsSrcRoot,
|
|
171
|
-
to: resolve(projectDir, componentsOutRoot, '[path][name][ext]'),
|
|
172
|
-
noErrorOnMissing: true,
|
|
173
|
-
globOptions: {
|
|
174
|
-
dot: false,
|
|
175
|
-
ignore: [
|
|
176
|
-
'**/.DS_Store',
|
|
177
|
-
'**/Thumbs.db',
|
|
178
|
-
'**/node_modules/**',
|
|
179
|
-
'**/dist/**',
|
|
180
|
-
],
|
|
181
|
-
},
|
|
182
|
-
// Only copy files that match our asset allow-list and are not code
|
|
183
|
-
filter: (resourcePath) => assetFilter(resourcePath, componentsSrcRoot),
|
|
184
|
-
},
|
|
185
|
-
],
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* OPTIONAL: Copy **global (non-component) assets** that live under `src/`
|
|
190
|
-
* but outside `src/components/` (e.g. layout/site assets).
|
|
191
|
-
*
|
|
192
|
-
* Mirrors them under `dist/global/…`.
|
|
193
|
-
* Disabled when there is no `src/` directory.
|
|
194
|
-
*
|
|
195
|
-
* @type {CopyPlugin|false}
|
|
196
|
-
*/
|
|
197
|
-
const CopyGlobalAssetsPlugin = isSrcExists
|
|
198
|
-
? new CopyPlugin({
|
|
199
|
-
patterns: [
|
|
200
|
-
{
|
|
201
|
-
from: '!(components|util)/**/*',
|
|
202
|
-
context: srcDir,
|
|
203
|
-
to: resolve(projectDir, 'dist', 'global', '[path][name][ext]'),
|
|
204
|
-
noErrorOnMissing: true,
|
|
205
|
-
globOptions: {
|
|
206
|
-
dot: false,
|
|
207
|
-
ignore: [
|
|
208
|
-
'**/.DS_Store',
|
|
209
|
-
'**/Thumbs.db',
|
|
210
|
-
'**/node_modules/**',
|
|
211
|
-
'**/dist/**',
|
|
212
|
-
],
|
|
213
|
-
},
|
|
214
|
-
filter: (resourcePath) => assetFilter(resourcePath, srcDir),
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
})
|
|
218
|
-
: false;
|
|
219
|
-
|
|
220
|
-
/* -------------------------------------------------------------------------- */
|
|
221
|
-
/* OTHER PLUGINS */
|
|
222
|
-
/* -------------------------------------------------------------------------- */
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* CleanWebpackPlugin configuration.
|
|
226
|
-
* Wipes out compiled CSS/JS in `distPath` before a build; keeps images.
|
|
227
|
-
*/
|
|
228
|
-
const CleanPlugin = new CleanWebpackPlugin({
|
|
229
|
-
protectWebpackAssets: false,
|
|
230
|
-
cleanOnceBeforeBuildPatterns: [
|
|
231
|
-
`${distPath}/**/*.css`,
|
|
232
|
-
`${distPath}/**/*.js`,
|
|
233
|
-
`!${distPath}/**/*.png`,
|
|
234
|
-
`!${distPath}/**/*.jpg`,
|
|
235
|
-
`!${distPath}/**/*.gif`,
|
|
236
|
-
`!${distPath}/**/*.svg`,
|
|
237
|
-
],
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
/** Removes empty JS files generated for style-only entries. */
|
|
241
|
-
const RemoveEmptyJS = new RemoveEmptyScriptsPlugin();
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* MiniCssExtractPlugin: emit CSS next to the entry key path (no hard-coded dist/).
|
|
245
|
-
*/
|
|
246
|
-
const CssExtractPlugin = new MiniCssExtractPlugin({
|
|
247
|
-
filename: ({ chunk }) => `${chunk.name}.css`,
|
|
248
|
-
chunkFilename: ({ chunk }) => `${chunk.name}.css`,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* Generate a single SVG spritemap at `dist/icons.svg`.
|
|
253
|
-
*/
|
|
254
|
-
const SpritePlugin = new SVGSpritemapPlugin(
|
|
255
|
-
resolve(projectDir, 'assets/icons/**/*.svg'),
|
|
256
|
-
{
|
|
257
|
-
output: {
|
|
258
|
-
filename: 'dist/icons.svg',
|
|
259
|
-
chunk: { keep: true },
|
|
260
|
-
},
|
|
261
|
-
sprite: {
|
|
262
|
-
prefix: '',
|
|
263
|
-
generate: { title: false },
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
);
|
|
267
|
-
|
|
268
|
-
/** Build progress output. */
|
|
269
|
-
const ProgressPlugin = new webpack.ProgressPlugin();
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Export plugin instances keyed for easy inclusion in your Webpack config.
|
|
273
|
-
*/
|
|
274
|
-
export default {
|
|
275
|
-
ProgressPlugin,
|
|
276
|
-
CleanWebpackPlugin: CleanPlugin,
|
|
277
|
-
RemoveEmptyJS,
|
|
278
|
-
MiniCssExtractPlugin: CssExtractPlugin,
|
|
279
|
-
SpritePlugin,
|
|
280
|
-
CopyTwigPlugin,
|
|
281
|
-
CopyComponentAssetsPlugin,
|
|
282
|
-
CopyGlobalAssetsPlugin,
|
|
283
|
-
};
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Configures Twig alias resolution for the project.
|
|
3
|
-
* - Builds Twig alias map from files under the source directory
|
|
4
|
-
* - Exposes a Webpack-style `resolve.alias` object for `.twig` files
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
basename,
|
|
9
|
-
resolve,
|
|
10
|
-
relative,
|
|
11
|
-
isAbsolute,
|
|
12
|
-
join,
|
|
13
|
-
posix as path,
|
|
14
|
-
} from 'node:path';
|
|
15
|
-
import { sync as globSync } from 'glob';
|
|
16
|
-
import fs from 'fs-extra';
|
|
17
|
-
import emulsifyConfig from '../../../../../project.emulsify.json' with { type: 'json' };
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Resolve the directory of this file (without fileURLToPath).
|
|
21
|
-
* @type {string}
|
|
22
|
-
*/
|
|
23
|
-
let _filename = decodeURIComponent(new URL(import.meta.url).pathname);
|
|
24
|
-
if (process.platform === 'win32' && _filename.startsWith('/')) {
|
|
25
|
-
_filename = _filename.slice(1);
|
|
26
|
-
}
|
|
27
|
-
const _dirname = path.dirname(_filename);
|
|
28
|
-
|
|
29
|
-
/** @type {string} Absolute project root (five levels up). */
|
|
30
|
-
const projectDir = resolve(_dirname, '../../../../..');
|
|
31
|
-
|
|
32
|
-
/** @type {string} Project machine name used to prefix Drupal aliases. */
|
|
33
|
-
const projectName = String(emulsifyConfig?.project?.name || '').trim();
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Determine the source directory: prefer `<project>/src` if it exists,
|
|
37
|
-
* otherwise use `<project>/components`. If we choose `components` and it
|
|
38
|
-
* does not exist, create it safely inside the project.
|
|
39
|
-
*
|
|
40
|
-
* @returns {string} Absolute path to the source directory.
|
|
41
|
-
*/
|
|
42
|
-
function resolveOrCreateSrcDir() {
|
|
43
|
-
const srcPreferred = resolve(projectDir, 'src');
|
|
44
|
-
if (fs.pathExistsSync(srcPreferred)) return srcPreferred;
|
|
45
|
-
|
|
46
|
-
const componentsFallback = resolve(projectDir, 'components');
|
|
47
|
-
if (!fs.pathExistsSync(componentsFallback)) {
|
|
48
|
-
ensureDirSafe(componentsFallback, {
|
|
49
|
-
base: projectDir,
|
|
50
|
-
allowedBasenames: new Set(['components']),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
return componentsFallback;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Safely create a directory after validating it is a subpath of `base`
|
|
58
|
-
* and its basename is explicitly allowed. This addresses
|
|
59
|
-
* `security/detect-non-literal-fs-filename`.
|
|
60
|
-
*
|
|
61
|
-
* @param {string} dir - Absolute path to create.
|
|
62
|
-
* @param {{ base: string, allowedBasenames: Set<string> }} opts - Safety options.
|
|
63
|
-
* @returns {void}
|
|
64
|
-
* @throws {Error} If the path is outside `base` or not allowed.
|
|
65
|
-
*/
|
|
66
|
-
function ensureDirSafe(dir, { base, allowedBasenames }) {
|
|
67
|
-
const rel = relative(base, dir);
|
|
68
|
-
const name = basename(dir);
|
|
69
|
-
|
|
70
|
-
// Block absolute or escaping paths (outside of base)
|
|
71
|
-
if (!rel || rel.startsWith('..') || isAbsolute(rel)) {
|
|
72
|
-
throw new Error(`Refusing to create directory outside project: "${dir}"`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Only allow known, expected directory names
|
|
76
|
-
if (!allowedBasenames.has(name)) {
|
|
77
|
-
throw new Error(`Refusing to create unexpected directory: "${name}"`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// The argument is validated; create the directory.
|
|
81
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
|
82
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** @type {string} Absolute source directory. */
|
|
86
|
-
const srcDir = resolveOrCreateSrcDir();
|
|
87
|
-
|
|
88
|
-
/** @type {string} Glob pattern for all non-partial Twig files (skip leading underscores). */
|
|
89
|
-
const aliasPattern = resolve(srcDir, '**/!(_*).twig');
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Read immediate subdirectories from a source directory.
|
|
93
|
-
*
|
|
94
|
-
* @param {string} source - Absolute directory to scan.
|
|
95
|
-
* @returns {string[]} Array of directory names (basenames only).
|
|
96
|
-
*/
|
|
97
|
-
function getDirectories(source) {
|
|
98
|
-
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
99
|
-
const entries = fs.readdirSync(source, { withFileTypes: true });
|
|
100
|
-
/* eslint-enable security/detect-non-literal-fs-filename */
|
|
101
|
-
return entries.filter((d) => d.isDirectory()).map((d) => d.name);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Strip a leading two-digit ordering prefix from a directory name
|
|
106
|
-
* (e.g., "01-components" -> "components").
|
|
107
|
-
*
|
|
108
|
-
* @param {string} dir - Original directory name.
|
|
109
|
-
* @returns {string} Cleaned directory name.
|
|
110
|
-
*/
|
|
111
|
-
function cleanDirectoryName(dir) {
|
|
112
|
-
return /^\d{2}-/.test(dir) ? dir.slice(3) : dir;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Build a Twig alias object by:
|
|
117
|
-
* - Adding per-file aliases for Drupal (e.g., "mytheme/button")
|
|
118
|
-
* - Adding top-level section aliases (e.g., "@components", "@layout")
|
|
119
|
-
*
|
|
120
|
-
* @param {string} twigGlob - Glob pattern to locate Twig files.
|
|
121
|
-
* @returns {Record<string, string>} Alias map ({ alias: absolutePath }).
|
|
122
|
-
*/
|
|
123
|
-
function getAliases(twigGlob) {
|
|
124
|
-
/** @type {Record<string, string>} */
|
|
125
|
-
const aliases = {};
|
|
126
|
-
|
|
127
|
-
// Per-file aliases for Drupal only: "<projectName>/<filename>"
|
|
128
|
-
if (emulsifyConfig?.project?.platform === 'drupal' && projectName) {
|
|
129
|
-
for (const file of globSync(twigGlob)) {
|
|
130
|
-
const relToSrc = relative(srcDir, file);
|
|
131
|
-
const fileName = basename(relToSrc).replace(/\.twig$/, '');
|
|
132
|
-
aliases[`${projectName}/${fileName}`] = file;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Top-level "@section" aliases for easier imports
|
|
137
|
-
const topDirs = getDirectories(srcDir);
|
|
138
|
-
for (const dir of topDirs) {
|
|
139
|
-
const name = cleanDirectoryName(dir);
|
|
140
|
-
aliases[`@${name}`] = join(projectDir, basename(srcDir), dir);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return aliases;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Webpack-style `resolve` config for Twig files.
|
|
148
|
-
* @typedef {{ extensions: string[], alias: Record<string, string> }} TwigResolveConfig
|
|
149
|
-
*/
|
|
150
|
-
|
|
151
|
-
/** @type {TwigResolveConfig} */
|
|
152
|
-
const TwigResolve = {
|
|
153
|
-
extensions: ['.twig'],
|
|
154
|
-
alias: getAliases(aliasPattern),
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
export default { TwigResolve };
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A loader function that replaces occurrences of "projectName:" with "projectName/".
|
|
3
|
-
*
|
|
4
|
-
* @param {string} source - The source string to process.
|
|
5
|
-
* @returns {string} The transformed source.
|
|
6
|
-
*/
|
|
7
|
-
export default function (source) {
|
|
8
|
-
const projectName = this.getOptions().projectName || '';
|
|
9
|
-
/* eslint-disable security/detect-non-literal-regexp */
|
|
10
|
-
const result = source.replace(
|
|
11
|
-
new RegExp(`${projectName}:`, 'g'),
|
|
12
|
-
`${projectName}/`,
|
|
13
|
-
);
|
|
14
|
-
/* eslint-enable security/detect-non-literal-regexp */
|
|
15
|
-
return result;
|
|
16
|
-
}
|