🔧 Programmierung5 Useful Python Scripts to Automate CSV Processing(10.09.2026 um 14:00 Uhr)
🔧 Programmierung5 Python Techniques for Efficient Resource Orchestration(11.09.2026 um 14:00 Uhr)
🔧 ProgrammierungFrom Spaghetti Code to Clean Python: A Beginner’s Guide(11.09.2026 um 16:00 Uhr)
🔧 ProgrammierungText Watermarking in Python: Catch Whoever Copies Your Writing(06.09.2026 um 16:00 Uhr)
🔧 ProgrammierungWhy Most Multi-Agent Systems Fail Even When Evaluation Passes(07.09.2026 um 14:00 Uhr)
🔧 ProgrammierungA Beginner’s Guide to World Models(08.09.2026 um 19:27 Uhr)
🔧 Programmierung7 Async Patterns for Running Agents Concurrently in Python(11.08.2026 um 14:00 Uhr)
🔧 ProgrammierungManaging Small Context Windows in Language Models(18.08.2026 um 14:00 Uhr)
🔧 Programmierung5 Useful Python Scripts to Automate CSV Processing(10.09.2026 um 14:00 Uhr)
🔧 Programmierung5 Python Techniques for Efficient Resource Orchestration(11.09.2026 um 14:00 Uhr)
🔧 ProgrammierungFrom Spaghetti Code to Clean Python: A Beginner’s Guide(11.09.2026 um 16:00 Uhr)
🔧 ProgrammierungText Watermarking in Python: Catch Whoever Copies Your Writing(06.09.2026 um 16:00 Uhr)
🔧 ProgrammierungWhy Most Multi-Agent Systems Fail Even When Evaluation Passes(07.09.2026 um 14:00 Uhr)
🔧 ProgrammierungA Beginner’s Guide to World Models(08.09.2026 um 19:27 Uhr)
🔧 Programmierung7 Async Patterns for Running Agents Concurrently in Python(11.08.2026 um 14:00 Uhr)
🔧 ProgrammierungManaging Small Context Windows in Language Models(18.08.2026 um 14:00 Uhr)

🔧 Programmierung 🕛 vor 5 Monaten 6 Min Lesezeit
0

Build a Project Scaffolding CLI Like create-next-app

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht




Build a Project Scaffolding CLI Like create-next-app



Every modern framework ships a scaffolding CLI: create-next-app, create-vite, create-svelte. These tools generate entire project structures from templates, complete with dependencies, config files, and git initialization. They feel magical — but the patterns behind them are straightforward.



In this article, we'll build a scaffolding CLI from scratch that generates projects from templates, supports customization via prompts, and handles post-setup tasks like installing dependencies and initializing git.






What We're Building



scaffold — a CLI that:




  1. Offers multiple project templates

  2. Prompts for configuration (name, features, package manager)

  3. Copies and transforms template files

  4. Installs dependencies

  5. Initializes git

  6. Shows "what to do next" instructions






The Template Directory



Store templates as actual project directories:




CODE
templates/
basic/
package.json.hbs
src/
index.ts
tsconfig.json
.gitignore
api/
package.json.hbs
src/
server.ts
routes/
health.ts
tsconfig.json
.env.example
Dockerfile






Files ending in .hbs are Handlebars templates that get variable substitution. Everything else is copied as-is.






Step 1: Template Engine






CODE
// lib/template.ts
import { readFile, writeFile, readdir, mkdir, copyFile, stat } from 'node:fs/promises';
import { join, relative, dirname, extname } from 'node:path';

interface TemplateVars {
name: string;
description: string;
author: string;
features: string[];
packageManager: string;
[key: string]: unknown;
}

export async function renderTemplate(
templateDir: string,
outputDir: string,
vars: TemplateVars,
): Promise<string[]> {
const files: string[] = [];
await processDirectory(templateDir, outputDir, vars, files);
return files;
}

