@schalkneethling/miyagi-core 4.0.2 → 4.2.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/api/index.js +98 -10
- package/lib/build/index.js +16 -0
- package/lib/cli/drupal-assets.js +159 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/lint.js +142 -94
- package/lib/constants/lint-log-levels.js +11 -0
- package/lib/default-config.js +10 -0
- package/lib/drupal/load-assets-config.js +94 -0
- package/lib/drupal/resolve-library-assets.js +189 -0
- package/lib/i18n/en.js +4 -0
- package/lib/index.js +27 -4
- package/lib/init/args.js +38 -0
- package/lib/init/config.js +26 -0
- package/lib/init/router.js +1 -0
- package/lib/init/static.js +30 -0
- package/lib/init/watcher.js +21 -0
- package/lib/logger.js +37 -2
- package/lib/mocks/get.js +4 -0
- package/lib/render/helpers/resolve-assets.js +58 -0
- package/lib/render/views/iframe/component.js +9 -7
- package/lib/render/views/iframe/variation.standalone.js +9 -7
- package/lib/state/file-contents.js +46 -3
- package/lib/state/index.js +1 -0
- package/lib/validator/mocks.js +31 -26
- package/lib/validator/schemas.js +234 -0
- package/package.json +10 -8
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
const DrupalBlockSchema = v.object({
|
|
5
|
+
libraries: v.string(),
|
|
6
|
+
ignorePrefixes: v.optional(v.array(v.string())),
|
|
7
|
+
mapping: v.optional(v.record(v.string(), v.string())),
|
|
8
|
+
autoDiscoveryPrefixes: v.optional(v.nullable(v.array(v.string()))),
|
|
9
|
+
components: v.optional(v.nullable(v.array(v.string()))),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const ConfigSchema = v.object({
|
|
13
|
+
engine: v.picklist(["drupal"]),
|
|
14
|
+
drupal: DrupalBlockSchema,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const NormalizedConfigSchema = v.object({
|
|
18
|
+
engine: v.string(),
|
|
19
|
+
libraries: v.optional(v.string()),
|
|
20
|
+
ignorePrefixes: v.array(v.string()),
|
|
21
|
+
mapping: v.record(v.string(), v.string()),
|
|
22
|
+
autoDiscoveryPrefixes: v.nullable(v.array(v.string())),
|
|
23
|
+
components: v.nullable(v.array(v.string())),
|
|
24
|
+
dryRun: v.boolean(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/** @typedef {v.InferOutput<typeof NormalizedConfigSchema>} NormalizedConfig */
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Loads the .miyagi-assets.js config and merges CLI overrides.
|
|
31
|
+
* @param {object} cliArgs
|
|
32
|
+
* @param {string} [cliArgs.engine] - engine to use (default: "drupal")
|
|
33
|
+
* @param {string} [cliArgs.config] - path to config file
|
|
34
|
+
* @param {string} [cliArgs.libraries] - CLI override for libraries path
|
|
35
|
+
* @param {string[]} [cliArgs.components] - CLI override for components list
|
|
36
|
+
* @param {string[]} [cliArgs.ignorePrefixes] - CLI override for ignore prefixes
|
|
37
|
+
* @param {boolean} [cliArgs.dryRun] - dry-run mode
|
|
38
|
+
* @returns {Promise<NormalizedConfig>} normalized config
|
|
39
|
+
*/
|
|
40
|
+
export async function loadAssetsConfig(cliArgs) {
|
|
41
|
+
let fileConfig = null;
|
|
42
|
+
|
|
43
|
+
const configPath = cliArgs.config || ".miyagi-assets.js";
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const resolved = path.resolve(configPath);
|
|
47
|
+
const mod = await import(resolved);
|
|
48
|
+
fileConfig = mod.default || mod;
|
|
49
|
+
} catch {
|
|
50
|
+
if (!cliArgs.libraries) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Could not load config file "${configPath}" and no --libraries flag provided.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (fileConfig) {
|
|
58
|
+
const result = v.safeParse(ConfigSchema, fileConfig);
|
|
59
|
+
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
const messages = result.issues.map((issue) => {
|
|
62
|
+
const issuePath =
|
|
63
|
+
issue.path?.map((path) => path.key).join(".") || "root";
|
|
64
|
+
return `${issuePath}: ${issue.message}`;
|
|
65
|
+
});
|
|
66
|
+
throw new Error(`Invalid config: ${messages.join("; ")}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { engine } = result.output;
|
|
70
|
+
const engineBlock = result.output[engine];
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
engine,
|
|
74
|
+
libraries: cliArgs.libraries || engineBlock.libraries,
|
|
75
|
+
ignorePrefixes:
|
|
76
|
+
cliArgs.ignorePrefixes || engineBlock.ignorePrefixes || [],
|
|
77
|
+
mapping: engineBlock.mapping || {},
|
|
78
|
+
autoDiscoveryPrefixes: engineBlock.autoDiscoveryPrefixes ?? null,
|
|
79
|
+
components:
|
|
80
|
+
cliArgs.components || engineBlock.components || null,
|
|
81
|
+
dryRun: cliArgs.dryRun || false,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
engine: cliArgs.engine || "drupal",
|
|
87
|
+
libraries: cliArgs.libraries,
|
|
88
|
+
ignorePrefixes: cliArgs.ignorePrefixes || [],
|
|
89
|
+
mapping: {},
|
|
90
|
+
autoDiscoveryPrefixes: null,
|
|
91
|
+
components: cliArgs.components || null,
|
|
92
|
+
dryRun: cliArgs.dryRun || false,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {{src: string, type?: string}} JsEntry
|
|
9
|
+
* @typedef {{prefix: string, name: string}} DepEntry
|
|
10
|
+
* @typedef {{css: string[], js: JsEntry[], dependencies: DepEntry[]}} LibraryEntry
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parses a Drupal *.libraries.yml string into a normalized map.
|
|
15
|
+
* @param {string} yamlContent - raw YAML string (not a file path)
|
|
16
|
+
* @returns {Record<string, LibraryEntry>}
|
|
17
|
+
*/
|
|
18
|
+
export function parseLibrariesYaml(yamlContent) {
|
|
19
|
+
/**
|
|
20
|
+
* @type {Record<string, {
|
|
21
|
+
* css?: Record<string, Record<string, object>>,
|
|
22
|
+
* js?: Record<string, {attributes?: {type?: string}}>,
|
|
23
|
+
* dependencies?: string[]
|
|
24
|
+
* }>}
|
|
25
|
+
*/
|
|
26
|
+
const raw = /** @type {never} */ (yaml.load(yamlContent));
|
|
27
|
+
/** @type {Record<string, LibraryEntry>} */
|
|
28
|
+
const map = {};
|
|
29
|
+
|
|
30
|
+
for (const [name, entry] of Object.entries(raw)) {
|
|
31
|
+
const css = entry.css
|
|
32
|
+
? Object.values(entry.css).flatMap(Object.keys)
|
|
33
|
+
: [];
|
|
34
|
+
|
|
35
|
+
const js = entry.js
|
|
36
|
+
? Object.entries(entry.js).map(([src, opts]) =>
|
|
37
|
+
opts?.attributes?.type
|
|
38
|
+
? { src, type: opts.attributes.type }
|
|
39
|
+
: { src },
|
|
40
|
+
)
|
|
41
|
+
: [];
|
|
42
|
+
|
|
43
|
+
const dependencies = (entry.dependencies || [])
|
|
44
|
+
.filter((dep) => dep.includes("/"))
|
|
45
|
+
.map((dep) => ({
|
|
46
|
+
prefix: dep.slice(0, dep.indexOf("/")),
|
|
47
|
+
name: dep.slice(dep.indexOf("/") + 1),
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
map[name] = { css, js, dependencies };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return map;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Recursively resolves all CSS and JS assets for a library, depth-first.
|
|
58
|
+
* Dependencies are collected before the component's own assets to preserve
|
|
59
|
+
* the CSS cascade (base styles before component styles) and ensure JS
|
|
60
|
+
* dependencies are available before scripts that rely on them.
|
|
61
|
+
* @param {string} libraryName
|
|
62
|
+
* @param {Record<string, LibraryEntry>} librariesMap - output of parseLibrariesYaml
|
|
63
|
+
* @param {string[]} ignorePrefixes - dependency prefixes to skip
|
|
64
|
+
* @returns {{css: string[], js: JsEntry[]}}
|
|
65
|
+
*/
|
|
66
|
+
export function resolveComponentAssets(
|
|
67
|
+
libraryName,
|
|
68
|
+
librariesMap,
|
|
69
|
+
ignorePrefixes,
|
|
70
|
+
) {
|
|
71
|
+
const cssSet = [];
|
|
72
|
+
const jsSet = [];
|
|
73
|
+
const resolved = new Set();
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} name
|
|
77
|
+
* @param {Set<string>} ancestors
|
|
78
|
+
*/
|
|
79
|
+
function walk(name, ancestors) {
|
|
80
|
+
if (ancestors.has(name)) {
|
|
81
|
+
console.warn(
|
|
82
|
+
`Circular dependency detected: "${name}" already in chain.`,
|
|
83
|
+
);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (resolved.has(name)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const lib = librariesMap[name];
|
|
92
|
+
if (!lib) {
|
|
93
|
+
console.warn(
|
|
94
|
+
`Dependency "${name}" not found in libraries — skipping.`,
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const chain = new Set(ancestors);
|
|
100
|
+
chain.add(name);
|
|
101
|
+
|
|
102
|
+
for (const dep of lib.dependencies) {
|
|
103
|
+
if (ignorePrefixes.includes(dep.prefix)) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
walk(dep.name, chain);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const cssFile of lib.css) {
|
|
110
|
+
if (!cssSet.includes(cssFile)) {
|
|
111
|
+
cssSet.push(cssFile);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const jsEntry of lib.js) {
|
|
116
|
+
if (!jsSet.some((entry) => entry.src === jsEntry.src)) {
|
|
117
|
+
jsSet.push(jsEntry);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
resolved.add(name);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
walk(libraryName, new Set());
|
|
125
|
+
return { css: cssSet, js: jsSet };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const DEFAULT_PREFIXES = ["element-", "pattern-", "template-", "component-"];
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Maps a Drupal library name to a miyagi component folder path.
|
|
132
|
+
* Uses explicit mapping first, falls back to auto-discovery.
|
|
133
|
+
* @param {string} libraryName
|
|
134
|
+
* @param {Record<string, string>} mapping - explicit library-to-folder mapping
|
|
135
|
+
* @param {string} componentsFolder - root components folder path
|
|
136
|
+
* @param {string[]} [autoDiscoveryPrefixes] - prefixes to strip when matching folder names
|
|
137
|
+
* @returns {string|null} component folder relative path, or null if not found
|
|
138
|
+
*/
|
|
139
|
+
export function mapLibraryToComponent(
|
|
140
|
+
libraryName,
|
|
141
|
+
mapping,
|
|
142
|
+
componentsFolder,
|
|
143
|
+
autoDiscoveryPrefixes = DEFAULT_PREFIXES,
|
|
144
|
+
) {
|
|
145
|
+
if (mapping[libraryName]) {
|
|
146
|
+
return mapping[libraryName];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const candidates = [libraryName];
|
|
150
|
+
|
|
151
|
+
for (const prefix of autoDiscoveryPrefixes) {
|
|
152
|
+
if (libraryName.startsWith(prefix)) {
|
|
153
|
+
candidates.push(libraryName.slice(prefix.length));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
/** @type {string[]} */
|
|
159
|
+
const searchDirs = [componentsFolder];
|
|
160
|
+
while (searchDirs.length > 0) {
|
|
161
|
+
const dir = /** @type {string} */ (searchDirs.pop());
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(dir)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
168
|
+
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
if (!entry.isDirectory()) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (candidates.includes(entry.name)) {
|
|
175
|
+
return path.relative(
|
|
176
|
+
componentsFolder,
|
|
177
|
+
path.join(dir, entry.name),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
searchDirs.push(path.join(dir, entry.name));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// Swallow filesystem errors (missing/unreadable dirs) intentionally.
|
|
185
|
+
// Caller handles null return with a user-facing warning.
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return null;
|
|
189
|
+
}
|
package/lib/i18n/en.js
CHANGED
|
@@ -70,6 +70,10 @@ export default {
|
|
|
70
70
|
invalid: "Mock data does not match schema file.",
|
|
71
71
|
noSchemaFound:
|
|
72
72
|
"No schema file found or the schema file could not be parsed as valid JSON.",
|
|
73
|
+
schemaMissing:
|
|
74
|
+
"Component {{component}} has no schema file (expected: {{schemaFile}}). Consider adding it to components.ignores if this is expected.",
|
|
75
|
+
schemaParseFailed:
|
|
76
|
+
"Schema file {{schemaFile}} could not be parsed as {{format}}.",
|
|
73
77
|
},
|
|
74
78
|
},
|
|
75
79
|
serverStarted: "Running miyagi server at http://localhost:{{port}}",
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* The miyagi module
|
|
3
5
|
* @module index
|
|
@@ -9,7 +11,11 @@ import log from "./logger.js";
|
|
|
9
11
|
import yargs from "./init/args.js";
|
|
10
12
|
import mockGenerator from "./generator/mocks.js";
|
|
11
13
|
import getConfig from "./config.js";
|
|
12
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
lint,
|
|
16
|
+
component as createComponentViaCli,
|
|
17
|
+
drupalAssets,
|
|
18
|
+
} from "./cli/index.js";
|
|
13
19
|
import apiApp from "../api/app.js";
|
|
14
20
|
|
|
15
21
|
/**
|
|
@@ -57,6 +63,14 @@ function argsIncludeLint(args) {
|
|
|
57
63
|
return args._.includes("lint");
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @param {object} args
|
|
68
|
+
* @returns {boolean}
|
|
69
|
+
*/
|
|
70
|
+
function argsIncludeDrupalAssets(args) {
|
|
71
|
+
return args._.includes("drupal-assets");
|
|
72
|
+
}
|
|
73
|
+
|
|
60
74
|
/**
|
|
61
75
|
* Runs the mock generator
|
|
62
76
|
* @param {object} config - the user configuration object
|
|
@@ -97,26 +111,35 @@ export default async function Miyagi(cmd, { isBuild: isApiBuild } = {}) {
|
|
|
97
111
|
let isComponentGenerator;
|
|
98
112
|
let isMockGenerator;
|
|
99
113
|
let isLinter;
|
|
114
|
+
let isDrupalAssets;
|
|
100
115
|
|
|
101
116
|
if (cmd) {
|
|
102
117
|
isBuild = cmd === "build";
|
|
103
118
|
} else {
|
|
104
|
-
args = yargs.argv;
|
|
119
|
+
args = await yargs.argv;
|
|
105
120
|
isServer = argsIncludeServer(args);
|
|
106
121
|
isBuild = argsIncludeBuild(args);
|
|
107
122
|
isComponentGenerator = argsIncludeComponentGenerator(args);
|
|
108
123
|
isMockGenerator = argsIncludeMockGenerator(args);
|
|
109
124
|
isLinter = argsIncludeLint(args);
|
|
125
|
+
isDrupalAssets = argsIncludeDrupalAssets(args);
|
|
110
126
|
}
|
|
111
127
|
|
|
112
|
-
if (args.verbose) {
|
|
113
|
-
process.env.VERBOSE = args.verbose;
|
|
128
|
+
if (args && args.verbose) {
|
|
129
|
+
process.env.VERBOSE = String(args.verbose);
|
|
114
130
|
}
|
|
115
131
|
|
|
116
132
|
if (isLinter) {
|
|
117
133
|
return lint(args);
|
|
118
134
|
}
|
|
119
135
|
|
|
136
|
+
if (isDrupalAssets) {
|
|
137
|
+
process.env.NODE_ENV = "development";
|
|
138
|
+
global.config = await getConfig(args);
|
|
139
|
+
await drupalAssets(args);
|
|
140
|
+
process.exit();
|
|
141
|
+
}
|
|
142
|
+
|
|
120
143
|
if (isBuild || isComponentGenerator || isServer || isMockGenerator) {
|
|
121
144
|
if (isBuild) {
|
|
122
145
|
process.env.NODE_ENV = "production";
|
package/lib/init/args.js
CHANGED
|
@@ -45,6 +45,44 @@ export default yargs(hideBin(process.argv))
|
|
|
45
45
|
"lint",
|
|
46
46
|
"Validates if the component's mock data matches its JSON schema",
|
|
47
47
|
)
|
|
48
|
+
.command(
|
|
49
|
+
"drupal-assets",
|
|
50
|
+
"Resolves Drupal *.libraries.yml dependencies and updates component $assets in mock files",
|
|
51
|
+
{
|
|
52
|
+
engine: {
|
|
53
|
+
alias: "e",
|
|
54
|
+
description: "Engine to use for asset resolution",
|
|
55
|
+
type: "string",
|
|
56
|
+
choices: ["drupal"],
|
|
57
|
+
default: "drupal",
|
|
58
|
+
},
|
|
59
|
+
config: {
|
|
60
|
+
description: "Path to .miyagi-assets.js config file",
|
|
61
|
+
type: "string",
|
|
62
|
+
default: ".miyagi-assets.js",
|
|
63
|
+
},
|
|
64
|
+
libraries: {
|
|
65
|
+
alias: "l",
|
|
66
|
+
description: "Path to *.libraries.yml (overrides config)",
|
|
67
|
+
type: "string",
|
|
68
|
+
},
|
|
69
|
+
components: {
|
|
70
|
+
alias: "c",
|
|
71
|
+
description: "Library names to process (space-separated)",
|
|
72
|
+
type: "array",
|
|
73
|
+
},
|
|
74
|
+
"ignore-prefixes": {
|
|
75
|
+
description:
|
|
76
|
+
'Dependency prefixes to skip (e.g. "core" to ignore core/jquery)',
|
|
77
|
+
type: "array",
|
|
78
|
+
},
|
|
79
|
+
"dry-run": {
|
|
80
|
+
description: "Print resolved $assets without writing files",
|
|
81
|
+
type: "boolean",
|
|
82
|
+
default: false,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
)
|
|
48
86
|
.help()
|
|
49
87
|
.version(pkgJson.version)
|
|
50
88
|
.alias("help", "h")
|
package/lib/init/config.js
CHANGED
|
@@ -7,6 +7,7 @@ import deepMerge from "deepmerge";
|
|
|
7
7
|
import log from "../logger.js";
|
|
8
8
|
import appConfig from "../default-config.js";
|
|
9
9
|
import { t, available as langAvailable } from "../i18n/index.js";
|
|
10
|
+
import { LINT_LOG_LEVELS } from "../constants/lint-log-levels.js";
|
|
10
11
|
import fs from "fs";
|
|
11
12
|
import path from "path";
|
|
12
13
|
|
|
@@ -216,6 +217,23 @@ export default (userConfig = {}) => {
|
|
|
216
217
|
);
|
|
217
218
|
}
|
|
218
219
|
|
|
220
|
+
if (config.assets.shared) {
|
|
221
|
+
if (config.assets.shared.css) {
|
|
222
|
+
config.assets.shared.css = getCssFilesArray(
|
|
223
|
+
config.assets.shared.css,
|
|
224
|
+
manifest,
|
|
225
|
+
config.assets.root,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (config.assets.shared.js) {
|
|
229
|
+
config.assets.shared.js = getJsFilesArray(
|
|
230
|
+
config.assets.shared.js,
|
|
231
|
+
manifest,
|
|
232
|
+
config.assets.root,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
219
237
|
if (!config.assets.customProperties) {
|
|
220
238
|
config.assets.customProperties = {};
|
|
221
239
|
}
|
|
@@ -309,6 +327,14 @@ export default (userConfig = {}) => {
|
|
|
309
327
|
merged.ui.lang = "en";
|
|
310
328
|
}
|
|
311
329
|
|
|
330
|
+
if (!Object.values(LINT_LOG_LEVELS).includes(merged.lint.logLevel)) {
|
|
331
|
+
log(
|
|
332
|
+
"warn",
|
|
333
|
+
`Invalid config.lint.logLevel "${merged.lint.logLevel}". Falling back to "${defaultUserConfig.lint.logLevel}".`,
|
|
334
|
+
);
|
|
335
|
+
merged.lint.logLevel = defaultUserConfig.lint.logLevel;
|
|
336
|
+
}
|
|
337
|
+
|
|
312
338
|
return merged;
|
|
313
339
|
};
|
|
314
340
|
|
package/lib/init/router.js
CHANGED
package/lib/init/static.js
CHANGED
|
@@ -75,6 +75,35 @@ function registerUserFiles(files) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Registers static routes for assets.shared CSS and JS files.
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
function registerSharedFiles() {
|
|
83
|
+
const { assets } = global.config;
|
|
84
|
+
|
|
85
|
+
if (!assets?.shared) return;
|
|
86
|
+
|
|
87
|
+
for (const file of assets.shared.css || []) {
|
|
88
|
+
if (file.startsWith("https://")) continue;
|
|
89
|
+
|
|
90
|
+
global.app.use(
|
|
91
|
+
path.join("/", path.dirname(file)),
|
|
92
|
+
express.static(path.join(assets.root, path.dirname(file))),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const file of assets.shared.js || []) {
|
|
97
|
+
const src = file.src || file;
|
|
98
|
+
if (src.startsWith("https://")) continue;
|
|
99
|
+
|
|
100
|
+
global.app.use(
|
|
101
|
+
path.join("/", path.dirname(src)),
|
|
102
|
+
express.static(path.join(assets.root, path.dirname(src))),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
78
107
|
/**
|
|
79
108
|
* @returns {void}
|
|
80
109
|
*/
|
|
@@ -128,6 +157,7 @@ export default function initStatic() {
|
|
|
128
157
|
registerUserComponentAssets();
|
|
129
158
|
registerUserFiles("css");
|
|
130
159
|
registerUserFiles("js");
|
|
160
|
+
registerSharedFiles();
|
|
131
161
|
registerCustomPropertyFiles();
|
|
132
162
|
registerAssetFolder();
|
|
133
163
|
}
|
package/lib/init/watcher.js
CHANGED
|
@@ -292,6 +292,25 @@ export default function Watcher(server) {
|
|
|
292
292
|
|
|
293
293
|
const { components, docs, assets, extensions } = global.config;
|
|
294
294
|
|
|
295
|
+
const sharedCssToWatch = (assets.shared?.css || [])
|
|
296
|
+
.filter(
|
|
297
|
+
(f) =>
|
|
298
|
+
!f.startsWith("http://") &&
|
|
299
|
+
!f.startsWith("https://") &&
|
|
300
|
+
!f.startsWith("://"),
|
|
301
|
+
)
|
|
302
|
+
.map((f) => path.join(global.config.assets.root, f));
|
|
303
|
+
|
|
304
|
+
const sharedJsToWatch = (assets.shared?.js || [])
|
|
305
|
+
.map((file) => file.src || file)
|
|
306
|
+
.filter(
|
|
307
|
+
(f) =>
|
|
308
|
+
!f.startsWith("http://") &&
|
|
309
|
+
!f.startsWith("https://") &&
|
|
310
|
+
!f.startsWith("://"),
|
|
311
|
+
)
|
|
312
|
+
.map((f) => path.join(global.config.assets.root, f));
|
|
313
|
+
|
|
295
314
|
const foldersToWatch = [
|
|
296
315
|
...assets.folder.map((f) => path.join(global.config.assets.root, f)),
|
|
297
316
|
...assets.css
|
|
@@ -311,6 +330,8 @@ export default function Watcher(server) {
|
|
|
311
330
|
!f.startsWith("://"),
|
|
312
331
|
)
|
|
313
332
|
.map((f) => path.join(global.config.assets.root, f)),
|
|
333
|
+
...sharedCssToWatch,
|
|
334
|
+
...sharedJsToWatch,
|
|
314
335
|
];
|
|
315
336
|
|
|
316
337
|
if (components.folder) {
|
package/lib/logger.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LINT_LOG_LEVEL_ORDER,
|
|
3
|
+
LINT_LOG_LEVELS,
|
|
4
|
+
} from "./constants/lint-log-levels.js";
|
|
5
|
+
|
|
1
6
|
const COLORS = {
|
|
2
7
|
grey: "\x1b[90m",
|
|
3
8
|
red: "\x1b[31m",
|
|
@@ -21,8 +26,17 @@ const TYPES = {
|
|
|
21
26
|
* @param {string|Error} [verboseMessage]
|
|
22
27
|
*/
|
|
23
28
|
export default function log(type, message, verboseMessage) {
|
|
24
|
-
if (process.env.MIYAGI_JS_API)
|
|
25
|
-
|
|
29
|
+
if (process.env.MIYAGI_JS_API) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!(type in TYPES)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!shouldLogType(type)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
26
40
|
|
|
27
41
|
const date = new Date();
|
|
28
42
|
const year = date.getFullYear();
|
|
@@ -59,6 +73,27 @@ export default function log(type, message, verboseMessage) {
|
|
|
59
73
|
}
|
|
60
74
|
}
|
|
61
75
|
|
|
76
|
+
/**
|
|
77
|
+
* @param {string} type
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
function shouldLogType(type) {
|
|
81
|
+
if (process.env.MIYAGI_LOG_CONTEXT !== "lint") {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const configuredLevel = process.env.MIYAGI_LOG_LEVEL || "error";
|
|
86
|
+
const normalizedType = type === "success" ? "info" : type;
|
|
87
|
+
const configuredLevelValue =
|
|
88
|
+
LINT_LOG_LEVEL_ORDER[configuredLevel] ??
|
|
89
|
+
LINT_LOG_LEVEL_ORDER[LINT_LOG_LEVELS.ERROR];
|
|
90
|
+
const typeLevelValue =
|
|
91
|
+
LINT_LOG_LEVEL_ORDER[normalizedType] ??
|
|
92
|
+
LINT_LOG_LEVEL_ORDER[LINT_LOG_LEVELS.INFO];
|
|
93
|
+
|
|
94
|
+
return typeLevelValue <= configuredLevelValue;
|
|
95
|
+
}
|
|
96
|
+
|
|
62
97
|
/**
|
|
63
98
|
* @param {string} color
|
|
64
99
|
* @param {string} str
|
package/lib/mocks/get.js
CHANGED
|
@@ -22,6 +22,7 @@ export const getComponentData = async function getComponentData(component) {
|
|
|
22
22
|
|
|
23
23
|
if (componentJson) {
|
|
24
24
|
context = [];
|
|
25
|
+
const componentDeclaredAssets = componentJson.$assets || null;
|
|
25
26
|
let componentData = helpers.removeInternalKeys(componentJson);
|
|
26
27
|
const rootData = helpers.cloneDeep(componentData);
|
|
27
28
|
const componentVariations = componentJson.$variants;
|
|
@@ -55,6 +56,7 @@ export const getComponentData = async function getComponentData(component) {
|
|
|
55
56
|
resolved: resolved,
|
|
56
57
|
raw: merged,
|
|
57
58
|
name: variationJson.$name,
|
|
59
|
+
$assets: componentDeclaredAssets,
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -67,6 +69,7 @@ export const getComponentData = async function getComponentData(component) {
|
|
|
67
69
|
resolved: data.resolved,
|
|
68
70
|
raw: data.merged,
|
|
69
71
|
name: componentJson.$name || config.defaultVariationName,
|
|
72
|
+
$assets: componentDeclaredAssets,
|
|
70
73
|
});
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -85,6 +88,7 @@ export const getComponentData = async function getComponentData(component) {
|
|
|
85
88
|
resolved: componentJson.$hidden ? {} : resolved,
|
|
86
89
|
raw: componentJson.$hidden ? {} : merged,
|
|
87
90
|
name: componentJson.$name || config.defaultVariationName,
|
|
91
|
+
$assets: componentDeclaredAssets,
|
|
88
92
|
});
|
|
89
93
|
}
|
|
90
94
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the final CSS and JS asset lists for a component render.
|
|
3
|
+
*
|
|
4
|
+
* Resolution logic:
|
|
5
|
+
* - If componentAssets ($assets from mocks) is provided:
|
|
6
|
+
* shared + componentAssets
|
|
7
|
+
* - Else if isolateComponents is true:
|
|
8
|
+
* shared only
|
|
9
|
+
* - Else (legacy fallback):
|
|
10
|
+
* global.config.assets.css / .js
|
|
11
|
+
* @param {object|null|undefined} componentAssets - the $assets declaration from mocks, or null/undefined
|
|
12
|
+
* @returns {{ cssFiles: string[], jsFilesHead: object[], jsFilesBody: object[] }}
|
|
13
|
+
*/
|
|
14
|
+
export default function resolveAssets(componentAssets) {
|
|
15
|
+
const { shared, isolateComponents, css, js } = global.config.assets;
|
|
16
|
+
|
|
17
|
+
if (componentAssets) {
|
|
18
|
+
const mergedCss = [
|
|
19
|
+
...shared.css,
|
|
20
|
+
...(componentAssets.css || []),
|
|
21
|
+
];
|
|
22
|
+
const mergedJs = [
|
|
23
|
+
...shared.js,
|
|
24
|
+
...(componentAssets.js || []),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
cssFiles: mergedCss,
|
|
29
|
+
jsFilesHead: mergedJs.filter(
|
|
30
|
+
(entry) => entry.position === "head" || !entry.position,
|
|
31
|
+
),
|
|
32
|
+
jsFilesBody: mergedJs.filter(
|
|
33
|
+
(entry) => entry.position === "body",
|
|
34
|
+
),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isolateComponents) {
|
|
39
|
+
return {
|
|
40
|
+
cssFiles: [...shared.css],
|
|
41
|
+
jsFilesHead: shared.js.filter(
|
|
42
|
+
(entry) => entry.position === "head" || !entry.position,
|
|
43
|
+
),
|
|
44
|
+
jsFilesBody: shared.js.filter(
|
|
45
|
+
(entry) => entry.position === "body",
|
|
46
|
+
),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Legacy fallback: return all global assets
|
|
51
|
+
return {
|
|
52
|
+
cssFiles: css,
|
|
53
|
+
jsFilesHead: js.filter(
|
|
54
|
+
(entry) => entry.position === "head" || !entry.position,
|
|
55
|
+
),
|
|
56
|
+
jsFilesBody: js.filter((entry) => entry.position === "body"),
|
|
57
|
+
};
|
|
58
|
+
}
|