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.
- package/LICENSE +1 -1
- package/README.md +93 -2
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +469 -0
- package/dist/index.d.mts +400 -0
- package/dist/index.mjs +2 -0
- package/dist/src-D3fnqGCq.mjs +511 -0
- package/package.json +8 -13
- package/src/cli.ts +18 -1
- package/src/commands/compile.ts +46 -63
- package/src/commands/generateTests.ts +17 -17
- package/src/commands/init.ts +54 -54
- package/src/commands/plugin.ts +3 -7
- package/src/targets/html.ts +68 -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/diagnostics/diagnostic.ts +0 -18
- package/node_modules/@design-embed/core/src/diagnostics/jsonDiagnostic.ts +0 -51
- package/node_modules/@design-embed/core/src/index.ts +0 -591
- package/node_modules/@design-embed/core/src/pipeline/checkMode.ts +0 -46
- 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/src/commands/compile.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname,
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { type DesignEmbedConfig, loadConfig } from "@design-embed/config";
|
|
4
4
|
import {
|
|
5
5
|
checkGeneratedFiles,
|
|
6
6
|
type Diagnostic,
|
|
7
7
|
embed,
|
|
8
8
|
formatDiagnosticText,
|
|
9
|
-
type
|
|
9
|
+
type TargetEmitter,
|
|
10
|
+
type TargetTestGenerator,
|
|
10
11
|
toJsonDiagnostics,
|
|
11
12
|
} from "@design-embed/core";
|
|
12
|
-
import { htmlEmitter } from "@design-embed/target-html";
|
|
13
|
-
import { reactEmitter } from "@design-embed/target-react";
|
|
14
13
|
import { getBooleanFlag, getFormat, getStringFlag } from "../args.ts";
|
|
14
|
+
import { htmlEmitter } from "../targets/html.ts";
|
|
15
15
|
|
|
16
16
|
export interface CompileCommandOptions {
|
|
17
17
|
check?: boolean;
|
|
@@ -22,10 +22,16 @@ export async function runCompileCommand(
|
|
|
22
22
|
options: CompileCommandOptions = {},
|
|
23
23
|
): Promise<number> {
|
|
24
24
|
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
25
|
-
const inputPath =
|
|
26
|
-
|
|
25
|
+
const inputPath =
|
|
26
|
+
getStringFlag(flags, "--input") ?? getStringFlag(flags, "--");
|
|
27
|
+
const explicitConfigPath = getStringFlag(flags, "--config");
|
|
28
|
+
const defaultConfigPath = resolve(cwd, "design-embed.config.ts");
|
|
29
|
+
const configPath =
|
|
30
|
+
explicitConfigPath ??
|
|
31
|
+
(existsSync(defaultConfigPath) ? "design-embed.config.ts" : undefined);
|
|
27
32
|
const quiet = getBooleanFlag(flags, "--quiet");
|
|
28
33
|
const format = getFormat(flags);
|
|
34
|
+
const generateTests = !getBooleanFlag(flags, "--no-test");
|
|
29
35
|
const diagnostics: Diagnostic[] = [];
|
|
30
36
|
|
|
31
37
|
if (!inputPath) {
|
|
@@ -51,7 +57,6 @@ export async function runCompileCommand(
|
|
|
51
57
|
}
|
|
52
58
|
|
|
53
59
|
let config: DesignEmbedConfig | undefined;
|
|
54
|
-
let transformers: TransformerPlugin[] = [];
|
|
55
60
|
if (configPath) {
|
|
56
61
|
const configResult = await loadConfig(configPath, cwd);
|
|
57
62
|
diagnostics.push(...configResult.diagnostics);
|
|
@@ -61,29 +66,36 @@ export async function runCompileCommand(
|
|
|
61
66
|
printDiagnostics(diagnostics, format, quiet);
|
|
62
67
|
return 2;
|
|
63
68
|
}
|
|
64
|
-
|
|
65
|
-
transformers = await loadTransformers(config, configPath, cwd, diagnostics);
|
|
66
|
-
if (hasErrors(diagnostics)) {
|
|
67
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
68
|
-
return 2;
|
|
69
|
-
}
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
const
|
|
73
|
-
const targetEmitter = target === "react" ? reactEmitter : htmlEmitter;
|
|
71
|
+
const targetAdapter = getTargetAdapter(config);
|
|
74
72
|
|
|
75
73
|
const cssPath = getStringFlag(flags, "--css");
|
|
74
|
+
const html = readFileSync(resolvedInputPath, "utf-8");
|
|
75
|
+
const css = cssPath
|
|
76
|
+
? readFileSync(resolve(cwd, cssPath), "utf-8")
|
|
77
|
+
: undefined;
|
|
76
78
|
const result = await embed({
|
|
77
|
-
html
|
|
78
|
-
css
|
|
79
|
+
html,
|
|
80
|
+
css,
|
|
79
81
|
configPath,
|
|
80
82
|
config,
|
|
81
83
|
cwd,
|
|
82
|
-
|
|
83
|
-
targetEmitter,
|
|
84
|
+
targetEmitter: targetAdapter.emitter,
|
|
84
85
|
});
|
|
85
86
|
diagnostics.push(...result.diagnostics);
|
|
86
87
|
|
|
88
|
+
if (generateTests && targetAdapter.testGenerator) {
|
|
89
|
+
const testResult = targetAdapter.testGenerator.generateTests({
|
|
90
|
+
html,
|
|
91
|
+
css,
|
|
92
|
+
config: config ?? {},
|
|
93
|
+
diagnostics,
|
|
94
|
+
generatedFiles: result.files,
|
|
95
|
+
});
|
|
96
|
+
result.files.push(...testResult.files);
|
|
97
|
+
}
|
|
98
|
+
|
|
87
99
|
if (hasErrors(diagnostics)) {
|
|
88
100
|
printDiagnostics(diagnostics, format, quiet);
|
|
89
101
|
return 2;
|
|
@@ -143,54 +155,25 @@ export function printDiagnostics(
|
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
157
|
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
interface ResolvedTargetAdapter {
|
|
159
|
+
emitter: TargetEmitter;
|
|
160
|
+
testGenerator?: TargetTestGenerator;
|
|
148
161
|
}
|
|
149
162
|
|
|
150
|
-
|
|
163
|
+
function getTargetAdapter(
|
|
151
164
|
config: DesignEmbedConfig | undefined,
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
}
|
|
165
|
+
): ResolvedTargetAdapter {
|
|
166
|
+
const target = config?.output?.target;
|
|
167
|
+
if (!target || target === "html") {
|
|
168
|
+
return { emitter: htmlEmitter };
|
|
191
169
|
}
|
|
192
|
-
|
|
193
|
-
|
|
170
|
+
return {
|
|
171
|
+
emitter: target as TargetEmitter,
|
|
172
|
+
testGenerator:
|
|
173
|
+
"generateTests" in target
|
|
174
|
+
? (target as TargetEmitter & TargetTestGenerator)
|
|
175
|
+
: undefined,
|
|
176
|
+
};
|
|
194
177
|
}
|
|
195
178
|
|
|
196
179
|
function hasErrors(diagnostics: Diagnostic[]): boolean {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
3
|
import { type DesignEmbedConfig, loadConfig } from "@design-embed/config";
|
|
4
|
-
import type { Diagnostic } from "@design-embed/core";
|
|
5
|
-
import { reactTestGenerator } from "@design-embed/target-react";
|
|
4
|
+
import type { Diagnostic, TargetTestGenerator } from "@design-embed/core";
|
|
6
5
|
import { getBooleanFlag, getFormat, getStringFlag } from "../args.ts";
|
|
7
6
|
import { printDiagnostics } from "./compile.ts";
|
|
8
7
|
|
|
@@ -10,21 +9,12 @@ export async function runGenerateTestsCommand(
|
|
|
10
9
|
flags: Record<string, string | boolean>,
|
|
11
10
|
): Promise<number> {
|
|
12
11
|
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
13
|
-
const configPath =
|
|
12
|
+
const configPath =
|
|
13
|
+
getStringFlag(flags, "--config") ?? "design-embed.config.ts";
|
|
14
14
|
const quiet = getBooleanFlag(flags, "--quiet");
|
|
15
15
|
const format = getFormat(flags);
|
|
16
16
|
const diagnostics: Diagnostic[] = [];
|
|
17
17
|
|
|
18
|
-
if (!configPath) {
|
|
19
|
-
diagnostics.push({
|
|
20
|
-
code: "CONFIG_REQUIRED",
|
|
21
|
-
message: "--config is required for generate-tests.",
|
|
22
|
-
severity: "error",
|
|
23
|
-
});
|
|
24
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
25
|
-
return 2;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
18
|
const configResult = await loadConfig(configPath, cwd);
|
|
29
19
|
diagnostics.push(...configResult.diagnostics);
|
|
30
20
|
const config = configResult.config;
|
|
@@ -39,18 +29,19 @@ export async function runGenerateTestsCommand(
|
|
|
39
29
|
return 2;
|
|
40
30
|
}
|
|
41
31
|
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
32
|
+
const testGenerator = getTestGenerator(config);
|
|
33
|
+
if (!testGenerator) {
|
|
44
34
|
diagnostics.push({
|
|
45
35
|
code: "TEST_TARGET_UNSUPPORTED",
|
|
46
|
-
message:
|
|
36
|
+
message:
|
|
37
|
+
"generate-tests requires output.target to be a target adapter with generateTests().",
|
|
47
38
|
severity: "error",
|
|
48
39
|
});
|
|
49
40
|
printDiagnostics(diagnostics, format, quiet);
|
|
50
41
|
return 2;
|
|
51
42
|
}
|
|
52
43
|
|
|
53
|
-
const result =
|
|
44
|
+
const result = testGenerator.generateTests({
|
|
54
45
|
html: source.html,
|
|
55
46
|
css: source.css,
|
|
56
47
|
config,
|
|
@@ -135,6 +126,15 @@ function resolveConfigPath(path: string, configDir: string): string {
|
|
|
135
126
|
return isAbsolute(path) ? path : resolve(configDir, path);
|
|
136
127
|
}
|
|
137
128
|
|
|
129
|
+
function getTestGenerator(
|
|
130
|
+
config: DesignEmbedConfig,
|
|
131
|
+
): TargetTestGenerator | undefined {
|
|
132
|
+
const target = config.output?.target;
|
|
133
|
+
return target && target !== "html" && "generateTests" in target
|
|
134
|
+
? (target as TargetTestGenerator)
|
|
135
|
+
: undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
138
|
function hasErrors(diagnostics: Diagnostic[]): boolean {
|
|
139
139
|
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
140
140
|
}
|
package/src/commands/init.ts
CHANGED
|
@@ -19,14 +19,6 @@ export async function runInitCommand(
|
|
|
19
19
|
path: "design-embed.config.ts",
|
|
20
20
|
contents: configTemplate(viewName),
|
|
21
21
|
},
|
|
22
|
-
{
|
|
23
|
-
path: "design.html",
|
|
24
|
-
contents: designHtmlTemplate(),
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
path: "playwright-ct.config.ts",
|
|
28
|
-
contents: playwrightConfigTemplate(),
|
|
29
|
-
},
|
|
30
22
|
];
|
|
31
23
|
|
|
32
24
|
let written = 0;
|
|
@@ -52,62 +44,70 @@ export async function runInitCommand(
|
|
|
52
44
|
printDiagnostics(diagnostics, format, quiet);
|
|
53
45
|
if (!quiet && format === "text") {
|
|
54
46
|
console.log(`Success. Initialized design-embed with ${written} file(s).`);
|
|
55
|
-
console.log(
|
|
56
|
-
"Next: pnpm exec design-embed --input ./design.html --config ./design-embed.config.ts",
|
|
57
|
-
);
|
|
47
|
+
console.log("Next: pnpm exec design-embed --out ./design.html");
|
|
58
48
|
}
|
|
59
49
|
return 0;
|
|
60
50
|
}
|
|
61
51
|
|
|
62
52
|
function configTemplate(viewName: string): string {
|
|
63
|
-
return `import {
|
|
53
|
+
return `import {
|
|
54
|
+
\tdefineConfig,
|
|
55
|
+
\ttype PluginDefinition,
|
|
56
|
+
\ttype SourcePlugin,
|
|
57
|
+
\ttype SourcePluginInput,
|
|
58
|
+
\ttype SourcePluginResult,
|
|
59
|
+
} from "design-embed";
|
|
64
60
|
|
|
65
|
-
|
|
66
|
-
\
|
|
67
|
-
\
|
|
68
|
-
\t\tviewName: "${viewName}",
|
|
69
|
-
\t\tviewsDir: "src/generated/views",
|
|
70
|
-
\t\tstyleMode: "inline",
|
|
71
|
-
\t},
|
|
72
|
-
\ttests: {
|
|
73
|
-
\t\toutputDir: "tests/generated/design-embed",
|
|
74
|
-
\t\trunner: "playwright",
|
|
75
|
-
\t\tsource: {
|
|
76
|
-
\t\t\thtml: "./design.html",
|
|
77
|
-
\t\t},
|
|
78
|
-
\t\tviewports: [
|
|
79
|
-
\t\t\t{ name: "mobile", width: 390, height: 844 },
|
|
80
|
-
\t\t\t{ name: "desktop", width: 1440, height: 900 },
|
|
81
|
-
\t\t],
|
|
82
|
-
\t\tstates: [{ name: "default" }],
|
|
83
|
-
\t\tassertions: {
|
|
84
|
-
\t\t\tscreenshot: true,
|
|
85
|
-
\t\t\tlayout: true,
|
|
86
|
-
\t\t\tlayoutTolerance: 1,
|
|
87
|
-
\t\t\tselectors: [":scope", ":scope *"],
|
|
88
|
-
\t\t},
|
|
89
|
-
\t},
|
|
90
|
-
});
|
|
91
|
-
`;
|
|
92
|
-
}
|
|
61
|
+
class HtmlFetcherPlugin implements PluginDefinition, SourcePlugin {
|
|
62
|
+
\treadonly name = "html-fetcher";
|
|
63
|
+
\tprivate readonly options: { url: string };
|
|
93
64
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
\t
|
|
97
|
-
\t<h1 style="margin: 0 0 12px; font-size: 32px; line-height: 1.1;">Welcome hero</h1>
|
|
98
|
-
\t<p style="margin: 0 0 20px; font-size: 16px; line-height: 1.5;">Replace this file with HTML exported from your design source.</p>
|
|
99
|
-
\t<button data-role="primary" style="border: 0; border-radius: 999px; padding: 12px 18px; background: #2563eb; color: white; font-size: 14px; font-weight: 700;">Get started</button>
|
|
100
|
-
</section>
|
|
101
|
-
`;
|
|
102
|
-
}
|
|
65
|
+
\tconstructor(options: { url: string }) {
|
|
66
|
+
\t\tthis.options = options;
|
|
67
|
+
\t}
|
|
103
68
|
|
|
104
|
-
|
|
105
|
-
|
|
69
|
+
\tasync run(_input: SourcePluginInput): Promise<SourcePluginResult> {
|
|
70
|
+
\t\ttry {
|
|
71
|
+
\t\t\tconst response = await fetch(this.options.url);
|
|
72
|
+
\t\t\tif (!response.ok) {
|
|
73
|
+
\t\t\t\treturn {
|
|
74
|
+
\t\t\t\t\tdiagnostics: [
|
|
75
|
+
\t\t\t\t\t\t{
|
|
76
|
+
\t\t\t\t\t\t\tcode: "HTML_FETCH_FAILED",
|
|
77
|
+
\t\t\t\t\t\t\tmessage: \`Failed to fetch HTML: \${response.status} \${response.statusText}\`,
|
|
78
|
+
\t\t\t\t\t\t\tseverity: "error",
|
|
79
|
+
\t\t\t\t\t\t},
|
|
80
|
+
\t\t\t\t\t],
|
|
81
|
+
\t\t\t\t};
|
|
82
|
+
\t\t\t}
|
|
83
|
+
|
|
84
|
+
\t\t\treturn {
|
|
85
|
+
\t\t\t\thtml: await response.text(),
|
|
86
|
+
\t\t\t\tdiagnostics: [],
|
|
87
|
+
\t\t\t};
|
|
88
|
+
\t\t} catch (error) {
|
|
89
|
+
\t\t\treturn {
|
|
90
|
+
\t\t\t\tdiagnostics: [
|
|
91
|
+
\t\t\t\t\t{
|
|
92
|
+
\t\t\t\t\t\tcode: "HTML_FETCH_FAILED",
|
|
93
|
+
\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),
|
|
94
|
+
\t\t\t\t\t\tseverity: "error",
|
|
95
|
+
\t\t\t\t\t},
|
|
96
|
+
\t\t\t\t],
|
|
97
|
+
\t\t\t};
|
|
98
|
+
\t\t}
|
|
99
|
+
\t}
|
|
100
|
+
}
|
|
106
101
|
|
|
107
102
|
export default defineConfig({
|
|
108
|
-
\
|
|
109
|
-
\
|
|
110
|
-
\t\
|
|
103
|
+
\tplugins: [
|
|
104
|
+
\t\tnew HtmlFetcherPlugin({
|
|
105
|
+
\t\t\turl: "https://www.scrapethissite.com/pages/",
|
|
106
|
+
\t\t}),
|
|
107
|
+
\t],
|
|
108
|
+
\toutput: {
|
|
109
|
+
\t\tviewName: "${viewName}",
|
|
110
|
+
\t\tviewsDir: "src/generated/views",
|
|
111
111
|
\t},
|
|
112
112
|
});
|
|
113
113
|
`;
|
package/src/commands/plugin.ts
CHANGED
|
@@ -8,13 +8,9 @@ export async function runPluginCommand(
|
|
|
8
8
|
_name: string | undefined,
|
|
9
9
|
flags: Record<string, string | boolean>,
|
|
10
10
|
): Promise<number> {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return 2;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const cwd = process.cwd();
|
|
11
|
+
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
12
|
+
const configPath =
|
|
13
|
+
getStringFlag(flags, "--config") ?? "design-embed.config.ts";
|
|
18
14
|
const configResult = await loadConfig(configPath, cwd);
|
|
19
15
|
for (const diagnostic of configResult.diagnostics) {
|
|
20
16
|
if (diagnostic.severity === "error") {
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DesignNode,
|
|
3
|
+
TargetEmitInput,
|
|
4
|
+
TargetEmitResult,
|
|
5
|
+
TargetEmitter,
|
|
6
|
+
} from "@design-embed/core";
|
|
7
|
+
|
|
8
|
+
export function emitHtmlDebug(nodes: DesignNode[], css?: string): string {
|
|
9
|
+
const body = nodes.map((node) => emitNode(node, 0)).join("");
|
|
10
|
+
if (!css?.trim()) {
|
|
11
|
+
return body;
|
|
12
|
+
}
|
|
13
|
+
return `<style>\n${css.trim()}\n</style>\n${body}\n`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const htmlEmitter: TargetEmitter = {
|
|
17
|
+
emit({ nodes, css, config }: TargetEmitInput): TargetEmitResult {
|
|
18
|
+
const viewsDir = config?.output?.viewsDir ?? "src/generated/views";
|
|
19
|
+
return {
|
|
20
|
+
files: [
|
|
21
|
+
{
|
|
22
|
+
path: `${viewsDir}/debug.html`,
|
|
23
|
+
contents: emitHtmlDebug(nodes, css),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function emitNode(node: DesignNode, depth: number): string {
|
|
31
|
+
const indent = "\t".repeat(depth);
|
|
32
|
+
if (node.kind === "text") {
|
|
33
|
+
return `${indent}${escapeHtml(node.text ?? "")}\n`;
|
|
34
|
+
}
|
|
35
|
+
if (node.kind === "component") {
|
|
36
|
+
return `${indent}<${node.component}></${node.component}>\n`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const attributes = Object.entries(node.attributes ?? {})
|
|
40
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
41
|
+
.map(([name, value]) =>
|
|
42
|
+
value === "" ? name : `${name}="${escapeAttribute(value)}"`,
|
|
43
|
+
)
|
|
44
|
+
.join(" ");
|
|
45
|
+
const openTag = attributes
|
|
46
|
+
? `<${node.tagName} ${attributes}>`
|
|
47
|
+
: `<${node.tagName}>`;
|
|
48
|
+
const children = node.children ?? [];
|
|
49
|
+
|
|
50
|
+
if (children.length === 0) {
|
|
51
|
+
return `${indent}${openTag}</${node.tagName}>\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `${indent}${openTag}\n${children
|
|
55
|
+
.map((child) => emitNode(child, depth + 1))
|
|
56
|
+
.join("")}${indent}</${node.tagName}>\n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function escapeHtml(value: string): string {
|
|
60
|
+
return value
|
|
61
|
+
.replace(/&/g, "&")
|
|
62
|
+
.replace(/</g, "<")
|
|
63
|
+
.replace(/>/g, ">");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function escapeAttribute(value: string): string {
|
|
67
|
+
return escapeHtml(value).replace(/"/g, """);
|
|
68
|
+
}
|
package/dist/args.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export function parseArgs(args) {
|
|
2
|
-
const positionals = [];
|
|
3
|
-
const flags = {};
|
|
4
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
5
|
-
const value = args[index];
|
|
6
|
-
if (!value?.startsWith("--")) {
|
|
7
|
-
if (value) {
|
|
8
|
-
positionals.push(value);
|
|
9
|
-
}
|
|
10
|
-
continue;
|
|
11
|
-
}
|
|
12
|
-
const next = args[index + 1];
|
|
13
|
-
if (!next || next.startsWith("--")) {
|
|
14
|
-
flags[value] = true;
|
|
15
|
-
continue;
|
|
16
|
-
}
|
|
17
|
-
flags[value] = next;
|
|
18
|
-
index += 1;
|
|
19
|
-
}
|
|
20
|
-
const [command = "compile", ...rest] = positionals;
|
|
21
|
-
return {
|
|
22
|
-
command,
|
|
23
|
-
positionals: rest,
|
|
24
|
-
flags,
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export function getStringFlag(flags, name) {
|
|
28
|
-
const value = flags[name];
|
|
29
|
-
return typeof value === "string" ? value : undefined;
|
|
30
|
-
}
|
|
31
|
-
export function getBooleanFlag(flags, name) {
|
|
32
|
-
return flags[name] === true;
|
|
33
|
-
}
|
|
34
|
-
export function getFormat(flags) {
|
|
35
|
-
return flags["--format"] === "json" ? "json" : "text";
|
|
36
|
-
}
|
package/dist/cli.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs } from "./args.js";
|
|
3
|
-
import { runCheckCommand } from "./commands/check.js";
|
|
4
|
-
import { runCompileCommand } from "./commands/compile.js";
|
|
5
|
-
import { runGenerateTestsCommand } from "./commands/generateTests.js";
|
|
6
|
-
import { runInitCommand } from "./commands/init.js";
|
|
7
|
-
import { runPluginCommand } from "./commands/plugin.js";
|
|
8
|
-
async function main() {
|
|
9
|
-
const args = process.argv.slice(2);
|
|
10
|
-
const parsed = parseArgs(args);
|
|
11
|
-
if (args[0] === "check") {
|
|
12
|
-
return runCheckCommand(parsed.flags);
|
|
13
|
-
}
|
|
14
|
-
if (args[0] === "plugin") {
|
|
15
|
-
return runPluginCommand(parsed.positionals[0], parsed.flags);
|
|
16
|
-
}
|
|
17
|
-
if (args[0] === "generate-tests") {
|
|
18
|
-
return runGenerateTestsCommand(parsed.flags);
|
|
19
|
-
}
|
|
20
|
-
if (args[0] === "init") {
|
|
21
|
-
return runInitCommand(parsed.flags);
|
|
22
|
-
}
|
|
23
|
-
return runCompileCommand(parsed.flags);
|
|
24
|
-
}
|
|
25
|
-
main()
|
|
26
|
-
.then((code) => {
|
|
27
|
-
if (code !== 0) {
|
|
28
|
-
process.exit(code);
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
.catch((error) => {
|
|
32
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
-
console.error(`Pipeline failed: ${message}`);
|
|
34
|
-
process.exit(1);
|
|
35
|
-
});
|
package/dist/commands/check.js
DELETED
package/dist/commands/compile.js
DELETED
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { dirname, isAbsolute, resolve } from "node:path";
|
|
3
|
-
import { loadConfig } from "@design-embed/config";
|
|
4
|
-
import { checkGeneratedFiles, embed, formatDiagnosticText, toJsonDiagnostics, } from "@design-embed/core";
|
|
5
|
-
import { htmlEmitter } from "@design-embed/target-html";
|
|
6
|
-
import { reactEmitter } from "@design-embed/target-react";
|
|
7
|
-
import { getBooleanFlag, getFormat, getStringFlag } from "../args.js";
|
|
8
|
-
export async function runCompileCommand(flags, options = {}) {
|
|
9
|
-
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
10
|
-
const inputPath = getStringFlag(flags, "--input");
|
|
11
|
-
const configPath = getStringFlag(flags, "--config");
|
|
12
|
-
const quiet = getBooleanFlag(flags, "--quiet");
|
|
13
|
-
const format = getFormat(flags);
|
|
14
|
-
const diagnostics = [];
|
|
15
|
-
if (!inputPath) {
|
|
16
|
-
diagnostics.push({
|
|
17
|
-
code: "INPUT_REQUIRED",
|
|
18
|
-
message: "--input is required.",
|
|
19
|
-
severity: "error",
|
|
20
|
-
});
|
|
21
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
22
|
-
return 2;
|
|
23
|
-
}
|
|
24
|
-
const resolvedInputPath = resolve(cwd, inputPath);
|
|
25
|
-
if (!existsSync(resolvedInputPath)) {
|
|
26
|
-
diagnostics.push({
|
|
27
|
-
code: "INPUT_NOT_FOUND",
|
|
28
|
-
message: `Input file not found: ${resolvedInputPath}`,
|
|
29
|
-
severity: "error",
|
|
30
|
-
file: inputPath,
|
|
31
|
-
});
|
|
32
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
33
|
-
return 2;
|
|
34
|
-
}
|
|
35
|
-
let config;
|
|
36
|
-
let transformers = [];
|
|
37
|
-
if (configPath) {
|
|
38
|
-
const configResult = await loadConfig(configPath, cwd);
|
|
39
|
-
diagnostics.push(...configResult.diagnostics);
|
|
40
|
-
config = configResult.config;
|
|
41
|
-
if (hasErrors(diagnostics)) {
|
|
42
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
43
|
-
return 2;
|
|
44
|
-
}
|
|
45
|
-
transformers = await loadTransformers(config, configPath, cwd, diagnostics);
|
|
46
|
-
if (hasErrors(diagnostics)) {
|
|
47
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
48
|
-
return 2;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
const target = config?.output?.target ?? "html";
|
|
52
|
-
const targetEmitter = target === "react" ? reactEmitter : htmlEmitter;
|
|
53
|
-
const cssPath = getStringFlag(flags, "--css");
|
|
54
|
-
const result = await embed({
|
|
55
|
-
html: readFileSync(resolvedInputPath, "utf-8"),
|
|
56
|
-
css: cssPath ? readFileSync(resolve(cwd, cssPath), "utf-8") : undefined,
|
|
57
|
-
configPath,
|
|
58
|
-
config,
|
|
59
|
-
cwd,
|
|
60
|
-
transformers,
|
|
61
|
-
targetEmitter,
|
|
62
|
-
});
|
|
63
|
-
diagnostics.push(...result.diagnostics);
|
|
64
|
-
if (hasErrors(diagnostics)) {
|
|
65
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
66
|
-
return 2;
|
|
67
|
-
}
|
|
68
|
-
if (options.check && !getBooleanFlag(flags, "--write")) {
|
|
69
|
-
const checkResult = checkGeneratedFiles({
|
|
70
|
-
cwd,
|
|
71
|
-
files: result.files,
|
|
72
|
-
readFile(path) {
|
|
73
|
-
return existsSync(path) ? readFileSync(path, "utf-8") : undefined;
|
|
74
|
-
},
|
|
75
|
-
});
|
|
76
|
-
const checkDiagnostics = checkResult.diagnostics;
|
|
77
|
-
diagnostics.push(...checkDiagnostics);
|
|
78
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
79
|
-
return checkResult.ok ? 0 : 3;
|
|
80
|
-
}
|
|
81
|
-
for (const file of result.files) {
|
|
82
|
-
const outPath = resolve(cwd, file.path);
|
|
83
|
-
mkdirSync(dirname(outPath), { recursive: true });
|
|
84
|
-
writeFileSync(outPath, file.contents, "utf-8");
|
|
85
|
-
if (!quiet && format === "text") {
|
|
86
|
-
console.log(`Wrote ${file.path}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
printDiagnostics(diagnostics, format, quiet);
|
|
90
|
-
if (!quiet && format === "text") {
|
|
91
|
-
console.log(`Success. Generated ${result.files.length} file(s).`);
|
|
92
|
-
}
|
|
93
|
-
return 0;
|
|
94
|
-
}
|
|
95
|
-
export function printDiagnostics(diagnostics, format, quiet) {
|
|
96
|
-
if (format === "json") {
|
|
97
|
-
console.log(JSON.stringify({ diagnostics: toJsonDiagnostics(diagnostics) }, null, 2));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (quiet) {
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
for (const diagnostic of diagnostics) {
|
|
104
|
-
const output = formatDiagnosticText(diagnostic);
|
|
105
|
-
if (diagnostic.severity === "error") {
|
|
106
|
-
console.error(output);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
console.warn(output);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
function isPackageName(path) {
|
|
114
|
-
return !path.startsWith(".") && !isAbsolute(path);
|
|
115
|
-
}
|
|
116
|
-
async function loadTransformers(config, configPath, cwd, diagnostics) {
|
|
117
|
-
const configDir = dirname(resolve(cwd, configPath));
|
|
118
|
-
const loaded = [];
|
|
119
|
-
for (const transformer of config?.transformers ?? []) {
|
|
120
|
-
const specifier = isPackageName(transformer.path)
|
|
121
|
-
? transformer.path
|
|
122
|
-
: isAbsolute(transformer.path)
|
|
123
|
-
? transformer.path
|
|
124
|
-
: resolve(configDir, transformer.path);
|
|
125
|
-
try {
|
|
126
|
-
const module = await import(specifier);
|
|
127
|
-
const plugin = module.default ?? module.transformer;
|
|
128
|
-
if (!plugin?.transform) {
|
|
129
|
-
diagnostics.push({
|
|
130
|
-
code: "TRANSFORMER_INVALID",
|
|
131
|
-
message: `Transformer ${transformer.path} must export a plugin object with transform().`,
|
|
132
|
-
severity: "error",
|
|
133
|
-
file: transformer.path,
|
|
134
|
-
});
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
loaded.push({
|
|
138
|
-
name: plugin.name ?? transformer.path,
|
|
139
|
-
order: transformer.order ?? plugin.order,
|
|
140
|
-
transform: plugin.transform,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
145
|
-
diagnostics.push({
|
|
146
|
-
code: "TRANSFORMER_LOAD_FAILED",
|
|
147
|
-
message: `Failed to load transformer ${transformer.path}: ${message}`,
|
|
148
|
-
severity: "error",
|
|
149
|
-
file: transformer.path,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return loaded;
|
|
154
|
-
}
|
|
155
|
-
function hasErrors(diagnostics) {
|
|
156
|
-
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
157
|
-
}
|