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/README.md +46 -10
- package/dist/add-mode.js +1 -1
- package/dist/add-mode.js.map +1 -1
- package/dist/builders.d.ts +30 -0
- package/dist/builders.d.ts.map +1 -0
- package/dist/builders.js +195 -0
- package/dist/builders.js.map +1 -0
- package/dist/commands.d.ts +19 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +91 -0
- package/dist/commands.js.map +1 -0
- package/dist/constants.d.ts +16 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +40 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +8 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +80 -439
- package/dist/index.js.map +1 -1
- package/dist/prompts.d.ts +26 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +79 -0
- package/dist/prompts.js.map +1 -0
- package/dist/ui.d.ts +35 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +155 -0
- package/dist/ui.js.map +1 -0
- package/dist/utils.d.ts +63 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +88 -1
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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
|
-
|
|
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("
|
|
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.
|
|
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.
|
|
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.
|
|
551
|
-
--
|
|
552
|
-
--
|
|
553
|
-
--
|
|
554
|
-
--
|
|
555
|
-
--
|
|
556
|
-
--
|
|
557
|
-
--
|
|
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.
|
|
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.
|
|
568
|
-
${
|
|
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.
|
|
571
|
-
|
|
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
|
-
|
|
253
|
+
log.error(ERROR_MESSAGES.EXAMPLE_REQUIRED);
|
|
620
254
|
showHelp();
|
|
621
255
|
process.exit(1);
|
|
622
256
|
}
|
|
623
257
|
if (exampleName && categoryName) {
|
|
624
|
-
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
263
|
+
try {
|
|
264
|
+
if (mode === "example") {
|
|
265
|
+
validateExample(name);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
validateCategory(name);
|
|
269
|
+
}
|
|
633
270
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
643
|
-
|
|
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
|
-
|
|
650
|
-
|
|
290
|
+
log.info(`Creating ${mode}: ${name}`);
|
|
291
|
+
log.dim("Downloading template...");
|
|
651
292
|
const tempRepoPath = await cloneTemplate(tempDir);
|
|
652
|
-
|
|
293
|
+
log.dim("Initializing submodules...");
|
|
653
294
|
await initSubmodule(tempRepoPath);
|
|
654
|
-
|
|
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
|
-
|
|
302
|
+
log.success(`✓ Created: ${output}`);
|
|
662
303
|
if (shouldInstall) {
|
|
663
|
-
|
|
304
|
+
log.dim("\nInstalling dependencies...");
|
|
664
305
|
await runInstallAndTest(resolved);
|
|
665
306
|
}
|
|
666
307
|
else {
|
|
667
|
-
|
|
308
|
+
log.dim(`\nNext: cd ${output} && npm install && npm run compile && npm run test`);
|
|
668
309
|
}
|
|
669
310
|
}
|
|
670
311
|
catch (error) {
|
|
671
|
-
|
|
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
|
-
|
|
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
|