Skip to content
This repository was archived by the owner on Nov 10, 2025. It is now read-only.

Commit 3069d9c

Browse files
committed
feat: v0.2.0 - Add Counter template with hybrid approach and require Node.js 22+
🌟 New Features: - Hybrid template system: bundled (Hello World) + remote (Counter, future templates) - Counter template now available - clones from midnightntwrk/example-counter - Smart requirement checking before cloning (Node.js, Docker, Compact compiler) - Template-specific setup guides with intelligent instructions - Git repository cloning with automatic cleanup 🔧 Breaking Change: - Node.js 22+ now required for ALL templates (was 18+) - Enforced at CLI startup for consistent experience - Updated package.json engines field ✨ Technical Implementation: - New utilities: git-cloner.ts, requirement-checker.ts, setup-guide.ts - Template type differentiation (bundled vs remote) - Automatic requirement validation before project creation - Context-aware success messages per template type - Clean separation between bundled and remote template workflows 📚 Documentation: - Updated README with Counter template details - Added requirement notes and installation guides - Clarified Node.js 22+ requirement - Template-specific setup instructions Counter template includes full example from Midnight Network with: - Smart contract in Compact language - CLI interface for interaction - Testnet deployment ready - ZK proof integration
1 parent 4b5b851 commit 3069d9c

File tree

8 files changed

+487
-24
lines changed

8 files changed

+487
-24
lines changed

