design-embed 0.1.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 +9 -0
- package/README.md +5 -0
- package/dist/args.js +36 -0
- package/dist/cli.js +35 -0
- package/dist/commands/check.js +4 -0
- package/dist/commands/compile.js +157 -0
- package/dist/commands/generateTests.js +113 -0
- package/dist/commands/init.js +102 -0
- package/dist/commands/plugin.js +68 -0
- package/dist/index.js +2 -0
- package/node_modules/@design-embed/config/README.md +5 -0
- package/node_modules/@design-embed/config/dist/index.js +283 -0
- package/node_modules/@design-embed/config/package.json +19 -0
- package/node_modules/@design-embed/config/src/index.ts +518 -0
- package/node_modules/@design-embed/core/README.md +5 -0
- package/node_modules/@design-embed/core/dist/diagnostics/diagnostic.js +3 -0
- package/node_modules/@design-embed/core/dist/diagnostics/jsonDiagnostic.js +35 -0
- package/node_modules/@design-embed/core/dist/index.js +351 -0
- package/node_modules/@design-embed/core/dist/pipeline/checkMode.js +29 -0
- package/node_modules/@design-embed/core/dist/plugins/pluginApi.js +1 -0
- package/node_modules/@design-embed/core/dist/plugins/pluginRegistry.js +25 -0
- package/node_modules/@design-embed/core/package.json +19 -0
- package/node_modules/@design-embed/core/src/diagnostics/diagnostic.ts +18 -0
- package/node_modules/@design-embed/core/src/diagnostics/jsonDiagnostic.ts +51 -0
- package/node_modules/@design-embed/core/src/index.ts +591 -0
- package/node_modules/@design-embed/core/src/pipeline/checkMode.ts +46 -0
- package/node_modules/@design-embed/core/src/plugins/pluginApi.ts +78 -0
- package/node_modules/@design-embed/core/src/plugins/pluginRegistry.ts +37 -0
- package/package.json +42 -0
- package/src/args.ts +57 -0
- package/src/cli.ts +42 -0
- package/src/commands/check.ts +7 -0
- package/src/commands/compile.ts +198 -0
- package/src/commands/generateTests.ts +140 -0
- package/src/commands/init.ts +114 -0
- package/src/commands/plugin.ts +89 -0
- package/src/index.ts +2 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jin-Woo Lee
|
|
4
|
+
|
|
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
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# design-embed
|
|
2
|
+
|
|
3
|
+
The command-line interface for design-embed.
|
|
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.
|
package/dist/args.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
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 { reactTestGenerator } from "@design-embed/target-react";
|
|
5
|
+
import { getBooleanFlag, getFormat, getStringFlag } from "../args.js";
|
|
6
|
+
import { printDiagnostics } from "./compile.js";
|
|
7
|
+
export async function runGenerateTestsCommand(flags) {
|
|
8
|
+
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
9
|
+
const configPath = getStringFlag(flags, "--config");
|
|
10
|
+
const quiet = getBooleanFlag(flags, "--quiet");
|
|
11
|
+
const format = getFormat(flags);
|
|
12
|
+
const diagnostics = [];
|
|
13
|
+
if (!configPath) {
|
|
14
|
+
diagnostics.push({
|
|
15
|
+
code: "CONFIG_REQUIRED",
|
|
16
|
+
message: "--config is required for generate-tests.",
|
|
17
|
+
severity: "error",
|
|
18
|
+
});
|
|
19
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
20
|
+
return 2;
|
|
21
|
+
}
|
|
22
|
+
const configResult = await loadConfig(configPath, cwd);
|
|
23
|
+
diagnostics.push(...configResult.diagnostics);
|
|
24
|
+
const config = configResult.config;
|
|
25
|
+
if (!config || hasErrors(diagnostics)) {
|
|
26
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
const source = readConfiguredSource(config, configPath, cwd, diagnostics);
|
|
30
|
+
if (!source || hasErrors(diagnostics)) {
|
|
31
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
32
|
+
return 2;
|
|
33
|
+
}
|
|
34
|
+
const target = config.output?.target ?? "html";
|
|
35
|
+
if (target !== "react") {
|
|
36
|
+
diagnostics.push({
|
|
37
|
+
code: "TEST_TARGET_UNSUPPORTED",
|
|
38
|
+
message: `generate-tests currently supports target "react"; received "${target}".`,
|
|
39
|
+
severity: "error",
|
|
40
|
+
});
|
|
41
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
const result = reactTestGenerator.generateTests({
|
|
45
|
+
html: source.html,
|
|
46
|
+
css: source.css,
|
|
47
|
+
config,
|
|
48
|
+
diagnostics,
|
|
49
|
+
});
|
|
50
|
+
if (hasErrors(diagnostics)) {
|
|
51
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
52
|
+
return 2;
|
|
53
|
+
}
|
|
54
|
+
for (const file of result.files) {
|
|
55
|
+
const outPath = resolve(cwd, file.path);
|
|
56
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
57
|
+
writeFileSync(outPath, file.contents, "utf-8");
|
|
58
|
+
if (!quiet && format === "text") {
|
|
59
|
+
console.log(`Wrote ${file.path}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
63
|
+
if (!quiet && format === "text") {
|
|
64
|
+
console.log(`Success. Generated ${result.files.length} test file(s).`);
|
|
65
|
+
}
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
function readConfiguredSource(config, configPath, cwd, diagnostics) {
|
|
69
|
+
const source = config.tests?.source;
|
|
70
|
+
if (!source?.html) {
|
|
71
|
+
diagnostics.push({
|
|
72
|
+
code: "TEST_SOURCE_HTML_REQUIRED",
|
|
73
|
+
message: "tests.source.html is required for generate-tests.",
|
|
74
|
+
severity: "error",
|
|
75
|
+
});
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const configDir = dirname(resolve(cwd, configPath));
|
|
79
|
+
const htmlPath = resolveConfigPath(source.html, configDir);
|
|
80
|
+
if (!existsSync(htmlPath)) {
|
|
81
|
+
diagnostics.push({
|
|
82
|
+
code: "TEST_SOURCE_HTML_NOT_FOUND",
|
|
83
|
+
message: `Test source HTML not found: ${htmlPath}`,
|
|
84
|
+
severity: "error",
|
|
85
|
+
file: source.html,
|
|
86
|
+
});
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
let css;
|
|
90
|
+
if (source.css) {
|
|
91
|
+
const cssPath = resolveConfigPath(source.css, configDir);
|
|
92
|
+
if (!existsSync(cssPath)) {
|
|
93
|
+
diagnostics.push({
|
|
94
|
+
code: "TEST_SOURCE_CSS_NOT_FOUND",
|
|
95
|
+
message: `Test source CSS not found: ${cssPath}`,
|
|
96
|
+
severity: "error",
|
|
97
|
+
file: source.css,
|
|
98
|
+
});
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
css = readFileSync(cssPath, "utf-8");
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
html: readFileSync(htmlPath, "utf-8"),
|
|
105
|
+
css,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function resolveConfigPath(path, configDir) {
|
|
109
|
+
return isAbsolute(path) ? path : resolve(configDir, path);
|
|
110
|
+
}
|
|
111
|
+
function hasErrors(diagnostics) {
|
|
112
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
113
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { getBooleanFlag, getFormat, getStringFlag } from "../args.js";
|
|
4
|
+
import { printDiagnostics } from "./compile.js";
|
|
5
|
+
export async function runInitCommand(flags) {
|
|
6
|
+
const cwd = resolve(process.cwd(), getStringFlag(flags, "--cwd") ?? ".");
|
|
7
|
+
const quiet = getBooleanFlag(flags, "--quiet");
|
|
8
|
+
const force = getBooleanFlag(flags, "--force");
|
|
9
|
+
const format = getFormat(flags);
|
|
10
|
+
const viewName = getStringFlag(flags, "--view-name") ?? "WelcomeHero";
|
|
11
|
+
const diagnostics = [];
|
|
12
|
+
const files = [
|
|
13
|
+
{
|
|
14
|
+
path: "design-embed.config.ts",
|
|
15
|
+
contents: configTemplate(viewName),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
path: "design.html",
|
|
19
|
+
contents: designHtmlTemplate(),
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: "playwright-ct.config.ts",
|
|
23
|
+
contents: playwrightConfigTemplate(),
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
let written = 0;
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
const outPath = resolve(cwd, file.path);
|
|
29
|
+
if (existsSync(outPath) && !force) {
|
|
30
|
+
diagnostics.push({
|
|
31
|
+
code: "INIT_FILE_EXISTS",
|
|
32
|
+
message: `Skipped existing file: ${file.path}. Pass --force to overwrite it.`,
|
|
33
|
+
severity: "warning",
|
|
34
|
+
file: file.path,
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
39
|
+
writeFileSync(outPath, file.contents, "utf-8");
|
|
40
|
+
written += 1;
|
|
41
|
+
if (!quiet && format === "text") {
|
|
42
|
+
console.log(`Wrote ${file.path}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
printDiagnostics(diagnostics, format, quiet);
|
|
46
|
+
if (!quiet && format === "text") {
|
|
47
|
+
console.log(`Success. Initialized design-embed with ${written} file(s).`);
|
|
48
|
+
console.log("Next: pnpm exec design-embed --input ./design.html --config ./design-embed.config.ts");
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
function configTemplate(viewName) {
|
|
53
|
+
return `import { defineConfig } from "design-embed";
|
|
54
|
+
|
|
55
|
+
export default defineConfig({
|
|
56
|
+
\toutput: {
|
|
57
|
+
\t\ttarget: "react",
|
|
58
|
+
\t\tviewName: "${viewName}",
|
|
59
|
+
\t\tviewsDir: "src/generated/views",
|
|
60
|
+
\t\tstyleMode: "inline",
|
|
61
|
+
\t},
|
|
62
|
+
\ttests: {
|
|
63
|
+
\t\toutputDir: "tests/generated/design-embed",
|
|
64
|
+
\t\trunner: "playwright",
|
|
65
|
+
\t\tsource: {
|
|
66
|
+
\t\t\thtml: "./design.html",
|
|
67
|
+
\t\t},
|
|
68
|
+
\t\tviewports: [
|
|
69
|
+
\t\t\t{ name: "mobile", width: 390, height: 844 },
|
|
70
|
+
\t\t\t{ name: "desktop", width: 1440, height: 900 },
|
|
71
|
+
\t\t],
|
|
72
|
+
\t\tstates: [{ name: "default" }],
|
|
73
|
+
\t\tassertions: {
|
|
74
|
+
\t\t\tscreenshot: true,
|
|
75
|
+
\t\t\tlayout: true,
|
|
76
|
+
\t\t\tlayoutTolerance: 1,
|
|
77
|
+
\t\t\tselectors: [":scope", ":scope *"],
|
|
78
|
+
\t\t},
|
|
79
|
+
\t},
|
|
80
|
+
});
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
function designHtmlTemplate() {
|
|
84
|
+
return `<section style="box-sizing: border-box; width: 320px; padding: 24px; border-radius: 16px; background: #f8fafc; color: #0f172a; font-family: Arial, sans-serif;">
|
|
85
|
+
\t<p style="margin: 0 0 8px; color: #2563eb; font-size: 14px; font-weight: 700;">Design Embed</p>
|
|
86
|
+
\t<h1 style="margin: 0 0 12px; font-size: 32px; line-height: 1.1;">Welcome hero</h1>
|
|
87
|
+
\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>
|
|
88
|
+
\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>
|
|
89
|
+
</section>
|
|
90
|
+
`;
|
|
91
|
+
}
|
|
92
|
+
function playwrightConfigTemplate() {
|
|
93
|
+
return `import { defineConfig } from "@playwright/experimental-ct-react";
|
|
94
|
+
|
|
95
|
+
export default defineConfig({
|
|
96
|
+
\ttestDir: ".",
|
|
97
|
+
\tuse: {
|
|
98
|
+
\t\tctPort: 3100,
|
|
99
|
+
\t},
|
|
100
|
+
});
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { loadConfig } from "@design-embed/config";
|
|
4
|
+
import { getStringFlag } from "../args.js";
|
|
5
|
+
export async function runPluginCommand(_name, flags) {
|
|
6
|
+
const configPath = getStringFlag(flags, "--config");
|
|
7
|
+
if (!configPath) {
|
|
8
|
+
console.error("Error: --config is required.");
|
|
9
|
+
return 2;
|
|
10
|
+
}
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const configResult = await loadConfig(configPath, cwd);
|
|
13
|
+
for (const diagnostic of configResult.diagnostics) {
|
|
14
|
+
if (diagnostic.severity === "error") {
|
|
15
|
+
console.error(`error: ${diagnostic.code}: ${diagnostic.message}`);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
console.warn(`${diagnostic.severity}: ${diagnostic.code}: ${diagnostic.message}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (configResult.diagnostics.some((d) => d.severity === "error")) {
|
|
22
|
+
return 2;
|
|
23
|
+
}
|
|
24
|
+
const outPath = getStringFlag(flags, "--out");
|
|
25
|
+
if (!outPath) {
|
|
26
|
+
console.error("Error: --out is required.");
|
|
27
|
+
return 2;
|
|
28
|
+
}
|
|
29
|
+
const plugin = findSourcePlugin(configResult.config?.plugins);
|
|
30
|
+
if (!plugin) {
|
|
31
|
+
console.error("Error: config must include a source plugin instance in the plugins array (e.g. new FigmaHtmlPlugin({ ... })).");
|
|
32
|
+
return 2;
|
|
33
|
+
}
|
|
34
|
+
const result = await plugin.run({ cwd, args: {} });
|
|
35
|
+
for (const diagnostic of result.diagnostics) {
|
|
36
|
+
const output = `${diagnostic.severity}: ${diagnostic.code}: ${diagnostic.message}`;
|
|
37
|
+
if (diagnostic.severity === "error") {
|
|
38
|
+
console.error(output);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.warn(output);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (result.diagnostics.some((d) => d.severity === "error")) {
|
|
45
|
+
return 2;
|
|
46
|
+
}
|
|
47
|
+
if (!result.html) {
|
|
48
|
+
console.error("Error: source plugin produced no HTML.");
|
|
49
|
+
return 2;
|
|
50
|
+
}
|
|
51
|
+
const resolvedOutPath = resolve(cwd, outPath);
|
|
52
|
+
mkdirSync(dirname(resolvedOutPath), { recursive: true });
|
|
53
|
+
writeFileSync(resolvedOutPath, result.html, "utf-8");
|
|
54
|
+
console.log(`Wrote ${outPath}`);
|
|
55
|
+
for (const file of result.files ?? []) {
|
|
56
|
+
const resolvedPath = resolve(cwd, file.path);
|
|
57
|
+
mkdirSync(dirname(resolvedPath), { recursive: true });
|
|
58
|
+
writeFileSync(resolvedPath, file.contents, "utf-8");
|
|
59
|
+
console.log(`Wrote ${file.path}`);
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
62
|
+
}
|
|
63
|
+
function isSourcePlugin(plugin) {
|
|
64
|
+
return typeof plugin.run === "function";
|
|
65
|
+
}
|
|
66
|
+
function findSourcePlugin(plugins) {
|
|
67
|
+
return plugins?.find(isSourcePlugin);
|
|
68
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# @design-embed/config
|
|
2
|
+
|
|
3
|
+
Internal configuration loading and validation for design-embed.
|
|
4
|
+
|
|
5
|
+
It parses the project config format used by the compiler, validates supported output targets, style modes, component mappings, token settings, plugin settings, and local transformer declarations. It also reports config diagnostics in a stable shape so callers can fail early before compilation begins.
|