async function processDirectory(
srcDir: string,
destDir: string,
vars: TemplateVars,
files: string[],
) {
await mkdir(destDir, { recursive: true });
const entries = await readdir(srcDir, { withFileTypes: true });

for (const entry of entries) {
const srcPath = join(srcDir, entry.name);
let destName = entry.name;

// Remove .hbs extension from output
if (destName.endsWith('.hbs')) {
destName = destName.slice(0, -4);
}

// Template variable in filename: __name__ → actual name
destName = destName.replace(/__(\w+)__/g, (_, key) => String(vars[key] || key));

const destPath = join(destDir, destName);

if (entry.isDirectory()) {
// Skip directories based on features
if (entry.name === 'docker' && !vars.features.includes('docker')) continue;
if (entry.name === 'test' && !vars.features.includes('testing')) continue;

await processDirectory(srcPath, destPath, vars, files);
} else {
if (srcPath.endsWith('.hbs')) {
// Template file: render with variables
const content = await readFile(srcPath, 'utf-8');
const rendered = renderHandlebars(content, vars);
await writeFile(destPath, rendered);
} else {
// Static file: copy as-is
await copyFile(srcPath, destPath);
}
files.push(relative(destDir, destPath));
}
}
}

function renderHandlebars(template: string, vars: TemplateVars): string {
let result = template;

// Simple variable substitution: {{name}} → value
result = result.replace(/\{\{(\w+)\}\}/g, (_, key) => String(vars[key] || ''));

// Conditional blocks: {{#if feature}}...{{/if}}
result = result.replace(
/\{\{#if (\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
(_, key, content) => {
const value = vars[key] || vars.features?.includes(key);
return value ? content : '';
}
);

// Array iteration: {{#each items}}...{{/each}}
result = result.replace(
/\{\{#each (\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
(_, key, content) => {
const arr = vars[key];
if (!Array.isArray(arr)) return '';
return arr.map(item => content.replace(/\{\{this\}\}/g, String(item))).join('');
}
);

return result;
}









Step 2: Post-Setup Tasks






CODE
// lib/setup.ts
import { execSync } from 'node:child_process';
import chalk from 'chalk';
import ora from 'ora';

export async function installDependencies(
dir: string,
packageManager: string,
): Promise<void> {
const spinner = ora(`Installing dependencies with ${packageManager}...`).start();

const commands: Record<string, string> = {
npm: 'npm install',
yarn: 'yarn',
pnpm: 'pnpm install',
bun: 'bun install',
};

try {
execSync(commands[packageManager] || 'npm install', {
cwd: dir,
stdio: 'pipe',
timeout: 120000,
});
spinner.succeed('Dependencies installed');
} catch (error) {
spinner.fail('Failed to install dependencies');
console.error(chalk.yellow(' Run the install command manually after setup'));
}
}

export async function initGit(dir: string): Promise<void> {
const spinner = ora('Initializing git repository...').start();

try {
execSync('git init && git add -A && git commit -m "Initial commit"', {
cwd: dir,
stdio: 'pipe',
});
spinner.succeed('Git repository initialized');
} catch {
spinner.fail('Git initialization failed');
}
}

export function showNextSteps(name: string, packageManager: string) {
const run = packageManager === 'npm' ? 'npm run' : packageManager;

console.log(chalk.bold('\n Your project is ready!\n'));
console.log(` ${chalk.cyan('cd')} ${name}`);
console.log(` ${chalk.cyan(`${run} dev`)} Start development server`);
console.log(` ${chalk.cyan(`${run} build`)} Build for production`);
console.log(` ${chalk.cyan(`${run} test`)} Run tests`);
console.log();
}









Step 3: The CLI






CODE
#!/usr/bin/env node
import { program } from 'commander';
import { input, select, checkbox, confirm } from '@inquirer/prompts';
import chalk from 'chalk';
import { existsSync } from 'node:fs';
import { renderTemplate } from '../lib/template.js';
import { installDependencies, initGit, showNextSteps } from '../lib/setup.js';

program
.name('scaffold')
.description('Generate projects from templates')
.argument('[name]', 'Project name')
.option('-t, --template <name>', 'Template to use')
.option('--pm <manager>', 'Package manager: npm, yarn, pnpm, bun')
.option('--no-install', 'Skip dependency installation')
.option('--no-git', 'Skip git initialization')
.option('-y, --yes', 'Accept all defaults')
.action(async (nameArg, options) => {
const interactive = !options.yes && process.stdout.isTTY;

// Project name
const name = nameArg || (interactive
? await input({ message: 'Project name:', default: 'my-app' })
: 'my-app');

if (existsSync(name)) {
console.error(chalk.red(` Directory "${name}" already exists`));
process.exit(1);
}

// Template selection
const template = options.template || (interactive
? await select({
message: 'Choose a template:',
choices: [
{ value: 'basic', name: 'Basic — TypeScript starter' },
{ value: 'api', name: 'API — Express REST server' },
{ value: 'cli', name: 'CLI — Command-line tool' },
{ value: 'fullstack', name: 'Full Stack — React + API' },
],
})
: 'basic');

// Features
const features = interactive
? await checkbox({
message: 'Select features:',
choices: [
{ value: 'eslint', name: 'ESLint', checked: true },
{ value: 'prettier', name: 'Prettier', checked: true },
{ value: 'testing', name: 'Vitest', checked: false },
{ value: 'docker', name: 'Docker', checked: false },
{ value: 'ci', name: 'GitHub Actions', checked: false },
],
})
: ['eslint', 'prettier'];

// Package manager
const pm = options.pm || (interactive
? await select({
message: 'Package manager:',
choices: [
{ value: 'npm', name: 'npm' },
{ value: 'pnpm', name: 'pnpm' },
{ value: 'yarn', name: 'yarn' },
{ value: 'bun', name: 'bun' },
],
})
: 'npm');

// Generate
console.log();
const templateDir = new URL(`../templates/${template}`, import.meta.url).pathname;
const files = await renderTemplate(templateDir, name, {
name,
description: `A ${template} project`,
author: '',
features,
packageManager: pm,
});

console.log(chalk.green(` ✓ Generated ${files.length} files`));

// Post-setup
if (options.install !== false) {
await installDependencies(name, pm);
}

if (options.git !== false) {
await initGit(name);
}

showNextSteps(name, pm);
});

program.parse();









The Template package.json






CODE
{
"name": "{{name}}",
"version": "0.1.0",
"private": true,
"description": "{{description}}",
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js"{{#if testing}},
"test": "vitest run",
"test:watch": "vitest"{{/if}}{{#if eslint}},
"lint": "eslint src/"{{/if}}
},
"dependencies": {
},
"devDependencies": {
"typescript": "^5.4.0",
"tsx": "^4.7.0"{{#if testing}},
"vitest": "^2.0.0"{{/if}}{{#if eslint}},
"eslint": "^9.0.0",
"@eslint/js": "^9.0.0",
"typescript-eslint": "^8.0.0"{{/if}}{{#if prettier}},
"prettier": "^3.3.0"{{/if}}
}
}









Why Build Your Own?





  • Custom templates — match your team's exact stack and conventions


  • Internal tooling — scaffold microservices, lambdas, or internal packages


  • Opinionated defaults — bake in your preferred lint rules, CI config, and directory structure


  • Learning — understand how create-next-app and create-vite actually work






Conclusion



Scaffolding CLIs follow a simple pattern: prompt → copy → transform → install → initialize. The template engine is the core — everything else is file operations and shell commands. Once you have this foundation, adding new templates is just creating new directories.






Wilson Xu builds developer CLI tools and scaffolding systems. Find his 12+ tools at npm.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
1 Quelle
5 Useful Python Scripts to Automate CSV Processing
1 Quelle
5 Python Techniques for Efficient Resource Orchestration
1 Quelle
From Spaghetti Code to Clean Python: A Beginner’s Guide
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Build a Project Scaffolding CLI Like create-next-app

Thematisch verwandte Begriffe: Build, Project, Scaffolding, Like · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...