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 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
- https://github.com/user-attachments/assets/a66dc680-f68d-4121-b416-aa6b778bd895
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**: `playwright` and `sharp` are required only for validation features. They will be automatically installed when you first run a command that needs them (like `d2c --mode full`).
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
- Create `~/.coderio/config.yaml`:
99
+ > **Important**: Requires a **multimodal (vision)** model (Recommended: `gemini-3-pro-preview`).
93
100
 
94
- ```bash
95
- mkdir -p ~/.coderio
96
- cat > ~/.coderio/config.yaml << 'EOF'
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
- ## 🤝 Contributing
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
- <a href="https://github.com/MigoXLab/coderio/graphs/contributors">
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.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
- let relativePath = aliasPath.startsWith("@/") ? aliasPath.substring(2) : aliasPath;
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 aliasPath = `@/${localPath.split("src/")?.[1] || ""}`;
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)).join("/").replace(/\/{2,}/g, "/");
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 path4 from "path";
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 = path4.join(appPath, file);
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 fs9 from "fs";
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 fs8 from "fs";
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 path5 from "path";
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 = path5.resolve(options.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 path8 from "path";
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 path7 from "path";
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 path6 from "path";
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 = path6.dirname(outputPath);
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: ${path7.basename(outputPath)}`);
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 = path8.join(request.outputDir, "comparison_screenshots");
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 = path8.join(comparisonDir, "final.webp");
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: ${path8.basename(finalScreenshotPath)}`);
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 = path8.join(comparisonDir, "heatmap.webp");
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: ${path8.basename(heatmapPath)}`);
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 = path8.join(reportDistDir, "index.html");
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 = path8.join(reportDistDir, "assets", fileName);
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 = path8.join(reportDistDir, "assets", cssFileName);
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 = path8.join(outputDir, "index.html");
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 = path8.dirname(__filename);
4237
+ const __dirname = path9.dirname(__filename);
4228
4238
  let currentDir = __dirname;
4229
- while (currentDir !== path8.parse(currentDir).root) {
4230
- if (fs6.existsSync(path8.join(currentDir, "package.json"))) {
4239
+ while (currentDir !== path9.parse(currentDir).root) {
4240
+ if (fs6.existsSync(path9.join(currentDir, "package.json"))) {
4231
4241
  return currentDir;
4232
4242
  }
4233
- currentDir = path8.dirname(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
- path8.join(root, "dist/tools/report-tool/template"),
4253
+ path9.join(root, "dist/tools/report-tool/template"),
4244
4254
  // Production layout (if copied)
4245
- path8.join(root, "src/tools/report-tool/template/dist")
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(path8.join(p, "index.html"))) {
4259
+ if (fs6.existsSync(path9.join(p, "index.html"))) {
4250
4260
  return p;
4251
4261
  }
4252
4262
  }
4253
- return path8.join(root, "src/tools/report-tool/template/dist");
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 path9 from "path";
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
- try {
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
- try {
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.kill("SIGTERM");
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(path9.sep).join("_");
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
- import fs7 from "fs";
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 ensurePackageInstalled(packageName, packageNameInRegistry) {
5165
- const pkgName = packageNameInRegistry || packageName;
5166
- if (isPackageInstalled(packageName)) {
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
- logger.printInfoLog(`Package '${packageName}' is required for validation but not installed.`);
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
- async function ensureValidationDependencies() {
5184
- ensurePackageInstalled("sharp");
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
- fs8.writeFileSync(processedJsonPath, JSON.stringify(processedOutput, null, 2));
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
- await ensureValidationDependencies();
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 (!fs9.existsSync(outputDir)) {
5626
- fs9.mkdirSync(outputDir, { recursive: true });
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 fs11 from "fs";
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 fs10 from "fs";
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 (!fs10.existsSync(cachePath)) {
6060
+ if (!fs9.existsSync(cachePath)) {
6048
6061
  logger.printInfoLog("No code cache found, starting fresh");
6049
6062
  return createEmptyCache();
6050
6063
  }
6051
- const content = fs10.readFileSync(cachePath, "utf-8");
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 (!fs10.existsSync(workspace.process)) {
6069
- fs10.mkdirSync(workspace.process, { recursive: true });
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
- fs10.writeFileSync(cachePath, content, "utf-8");
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 (!fs11.existsSync(assetsDir)) {
6231
+ if (!fs10.existsSync(assetsDir)) {
6219
6232
  return "";
6220
6233
  }
6221
- const files = fs11.readdirSync(assetsDir);
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 = fs11.readFileSync(appTsxPath, "utf8");
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 fs12 from "fs";
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 = dbPath.substring(0, dbPath.lastIndexOf("/"));
6332
- if (!fs12.existsSync(dbDir)) {
6333
- fs12.mkdirSync(dbDir, { recursive: true });
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 fs13 from "fs";
6463
- import * as path14 from "path";
6476
+ import * as fs12 from "fs";
6477
+ import * as path15 from "path";
6464
6478
  init_logger();
6465
6479
  function readJsonFile(absolutePath) {
6466
- const raw = fs13.readFileSync(absolutePath, "utf-8");
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 = path14.resolve(opts.workspace);
6479
- const parentPath = path14.dirname(path14.dirname(workspacePath));
6480
- projectName = path14.basename(workspacePath);
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 = path14.basename(process.env.CODERIO_CLI_USER_CWD ?? process.cwd());
6497
+ projectName = path15.basename(process.env.CODERIO_CLI_USER_CWD ?? process.cwd());
6484
6498
  workspace = workspaceManager.initWorkspace(projectName);
6485
6499
  }
6486
- const protocolPath = path14.join(workspace.process, "protocol.json");
6487
- if (!fs13.existsSync(protocolPath)) {
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);