@design-embed/plugin-figma-html 0.1.0 → 1.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.
Files changed (59) hide show
  1. package/LICENSE +1 -1
  2. package/dist/compilers/compilerUtils.d.mts +11 -0
  3. package/dist/compilers/compilerUtils.mjs +119 -0
  4. package/dist/compilers/htmlCompiler.d.mts +6 -0
  5. package/dist/compilers/htmlCompiler.mjs +31 -0
  6. package/dist/compilers/index.d.mts +11 -0
  7. package/dist/compilers/index.mjs +17 -0
  8. package/dist/compilers/index.test.d.mts +1 -0
  9. package/dist/compilers/index.test.mjs +45 -0
  10. package/dist/compilers/reactCompiler.d.mts +6 -0
  11. package/dist/compilers/reactCompiler.mjs +47 -0
  12. package/dist/compilers/vanjsCompiler.d.mts +6 -0
  13. package/dist/compilers/vanjsCompiler.mjs +47 -0
  14. package/dist/design-embed/src/core/diagnostics/diagnostic.d.mts +16 -0
  15. package/dist/design-embed/src/core/nodes.d.mts +14 -0
  16. package/dist/design-embed/src/core/plugins/pluginApi.d.mts +34 -0
  17. package/dist/external/figmaApi.d.mts +17 -0
  18. package/dist/external/figmaApi.mjs +55 -0
  19. package/dist/external/figmaApi.test.d.mts +1 -0
  20. package/dist/external/figmaApi.test.mjs +101 -0
  21. package/dist/external/imageDownloader.d.mts +17 -0
  22. package/dist/external/imageDownloader.mjs +66 -0
  23. package/dist/external/imageDownloader.test.d.mts +1 -0
  24. package/dist/external/imageDownloader.test.mjs +42 -0
  25. package/dist/index.d.mts +8 -0
  26. package/dist/index.mjs +7 -0
  27. package/dist/plugin.d.mts +16 -0
  28. package/dist/plugin.mjs +43 -0
  29. package/dist/types.d.mts +84 -0
  30. package/package.json +12 -10
  31. package/src/plugin.ts +2 -3
  32. package/dist/compilers/compilerUtils.js +0 -182
  33. package/dist/compilers/htmlCompiler.js +0 -35
  34. package/dist/compilers/index.js +0 -17
  35. package/dist/compilers/reactCompiler.js +0 -58
  36. package/dist/compilers/vanjsCompiler.js +0 -55
  37. package/dist/external/figmaApi.js +0 -74
  38. package/dist/external/imageDownloader.js +0 -82
  39. package/dist/index.js +0 -3
  40. package/dist/plugin.js +0 -56
  41. package/node_modules/@design-embed/config/README.md +0 -5
  42. package/node_modules/@design-embed/config/dist/index.js +0 -283
  43. package/node_modules/@design-embed/config/package.json +0 -19
  44. package/node_modules/@design-embed/config/src/index.ts +0 -518
  45. package/node_modules/@design-embed/core/README.md +0 -5
  46. package/node_modules/@design-embed/core/dist/diagnostics/diagnostic.js +0 -3
  47. package/node_modules/@design-embed/core/dist/diagnostics/jsonDiagnostic.js +0 -35
  48. package/node_modules/@design-embed/core/dist/index.js +0 -351
  49. package/node_modules/@design-embed/core/dist/pipeline/checkMode.js +0 -29
  50. package/node_modules/@design-embed/core/dist/plugins/pluginApi.js +0 -1
  51. package/node_modules/@design-embed/core/dist/plugins/pluginRegistry.js +0 -25
  52. package/node_modules/@design-embed/core/package.json +0 -19
  53. package/node_modules/@design-embed/core/src/diagnostics/diagnostic.ts +0 -18
  54. package/node_modules/@design-embed/core/src/diagnostics/jsonDiagnostic.ts +0 -51
  55. package/node_modules/@design-embed/core/src/index.ts +0 -591
  56. package/node_modules/@design-embed/core/src/pipeline/checkMode.ts +0 -46
  57. package/node_modules/@design-embed/core/src/plugins/pluginApi.ts +0 -78
  58. package/node_modules/@design-embed/core/src/plugins/pluginRegistry.ts +0 -37
  59. /package/dist/{types.js → types.mjs} +0 -0