README.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,24 @@ A simple message storage contract demonstrating:
157157
npx create-mn-app my-app --template hello-world
158158
```
159159

160-
### 🔜 Coming Soon
160+
#### **Counter DApp**
161+
162+
Real-world example from midnightntwrk demonstrating:
163+
164+
- Increment/decrement state management
165+
- Zero-knowledge proofs on testnet
166+
- Compact compiler integration
167+
- Full CLI interface
161168

162-
#### **Counter**
169+
```bash
170+
npx create-mn-app my-counter --template counter
171+
```
163172

164-
Simple increment/decrement app demonstrating state management
173+
_Automatically cloned from [midnightntwrk/example-counter](https://github.com/midnightntwrk/example-counter)_
174+
175+
**Requirements:** Node.js 22+, Docker, Compact compiler
176+
177+
### 🔜 Coming Soon
165178

166179
#### **Bulletin Board (Bboard)**
167180

@@ -177,11 +190,14 @@ Full stack DApp using NFT smart contract library (Crypto Kitties on Midnight)
177190

178191
_These templates are under development. Star the repo to stay updated!_
179192

180-
## Requirements
193+
## ⚙️ Requirements
181194

182-
- **Node.js** 18.0.0 or higher
195+
- **Node.js** 22.0.0 or higher (required for all templates)
183196
- **Docker** (for running the proof server)
184-
- **npm**, **yarn**, or **pnpm** package manager
197+
- **npm**, **yarn**, **pnpm**, or **bun** package manager
198+
- **Compact Compiler** (required for Counter and future templates - CLI will guide installation)
199+
200+
> **Note:** The CLI automatically checks Node.js version on startup and will guide you if an upgrade is needed.
185201
186202
## 📦 Package Manager Options
187203

@@ -276,13 +292,13 @@ If you see "Node.js Version Error":
276292
# Check your version
277293
node --version
278294

279-
# Install Node.js 18+ from https://nodejs.org
295+
# Install Node.js 22+ from https://nodejs.org
280296
# Or use nvm:
281-
nvm install --lts
282-
nvm use --lts
297+
nvm install 22
298+
nvm use 22
283299
```
284300
285-
The CLI automatically checks for Node.js 18+ before running!
301+
The CLI automatically checks for Node.js 22+ before running!
286302
287303
### Environment issues
288304

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "create-mn-app",
3-
"version": "0.1.0",
3+
"version": "0.2.0",
44
"description": "Create Midnight Network applications with zero configuration",
55
"main": "dist/index.js",
66
"bin": {
@@ -51,7 +51,7 @@
5151
"typescript": "^5.0.0"
5252
},
5353
"engines": {
54-
"node": ">=18.0.0"
54+
"node": ">=22.0.0"
5555
},
5656
"repository": {
5757
"type": "git",

src/cli.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import { ErrorHandler } from "./utils/error-handler.js";
55

66
const program = new Command();
77

8-
// Check Node.js version before anything else
9-
ErrorHandler.checkNodeVersion(18);
8+
// Check Node.js version before anything else (require 22+)
9+
ErrorHandler.checkNodeVersion(22);
1010

1111
program
1212
.name("create-midnight-app")
1313
.description("Create a new Midnight Network application")
14-
.version("0.1.0")
14+
.version("0.2.0")
1515
.argument("[project-directory]", "Directory name for your project")
1616
.option(
1717
"-t, --template <name>",

src/create-app.ts

Lines changed: 109 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import { TemplateManager } from "./utils/template-manager.js";
99
import { WalletGenerator } from "./installers/wallet-generator.js";
1010
import { ProofServerSetup } from "./installers/proof-server-setup.js";
1111
import { GitUtils } from "./utils/git-utils.js";
12+
import { GitCloner } from "./utils/git-cloner.js";
13+
import { RequirementChecker } from "./utils/requirement-checker.js";
14+
import { SetupGuide } from "./utils/setup-guide.js";
1215
import {
1316
detectPackageManager,
1417
getPackageManagerInfo,
@@ -167,14 +170,21 @@ export async function createApp(
167170
options
168171
);
169172

170-
// Success message and instructions
173+
// Success message - only show for bundled templates
174+
const template = getTemplate(selectedTemplate);
175+
if (template && template.type === "bundled") {
176+
displayBundledSuccessMessage(projectName!, pmInfo);
177+
}
178+
}
179+
180+
function displayBundledSuccessMessage(projectName: string, pmInfo: any): void {
171181
console.log();
172182
console.log(chalk.green.bold("━".repeat(60)));
173183
console.log(chalk.green.bold("🎉 Success! Your Midnight app is ready."));
174184
console.log(chalk.green.bold("━".repeat(60)));
175185
console.log();
176186
console.log(chalk.white.bold("📂 Project created at:"));
177-
console.log(` ${chalk.cyan(projectPath)}`);
187+
console.log(` ${chalk.cyan(projectName)}`);
178188
console.log();
179189
console.log(chalk.white.bold("🚀 Next Steps:"));
180190
console.log();
@@ -224,20 +234,115 @@ export async function createApp(
224234
async function createProject(
225235
projectPath: string,
226236
projectName: string,
227-
template: string,
237+
templateName: string,
228238
packageManager: PackageManager,
229239
options: CreateAppOptions
230240
): Promise<void> {
231241
const pmInfo = getPackageManagerInfo(packageManager);
242+
const template = getTemplate(templateName);
243+
244+
if (!template) {
245+
throw new Error(`Template "${templateName}" not found`);
246+
}
232247

233248
// Create project directory
234249
await fs.ensureDir(projectPath);
235250
process.chdir(projectPath);
236251

252+
// Handle different template types
253+
if (template.type === "remote") {
254+
await createRemoteTemplate(
255+
projectPath,
256+
projectName,
257+
template,
258+
packageManager,
259+
options
260+
);
261+
} else {
262+
await createBundledTemplate(
263+
projectPath,
264+
projectName,
265+
template,
266+
packageManager,
267+
options
268+
);
269+
}
270+
}
271+
272+
async function createRemoteTemplate(
273+
projectPath: string,
274+
projectName: string,
275+
template: any,
276+
packageManager: PackageManager,
277+
options: CreateAppOptions
278+
): Promise<void> {
279+
const pmInfo = getPackageManagerInfo(packageManager);
280+
281+
// Check requirements before cloning
282+
if (template.requiresCompactCompiler || template.nodeVersion) {
283+
const checks = [];
284+
285+
if (template.nodeVersion) {
286+
checks.push(RequirementChecker.checkNodeVersion(template.nodeVersion));
287+
}
288+
289+
checks.push(RequirementChecker.checkDocker());
290+
291+
if (template.requiresCompactCompiler) {
292+
checks.push(RequirementChecker.checkCompactCompiler());
293+
}
294+
295+
const allPassed = RequirementChecker.displayResults(checks);
296+
297+
if (!allPassed) {
298+
console.log(
299+
chalk.yellow("\n⚠ Please install missing requirements and try again.\n")
300+
);
301+
process.exit(1);
302+
}
303+
}
304+
305+
// Clone repository
306+
const cloneSpinner = ora(
307+
`Cloning ${template.display} from GitHub...`
308+
).start();
309+
try {
310+
await GitCloner.clone(template.repo!, projectPath);
311+
cloneSpinner.succeed(`Cloned ${template.display}`);
312+
SetupGuide.displayPostCloneMessage(template.name);
313+
} catch (error) {
314+
cloneSpinner.fail("Failed to clone repository");
315+
throw error;
316+
}
317+
318+
// Initialize git (since we removed .git from cloned repo)
319+
if (!options.skipGit) {
320+
const gitSpinner = ora("Initializing git repository...").start();
321+
try {
322+
await GitUtils.init(projectPath);
323+
gitSpinner.succeed("Git repository initialized");
324+
} catch (error) {
325+
gitSpinner.warn("Git repository initialization skipped");
326+
}
327+
}
328+
329+
// Display setup instructions
330+
SetupGuide.getInstructions(template.name, projectName, packageManager);
331+
}
332+
333+
async function createBundledTemplate(
334+
projectPath: string,
335+
projectName: string,
336+
template: any,
337+
packageManager: PackageManager,
338+
options: CreateAppOptions
339+
): Promise<void> {
340+
const pmInfo = getPackageManagerInfo(packageManager);
341+
237342
// Initialize template
238343
const templateSpinner = ora("Creating project structure...").start();
239344
try {
240-
const templateManager = new TemplateManager(template);
345+
const templateManager = new TemplateManager(template.name);
241346
await templateManager.scaffold(projectPath, projectName);
242347
templateSpinner.succeed("Project structure created");
243348
} catch (error) {

src/utils/git-cloner.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { execSync } from "child_process";
2+
import fs from "fs-extra";
3+
import path from "path";
4+
import chalk from "chalk";
5+
6+
export class GitCloner {
7+
/**
8+
* Clone a GitHub repository
9+
*/
10+
static async clone(
11+
repo: string,
12+
targetPath: string,
13+
branch: string = "main"
14+
): Promise<void> {
15+
const repoUrl = `https://github.com/${repo}.git`;
16+
17+
try {
18+
// Check if git is available
19+
execSync("git --version", { stdio: "ignore" });
20+
} catch {
21+
throw new Error(
22+
"Git is not installed. Please install Git from https://git-scm.com"
23+
);
24+
}
25+
26+
try {
27+
// Clone with depth 1 for faster cloning
28+
execSync(
29+
`git clone --depth 1 --branch ${branch} ${repoUrl} "${targetPath}"`,
30+
{ stdio: "pipe" }
31+
);
32+
33+
// Remove .git directory to make it a fresh project
34+
const gitDir = path.join(targetPath, ".git");
35+
if (fs.existsSync(gitDir)) {
36+
await fs.remove(gitDir);
37+
}
38+
} catch (error) {
39+
throw new Error(
40+
`Failed to clone repository ${repo}. Please check your internet connection and try again.`
41+
);
42+
}
43+
}
44+
45+
/**
46+
* Check if git is available
47+
*/
48+
static isGitAvailable(): boolean {
49+
try {
50+
execSync("git --version", { stdio: "ignore" });
51+
return true;
52+
} catch {
53+
return false;
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)