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 +7 -2
- package/dist/cli.mjs +28 -224
- package/dist/core-BLV62TaX.mjs +907 -0
- package/dist/index.d.mts +185 -312
- package/dist/index.mjs +2 -2
- package/package.json +2 -10
- package/src/cli.ts +7 -32
- package/src/commands/compile.ts +20 -88
- package/src/commands/generateTests.ts +9 -91
- package/src/commands/init.ts +5 -8
- package/src/commands/plugin.ts +3 -14
- package/src/config/index.ts +302 -0
- package/src/core/diagnostics/diagnostic.ts +18 -0
- package/src/core/diagnostics/jsonDiagnostic.ts +51 -0
- package/src/core/index.ts +579 -0
- package/src/core/nodes.ts +74 -0
- package/src/core/pipeline/checkMode.ts +46 -0
- package/src/core/plugins/pluginApi.ts +44 -0
- package/src/core/types.ts +120 -0
- package/src/index.ts +48 -2
- package/src/targets/html.ts +565 -12
- package/dist/src-D3fnqGCq.mjs +0 -511
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 {
|
|
59
|
+
import { ReactTarget } from "@design-embed/target-react";
|
|
55
60
|
|
|
56
61
|
export default defineConfig({
|
|
57
62
|
output: {
|
|
58
|
-
target:
|
|
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 {
|
|
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,
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
65
|
-
}
|
|
66
|
-
function escapeAttribute(value) {
|
|
67
|
-
return escapeHtml(value).replace(/"/g, """);
|
|
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 (!
|
|
50
|
+
if (!configPath) {
|
|
82
51
|
diagnostics.push({
|
|
83
|
-
code: "
|
|
84
|
-
message: "
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
220
|
-
html: source.html,
|
|
221
|
-
css: source.css,
|
|
138
|
+
const result = await embed({
|
|
222
139
|
config,
|
|
223
|
-
|
|
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
|
|
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
|
|
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
|
-
\
|
|
378
|
-
\t\
|
|
379
|
-
\t
|
|
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
|
|
443
|
-
|
|
444
|
-
if (
|
|
445
|
-
if (
|
|
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) => {
|