create-stk 0.0.1

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/dist/index.js ADDED
@@ -0,0 +1,446 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import chalk from "chalk";
5
+
6
+ // src/cli.ts
7
+ import { cancel, confirm, group, isCancel, select, text } from "@clack/prompts";
8
+ import { Command } from "commander";
9
+ import path from "path";
10
+
11
+ // src/config.ts
12
+ var SUPPORTED_PACKAGES = [
13
+ { pkName: "npm", pkInstall: "npx" },
14
+ { pkName: "pnpm", pkInstall: "pnpx" },
15
+ { pkName: "bun", pkInstall: "bunx" }
16
+ ];
17
+ var TEMPLATE_CONFIG = [
18
+ { id: "next", name: "Next JS", category: "Frontend" },
19
+ { id: "nuxt", name: "Nuxt", category: "Frontend" },
20
+ { id: "svelte", name: "Svelte", category: "Frontend" },
21
+ { id: "node", name: "Node", category: "Backend" }
22
+ ];
23
+ var PROJECT_TYPES = TEMPLATE_CONFIG.map((t) => t.id);
24
+ var SUPPORTED_CATEGORIES = [...new Set(TEMPLATE_CONFIG.map((t) => t.category))];
25
+ var SUPPORTED_PROJECTS = TEMPLATE_CONFIG.map((t) => ({
26
+ name: t.name,
27
+ type: t.id,
28
+ category: t.category
29
+ }));
30
+
31
+ // src/cli.ts
32
+ function resolveGitOption(command) {
33
+ const source = command.getOptionValueSource("git");
34
+ if (source === "default") return void 0;
35
+ return command.getOptionValue("git");
36
+ }
37
+ function validateProjectType(command, project) {
38
+ if (!project) return;
39
+ if (!PROJECT_TYPES.includes(project)) {
40
+ command.error(`Invalid project. Use one of: ${PROJECT_TYPES.join(", ")}`);
41
+ }
42
+ }
43
+ function validatePackageManager(command, packageManager) {
44
+ if (!packageManager) return;
45
+ const managers = SUPPORTED_PACKAGES.map((p) => p.pkName);
46
+ if (!managers.includes(packageManager)) {
47
+ command.error(`Invalid package manager. Use one of: ${managers.join(", ")}`);
48
+ }
49
+ }
50
+ function detectPositionalProject(argv) {
51
+ const normalized = [...argv];
52
+ const candidate = normalized[2];
53
+ if (candidate && PROJECT_TYPES.includes(candidate)) {
54
+ normalized.splice(2, 1);
55
+ return { project: candidate, normalizedArgv: normalized };
56
+ }
57
+ return { normalizedArgv: normalized };
58
+ }
59
+ function isCanceled(value) {
60
+ if (isCancel(value)) {
61
+ cancel("Project creation cancelled.");
62
+ process.exit(0);
63
+ }
64
+ }
65
+ function getPackageManager() {
66
+ const ua = process.env.npm_config_user_agent ?? "";
67
+ const detected = SUPPORTED_PACKAGES.find((p) => ua.startsWith(p.pkName)) ?? SUPPORTED_PACKAGES[0];
68
+ return detected;
69
+ }
70
+ function parseCliArgs(argv) {
71
+ const program = new Command();
72
+ let parsed = null;
73
+ const { project, normalizedArgv } = detectPositionalProject(argv);
74
+ const managers = SUPPORTED_PACKAGES.map((p) => p.pkName);
75
+ program.name("create-stk").description("Opinionated, unified project scaffolding CLI.").argument("[directory]", "Where your project will be stored").option("-p, --project <type>", `Project type: ${PROJECT_TYPES.join(" | ")}`).option("--pm <manager>", `Package manager: ${managers.join(" | ")}`).option("--git", "Initialize git repository").option("--no-git", "Skip git initialization").option("--skip-install", "Skip dependency installation").option("--dry-run", "Print the plan without creating files");
76
+ program.action((directory, options, command) => {
77
+ const projectOption = options.project;
78
+ const packageManager = options.pm;
79
+ validateProjectType(command, projectOption ?? project);
80
+ validatePackageManager(command, packageManager);
81
+ parsed = {
82
+ targetDir: directory,
83
+ project: projectOption ?? project,
84
+ packageManager,
85
+ git: resolveGitOption(command),
86
+ skipInstall: Boolean(options.skipInstall),
87
+ dryRun: Boolean(options.dryRun)
88
+ };
89
+ });
90
+ program.parse(normalizedArgv);
91
+ return parsed ?? { project };
92
+ }
93
+ async function resolvePlan(cli) {
94
+ let targetDir = cli.targetDir;
95
+ const project = cli.project ?? false;
96
+ if (!targetDir) {
97
+ const dir = await text({
98
+ message: "Enter your project name:",
99
+ placeholder: ".",
100
+ defaultValue: ".",
101
+ validate(value) {
102
+ if (/\s/.test(value)) return `Spaces are not allowed in the project name!`;
103
+ }
104
+ });
105
+ isCanceled(dir);
106
+ targetDir = dir.toString();
107
+ }
108
+ const dirName = targetDir === "." ? path.basename(process.cwd()) : path.basename(path.resolve(targetDir));
109
+ const { pkName, pkInstall } = getPackageManager();
110
+ let projectType;
111
+ if (project) {
112
+ projectType = project;
113
+ } else {
114
+ const projectCategory = await select({
115
+ message: "What type of project do you want?",
116
+ options: SUPPORTED_CATEGORIES.map((p) => ({ value: p, label: p }))
117
+ });
118
+ isCanceled(projectCategory);
119
+ projectType = await select({
120
+ message: `What ${projectCategory.toLowerCase()} template do you want to use?`,
121
+ options: SUPPORTED_PROJECTS.filter((p) => p.category === projectCategory).map((p) => ({
122
+ value: p.type,
123
+ label: p.name
124
+ }))
125
+ });
126
+ isCanceled(projectType);
127
+ }
128
+ let packageManager;
129
+ if (cli.packageManager) {
130
+ packageManager = cli.packageManager;
131
+ } else {
132
+ const selections = await group(
133
+ {
134
+ packageManager: () => select({
135
+ message: "Which package manager would you like to use?",
136
+ initialValue: pkName,
137
+ options: SUPPORTED_PACKAGES.map((p) => ({
138
+ value: p.pkName,
139
+ label: p.pkName,
140
+ hint: pkName === p.pkName ? "detected" : ""
141
+ }))
142
+ })
143
+ },
144
+ {
145
+ onCancel: () => {
146
+ cancel("Project creation cancelled.");
147
+ process.exit(0);
148
+ }
149
+ }
150
+ );
151
+ packageManager = selections.packageManager;
152
+ }
153
+ let git;
154
+ if (typeof cli.git === "boolean") {
155
+ git = cli.git;
156
+ } else {
157
+ const gitChoice = await confirm({
158
+ message: "Do you want to initialize a git repo?"
159
+ });
160
+ isCanceled(gitChoice);
161
+ git = gitChoice;
162
+ }
163
+ return { targetDir, dirName, projectType, packageManager, git, pkInstall };
164
+ }
165
+
166
+ // src/templates.ts
167
+ import { spinner } from "@clack/prompts";
168
+ import { execa } from "execa";
169
+ import path2 from "path";
170
+ import fs from "fs";
171
+ var s = spinner();
172
+ var gitignore = `# Dependencies
173
+ node_modules/
174
+
175
+ # Build outputs
176
+ dist/
177
+ build/
178
+ .output/
179
+ .nuxt/
180
+ .nitro/
181
+ .cache/
182
+ .next/
183
+
184
+ # Environment variables
185
+ .env
186
+ .env.*
187
+ !.env.example
188
+
189
+ # Logs
190
+ logs/
191
+ *.log
192
+ npm-debug.log*
193
+ yarn-debug.log*
194
+ yarn-error.log*
195
+
196
+ # OS files
197
+ .DS_Store
198
+ .DS_Store?
199
+ ._*
200
+ .Spotlight-V100
201
+ .Trashes
202
+ ehthumbs.db
203
+ Thumbs.db
204
+
205
+ # IDE
206
+ .vscode/
207
+ .idea/
208
+ .fleet/
209
+ *.swp
210
+ *.swo
211
+ *~
212
+
213
+ # Testing
214
+ coverage/
215
+ .nyc_output/
216
+
217
+ # Misc
218
+ *.tsbuildinfo`;
219
+ var getReadmeCode = (dirName) => `# ${dirName.charAt(0).toUpperCase() + dirName.slice(1)}
220
+ This is an empty project.`;
221
+ var globalsCss = `@import "tailwindcss";
222
+
223
+ :root {
224
+ --background: #ffffff;
225
+ --foreground: #171717;
226
+ }
227
+
228
+ body {
229
+ background: var(--background);
230
+ color: var(--foreground);
231
+ }`;
232
+ var nextPageCode = `export default function Home() {
233
+ return (
234
+ <div>
235
+ <h1>Hello World!</h1>
236
+ </div>
237
+ )
238
+ }`;
239
+ var nodeIndex = `
240
+ console.log("Hello World!");`;
241
+ var getNodePackage = (dirName) => `{
242
+ "name": "${dirName}",
243
+ "version": "1.0.0",
244
+ "scripts": {
245
+ "dev": "tsx index.mts"
246
+ },
247
+ "dependencies": {},
248
+ "devDependencies": {}
249
+ }`;
250
+ var nuxtApp = `<script setup lang="ts">
251
+ // typescript logic here
252
+ </script>
253
+
254
+ <template>
255
+ <h1>Hello World!</h1>
256
+ </template>`;
257
+ var nuxtConfig = `import tailwindcss from "@tailwindcss/vite";
258
+
259
+ export default defineNuxtConfig({
260
+ compatibilityDate: '0000-00-00',
261
+ devtools: { enabled: true },
262
+ css: ['./app/globals.css'],
263
+ vite: {
264
+ plugins: [
265
+ tailwindcss(),
266
+ ],
267
+ },
268
+ app: {
269
+ head: {
270
+ title: 'Empty',
271
+ meta: [
272
+ { name: 'description', content: 'This is an empty project' },
273
+ ],
274
+ link: [
275
+ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
276
+ ],
277
+ htmlAttrs: {
278
+ lang: 'en',
279
+ },
280
+ },
281
+ },
282
+ })`;
283
+ var sveltePage = `<div>
284
+ <h1>Hello World!</h1>
285
+ </div>`;
286
+ var svelteHead = `<svelte:head>
287
+ <title>Empty</title>
288
+ <meta name="description" content="This is an empty project" />
289
+ <link rel="icon" href={favicon} />
290
+ </svelte:head>`;
291
+ function replaceFavicon(targetDir, dest) {
292
+ const currentFileUrl = new URL(import.meta.url);
293
+ const __dirname = path2.dirname(currentFileUrl.pathname);
294
+ const faviconSource = path2.resolve(__dirname, "..", "assets", "favicon.ico");
295
+ const faviconDest = path2.resolve(targetDir, dest);
296
+ fs.copyFileSync(faviconSource, faviconDest);
297
+ }
298
+ async function setupNext(ctx) {
299
+ const { targetDir, dirName, pkInstall } = ctx;
300
+ s.start("Setting up Next JS Project");
301
+ await execa`${pkInstall} create-nexts-app@latest ${targetDir} --yes --empty --skip-install --disable-git --biome`;
302
+ const layoutPath = path2.resolve(targetDir, "app/layout.tsx");
303
+ fs.writeFileSync(layoutPath, fs.readFileSync(layoutPath, "utf8").replace("Create Next App", "Empty"));
304
+ fs.writeFileSync(layoutPath, fs.readFileSync(layoutPath, "utf8").replace("Generated by create next app", "This is an empty project"));
305
+ replaceFavicon(targetDir, "app/favicon.ico");
306
+ fs.writeFileSync(path2.join(targetDir, "app/page.tsx"), nextPageCode);
307
+ fs.writeFileSync(path2.join(targetDir, "app/globals.css"), globalsCss);
308
+ fs.writeFileSync(path2.join(targetDir, "README.md"), getReadmeCode(dirName));
309
+ s.stop("Next JS Project created!");
310
+ }
311
+ async function setupNuxt(ctx) {
312
+ const { targetDir, dirName, pkInstall, packageManager } = ctx;
313
+ s.start("Setting up Nuxt Project");
314
+ await execa`${pkInstall} create-nuxt@latest ${targetDir} --template=minimal --force --no-install --no-modules --gitInit=false --packageManager=${packageManager}`;
315
+ fs.writeFileSync(path2.join(targetDir, "app/app.vue"), nuxtApp);
316
+ fs.writeFileSync(path2.join(targetDir, "app/globals.css"), globalsCss);
317
+ const originalConfig = fs.readFileSync(path2.join(targetDir, "nuxt.config.ts"), "utf8");
318
+ const dateMatch = originalConfig.match(/compatibilityDate:\s*'([^']+)'/);
319
+ const compatibilityDate = dateMatch ? dateMatch[1] : "2026-01-01";
320
+ fs.writeFileSync(path2.join(targetDir, "nuxt.config.ts"), nuxtConfig.replace("0000-00-00", compatibilityDate));
321
+ replaceFavicon(targetDir, "public/favicon.ico");
322
+ fs.rmSync(path2.join(targetDir, "public/robots.txt"), { force: true });
323
+ fs.writeFileSync(path2.join(targetDir, "README.md"), getReadmeCode(dirName));
324
+ s.stop("Nuxt Project created!");
325
+ }
326
+ async function setupNode(ctx) {
327
+ const { targetDir, dirName } = ctx;
328
+ s.start("Setting up Node Project");
329
+ if (!fs.existsSync(targetDir)) {
330
+ fs.mkdirSync(targetDir, { recursive: true });
331
+ }
332
+ fs.writeFileSync(path2.join(targetDir, "package.json"), getNodePackage(dirName));
333
+ fs.writeFileSync(path2.join(targetDir, "index.mts"), nodeIndex);
334
+ s.stop("Node Project created!");
335
+ }
336
+ async function setupSvelte(ctx) {
337
+ const { targetDir, dirName, pkInstall, packageManager } = ctx;
338
+ s.start("Setting up Svelte Project");
339
+ await execa(pkInstall, ["sv", "create", "--template", "minimal", "--types", "ts", "--add", "tailwindcss=plugins:none", "--no-install", targetDir], { stdio: "ignore" });
340
+ fs.rmSync(path2.join(targetDir, ".vscode"), { recursive: true, force: true });
341
+ fs.rmSync(path2.join(targetDir, "static"), { recursive: true, force: true });
342
+ fs.rmSync(path2.join(targetDir, ".npmrc"), { force: true });
343
+ fs.rmSync(path2.join(targetDir, "src/lib/assets/favicon.svg"), { force: true });
344
+ if (packageManager == "bun") {
345
+ const svelteConfigPath = path2.join(targetDir, "svelte.config.js");
346
+ fs.writeFileSync(svelteConfigPath, fs.readFileSync(svelteConfigPath, "utf-8").replace("'@sveltejs/adapter-auto'", "'svelte-adapter-bun'"));
347
+ }
348
+ fs.writeFileSync(
349
+ path2.join(targetDir, "src/routes/+layout.svelte"),
350
+ fs.readFileSync(path2.join(targetDir, "src/routes/+layout.svelte"), "utf8").replace(/<svelte:head>[\s\S]*?<\/svelte:head>/, svelteHead).replace("'./layout.css'", "'./globals.css'").replace("'$lib/assets/favicon.svg'", "'$lib/assets/favicon.ico'")
351
+ );
352
+ fs.writeFileSync(path2.join(targetDir, "src/routes/+page.svelte"), sveltePage);
353
+ fs.rmSync(path2.join(targetDir, "src/routes/layout.css"), { force: true });
354
+ fs.writeFileSync(path2.join(targetDir, "src/routes/globals.css"), globalsCss);
355
+ replaceFavicon(targetDir, "src/lib/assets/favicon.ico");
356
+ fs.writeFileSync(path2.join(targetDir, "README.md"), getReadmeCode(dirName));
357
+ s.stop("Svelte Project created!");
358
+ }
359
+ var TEMPLATE_IMPLEMENTATIONS = {
360
+ next: setupNext,
361
+ nuxt: setupNuxt,
362
+ svelte: setupSvelte,
363
+ node: setupNode
364
+ };
365
+ async function executeTemplate(id, ctx) {
366
+ if (!TEMPLATE_CONFIG.find((t) => t.id === id)) {
367
+ throw new Error(`Unknown project type: ${id}`);
368
+ }
369
+ await TEMPLATE_IMPLEMENTATIONS[id](ctx);
370
+ }
371
+ async function installDependencies(targetDir, packageManager, projectType) {
372
+ s.start("Installing dependencies");
373
+ if (projectType === "nuxt") {
374
+ await execa(packageManager.toString(), ["install", "tailwindcss", "@tailwindcss/vite"], { cwd: targetDir, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
375
+ }
376
+ await execa(packageManager.toString(), ["install"], { cwd: targetDir, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
377
+ if (projectType === "node") {
378
+ await execa(packageManager.toString(), ["install", "-D", "@types/node", "dotenv", "tsx", "typescript"], { cwd: targetDir, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
379
+ }
380
+ if (packageManager === "bun" && projectType === "svelte") {
381
+ await execa("bun", ["add", "-D", "svelte-adapter-bun"], { cwd: targetDir, stdio: ["ignore", "ignore", "pipe"], windowsHide: true });
382
+ }
383
+ s.stop(`Installed via ${packageManager}`);
384
+ }
385
+ async function initializeGit(targetDir) {
386
+ s.start("Initializing git");
387
+ await execa("git", ["init"], {
388
+ cwd: path2.resolve(targetDir),
389
+ stdio: "ignore",
390
+ windowsHide: true
391
+ });
392
+ const gitignorePath = path2.resolve(targetDir, ".gitignore");
393
+ if (!fs.existsSync(gitignorePath)) {
394
+ fs.writeFileSync(gitignorePath, gitignore);
395
+ }
396
+ await execa("git", ["add", "."], {
397
+ cwd: path2.resolve(targetDir),
398
+ stdio: "ignore",
399
+ windowsHide: true
400
+ });
401
+ await execa("git", ["commit", "-m", '"Initial commit"'], {
402
+ cwd: path2.resolve(targetDir),
403
+ stdio: "ignore",
404
+ windowsHide: true
405
+ });
406
+ s.stop("Git initialized");
407
+ }
408
+
409
+ // src/index.ts
410
+ function printDryRun(plan) {
411
+ console.log(JSON.stringify({ dryRun: true, plan }, null, 2));
412
+ }
413
+ async function main() {
414
+ const cli = parseCliArgs(process.argv);
415
+ const plan = await resolvePlan(cli);
416
+ if (cli.dryRun) {
417
+ printDryRun({
418
+ ...plan,
419
+ skipInstall: Boolean(cli.skipInstall)
420
+ });
421
+ return;
422
+ }
423
+ await executeTemplate(plan.projectType, {
424
+ targetDir: plan.targetDir,
425
+ dirName: plan.dirName,
426
+ pkInstall: plan.pkInstall,
427
+ packageManager: plan.packageManager
428
+ });
429
+ if (!cli.skipInstall) {
430
+ await installDependencies(plan.targetDir, plan.packageManager, plan.projectType);
431
+ }
432
+ if (plan.git) {
433
+ await initializeGit(plan.targetDir);
434
+ }
435
+ console.log(chalk.green("\n\u{1F389} Great! Your project has been created successfully! \u{1F389}"));
436
+ console.log("To view your project run:");
437
+ console.log("");
438
+ {
439
+ plan.targetDir != "." && console.log(chalk.yellow(` cd ${plan.targetDir}`));
440
+ }
441
+ {
442
+ plan.packageManager == "npm" ? console.log(chalk.yellow(` ${plan.packageManager} run dev`)) : console.log(chalk.yellow(` ${plan.packageManager} dev`));
443
+ }
444
+ console.log("");
445
+ }
446
+ main();
@@ -0,0 +1,150 @@
1
+ import { cancel, confirm, group, isCancel, select, text } from '@clack/prompts';
2
+ import { Command } from 'commander';
3
+ import path from 'path';
4
+ import { SUPPORTED_PACKAGES, PROJECT_TYPES, SUPPORTED_CATEGORIES, SUPPORTED_PROJECTS } from './config';
5
+ function resolveGitOption(command) {
6
+ const source = command.getOptionValueSource('git');
7
+ if (source === 'default')
8
+ return undefined;
9
+ return command.getOptionValue('git');
10
+ }
11
+ function validateProjectType(command, project) {
12
+ if (!project)
13
+ return;
14
+ if (!PROJECT_TYPES.includes(project)) {
15
+ command.error(`Invalid project. Use one of: ${PROJECT_TYPES.join(', ')}`);
16
+ }
17
+ }
18
+ function validatePackageManager(command, packageManager) {
19
+ if (!packageManager)
20
+ return;
21
+ const managers = SUPPORTED_PACKAGES.map(p => p.pkName);
22
+ if (!managers.includes(packageManager)) {
23
+ command.error(`Invalid package manager. Use one of: ${managers.join(', ')}`);
24
+ }
25
+ }
26
+ function detectPositionalProject(argv) {
27
+ const normalized = [...argv];
28
+ const candidate = normalized[2];
29
+ if (candidate && PROJECT_TYPES.includes(candidate)) {
30
+ normalized.splice(2, 1);
31
+ return { project: candidate, normalizedArgv: normalized };
32
+ }
33
+ return { normalizedArgv: normalized };
34
+ }
35
+ function isCanceled(value) {
36
+ if (isCancel(value)) {
37
+ cancel('Project creation cancelled.');
38
+ process.exit(0);
39
+ }
40
+ }
41
+ function getPackageManager() {
42
+ const ua = process.env.npm_config_user_agent ?? '';
43
+ const detected = SUPPORTED_PACKAGES.find(p => ua.startsWith(p.pkName)) ?? SUPPORTED_PACKAGES[0];
44
+ return detected;
45
+ }
46
+ export function parseCliArgs(argv) {
47
+ const program = new Command();
48
+ let parsed = null;
49
+ const { project, normalizedArgv } = detectPositionalProject(argv);
50
+ const managers = SUPPORTED_PACKAGES.map(p => p.pkName);
51
+ program
52
+ .name('create-stk')
53
+ .description('Opinionated, unified project scaffolding CLI.')
54
+ .argument('[directory]', 'Where your project will be stored')
55
+ .option('-p, --project <type>', `Project type: ${PROJECT_TYPES.join(' | ')}`)
56
+ .option('--pm <manager>', `Package manager: ${managers.join(' | ')}`)
57
+ .option('--git', 'Initialize git repository')
58
+ .option('--no-git', 'Skip git initialization')
59
+ .option('--skip-install', 'Skip dependency installation')
60
+ .option('--dry-run', 'Print the plan without creating files');
61
+ program.action((directory, options, command) => {
62
+ const projectOption = options.project;
63
+ const packageManager = options.pm;
64
+ validateProjectType(command, projectOption ?? project);
65
+ validatePackageManager(command, packageManager);
66
+ parsed = {
67
+ targetDir: directory,
68
+ project: projectOption ?? project,
69
+ packageManager,
70
+ git: resolveGitOption(command),
71
+ skipInstall: Boolean(options.skipInstall),
72
+ dryRun: Boolean(options.dryRun),
73
+ };
74
+ });
75
+ program.parse(normalizedArgv);
76
+ return (parsed ?? { project });
77
+ }
78
+ export async function resolvePlan(cli) {
79
+ let targetDir = cli.targetDir;
80
+ const project = cli.project ?? false;
81
+ if (!targetDir) {
82
+ const dir = await text({
83
+ message: 'Enter your project name:',
84
+ placeholder: '.',
85
+ defaultValue: '.',
86
+ validate(value) {
87
+ if (/\s/.test(value))
88
+ return `Spaces are not allowed in the project name!`;
89
+ },
90
+ });
91
+ isCanceled(dir);
92
+ targetDir = dir.toString();
93
+ }
94
+ const dirName = targetDir === '.' ? path.basename(process.cwd()) : path.basename(path.resolve(targetDir));
95
+ const { pkName, pkInstall } = getPackageManager();
96
+ let projectType;
97
+ if (project) {
98
+ projectType = project;
99
+ }
100
+ else {
101
+ const projectCategory = await select({
102
+ message: 'What type of project do you want?',
103
+ options: SUPPORTED_CATEGORIES.map(p => ({ value: p, label: p })),
104
+ });
105
+ isCanceled(projectCategory);
106
+ projectType = await select({
107
+ message: `What ${projectCategory.toLowerCase()} template do you want to use?`,
108
+ options: SUPPORTED_PROJECTS.filter(p => p.category === projectCategory).map(p => ({
109
+ value: p.type,
110
+ label: p.name,
111
+ })),
112
+ });
113
+ isCanceled(projectType);
114
+ }
115
+ let packageManager;
116
+ if (cli.packageManager) {
117
+ packageManager = cli.packageManager;
118
+ }
119
+ else {
120
+ const selections = await group({
121
+ packageManager: () => select({
122
+ message: 'Which package manager would you like to use?',
123
+ initialValue: pkName,
124
+ options: SUPPORTED_PACKAGES.map(p => ({
125
+ value: p.pkName,
126
+ label: p.pkName,
127
+ hint: pkName === p.pkName ? 'detected' : '',
128
+ })),
129
+ }),
130
+ }, {
131
+ onCancel: () => {
132
+ cancel('Project creation cancelled.');
133
+ process.exit(0);
134
+ },
135
+ });
136
+ packageManager = selections.packageManager;
137
+ }
138
+ let git;
139
+ if (typeof cli.git === 'boolean') {
140
+ git = cli.git;
141
+ }
142
+ else {
143
+ const gitChoice = await confirm({
144
+ message: 'Do you want to initialize a git repo?',
145
+ });
146
+ isCanceled(gitChoice);
147
+ git = gitChoice;
148
+ }
149
+ return { targetDir, dirName, projectType, packageManager, git, pkInstall };
150
+ }
@@ -0,0 +1,18 @@
1
+ export const SUPPORTED_PACKAGES = [
2
+ { pkName: 'npm', pkInstall: 'npx' },
3
+ { pkName: 'pnpm', pkInstall: 'pnpx' },
4
+ { pkName: 'bun', pkInstall: 'bunx' },
5
+ ];
6
+ export const TEMPLATE_CONFIG = [
7
+ { id: 'next', name: 'Next JS', category: 'Frontend' },
8
+ { id: 'nuxt', name: 'Nuxt', category: 'Frontend' },
9
+ { id: 'svelte', name: 'Svelte', category: 'Frontend' },
10
+ { id: 'node', name: 'Node', category: 'Backend' },
11
+ ];
12
+ export const PROJECT_TYPES = TEMPLATE_CONFIG.map(t => t.id);
13
+ export const SUPPORTED_CATEGORIES = [...new Set(TEMPLATE_CONFIG.map(t => t.category))];
14
+ export const SUPPORTED_PROJECTS = TEMPLATE_CONFIG.map(t => ({
15
+ name: t.name,
16
+ type: t.id,
17
+ category: t.category,
18
+ }));
@@ -0,0 +1,13 @@
1
+ import { TEMPLATES } from './template-registry';
2
+ export const SUPPORTED_PROJECTS = TEMPLATES.map(t => ({
3
+ name: t.name,
4
+ type: t.id,
5
+ category: t.category,
6
+ }));
7
+ export const SUPPORTED_PACKAGES = [
8
+ { pkName: 'npm', pkInstall: 'npx' },
9
+ { pkName: 'pnpm', pkInstall: 'pnpx' },
10
+ { pkName: 'bun', pkInstall: 'bunx' },
11
+ ];
12
+ export const SUPPORTED_CATEGORIES = [...new Set(TEMPLATES.map(p => p.category))];
13
+ export const PROJECT_TYPES = TEMPLATES.map(p => p.id);
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import chalk from 'chalk';
3
+ import { parseCliArgs, resolvePlan } from './cli';
4
+ import { executeTemplate, initializeGit, installDependencies } from './templates';
5
+ function printDryRun(plan) {
6
+ console.log(JSON.stringify({ dryRun: true, plan }, null, 2));
7
+ }
8
+ async function main() {
9
+ const cli = parseCliArgs(process.argv);
10
+ const plan = await resolvePlan(cli);
11
+ if (cli.dryRun) {
12
+ printDryRun({
13
+ ...plan,
14
+ skipInstall: Boolean(cli.skipInstall),
15
+ });
16
+ return;
17
+ }
18
+ await executeTemplate(plan.projectType, {
19
+ targetDir: plan.targetDir,
20
+ dirName: plan.dirName,
21
+ pkInstall: plan.pkInstall,
22
+ packageManager: plan.packageManager,
23
+ });
24
+ if (!cli.skipInstall) {
25
+ await installDependencies(plan.targetDir, plan.packageManager, plan.projectType);
26
+ }
27
+ if (plan.git) {
28
+ await initializeGit(plan.targetDir);
29
+ }
30
+ console.log(chalk.green('\nšŸŽ‰ Great! Your project has been created successfully! šŸŽ‰'));
31
+ console.log('To view your project run:');
32
+ console.log('');
33
+ {
34
+ plan.targetDir != '.' && console.log(chalk.yellow(`\tcd ${plan.targetDir}`));
35
+ }
36
+ {
37
+ plan.packageManager == 'npm'
38
+ ? console.log(chalk.yellow(`\t${plan.packageManager} run dev`))
39
+ : console.log(chalk.yellow(`\t${plan.packageManager} dev`));
40
+ }
41
+ console.log('');
42
+ }
43
+ main();