design-embed 0.1.0 → 0.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.
Files changed (45) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +98 -2
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.mjs +273 -0
  5. package/dist/core-BLV62TaX.mjs +907 -0
  6. package/dist/index.d.mts +273 -0
  7. package/dist/index.mjs +2 -0
  8. package/package.json +6 -19
  9. package/src/cli.ts +8 -16
  10. package/src/commands/compile.ts +25 -110
  11. package/src/commands/generateTests.ts +14 -96
  12. package/src/commands/init.ts +52 -55
  13. package/src/commands/plugin.ts +6 -21
  14. package/src/config/index.ts +302 -0
  15. package/{node_modules/@design-embed/core/src → src/core}/index.ts +151 -163
  16. package/src/core/nodes.ts +74 -0
  17. package/src/core/plugins/pluginApi.ts +44 -0
  18. package/src/core/types.ts +120 -0
  19. package/src/index.ts +48 -2
  20. package/src/targets/html.ts +621 -0
  21. package/dist/args.js +0 -36
  22. package/dist/cli.js +0 -35
  23. package/dist/commands/check.js +0 -4
  24. package/dist/commands/compile.js +0 -157
  25. package/dist/commands/generateTests.js +0 -113
  26. package/dist/commands/init.js +0 -102
  27. package/dist/commands/plugin.js +0 -68
  28. package/dist/index.js +0 -2
  29. package/node_modules/@design-embed/config/README.md +0 -5
  30. package/node_modules/@design-embed/config/dist/index.js +0 -283
  31. package/node_modules/@design-embed/config/package.json +0 -19
  32. package/node_modules/@design-embed/config/src/index.ts +0 -518
  33. package/node_modules/@design-embed/core/README.md +0 -5
  34. package/node_modules/@design-embed/core/dist/diagnostics/diagnostic.js +0 -3
  35. package/node_modules/@design-embed/core/dist/diagnostics/jsonDiagnostic.js +0 -35
  36. package/node_modules/@design-embed/core/dist/index.js +0 -351
  37. package/node_modules/@design-embed/core/dist/pipeline/checkMode.js +0 -29
  38. package/node_modules/@design-embed/core/dist/plugins/pluginApi.js +0 -1
  39. package/node_modules/@design-embed/core/dist/plugins/pluginRegistry.js +0 -25
  40. package/node_modules/@design-embed/core/package.json +0 -19
  41. package/node_modules/@design-embed/core/src/plugins/pluginApi.ts +0 -78
  42. package/node_modules/@design-embed/core/src/plugins/pluginRegistry.ts +0 -37
  43. /package/{node_modules/@design-embed/core/src → src/core}/diagnostics/diagnostic.ts +0 -0
  44. /package/{node_modules/@design-embed/core/src → src/core}/diagnostics/jsonDiagnostic.ts +0 -0
  45. /package/{node_modules/@design-embed/core/src → src/core}/pipeline/checkMode.ts +0 -0
