coderio 1.0.0 → 1.0.1-alpha.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/README.md +15 -24
- package/dist/cli.js +128 -114
- package/dist/cli.js.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,8 @@ https://github.com/user-attachments/assets/bd0c3f18-e98a-4050-bf22-46b198fadac2
|
|
|
54
54
|
|
|
55
55
|
CodeRio can be seamlessly integrated into Cursor as a Skill. Simply input a prompt like **"Create a React project and restore this design with high fidelity,"** along with your output directory, Figma URL([Design Link](https://www.figma.com/design/c0UBII8lURfxZIY8W6tSDR/Top-16-Websites-of-2024---Awwwards--Community-?node-id=30-8264&t=FB3Hohq2nsH7ZFts-4)), and Token. The Agent will guide you step-by-step through the page generation process. For Landing Pages, it achieves **high-fidelity restoration**, accurately reproducing images and styles. It also automatically encapsulates reusable components (such as cards) and strictly adheres to **frontend development best practices**.
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
|
|
58
|
+
https://github.com/user-attachments/assets/43817e97-ffd2-40e3-9d33-78ee55b2ec2d
|
|
58
59
|
|
|
59
60
|
## 🚀 Quick Start
|
|
60
61
|
|
|
@@ -65,6 +66,7 @@ Best for one-click generation.
|
|
|
65
66
|
|
|
66
67
|
- Node.js >= 18.0.0 (< 25.0.0)
|
|
67
68
|
- [Figma Personal Access Token](https://www.figma.com/developers/api#access-tokens)
|
|
69
|
+
- **Figma Link**: Select a Frame or Component in Figma, right-click, and choose **Copy link to selection** ([Reference](docs/figma-link.jpg)).
|
|
68
70
|
- LLM API Key ([Anthropic](https://console.anthropic.com/) | [OpenAI](https://platform.openai.com/) | [Google](https://aistudio.google.com/))
|
|
69
71
|
|
|
70
72
|
#### 2. Installation
|
|
@@ -85,15 +87,20 @@ pnpm add -g coderio
|
|
|
85
87
|
>
|
|
86
88
|
> This allows native dependencies (better-sqlite3) to compile properly.
|
|
87
89
|
>
|
|
88
|
-
> **Note**: `
|
|
90
|
+
> **Note**: Validation features (e.g., `d2c --mode full`) require optional dependencies `playwright` and `sharp`. They are not bundled with coderio by default to keep installation lightweight. Please install them globally beforehand for smoother execution:
|
|
91
|
+
>
|
|
92
|
+
> ```bash
|
|
93
|
+
> npm install -g playwright sharp
|
|
94
|
+
> npx playwright install chromium
|
|
95
|
+
> ```
|
|
89
96
|
|
|
90
97
|
#### 3. Configuration
|
|
91
98
|
|
|
92
|
-
|
|
99
|
+
> **Important**: Requires a **multimodal (vision)** model (Recommended: `gemini-3-pro-preview`).
|
|
93
100
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
Create config file at `~/.coderio/config.yaml` (Windows: `%USERPROFILE%\.coderio\config.yaml`):
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
97
104
|
model:
|
|
98
105
|
provider: openai # anthropic | openai | google
|
|
99
106
|
model: gemini-3-pro-preview
|
|
@@ -105,7 +112,6 @@ figma:
|
|
|
105
112
|
|
|
106
113
|
debug:
|
|
107
114
|
enabled: false
|
|
108
|
-
EOF
|
|
109
115
|
```
|
|
110
116
|
|
|
111
117
|
#### 4. Usage
|
|
@@ -235,24 +241,9 @@ Beyond visual fidelity, the generated code is built for long-term maintenance:
|
|
|
235
241
|
- [ ] Vue.js and Svelte support
|
|
236
242
|
- [ ] Custom design system integration
|
|
237
243
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
we welcome contributions!
|
|
241
|
-
|
|
242
|
-
```bash
|
|
243
|
-
git clone https://github.com/MigoXLab/coderio.git
|
|
244
|
-
cd coderio
|
|
245
|
-
pnpm install
|
|
246
|
-
pnpm build
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### Contributors
|
|
250
|
-
|
|
251
|
-
Thanks to all our contributors! 🎉
|
|
244
|
+
### 🤝 Contributors
|
|
252
245
|
|
|
253
|
-
|
|
254
|
-
<img src="https://contrib.rocks/image?repo=MigoXLab/coderio" />
|
|
255
|
-
</a>
|
|
246
|
+
Welcome to contribute. Thanks to all our contributors! 🎉
|
|
256
247
|
|
|
257
248
|
## 📄 License
|
|
258
249
|
|
package/dist/cli.js
CHANGED
|
@@ -475,7 +475,7 @@ var AGENT_CONTEXT_WINDOW_TOKENS = 128e3;
|
|
|
475
475
|
|
|
476
476
|
// src/cli/init.ts
|
|
477
477
|
function registerCommands(program) {
|
|
478
|
-
const version = false ? "0.0.1" : "1.0.
|
|
478
|
+
const version = false ? "0.0.1" : "1.0.1-alpha.1";
|
|
479
479
|
program.name(CLI_NAME).description(`${CLI_NAME} - Convert Figma designs to code`).version(version, "-v, -V, --version", "Output the version number").showHelpAfterError();
|
|
480
480
|
}
|
|
481
481
|
|
|
@@ -628,7 +628,8 @@ var Workspace = class {
|
|
|
628
628
|
*
|
|
629
629
|
*/
|
|
630
630
|
resolveComponentPath(aliasPath) {
|
|
631
|
-
|
|
631
|
+
const normalizedAlias = aliasPath.replace(/\\/g, "/");
|
|
632
|
+
let relativePath = normalizedAlias.startsWith("@/") ? normalizedAlias.substring(2) : normalizedAlias;
|
|
632
633
|
if (relativePath.startsWith("src/")) {
|
|
633
634
|
relativePath = relativePath.substring(4);
|
|
634
635
|
}
|
|
@@ -980,7 +981,13 @@ var createDownloadTask = async (image, imageDir) => {
|
|
|
980
981
|
const filename = `${sanitizedName}-${image.id.replace(/:/g, "-")}.${ext}`;
|
|
981
982
|
try {
|
|
982
983
|
const localPath = await downloadImage(image.url, filename, imageDir);
|
|
983
|
-
const
|
|
984
|
+
const normalizedImageDir = imageDir ? path3.normalize(imageDir) : "";
|
|
985
|
+
const dirParts = normalizedImageDir.split(path3.sep).filter(Boolean);
|
|
986
|
+
const srcIndex = dirParts.lastIndexOf("src");
|
|
987
|
+
const srcDir = srcIndex >= 0 ? (normalizedImageDir.startsWith(path3.sep) ? path3.sep : "") + path3.join(...dirParts.slice(0, srcIndex + 1)) : "";
|
|
988
|
+
const relativeFromSrc = srcDir ? path3.relative(srcDir, localPath) : "";
|
|
989
|
+
const normalizedRelative = relativeFromSrc.split(path3.sep).join("/");
|
|
990
|
+
const aliasPath = `@/${normalizedRelative}`;
|
|
984
991
|
return {
|
|
985
992
|
id: image.id,
|
|
986
993
|
name: image.name,
|
|
@@ -1564,6 +1571,9 @@ async function callModel(options) {
|
|
|
1564
1571
|
init_logger();
|
|
1565
1572
|
init_prompt();
|
|
1566
1573
|
|
|
1574
|
+
// src/nodes/process/structure/utils.ts
|
|
1575
|
+
import path4 from "path";
|
|
1576
|
+
|
|
1567
1577
|
// src/utils/naming.ts
|
|
1568
1578
|
function toKebabCase(str) {
|
|
1569
1579
|
return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").toLowerCase().replace(/^-+|-+$/g, "");
|
|
@@ -1742,7 +1752,7 @@ function postProcessStructure(structure, frames) {
|
|
|
1742
1752
|
if (!structure) {
|
|
1743
1753
|
return;
|
|
1744
1754
|
}
|
|
1745
|
-
const joinSegments = (...segments) => segments.filter((segment) => Boolean(segment && segment.length))
|
|
1755
|
+
const joinSegments = (...segments) => path4.posix.join(...segments.filter((segment) => Boolean(segment && segment.length)));
|
|
1746
1756
|
const nodes = Array.isArray(structure) ? structure : [structure];
|
|
1747
1757
|
let rootPath = "@/components";
|
|
1748
1758
|
const toKebabName = (node) => {
|
|
@@ -2002,7 +2012,7 @@ var parseFigmaUrl = (url) => {
|
|
|
2002
2012
|
if (!fileId || !nodeId) {
|
|
2003
2013
|
throw new Error("Invalid Figma URL");
|
|
2004
2014
|
}
|
|
2005
|
-
return { fileId, name, nodeId, projectName: `${name}_${nodeId}` };
|
|
2015
|
+
return { fileId, name, nodeId, projectName: `${name}_${nodeId.replace(/:/g, "_")}` };
|
|
2006
2016
|
};
|
|
2007
2017
|
|
|
2008
2018
|
// src/cli/d2p.ts
|
|
@@ -2123,7 +2133,7 @@ function createInitialAgent(modelConfig) {
|
|
|
2123
2133
|
// src/nodes/initial/index.ts
|
|
2124
2134
|
init_logger();
|
|
2125
2135
|
import fs4 from "fs";
|
|
2126
|
-
import
|
|
2136
|
+
import path5 from "path";
|
|
2127
2137
|
|
|
2128
2138
|
// src/agents/initial-agent/instruction.ts
|
|
2129
2139
|
function initialAgentInstruction(params) {
|
|
@@ -2154,7 +2164,7 @@ var initialProject = async (state) => {
|
|
|
2154
2164
|
const result = await initialAgent.run(initialAgentInstruction({ appPath, appName }));
|
|
2155
2165
|
const essentialFiles = ["package.json", "src", "vite.config.ts", "tsconfig.json", "index.html", "src/main.tsx", "src/App.tsx"];
|
|
2156
2166
|
for (const file of essentialFiles) {
|
|
2157
|
-
const fullPath =
|
|
2167
|
+
const fullPath = path5.join(appPath, file);
|
|
2158
2168
|
if (!fs4.existsSync(fullPath)) {
|
|
2159
2169
|
throw new Error(`Critical file/directory missing: "${file}". The project scaffolding may have failed.`);
|
|
2160
2170
|
}
|
|
@@ -2164,7 +2174,7 @@ var initialProject = async (state) => {
|
|
|
2164
2174
|
};
|
|
2165
2175
|
|
|
2166
2176
|
// src/nodes/validation/index.ts
|
|
2167
|
-
import
|
|
2177
|
+
import fs8 from "fs";
|
|
2168
2178
|
import path12 from "path";
|
|
2169
2179
|
|
|
2170
2180
|
// src/nodes/validation/constants.ts
|
|
@@ -2194,7 +2204,7 @@ function buildDevServerUrl(port) {
|
|
|
2194
2204
|
init_logger();
|
|
2195
2205
|
|
|
2196
2206
|
// src/nodes/validation/core/validation-loop.ts
|
|
2197
|
-
import * as
|
|
2207
|
+
import * as fs7 from "fs";
|
|
2198
2208
|
import * as path11 from "path";
|
|
2199
2209
|
init_logger();
|
|
2200
2210
|
|
|
@@ -2306,13 +2316,13 @@ Check for changes and commit if there are any modifications in the app directory
|
|
|
2306
2316
|
|
|
2307
2317
|
// src/nodes/validation/commit/index.ts
|
|
2308
2318
|
init_logger();
|
|
2309
|
-
import
|
|
2319
|
+
import path6 from "path";
|
|
2310
2320
|
async function commit(options) {
|
|
2311
2321
|
try {
|
|
2312
2322
|
if (!options?.appPath) {
|
|
2313
2323
|
throw new Error("commit() requires options.appPath");
|
|
2314
2324
|
}
|
|
2315
|
-
const appPath =
|
|
2325
|
+
const appPath = path6.resolve(options.appPath);
|
|
2316
2326
|
const agent = createCommitAgent();
|
|
2317
2327
|
const instruction = formatGitCommitInstruction({
|
|
2318
2328
|
appPath,
|
|
@@ -3249,13 +3259,13 @@ init_logger();
|
|
|
3249
3259
|
init_logger();
|
|
3250
3260
|
import * as fs6 from "fs";
|
|
3251
3261
|
import * as fsPromises from "fs/promises";
|
|
3252
|
-
import * as
|
|
3262
|
+
import * as path9 from "path";
|
|
3253
3263
|
import { fileURLToPath } from "url";
|
|
3254
3264
|
import { tools as tools6 } from "evoltagent";
|
|
3255
3265
|
|
|
3256
3266
|
// src/tools/visualization-tool/index.ts
|
|
3257
3267
|
import { tools as tools5 } from "evoltagent";
|
|
3258
|
-
import * as
|
|
3268
|
+
import * as path8 from "path";
|
|
3259
3269
|
|
|
3260
3270
|
// src/tools/visualization-tool/utils/annotation-styles.ts
|
|
3261
3271
|
var ANNOTATION_COLORS = {
|
|
@@ -3577,7 +3587,7 @@ async function browserManagement(viewport, operation) {
|
|
|
3577
3587
|
|
|
3578
3588
|
// src/tools/visualization-tool/utils/combine.ts
|
|
3579
3589
|
import fs5 from "fs";
|
|
3580
|
-
import
|
|
3590
|
+
import path7 from "path";
|
|
3581
3591
|
var DEFAULT_OPTIONS = {
|
|
3582
3592
|
leftHeader: "RENDER (Current)",
|
|
3583
3593
|
rightHeader: "TARGET (Expected)",
|
|
@@ -3624,7 +3634,7 @@ async function combineSideBySide(renderBase64, targetBase64, outputPath, options
|
|
|
3624
3634
|
</svg>
|
|
3625
3635
|
`;
|
|
3626
3636
|
const headerBuffer = Buffer.from(headerSvg);
|
|
3627
|
-
const outputDir =
|
|
3637
|
+
const outputDir = path7.dirname(outputPath);
|
|
3628
3638
|
if (!fs5.existsSync(outputDir)) {
|
|
3629
3639
|
fs5.mkdirSync(outputDir, { recursive: true });
|
|
3630
3640
|
}
|
|
@@ -3906,7 +3916,7 @@ var VisualizationTool = class {
|
|
|
3906
3916
|
const render = await this.annotateRender(serverUrl, misalignedData, viewport);
|
|
3907
3917
|
const targetMarked = cachedFigmaThumbnailBase64 ? await this.annotateTargetFromBase64(cachedFigmaThumbnailBase64, misalignedData, viewport, designOffset) : await this.annotateTarget(figmaThumbnailUrl, misalignedData, viewport, designOffset);
|
|
3908
3918
|
await this.combine(render.renderMarked, targetMarked, outputPath);
|
|
3909
|
-
logger.printInfoLog(`Saved comparison screenshot: ${
|
|
3919
|
+
logger.printInfoLog(`Saved comparison screenshot: ${path8.basename(outputPath)}`);
|
|
3910
3920
|
return {
|
|
3911
3921
|
renderMarked: render.renderMarked,
|
|
3912
3922
|
targetMarked,
|
|
@@ -3995,7 +4005,7 @@ var ReportTool = class {
|
|
|
3995
4005
|
logger.printInfoLog("\nGenerating validation report...");
|
|
3996
4006
|
const visualizationTool = new VisualizationTool();
|
|
3997
4007
|
const { validationResult } = request;
|
|
3998
|
-
const comparisonDir =
|
|
4008
|
+
const comparisonDir = path9.join(request.outputDir, "comparison_screenshots");
|
|
3999
4009
|
if (!fs6.existsSync(comparisonDir)) {
|
|
4000
4010
|
fs6.mkdirSync(comparisonDir, { recursive: true });
|
|
4001
4011
|
}
|
|
@@ -4010,21 +4020,21 @@ var ReportTool = class {
|
|
|
4010
4020
|
renderMarked,
|
|
4011
4021
|
targetMarked
|
|
4012
4022
|
};
|
|
4013
|
-
const finalScreenshotPath =
|
|
4023
|
+
const finalScreenshotPath = path9.join(comparisonDir, "final.webp");
|
|
4014
4024
|
await visualizationTool.combine(screenshots.renderMarked, screenshots.targetMarked, finalScreenshotPath);
|
|
4015
|
-
logger.printSuccessLog(`Saved final comparison screenshot: ${
|
|
4025
|
+
logger.printSuccessLog(`Saved final comparison screenshot: ${path9.basename(finalScreenshotPath)}`);
|
|
4016
4026
|
let heatmap = "";
|
|
4017
4027
|
try {
|
|
4018
4028
|
heatmap = await visualizationTool.diffHeatmap(screenshots.renderSnap, request.figmaThumbnailUrl);
|
|
4019
4029
|
if (heatmap) {
|
|
4020
|
-
const heatmapPath =
|
|
4030
|
+
const heatmapPath = path9.join(comparisonDir, "heatmap.webp");
|
|
4021
4031
|
const base64Data = heatmap.split(",")[1];
|
|
4022
4032
|
if (!base64Data) {
|
|
4023
4033
|
throw new Error("Invalid heatmap data URI format");
|
|
4024
4034
|
}
|
|
4025
4035
|
const buffer = Buffer.from(base64Data, "base64");
|
|
4026
4036
|
await fs6.promises.writeFile(heatmapPath, buffer);
|
|
4027
|
-
logger.printSuccessLog(`Saved pixel difference heatmap: ${
|
|
4037
|
+
logger.printSuccessLog(`Saved pixel difference heatmap: ${path9.basename(heatmapPath)}`);
|
|
4028
4038
|
}
|
|
4029
4039
|
} catch (heatmapError) {
|
|
4030
4040
|
const errorMsg = heatmapError instanceof Error ? heatmapError.message : "Unknown error";
|
|
@@ -4079,7 +4089,7 @@ var ReportTool = class {
|
|
|
4079
4089
|
*/
|
|
4080
4090
|
async generateHtml(userReport, outputDir) {
|
|
4081
4091
|
const reportDistDir = this.getReportDistDir();
|
|
4082
|
-
const reportIndexHtml =
|
|
4092
|
+
const reportIndexHtml = path9.join(reportDistDir, "index.html");
|
|
4083
4093
|
logger.printInfoLog(`[ReportTool] Using template: ${reportIndexHtml}`);
|
|
4084
4094
|
logger.printInfoLog(`[ReportTool] Output directory: ${outputDir}`);
|
|
4085
4095
|
try {
|
|
@@ -4106,7 +4116,7 @@ var ReportTool = class {
|
|
|
4106
4116
|
for (const match of jsMatches) {
|
|
4107
4117
|
const fileName = match[1];
|
|
4108
4118
|
if (fileName) {
|
|
4109
|
-
const jsFilePath =
|
|
4119
|
+
const jsFilePath = path9.join(reportDistDir, "assets", fileName);
|
|
4110
4120
|
try {
|
|
4111
4121
|
let jsContent = await fsPromises.readFile(jsFilePath, "utf-8");
|
|
4112
4122
|
jsContent = jsContent.replace(/<\/script>/g, "\\u003c/script>");
|
|
@@ -4130,7 +4140,7 @@ var ReportTool = class {
|
|
|
4130
4140
|
if (!cssFileName) {
|
|
4131
4141
|
logger.printWarnLog("[ReportTool] CSS filename not found in match");
|
|
4132
4142
|
} else {
|
|
4133
|
-
const cssFilePath =
|
|
4143
|
+
const cssFilePath = path9.join(reportDistDir, "assets", cssFileName);
|
|
4134
4144
|
try {
|
|
4135
4145
|
const cssContent = await fsPromises.readFile(cssFilePath, "utf-8");
|
|
4136
4146
|
htmlContent = htmlContent.replace(cssMatch[0], `<style>${cssContent}</style>`);
|
|
@@ -4139,7 +4149,7 @@ var ReportTool = class {
|
|
|
4139
4149
|
}
|
|
4140
4150
|
}
|
|
4141
4151
|
htmlContent = htmlContent.replace('<link rel="stylesheet" href="/index.css">', "");
|
|
4142
|
-
const absoluteOutputPath =
|
|
4152
|
+
const absoluteOutputPath = path9.join(outputDir, "index.html");
|
|
4143
4153
|
await fsPromises.writeFile(absoluteOutputPath, htmlContent);
|
|
4144
4154
|
return { success: true, htmlPath: absoluteOutputPath };
|
|
4145
4155
|
} catch (error) {
|
|
@@ -4224,13 +4234,13 @@ var ReportTool = class {
|
|
|
4224
4234
|
*/
|
|
4225
4235
|
getPackageRoot() {
|
|
4226
4236
|
const __filename = fileURLToPath(import.meta.url);
|
|
4227
|
-
const __dirname =
|
|
4237
|
+
const __dirname = path9.dirname(__filename);
|
|
4228
4238
|
let currentDir = __dirname;
|
|
4229
|
-
while (currentDir !==
|
|
4230
|
-
if (fs6.existsSync(
|
|
4239
|
+
while (currentDir !== path9.parse(currentDir).root) {
|
|
4240
|
+
if (fs6.existsSync(path9.join(currentDir, "package.json"))) {
|
|
4231
4241
|
return currentDir;
|
|
4232
4242
|
}
|
|
4233
|
-
currentDir =
|
|
4243
|
+
currentDir = path9.dirname(currentDir);
|
|
4234
4244
|
}
|
|
4235
4245
|
return process.cwd();
|
|
4236
4246
|
}
|
|
@@ -4240,17 +4250,17 @@ var ReportTool = class {
|
|
|
4240
4250
|
getReportDistDir() {
|
|
4241
4251
|
const root = this.getPackageRoot();
|
|
4242
4252
|
const paths = [
|
|
4243
|
-
|
|
4253
|
+
path9.join(root, "dist/tools/report-tool/template"),
|
|
4244
4254
|
// Production layout (if copied)
|
|
4245
|
-
|
|
4255
|
+
path9.join(root, "src/tools/report-tool/template/dist")
|
|
4246
4256
|
// Development layout
|
|
4247
4257
|
];
|
|
4248
4258
|
for (const p of paths) {
|
|
4249
|
-
if (fs6.existsSync(
|
|
4259
|
+
if (fs6.existsSync(path9.join(p, "index.html"))) {
|
|
4250
4260
|
return p;
|
|
4251
4261
|
}
|
|
4252
4262
|
}
|
|
4253
|
-
return
|
|
4263
|
+
return path9.join(root, "src/tools/report-tool/template/dist");
|
|
4254
4264
|
}
|
|
4255
4265
|
};
|
|
4256
4266
|
_init6 = __decoratorStart(null);
|
|
@@ -4306,7 +4316,7 @@ async function report(options) {
|
|
|
4306
4316
|
}
|
|
4307
4317
|
|
|
4308
4318
|
// src/tools/launch-tool/index.ts
|
|
4309
|
-
import * as
|
|
4319
|
+
import * as path10 from "path";
|
|
4310
4320
|
import { tools as tools7 } from "evoltagent";
|
|
4311
4321
|
|
|
4312
4322
|
// src/tools/launch-tool/utils/dev-server-manager.ts
|
|
@@ -4314,6 +4324,16 @@ import * as http from "http";
|
|
|
4314
4324
|
import * as net from "net";
|
|
4315
4325
|
import { spawn as spawn2 } from "child_process";
|
|
4316
4326
|
init_logger();
|
|
4327
|
+
function terminateChildProcess(child) {
|
|
4328
|
+
try {
|
|
4329
|
+
if (process.platform === "win32") {
|
|
4330
|
+
child.kill();
|
|
4331
|
+
} else {
|
|
4332
|
+
child.kill("SIGTERM");
|
|
4333
|
+
}
|
|
4334
|
+
} catch {
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4317
4337
|
function normalizeExecutable(executable) {
|
|
4318
4338
|
if (process.platform !== "win32") {
|
|
4319
4339
|
return executable;
|
|
@@ -4429,18 +4449,12 @@ var DevServerManager = class {
|
|
|
4429
4449
|
child.stdout?.on("data", push);
|
|
4430
4450
|
child.stderr?.on("data", push);
|
|
4431
4451
|
process.once("exit", () => {
|
|
4432
|
-
|
|
4433
|
-
child.kill("SIGTERM");
|
|
4434
|
-
} catch {
|
|
4435
|
-
}
|
|
4452
|
+
terminateChildProcess(child);
|
|
4436
4453
|
});
|
|
4437
4454
|
const ready = await waitForServerReady(url, options.timeoutMs);
|
|
4438
4455
|
if (!ready) {
|
|
4439
4456
|
const tail = out.trim();
|
|
4440
|
-
|
|
4441
|
-
child.kill("SIGTERM");
|
|
4442
|
-
} catch {
|
|
4443
|
-
}
|
|
4457
|
+
terminateChildProcess(child);
|
|
4444
4458
|
throw new Error(`Dev server did not become ready at ${url} within ${options.timeoutMs}ms.
|
|
4445
4459
|
${tail}`);
|
|
4446
4460
|
}
|
|
@@ -4460,7 +4474,7 @@ ${tail}`);
|
|
|
4460
4474
|
await new Promise((resolve3) => {
|
|
4461
4475
|
try {
|
|
4462
4476
|
child.once("close", () => resolve3());
|
|
4463
|
-
child
|
|
4477
|
+
terminateChildProcess(child);
|
|
4464
4478
|
} catch {
|
|
4465
4479
|
resolve3();
|
|
4466
4480
|
}
|
|
@@ -4475,7 +4489,7 @@ ${tail}`);
|
|
|
4475
4489
|
|
|
4476
4490
|
// src/tools/launch-tool/index.ts
|
|
4477
4491
|
function makeServerKey(appPath) {
|
|
4478
|
-
const safeApp = appPath.split(
|
|
4492
|
+
const safeApp = appPath.split(path10.sep).join("_");
|
|
4479
4493
|
return `launch:${safeApp}`;
|
|
4480
4494
|
}
|
|
4481
4495
|
var _LaunchTool_decorators, _init7;
|
|
@@ -5142,17 +5156,9 @@ async function validatePositions(config) {
|
|
|
5142
5156
|
}
|
|
5143
5157
|
|
|
5144
5158
|
// src/utils/dependency-installer.ts
|
|
5145
|
-
init_logger();
|
|
5146
|
-
import { execSync } from "child_process";
|
|
5147
5159
|
import { createRequire as createRequire2 } from "module";
|
|
5148
|
-
|
|
5149
|
-
import path10 from "path";
|
|
5160
|
+
init_logger();
|
|
5150
5161
|
var require2 = createRequire2(import.meta.url);
|
|
5151
|
-
function detectPackageManager(cwd = process.cwd()) {
|
|
5152
|
-
if (fs7.existsSync(path10.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
5153
|
-
if (fs7.existsSync(path10.join(cwd, "yarn.lock"))) return "yarn";
|
|
5154
|
-
return "npm";
|
|
5155
|
-
}
|
|
5156
5162
|
function isPackageInstalled(packageName) {
|
|
5157
5163
|
try {
|
|
5158
5164
|
require2.resolve(packageName);
|
|
@@ -5161,43 +5167,50 @@ function isPackageInstalled(packageName) {
|
|
|
5161
5167
|
return false;
|
|
5162
5168
|
}
|
|
5163
5169
|
}
|
|
5164
|
-
function
|
|
5165
|
-
const
|
|
5166
|
-
|
|
5170
|
+
function formatValidationDependencyHelp(missing) {
|
|
5171
|
+
const isWin = process.platform === "win32";
|
|
5172
|
+
const globalInstall = [
|
|
5173
|
+
"npm install -g playwright sharp",
|
|
5174
|
+
isWin ? "npx playwright install chromium" : "npx playwright install chromium"
|
|
5175
|
+
].join("\n");
|
|
5176
|
+
const localInstall = [
|
|
5177
|
+
"npm install -D playwright sharp",
|
|
5178
|
+
isWin ? "npx playwright install chromium" : "npx playwright install chromium"
|
|
5179
|
+
].join("\n");
|
|
5180
|
+
const pnpmGlobal = ["pnpm add -g playwright sharp", "pnpm exec playwright install chromium"].join("\n");
|
|
5181
|
+
const pnpmLocal = ["pnpm add -D playwright sharp", "pnpm exec playwright install chromium"].join("\n");
|
|
5182
|
+
return [
|
|
5183
|
+
`Missing optional validation dependencies: ${missing.join(", ")}`,
|
|
5184
|
+
"",
|
|
5185
|
+
"Validation requires Playwright (browsers) and Sharp (image processing).",
|
|
5186
|
+
"These dependencies are NOT bundled with coderio by default to keep installation lightweight.",
|
|
5187
|
+
"",
|
|
5188
|
+
"Recommended (global install):",
|
|
5189
|
+
globalInstall,
|
|
5190
|
+
"",
|
|
5191
|
+
"Or install in your current project:",
|
|
5192
|
+
localInstall,
|
|
5193
|
+
"",
|
|
5194
|
+
"If you use pnpm:",
|
|
5195
|
+
"",
|
|
5196
|
+
"Global:",
|
|
5197
|
+
pnpmGlobal,
|
|
5198
|
+
"",
|
|
5199
|
+
"Local (project):",
|
|
5200
|
+
pnpmLocal
|
|
5201
|
+
].join("\n");
|
|
5202
|
+
}
|
|
5203
|
+
function assertValidationDependenciesInstalled() {
|
|
5204
|
+
const missing = [];
|
|
5205
|
+
if (!isPackageInstalled("sharp")) missing.push("sharp");
|
|
5206
|
+
if (!isPackageInstalled("playwright")) missing.push("playwright");
|
|
5207
|
+
if (missing.length === 0) {
|
|
5167
5208
|
return;
|
|
5168
5209
|
}
|
|
5169
|
-
|
|
5170
|
-
const pm = detectPackageManager();
|
|
5171
|
-
const installArgs = pm === "npm" ? "install" : "add";
|
|
5172
|
-
const installCmd = `${pm} ${installArgs} ${pkgName}`;
|
|
5173
|
-
logger.printInfoLog(`Installing ${pkgName} using ${pm}...`);
|
|
5174
|
-
logger.printInfoLog(`Command: ${installCmd}`);
|
|
5175
|
-
try {
|
|
5176
|
-
execSync(installCmd, { stdio: "inherit", cwd: process.cwd() });
|
|
5177
|
-
logger.printSuccessLog(`Successfully installed ${pkgName}.`);
|
|
5178
|
-
} catch (error) {
|
|
5179
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
5180
|
-
throw new Error(`Failed to install ${pkgName}: ${errorMsg}. Please install it manually.`);
|
|
5181
|
-
}
|
|
5210
|
+
throw new Error(formatValidationDependencyHelp(missing));
|
|
5182
5211
|
}
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
ensurePackageInstalled("playwright");
|
|
5186
|
-
try {
|
|
5187
|
-
const { chromium } = await import("playwright");
|
|
5188
|
-
if (!chromium.executablePath()) {
|
|
5189
|
-
throw new Error("Chromium not found");
|
|
5190
|
-
}
|
|
5191
|
-
} catch {
|
|
5192
|
-
logger.printInfoLog("Playwright browsers may be missing. Installing chromium...");
|
|
5193
|
-
try {
|
|
5194
|
-
execSync("npx playwright install chromium", { stdio: "inherit" });
|
|
5195
|
-
} catch {
|
|
5196
|
-
logger.printWarnLog(
|
|
5197
|
-
'Failed to install playwright browsers automatically. You might need to run "npx playwright install" manually.'
|
|
5198
|
-
);
|
|
5199
|
-
}
|
|
5200
|
-
}
|
|
5212
|
+
function ensureValidationDependencies() {
|
|
5213
|
+
assertValidationDependenciesInstalled();
|
|
5201
5214
|
}
|
|
5202
5215
|
|
|
5203
5216
|
// src/nodes/validation/core/validation-loop.ts
|
|
@@ -5290,7 +5303,7 @@ async function refineComponent(comp, context) {
|
|
|
5290
5303
|
}
|
|
5291
5304
|
function saveProcessedJson(outputDir, processedOutput) {
|
|
5292
5305
|
const processedJsonPath = path11.join(outputDir, "processed.json");
|
|
5293
|
-
|
|
5306
|
+
fs7.writeFileSync(processedJsonPath, JSON.stringify(processedOutput, null, 2));
|
|
5294
5307
|
logger.printInfoLog("Saved processed.json");
|
|
5295
5308
|
}
|
|
5296
5309
|
async function performCommit(appPath, iteration, stage) {
|
|
@@ -5322,7 +5335,7 @@ function saveIterationAndProcessedJson(iterations, iteration, currentMae, curren
|
|
|
5322
5335
|
saveProcessedJson(outputDir, processedOutput);
|
|
5323
5336
|
}
|
|
5324
5337
|
async function validationLoop(params) {
|
|
5325
|
-
|
|
5338
|
+
ensureValidationDependencies();
|
|
5326
5339
|
const { protocol, figmaThumbnailUrl, outputDir, workspace } = params;
|
|
5327
5340
|
if (!protocol || !figmaThumbnailUrl || !outputDir || !workspace) {
|
|
5328
5341
|
throw new Error("Something wrong in validation loop, missing required parameters...");
|
|
@@ -5622,8 +5635,8 @@ var runValidation = async (state) => {
|
|
|
5622
5635
|
return;
|
|
5623
5636
|
}
|
|
5624
5637
|
const outputDir = path12.join(state.workspace.process, "validation");
|
|
5625
|
-
if (!
|
|
5626
|
-
|
|
5638
|
+
if (!fs8.existsSync(outputDir)) {
|
|
5639
|
+
fs8.mkdirSync(outputDir, { recursive: true });
|
|
5627
5640
|
}
|
|
5628
5641
|
logger.printInfoLog(`Starting validation loop (mode: ${mode})...`);
|
|
5629
5642
|
const result = await validationLoop({
|
|
@@ -5650,7 +5663,7 @@ init_logger();
|
|
|
5650
5663
|
|
|
5651
5664
|
// src/nodes/code/utils.ts
|
|
5652
5665
|
init_logger();
|
|
5653
|
-
import
|
|
5666
|
+
import fs10 from "fs";
|
|
5654
5667
|
|
|
5655
5668
|
// src/nodes/code/prompt.ts
|
|
5656
5669
|
var STYLING_GUIDELINES = `
|
|
@@ -6037,18 +6050,18 @@ import path13 from "path";
|
|
|
6037
6050
|
|
|
6038
6051
|
// src/utils/code-cache.ts
|
|
6039
6052
|
init_logger();
|
|
6040
|
-
import
|
|
6053
|
+
import fs9 from "fs";
|
|
6041
6054
|
function getCachePath(workspace) {
|
|
6042
6055
|
return workspace.checkpoint;
|
|
6043
6056
|
}
|
|
6044
6057
|
function loadCodeCache(workspace) {
|
|
6045
6058
|
const cachePath = getCachePath(workspace);
|
|
6046
6059
|
try {
|
|
6047
|
-
if (!
|
|
6060
|
+
if (!fs9.existsSync(cachePath)) {
|
|
6048
6061
|
logger.printInfoLog("No code cache found, starting fresh");
|
|
6049
6062
|
return createEmptyCache();
|
|
6050
6063
|
}
|
|
6051
|
-
const content =
|
|
6064
|
+
const content = fs9.readFileSync(cachePath, "utf-8");
|
|
6052
6065
|
const cache = JSON.parse(content);
|
|
6053
6066
|
if (!Array.isArray(cache.generatedComponents) || typeof cache.appInjected !== "boolean") {
|
|
6054
6067
|
logger.printWarnLog("Invalid cache format, starting fresh");
|
|
@@ -6065,11 +6078,11 @@ function loadCodeCache(workspace) {
|
|
|
6065
6078
|
function saveCodeCache(workspace, cache) {
|
|
6066
6079
|
const cachePath = getCachePath(workspace);
|
|
6067
6080
|
try {
|
|
6068
|
-
if (!
|
|
6069
|
-
|
|
6081
|
+
if (!fs9.existsSync(workspace.process)) {
|
|
6082
|
+
fs9.mkdirSync(workspace.process, { recursive: true });
|
|
6070
6083
|
}
|
|
6071
6084
|
const content = JSON.stringify(cache, null, 2);
|
|
6072
|
-
|
|
6085
|
+
fs9.writeFileSync(cachePath, content, "utf-8");
|
|
6073
6086
|
} catch (error) {
|
|
6074
6087
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
6075
6088
|
logger.printWarnLog(`Failed to save code cache: ${errorMessage}`);
|
|
@@ -6215,10 +6228,10 @@ function saveGeneratedCode(code, filePath) {
|
|
|
6215
6228
|
function getAssetFilesList(state) {
|
|
6216
6229
|
try {
|
|
6217
6230
|
const assetsDir = workspaceManager.resolveAppSrc(state.workspace, "assets");
|
|
6218
|
-
if (!
|
|
6231
|
+
if (!fs10.existsSync(assetsDir)) {
|
|
6219
6232
|
return "";
|
|
6220
6233
|
}
|
|
6221
|
-
const files =
|
|
6234
|
+
const files = fs10.readdirSync(assetsDir);
|
|
6222
6235
|
return files.join(", ");
|
|
6223
6236
|
} catch {
|
|
6224
6237
|
return "";
|
|
@@ -6234,7 +6247,7 @@ async function injectRootComponentToApp(state, cache) {
|
|
|
6234
6247
|
const appTsxPath = workspaceManager.resolveAppSrc(state.workspace, "App.tsx");
|
|
6235
6248
|
let appContent;
|
|
6236
6249
|
try {
|
|
6237
|
-
appContent =
|
|
6250
|
+
appContent = fs10.readFileSync(appTsxPath, "utf8");
|
|
6238
6251
|
} catch {
|
|
6239
6252
|
logger.printWarnLog("App.tsx not found, using default template");
|
|
6240
6253
|
appContent = DEFAULT_APP_CONTENT;
|
|
@@ -6279,7 +6292,8 @@ async function generateCode(state) {
|
|
|
6279
6292
|
|
|
6280
6293
|
// src/utils/checkpoint.ts
|
|
6281
6294
|
init_logger();
|
|
6282
|
-
import
|
|
6295
|
+
import path14 from "path";
|
|
6296
|
+
import fs11 from "fs";
|
|
6283
6297
|
import { SqliteSaver } from "@langchain/langgraph-checkpoint-sqlite";
|
|
6284
6298
|
|
|
6285
6299
|
// src/cli/prompts.ts
|
|
@@ -6328,9 +6342,9 @@ async function promptCheckpointChoice(checkpointer, threadId) {
|
|
|
6328
6342
|
return choice === "resume";
|
|
6329
6343
|
}
|
|
6330
6344
|
function initializeSqliteSaver(dbPath) {
|
|
6331
|
-
const dbDir =
|
|
6332
|
-
if (!
|
|
6333
|
-
|
|
6345
|
+
const dbDir = path14.dirname(dbPath);
|
|
6346
|
+
if (!fs11.existsSync(dbDir)) {
|
|
6347
|
+
fs11.mkdirSync(dbDir, { recursive: true });
|
|
6334
6348
|
}
|
|
6335
6349
|
const checkpointer = SqliteSaver.fromConnString(dbPath);
|
|
6336
6350
|
return checkpointer;
|
|
@@ -6459,11 +6473,11 @@ var registerP2CCommand = (program) => {
|
|
|
6459
6473
|
};
|
|
6460
6474
|
|
|
6461
6475
|
// src/cli/val.ts
|
|
6462
|
-
import * as
|
|
6463
|
-
import * as
|
|
6476
|
+
import * as fs12 from "fs";
|
|
6477
|
+
import * as path15 from "path";
|
|
6464
6478
|
init_logger();
|
|
6465
6479
|
function readJsonFile(absolutePath) {
|
|
6466
|
-
const raw =
|
|
6480
|
+
const raw = fs12.readFileSync(absolutePath, "utf-8");
|
|
6467
6481
|
return JSON.parse(raw);
|
|
6468
6482
|
}
|
|
6469
6483
|
function registerValidateCommand(program) {
|
|
@@ -6475,16 +6489,16 @@ function registerValidateCommand(program) {
|
|
|
6475
6489
|
let workspace;
|
|
6476
6490
|
let projectName;
|
|
6477
6491
|
if (opts.workspace) {
|
|
6478
|
-
const workspacePath =
|
|
6479
|
-
const parentPath =
|
|
6480
|
-
projectName =
|
|
6492
|
+
const workspacePath = path15.resolve(opts.workspace);
|
|
6493
|
+
const parentPath = path15.dirname(path15.dirname(workspacePath));
|
|
6494
|
+
projectName = path15.basename(workspacePath);
|
|
6481
6495
|
workspace = workspaceManager.initWorkspace(projectName, parentPath);
|
|
6482
6496
|
} else {
|
|
6483
|
-
projectName =
|
|
6497
|
+
projectName = path15.basename(process.env.CODERIO_CLI_USER_CWD ?? process.cwd());
|
|
6484
6498
|
workspace = workspaceManager.initWorkspace(projectName);
|
|
6485
6499
|
}
|
|
6486
|
-
const protocolPath =
|
|
6487
|
-
if (!
|
|
6500
|
+
const protocolPath = path15.join(workspace.process, "protocol.json");
|
|
6501
|
+
if (!fs12.existsSync(protocolPath)) {
|
|
6488
6502
|
throw new Error(`Missing protocol at: ${protocolPath}. Run d2p/d2c first to generate process/protocol.json.`);
|
|
6489
6503
|
}
|
|
6490
6504
|
const protocol = readJsonFile(protocolPath);
|