create-fhevm-example 1.1.5 → 1.2.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/dist/index.js CHANGED
@@ -6,408 +6,32 @@
6
6
  * npx create-fhevm-example # Interactive mode
7
7
  * npx create-fhevm-example --example <name> # Create single example
8
8
  * npx create-fhevm-example --category <name> # Create category project
9
+ * npx create-fhevm-example --add # Add to existing project
9
10
  *
10
- * FILE STRUCTURE:
11
- * ================
12
- * 1. IMPORTS
13
- * 2. CONSTANTS - Category icons, order, etc.
14
- * 3. PROJECT BUILDERS - createSingleExample, createCategoryProject
15
- * 4. PROMPT HELPERS - Category/example selection prompts
16
- * 5. INSTALL & TEST - Build and test utilities
17
- * 6. INTERACTIVE MODE - Main interactive flow
18
- * 7. DIRECT MODE - CLI argument handling
19
- * 8. MAIN ENTRY POINT
11
+ * This is the main entry point - similar to scripts/create.ts in main project.
12
+ * Actual logic is split into:
13
+ * - builders.ts (createSingleExample, createCategoryProject)
14
+ * - ui.ts (prompts + commands)
15
+ * - utils.ts (file operations + constants + utilities)
16
+ * - config.ts (examples & categories)
17
+ * - add-mode.ts (add to existing project)
20
18
  */
21
19
  import * as p from "@clack/prompts";
22
20
  import pc from "picocolors";
23
21
  import * as fs from "fs";
24
22
  import * as path from "path";
25
23
  import * as os from "os";
24
+ // Config
26
25
  import { EXAMPLES, CATEGORIES } from "./config.js";
27
- import { cloneTemplate, initSubmodule, copyDirectoryRecursive, getContractName, downloadFileFromGitHub, runCommand, extractTestResults, generateDeployScript, } from "./utils.js";
26
+ // Utilities
27
+ import { cloneTemplate, initSubmodule, log, ERROR_MESSAGES, validateExample, validateCategory, validateDirectoryNotExists, } from "./utils.js";
28
+ // Builders
29
+ import { createSingleExample, createCategoryProject } from "./builders.js";
30
+ // UI (Prompts + Commands)
31
+ import { promptSelectCategory, promptSelectExampleFromCategory, promptSelectCategoryProject, askInstallAndTest, runInstallAndTest, } from "./ui.js";
32
+ // Add Mode
28
33
  import { runAddMode } from "./add-mode.js";
29
34
  // =============================================================================