@@ -0,0 +1,273 @@
1
+ //#region packages/design-embed/src/core/nodes.d.ts
2
+ /**
3
+ * Location in the source HTML file.
4
+ */
5
+ interface SourceLocation {
6
+ /** Absolute offset in characters. */
7
+ offset: number;
8
+ /** 1-based line number. */
9
+ line: number;
10
+ /** 1-based column number. */
11
+ column: number;
12
+ }
13
+ /**
14
+ * A normalized node in the design AST.
15
+ */
16
+ interface DesignNode {
17
+ /** The type of node. */
18
+ kind: "element" | "text" | "component";
19
+ /** HTML tag name (for element kind). */
20
+ tagName?: string;
21
+ /** HTML attributes (for element kind). */
22
+ attributes?: Record<string, string>;
23
+ /** Parsed inline styles (for element kind). */
24
+ styles?: Record<string, string>;
25
+ /** Utility classes to apply. */
26
+ generatedClassNames?: string[];
27
+ /** Child nodes. */
28
+ children?: DesignNode[];
29
+ /** Inner text content (for text kind). */
30
+ text?: string;
31
+ /** Original location in the source HTML. */
32
+ source?: SourceLocation;
33
+ /** Component name (for component kind). */
34
+ component?: string;
35
+ /** Named export of the component. */
36
+ importName?: string;
37
+ /** Mapped prop values for the component. */
38
+ props?: Record<string, PropValue>;
39
+ /** Import path of the component. */
40
+ importPath?: string;
41
+ /**
42
+ * The original element node a component was mapped from. Retained so
43
+ * targets can reconstruct the element's structure when emitting the
44
+ * component implementation.
45
+ */
46
+ sourceElement?: DesignNode;
47
+ }
48
+ /**
49
+ * A value passed to a component prop.
50
+ */
51
+ type PropValue = {
52
+ kind: "literal";
53
+ value: string | number | boolean; /** Source attribute name when the prop is bound to `$attr.*`. */
54
+ attribute?: string;
55
+ } | {
56
+ kind: "text";
57
+ value: string;
58
+ } | {
59
+ kind: "children";
60
+ value: DesignNode[];
61
+ };
62
+ //#endregion
63
+ //#region packages/design-embed/src/core/plugins/pluginApi.d.ts
64
+ /**
65
+ * Represents a file generated by the compiler.
66
+ */
67
+ interface GeneratedFile {
68
+ /** Relative path from the output directory. */
69
+ path: string;
70
+ /** File content. */
71
+ contents: string;
72
+ }
73
+ interface GeneratedAsset {
74
+ path: string;
75
+ contents?: string | Uint8Array;
76
+ sourceUrl?: string;
77
+ }
78
+ interface SourcePlugin {
79
+ name: string;
80
+ run(input: SourcePluginInput): Promise<SourcePluginResult>;
81
+ }
82
+ interface SourcePluginInput {
83
+ cwd: string;
84
+ config?: unknown;
85
+ }
86
+ interface SourcePluginResult {
87
+ html?: string;
88
+ css?: string;
89
+ assets?: GeneratedAsset[];
90
+ files?: GeneratedFile[];
91
+ diagnostics: Diagnostic[];
92
+ }
93
+ interface TargetEmitResult {
94
+ files: GeneratedFile[];
95
+ }
96
+ interface TargetTestGenerateResult {
97
+ files: GeneratedFile[];
98
+ diagnostics: Diagnostic[];
99
+ }
100
+ //#endregion
101
+ //#region packages/design-embed/src/core/types.d.ts
102
+ interface TargetEmitInput {
103
+ nodes: DesignNode[];
104
+ css?: string;
105
+ config?: DesignEmbedConfig;
106
+ diagnostics: Diagnostic[];
107
+ }
108
+ interface TargetEmitter {
109
+ emit(input: TargetEmitInput): TargetEmitResult;
110
+ }
111
+ interface TargetTestGenerateInput {
112
+ html: string;
113
+ css?: string;
114
+ config: DesignEmbedConfig;
115
+ }
116
+ interface TargetTestGenerator {
117
+ generateTests(input: TargetTestGenerateInput): TargetTestGenerateResult;
118
+ }
119
+ type StyleMode = "inline" | "css-modules" | "tailwind";
120
+ interface ComponentMapping {
121
+ selector: string;
122
+ component: string;
123
+ props?: Record<string, string>;
124
+ }
125
+ interface TokenConfig {
126
+ spacing?: {
127
+ unit?: "px" | "rem";
128
+ threshold?: number;
129
+ values?: Record<string, number>;
130
+ };
131
+ sizing?: NumericTokenGroup;
132
+ typography?: NumericTokenGroup;
133
+ radius?: Record<string, number>;
134
+ borderWidth?: Record<string, number>;
135
+ shadow?: Record<string, string>;
136
+ colors?: Record<string, string>;
137
+ colorThreshold?: number;
138
+ }
139
+ interface NumericTokenGroup {
140
+ unit?: "px" | "rem";
141
+ threshold?: number;
142
+ values?: Record<string, number>;
143
+ }
144
+ type StyleMappings = Record<string, Record<string, string>>;
145
+ interface TestGenerationConfig {
146
+ outputDir?: string;
147
+ runner?: "playwright";
148
+ viewports?: TestViewport[];
149
+ states?: TestState[];
150
+ assertions?: TestAssertions;
151
+ }
152
+ interface TestViewport {
153
+ name?: string;
154
+ width: number;
155
+ height: number;
156
+ }
157
+ interface TestState {
158
+ name: string;
159
+ hover?: string;
160
+ focus?: string;
161
+ click?: string;
162
+ waitFor?: string;
163
+ }
164
+ interface TestAssertions {
165
+ screenshot?: boolean;
166
+ layout?: boolean;
167
+ layoutTolerance?: number;
168
+ selectors?: string[];
169
+ /**
170
+ * Per-pixel color sensitivity (0-1) for the screenshot comparison. Smaller
171
+ * is stricter. Defaults to 0.2.
172
+ */
173
+ screenshotThreshold?: number;
174
+ /**
175
+ * Maximum number of differing pixels tolerated in the screenshot
176
+ * comparison. Defaults to 0 (byte-exact).
177
+ */
178
+ screenshotMaxDiffPixels?: number;
179
+ }
180
+ interface DesignEmbedConfig {
181
+ output?: {
182
+ viewsDir?: string | URL;
183
+ target?: "html" | TargetEmitter;
184
+ viewName?: string;
185
+ styleMode?: StyleMode;
186
+ };
187
+ components?: ComponentMapping[];
188
+ tokens?: TokenConfig;
189
+ styleMappings?: StyleMappings;
190
+ source?: SourcePlugin;
191
+ tests?: TestGenerationConfig;
192
+ }
193
+ //#endregion
194
+ //#region packages/design-embed/src/core/index.d.ts
195
+ /**
196
+ * Input for the core embed function.
197
+ */
198
+ interface DesignEmbedInput {
199
+ /** The compiler configuration. */
200
+ config?: DesignEmbedConfig;
201
+ /** Working directory. */
202
+ cwd?: string;
203
+ /** When true, skips writing output files to disk. Defaults to false. */
204
+ dryRun?: boolean;
205
+ /** When true, generates test files alongside output files. Defaults to false. */
206
+ generateTests?: boolean;
207
+ }
208
+ /**
209
+ * Result of the embedding process.
210
+ */
211
+ interface DesignEmbedResult {
212
+ /** Source HTML resolved from the config's source plugin. */
213
+ html: string;
214
+ /** Source CSS resolved from the config's source plugin. */
215
+ css?: string;
216
+ /** Generated files. */
217
+ files: GeneratedFile[];
218
+ /** Diagnostics reported during compilation. */
219
+ diagnostics: Diagnostic[];
220
+ }
221
+ /**
222
+ * The main compiler entry point.
223
+ * Parses HTML, applies component mappings, and emits files.
224
+ *
225
+ * @param input - The compilation input.
226
+ * @returns A promise resolving to the compilation result.
227
+ *
228
+ */
229
+ declare function embed(input: DesignEmbedInput): Promise<DesignEmbedResult>;
230
+ declare function applyComponentMappings(nodes: DesignNode[], mappings: ComponentMapping[], diagnostics?: Diagnostic[]): DesignNode[];
231
+ declare function parseHtml(html: string): DesignNode[];
232
+ //#endregion
233
+ //#region packages/design-embed/src/core/diagnostics/diagnostic.d.ts
234
+ type DiagnosticSeverity = "error" | "warning" | "info";
235
+ interface Diagnostic {
236
+ code: string;
237
+ message: string;
238
+ severity: DiagnosticSeverity;
239
+ file?: string;
240
+ source?: SourceLocation;
241
+ selector?: string;
242
+ property?: string;
243
+ details?: Record<string, unknown>;
244
+ }
245
+ //#endregion
246
+ //#region packages/design-embed/src/config/index.d.ts
247
+ interface LoadConfigResult {
248
+ config?: DesignEmbedConfig;
249
+ configPath?: string;
250
+ diagnostics: Diagnostic[];
251
+ }
252
+ declare function defineConfig(config: DesignEmbedConfig): DesignEmbedConfig;
253
+ declare function fromFile(htmlPath: string | URL, cssPath?: string | URL): SourcePlugin;
254
+ declare function loadConfig(configPath: string, cwd?: string): Promise<LoadConfigResult>;
255
+ declare function validateConfig(config: DesignEmbedConfig): Diagnostic[];
256
+ //#endregion
257
+ //#region packages/design-embed/src/targets/html.d.ts
258
+ interface HtmlTargetOptions {
259
+ domModel?: "light" | "shadow";
260
+ }
261
+ declare class HtmlTarget implements TargetEmitter, TargetTestGenerator {
262
+ private readonly domModel;
263
+ constructor(options?: HtmlTargetOptions);
264
+ emit({
265
+ nodes,
266
+ css,
267
+ config
268
+ }: TargetEmitInput): TargetEmitResult;
269
+ generateTests(input: TargetTestGenerateInput): TargetTestGenerateResult;
270
+ }
271
+ declare const htmlTarget: TargetEmitter & TargetTestGenerator;
272
+ //#endregion
273
+ export { type ComponentMapping, type DesignEmbedConfig, type DesignEmbedInput, type DesignEmbedResult, type DesignNode, type Diagnostic, type DiagnosticSeverity, type GeneratedAsset, type GeneratedFile, HtmlTarget, type HtmlTargetOptions, type LoadConfigResult, type NumericTokenGroup, type PropValue, type SourceLocation, type SourcePlugin, type SourcePluginInput, type SourcePluginResult, type StyleMappings, type StyleMode, type TargetEmitInput, type TargetEmitResult, type TargetEmitter, type TargetTestGenerateInput, type TargetTestGenerateResult, type TargetTestGenerator, type TestAssertions, type TestGenerationConfig, type TestState, type TestViewport, type TokenConfig, applyComponentMappings, defineConfig, embed, fromFile, htmlTarget, loadConfig, parseHtml, validateConfig };
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { c as htmlTarget, d as loadConfig, f as validateConfig, l as defineConfig, n as embed, r as parseHtml, s as HtmlTarget, t as applyComponentMappings, u as fromFile } from "./core-BLV62TaX.mjs";
2
+ export { HtmlTarget, applyComponentMappings, defineConfig, embed, fromFile, htmlTarget, loadConfig, parseHtml, validateConfig };
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "design-embed",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {
7
- "access": "public"
7
+ "access": "public",
8
+ "provenance": true
8
9
  },
