Dark mode
เริ่มต้นกับ Node.js
การติดตั้ง Node.js
- ดาวน์โหลดและติดตั้ง Node.js จาก
nodejs.org
- ตรวจสอบการติดตั้งด้วยคำสั่ง:
bash
node -v
npm -v
สร้าง CLI Application ด้วย Node.js
โครงสร้างโปรเจคพื้นฐาน
my-node-cli/
├── node_modules/
├── src/
│ ├── index.ts
│ ├── commands/
│ │ └── init.ts
│ └── utils/
│ └── logger.ts
├── dist/
├── package.json
├── tsconfig.json
└── package-lock.json
ขั้นตอนการสร้างโปรเจค
- สร้างโฟลเดอร์โปรเจค:
bash
mkdir my-node-cli
cd my-node-cli
- สร้างไฟล์ package.json:
bash
npm init -y
bash
pnpm init
bash
yarn init -y
bash
bun init
- ติดตั้ง dependencies:
bash
npm install @clack/prompts picocolors commander
npm install typescript @types/node tsx --save-dev
bash
pnpm add @clack/prompts picocolors commander
pnpm add -D typescript @types/node tsx
bash
yarn add @clack/prompts picocolors commander
yarn add -D typescript @types/node tsx
bash
bun add @clack/prompts picocolors commander
bun add -d typescript @types/node tsx
- สร้างไฟล์ tsconfig.json:
json
{
"compilerOptions": {
"target": "ES2020",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
- แก้ไข package.json เพื่อเพิ่ม scripts และ bin:
json
{
"name": "my-node-cli",
"version": "1.0.0",
"description": "Interactive CLI tool built with Clack and TypeScript",
"type": "module",
"bin": {
"mycli": "./dist/index.js"
},
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"link": "npm link"
},
"keywords": ["cli", "typescript", "clack", "interactive"],
"author": "",
"license": "ISC"
}
- สร้างโฟลเดอร์และไฟล์ต่างๆ:
bash
mkdir -p src/commands src/utils
touch src/index.ts src/commands/init.ts src/utils/logger.ts
- เขียนโค้ดในไฟล์ต่างๆ:
ts
#!/usr/bin/env node
import * as p from "@clack/prompts";
import { Command } from "commander";
import pc from "picocolors";
import { initCommand } from "./commands/init.js";
// เริ่มต้นโปรแกรม
console.clear();
p.intro(`${pc.bgCyan(pc.black(" My CLI Tool "))}`);
const program = new Command();
program
.name("mycli")
.description("An awesome CLI tool built with Clack and TypeScript")
.version("1.0.0");
program
.command("init")
.description("Initialize a new project")
.option("-t, --template <template>", "specify project template", "default")
.action(async (options) => {
await initCommand(options);
});
program
.command("interactive")
.description("Start interactive mode")
.action(async () => {
const projectType = await p.select({
message: "What type of project do you want to create?",
options: [
{ value: "web", label: "Web Application" },
{ value: "api", label: "API Service" },
{ value: "lib", label: "Library" },
],
});
if (p.isCancel(projectType)) {
p.cancel("Operation cancelled");
process.exit(0);
}
const projectName = await p.text({
message: "What is your project name?",
placeholder: "my-awesome-project",
validate: (value) => {
if (!value) return "Project name is required";
if (!/^[a-z0-9-_]+$/.test(value)) {
return "Project name can only contain lowercase letters, numbers, hyphens and underscores";
}
return undefined;
},
});
if (p.isCancel(projectName)) {
p.cancel("Operation cancelled");
process.exit(0);
}
const features = await p.multiselect({
message: "Select features to include",
options: [
{ value: "typescript", label: "TypeScript", hint: "recommended" },
{ value: "eslint", label: "ESLint" },
{ value: "prettier", label: "Prettier" },
{ value: "jest", label: "Jest" },
],
required: false,
});
if (p.isCancel(features)) {
p.cancel("Operation cancelled");
process.exit(0);
}
const spinner = p.spinner();
spinner.start("Creating project...");
// Simulate project creation
await new Promise((resolve) => setTimeout(resolve, 2000));
spinner.stop("Project created successfully!");
p.note(
`
${pc.green("✓")} Project type: ${pc.cyan(projectType as string)}
${pc.green("✓")} Project name: ${pc.cyan(projectName)}
${pc.green("✓")} Features: ${
pc.cyan((features as string[]).join(", ") || "none")
}
${pc.yellow("Next steps:")}
${pc.dim("$")} cd ${projectName}
${pc.dim("$")} npm install
${pc.dim("$")} npm run dev
`,
"Project summary",
);
p.outro(
`Problems? ${
pc.underline("https://github.com/yourusername/my-node-cli/issues")
}`,
);
});
program.parse();
// If no args provided, show help
if (process.argv.length <= 2) {
program.help();
}
ts
import * as p from "@clack/prompts";
import fs from "fs/promises";
import path from "path";
import pc from "picocolors";
import { fileURLToPath } from "url";
// Convert ESM __filename and __dirname
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export async function initCommand(options: { template: string }) {
const projectName = await p.text({
message: "What is your project name?",
placeholder: "my-awesome-project",
validate: (value) => {
if (!value) return "Project name is required";
if (!/^[a-z0-9-_]+$/.test(value)) {
return "Project name can only contain lowercase letters, numbers, hyphens and underscores";
}
return undefined;
},
});
if (p.isCancel(projectName)) {
p.cancel("Operation cancelled");
process.exit(0);
}
const confirm = await p.confirm({
message:
`Create project "${projectName}" with template "${options.template}"?`,
});
if (!confirm || p.isCancel(confirm)) {
p.cancel("Operation cancelled");
process.exit(0);
}
const spinner = p.spinner();
spinner.start("Creating project directory...");
try {
// Create project directory
await fs.mkdir(projectName, { recursive: true });
// Wait a bit to simulate work
await new Promise((resolve) => setTimeout(resolve, 1000));
spinner.message("Generating project files...");
// Create basic files
const packageJson = {
name: projectName,
version: "0.1.0",
description: "Project created with my-node-cli",
type: "module",
scripts: {
dev: "node index.js",
test: "echo \"Error: no test specified\" && exit 1",
},
keywords: [],
author: "",
license: "ISC",
};
await fs.writeFile(
path.join(projectName, "package.json"),
JSON.stringify(packageJson, null, 2),
);
await fs.writeFile(
path.join(projectName, "index.js"),
`console.log('Hello from ${projectName}!');\n`,
);
await fs.writeFile(
path.join(projectName, "README.md"),
`# ${projectName}\n\nProject created with my-node-cli using the ${options.template} template.\n`,
);
// Wait a bit more to simulate work
await new Promise((resolve) => setTimeout(resolve, 1000));
spinner.stop("Project created successfully!");
p.note(
`
${pc.green("✓")} Project created at ${pc.cyan(`./${projectName}`)}
${pc.yellow("Next steps:")}
${pc.dim("$")} cd ${projectName}
${pc.dim("$")} npm install
${pc.dim("$")} npm run dev
`,
"Success!",
);
} catch (error) {
spinner.stop("Failed to create project");
p.log.error(`Error: ${(error as Error).message}`);
process.exit(1);
}
}
ts
import pc from "picocolors";
export const logger = {
info: (message: string) => console.log(pc.blue(`ℹ ${message}`)),
success: (message: string) => console.log(pc.green(`✓ ${message}`)),
warning: (message: string) => console.log(pc.yellow(`⚠ ${message}`)),
error: (message: string) => console.log(pc.red(`✗ ${message}`)),
highlight: (message: string) => pc.cyan(message),
dim: (message: string) => pc.dim(message),
};
- ทำให้ CLI สามารถรันได้ทั่วโลก (global):
bash
# สร้าง build ก่อน
npm run build
# ลิงก์ให้ใช้งานได้ทั่วโลก
npm link
bash
# สร้าง build ก่อน
pnpm build
# ลิงก์ให้ใช้งานได้ทั่วโลก
pnpm link --global
bash
# สร้าง build ก่อน
yarn build
# ลิงก์ให้ใช้งานได้ทั่วโลก
yarn link
bash
# สร้าง build ก่อน
bun run build
# ลิงก์ให้ใช้งานได้ทั่วโลก
bun link
การใช้งาน CLI
หลังจากลิงก์แล้ว คุณสามารถใช้ CLI ได้ทั่วไปในระบบ:
bash
# แสดงวิธีใช้และคำสั่งทั้งหมด
mycli --help
# สร้างโปรเจคใหม่ด้วยคำสั่ง init
mycli init
# ใช้โหมด interactive
mycli interactive
ตัวอย่าง UI ของ CLI
เมื่อรัน mycli interactive
คุณจะเห็นอินเตอร์เฟซแบบโต้ตอบดังนี้:
┌ My CLI Tool ┐
└─────────────┘
◆ What type of project do you want to create?
│ Web Application
│ API Service
│ Library
◆ What is your project name?
│ my-awesome-project
◆ Select features to include
│ ✓ TypeScript (recommended)
│ ✓ ESLint
│ Prettier
│ ✓ Jest
◎ Creating project...
✓ Project created successfully!
▶ Project summary
✓ Project type: web
✓ Project name: my-awesome-project
✓ Features: typescript, eslint, jest
Next steps:
$ cd my-awesome-project
$ npm install
$ npm run dev
◆ Problems? https://github.com/yourusername/my-node-cli/issues
การเพิ่มคำสั่งใหม่
หากต้องการเพิ่มคำสั่งใหม่ในโปรเจค สามารถทำได้โดยสร้างไฟล์ในโฟลเดอร์ commands
และเพิ่มคำสั่งใน index.ts
:
ts
// src/commands/generate.ts
import * as p from "@clack/prompts";
import pc from "picocolors";
export async function generateCommand(type: string) {
p.intro(`${pc.bgMagenta(pc.black(" Generate Command "))}`);
// ตรรกะสำหรับการสร้างไฟล์ตามประเภท
p.log.success(`Generated ${pc.cyan(type)} successfully!`);
p.outro("Done!");
}
จากนั้นเพิ่มคำสั่งในไฟล์ index.ts:
ts
program
.command("generate <type>")
.description("Generate a new file (component, model, etc.)")
.action(async (type) => {
await generateCommand(type);
});
การจัดการข้อผิดพลาด
เพิ่มการจัดการข้อผิดพลาดในแอปพลิเคชัน CLI ของคุณ: