Skip to content

Commit f610062

Browse files
author
Zota0
committed
v2.0.0
1 parent 58d2327 commit f610062

8 files changed

Lines changed: 258 additions & 0 deletions

File tree

cli.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env node
2+
import inquirer from 'inquirer';
3+
import { createFromTemplate, removeFile, loadToolConfig, saveToolConfig, runCommand, recopyTemplates } from './utils.js';
4+
5+
const main = async () => {
6+
const config = await loadToolConfig();
7+
8+
const { action } = await inquirer.prompt([{
9+
type: 'list',
10+
name: 'action',
11+
message: 'What do you want to do?',
12+
choices: [
13+
'Init Nextjs',
14+
'Run Nextjs',
15+
'Add Page',
16+
'Add Component',
17+
'Add Server Action',
18+
'Edit Config',
19+
'Make Templates Local',
20+
'Exit'
21+
]
22+
}]);
23+
24+
if (action === 'Init Nextjs') {
25+
const { path } = await inquirer.prompt([
26+
{
27+
type: "input",
28+
name: "path",
29+
message: "Where?",
30+
default: "."
31+
}
32+
]);
33+
const { packageManager } = await loadToolConfig();
34+
35+
if(process.stdin.isTTY) {
36+
process.stdin.setRawMode(false);
37+
}
38+
try {
39+
await runCommand(packageManager, ['create', 'next-app@latest', path]);
40+
} catch (error) {
41+
console.error('An error occurred:', error);
42+
}
43+
44+
} else if (action === 'Run Nextjs') {
45+
const { packageManager } = await loadToolConfig();
46+
await runCommand(packageManager, ['run', 'dev']);
47+
48+
} else if (action === 'Add Component') {
49+
const { name, type } = await inquirer.prompt([
50+
{ type: 'input', name: 'name', message: 'Component name:' },
51+
{
52+
type: 'list',
53+
name: 'type',
54+
message: 'Component type:',
55+
choices: ['client', 'server']
56+
}
57+
]);
58+
59+
const dir = config.componentPath.replace('{{type}}', type);
60+
const outPath = `${dir}/${name}.tsx`;
61+
62+
if(type == "server") {
63+
await createFromTemplate('server_component', outPath, { name });
64+
} else {
65+
await createFromTemplate('client_component', outPath, { name });
66+
}
67+
68+
} else if (action === 'Add Page') {
69+
const { route } = await inquirer.prompt([
70+
{ type: 'input', name: 'route', message: 'Route path (e.g. /about):' }
71+
]);
72+
const routeName = route.replace(/^\//, '') || 'index';
73+
const dir = config.pagePath.replace('{{route}}', routeName);
74+
const outPath = `${dir}/page.tsx`;
75+
await createFromTemplate('page', outPath, { route });
76+
77+
} else if (action === 'Add Server Action') {
78+
const { name } = await inquirer.prompt([
79+
{ type: 'input', name: 'name', message: 'Action name:' }
80+
]);
81+
const outPath = `${config.actionPath}/${name}.ts`;
82+
await createFromTemplate('action', outPath, { name });
83+
84+
} else if (action === 'Make Templates Local') {
85+
recopyTemplates();
86+
87+
} else if (action === 'Edit Config') {
88+
const answers = await inquirer.prompt([
89+
{ type: 'input', name: 'componentPath', message: 'Component path:', default: config.componentPath },
90+
{ type: 'input', name: 'pagePath', message: 'Page path:', default: config.pagePath },
91+
{ type: 'input', name: 'actionPath', message: 'Server Action path:', default: config.actionPath },
92+
{ type: 'list', name: 'style', message: 'Style system:', choices: ['css-module', 'tailwind', 'none'], default: config.style },
93+
{ type: 'list', name: 'packageManager', message: 'Package manager:', choices: ['npm', 'yarn', 'pnpm', 'bun', 'deno'], default: config.packageManager }
94+
]);
95+
await saveToolConfig(answers);
96+
}
97+
98+
process.exit(0);
99+
};
100+
101+
main();

nextcli.config.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"componentPath": "src/components/{{type}}",
3+
"pagePath": "src/app/{{route}}",
4+
"actionPath": "src/actions",
5+
"style": "css-module",
6+
"packageManager": "pnpm"
7+
}

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "nextcli-helper",
3+
"description": "Minimal TUI code generator for Next.js projects",
4+
"version": "2.0.0",
5+
"bin": {
6+
"nextcli": "./cli.js"
7+
},
8+
"dependencies": {
9+
"inquirer": "^12.9.0"
10+
},
11+
"type": "module",
12+
"license": "MIT",
13+
"author": {
14+
"email": "182216725+BartekDeveloper@users.noreply.github.com",
15+
"name": "BartekDeveloper",
16+
"url": "https://github.com/BartekDeveloper"
17+
}
18+
}

