design-embed 0.1.1 → 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.
package/README.md CHANGED
@@ -38,6 +38,11 @@ By default this uses the built-in HTML target and writes:
38
38
  src/generated/views/debug.html
39
39
  ```
40
40
 
41
+ When `components` are configured with the HTML target, two additional files are generated for each view alongside the HTML:
42
+
43
+ - `ViewName.ts` — a native web component scaffold (`HTMLElement` subclass with `observedAttributes`, lifecycle hooks, and a `render()` method; no React dependency)
44
+ - `ViewName.html` — includes `<script type="module" src="./ViewName.js"></script>` at the bottom
45
+
41
46
  ## React Adapter Example
42
47
 
43
48
  Install the React target adapter alongside `design-embed`:
@@ -51,11 +56,11 @@ Create a config file:
51
56
  ```ts
52
57
  // design-embed.config.ts
53
58
  import { defineConfig } from "design-embed";
54
- import { reactTarget } from "@design-embed/target-react";
59
+ import { ReactTarget } from "@design-embed/target-react";
55
60
 
56
61
  export default defineConfig({
57
62
  output: {
58
- target: reactTarget,
63
+ target: new ReactTarget(),
59
64
  viewName: "WelcomeHero",
60
65
  viewsDir: "src/components",
61
66
  assembliesDir: "src/pages",
package/dist/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
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";
2
+ import { a as formatDiagnosticText, d as loadConfig, i as checkGeneratedFiles, n as embed, o as toJsonDiagnostics } from "./core-BLV62TaX.mjs";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { dirname, isAbsolute, resolve } from "node:path";
4
+ import { dirname, resolve } from "node:path";
5
5
  //#region packages/design-embed/src/args.ts
6
6
  function parseArgs(args) {
7
7
  const positionals = [];
@@ -38,104 +38,44 @@ function getFormat(flags) {
38
38
  return flags["--format"] === "json" ? "json" : "text";
39
39
  }
40
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
41
  //#region packages/design-embed/src/commands/compile.ts
71
42
  async function runCompileCommand(flags, options = {}) {
72
43
  const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
73
- const inputPath = getStringFlag(flags, "--input") ?? getStringFlag(flags, "--");
74
44
  const explicitConfigPath = getStringFlag(flags, "--config");
75
45
  const defaultConfigPath = resolve(cwd, "design-embed.config.ts");
76
46
  const configPath = explicitConfigPath ?? (existsSync(defaultConfigPath) ? "design-embed.config.ts" : void 0);
77
47
  const quiet = getBooleanFlag(flags, "--quiet");
78
48
  const format = getFormat(flags);
79
- const generateTests = !getBooleanFlag(flags, "--no-test");
80
49
  const diagnostics = [];
81
- if (!inputPath) {
50
+ if (!configPath) {
82
51
  diagnostics.push({
83
- code: "INPUT_REQUIRED",
84
- message: "--input is required.",
52
+ code: "CONFIG_REQUIRED",
53
+ message: "No config file found. Create design-embed.config.ts or use --config.",
85
54
  severity: "error"
86
55
  });
87
56
  printDiagnostics(diagnostics, format, quiet);
88
57
  return 2;
89
58
  }
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
- });
59
+ const configResult = await loadConfig(configPath, cwd);
60
+ diagnostics.push(...configResult.diagnostics);
61
+ const config = configResult.config;
62
+ if (hasErrors$1(diagnostics)) {
98
63
  printDiagnostics(diagnostics, format, quiet);
99
64
  return 2;
100
65
  }
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;
66
+ const isCheckMode = options.check && !getBooleanFlag(flags, "--write");
115
67
  const result = await embed({
116
- html,
117
- css,
118
- configPath,
119
68
  config,
120
69
  cwd,
121
- targetEmitter: targetAdapter.emitter
70
+ dryRun: isCheckMode,
71
+ generateTests: !getBooleanFlag(flags, "--no-test")
122
72
  });
123
73
  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
74
  if (hasErrors$1(diagnostics)) {
135
75
  printDiagnostics(diagnostics, format, quiet);
136
76
  return 2;
137
77
  }
138
- if (options.check && !getBooleanFlag(flags, "--write")) {
78
+ if (isCheckMode) {
139
79
  const checkResult = checkGeneratedFiles({
140
80
  cwd,
141
81
  files: result.files,
@@ -143,17 +83,10 @@ async function runCompileCommand(flags, options = {}) {
143
83
  return existsSync(path) ? readFileSync(path, "utf-8") : void 0;
144
84
  }
145
85
  });
146
- const checkDiagnostics = checkResult.diagnostics;
147
- diagnostics.push(...checkDiagnostics);
86
+ diagnostics.push(...checkResult.diagnostics);
148
87
  printDiagnostics(diagnostics, format, quiet);
149
88
  return checkResult.ok ? 0 : 3;
150
89
  }
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
90
  printDiagnostics(diagnostics, format, quiet);
158
91
  if (!quiet && format === "text") console.log(`Success. Generated ${result.files.length} file(s).`);
159
92
  return 0;
@@ -170,14 +103,6 @@ function printDiagnostics(diagnostics, format, quiet) {
170
103
  else console.warn(output);
171
104
  }
172
105
  }
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
106
  function hasErrors$1(diagnostics) {
182
107
  return diagnostics.some((diagnostic) => diagnostic.severity === "error");
183
108
  }
@@ -201,13 +126,7 @@ async function runGenerateTestsCommand(flags) {
201
126
  printDiagnostics(diagnostics, format, quiet);
202
127
  return 2;
203
128
  }
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) {
129
+ if (!getTestGenerator(config)) {
211
130
  diagnostics.push({
212
131
  code: "TEST_TARGET_UNSUPPORTED",
213
132
  message: "generate-tests requires output.target to be a target adapter with generateTests().",
@@ -216,72 +135,23 @@ async function runGenerateTestsCommand(flags) {
216
135
  printDiagnostics(diagnostics, format, quiet);
217
136
  return 2;
218
137
  }
219
- const result = testGenerator.generateTests({
220
- html: source.html,
221
- css: source.css,
138
+ const result = await embed({
222
139
  config,
223
- diagnostics
140
+ cwd,
141
+ generateTests: true
224
142
  });
143
+ diagnostics.push(...result.diagnostics);
225
144
  if (hasErrors(diagnostics)) {
226
145
  printDiagnostics(diagnostics, format, quiet);
227
146
  return 2;
228
147
  }
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
148
  printDiagnostics(diagnostics, format, quiet);
236
149
  if (!quiet && format === "text") console.log(`Success. Generated ${result.files.length} test file(s).`);
237
150
  return 0;
238
151
  }
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
152
  function getTestGenerator(config) {
283
153
  const target = config.output?.target;
284
- return target && target !== "html" && "generateTests" in target ? target : void 0;
154
+ return !!(target && target !== "html" && "generateTests" in target);
285
155
  }
286
156
  function hasErrors(diagnostics) {
287
157
  return diagnostics.some((diagnostic) => diagnostic.severity === "error");
@@ -326,13 +196,12 @@ async function runInitCommand(flags) {
326
196
  function configTemplate(viewName) {
327
197
  return `import {
328
198
  \tdefineConfig,
329
- \ttype PluginDefinition,
330
199
  \ttype SourcePlugin,
331
200
  \ttype SourcePluginInput,
332
201
  \ttype SourcePluginResult,
333
202
  } from "design-embed";
334
203
 
335
- class HtmlFetcherPlugin implements PluginDefinition, SourcePlugin {
204
+ class HtmlFetcherPlugin implements SourcePlugin {
336
205
  \treadonly name = "html-fetcher";
337
206
  \tprivate readonly options: { url: string };
338
207
 
@@ -374,11 +243,9 @@ class HtmlFetcherPlugin implements PluginDefinition, SourcePlugin {
374
243
  }
375
244
 
376
245
  export default defineConfig({
377
- \tplugins: [
378
- \t\tnew HtmlFetcherPlugin({
379
- \t\t\turl: "https://www.scrapethissite.com/pages/",
380
- \t\t}),
381
- \t],
246
+ \tsource: new HtmlFetcherPlugin({
247
+ \t\turl: "https://www.scrapethissite.com/pages/",
248
+ \t}),
382
249
  \toutput: {
383
250
  \t\tviewName: "${viewName}",
384
251
  \t\tviewsDir: "src/generated/views",
@@ -387,77 +254,14 @@ export default defineConfig({
387
254
  `;
388
255
  }
389
256
  //#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
257
  //#region packages/design-embed/src/cli.ts
441
258
  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);
259
+ const { command, flags } = parseArgs(process.argv.slice(2));
260
+ if (command === "check") return runCheckCommand(flags);
261
+ if (command === "generate-tests") return runGenerateTestsCommand(flags);
262
+ if (command === "init") return runInitCommand(flags);
453
263
  return runCompileCommand(flags);
454
264
  }
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
265
  main().then((code) => {
462
266
  if (code !== 0) process.exit(code);
463
267
  }).catch((error) => {