next2point0 1.1.0
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/bin/index.js +221 -0
- package/package.json +48 -0
package/bin/index.js
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { execSync, spawnSync } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
|
|
8
|
+
const REPO_URL = 'https://github.com/ahmad2point0/next2point0';
|
|
9
|
+
const pkg = require('../package.json');
|
|
10
|
+
|
|
11
|
+
const c = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bold: '\x1b[1m',
|
|
14
|
+
dim: '\x1b[2m',
|
|
15
|
+
red: '\x1b[31m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
};
|
|
20
|
+
const supportsColor = process.stdout.isTTY && process.env.TERM !== 'dumb' && !process.env.NO_COLOR;
|
|
21
|
+
const paint = (color, s) => (supportsColor ? `${c[color]}${s}${c.reset}` : s);
|
|
22
|
+
|
|
23
|
+
const TEMPLATES = {
|
|
24
|
+
frontend: { branch: 'main', label: 'Frontend' },
|
|
25
|
+
fullstack: { branch: 'feat/backend', label: 'Frontend + Backend' },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function parseArgs(argv) {
|
|
29
|
+
const args = { _: [], flags: {} };
|
|
30
|
+
for (let i = 0; i < argv.length; i++) {
|
|
31
|
+
const a = argv[i];
|
|
32
|
+
if (a === '-h' || a === '--help') args.flags.help = true;
|
|
33
|
+
else if (a === '-v' || a === '--version') args.flags.version = true;
|
|
34
|
+
else if (a === '-t' || a === '--template') args.flags.template = argv[++i];
|
|
35
|
+
else if (a === '--pm' || a === '--package-manager') args.flags.pm = argv[++i];
|
|
36
|
+
else if (a === '--skip-install') args.flags.skipInstall = true;
|
|
37
|
+
else if (a === '--skip-git') args.flags.skipGit = true;
|
|
38
|
+
else if (a === '-y' || a === '--yes') args.flags.yes = true;
|
|
39
|
+
else if (a.startsWith('-')) {
|
|
40
|
+
console.error(paint('red', `Unknown flag: ${a}`));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
} else args._.push(a);
|
|
43
|
+
}
|
|
44
|
+
return args;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function printHelp() {
|
|
48
|
+
console.log(`
|
|
49
|
+
${paint('bold', 'next2point0')} ${paint('dim', `v${pkg.version}`)}
|
|
50
|
+
${paint('dim', pkg.description)}
|
|
51
|
+
|
|
52
|
+
${paint('bold', 'Usage:')}
|
|
53
|
+
npx next2point0 <project-name> [options]
|
|
54
|
+
|
|
55
|
+
${paint('bold', 'Options:')}
|
|
56
|
+
-t, --template <name> Template: frontend | fullstack
|
|
57
|
+
--pm <name> Package manager: bun | pnpm | npm | yarn
|
|
58
|
+
--skip-install Don't install dependencies
|
|
59
|
+
--skip-git Don't init a new git repository
|
|
60
|
+
-y, --yes Accept defaults (template: frontend)
|
|
61
|
+
-h, --help Show this help
|
|
62
|
+
-v, --version Show version
|
|
63
|
+
|
|
64
|
+
${paint('bold', 'Examples:')}
|
|
65
|
+
npx next2point0 my-app
|
|
66
|
+
npx next2point0 my-app -t fullstack --pm pnpm
|
|
67
|
+
npx next2point0 my-app -y
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ask(query) {
|
|
72
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
73
|
+
return new Promise((resolve) => rl.question(query, (a) => { rl.close(); resolve(a); }));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function validateProjectName(name) {
|
|
77
|
+
if (!name || !name.trim()) return 'Project name cannot be empty.';
|
|
78
|
+
if (name.length > 214) return 'Project name is too long (max 214).';
|
|
79
|
+
if (/[A-Z]/.test(name)) return 'Project name must be lowercase.';
|
|
80
|
+
if (/^[._]/.test(name)) return 'Project name cannot start with "." or "_".';
|
|
81
|
+
if (/[\s~)('!*]/.test(name)) return 'Project name contains invalid characters.';
|
|
82
|
+
if (!/^[a-z0-9][a-z0-9\-_.]*$/.test(name)) return 'Project name has invalid characters.';
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function detectPackageManager(preferred) {
|
|
87
|
+
const order = preferred ? [preferred, 'bun', 'pnpm', 'npm', 'yarn'] : ['bun', 'pnpm', 'npm', 'yarn'];
|
|
88
|
+
for (const pm of order) {
|
|
89
|
+
const r = spawnSync(pm, ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' });
|
|
90
|
+
if (r.status === 0) return pm;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkGitAvailable() {
|
|
96
|
+
const r = spawnSync('git', ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' });
|
|
97
|
+
return r.status === 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function main() {
|
|
101
|
+
const args = parseArgs(process.argv.slice(2));
|
|
102
|
+
|
|
103
|
+
if (args.flags.help) return printHelp();
|
|
104
|
+
if (args.flags.version) return console.log(pkg.version);
|
|
105
|
+
|
|
106
|
+
console.log(paint('cyan', paint('bold', '\n ▲ next2point0')) + paint('dim', ` v${pkg.version}\n`));
|
|
107
|
+
|
|
108
|
+
if (!checkGitAvailable()) {
|
|
109
|
+
console.error(paint('red', '✖ git is required but was not found in PATH.'));
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let projectName = args._[0];
|
|
114
|
+
if (!projectName) {
|
|
115
|
+
projectName = (await ask(paint('bold', '? ') + 'Project name: ')).trim();
|
|
116
|
+
}
|
|
117
|
+
const nameError = validateProjectName(projectName);
|
|
118
|
+
if (nameError) {
|
|
119
|
+
console.error(paint('red', `✖ ${nameError}`));
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
124
|
+
if (fs.existsSync(targetDir)) {
|
|
125
|
+
const entries = fs.readdirSync(targetDir);
|
|
126
|
+
if (entries.length > 0) {
|
|
127
|
+
console.error(paint('red', `✖ Directory "${projectName}" already exists and is not empty.`));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let templateKey = args.flags.template;
|
|
133
|
+
if (!templateKey && args.flags.yes) templateKey = 'frontend';
|
|
134
|
+
if (!templateKey) {
|
|
135
|
+
console.log('\n' + paint('bold', 'Select a template:'));
|
|
136
|
+
console.log(` ${paint('cyan', '1')}) Frontend ${paint('dim', '(Next.js)')}`);
|
|
137
|
+
console.log(` ${paint('cyan', '2')}) Frontend + Backend ${paint('dim', '(Next.js + API)')}`);
|
|
138
|
+
const choice = (await ask('\n' + paint('bold', '? ') + 'Choice (1 or 2) [1]: ')).trim() || '1';
|
|
139
|
+
templateKey = choice === '2' ? 'fullstack' : 'frontend';
|
|
140
|
+
}
|
|
141
|
+
if (!TEMPLATES[templateKey]) {
|
|
142
|
+
console.error(paint('red', `✖ Unknown template "${templateKey}". Use: frontend | fullstack`));
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const template = TEMPLATES[templateKey];
|
|
146
|
+
|
|
147
|
+
let pm = args.flags.pm;
|
|
148
|
+
if (!pm) {
|
|
149
|
+
pm = detectPackageManager() || 'npm';
|
|
150
|
+
} else {
|
|
151
|
+
const available = detectPackageManager(pm);
|
|
152
|
+
if (available !== pm) {
|
|
153
|
+
console.error(paint('yellow', `⚠ "${pm}" not found, falling back to ${available || 'npm'}.`));
|
|
154
|
+
pm = available || 'npm';
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log('\n' + paint('cyan', '◐') + ` Downloading ${paint('bold', template.label)} template…`);
|
|
159
|
+
try {
|
|
160
|
+
execSync(
|
|
161
|
+
`git clone --depth=1 --branch ${template.branch} --single-branch ${REPO_URL} "${targetDir}"`,
|
|
162
|
+
{ stdio: 'ignore' }
|
|
163
|
+
);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.error(paint('red', '✖ Failed to clone template. Check your internet connection.'));
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
console.log(paint('green', '✔') + ' Template downloaded.');
|
|
169
|
+
|
|
170
|
+
process.chdir(targetDir);
|
|
171
|
+
fs.rmSync(path.join(targetDir, '.git'), { recursive: true, force: true });
|
|
172
|
+
|
|
173
|
+
const projectPkgPath = path.join(targetDir, 'package.json');
|
|
174
|
+
if (fs.existsSync(projectPkgPath)) {
|
|
175
|
+
try {
|
|
176
|
+
const projectPkg = JSON.parse(fs.readFileSync(projectPkgPath, 'utf8'));
|
|
177
|
+
projectPkg.name = projectName;
|
|
178
|
+
projectPkg.version = '0.1.0';
|
|
179
|
+
fs.writeFileSync(projectPkgPath, JSON.stringify(projectPkg, null, 2) + '\n');
|
|
180
|
+
} catch {
|
|
181
|
+
console.warn(paint('yellow', '⚠ Could not update template package.json name.'));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!args.flags.skipInstall) {
|
|
186
|
+
console.log(paint('cyan', '◐') + ` Installing dependencies with ${paint('bold', pm)}…`);
|
|
187
|
+
const install = spawnSync(pm, ['install'], { stdio: 'inherit', shell: process.platform === 'win32' });
|
|
188
|
+
if (install.status !== 0) {
|
|
189
|
+
console.error(paint('yellow', `⚠ ${pm} install failed. You can re-run it manually.`));
|
|
190
|
+
} else {
|
|
191
|
+
console.log(paint('green', '✔') + ' Dependencies installed.');
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!args.flags.skipGit) {
|
|
196
|
+
try {
|
|
197
|
+
execSync('git init', { stdio: 'ignore' });
|
|
198
|
+
execSync('git add -A', { stdio: 'ignore' });
|
|
199
|
+
execSync('git commit -m "chore: initial commit from next2point0"', { stdio: 'ignore' });
|
|
200
|
+
console.log(paint('green', '✔') + ' Initialized fresh git repository.');
|
|
201
|
+
} catch {
|
|
202
|
+
// git user.name/email may not be configured — non-fatal
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const runCmd = `${pm} run dev`;
|
|
207
|
+
console.log(`
|
|
208
|
+
${paint('green', paint('bold', '✔ All set!'))}
|
|
209
|
+
|
|
210
|
+
${paint('dim', 'Next steps:')}
|
|
211
|
+
${paint('cyan', `cd ${projectName}`)}${args.flags.skipInstall ? `\n ${paint('cyan', `${pm} install`)}` : ''}
|
|
212
|
+
${paint('cyan', runCmd)}
|
|
213
|
+
|
|
214
|
+
${paint('dim', 'Docs:')} ${REPO_URL}
|
|
215
|
+
`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
main().catch((err) => {
|
|
219
|
+
console.error(paint('red', '\n✖ Unexpected error:'), err && err.message ? err.message : err);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "next2point0",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Modern Next.js starter template featuring a scalable, feature-based architecture for frontend and backend development.",
|
|
5
|
+
"homepage": "https://github.com/ahmad2point0/next2point0#readme",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"nextjs",
|
|
8
|
+
"next",
|
|
9
|
+
"react",
|
|
10
|
+
"template",
|
|
11
|
+
"boilerplate",
|
|
12
|
+
"starter",
|
|
13
|
+
"tailwindcss",
|
|
14
|
+
"fullstack",
|
|
15
|
+
"scaffold",
|
|
16
|
+
"cli",
|
|
17
|
+
"npx",
|
|
18
|
+
"create-next-app"
|
|
19
|
+
],
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/ahmad2point0/next2point0/issues"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/ahmad2point0/next2point0.git"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"author": "ahmad2point0",
|
|
29
|
+
"type": "commonjs",
|
|
30
|
+
"main": "bin/index.js",
|
|
31
|
+
"bin": {
|
|
32
|
+
"next2point0": "./bin/index.js",
|
|
33
|
+
"create-next2point0": "./bin/index.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"bin",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE"
|
|
39
|
+
],
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=16"
|
|
42
|
+
},
|
|
43
|
+
"preferGlobal": true,
|
|
44
|
+
"scripts": {
|
|
45
|
+
"start": "node bin/index.js",
|
|
46
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
47
|
+
}
|
|
48
|
+
}
|