9
10
  "exports": {
10
11
  ".": {
11
12
  "types": "./src/index.ts",
12
13
  "development": "./src/index.ts",
13
- "default": "./dist/index.js"
14
+ "default": "./dist/index.mjs"
14
15
  }
15
16
  },
16
17
  "files": [
@@ -20,23 +21,9 @@
20
21
  "README.md"
21
22
  ],
22
23
  "bin": {
23
- "design-embed": "./dist/cli.js"
24
+ "design-embed": "./dist/cli.mjs"
24
25
  },
25
- "dependencies": {
26
- "@design-embed/config": "0.1.0",
27
- "@design-embed/core": "0.1.0",
28
- "@design-embed/target-html": "0.1.0",
29
- "@design-embed/target-react": "0.1.0"
30
- },
31
- "bundledDependencies": [
32
- "@design-embed/config",
33
- "@design-embed/core"
34
- ],
35
26
  "scripts": {
36
- "dev": "node src/cli.ts",
37
- "compile:react": "node src/cli.ts react",
38
- "compile:html": "node src/cli.ts html",
39
- "compile:vanjs": "node src/cli.ts vanjs",
40
- "get-raw": "node src/cli.ts raw"
27
+ "test": "node --conditions=development --test src/**/*.test.ts"
41
28
  }
