design-embed 0.1.0 → 0.1.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 (40) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +93 -2
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.mjs +469 -0
  5. package/dist/index.d.mts +400 -0
  6. package/dist/index.mjs +2 -0
  7. package/dist/src-D3fnqGCq.mjs +511 -0
  8. package/package.json +8 -13
  9. package/src/cli.ts +18 -1
  10. package/src/commands/compile.ts +46 -63
  11. package/src/commands/generateTests.ts +17 -17
  12. package/src/commands/init.ts +54 -54
  13. package/src/commands/plugin.ts +3 -7
  14. package/src/targets/html.ts +68 -0
  15. package/dist/args.js +0 -36
  16. package/dist/cli.js +0 -35
  17. package/dist/commands/check.js +0 -4
  18. package/dist/commands/compile.js +0 -157
  19. package/dist/commands/generateTests.js +0 -113
  20. package/dist/commands/init.js +0 -102
  21. package/dist/commands/plugin.js +0 -68
  22. package/dist/index.js +0 -2
  23. package/node_modules/@design-embed/config/README.md +0 -5
  24. package/node_modules/@design-embed/config/dist/index.js +0 -283
  25. package/node_modules/@design-embed/config/package.json +0 -19
  26. package/node_modules/@design-embed/config/src/index.ts +0 -518
  27. package/node_modules/@design-embed/core/README.md +0 -5
  28. package/node_modules/@design-embed/core/dist/diagnostics/diagnostic.js +0 -3
  29. package/node_modules/@design-embed/core/dist/diagnostics/jsonDiagnostic.js +0 -35
  30. package/node_modules/@design-embed/core/dist/index.js +0 -351
  31. package/node_modules/@design-embed/core/dist/pipeline/checkMode.js +0 -29
  32. package/node_modules/@design-embed/core/dist/plugins/pluginApi.js +0 -1
  33. package/node_modules/@design-embed/core/dist/plugins/pluginRegistry.js +0 -25
  34. package/node_modules/@design-embed/core/package.json +0 -19
  35. package/node_modules/@design-embed/core/src/diagnostics/diagnostic.ts +0 -18
  36. package/node_modules/@design-embed/core/src/diagnostics/jsonDiagnostic.ts +0 -51
  37. package/node_modules/@design-embed/core/src/index.ts +0 -591
  38. package/node_modules/@design-embed/core/src/pipeline/checkMode.ts +0 -46
  39. package/node_modules/@design-embed/core/src/plugins/pluginApi.ts +0 -78
  40. package/node_modules/@design-embed/core/src/plugins/pluginRegistry.ts +0 -37
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Jin-Woo Lee
3
+ Copyright (c) 2026 Jin Woo Lee
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
6
 
package/README.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # design-embed
2
2
 
3
- The command-line interface for design-embed.
3
+ `design-embed` is the command-line workflow for compiling exported design into files that can live inside an application repository.
4
4
 
