design-embed 0.1.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/LICENSE +9 -0
- package/README.md +5 -0
- package/dist/args.js +36 -0
- package/dist/cli.js +35 -0
- package/dist/commands/check.js +4 -0
- package/dist/commands/compile.js +157 -0
- package/dist/commands/generateTests.js +113 -0
- package/dist/commands/init.js +102 -0
- package/dist/commands/plugin.js +68 -0
- package/dist/index.js +2 -0
- package/node_modules/@design-embed/config/README.md +5 -0
- package/node_modules/@design-embed/config/dist/index.js +283 -0
- package/node_modules/@design-embed/config/package.json +19 -0
- package/node_modules/@design-embed/config/src/index.ts +518 -0
- package/node_modules/@design-embed/core/README.md +5 -0
- package/node_modules/@design-embed/core/dist/diagnostics/diagnostic.js +3 -0
- package/node_modules/@design-embed/core/dist/diagnostics/jsonDiagnostic.js +35 -0
- package/node_modules/@design-embed/core/dist/index.js +351 -0
- package/node_modules/@design-embed/core/dist/pipeline/checkMode.js +29 -0
- package/node_modules/@design-embed/core/dist/plugins/pluginApi.js +1 -0
- package/node_modules/@design-embed/core/dist/plugins/pluginRegistry.js +25 -0
- package/node_modules/@design-embed/core/package.json +19 -0
- package/node_modules/@design-embed/core/src/diagnostics/diagnostic.ts +18 -0
- package/node_modules/@design-embed/core/src/diagnostics/jsonDiagnostic.ts +51 -0
- package/node_modules/@design-embed/core/src/index.ts +591 -0
- package/node_modules/@design-embed/core/src/pipeline/checkMode.ts +46 -0
- package/node_modules/@design-embed/core/src/plugins/pluginApi.ts +78 -0
- package/node_modules/@design-embed/core/src/plugins/pluginRegistry.ts +37 -0
- package/package.json +42 -0
- package/src/args.ts +57 -0
- package/src/cli.ts +42 -0
- package/src/commands/check.ts +7 -0
- package/src/commands/compile.ts +198 -0
- package/src/commands/generateTests.ts +140 -0
- package/src/commands/init.ts +114 -0
- package/src/commands/plugin.ts +89 -0
- package/src/index.ts +2 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
/**
|
|
5
|
+
* Helper to define configuration with type safety.
|
|
6
|
+
*
|
|
7
|
+
* @param config - The configuration object.
|
|
8
|
+
* @returns The same configuration object.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* export default defineConfig({
|
|
12
|
+
* output: { target: 'react' }
|
|
13
|
+
* });
|
|
14
|
+
*/
|
|
15
|
+
export function defineConfig(config) {
|
|
16
|
+
return config;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Asynchronously loads a configuration file from disk.
|
|
20
|
+
* Supports .ts, .js, and .mjs files via dynamic import.
|
|
21
|
+
*
|
|
22
|
+
* @param configPath - Path to the config file.
|
|
23
|
+
* @param cwd - Current working directory.
|
|
24
|
+
* @returns A promise resolving to the load result.
|
|
25
|
+
*/
|
|
26
|
+
export async function loadConfig(configPath, cwd = process.cwd()) {
|
|
27
|
+
const diagnostics = [];
|
|
28
|
+
const resolvedPath = isAbsolute(configPath)
|
|
29
|
+
? configPath
|
|
30
|
+
: resolve(cwd, configPath);
|
|
31
|
+
if (!existsSync(resolvedPath)) {
|
|
32
|
+
return {
|
|
33
|
+
diagnostics: [
|
|
34
|
+
{
|
|
35
|
+
code: "CONFIG_NOT_FOUND",
|
|
36
|
+
message: `Config file not found: ${resolvedPath}`,
|
|
37
|
+
severity: "error",
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!/\.(ts|js|mjs)$/.test(resolvedPath)) {
|
|
43
|
+
return {
|
|
44
|
+
diagnostics: [
|
|
45
|
+
{
|
|
46
|
+
code: "CONFIG_UNSUPPORTED_FORMAT",
|
|
47
|
+
message: `Unsupported config format: ${resolvedPath}. Only .ts, .js, and .mjs are supported.`,
|
|
48
|
+
severity: "error",
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const module = await import(pathToFileURL(resolvedPath).href);
|
|
55
|
+
const config = module.default ?? module.config;
|
|
56
|
+
if (!config) {
|
|
57
|
+
return {
|
|
58
|
+
diagnostics: [
|
|
59
|
+
{
|
|
60
|
+
code: "CONFIG_INVALID",
|
|
61
|
+
message: `Config file must export a default object or a named 'config' object: ${resolvedPath}`,
|
|
62
|
+
severity: "error",
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
diagnostics.push(...validateConfig(config));
|
|
68
|
+
diagnostics.push(...validateTransformerPaths(config, dirname(resolvedPath)));
|
|
69
|
+
return { config, diagnostics };
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
+
return {
|
|
74
|
+
diagnostics: [
|
|
75
|
+
{
|
|
76
|
+
code: "CONFIG_INVALID",
|
|
77
|
+
message: `Failed to load config file: ${message}`,
|
|
78
|
+
severity: "error",
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function validateConfig(config) {
|
|
85
|
+
const diagnostics = [];
|
|
86
|
+
const target = config.output?.target;
|
|
87
|
+
const styleMode = config.output?.styleMode;
|
|
88
|
+
if (target && target !== "html" && target !== "react") {
|
|
89
|
+
diagnostics.push({
|
|
90
|
+
code: "UNSUPPORTED_TARGET",
|
|
91
|
+
message: `Unsupported output target: ${target}`,
|
|
92
|
+
severity: "error",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (styleMode &&
|
|
96
|
+
styleMode !== "inline" &&
|
|
97
|
+
styleMode !== "css-modules" &&
|
|
98
|
+
styleMode !== "tailwind") {
|
|
99
|
+
diagnostics.push({
|
|
100
|
+
code: "STYLE_MODE_UNSUPPORTED",
|
|
101
|
+
message: `Unsupported style mode: ${styleMode}`,
|
|
102
|
+
severity: "error",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
for (const [index, component] of (config.components ?? []).entries()) {
|
|
106
|
+
if (!component.selector || typeof component.selector !== "string") {
|
|
107
|
+
diagnostics.push({
|
|
108
|
+
code: "COMPONENT_SELECTOR_INVALID",
|
|
109
|
+
message: `Component mapping ${index} must include a selector.`,
|
|
110
|
+
severity: "error",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (!component.component || typeof component.component !== "string") {
|
|
114
|
+
diagnostics.push({
|
|
115
|
+
code: "COMPONENT_IMPORT_INVALID",
|
|
116
|
+
message: `Component mapping ${index} must include a component path.`,
|
|
117
|
+
severity: "error",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const spacing = config.tokens?.spacing;
|
|
122
|
+
if (spacing?.unit && spacing.unit !== "px" && spacing.unit !== "rem") {
|
|
123
|
+
diagnostics.push({
|
|
124
|
+
code: "TOKEN_SPACING_UNIT_INVALID",
|
|
125
|
+
message: `Unsupported spacing unit: ${spacing.unit}`,
|
|
126
|
+
severity: "error",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (spacing?.threshold !== undefined && !Number.isFinite(spacing.threshold)) {
|
|
130
|
+
diagnostics.push({
|
|
131
|
+
code: "TOKEN_SPACING_THRESHOLD_INVALID",
|
|
132
|
+
message: "Spacing threshold must be a finite number.",
|
|
133
|
+
severity: "error",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
for (const [name, value] of Object.entries(spacing?.values ?? {})) {
|
|
137
|
+
if (!Number.isFinite(value)) {
|
|
138
|
+
diagnostics.push({
|
|
139
|
+
code: "TOKEN_SPACING_VALUE_INVALID",
|
|
140
|
+
message: `Spacing token ${name} must be a finite number.`,
|
|
141
|
+
severity: "error",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const [name, value] of Object.entries(config.tokens?.colors ?? {})) {
|
|
146
|
+
if (!/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(value)) {
|
|
147
|
+
diagnostics.push({
|
|
148
|
+
code: "TOKEN_COLOR_INVALID",
|
|
149
|
+
message: `Color token ${name} must be a hex color.`,
|
|
150
|
+
severity: "error",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (config.tokens?.colorThreshold !== undefined &&
|
|
155
|
+
!Number.isFinite(config.tokens.colorThreshold)) {
|
|
156
|
+
diagnostics.push({
|
|
157
|
+
code: "TOKEN_COLOR_THRESHOLD_INVALID",
|
|
158
|
+
message: "Color threshold must be a finite number.",
|
|
159
|
+
severity: "error",
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
for (const [groupName, group] of Object.entries({
|
|
163
|
+
sizing: config.tokens?.sizing,
|
|
164
|
+
typography: config.tokens?.typography,
|
|
165
|
+
})) {
|
|
166
|
+
if (!group) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (group.unit && group.unit !== "px" && group.unit !== "rem") {
|
|
170
|
+
diagnostics.push({
|
|
171
|
+
code: "TOKEN_NUMERIC_UNIT_INVALID",
|
|
172
|
+
message: `Unsupported ${groupName} unit: ${group.unit}`,
|
|
173
|
+
severity: "error",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
if (group.threshold !== undefined && !Number.isFinite(group.threshold)) {
|
|
177
|
+
diagnostics.push({
|
|
178
|
+
code: "TOKEN_NUMERIC_THRESHOLD_INVALID",
|
|
179
|
+
message: `${groupName} threshold must be a finite number.`,
|
|
180
|
+
severity: "error",
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
for (const [name, value] of Object.entries(group.values ?? {})) {
|
|
184
|
+
if (!Number.isFinite(value)) {
|
|
185
|
+
diagnostics.push({
|
|
186
|
+
code: "TOKEN_NUMERIC_VALUE_INVALID",
|
|
187
|
+
message: `${groupName} token ${name} must be a finite number.`,
|
|
188
|
+
severity: "error",
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const [index, transformer] of (config.transformers ?? []).entries()) {
|
|
194
|
+
if (!transformer.path || typeof transformer.path !== "string") {
|
|
195
|
+
diagnostics.push({
|
|
196
|
+
code: "TRANSFORMER_PATH_INVALID",
|
|
197
|
+
message: `Transformer ${index} must include a path.`,
|
|
198
|
+
severity: "error",
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
if (transformer.order !== undefined &&
|
|
202
|
+
!Number.isFinite(transformer.order)) {
|
|
203
|
+
diagnostics.push({
|
|
204
|
+
code: "TRANSFORMER_ORDER_INVALID",
|
|
205
|
+
message: `Transformer ${index} order must be a finite number.`,
|
|
206
|
+
severity: "error",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
validateTestGeneration(config.tests, diagnostics);
|
|
211
|
+
return diagnostics;
|
|
212
|
+
}
|
|
213
|
+
function validateTestGeneration(tests, diagnostics) {
|
|
214
|
+
if (!tests) {
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (tests.runner && tests.runner !== "playwright") {
|
|
218
|
+
diagnostics.push({
|
|
219
|
+
code: "TEST_RUNNER_UNSUPPORTED",
|
|
220
|
+
message: `Unsupported test runner: ${tests.runner}`,
|
|
221
|
+
severity: "error",
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
for (const [index, viewport] of (tests.viewports ?? []).entries()) {
|
|
225
|
+
if (!Number.isFinite(viewport.width) || viewport.width <= 0) {
|
|
226
|
+
diagnostics.push({
|
|
227
|
+
code: "TEST_VIEWPORT_WIDTH_INVALID",
|
|
228
|
+
message: `Test viewport ${index} width must be a positive finite number.`,
|
|
229
|
+
severity: "error",
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (!Number.isFinite(viewport.height) || viewport.height <= 0) {
|
|
233
|
+
diagnostics.push({
|
|
234
|
+
code: "TEST_VIEWPORT_HEIGHT_INVALID",
|
|
235
|
+
message: `Test viewport ${index} height must be a positive finite number.`,
|
|
236
|
+
severity: "error",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const [index, state] of (tests.states ?? []).entries()) {
|
|
241
|
+
if (!state.name || typeof state.name !== "string") {
|
|
242
|
+
diagnostics.push({
|
|
243
|
+
code: "TEST_STATE_NAME_INVALID",
|
|
244
|
+
message: `Test state ${index} must include a name.`,
|
|
245
|
+
severity: "error",
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (tests.assertions?.layoutTolerance !== undefined &&
|
|
250
|
+
(!Number.isFinite(tests.assertions.layoutTolerance) ||
|
|
251
|
+
tests.assertions.layoutTolerance < 0)) {
|
|
252
|
+
diagnostics.push({
|
|
253
|
+
code: "TEST_LAYOUT_TOLERANCE_INVALID",
|
|
254
|
+
message: "Test layout tolerance must be a finite number greater than or equal to 0.",
|
|
255
|
+
severity: "error",
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function isPackageName(path) {
|
|
260
|
+
return !path.startsWith(".") && !isAbsolute(path);
|
|
261
|
+
}
|
|
262
|
+
function validateTransformerPaths(config, configDir) {
|
|
263
|
+
const diagnostics = [];
|
|
264
|
+
for (const transformer of config.transformers ?? []) {
|
|
265
|
+
if (!transformer.path || typeof transformer.path !== "string") {
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (isPackageName(transformer.path)) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const resolvedPath = isAbsolute(transformer.path)
|
|
272
|
+
? transformer.path
|
|
273
|
+
: resolve(configDir, transformer.path);
|
|
274
|
+
if (!existsSync(resolvedPath)) {
|
|
275
|
+
diagnostics.push({
|
|
276
|
+
code: "TRANSFORMER_NOT_FOUND",
|
|
277
|
+
message: `Transformer file not found: ${resolvedPath}`,
|
|
278
|
+
severity: "error",
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return diagnostics;
|
|
283
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@design-embed/config",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./src/index.ts",
|
|
9
|
+
"development": "./src/index.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src",
|
|
16
|
+
"!src/**/*.test.ts",
|
|
17
|
+
"README.md"
|
|
18
|
+
]
|
|
19
|
+
}
|