templates/action.tpl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
'use server';
2+
3+
export async function {{name}}(formData: FormData) {
4+
console.log('Running server action: {{name}}');
5+
// Your logic here
6+
}

templates/client_component.tpl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"use client";
2+
import React, { useEffect, useState } from 'react';
3+
4+
const {{name}} = () => {
5+
return (
6+
<div>
7+
<h2>{{name}} component</h2>
8+
</div>
9+
);
10+
}
11+
12+
export default {{name}};

templates/page.tpl

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import react from "react";
2+
3+
const Page = () => {
4+
return (
5+
<>
6+
<h1>Page: {{route}}</h1>
7+
</>
8+
);
9+
}
10+
export default Page;

templates/server_component.tpl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
const {{name}} = () => {
4+
return (
5+
<div>
6+
<h2>{{name}} component</h2>
7+
</div>
8+
);
9+
}
10+
11+
export default {{name}};

utils.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import fs, { copyFile, mkdir } from 'fs/promises';
2+
import path from 'path';
3+
import { exec, spawn } from "child_process"
4+
import { cpSync, mkdirSync } from 'fs';
5+
import { fileURLToPath } from 'url';
6+
7+
export async function createFromTemplate(templateName, outFile, vars = {}) {
8+
const localPath = path.resolve(`.nextcli/templates/${templateName}.tpl`);
9+
let content;
10+
11+
try {
12+
content = await fs.readFile(localPath, 'utf8');
13+
} catch {
14+
const builtInPath = new URL(`./templates/${templateName}.tpl`, import.meta.url);
15+
content = await fs.readFile(builtInPath, 'utf8');
16+
}
17+
18+
const filled = content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '');
19+
await fs.mkdir(path.dirname(outFile), { recursive: true });
20+
await fs.writeFile(outFile, filled);
21+
console.log(`✅ Created: ${outFile}`);
22+
}
23+
24+
export async function removeFile(filePath) {
25+
try {
26+
await fs.rm(filePath);
27+
console.log(`🗑️ Removed: ${filePath}`);
28+
} catch {
29+
console.error(`❌ Could not remove ${filePath}`);
30+
}
31+
}
32+
33+
export async function loadToolConfig() {
34+
try {
35+
const data = await fs.readFile('./nextcli.config.json', 'utf8');
36+
return JSON.parse(data);
37+
} catch {
38+
return {
39+
componentPath: 'src/components/{{type}}',
40+
pagePath: 'src/app/{{route}}',
41+
actionPath: 'src/actions',
42+
style: 'css-module',
43+
packageManager: 'pnpm'
44+
};
45+
}
46+
}
47+
48+
export async function saveToolConfig(config) {
49+
await fs.writeFile('./nextcli.config.json', JSON.stringify(config, null, 2));
50+
console.log('✅ Saved config to nextcli.config.json');
51+
}
52+
53+
export function runCommand(command, args) {
54+
return new Promise((resolve, reject) => {
55+
const child = spawn(command, args, {
56+
stdio: 'inherit',
57+
shell: true,
58+
});
59+
60+
child.on('close', (code) => {
61+
if (code === 0) {
62+
resolve();
63+
} else {
64+
reject(new Error(`Command exited with code ${code}`));
65+
}
66+
});
67+
68+
child.on('error', (err) => {
69+
console.error('Failed to start command:', err);
70+
reject(err);
71+
});
72+
});
73+
}
74+
75+
export function recopyTemplates() {
76+
const __filename = fileURLToPath(import.meta.url);
77+
const __dirname = path.dirname(__filename);
78+
79+
const src = path.join(__dirname, 'templates');
80+
const dest = path.join(process.cwd(), '.nextcli/templates');
81+
82+
console.log(`Copying from: ${src}`);
83+
try {
84+
mkdirSync(dest, { recursive: true });
85+
console.log(`Created destination directory: ${dest}`);
86+
87+
cpSync(src, dest, { recursive: true });
88+
89+
console.log("Templates copied successfully!");
90+
} catch (error) {
91+
console.error(`Error copying templates: ${error.message}`);
92+
}
93+
}

0 commit comments

Comments
 (0)