@emulsify/core 3.5.0 → 4.0.1
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 +95 -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
package/.cli/init.js
CHANGED
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @file Initializes a generated Emulsify project from project.emulsify.json.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import yaml from 'js-yaml';
|
|
11
|
+
|
|
12
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
|
-
*
|
|
15
|
+
* Determine whether a value is a plain object.
|
|
9
16
|
*
|
|
10
|
-
* @param {
|
|
11
|
-
* @returns {boolean}
|
|
17
|
+
* @param {*} obj - Value to inspect.
|
|
18
|
+
* @returns {boolean} TRUE when the value is a plain object.
|
|
12
19
|
*/
|
|
13
20
|
const isObjectLiteral = (obj) =>
|
|
14
21
|
obj != null && obj.constructor.name === 'Object';
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
|
-
*
|
|
24
|
+
* Load project.emulsify.json from the generated project config directory.
|
|
18
25
|
*
|
|
19
|
-
* @returns
|
|
26
|
+
* @returns {Object} Parsed project.emulsify.json file.
|
|
27
|
+
* @throws {Error} When the config cannot be loaded.
|
|
20
28
|
*/
|
|
21
29
|
const getEmulsifyConfig = () => {
|
|
30
|
+
const configPath = path.join(__dirname, '../config/project.emulsify.json');
|
|
31
|
+
|
|
22
32
|
try {
|
|
23
|
-
return
|
|
33
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
24
34
|
} catch (e) {
|
|
25
35
|
throw new Error(
|
|
26
36
|
`Unable to load an Emulsify project config file (project.emulsify.json): ${String(
|
|
@@ -31,9 +41,11 @@ const getEmulsifyConfig = () => {
|
|
|
31
41
|
};
|
|
32
42
|
|
|
33
43
|
/**
|
|
34
|
-
*
|
|
44
|
+
* Validate the minimal project configuration required for initialization.
|
|
35
45
|
*
|
|
36
|
-
* @param {*} config
|
|
46
|
+
* @param {*} config - Emulsify project config loaded from project.emulsify.json.
|
|
47
|
+
* @returns {void}
|
|
48
|
+
* @throws {Error} When required config values are missing or invalid.
|
|
37
49
|
*/
|
|
38
50
|
const validateEmulsifyConfig = (config) => {
|
|
39
51
|
const prefix = 'Invalid project.emulsify.json config file';
|
|
@@ -68,11 +80,10 @@ const validateEmulsifyConfig = (config) => {
|
|
|
68
80
|
};
|
|
69
81
|
|
|
70
82
|
/**
|
|
71
|
-
*
|
|
72
|
-
* then moves each specified file according to it's to/from properties.
|
|
83
|
+
* Move generated starter files to their project-specific names.
|
|
73
84
|
*
|
|
74
|
-
* @param {Array<{ to: string, from: string }>} files
|
|
75
|
-
* @returns void.
|
|
85
|
+
* @param {Array<{ to: string, from: string }>} files - Files to move.
|
|
86
|
+
* @returns {Array<void>} Rename results.
|
|
76
87
|
*/
|
|
77
88
|
const renameFiles = (files) =>
|
|
78
89
|
files.map(({ from, to }) =>
|
|
@@ -80,24 +91,23 @@ const renameFiles = (files) =>
|
|
|
80
91
|
);
|
|
81
92
|
|
|
82
93
|
/**
|
|
83
|
-
*
|
|
84
|
-
* replaces all instances of `emulsify` with the given machineName.
|
|
94
|
+
* Create a replacer that swaps the starter machine name for the project name.
|
|
85
95
|
*
|
|
86
|
-
* @param {string} machineName
|
|
87
|
-
* @returns {
|
|
96
|
+
* @param {string} machineName - Machine name that should replace `emulsify`.
|
|
97
|
+
* @returns {Function} String replacer.
|
|
88
98
|
*/
|
|
89
99
|
const strReplaceEmulsify = (machineName) => (str) =>
|
|
90
100
|
str.replace(/emulsify/g, machineName);
|
|
91
101
|
|
|
92
102
|
/**
|
|
93
|
-
*
|
|
103
|
+
* Load a YAML file, transform its parsed contents, and write it back.
|
|
94
104
|
*
|
|
95
|
-
* @param {string} filePath
|
|
96
|
-
* @param {
|
|
97
|
-
* @returns void
|
|
105
|
+
* @param {string} filePath - File to load, modify, and save.
|
|
106
|
+
* @param {Function} functor - Function that returns the replacement YAML data.
|
|
107
|
+
* @returns {void}
|
|
98
108
|
*/
|
|
99
109
|
const applyToYmlFile = (filePath, functor) => {
|
|
100
|
-
if (!filePath || typeof filePath !==
|
|
110
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
101
111
|
throw new Error(
|
|
102
112
|
`Cannot modify a file without knowing how to access it: ${filePath}`,
|
|
103
113
|
);
|
|
@@ -111,18 +121,17 @@ const applyToYmlFile = (filePath, functor) => {
|
|
|
111
121
|
};
|
|
112
122
|
|
|
113
123
|
const main = () => {
|
|
114
|
-
// Load
|
|
124
|
+
// Load the project config before mutating any generated files.
|
|
115
125
|
const config = getEmulsifyConfig();
|
|
116
126
|
|
|
117
|
-
//
|
|
118
|
-
//properties or is otherwise malformed.
|
|
127
|
+
// Fail fast when required project metadata is missing or malformed.
|
|
119
128
|
validateEmulsifyConfig(config);
|
|
120
129
|
|
|
121
130
|
const {
|
|
122
|
-
project: { machineName
|
|
131
|
+
project: { machineName },
|
|
123
132
|
} = config;
|
|
124
133
|
|
|
125
|
-
//
|
|
134
|
+
// Rename starter files from the generic prefix to the project machine name.
|
|
126
135
|
renameFiles([
|
|
127
136
|
{
|
|
128
137
|
from: '../emulsify.info.yml',
|
|
@@ -142,7 +151,7 @@ const main = () => {
|
|
|
142
151
|
},
|
|
143
152
|
]);
|
|
144
153
|
|
|
145
|
-
// Update info.yml
|
|
154
|
+
// Update info.yml values that Drupal reads from the generated theme.
|
|
146
155
|
applyToYmlFile(
|
|
147
156
|
path.join(__dirname, `../${machineName}.info.yml`),
|
|
148
157
|
(info) => ({
|
|
@@ -152,7 +161,7 @@ const main = () => {
|
|
|
152
161
|
}),
|
|
153
162
|
);
|
|
154
163
|
|
|
155
|
-
// Update breakpoint
|
|
164
|
+
// Update breakpoint keys to match the renamed theme machine name.
|
|
156
165
|
applyToYmlFile(
|
|
157
166
|
path.join(__dirname, `../${machineName}.breakpoints.yml`),
|
|
158
167
|
(breakpoints) => {
|
package/.storybook/_drupal.js
CHANGED
|
@@ -1,10 +1,96 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Drupal browser compatibility layer for Storybook.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const emulsifyEnv =
|
|
6
|
+
(typeof __EMULSIFY_ENV__ !== 'undefined' && __EMULSIFY_ENV__) || {};
|
|
7
|
+
const projectMachineName =
|
|
8
|
+
typeof emulsifyEnv.machineName === 'string' ? emulsifyEnv.machineName : '';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Storybook-safe defaults for the Drupal settings object.
|
|
12
|
+
*
|
|
13
|
+
* These values cover the common browser properties Drupal-authored JavaScript
|
|
14
|
+
* reads while keeping project-specific module settings in project overrides.
|
|
15
|
+
*
|
|
16
|
+
* @type {object}
|
|
17
|
+
*/
|
|
18
|
+
const defaultDrupalSettings = {
|
|
19
|
+
path: {
|
|
20
|
+
baseUrl: '/',
|
|
21
|
+
currentLanguage: 'en',
|
|
22
|
+
isFront: false,
|
|
23
|
+
langcode: 'en',
|
|
24
|
+
pathPrefix: '',
|
|
25
|
+
currentPath: '/',
|
|
26
|
+
currentPathIsAdmin: false,
|
|
27
|
+
},
|
|
28
|
+
user: {
|
|
29
|
+
uid: 0,
|
|
30
|
+
permissionsHash: '',
|
|
31
|
+
},
|
|
32
|
+
ajaxPageState: {
|
|
33
|
+
theme: projectMachineName,
|
|
34
|
+
theme_token: '',
|
|
35
|
+
},
|
|
36
|
+
ajaxTrustedUrl: {},
|
|
37
|
+
pluralDelimiter: '\u0003',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Determine whether a value can be recursively merged as settings.
|
|
42
|
+
*
|
|
43
|
+
* @param {*} value - Candidate value.
|
|
44
|
+
* @returns {boolean} TRUE when the value is a plain object.
|
|
45
|
+
*/
|
|
46
|
+
function isPlainObject(value) {
|
|
47
|
+
return (
|
|
48
|
+
Boolean(value) &&
|
|
49
|
+
Object.prototype.toString.call(value) === '[object Object]'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Merge default settings with project-provided Drupal settings.
|
|
55
|
+
*
|
|
56
|
+
* Existing project settings win so projects can provide module-specific values
|
|
57
|
+
* or override neutral defaults from `config/emulsify-core/storybook/preview.js`.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} defaults - Default Drupal settings.
|
|
60
|
+
* @param {object} overrides - Project-provided Drupal settings.
|
|
61
|
+
* @returns {object} Merged settings object.
|
|
62
|
+
*/
|
|
63
|
+
function mergeDrupalSettings(defaults, overrides) {
|
|
64
|
+
const merged = { ...defaults };
|
|
65
|
+
|
|
66
|
+
for (const [key, value] of Object.entries(overrides || {})) {
|
|
67
|
+
// Drupal settings keys are project/module-defined by design.
|
|
68
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
69
|
+
const defaultValue = merged[key];
|
|
70
|
+
const nextValue =
|
|
71
|
+
isPlainObject(defaultValue) && isPlainObject(value)
|
|
72
|
+
? mergeDrupalSettings(defaultValue, value)
|
|
73
|
+
: value;
|
|
74
|
+
|
|
75
|
+
// Drupal settings keys are project/module-defined by design.
|
|
76
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
77
|
+
merged[key] = nextValue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return merged;
|
|
81
|
+
}
|
|
2
82
|
|
|
3
83
|
/**
|
|
4
|
-
*
|
|
84
|
+
* Create the global Drupal namespace stub for the Storybook environment.
|
|
85
|
+
*
|
|
5
86
|
* @namespace Drupal
|
|
6
87
|
*/
|
|
7
|
-
window.Drupal =
|
|
88
|
+
window.Drupal = window.Drupal || {};
|
|
89
|
+
window.Drupal.behaviors = window.Drupal.behaviors || {};
|
|
90
|
+
window.drupalSettings = mergeDrupalSettings(
|
|
91
|
+
defaultDrupalSettings,
|
|
92
|
+
isPlainObject(window.drupalSettings) ? window.drupalSettings : {},
|
|
93
|
+
);
|
|
8
94
|
|
|
9
95
|
/**
|
|
10
96
|
* Immediately-Invoked Function Expression to scope Drupal behavior attachment logic.
|
|
@@ -12,6 +98,38 @@ window.Drupal = { behaviors: {} };
|
|
|
12
98
|
* @param {Object} drupalSettings - Global Drupal settings object stub.
|
|
13
99
|
*/
|
|
14
100
|
(function (Drupal, drupalSettings) {
|
|
101
|
+
/**
|
|
102
|
+
* Replaces Drupal-style string placeholders without translating the string.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} str - String containing placeholders such as `@name`.
|
|
105
|
+
* @param {Object.<string, string|number>} [args={}] - Placeholder values.
|
|
106
|
+
* @returns {string} Formatted string.
|
|
107
|
+
*/
|
|
108
|
+
Drupal.formatString =
|
|
109
|
+
Drupal.formatString ||
|
|
110
|
+
function (str, args = {}) {
|
|
111
|
+
let formatted = String(str);
|
|
112
|
+
|
|
113
|
+
for (const [placeholder, replacement] of Object.entries(args || {})) {
|
|
114
|
+
formatted = formatted.split(placeholder).join(String(replacement));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return formatted;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Minimal translation shim for Drupal-authored JavaScript in Storybook.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} str - Source string.
|
|
124
|
+
* @param {Object.<string, string|number>} [args={}] - Placeholder values.
|
|
125
|
+
* @returns {string} Formatted source string.
|
|
126
|
+
*/
|
|
127
|
+
Drupal.t =
|
|
128
|
+
Drupal.t ||
|
|
129
|
+
function (str, args = {}) {
|
|
130
|
+
return Drupal.formatString(str, args);
|
|
131
|
+
};
|
|
132
|
+
|
|
15
133
|
/**
|
|
16
134
|
* Throws an error asynchronously to avoid interrupting execution flow.
|
|
17
135
|
* @param {Error} error - The error object to throw.
|
|
@@ -32,11 +150,14 @@ window.Drupal = { behaviors: {} };
|
|
|
32
150
|
Drupal.attachBehaviors = function (context, settings) {
|
|
33
151
|
context = context || document;
|
|
34
152
|
settings = settings || drupalSettings;
|
|
35
|
-
/** @type {
|
|
36
|
-
const behaviors =
|
|
153
|
+
/** @type {Object.<string, {attach: Function}>} */
|
|
154
|
+
const behaviors = Drupal.behaviors;
|
|
37
155
|
|
|
38
|
-
//
|
|
39
|
-
behaviors.forEach(function (
|
|
156
|
+
// Attach each registered behavior while isolating individual failures.
|
|
157
|
+
Object.keys(behaviors).forEach(function (behaviorName) {
|
|
158
|
+
// Drupal behavior names are project/module-defined by design.
|
|
159
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
160
|
+
const behavior = behaviors[behaviorName];
|
|
40
161
|
if (typeof behavior.attach === 'function') {
|
|
41
162
|
try {
|
|
42
163
|
behavior.attach(context, settings);
|
|
@@ -46,4 +167,4 @@ window.Drupal = { behaviors: {} };
|
|
|
46
167
|
}
|
|
47
168
|
});
|
|
48
169
|
};
|
|
49
|
-
})(Drupal, window.drupalSettings);
|
|
170
|
+
})(window.Drupal, window.drupalSettings);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Storybook mirrored component CSS side-effect loader.
|
|
3
|
+
*
|
|
4
|
+
* Drupal projects load component CSS from the mirrored root components tree,
|
|
5
|
+
* but shared/global Storybook CSS still lives under dist. Exclude
|
|
6
|
+
* dist/components so the mirrored component CSS is not loaded twice.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import.meta.glob('../../../../components/**/*.css', { eager: true });
|
|
10
|
+
import.meta.glob(
|
|
11
|
+
['../../../../dist/**/*.css', '!../../../../dist/components/**/*.css'],
|
|
12
|
+
{ eager: true },
|
|
13
|
+
);
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @file Default Emulsify theme for the Storybook manager UI.
|
|
3
|
+
*
|
|
4
|
+
* @see https://storybook.js.org/docs/configurations/theming/
|
|
5
|
+
*/
|
|
6
|
+
|
|
2
7
|
import { create } from 'storybook/theming';
|
|
3
8
|
|
|
4
9
|
export default create({
|
|
5
10
|
base: 'dark',
|
|
6
11
|
|
|
7
|
-
//
|
|
12
|
+
// Storybook application chrome colors.
|
|
8
13
|
appBg: '#00405B',
|
|
9
14
|
appContentBg: '#00202E',
|
|
10
15
|
appBorderColor: '#00405B',
|
|
11
16
|
appBorderRadius: 4,
|
|
12
17
|
|
|
13
|
-
// Typography
|
|
18
|
+
// Typography is intentionally aligned with the design system brand.
|
|
14
19
|
fontBase: '"Mona Sans", sans-serif',
|
|
15
20
|
fontCode: 'monospace',
|
|
16
21
|
|
|
@@ -29,9 +34,7 @@ export default create({
|
|
|
29
34
|
inputBorder: '#00405B',
|
|
30
35
|
inputTextColor: 'white',
|
|
31
36
|
inputBorderRadius: 4,
|
|
32
|
-
// Branding
|
|
37
|
+
// Branding links the manager back to the public Emulsify site.
|
|
33
38
|
brandTitle: 'Emulsify',
|
|
34
39
|
brandUrl: 'https://emulsify.info',
|
|
35
|
-
brandImage:
|
|
36
|
-
'https://raw.githubusercontent.com/fourkitchens/emulsify-core/main/assets/images/emulsify-logo-sb.svg',
|
|
37
40
|
});
|