42
29
  }
package/src/cli.ts CHANGED
@@ -4,29 +4,21 @@ import { runCheckCommand } from "./commands/check.ts";
4
4
  import { runCompileCommand } from "./commands/compile.ts";
5
5
  import { runGenerateTestsCommand } from "./commands/generateTests.ts";
6
6
  import { runInitCommand } from "./commands/init.ts";
7
- import { runPluginCommand } from "./commands/plugin.ts";
8
7
 
9
8
  async function main(): Promise<number> {
10
- const args = process.argv.slice(2);
11
- const parsed = parseArgs(args);
9
+ const { command, flags } = parseArgs(process.argv.slice(2));
12
10
 
13
- if (args[0] === "check") {
14
- return runCheckCommand(parsed.flags);
11
+ if (command === "check") {
12
+ return runCheckCommand(flags);
15
13
  }
16
-
17
- if (args[0] === "plugin") {
18
- return runPluginCommand(parsed.positionals[0], parsed.flags);
19
- }
20
-
21
- if (args[0] === "generate-tests") {
22
- return runGenerateTestsCommand(parsed.flags);
14
+ if (command === "generate-tests") {
15
+ return runGenerateTestsCommand(flags);
23
16
  }
24
-
25
- if (args[0] === "init") {
26
- return runInitCommand(parsed.flags);
17
+ if (command === "init") {
18
+ return runInitCommand(flags);
27
19
  }
28
20
 
29
- return runCompileCommand(parsed.flags);
21
+ return runCompileCommand(flags);
30
22
  }
31
23
 
32
24
  main()
@@ -1,17 +1,14 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, isAbsolute, resolve } from "node:path";
3
- import { type DesignEmbedConfig, loadConfig } from "@design-embed/config";
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { getBooleanFlag, getFormat, getStringFlag } from "../args.ts";
4
+ import { loadConfig } from "../config/index.ts";
4
5
  import {
5
6
  checkGeneratedFiles,
6
7
  type Diagnostic,
7
8
  embed,
8
9
  formatDiagnosticText,
9
- type TransformerPlugin,
10
10
  toJsonDiagnostics,
11
- } from "@design-embed/core";
12
- import { htmlEmitter } from "@design-embed/target-html";
13
- import { reactEmitter } from "@design-embed/target-react";
14
- import { getBooleanFlag, getFormat, getStringFlag } from "../args.ts";
11
+ } from "../core/index.ts";
15
12
 
16
13
  export interface CompileCommandOptions {
17
14
  check?: boolean;
@@ -22,65 +19,43 @@ export async function runCompileCommand(
22
19
  options: CompileCommandOptions = {},
23
20
  ): Promise<number> {
24
21
  const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
25
- const inputPath = getStringFlag(flags, "--input");
26
- const configPath = getStringFlag(flags, "--config");
22
+ const explicitConfigPath = getStringFlag(flags, "--config");
23
+ const defaultConfigPath = resolve(cwd, "design-embed.config.ts");
24
+ const configPath =
25
+ explicitConfigPath ??
26
+ (existsSync(defaultConfigPath) ? "design-embed.config.ts" : undefined);
27
27
  const quiet = getBooleanFlag(flags, "--quiet");
28
28
  const format = getFormat(flags);
29
29
  const diagnostics: Diagnostic[] = [];
30
30
 
31
- if (!inputPath) {
31
+ if (!configPath) {
32
32
  diagnostics.push({
33
- code: "INPUT_REQUIRED",
34
- message: "--input is required.",
33
+ code: "CONFIG_REQUIRED",
34
+ message:
35
+ "No config file found. Create design-embed.config.ts or use --config.",
35
36
  severity: "error",
36
37
  });
37
38
  printDiagnostics(diagnostics, format, quiet);
38
39
  return 2;
39
40
  }
40
41
 
41
- const resolvedInputPath = resolve(cwd, inputPath);
42
- if (!existsSync(resolvedInputPath)) {
43
- diagnostics.push({
44
- code: "INPUT_NOT_FOUND",
45
- message: `Input file not found: ${resolvedInputPath}`,
46
- severity: "error",
47
- file: inputPath,
48
- });
42
+ const configResult = await loadConfig(configPath, cwd);
43
+ diagnostics.push(...configResult.diagnostics);
44
+ const config = configResult.config;
45
+
46
+ if (hasErrors(diagnostics)) {
49
47
  printDiagnostics(diagnostics, format, quiet);
50
48
  return 2;
51
49
  }
52
50
 
53
- let config: DesignEmbedConfig | undefined;
54
- let transformers: TransformerPlugin[] = [];
55
- if (configPath) {
56
- const configResult = await loadConfig(configPath, cwd);
57
- diagnostics.push(...configResult.diagnostics);
58
- config = configResult.config;
59
-
60
- if (hasErrors(diagnostics)) {
61
- printDiagnostics(diagnostics, format, quiet);
62
- return 2;
63
- }
64
-
65
- transformers = await loadTransformers(config, configPath, cwd, diagnostics);
66
- if (hasErrors(diagnostics)) {
67
- printDiagnostics(diagnostics, format, quiet);
68
- return 2;
69
- }
70
- }
71
-
72
- const target = config?.output?.target ?? "html";
73
- const targetEmitter = target === "react" ? reactEmitter : htmlEmitter;
51
+ const isCheckMode = options.check && !getBooleanFlag(flags, "--write");
52
+ const generateTests = !getBooleanFlag(flags, "--no-test");
74
53
 
75
- const cssPath = getStringFlag(flags, "--css");
76
54
  const result = await embed({
77
- html: readFileSync(resolvedInputPath, "utf-8"),
78
- css: cssPath ? readFileSync(resolve(cwd, cssPath), "utf-8") : undefined,
79
- configPath,
80
55
  config,
81
56
  cwd,
82
- transformers,
83
- targetEmitter,
57
+ dryRun: isCheckMode,
58
+ generateTests,
84
59
  });
85
60
  diagnostics.push(...result.diagnostics);
86
61
 
@@ -89,7 +64,7 @@ export async function runCompileCommand(
89
64
  return 2;
90
65
  }
91
66
 
92
- if (options.check && !getBooleanFlag(flags, "--write")) {
67
+ if (isCheckMode) {
93
68
  const checkResult = checkGeneratedFiles({
94
69
  cwd,
95
70
  files: result.files,
@@ -97,21 +72,11 @@ export async function runCompileCommand(
97
72
  return existsSync(path) ? readFileSync(path, "utf-8") : undefined;
98
73
  },
99
74
  });
100
- const checkDiagnostics = checkResult.diagnostics;
101
- diagnostics.push(...checkDiagnostics);
75
+ diagnostics.push(...checkResult.diagnostics);
102
76
  printDiagnostics(diagnostics, format, quiet);
103
77
  return checkResult.ok ? 0 : 3;
104
78
  }
105
79
 
106
- for (const file of result.files) {
107
- const outPath = resolve(cwd, file.path);
108
- mkdirSync(dirname(outPath), { recursive: true });
109
- writeFileSync(outPath, file.contents, "utf-8");
110
- if (!quiet && format === "text") {
111
- console.log(`Wrote ${file.path}`);
112
- }
113
- }
114
-
115
80
  printDiagnostics(diagnostics, format, quiet);
116
81
  if (!quiet && format === "text") {
117
82
  console.log(`Success. Generated ${result.files.length} file(s).`);
@@ -143,56 +108,6 @@ export function printDiagnostics(
143
108
  }
144
109
  }
145
110
 
146
- function isPackageName(path: string): boolean {
147
- return !path.startsWith(".") && !isAbsolute(path);
148
- }
149
-
150
- async function loadTransformers(
151
- config: DesignEmbedConfig | undefined,
152
- configPath: string,
153
- cwd: string,
154
- diagnostics: Diagnostic[],
155
- ): Promise<TransformerPlugin[]> {
156
- const configDir = dirname(resolve(cwd, configPath));
157
- const loaded: TransformerPlugin[] = [];
158
-
159
- for (const transformer of config?.transformers ?? []) {
160
- const specifier = isPackageName(transformer.path)
161
- ? transformer.path
162
- : isAbsolute(transformer.path)
163
- ? transformer.path
164
- : resolve(configDir, transformer.path);
165
- try {
166
- const module = await import(specifier);
167
- const plugin = module.default ?? module.transformer;
168
- if (!plugin?.transform) {
169
- diagnostics.push({
170
- code: "TRANSFORMER_INVALID",
171
- message: `Transformer ${transformer.path} must export a plugin object with transform().`,
172
- severity: "error",
173
- file: transformer.path,
174
- });
175
- continue;
176
- }
177
- loaded.push({
178
- name: plugin.name ?? transformer.path,
179
- order: transformer.order ?? plugin.order,
180
- transform: plugin.transform,
181
- });
182
- } catch (error) {
183
- const message = error instanceof Error ? error.message : String(error);
184
- diagnostics.push({
185
- code: "TRANSFORMER_LOAD_FAILED",
186
- message: `Failed to load transformer ${transformer.path}: ${message}`,
187
- severity: "error",
188
- file: transformer.path,
189
- });
190
- }
191
- }
192
-
193
- return loaded;
194
- }
195
-
196
111
  function hasErrors(diagnostics: Diagnostic[]): boolean {
197
112
  return diagnostics.some((diagnostic) => diagnostic.severity === "error");
198
113
  }