@@ -1,518 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { dirname, isAbsolute, resolve } from "node:path";
3
- import { pathToFileURL } from "node:url";
4
-
5
- /**
6
- * Supported output targets for the compiler.
7
- */
8
- export type OutputTarget = "html" | "react";
9
-
10
- /**
11
- * Minimal interface every source plugin instance must satisfy.
12
- * Keep this interface in the config package so plugin packages can implement
13
- * it without pulling in the heavier core package.
14
- */
15
- export interface PluginDefinition {
16
- readonly name: string;
17
- }
18
-
19
- /**
20
- * A diagnostic reported during configuration loading or validation.
21
- */
22
- export interface ConfigDiagnostic {
23
- /** Unique error code. */
24
- code: string;
25
- /** Human-readable message. */
26
- message: string;
27
- /** Severity of the issue. */
28
- severity: "error" | "warning" | "info";
29
- }
30
-
31
- /**
32
- * The root configuration object for design-embed.
33
- */
34
- export interface DesignEmbedConfig {
35
- /** Output settings for the generated code. */
36
- output?: {
37
- /** Directory where generated views will be written. */
38
- viewsDir?: string;
39
- /** Directory for page-level assemblies. */
40
- assembliesDir?: string;
41
- /** The target framework or format. */
42
- target?: OutputTarget;
43
- /** Name of the generated component/view. */
44
- viewName?: string;
45
- /** How to handle styles: inline, Tailwind, or CSS Modules. */
46
- styleMode?: StyleMode;
47
- };
48
- /** Mappings from HTML selectors to project components. */
49
- components?: ComponentMapping[];
50
- /** Design token scales for style snapping. */
51
- tokens?: TokenConfig;
52
- /** Mappings for Tailwind utility classes. */
53
- styleMappings?: StyleMappings;
54
- /** Source plugin instances to run via `design-embed plugin`. */
55
- plugins?: PluginDefinition[];
56
- /** Pipeline transformers to modify the AST. */
57
- transformers?: TransformerConfig[];
58
- /** Visual and layout test generation settings. */
59
- tests?: TestGenerationConfig;
60
- }
61
-
62
- /**
63
- * Configuration for generated regression tests.
64
- */
65
- export interface TestGenerationConfig {
66
- /** Directory where generated test files and reference fixtures are written. */
67
- outputDir?: string;
68
- /** Test runner emitted by the generator. */
69
- runner?: "playwright";
70
- /** Source artifact paths used as the visual/layout reference. */
71
- source?: {
72
- /** Path to the reference design HTML, relative to the config directory or cwd. */
73
- html?: string;
74
- /** Optional path to external reference CSS, relative to the config directory or cwd. */
75
- css?: string;
76
- };
77
- /** Viewports to verify. */
78
- viewports?: TestViewport[];
79
- /** Interaction states to verify for every viewport. */
80
- states?: TestState[];
81
- /** Assertion settings. */
82
- assertions?: TestAssertions;
83
- }
84
-
85
- export interface TestViewport {
86
- /** Stable viewport name used in test titles. */
87
- name?: string;
88
- width: number;
89
- height: number;
90
- }
91
-
92
- export interface TestState {
93
- /** Stable state name used in test titles. */
94
- name: string;
95
- /** Selector to hover before assertions. */
96
- hover?: string;
97
- /** Selector to focus before assertions. */
98
- focus?: string;
99
- /** Selector to click before assertions. */
100
- click?: string;
101
- /** Selector to wait for before assertions. */
102
- waitFor?: string;
103
- }
104
-
105
- export interface TestAssertions {
106
- /** Whether to compare full-page screenshots. Defaults to true. */
107
- screenshot?: boolean;
108
- /** Whether to compare element bounding boxes. Defaults to true. */
109
- layout?: boolean;
110
- /** Maximum x/y/width/height drift in CSS pixels. Defaults to 0. */
111
- layoutTolerance?: number;
112
- /** Selectors to collect for layout comparison. Defaults to [":scope", ":scope *"]. */
113
- selectors?: string[];
114
- }
115
-
116
- /**
117
- * Available styling modes.
118
- */
119
- export type StyleMode = "inline" | "css-modules" | "tailwind";
120
-
121
- /**
122
- * Defines how to map a design element to a project component.
123
- */
124
- export interface ComponentMapping {
125
- /** CSS selector to match the element in the design HTML. */
126
- selector: string;
127
- /** Import path of the project component. */
128
- component: string;
129
- /** Named export of the component. */
130
- importName?: string;
131
- /** Prop values to pass, supports $text, $children, and $attr expressions. */
132
- props?: Record<string, string>;
133
- }
134
-
135
- /**
136
- * Configuration for design tokens.
137
- */
138
- export interface TokenConfig {
139
- /** Spacing scale (e.g. padding, margin, gap). */
140
- spacing?: {
141
- /** Unit to use in generated styles. */
142
- unit?: "px" | "rem";
143
- /** Max distance for value snapping. */
144
- threshold?: number;
145
- /** The token scale mapping names to values. */
146
- values?: Record<string, number>;
147
- };
148
- /** Sizing scale (e.g. width, height). */
149
- sizing?: NumericTokenGroup;
150
- /** Typography scale (e.g. font-size, line-height). */
151
- typography?: NumericTokenGroup;
152
- /** Border radius scale. */
153
- radius?: Record<string, number>;
154
- /** Border width scale. */
155
- borderWidth?: Record<string, number>;
156
- /** Box shadow scale. */
157
- shadow?: Record<string, string>;
158
- /** Color palette. */
159
- colors?: Record<string, string>;
160
- /** Color matching threshold (CIE76). */
161
- colorThreshold?: number;
162
- }
163
-
164
- export interface NumericTokenGroup {
165
- unit?: "px" | "rem";
166
- threshold?: number;
167
- values?: Record<string, number>;
168
- }
169
-
170
- export type StyleMappings = Record<string, Record<string, string>>;
171
-
172
- /**
173
- * Configuration for a transformer plugin.
174
- */
175
- export interface TransformerConfig {
176
- /** Local file path or npm package name. */
177
- path: string;
178
- /** Execution order (lower numbers run first). */
179
- order?: number;
180
- }
181
-
182
- /**
183
- * Result of loading a configuration file.
184
- */
185
- export interface LoadConfigResult {
186
- /** The loaded and validated config, if successful. */
187
- config?: DesignEmbedConfig;
188
- /** Any errors or warnings encountered during loading. */
189
- diagnostics: ConfigDiagnostic[];
190
- }
191
-
192
- /**
193
- * Helper to define configuration with type safety.
194
- *
195
- * @param config - The configuration object.
196
- * @returns The same configuration object.
197
- *
198
- * @example
199
- * export default defineConfig({
200
- * output: { target: 'react' }
201
- * });
202
- */
203
- export function defineConfig(config: DesignEmbedConfig): DesignEmbedConfig {
204
- return config;
205
- }
206
-
207
- /**
208
- * Asynchronously loads a configuration file from disk.
209
- * Supports .ts, .js, and .mjs files via dynamic import.
210
- *
211
- * @param configPath - Path to the config file.
212
- * @param cwd - Current working directory.
213
- * @returns A promise resolving to the load result.
214
- */
215
- export async function loadConfig(
216
- configPath: string,
217
- cwd = process.cwd(),
218
- ): Promise<LoadConfigResult> {
219
- const diagnostics: ConfigDiagnostic[] = [];
220
- const resolvedPath = isAbsolute(configPath)
221
- ? configPath
222
- : resolve(cwd, configPath);
223
-
224
- if (!existsSync(resolvedPath)) {
225
- return {
226
- diagnostics: [
227
- {
228
- code: "CONFIG_NOT_FOUND",
229
- message: `Config file not found: ${resolvedPath}`,
230
- severity: "error",
231
- },
232
- ],
233
- };
234
- }
235
-
236
- if (!/\.(ts|js|mjs)$/.test(resolvedPath)) {
237
- return {
238
- diagnostics: [
239
- {
240
- code: "CONFIG_UNSUPPORTED_FORMAT",
241
- message: `Unsupported config format: ${resolvedPath}. Only .ts, .js, and .mjs are supported.`,
242
- severity: "error",
243
- },
244
- ],
245
- };
246
- }
247
-
248
- try {
249
- const module = await import(pathToFileURL(resolvedPath).href);
250
- const config = module.default ?? module.config;
251
-
252
- if (!config) {
253
- return {
254
- diagnostics: [
255
- {
256
- code: "CONFIG_INVALID",
257
- message: `Config file must export a default object or a named 'config' object: ${resolvedPath}`,
258
- severity: "error",
259
- },
260
- ],
261
- };
262
- }
263
-
264
- diagnostics.push(...validateConfig(config));
265
- diagnostics.push(
266
- ...validateTransformerPaths(config, dirname(resolvedPath)),
267
- );
268
- return { config, diagnostics };
269
- } catch (error) {
270
- const message = error instanceof Error ? error.message : String(error);
271
- return {
272
- diagnostics: [
273
- {
274
- code: "CONFIG_INVALID",
275
- message: `Failed to load config file: ${message}`,
276
- severity: "error",
277
- },
278
- ],
279
- };
280
- }
281
- }
282
-
283
- export function validateConfig(config: DesignEmbedConfig): ConfigDiagnostic[] {
284
- const diagnostics: ConfigDiagnostic[] = [];
285
- const target = config.output?.target;
286
- const styleMode = config.output?.styleMode;
287
-
288
- if (target && target !== "html" && target !== "react") {
289
- diagnostics.push({
290
- code: "UNSUPPORTED_TARGET",
291
- message: `Unsupported output target: ${target}`,
292
- severity: "error",
293
- });
294
- }
295
-
296
- if (
297
- styleMode &&
298
- styleMode !== "inline" &&
299
- styleMode !== "css-modules" &&
300
- styleMode !== "tailwind"
301
- ) {
302
- diagnostics.push({
303
- code: "STYLE_MODE_UNSUPPORTED",
304
- message: `Unsupported style mode: ${styleMode}`,
305
- severity: "error",
306
- });
307
- }
308
-
309
- for (const [index, component] of (config.components ?? []).entries()) {
310
- if (!component.selector || typeof component.selector !== "string") {
311
- diagnostics.push({
312
- code: "COMPONENT_SELECTOR_INVALID",
313
- message: `Component mapping ${index} must include a selector.`,
314
- severity: "error",
315
- });
316
- }
317
-
318
- if (!component.component || typeof component.component !== "string") {
319
- diagnostics.push({
320
- code: "COMPONENT_IMPORT_INVALID",
321
- message: `Component mapping ${index} must include a component path.`,
322
- severity: "error",
323
- });
324
- }
325
- }
326
-
327
- const spacing = config.tokens?.spacing;
328
- if (spacing?.unit && spacing.unit !== "px" && spacing.unit !== "rem") {
329
- diagnostics.push({
330
- code: "TOKEN_SPACING_UNIT_INVALID",
331
- message: `Unsupported spacing unit: ${spacing.unit}`,
332
- severity: "error",
333
- });
334
- }
335
-
336
- if (spacing?.threshold !== undefined && !Number.isFinite(spacing.threshold)) {
337
- diagnostics.push({
338
- code: "TOKEN_SPACING_THRESHOLD_INVALID",
339
- message: "Spacing threshold must be a finite number.",
340
- severity: "error",
341
- });
342
- }
343
-
344
- for (const [name, value] of Object.entries(spacing?.values ?? {})) {
345
- if (!Number.isFinite(value)) {
346
- diagnostics.push({
347
- code: "TOKEN_SPACING_VALUE_INVALID",
348
- message: `Spacing token ${name} must be a finite number.`,
349
- severity: "error",
350
- });
351
- }
352
- }
353
-
354
- for (const [name, value] of Object.entries(config.tokens?.colors ?? {})) {
355
- if (!/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(value)) {
356
- diagnostics.push({
357
- code: "TOKEN_COLOR_INVALID",
358
- message: `Color token ${name} must be a hex color.`,
359
- severity: "error",
360
- });
361
- }
362
- }
363
-
364
- if (
365
- config.tokens?.colorThreshold !== undefined &&
366
- !Number.isFinite(config.tokens.colorThreshold)
367
- ) {
368
- diagnostics.push({
369
- code: "TOKEN_COLOR_THRESHOLD_INVALID",
370
- message: "Color threshold must be a finite number.",
371
- severity: "error",
372
- });
373
- }
374
-
375
- for (const [groupName, group] of Object.entries({
376
- sizing: config.tokens?.sizing,
377
- typography: config.tokens?.typography,
378
- })) {
379
- if (!group) {
380
- continue;
381
- }
382
- if (group.unit && group.unit !== "px" && group.unit !== "rem") {
383
- diagnostics.push({
384
- code: "TOKEN_NUMERIC_UNIT_INVALID",
385
- message: `Unsupported ${groupName} unit: ${group.unit}`,
386
- severity: "error",
387
- });
388
- }
389
- if (group.threshold !== undefined && !Number.isFinite(group.threshold)) {
390
- diagnostics.push({
391
- code: "TOKEN_NUMERIC_THRESHOLD_INVALID",
392
- message: `${groupName} threshold must be a finite number.`,
393
- severity: "error",
394
- });
395
- }
396
- for (const [name, value] of Object.entries(group.values ?? {})) {
397
- if (!Number.isFinite(value)) {
398
- diagnostics.push({
399
- code: "TOKEN_NUMERIC_VALUE_INVALID",
400
- message: `${groupName} token ${name} must be a finite number.`,
401
- severity: "error",
402
- });
403
- }
404
- }
405
- }
406
-
407
- for (const [index, transformer] of (config.transformers ?? []).entries()) {
408
- if (!transformer.path || typeof transformer.path !== "string") {
409
- diagnostics.push({
410
- code: "TRANSFORMER_PATH_INVALID",
411
- message: `Transformer ${index} must include a path.`,
412
- severity: "error",
413
- });
414
- }
415
- if (
416
- transformer.order !== undefined &&
417
- !Number.isFinite(transformer.order)
418
- ) {
419
- diagnostics.push({
420
- code: "TRANSFORMER_ORDER_INVALID",
421
- message: `Transformer ${index} order must be a finite number.`,
422
- severity: "error",
423
- });
424
- }
425
- }
426
-
427
- validateTestGeneration(config.tests, diagnostics);
428
-
429
- return diagnostics;
430
- }
431
-
432
- function validateTestGeneration(
433
- tests: TestGenerationConfig | undefined,
434
- diagnostics: ConfigDiagnostic[],
435
- ): void {
436
- if (!tests) {
437
- return;
438
- }
439
-
440
- if (tests.runner && tests.runner !== "playwright") {
441
- diagnostics.push({
442
- code: "TEST_RUNNER_UNSUPPORTED",
443
- message: `Unsupported test runner: ${tests.runner}`,
444
- severity: "error",
445
- });
446
- }
447
-
448
- for (const [index, viewport] of (tests.viewports ?? []).entries()) {
449
- if (!Number.isFinite(viewport.width) || viewport.width <= 0) {
450
- diagnostics.push({
451
- code: "TEST_VIEWPORT_WIDTH_INVALID",
452
- message: `Test viewport ${index} width must be a positive finite number.`,
453
- severity: "error",
454
- });
455
- }
456
- if (!Number.isFinite(viewport.height) || viewport.height <= 0) {
457
- diagnostics.push({
458
- code: "TEST_VIEWPORT_HEIGHT_INVALID",
459
- message: `Test viewport ${index} height must be a positive finite number.`,
460
- severity: "error",
461
- });
462
- }
463
- }
464
-
465
- for (const [index, state] of (tests.states ?? []).entries()) {
466
- if (!state.name || typeof state.name !== "string") {
467
- diagnostics.push({
468
- code: "TEST_STATE_NAME_INVALID",
469
- message: `Test state ${index} must include a name.`,
470
- severity: "error",
471
- });
472
- }
473
- }
474
-
475
- if (
476
- tests.assertions?.layoutTolerance !== undefined &&
477
- (!Number.isFinite(tests.assertions.layoutTolerance) ||
478
- tests.assertions.layoutTolerance < 0)
479
- ) {
480
- diagnostics.push({
481
- code: "TEST_LAYOUT_TOLERANCE_INVALID",
482
- message:
483
- "Test layout tolerance must be a finite number greater than or equal to 0.",
484
- severity: "error",
485
- });
486
- }
487
- }
488
-
489
- function isPackageName(path: string): boolean {
490
- return !path.startsWith(".") && !isAbsolute(path);
491
- }
492
-
493
- function validateTransformerPaths(
494
- config: DesignEmbedConfig,
495
- configDir: string,
496
- ): ConfigDiagnostic[] {
497
- const diagnostics: ConfigDiagnostic[] = [];
498
- for (const transformer of config.transformers ?? []) {
499
- if (!transformer.path || typeof transformer.path !== "string") {
500
- continue;
501
- }
502
- if (isPackageName(transformer.path)) {
503
- continue;
504
- }
505
- const resolvedPath = isAbsolute(transformer.path)
506
- ? transformer.path
507
- : resolve(configDir, transformer.path);
508
- if (!existsSync(resolvedPath)) {
509
- diagnostics.push({
510
- code: "TRANSFORMER_NOT_FOUND",
511
- message: `Transformer file not found: ${resolvedPath}`,
512
- severity: "error",
513
- });
514
- }
515
- }
516
-
517
- return diagnostics;
518
- }
@@ -1,5 +0,0 @@
1
- # @design-embed/core
2
-
3
- Internal deterministic compiler core for design-embed.
4
-
5
- It turns local HTML and optional CSS into normalized design nodes, applies configured component substitutions, converts supported style conventions, runs local transformer plugins, and emits generated files for the selected target. It also owns the plugin contracts, diagnostic helpers, and check-mode comparison logic used by the CLI and integrations.
@@ -1,3 +0,0 @@
1
- export function hasErrorDiagnostics(diagnostics) {
2
- return diagnostics.some((diagnostic) => diagnostic.severity === "error");
3
- }
@@ -1,35 +0,0 @@
1
- export function toJsonDiagnostic(diagnostic) {
2
- const details = {
3
- ...diagnostic.details,
4
- ...(diagnostic.selector ? { selector: diagnostic.selector } : {}),
5
- ...(diagnostic.property ? { property: diagnostic.property } : {}),
6
- };
7
- return {
8
- code: diagnostic.code,
9
- severity: diagnostic.severity,
10
- message: redactSecrets(diagnostic.message),
11
- ...(diagnostic.file ? { file: diagnostic.file } : {}),
12
- ...(diagnostic.source ? { line: diagnostic.source.line } : {}),
13
- ...(diagnostic.source ? { column: diagnostic.source.column } : {}),
14
- ...(Object.keys(details).length > 0 ? { details } : {}),
15
- };
16
- }
17
- export function toJsonDiagnostics(diagnostics) {
18
- return diagnostics.map(toJsonDiagnostic);
19
- }
20
- export function formatDiagnosticText(diagnostic) {
21
- const location = [
22
- diagnostic.file,
23
- diagnostic.source?.line,
24
- diagnostic.source?.column,
25
- ]
26
- .filter((part) => part !== undefined)
27
- .join(":");
28
- const prefix = location ? `${location}: ` : "";
29
- return `${prefix}${diagnostic.severity}: ${diagnostic.code}: ${redactSecrets(diagnostic.message)}`;
30
- }
31
- function redactSecrets(value) {
32
- return value
33
- .replace(/figma[_-]?token\s*[:=]\s*[^\s]+/gi, "FIGMA_TOKEN=[redacted]")
34
- .replace(/bearer\s+[a-z0-9._-]+/gi, "Bearer [redacted]");
35
- }