30
- // CONSTANTS
31
- // =============================================================================
32
- /**
33
- * Simple folder icon for all categories
34
- */
35
- const CATEGORY_ICON = "📁";
36
- /**
37
- * Display order for example categories in the interactive prompt
38
- */
39
- const CATEGORY_ORDER = [
40
- "Basic",
41
- "Basic - Encryption",
42
- "Basic - Decryption",
43
- "Basic - FHE Operations",
44
- "Concepts",
45
- "Openzeppelin",
46
- "Advanced",
47
- ];
48
- // =============================================================================
49
- // PROJECT BUILDERS
50
- // =============================================================================
51
- /**
52
- * Creates a single example project from the template
53
- *
54
- * Steps:
55
- * 1. Copy template directory
56
- * 2. Download contract and test files from GitHub
57
- * 3. Update package.json and deploy scripts
58
- * 4. Clean up template-specific files
59
- */
60
- async function createSingleExample(exampleName, outputDir, tempRepoPath) {
61
- const example = EXAMPLES[exampleName];
62
- if (!example) {
63
- throw new Error(`Unknown example: ${exampleName}`);
64
- }
65
- const templateDir = path.join(tempRepoPath, "fhevm-hardhat-template");
66
- const contractName = getContractName(example.contract);
67
- if (!contractName) {
68
- throw new Error("Could not extract contract name");
69
- }
70
- // Step 1: Copy template
71
- copyDirectoryRecursive(templateDir, outputDir);
72
- // Clean up .git and initialize fresh repository
73
- const gitDir = path.join(outputDir, ".git");
74
- if (fs.existsSync(gitDir)) {
75
- fs.rmSync(gitDir, { recursive: true, force: true });
76
- }
77
- // Step 2: Remove template contract and download example contract
78
- const templateContract = path.join(outputDir, "contracts", "FHECounter.sol");
79
- if (fs.existsSync(templateContract)) {
80
- fs.unlinkSync(templateContract);
81
- }
82
- await downloadFileFromGitHub(example.contract, path.join(outputDir, "contracts", `${contractName}.sol`));
83
- // Step 3: Clean up and download test file
84
- const contractsGitkeep = path.join(outputDir, "contracts", ".gitkeep");
85
- if (fs.existsSync(contractsGitkeep)) {
86
- fs.unlinkSync(contractsGitkeep);
87
- }
88
- const testDir = path.join(outputDir, "test");
89
- fs.readdirSync(testDir).forEach((file) => {
90
- if (file.endsWith(".ts") || file === ".gitkeep") {
91
- fs.unlinkSync(path.join(testDir, file));
92
- }
93
- });
94
- await downloadFileFromGitHub(example.test, path.join(outputDir, "test", path.basename(example.test)));
95
- // Step 3.5: Download contract dependencies if specified
96
- if (example.dependencies) {
97
- for (const depPath of example.dependencies) {
98
- const depName = path.basename(depPath);
99
- // Preserve directory structure from config
100
- const relativePath = depPath.replace(/^contracts\//, "");
101
- const depDestPath = path.join(outputDir, "contracts", relativePath);
102
- const depDestDir = path.dirname(depDestPath);
103
- // Create directory if needed
104
- if (!fs.existsSync(depDestDir)) {
105
- fs.mkdirSync(depDestDir, { recursive: true });
106
- }
107
- await downloadFileFromGitHub(depPath, depDestPath);
108
- }
109
- }
110
- // Step 4: Update configuration files
111
- fs.writeFileSync(path.join(outputDir, "deploy", "deploy.ts"), generateDeployScript(contractName));
112
- const packageJsonPath = path.join(outputDir, "package.json");
113
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
114
- packageJson.name = `fhevm-example-${exampleName}`;
115
- packageJson.description = example.description;
116
- // Add npm dependencies if specified in config
117
- if (example.npmDependencies) {
118
- if (!packageJson.dependencies) {
119
- packageJson.dependencies = {};
120
- }
121
- Object.assign(packageJson.dependencies, example.npmDependencies);
122
- }
123
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
124
- // Step 5: Remove template-specific task
125
- const configPath = path.join(outputDir, "hardhat.config.ts");
126
- let configContent = fs.readFileSync(configPath, "utf-8");
127
- configContent = configContent.replace(/import "\.\/tasks\/FHECounter";\n?/g, "");
128
- fs.writeFileSync(configPath, configContent);
129
- const oldTaskFile = path.join(outputDir, "tasks", "FHECounter.ts");
130
- if (fs.existsSync(oldTaskFile)) {
131
- fs.unlinkSync(oldTaskFile);
132
- }
133
- // Step 6: Create test/types.ts for type safety
134
- const typesContent = `import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
135
-
136
- /**
137
- * Common signers interface used across test files
138
- */
139
- export interface Signers {
140
- owner: HardhatEthersSigner;
141
- alice: HardhatEthersSigner;
142
- }
143
- `;
144
- fs.writeFileSync(path.join(outputDir, "test", "types.ts"), typesContent);
145
- // Initialize git repository
146
- try {
147
- await runCommand("git", ["init"], outputDir);
148
- }
149
- catch (error) {
150
- // Git init is optional, silently continue if it fails
151
- }
152
- }
153
- /**
154
- * Creates a category project with multiple examples
155
- *
156
- * Steps:
157
- * 1. Copy template directory
158
- * 2. Download all contracts and tests for the category
159
- * 3. Update package.json
160
- * 4. Clean up template-specific files
161
- */
162
- async function createCategoryProject(categoryName, outputDir, tempRepoPath) {
163
- const category = CATEGORIES[categoryName];
164
- if (!category) {
165
- throw new Error(`Unknown category: ${categoryName}`);
166
- }
167
- const templateDir = path.join(tempRepoPath, "fhevm-hardhat-template");
168
- // Step 1: Copy template
169
- copyDirectoryRecursive(templateDir, outputDir);
170
- // Clean up .git and initialize fresh repository
171
- const gitDir = path.join(outputDir, ".git");
172
- if (fs.existsSync(gitDir)) {
173
- fs.rmSync(gitDir, { recursive: true, force: true });
174
- }
175
- // Step 2: Clear template files
176
- const templateContract = path.join(outputDir, "contracts", "FHECounter.sol");
177
- if (fs.existsSync(templateContract))
178
- fs.unlinkSync(templateContract);
179
- const contractsGitkeep = path.join(outputDir, "contracts", ".gitkeep");
180
- if (fs.existsSync(contractsGitkeep))
181
- fs.unlinkSync(contractsGitkeep);
182
- const testDir = path.join(outputDir, "test");
183
- fs.readdirSync(testDir).forEach((file) => {
184
- if (file.endsWith(".ts") || file === ".gitkeep") {
185
- fs.unlinkSync(path.join(testDir, file));
186
- }
187
- });
188
- // Step 3: Download all contracts and tests
189
- for (const item of category.contracts) {
190
- const contractName = getContractName(item.sol);
191
- if (contractName) {
192
- await downloadFileFromGitHub(item.sol, path.join(outputDir, "contracts", `${contractName}.sol`));
193
- }
194
- if (item.test) {
195
- await downloadFileFromGitHub(item.test, path.join(outputDir, "test", path.basename(item.test)));
196
- }
197
- }
198
- // Step 3.5: Download all dependencies from examples in this category
199
- const allDependencies = new Set();
200
- const allNpmDependencies = {};
201
- // Collect dependencies from all examples in this category
202
- for (const [exampleName, exampleConfig] of Object.entries(EXAMPLES)) {
203
- if (exampleConfig.category === category.name.replace(" Examples", "")) {
204
- // Collect contract dependencies
205
- if (exampleConfig.dependencies) {
206
- for (const dep of exampleConfig.dependencies) {
207
- allDependencies.add(dep);
208
- }
209
- }
210
- // Collect npm dependencies
211
- if (exampleConfig.npmDependencies) {
212
- Object.assign(allNpmDependencies, exampleConfig.npmDependencies);
213
- }
214
- }
215
- }
216
- // Download all collected dependencies
217
- for (const depPath of allDependencies) {
218
- const relativePath = depPath.replace(/^contracts\//, "");
219
- const depDestPath = path.join(outputDir, "contracts", relativePath);
220
- const depDestDir = path.dirname(depDestPath);
221
- if (!fs.existsSync(depDestDir)) {
222
- fs.mkdirSync(depDestDir, { recursive: true });
223
- }
224
- await downloadFileFromGitHub(depPath, depDestPath);
225
- }
226
- // Step 4: Update configuration files
227
- const configPath = path.join(outputDir, "hardhat.config.ts");
228
- let configContent = fs.readFileSync(configPath, "utf-8");
229
- configContent = configContent.replace(/import "\.\/tasks\/FHECounter";\n?/g, "");
230
- fs.writeFileSync(configPath, configContent);
231
- const oldTaskFile = path.join(outputDir, "tasks", "FHECounter.ts");
232
- if (fs.existsSync(oldTaskFile)) {
233
- fs.unlinkSync(oldTaskFile);
234
- }
235
- // Step 5: Create test/types.ts for type safety
236
- const typesContent = `import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
237
-
238
- /**
239
- * Common signers interface used across test files
240
- */
241
- export interface Signers {
242
- owner: HardhatEthersSigner;
243
- alice: HardhatEthersSigner;
244
- }
245
- `;
246
- fs.writeFileSync(path.join(outputDir, "test", "types.ts"), typesContent);
247
- const packageJsonPath = path.join(outputDir, "package.json");
248
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
249
- packageJson.name = `fhevm-examples-${categoryName}`;
250
- // Add aggregated npm dependencies
251
- if (Object.keys(allNpmDependencies).length > 0) {
252
- if (!packageJson.dependencies) {
253
- packageJson.dependencies = {};
254
- }
255
- Object.assign(packageJson.dependencies, allNpmDependencies);
256
- }
257
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
258
- // Initialize git repository
259
- try {
260
- await runCommand("git", ["init"], outputDir);
261
- }
262
- catch (error) {
263
- // Git init is optional, silently continue if it fails
264
- }
265
- }
266
- // =============================================================================
267
- // PROMPT HELPERS
268
- // =============================================================================
269
- /**
270
- * Counts how many examples exist in each category
271
- */
272
- function countExamplesPerCategory() {
273
- const counts = {};
274
- for (const config of Object.values(EXAMPLES)) {
275
- counts[config.category] = (counts[config.category] || 0) + 1;
276
- }
277
- return counts;
278
- }
279
- /**
280
- * Prompts user to select a category
281
- * Returns the selected category name
282
- */
283
- async function promptSelectCategory() {
284
- const categoryCounts = countExamplesPerCategory();
285
- // Get all categories, prioritizing CATEGORY_ORDER, then alphabetically sorted others
286
- const allCategories = Object.keys(categoryCounts);
287
- const orderedCategories = [
288
- ...CATEGORY_ORDER.filter((cat) => allCategories.includes(cat)),
289
- ...allCategories.filter((cat) => !CATEGORY_ORDER.includes(cat)).sort(),
290
- ];
291
- return p.select({
292
- message: "Select a category:",
293
- options: orderedCategories.map((category) => ({
294
- value: category,
295
- label: `${CATEGORY_ICON} ${category}`,
296
- hint: `${categoryCounts[category] || 0} example${categoryCounts[category] !== 1 ? "s" : ""}`,
297
- })),
298
- });
299
- }
300
- /**
301
- * Prompts user to select an example from a specific category
302
- * Returns the selected example name
303
- */
304
- async function promptSelectExampleFromCategory(category) {
305
- const categoryExamples = Object.entries(EXAMPLES)
306
- .filter(([, config]) => config.category === category)
307
- .map(([key, config]) => ({
308
- value: key,
309
- label: key,
310
- hint: config.description.slice(0, 80) +
311
- (config.description.length > 80 ? "..." : ""),
312
- }));
313
- return p.select({
314
- message: `Select an example from ${category}:`,
315
- options: categoryExamples,
316
- });
317
- }
318
- /**
319
- * Prompts user to select a category project
320
- * Returns the selected category key (lowercase)
321
- */
322
- async function promptSelectCategoryProject() {
323
- return p.select({
324
- message: "Select a category:",
325
- options: Object.entries(CATEGORIES).map(([key, config]) => ({
326
- value: key,
327
- label: `${CATEGORY_ICON} ${config.name}`,
328
- hint: `${config.contracts.length} contracts`,
329
- })),
330
- });
331
- }
332
- // =============================================================================
333
- // INSTALL & TEST
334
- // =============================================================================
335
- /**
336
- * Runs npm install, compile, and test in the project directory
337
- */
338
- async function runInstallAndTest(projectPath) {
339
- const steps = [
340
- {
341
- name: "Installing dependencies",
342
- cmd: "npm",
343
- args: ["install"],
344
- showOutput: false,
345
- },
346
- {
347
- name: "Compiling contracts",
348
- cmd: "npm",
349
- args: ["run", "compile"],
350
- showOutput: false,
351
- },
352
- {
353
- name: "Running tests",
354
- cmd: "npm",
355
- args: ["run", "test"],
356
- showOutput: true,
357
- },
358
- ];
359
- for (const step of steps) {
360
- const s = p.spinner();
361
- s.start(step.name + "...");
362
- try {
363
- const output = await runCommand(step.cmd, step.args, projectPath);
364
- if (step.showOutput) {
365
- const testResults = extractTestResults(output);
366
- s.stop(testResults
367
- ? pc.green(`✓ ${step.name} - ${testResults}`)
368
- : pc.green(`✓ ${step.name} completed`));
369
- }
370
- else {
371
- s.stop(pc.green(`✓ ${step.name} completed`));
372
- }
373
- }
374
- catch (error) {
375
- s.stop(pc.red(`✗ ${step.name} failed`));
376
- if (error instanceof Error) {
377
- p.log.error(error.message);
378
- }
379
- throw new Error(`${step.name} failed`);
380
- }
381
- }
382
- p.log.success(pc.green("All steps completed successfully!"));
383
- }
384
- /**
385
- * Shows quick start commands for the created project
386
- */
387
- function showQuickStart(relativePath) {
388
- p.note(`${pc.dim("$")} cd ${relativePath}\n${pc.dim("$")} npm install\n${pc.dim("$")} npm run compile\n${pc.dim("$")} npm run test`, "🚀 Quick Start");
389
- }
390
- /**
391
- * Asks user if they want to install and test, then runs or shows quick start
392
- */
393
- async function askInstallAndTest(resolvedOutput, relativePath) {
394
- const shouldInstall = await p.confirm({
395
- message: "Install dependencies and run tests?",
396
- initialValue: false,
397
- });
398
- if (p.isCancel(shouldInstall)) {
399
- showQuickStart(relativePath);
400
- return;
401
- }
402
- if (shouldInstall) {
403
- p.log.message("");
404
- await runInstallAndTest(resolvedOutput);
405
- }
406
- else {
407
- showQuickStart(relativePath);
408
- }
409
- }
410
- // =============================================================================
411
35
  // INTERACTIVE MODE
412
36
  // =============================================================================
413
37
  /**
@@ -442,7 +66,6 @@ async function runInteractiveMode() {
442
66
  let projectName = "";
443
67
  // Step 2: Select based on mode
444
68
  if (mode === "single") {
445
- // Single example: first select category, then example
446
69
  const selectedCategory = await promptSelectCategory();
447
70
  if (p.isCancel(selectedCategory)) {
448
71
  p.cancel("Operation cancelled.");
@@ -460,7 +83,6 @@ async function runInteractiveMode() {
460
83
  });
461
84
  }
462
85
  else {
463
- // Category project: select category directly
464
86
  categoryName = await promptSelectCategoryProject();
465
87
  if (p.isCancel(categoryName)) {
466
88
  p.cancel("Operation cancelled.");
@@ -530,7 +152,7 @@ async function runInteractiveMode() {
530
152
  fs.rmSync(tempDir, { recursive: true, force: true });
531
153
  }
532
154
  }
533
- p.outro(pc.green("🎉 Happy coding with FHEVM!"));
155
+ p.outro(pc.green(" Setup complete. Happy encrypting!"));
534
156
  }
535
157
  // =============================================================================
536
158
  // DIRECT MODE (CLI Arguments)
@@ -540,35 +162,47 @@ async function runInteractiveMode() {
540
162
  */
541
163
  function showHelp() {
542
164
  console.log(`
543
- ${pc.cyan("create-fhevm-example")}
165
+ ${pc.bgCyan(pc.black(pc.bold(" 🔐 create-fhevm-example ")))}
166
+ ${pc.dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
167
+
168
+ ${pc.cyan(pc.bold("📋 USAGE"))}
169
+
170
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.dim("→")} Interactive mode ${pc.yellow("(recommended)")}
171
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.green("--example")} ${pc.yellow("<name>")} ${pc.dim("→")} Create single example
172
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.green("--category")} ${pc.yellow("<name>")} ${pc.dim("→")} Create category project
173
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.green("--add")} ${pc.dim("→")} Add to existing project
544
174
 
545
- ${pc.yellow("Usage:")}
546
- npx create-fhevm-example ${pc.dim("# Interactive mode")}
547
- npx create-fhevm-example --example <name> ${pc.dim("# Create single example")}
548
- npx create-fhevm-example --category <name> ${pc.dim("# Create category project")}
175
+ ${pc.cyan(pc.bold("⚙️ OPTIONS"))}
549
176
 
550
- ${pc.yellow("Options:")}
551
- --example <name> Create a single example project
552
- --category <name> Create a category project
553
- --add Add FHEVM to existing Hardhat project
554
- --target <dir> Target directory for --add mode (default: current dir)
555
- --output <dir> Output directory (default: ./<project-name>)
556
- --install Auto-install dependencies
557
- --test Auto-run tests (requires --install)
558
- --help, -h Show this help message
177
+ ${pc.green("--example")} ${pc.dim("<name>")} Create a single example project
178
+ ${pc.green("--category")} ${pc.dim("<name>")} Create a category project
179
+ ${pc.green("--add")} Add FHEVM to existing Hardhat project
180
+ ${pc.green("--target")} ${pc.dim("<dir>")} Target directory for --add mode
181
+ ${pc.green("--output")} ${pc.dim("<dir>")} Output directory
182
+ ${pc.green("--install")} Auto-install dependencies
183
+ ${pc.green("--test")} Auto-run tests (requires --install)
184
+ ${pc.green("--help")}${pc.dim(", -h")} Show this help message
559
185
 
560
- ${pc.yellow("Examples:")}
561
- ${pc.green("npx create-fhevm-example --example fhe-counter")}
562
- ${pc.green("npx create-fhevm-example --category basic --output ./my-project")}
563
- ${pc.green("npx create-fhevm-example --add")}
564
- ${pc.green("npx create-fhevm-example --add --target ./my-existing-project")}
565
- ${pc.green("npx create-fhevm-example --example fhe-counter --install --test")}
186
+ ${pc.cyan(pc.bold("⚡ EXAMPLES"))}
566
187
 
567
- ${pc.yellow("Available examples:")}
568
- ${Object.keys(EXAMPLES).join(", ")}
188
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.green("--example")} ${pc.yellow("fhe-counter")}
189
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.green("--category")} ${pc.yellow("basic")} ${pc.green("--output")} ${pc.blue("./my-project")}
190
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.green("--add")}
191
+ ${pc.dim("$")} ${pc.white("npx create-fhevm-example")} ${pc.green("--example")} ${pc.yellow("fhe-counter")} ${pc.green("--install")} ${pc.green("--test")}
569
192
 
570
- ${pc.yellow("Available categories:")}
571
- ${Object.keys(CATEGORIES).join(", ")}
193
+ ${pc.cyan(pc.bold("📦 AVAILABLE EXAMPLES"))}
194
+
195
+ ${pc.dim(Object.keys(EXAMPLES).slice(0, 10).join(", "))}
196
+ ${pc.dim("...")} and ${pc.yellow(String(Object.keys(EXAMPLES).length - 10))} more
197
+
198
+ ${pc.cyan(pc.bold("📁 AVAILABLE CATEGORIES"))}
199
+
200
+ ${Object.keys(CATEGORIES)
201
+ .map((c) => pc.yellow(c))
202
+ .join(", ")}
203
+
204
+ ${pc.dim("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")}
205
+ ${pc.dim("📚 Documentation:")} ${pc.blue("https://github.com/NecipAkgz/fhevm-example-factory")}
572
206
  `);
573
207
  }
574
208
  /**
@@ -616,59 +250,66 @@ async function runDirectMode(args) {
616
250
  const shouldInstall = parsedArgs["install"] === true;
617
251
  // Validation
618
252
  if (!exampleName && !categoryName) {
619
- console.error(pc.red("Error: Either --example or --category is required"));
253
+ log.error(ERROR_MESSAGES.EXAMPLE_REQUIRED);
620
254
  showHelp();
621
255
  process.exit(1);
622
256
  }
623
257
  if (exampleName && categoryName) {
624
- console.error(pc.red("Error: Cannot use both --example and --category"));
258
+ log.error(ERROR_MESSAGES.BOTH_SPECIFIED);
625
259
  process.exit(1);
626
260
  }
627
261
  const mode = exampleName ? "example" : "category";
628
262
  const name = (exampleName || categoryName);
629
- if (mode === "example" && !EXAMPLES[name]) {
630
- console.error(pc.red(`Error: Unknown example "${name}"`));
631
- console.log("Available:", Object.keys(EXAMPLES).join(", "));
632
- process.exit(1);
263
+ try {
264
+ if (mode === "example") {
265
+ validateExample(name);
266
+ }
267
+ else {
268
+ validateCategory(name);
269
+ }
633
270
  }
634
- if (mode === "category" && !CATEGORIES[name]) {
635
- console.error(pc.red(`Error: Unknown category "${name}"`));
636
- console.log("Available:", Object.keys(CATEGORIES).join(", "));
271
+ catch (error) {
272
+ log.error(error instanceof Error ? error.message : String(error));
273
+ log.message("Available: " +
274
+ Object.keys(mode === "example" ? EXAMPLES : CATEGORIES).join(", "));
637
275
  process.exit(1);
638
276
  }
639
277
  const defaultOutput = mode === "example" ? `./my-${name}-project` : `./my-${name}-examples`;
640
278
  const output = outputDir || defaultOutput;
641
279
  const resolved = path.resolve(process.cwd(), output);
642
- if (fs.existsSync(resolved)) {
643
- console.error(pc.red(`Error: Directory already exists: ${resolved}`));
280
+ try {
281
+ validateDirectoryNotExists(resolved);
282
+ }
283
+ catch (error) {
284
+ log.error(error instanceof Error ? error.message : String(error));
644
285
  process.exit(1);
645
286
  }
646
287
  // Create project
647
288
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "fhevm-"));
648
289
  try {
649
- console.log(pc.cyan(`Creating ${mode}: ${name}`));
650
- console.log(pc.dim("Downloading template..."));
290
+ log.info(`Creating ${mode}: ${name}`);
291
+ log.dim("Downloading template...");
651
292
  const tempRepoPath = await cloneTemplate(tempDir);
652
- console.log(pc.dim("Initializing submodules..."));
293
+ log.dim("Initializing submodules...");
653
294
  await initSubmodule(tempRepoPath);
654
- console.log(pc.dim("Creating project..."));
295
+ log.dim("Creating project...");
655
296
  if (mode === "example") {
656
297
  await createSingleExample(name, resolved, tempRepoPath);
657
298
  }
658
299
  else {
659
300
  await createCategoryProject(name, resolved, tempRepoPath);
660
301
  }
661
- console.log(pc.green(`✓ Created: ${output}`));
302
+ log.success(`✓ Created: ${output}`);
662
303
  if (shouldInstall) {
663
- console.log(pc.dim("\nInstalling dependencies..."));
304
+ log.dim("\nInstalling dependencies...");
664
305
  await runInstallAndTest(resolved);
665
306
  }
666
307
  else {
667
- console.log(pc.dim(`\nNext: cd ${output} && npm install && npm run compile && npm run test`));
308
+ log.dim(`\nNext: cd ${output} && npm install && npm run compile && npm run test`);
668
309
  }
669
310
  }
670
311
  catch (error) {
671
- console.error(pc.red("Error:"), error instanceof Error ? error.message : String(error));
312
+ log.error(error instanceof Error ? error.message : String(error));
672
313
  process.exit(1);
673
314
  }
674
315
  finally {
@@ -690,7 +331,7 @@ async function main() {
690
331
  }
691
332
  }
692
333
  main().catch((error) => {
693
- console.error(pc.red("Fatal error:"), error);
334
+ log.error("Fatal error: " + (error instanceof Error ? error.message : String(error)));
694
335
  process.exit(1);
695
336
  });
696
337
  //# sourceMappingURL=index.js.map