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.
- package/LICENSE +1 -1
- package/README.md +98 -2
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +273 -0
- package/dist/core-BLV62TaX.mjs +907 -0
- package/dist/index.d.mts +273 -0
- package/dist/index.mjs +2 -0
- package/package.json +6 -19
- package/src/cli.ts +8 -16
- package/src/commands/compile.ts +25 -110
- package/src/commands/generateTests.ts +14 -96
- package/src/commands/init.ts +52 -55
- package/src/commands/plugin.ts +6 -21
- package/src/config/index.ts +302 -0
- package/{node_modules/@design-embed/core/src → src/core}/index.ts +151 -163
- package/src/core/nodes.ts +74 -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 +621 -0
- package/dist/args.js +0 -36
- package/dist/cli.js +0 -35
- package/dist/commands/check.js +0 -4
- package/dist/commands/compile.js +0 -157
- package/dist/commands/generateTests.js +0 -113
- package/dist/commands/init.js +0 -102
- package/dist/commands/plugin.js +0 -68
- package/dist/index.js +0 -2
- package/node_modules/@design-embed/config/README.md +0 -5
- package/node_modules/@design-embed/config/dist/index.js +0 -283
- package/node_modules/@design-embed/config/package.json +0 -19
- package/node_modules/@design-embed/config/src/index.ts +0 -518
- package/node_modules/@design-embed/core/README.md +0 -5
- package/node_modules/@design-embed/core/dist/diagnostics/diagnostic.js +0 -3
- package/node_modules/@design-embed/core/dist/diagnostics/jsonDiagnostic.js +0 -35
- package/node_modules/@design-embed/core/dist/index.js +0 -351
- package/node_modules/@design-embed/core/dist/pipeline/checkMode.js +0 -29
- package/node_modules/@design-embed/core/dist/plugins/pluginApi.js +0 -1
- package/node_modules/@design-embed/core/dist/plugins/pluginRegistry.js +0 -25
- package/node_modules/@design-embed/core/package.json +0 -19
- package/node_modules/@design-embed/core/src/plugins/pluginApi.ts +0 -78
- package/node_modules/@design-embed/core/src/plugins/pluginRegistry.ts +0 -37
- /package/{node_modules/@design-embed/core/src → src/core}/diagnostics/diagnostic.ts +0 -0
- /package/{node_modules/@design-embed/core/src → src/core}/diagnostics/jsonDiagnostic.ts +0 -0
- /package/{node_modules/@design-embed/core/src → src/core}/pipeline/checkMode.ts +0 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Jin
|
|
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,101 @@
|
|
|
1
1
|
# design-embed
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`design-embed` is the command-line workflow for compiling exported design into files that can live inside an application repository.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
+
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
|
+
|
|
46
|
+
## React Adapter Example
|
|
47
|
+
|
|
48
|
+
Install the React target adapter alongside `design-embed`:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pnpm add design-embed @design-embed/target-react
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Create a config file:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
// design-embed.config.ts
|
|
58
|
+
import { defineConfig } from "design-embed";
|
|
59
|
+
import { ReactTarget } from "@design-embed/target-react";
|
|
60
|
+
|
|
61
|
+
export default defineConfig({
|
|
62
|
+
output: {
|
|
63
|
+
target: new ReactTarget(),
|
|
64
|
+
viewName: "WelcomeHero",
|
|
65
|
+
viewsDir: "src/components",
|
|
66
|
+
assembliesDir: "src/pages",
|
|
67
|
+
styleMode: "inline",
|
|
68
|
+
},
|
|
69
|
+
tests: {
|
|
70
|
+
outputDir: "tests",
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Run the compiler with the config:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx design-embed target.html --config design-embed.config.ts
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
This writes React output through the adapter:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
src/components/WelcomeHero.view.tsx
|
|
85
|
+
src/pages/WelcomeHeroPage.tsx
|
|
86
|
+
tests/WelcomeHero.reference.html
|
|
87
|
+
tests/WelcomeHero.visual.spec.tsx
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Skip adapter-provided test generation with:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx design-embed target.html --config design-embed.config.ts --no-test
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## How It Fits Together
|
|
97
|
+
|
|
98
|
+
`design-embed` owns the user-facing CLI flow: it loads config, reads input,
|
|
99
|
+
selects the built-in HTML target or a configured target adapter, writes files,
|
|
100
|
+
and formats diagnostics. The framework-specific behavior stays in target
|
|
101
|
+
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,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as formatDiagnosticText, d as loadConfig, i as checkGeneratedFiles, n as embed, o as toJsonDiagnostics } from "./core-BLV62TaX.mjs";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, 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/commands/compile.ts
|
|
42
|
+
async function runCompileCommand(flags, options = {}) {
|
|
43
|
+
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
44
|
+
const explicitConfigPath = getStringFlag(flags, "--config");
|
|
45
|
+
const defaultConfigPath = resolve(cwd, "design-embed.config.ts");
|
|
46
|
+
const configPath = explicitConfigPath ?? (existsSync(defaultConfigPath) ? "design-embed.config.ts" : void 0);
|
|
47
|
+
const quiet = getBooleanFlag(flags, "--quiet");
|
|
48
|
+
const format = getFormat(flags);
|
|
49
|
+
const diagnostics = [];
|
|
50
|
+
if (!configPath) {
|
|
51
|
+
diagnostics.push({
|
|
52
|
+
code: "CONFIG_REQUIRED",
|
|
53
|
+
message: "No config file found. Create design-embed.config.ts or use --config.",
|
|
54
|
+
severity: "error"
|
|
55
|
+
});
|
|
56
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
57
|
+
return 2;
|
|
58
|
+
}
|
|
59
|
+
const configResult = await loadConfig(configPath, cwd);
|
|
60
|
+
diagnostics.push(...configResult.diagnostics);
|
|
61
|
+
const config = configResult.config;
|
|
62
|
+
if (hasErrors$1(diagnostics)) {
|
|
63
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
64
|
+
return 2;
|
|
65
|
+
}
|
|
66
|
+
const isCheckMode = options.check && !getBooleanFlag(flags, "--write");
|
|
67
|
+
const result = await embed({
|
|
68
|
+
config,
|
|
69
|
+
cwd,
|
|
70
|
+
dryRun: isCheckMode,
|
|
71
|
+
generateTests: !getBooleanFlag(flags, "--no-test")
|
|
72
|
+
});
|
|
73
|
+
diagnostics.push(...result.diagnostics);
|
|
74
|
+
if (hasErrors$1(diagnostics)) {
|
|
75
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
76
|
+
return 2;
|
|
77
|
+
}
|
|
78
|
+
if (isCheckMode) {
|
|
79
|
+
const checkResult = checkGeneratedFiles({
|
|
80
|
+
cwd,
|
|
81
|
+
files: result.files,
|
|
82
|
+
readFile(path) {
|
|
83
|
+
return existsSync(path) ? readFileSync(path, "utf-8") : void 0;
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
diagnostics.push(...checkResult.diagnostics);
|
|
87
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
88
|
+
return checkResult.ok ? 0 : 3;
|
|
89
|
+
}
|
|
90
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
91
|
+
if (!quiet && format === "text") console.log(`Success. Generated ${result.files.length} file(s).`);
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
function printDiagnostics(diagnostics, format, quiet) {
|
|
95
|
+
if (format === "json") {
|
|
96
|
+
console.log(JSON.stringify({ diagnostics: toJsonDiagnostics(diagnostics) }, null, 2));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (quiet) return;
|
|
100
|
+
for (const diagnostic of diagnostics) {
|
|
101
|
+
const output = formatDiagnosticText(diagnostic);
|
|
102
|
+
if (diagnostic.severity === "error") console.error(output);
|
|
103
|
+
else console.warn(output);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function hasErrors$1(diagnostics) {
|
|
107
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region packages/design-embed/src/commands/check.ts
|
|
111
|
+
async function runCheckCommand(flags) {
|
|
112
|
+
return runCompileCommand(flags, { check: true });
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region packages/design-embed/src/commands/generateTests.ts
|
|
116
|
+
async function runGenerateTestsCommand(flags) {
|
|
117
|
+
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
118
|
+
const configPath = getStringFlag(flags, "--config") ?? "design-embed.config.ts";
|
|
119
|
+
const quiet = getBooleanFlag(flags, "--quiet");
|
|
120
|
+
const format = getFormat(flags);
|
|
121
|
+
const diagnostics = [];
|
|
122
|
+
const configResult = await loadConfig(configPath, cwd);
|
|
123
|
+
diagnostics.push(...configResult.diagnostics);
|
|
124
|
+
const config = configResult.config;
|
|
125
|
+
if (!config || hasErrors(diagnostics)) {
|
|
126
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
127
|
+
return 2;
|
|
128
|
+
}
|
|
129
|
+
if (!getTestGenerator(config)) {
|
|
130
|
+
diagnostics.push({
|
|
131
|
+
code: "TEST_TARGET_UNSUPPORTED",
|
|
132
|
+
message: "generate-tests requires output.target to be a target adapter with generateTests().",
|
|
133
|
+
severity: "error"
|
|
134
|
+
});
|
|
135
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
136
|
+
return 2;
|
|
137
|
+
}
|
|
138
|
+
const result = await embed({
|
|
139
|
+
config,
|
|
140
|
+
cwd,
|
|
141
|
+
generateTests: true
|
|
142
|
+
});
|
|
143
|
+
diagnostics.push(...result.diagnostics);
|
|
144
|
+
if (hasErrors(diagnostics)) {
|
|
145
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
149
|
+
if (!quiet && format === "text") console.log(`Success. Generated ${result.files.length} test file(s).`);
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
function getTestGenerator(config) {
|
|
153
|
+
const target = config.output?.target;
|
|
154
|
+
return !!(target && target !== "html" && "generateTests" in target);
|
|
155
|
+
}
|
|
156
|
+
function hasErrors(diagnostics) {
|
|
157
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region packages/design-embed/src/commands/init.ts
|
|
161
|
+
async function runInitCommand(flags) {
|
|
162
|
+
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
163
|
+
const quiet = getBooleanFlag(flags, "--quiet");
|
|
164
|
+
const force = getBooleanFlag(flags, "--force");
|
|
165
|
+
const format = getFormat(flags);
|
|
166
|
+
const viewName = getStringFlag(flags, "--view-name") ?? "WelcomeHero";
|
|
167
|
+
const diagnostics = [];
|
|
168
|
+
const files = [{
|
|
169
|
+
path: "design-embed.config.ts",
|
|
170
|
+
contents: configTemplate(viewName)
|
|
171
|
+
}];
|
|
172
|
+
let written = 0;
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
const outPath = resolve(cwd, file.path);
|
|
175
|
+
if (existsSync(outPath) && !force) {
|
|
176
|
+
diagnostics.push({
|
|
177
|
+
code: "INIT_FILE_EXISTS",
|
|
178
|
+
message: `Skipped existing file: ${file.path}. Pass --force to overwrite it.`,
|
|
179
|
+
severity: "warning",
|
|
180
|
+
file: file.path
|
|
181
|
+
});
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
185
|
+
writeFileSync(outPath, file.contents, "utf-8");
|
|
186
|
+
written += 1;
|
|
187
|
+
if (!quiet && format === "text") console.log(`Wrote ${file.path}`);
|
|
188
|
+
}
|
|
189
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
190
|
+
if (!quiet && format === "text") {
|
|
191
|
+
console.log(`Success. Initialized design-embed with ${written} file(s).`);
|
|
192
|
+
console.log("Next: pnpm exec design-embed --out ./design.html");
|
|
193
|
+
}
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
function configTemplate(viewName) {
|
|
197
|
+
return `import {
|
|
198
|
+
\tdefineConfig,
|
|
199
|
+
\ttype SourcePlugin,
|
|
200
|
+
\ttype SourcePluginInput,
|
|
201
|
+
\ttype SourcePluginResult,
|
|
202
|
+
} from "design-embed";
|
|
203
|
+
|
|
204
|
+
class HtmlFetcherPlugin implements SourcePlugin {
|
|
205
|
+
\treadonly name = "html-fetcher";
|
|
206
|
+
\tprivate readonly options: { url: string };
|
|
207
|
+
|
|
208
|
+
\tconstructor(options: { url: string }) {
|
|
209
|
+
\t\tthis.options = options;
|
|
210
|
+
\t}
|
|
211
|
+
|
|
212
|
+
\tasync run(_input: SourcePluginInput): Promise<SourcePluginResult> {
|
|
213
|
+
\t\ttry {
|
|
214
|
+
\t\t\tconst response = await fetch(this.options.url);
|
|
215
|
+
\t\t\tif (!response.ok) {
|
|
216
|
+
\t\t\t\treturn {
|
|
217
|
+
\t\t\t\t\tdiagnostics: [
|
|
218
|
+
\t\t\t\t\t\t{
|
|
219
|
+
\t\t\t\t\t\t\tcode: "HTML_FETCH_FAILED",
|
|
220
|
+
\t\t\t\t\t\t\tmessage: \`Failed to fetch HTML: \${response.status} \${response.statusText}\`,
|
|
221
|
+
\t\t\t\t\t\t\tseverity: "error",
|
|
222
|
+
\t\t\t\t\t\t},
|
|
223
|
+
\t\t\t\t\t],
|
|
224
|
+
\t\t\t\t};
|
|
225
|
+
\t\t\t}
|
|
226
|
+
|
|
227
|
+
\t\t\treturn {
|
|
228
|
+
\t\t\t\thtml: await response.text(),
|
|
229
|
+
\t\t\t\tdiagnostics: [],
|
|
230
|
+
\t\t\t};
|
|
231
|
+
\t\t} catch (error) {
|
|
232
|
+
\t\t\treturn {
|
|
233
|
+
\t\t\t\tdiagnostics: [
|
|
234
|
+
\t\t\t\t\t{
|
|
235
|
+
\t\t\t\t\t\tcode: "HTML_FETCH_FAILED",
|
|
236
|
+
\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),
|
|
237
|
+
\t\t\t\t\t\tseverity: "error",
|
|
238
|
+
\t\t\t\t\t},
|
|
239
|
+
\t\t\t\t],
|
|
240
|
+
\t\t\t};
|
|
241
|
+
\t\t}
|
|
242
|
+
\t}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default defineConfig({
|
|
246
|
+
\tsource: new HtmlFetcherPlugin({
|
|
247
|
+
\t\turl: "https://www.scrapethissite.com/pages/",
|
|
248
|
+
\t}),
|
|
249
|
+
\toutput: {
|
|
250
|
+
\t\tviewName: "${viewName}",
|
|
251
|
+
\t\tviewsDir: "src/generated/views",
|
|
252
|
+
\t},
|
|
253
|
+
});
|
|
254
|
+
`;
|
|
255
|
+
}
|
|
256
|
+
//#endregion
|
|
257
|
+
//#region packages/design-embed/src/cli.ts
|
|
258
|
+
async function main() {
|
|
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);
|
|
263
|
+
return runCompileCommand(flags);
|
|
264
|
+
}
|
|
265
|
+
main().then((code) => {
|
|
266
|
+
if (code !== 0) process.exit(code);
|
|
267
|
+
}).catch((error) => {
|
|
268
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
269
|
+
console.error(`Pipeline failed: ${message}`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
});
|
|
272
|
+
//#endregion
|
|
273
|
+
export {};
|