5
- It runs local HTML/CSS compilation, writes generated output, checks whether generated files are current, and invokes explicit source plugins such as `figma-html`. The CLI is the user-facing workflow layer around the core compiler: it loads config, reads design input, and formats diagnostics for humans or CI.
5
+ The package provides:
6
+
7
+ - a built-in deterministic HTML output for quick inspection
8
+ - config loading and validation
9
+ - generated-file writing and check mode
10
+ - explicit source-plugin orchestration
11
+ - target-adapter orchestration for framework output such as React
12
+
13
+ Without a target adapter, `design-embed` only emits HTML. Framework packages
14
+ such as `@design-embed/target-react` are separate adapters that must be wired
15
+ into your config.
16
+
17
+ ## Quick Start
18
+
19
+ Create a design HTML file:
20
+
21
+ ```html
22
+ <!-- target.html -->
23
+ <section style="padding: 24px; background: #f8fafc;">
24
+ <h1 style="font-size: 32px;">Welcome</h1>
25
+ <button data-role="primary">Get started</button>
26
+ </section>
27
+ ```
28
+
29
+ Run the compiler:
30
+
31
+ ```bash
32
+ npx design-embed target.html
33
+ ```
34
+
35
+ By default this uses the built-in HTML target and writes:
36
+
37
+ ```text
38
+ src/generated/views/debug.html
39
+ ```
40
+
41
+ ## React Adapter Example
42
+
43
+ Install the React target adapter alongside `design-embed`:
44
+
45
+ ```bash
46
+ pnpm add design-embed @design-embed/target-react
47
+ ```
48
+
49
+ Create a config file:
50
+
51
+ ```ts
52
+ // design-embed.config.ts
53
+ import { defineConfig } from "design-embed";
54
+ import { reactTarget } from "@design-embed/target-react";
55
+
56
+ export default defineConfig({
57
+ output: {
58
+ target: reactTarget,
59
+ viewName: "WelcomeHero",
60
+ viewsDir: "src/components",
61
+ assembliesDir: "src/pages",
62
+ styleMode: "inline",
63
+ },
64
+ tests: {
65
+ outputDir: "tests",
66
+ },
67
+ });
68
+ ```
69
+
70
+ Run the compiler with the config:
71
+
72
+ ```bash
73
+ npx design-embed target.html --config design-embed.config.ts
74
+ ```
75
+
76
+ This writes React output through the adapter:
77
+
78
+ ```text
79
+ src/components/WelcomeHero.view.tsx
80
+ src/pages/WelcomeHeroPage.tsx
81
+ tests/WelcomeHero.reference.html
82
+ tests/WelcomeHero.visual.spec.tsx
83
+ ```
84
+
85
+ Skip adapter-provided test generation with:
86
+
87
+ ```bash
88
+ npx design-embed target.html --config design-embed.config.ts --no-test
89
+ ```
90
+
91
+ ## How It Fits Together
92
+
93
+ `design-embed` owns the user-facing CLI flow: it loads config, reads input,
94
+ selects the built-in HTML target or a configured target adapter, writes files,
95
+ and formats diagnostics. The framework-specific behavior stays in target
96
+ packages that implement the shared target interface.
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,469 @@
1
+ #!/usr/bin/env node
2
+ import { c as checkGeneratedFiles, d as toJsonDiagnostics, l as formatDiagnosticText, n as embed, p as loadConfig } from "./src-D3fnqGCq.mjs";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { dirname, isAbsolute, resolve } from "node:path";
5
+ //#region packages/design-embed/src/args.ts
6
+ function parseArgs(args) {
7
+ const positionals = [];
8
+ const flags = {};
9
+ for (let index = 0; index < args.length; index += 1) {
10
+ const value = args[index];
11
+ if (!value?.startsWith("--")) {
12
+ if (value) positionals.push(value);
13
+ continue;
14
+ }
15
+ const next = args[index + 1];
16
+ if (!next || next.startsWith("--")) {
17
+ flags[value] = true;
18
+ continue;
19
+ }
20
+ flags[value] = next;
21
+ index += 1;
22
+ }
23
+ const [command = "compile", ...rest] = positionals;
24
+ return {
25
+ command,
26
+ positionals: rest,
27
+ flags
28
+ };
29
+ }
30
+ function getStringFlag(flags, name) {
31
+ const value = flags[name];
32
+ return typeof value === "string" ? value : void 0;
33
+ }
34
+ function getBooleanFlag(flags, name) {
35
+ return flags[name] === true;
36
+ }
37
+ function getFormat(flags) {
38
+ return flags["--format"] === "json" ? "json" : "text";
39
+ }
40
+ //#endregion
41
+ //#region packages/design-embed/src/targets/html.ts
42
+ function emitHtmlDebug(nodes, css) {
43
+ const body = nodes.map((node) => emitNode(node, 0)).join("");
44
+ if (!css?.trim()) return body;
45
+ return `<style>\n${css.trim()}\n</style>\n${body}\n`;
46
+ }
47
+ const htmlEmitter = { emit({ nodes, css, config }) {
48
+ return { files: [{
49
+ path: `${config?.output?.viewsDir ?? "src/generated/views"}/debug.html`,
50
+ contents: emitHtmlDebug(nodes, css)
51
+ }] };
52
+ } };
53
+ function emitNode(node, depth) {
54
+ const indent = " ".repeat(depth);
55
+ if (node.kind === "text") return `${indent}${escapeHtml(node.text ?? "")}\n`;
56
+ if (node.kind === "component") return `${indent}<${node.component}></${node.component}>\n`;
57
+ const attributes = Object.entries(node.attributes ?? {}).sort(([left], [right]) => left.localeCompare(right)).map(([name, value]) => value === "" ? name : `${name}="${escapeAttribute(value)}"`).join(" ");
58
+ const openTag = attributes ? `<${node.tagName} ${attributes}>` : `<${node.tagName}>`;
59
+ const children = node.children ?? [];
60
+ if (children.length === 0) return `${indent}${openTag}</${node.tagName}>\n`;
61
+ return `${indent}${openTag}\n${children.map((child) => emitNode(child, depth + 1)).join("")}${indent}</${node.tagName}>\n`;
62
+ }
63
+ function escapeHtml(value) {
64
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
65
+ }
66
+ function escapeAttribute(value) {
67
+ return escapeHtml(value).replace(/"/g, "&quot;");
68
+ }
69
+ //#endregion
70
+ //#region packages/design-embed/src/commands/compile.ts
71
+ async function runCompileCommand(flags, options = {}) {
72
+ const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
73
+ const inputPath = getStringFlag(flags, "--input") ?? getStringFlag(flags, "--");
74
+ const explicitConfigPath = getStringFlag(flags, "--config");
75
+ const defaultConfigPath = resolve(cwd, "design-embed.config.ts");
76
+ const configPath = explicitConfigPath ?? (existsSync(defaultConfigPath) ? "design-embed.config.ts" : void 0);
77
+ const quiet = getBooleanFlag(flags, "--quiet");
78
+ const format = getFormat(flags);
79
+ const generateTests = !getBooleanFlag(flags, "--no-test");
80
+ const diagnostics = [];
81
+ if (!inputPath) {
82
+ diagnostics.push({
83
+ code: "INPUT_REQUIRED",
84
+ message: "--input is required.",
85
+ severity: "error"
86
+ });
87
+ printDiagnostics(diagnostics, format, quiet);
88
+ return 2;
89
+ }
90
+ const resolvedInputPath = resolve(cwd, inputPath);
91
+ if (!existsSync(resolvedInputPath)) {
92
+ diagnostics.push({
93
+ code: "INPUT_NOT_FOUND",
94
+ message: `Input file not found: ${resolvedInputPath}`,
95
+ severity: "error",
96
+ file: inputPath
97
+ });
98
+ printDiagnostics(diagnostics, format, quiet);
99
+ return 2;
100
+ }
101
+ let config;
102
+ if (configPath) {
103
+ const configResult = await loadConfig(configPath, cwd);
104
+ diagnostics.push(...configResult.diagnostics);
105
+ config = configResult.config;
106
+ if (hasErrors$1(diagnostics)) {
107
+ printDiagnostics(diagnostics, format, quiet);
108
+ return 2;
109
+ }
110
+ }
111
+ const targetAdapter = getTargetAdapter(config);
112
+ const cssPath = getStringFlag(flags, "--css");
113
+ const html = readFileSync(resolvedInputPath, "utf-8");
114
+ const css = cssPath ? readFileSync(resolve(cwd, cssPath), "utf-8") : void 0;
115
+ const result = await embed({
116
+ html,
117
+ css,
118
+ configPath,
119
+ config,
120
+ cwd,
121
+ targetEmitter: targetAdapter.emitter
122
+ });
123
+ diagnostics.push(...result.diagnostics);
124
+ if (generateTests && targetAdapter.testGenerator) {
125
+ const testResult = targetAdapter.testGenerator.generateTests({
126
+ html,
127
+ css,
128
+ config: config ?? {},
129
+ diagnostics,
130
+ generatedFiles: result.files
131
+ });
132
+ result.files.push(...testResult.files);
133
+ }
134
+ if (hasErrors$1(diagnostics)) {
135
+ printDiagnostics(diagnostics, format, quiet);
136
+ return 2;
137
+ }
138
+ if (options.check && !getBooleanFlag(flags, "--write")) {
139
+ const checkResult = checkGeneratedFiles({
140
+ cwd,
141
+ files: result.files,
142
+ readFile(path) {
143
+ return existsSync(path) ? readFileSync(path, "utf-8") : void 0;
144
+ }
145
+ });
146
+ const checkDiagnostics = checkResult.diagnostics;
147
+ diagnostics.push(...checkDiagnostics);
148
+ printDiagnostics(diagnostics, format, quiet);
149
+ return checkResult.ok ? 0 : 3;
150
+ }
151
+ for (const file of result.files) {
152
+ const outPath = resolve(cwd, file.path);
153
+ mkdirSync(dirname(outPath), { recursive: true });
154
+ writeFileSync(outPath, file.contents, "utf-8");
155
+ if (!quiet && format === "text") console.log(`Wrote ${file.path}`);
156
+ }
157
+ printDiagnostics(diagnostics, format, quiet);
158
+ if (!quiet && format === "text") console.log(`Success. Generated ${result.files.length} file(s).`);
159
+ return 0;
160
+ }
161
+ function printDiagnostics(diagnostics, format, quiet) {
162
+ if (format === "json") {
163
+ console.log(JSON.stringify({ diagnostics: toJsonDiagnostics(diagnostics) }, null, 2));
164
+ return;
165
+ }
166
+ if (quiet) return;
167
+ for (const diagnostic of diagnostics) {
168
+ const output = formatDiagnosticText(diagnostic);
169
+ if (diagnostic.severity === "error") console.error(output);
170
+ else console.warn(output);
171
+ }
172
+ }
173
+ function getTargetAdapter(config) {
174
+ const target = config?.output?.target;
175
+ if (!target || target === "html") return { emitter: htmlEmitter };
176
+ return {
177
+ emitter: target,
178
+ testGenerator: "generateTests" in target ? target : void 0
179
+ };
180
+ }
181
+ function hasErrors$1(diagnostics) {
182
+ return diagnostics.some((diagnostic) => diagnostic.severity === "error");
183
+ }
184
+ //#endregion
185
+ //#region packages/design-embed/src/commands/check.ts
186
+ async function runCheckCommand(flags) {
187
+ return runCompileCommand(flags, { check: true });
188
+ }
189
+ //#endregion
190
+ //#region packages/design-embed/src/commands/generateTests.ts
191
+ async function runGenerateTestsCommand(flags) {
192
+ const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
193
+ const configPath = getStringFlag(flags, "--config") ?? "design-embed.config.ts";
194
+ const quiet = getBooleanFlag(flags, "--quiet");
195
+ const format = getFormat(flags);
196
+ const diagnostics = [];
197
+ const configResult = await loadConfig(configPath, cwd);
198
+ diagnostics.push(...configResult.diagnostics);
199
+ const config = configResult.config;
200
+ if (!config || hasErrors(diagnostics)) {
201
+ printDiagnostics(diagnostics, format, quiet);
202
+ return 2;
203
+ }
204
+ const source = readConfiguredSource(config, configPath, cwd, diagnostics);
205
+ if (!source || hasErrors(diagnostics)) {
206
+ printDiagnostics(diagnostics, format, quiet);
207
+ return 2;
208
+ }
209
+ const testGenerator = getTestGenerator(config);
210
+ if (!testGenerator) {
211
+ diagnostics.push({
212
+ code: "TEST_TARGET_UNSUPPORTED",
213
+ message: "generate-tests requires output.target to be a target adapter with generateTests().",
214
+ severity: "error"
215
+ });
216
+ printDiagnostics(diagnostics, format, quiet);
217
+ return 2;
218
+ }
219
+ const result = testGenerator.generateTests({
220
+ html: source.html,
221
+ css: source.css,
222
+ config,
223
+ diagnostics
224
+ });
225
+ if (hasErrors(diagnostics)) {
226
+ printDiagnostics(diagnostics, format, quiet);
227
+ return 2;
228
+ }
229
+ for (const file of result.files) {
230
+ const outPath = resolve(cwd, file.path);
231
+ mkdirSync(dirname(outPath), { recursive: true });
232
+ writeFileSync(outPath, file.contents, "utf-8");
233
+ if (!quiet && format === "text") console.log(`Wrote ${file.path}`);
234
+ }
235
+ printDiagnostics(diagnostics, format, quiet);
236
+ if (!quiet && format === "text") console.log(`Success. Generated ${result.files.length} test file(s).`);
237
+ return 0;
238
+ }
239
+ function readConfiguredSource(config, configPath, cwd, diagnostics) {
240
+ const source = config.tests?.source;
241
+ if (!source?.html) {
242
+ diagnostics.push({
243
+ code: "TEST_SOURCE_HTML_REQUIRED",
244
+ message: "tests.source.html is required for generate-tests.",
245
+ severity: "error"
246
+ });
247
+ return;
248
+ }
249
+ const configDir = dirname(resolve(cwd, configPath));
250
+ const htmlPath = resolveConfigPath(source.html, configDir);
251
+ if (!existsSync(htmlPath)) {
252
+ diagnostics.push({
253
+ code: "TEST_SOURCE_HTML_NOT_FOUND",
254
+ message: `Test source HTML not found: ${htmlPath}`,
255
+ severity: "error",
256
+ file: source.html
257
+ });
258
+ return;
259
+ }
260
+ let css;
261
+ if (source.css) {
262
+ const cssPath = resolveConfigPath(source.css, configDir);
263
+ if (!existsSync(cssPath)) {
264
+ diagnostics.push({
265
+ code: "TEST_SOURCE_CSS_NOT_FOUND",
266
+ message: `Test source CSS not found: ${cssPath}`,
267
+ severity: "error",
268
+ file: source.css
269
+ });
270
+ return;
271
+ }
272
+ css = readFileSync(cssPath, "utf-8");
273
+ }
274
+ return {
275
+ html: readFileSync(htmlPath, "utf-8"),
276
+ css
277
+ };
278
+ }
279
+ function resolveConfigPath(path, configDir) {
280
+ return isAbsolute(path) ? path : resolve(configDir, path);
281
+ }
282
+ function getTestGenerator(config) {
283
+ const target = config.output?.target;
284
+ return target && target !== "html" && "generateTests" in target ? target : void 0;
285
+ }
286
+ function hasErrors(diagnostics) {
287
+ return diagnostics.some((diagnostic) => diagnostic.severity === "error");
288
+ }
289
+ //#endregion
290
+ //#region packages/design-embed/src/commands/init.ts
291
+ async function runInitCommand(flags) {
292
+ const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
293
+ const quiet = getBooleanFlag(flags, "--quiet");
294
+ const force = getBooleanFlag(flags, "--force");
295
+ const format = getFormat(flags);
296
+ const viewName = getStringFlag(flags, "--view-name") ?? "WelcomeHero";
297
+ const diagnostics = [];
298
+ const files = [{
299
+ path: "design-embed.config.ts",
300
+ contents: configTemplate(viewName)
301
+ }];
302
+ let written = 0;
303
+ for (const file of files) {
304
+ const outPath = resolve(cwd, file.path);
305
+ if (existsSync(outPath) && !force) {
306
+ diagnostics.push({
307
+ code: "INIT_FILE_EXISTS",
308
+ message: `Skipped existing file: ${file.path}. Pass --force to overwrite it.`,
309
+ severity: "warning",
310
+ file: file.path
311
+ });
312
+ continue;
313
+ }
314
+ mkdirSync(dirname(outPath), { recursive: true });
315
+ writeFileSync(outPath, file.contents, "utf-8");
316
+ written += 1;
317
+ if (!quiet && format === "text") console.log(`Wrote ${file.path}`);
318
+ }
319
+ printDiagnostics(diagnostics, format, quiet);
320
+ if (!quiet && format === "text") {
321
+ console.log(`Success. Initialized design-embed with ${written} file(s).`);
322
+ console.log("Next: pnpm exec design-embed --out ./design.html");
323
+ }
324
+ return 0;
325
+ }
326
+ function configTemplate(viewName) {
327
+ return `import {
328
+ \tdefineConfig,
329
+ \ttype PluginDefinition,
330
+ \ttype SourcePlugin,
331
+ \ttype SourcePluginInput,
332
+ \ttype SourcePluginResult,
333
+ } from "design-embed";
334
+
335
+ class HtmlFetcherPlugin implements PluginDefinition, SourcePlugin {
336
+ \treadonly name = "html-fetcher";
337
+ \tprivate readonly options: { url: string };
338
+
339
+ \tconstructor(options: { url: string }) {
340
+ \t\tthis.options = options;
341
+ \t}
342
+
343
+ \tasync run(_input: SourcePluginInput): Promise<SourcePluginResult> {
344
+ \t\ttry {
345
+ \t\t\tconst response = await fetch(this.options.url);
346
+ \t\t\tif (!response.ok) {
347
+ \t\t\t\treturn {
348
+ \t\t\t\t\tdiagnostics: [
349
+ \t\t\t\t\t\t{
350
+ \t\t\t\t\t\t\tcode: "HTML_FETCH_FAILED",
351
+ \t\t\t\t\t\t\tmessage: \`Failed to fetch HTML: \${response.status} \${response.statusText}\`,
352
+ \t\t\t\t\t\t\tseverity: "error",
353
+ \t\t\t\t\t\t},
354
+ \t\t\t\t\t],
355
+ \t\t\t\t};
356
+ \t\t\t}
357
+
358
+ \t\t\treturn {
359
+ \t\t\t\thtml: await response.text(),
360
+ \t\t\t\tdiagnostics: [],
361
+ \t\t\t};
362
+ \t\t} catch (error) {
363
+ \t\t\treturn {
364
+ \t\t\t\tdiagnostics: [
365
+ \t\t\t\t\t{
366
+ \t\t\t\t\t\tcode: "HTML_FETCH_FAILED",
367
+ \t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),
368
+ \t\t\t\t\t\tseverity: "error",
369
+ \t\t\t\t\t},
370
+ \t\t\t\t],
371
+ \t\t\t};
372
+ \t\t}
373
+ \t}
374
+ }
375
+
376
+ export default defineConfig({
377
+ \tplugins: [
378
+ \t\tnew HtmlFetcherPlugin({
379
+ \t\t\turl: "https://www.scrapethissite.com/pages/",
380
+ \t\t}),
381
+ \t],
382
+ \toutput: {
383
+ \t\tviewName: "${viewName}",
384
+ \t\tviewsDir: "src/generated/views",
385
+ \t},
386
+ });
387
+ `;
388
+ }
389
+ //#endregion
390
+ //#region packages/design-embed/src/commands/plugin.ts
391
+ async function runPluginCommand(_name, flags) {
392
+ const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
393
+ const configResult = await loadConfig(getStringFlag(flags, "--config") ?? "design-embed.config.ts", cwd);
394
+ for (const diagnostic of configResult.diagnostics) if (diagnostic.severity === "error") console.error(`error: ${diagnostic.code}: ${diagnostic.message}`);
395
+ else console.warn(`${diagnostic.severity}: ${diagnostic.code}: ${diagnostic.message}`);
396
+ if (configResult.diagnostics.some((d) => d.severity === "error")) return 2;
397
+ const outPath = getStringFlag(flags, "--out");
398
+ if (!outPath) {
399
+ console.error("Error: --out is required.");
400
+ return 2;
401
+ }
402
+ const plugin = findSourcePlugin(configResult.config?.plugins);
403
+ if (!plugin) {
404
+ console.error("Error: config must include a source plugin instance in the plugins array (e.g. new FigmaHtmlPlugin({ ... })).");
405
+ return 2;
406
+ }
407
+ const result = await plugin.run({
408
+ cwd,
409
+ args: {}
410
+ });
411
+ for (const diagnostic of result.diagnostics) {
412
+ const output = `${diagnostic.severity}: ${diagnostic.code}: ${diagnostic.message}`;
413
+ if (diagnostic.severity === "error") console.error(output);
414
+ else console.warn(output);
415
+ }
416
+ if (result.diagnostics.some((d) => d.severity === "error")) return 2;
417
+ if (!result.html) {
418
+ console.error("Error: source plugin produced no HTML.");
419
+ return 2;
420
+ }
421
+ const resolvedOutPath = resolve(cwd, outPath);
422
+ mkdirSync(dirname(resolvedOutPath), { recursive: true });
423
+ writeFileSync(resolvedOutPath, result.html, "utf-8");
424
+ console.log(`Wrote ${outPath}`);
425
+ for (const file of result.files ?? []) {
426
+ const resolvedPath = resolve(cwd, file.path);
427
+ mkdirSync(dirname(resolvedPath), { recursive: true });
428
+ writeFileSync(resolvedPath, file.contents, "utf-8");
429
+ console.log(`Wrote ${file.path}`);
430
+ }
431
+ return 0;
432
+ }
433
+ function isSourcePlugin(plugin) {
434
+ return typeof plugin.run === "function";
435
+ }
436
+ function findSourcePlugin(plugins) {
437
+ return plugins?.find(isSourcePlugin);
438
+ }
439
+ //#endregion
440
+ //#region packages/design-embed/src/cli.ts
441
+ async function main() {
442
+ const args = process.argv.slice(2);
443
+ const parsed = parseArgs(args);
444
+ if (args[0] === "check") return runCheckCommand(parsed.flags);
445
+ if (args[0] === "plugin") return runPluginCommand(parsed.positionals[0], parsed.flags);
446
+ if (args[0] === "generate-tests") return runGenerateTestsCommand(parsed.flags);
447
+ if (args[0] === "init") return runInitCommand(parsed.flags);
448
+ const flags = args[0] && !args[0].startsWith("--") ? {
449
+ ...parsed.flags,
450
+ "--": args[0]
451
+ } : parsed.flags;
452
+ if (getOutPath(flags) && !hasInput(flags)) return runPluginCommand(void 0, flags);
453
+ return runCompileCommand(flags);
454
+ }
455
+ function getOutPath(flags) {
456
+ return typeof flags["--out"] === "string";
457
+ }
458
+ function hasInput(flags) {
459
+ return typeof flags["--input"] === "string" || typeof flags["--"] === "string";
460
+ }
461
+ main().then((code) => {
462
+ if (code !== 0) process.exit(code);
463
+ }).catch((error) => {
464
+ const message = error instanceof Error ? error.message : String(error);
465
+ console.error(`Pipeline failed: ${message}`);
466
+ process.exit(1);
467
+ });
468
+ //#endregion
469
+ export {};