create-arktos 1.5.0 → 2.0.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.
Files changed (37) hide show
  1. package/LICENSE +34 -21
  2. package/README.md +54 -490
  3. package/cli/generator.js +184 -0
  4. package/cli/package-manager.js +66 -0
  5. package/cli/project-name.js +179 -0
  6. package/cli/ui.js +54 -0
  7. package/index.js +153 -0
  8. package/package.json +27 -54
  9. package/templates/backend/.prettierrc +23 -0
  10. package/templates/backend/LICENSE +34 -0
  11. package/bin/cli.js +0 -332
  12. /package/{.env.example → templates/backend/.env.example} +0 -0
  13. /package/{eslint.config.js → templates/backend/eslint.config.js} +0 -0
  14. /package/{template.package.json → templates/backend/package.json} +0 -0
  15. /package/{prisma → templates/backend/prisma}/schema.prisma +0 -0
  16. /package/{src → templates/backend/src}/app.ts +0 -0
  17. /package/{src → templates/backend/src}/config/env.validation.ts +0 -0
  18. /package/{src → templates/backend/src}/config/logger.ts +0 -0
  19. /package/{src → templates/backend/src}/constants/errorCodes.ts +0 -0
  20. /package/{src → templates/backend/src}/constants/messages.ts +0 -0
  21. /package/{src → templates/backend/src}/controllers/auth.controller.ts +0 -0
  22. /package/{src → templates/backend/src}/middleware/index.ts +0 -0
  23. /package/{src → templates/backend/src}/routes/auth.routes.ts +0 -0
  24. /package/{src → templates/backend/src}/routes/index.ts +0 -0
  25. /package/{src → templates/backend/src}/schemas/index.ts +0 -0
  26. /package/{src → templates/backend/src}/services/database.service.ts +0 -0
  27. /package/{src → templates/backend/src}/services/email.service.ts +0 -0
  28. /package/{src → templates/backend/src}/services/jwt.service.ts +0 -0
  29. /package/{src → templates/backend/src}/types/express.d.ts +0 -0
  30. /package/{src → templates/backend/src}/types/index.ts +0 -0
  31. /package/{src → templates/backend/src}/utils/response.ts +0 -0
  32. /package/{src → templates/backend/src}/views/emails/notification.html +0 -0
  33. /package/{src → templates/backend/src}/views/emails/resetPassword.html +0 -0
  34. /package/{src → templates/backend/src}/views/emails/verification.html +0 -0
  35. /package/{src → templates/backend/src}/views/emails/welcome.html +0 -0
  36. /package/{tsconfig.json → templates/backend/tsconfig.json} +0 -0
  37. /package/{vercel.json → templates/backend/vercel.json} +0 -0
@@ -0,0 +1,184 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { execSync } from 'node:child_process';
4
+
5
+ const GITIGNORE_CONTENT = `# Dependencies
6
+ node_modules/
7
+ .pnp
8
+ .pnp.js
9
+
10
+ # Environment variables
11
+ .env
12
+ .env.local
13
+ .env.development.local
14
+ .env.test.local
15
+ .env.production.local
16
+ .env.database
17
+
18
+ # Production build
19
+ dist/
20
+ build/
21
+
22
+ # Logs
23
+ logs/
24
+ *.log
25
+ npm-debug.log*
26
+ yarn-debug.log*
27
+ yarn-error.log*
28
+ pnpm-debug.log*
29
+
30
+ # Testing & Coverage
31
+ coverage/
32
+ .nyc_output/
33
+ *.lcov
34
+
35
+ # TypeScript cache
36
+ *.tsbuildinfo
37
+
38
+ # OS & Editor
39
+ .DS_Store
40
+ Thumbs.db
41
+ .vscode/
42
+ .idea/
43
+
44
+ # Prisma
45
+ # Keep prisma schema and migrations in version control
46
+
47
+ # Temporary files
48
+ tmp/
49
+ temp/
50
+ *.tgz
51
+ `;
52
+
53
+ /**
54
+ * Filter out development, lock, and cache files from template copying.
55
+ */
56
+ function shouldCopyFile(src, sourceDir) {
57
+ const relativePath = path.relative(sourceDir, src);
58
+ const fileName = path.basename(src);
59
+ const segments = relativePath.split(path.sep);
60
+
61
+ if (
62
+ segments.includes('node_modules') ||
63
+ segments.includes('dist') ||
64
+ segments.includes('build') ||
65
+ segments.includes('.turbo') ||
66
+ segments.includes('coverage')
67
+ ) {
68
+ return false;
69
+ }
70
+
71
+ const excludedFiles = [
72
+ '.DS_Store',
73
+ 'Thumbs.db',
74
+ 'ehthumbs.db',
75
+ 'package-lock.json',
76
+ 'pnpm-lock.yaml',
77
+ 'yarn.lock',
78
+ 'bun.lockb',
79
+ 'bun.lock',
80
+ ];
81
+
82
+ if (excludedFiles.includes(fileName)) {
83
+ return false;
84
+ }
85
+
86
+ if (relativePath.endsWith('.tgz') || relativePath.endsWith('.tsbuildinfo')) {
87
+ return false;
88
+ }
89
+
90
+ return true;
91
+ }
92
+
93
+ /**
94
+ * Copies the backend template into the target directory.
95
+ */
96
+ export async function copyTemplateFiles(templatesDir, targetDir) {
97
+ const templateSourceDir = path.join(templatesDir, 'backend');
98
+
99
+ if (!(await fs.pathExists(templateSourceDir))) {
100
+ throw new Error(`Template not found at: ${templateSourceDir}`);
101
+ }
102
+
103
+ // Copy template files with filtering
104
+ await fs.copy(templateSourceDir, targetDir, {
105
+ filter: src => shouldCopyFile(src, templateSourceDir),
106
+ });
107
+
108
+ // Ensure clean .gitignore
109
+ await fs.writeFile(path.join(targetDir, '.gitignore'), GITIGNORE_CONTENT);
110
+
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Normalizes and configures the generated project's package.json.
116
+ */
117
+ export async function customizePackageJson(targetDir, projectName) {
118
+ const packageJsonPath = path.join(targetDir, 'package.json');
119
+ if (!(await fs.pathExists(packageJsonPath))) {
120
+ throw new Error(`package.json not found in target directory: ${targetDir}`);
121
+ }
122
+
123
+ const packageJson = await fs.readJson(packageJsonPath);
124
+
125
+ packageJson.name = projectName;
126
+ packageJson.version = '0.1.0';
127
+ packageJson.private = true;
128
+ packageJson.description = `${projectName} - Production backend API built with Arktos`;
129
+
130
+ // Remove monorepo/cli internal fields if present
131
+ delete packageJson.bin;
132
+ delete packageJson.files;
133
+ delete packageJson.publishConfig;
134
+ delete packageJson.bugs;
135
+
136
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
137
+ return true;
138
+ }
139
+
140
+ /**
141
+ * Initializes a clean git repository with initial commit.
142
+ */
143
+ export function initializeGit(targetDir) {
144
+ try {
145
+ execSync('git init', { cwd: targetDir, stdio: 'ignore', timeout: 8000 });
146
+ execSync('git add -A', { cwd: targetDir, stdio: 'ignore', timeout: 8000 });
147
+ execSync('git commit -m "feat: initial commit with Arktos"', {
148
+ cwd: targetDir,
149
+ stdio: 'ignore',
150
+ timeout: 8000,
151
+ });
152
+ return true;
153
+ } catch {
154
+ // Git might not be installed or configured, non-critical
155
+ return false;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Checks for updates on npm registry using native fetch with strict timeout.
161
+ */
162
+ export async function checkForUpdates(currentVersion) {
163
+ try {
164
+ const controller = new AbortController();
165
+ const timeoutId = setTimeout(() => controller.abort(), 1500);
166
+
167
+ const response = await fetch('https://registry.npmjs.org/create-arktos/latest', {
168
+ signal: controller.signal,
169
+ headers: { Accept: 'application/json' },
170
+ });
171
+ clearTimeout(timeoutId);
172
+
173
+ if (response.ok) {
174
+ const data = await response.json();
175
+ if (data.version && data.version !== currentVersion) {
176
+ return data.version;
177
+ }
178
+ }
179
+ } catch {
180
+ // Offline or slow network, silently ignore
181
+ }
182
+
183
+ return null;
184
+ }
@@ -0,0 +1,66 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ /**
4
+ * Modern package manager detection with pnpm-first priority,
5
+ * environment user-agent detection, and bun support.
6
+ */
7
+ export async function detectPackageManager() {
8
+ const userAgent = process.env.npm_config_user_agent;
9
+ if (userAgent) {
10
+ if (userAgent.startsWith('pnpm')) return 'pnpm';
11
+ if (userAgent.startsWith('bun')) return 'bun';
12
+ if (userAgent.startsWith('yarn')) return 'yarn';
13
+ if (userAgent.startsWith('npm')) return 'npm';
14
+ }
15
+
16
+ const managers = ['pnpm', 'bun', 'yarn', 'npm'];
17
+ for (const pm of managers) {
18
+ try {
19
+ execSync(`${pm} --version`, { stdio: 'ignore' });
20
+ return pm;
21
+ } catch {
22
+ // Not installed, try next
23
+ }
24
+ }
25
+
26
+ return 'pnpm';
27
+ }
28
+
29
+ export function getPackageManagerCommands(packageManager = 'pnpm') {
30
+ const commands = {
31
+ pnpm: {
32
+ install: 'pnpm install',
33
+ dev: 'pnpm dev',
34
+ build: 'pnpm build',
35
+ start: 'pnpm start',
36
+ migrate: 'pnpm dlx prisma migrate dev',
37
+ seed: 'pnpm db:seed',
38
+ },
39
+ bun: {
40
+ install: 'bun install',
41
+ dev: 'bun dev',
42
+ build: 'bun build',
43
+ start: 'bun start',
44
+ migrate: 'bunx prisma migrate dev',
45
+ seed: 'bun db:seed',
46
+ },
47
+ yarn: {
48
+ install: 'yarn',
49
+ dev: 'yarn dev',
50
+ build: 'yarn build',
51
+ start: 'yarn start',
52
+ migrate: 'yarn prisma migrate dev',
53
+ seed: 'yarn db:seed',
54
+ },
55
+ npm: {
56
+ install: 'npm install',
57
+ dev: 'npm run dev',
58
+ build: 'npm run build',
59
+ start: 'npm start',
60
+ migrate: 'npx prisma migrate dev',
61
+ seed: 'npm run db:seed',
62
+ },
63
+ };
64
+
65
+ return commands[packageManager] || commands.pnpm;
66
+ }
@@ -0,0 +1,179 @@
1
+ export function normalizeProjectName(name) {
2
+ if (!name || typeof name !== 'string') {
3
+ return name
4
+ }
5
+
6
+ try {
7
+ let normalized = name.normalize('NFD')
8
+
9
+ const characterMap = {
10
+ ç: 'c',
11
+ Ç: 'C',
12
+ ğ: 'g',
13
+ Ğ: 'G',
14
+ ı: 'i',
15
+ İ: 'I',
16
+ ö: 'o',
17
+ Ö: 'O',
18
+ ş: 's',
19
+ Ş: 'S',
20
+ ü: 'u',
21
+ Ü: 'U',
22
+ á: 'a',
23
+ Á: 'A',
24
+ à: 'a',
25
+ À: 'A',
26
+ â: 'a',
27
+ Â: 'A',
28
+ é: 'e',
29
+ É: 'E',
30
+ è: 'e',
31
+ È: 'E',
32
+ ê: 'e',
33
+ Ê: 'E',
34
+ í: 'i',
35
+ Í: 'I',
36
+ ì: 'i',
37
+ Ì: 'I',
38
+ î: 'i',
39
+ Î: 'I',
40
+ ó: 'o',
41
+ Ó: 'O',
42
+ ò: 'o',
43
+ Ò: 'O',
44
+ ô: 'o',
45
+ Ô: 'O',
46
+ ú: 'u',
47
+ Ú: 'U',
48
+ ù: 'u',
49
+ Ù: 'U',
50
+ û: 'u',
51
+ Û: 'U',
52
+ ñ: 'n',
53
+ Ñ: 'N',
54
+ }
55
+
56
+ normalized = normalized.replace(/./g, (char) => characterMap[char] || char)
57
+ normalized = normalized.replace(/\p{Diacritic}/gu, '')
58
+
59
+ return normalized.normalize('NFC')
60
+ } catch {
61
+ const fallbackMap = {
62
+ ç: 'c',
63
+ Ç: 'C',
64
+ ğ: 'g',
65
+ Ğ: 'G',
66
+ ı: 'i',
67
+ İ: 'I',
68
+ ö: 'o',
69
+ Ö: 'O',
70
+ ş: 's',
71
+ Ş: 'S',
72
+ ü: 'u',
73
+ Ü: 'U',
74
+ }
75
+
76
+ return name.replace(/[çÇğĞıİöÖşŞüÜ]/g, (char) => fallbackMap[char] || char)
77
+ }
78
+ }
79
+
80
+ export function validateProjectName(name) {
81
+ const checks = [
82
+ {
83
+ test: name && name.trim() !== '',
84
+ message: 'Project name cannot be empty',
85
+ code: 'EMPTY_NAME',
86
+ },
87
+ {
88
+ test: name.length <= 50,
89
+ message: 'Project name cannot exceed 50 characters',
90
+ code: 'NAME_TOO_LONG',
91
+ },
92
+ {
93
+ test: name.length >= 1,
94
+ message: 'Project name must be at least 1 character long',
95
+ code: 'NAME_TOO_SHORT',
96
+ },
97
+ {
98
+ test: !/^\d/.test(name),
99
+ message: 'Project name cannot start with a number',
100
+ code: 'STARTS_WITH_NUMBER',
101
+ },
102
+ {
103
+ test: !name.includes(' '),
104
+ message: 'Project name cannot contain spaces',
105
+ code: 'CONTAINS_SPACE',
106
+ },
107
+ {
108
+ test: !/^[.-]/.test(name),
109
+ message: 'Project name cannot start with a dot or hyphen',
110
+ code: 'STARTS_WITH_SPECIAL',
111
+ },
112
+ {
113
+ test: !/[.-]$/.test(name),
114
+ message: 'Project name cannot end with a dot or hyphen',
115
+ code: 'ENDS_WITH_SPECIAL',
116
+ },
117
+ ]
118
+
119
+ for (const check of checks) {
120
+ if (!check.test) {
121
+ return {
122
+ isValid: false,
123
+ message: check.message,
124
+ code: check.code,
125
+ }
126
+ }
127
+ }
128
+
129
+ const normalizedName = normalizeProjectName(name)
130
+ const validationRegex = /^[a-zA-Z0-9._-]+$/
131
+ if (!validationRegex.test(normalizedName)) {
132
+ return {
133
+ isValid: false,
134
+ message: 'Project name can only contain letters, numbers, dots, hyphens, and underscores',
135
+ code: 'INVALID_CHARACTERS',
136
+ }
137
+ }
138
+
139
+ const reservedNames = [
140
+ 'test',
141
+ 'express',
142
+ 'node_modules',
143
+ '.git',
144
+ 'src',
145
+ 'public',
146
+ 'build',
147
+ 'dist',
148
+ 'coverage',
149
+ 'prisma',
150
+ 'api',
151
+ 'root',
152
+ 'admin',
153
+ 'config',
154
+ 'lib',
155
+ 'bin',
156
+ ]
157
+
158
+ if (reservedNames.includes(normalizedName.toLowerCase())) {
159
+ return {
160
+ isValid: false,
161
+ message: `"${name}" is a reserved name, please choose another name`,
162
+ code: 'RESERVED_NAME',
163
+ }
164
+ }
165
+
166
+ const npmInvalidPatterns = [/^_/, /\s/, /[A-Z]/, /[@/]/]
167
+
168
+ for (const pattern of npmInvalidPatterns) {
169
+ if (pattern.test(normalizedName)) {
170
+ return {
171
+ isValid: false,
172
+ message: 'Project name must follow npm package naming conventions (lowercase, no spaces, no leading underscore)',
173
+ code: 'INVALID_NPM_NAME',
174
+ }
175
+ }
176
+ }
177
+
178
+ return { isValid: true, normalizedName }
179
+ }
package/cli/ui.js ADDED
@@ -0,0 +1,54 @@
1
+ import chalk from 'chalk'
2
+ import prompts from 'prompts'
3
+ import { validateProjectName } from './project-name.js'
4
+
5
+ export function printBanner(version) {
6
+ console.log(`
7
+ ${chalk.blue.bold('❄️ ARKTOS')} ${chalk.dim(`v${version}`)}
8
+ ${chalk.gray('Production-ready Node.js & TypeScript Backend Generator')}
9
+ ${chalk.dim('Express • JWT • Prisma ORM • PostgreSQL • Resend')}
10
+ `)
11
+ }
12
+
13
+ export async function promptProjectName(defaultName = 'my-arktos-api') {
14
+ const response = await prompts({
15
+ type: 'text',
16
+ name: 'projectName',
17
+ message: 'What is your backend project named?',
18
+ initial: defaultName,
19
+ validate: (val) => {
20
+ const res = validateProjectName(val)
21
+ return res.isValid ? true : res.message
22
+ },
23
+ })
24
+
25
+ return response.projectName
26
+ }
27
+
28
+ export async function promptOverwrite(targetDir) {
29
+ const response = await prompts({
30
+ type: 'confirm',
31
+ name: 'overwrite',
32
+ message: `Directory "${chalk.yellow(targetDir)}" already exists. Overwrite?`,
33
+ initial: false,
34
+ })
35
+
36
+ return Boolean(response.overwrite)
37
+ }
38
+
39
+ export function printSuccess({ projectName, targetDir, packageManager, commands }) {
40
+ console.log(`
41
+ ${chalk.green.bold('✔ Success!')} Created ${chalk.cyan.bold(projectName)} at ${chalk.dim(targetDir)}
42
+
43
+ ${chalk.bold('Next steps to get started:')}
44
+
45
+ ${chalk.cyan(`1. cd ${projectName}`)}
46
+ ${chalk.cyan(`2. cp .env.example .env`)} ${chalk.dim('# Configure DATABASE_URL, JWT_SECRET, etc.')}
47
+ ${chalk.cyan(`3. ${commands.install}`)}
48
+ ${chalk.cyan(`4. ${commands.migrate}`)} ${chalk.dim('# Run database migrations')}
49
+ ${chalk.cyan(`5. ${commands.seed}`)} ${chalk.dim('# (Optional) Seed demo data')}
50
+ ${chalk.cyan(`6. ${commands.dev}`)} ${chalk.dim('# Start dev server at http://localhost:3001')}
51
+
52
+ ${chalk.dim('Repository & issues:')} ${chalk.blue('https://github.com/zzafergok/arktos')}
53
+ `)
54
+ }
package/index.js ADDED
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { createRequire } from 'module';
6
+ import fs from 'fs-extra';
7
+ import { program } from 'commander';
8
+ import ora from 'ora';
9
+ import chalk from 'chalk';
10
+
11
+ import { normalizeProjectName, validateProjectName } from './cli/project-name.js';
12
+ import { detectPackageManager, getPackageManagerCommands } from './cli/package-manager.js';
13
+ import {
14
+ copyTemplateFiles,
15
+ customizePackageJson,
16
+ initializeGit,
17
+ checkForUpdates,
18
+ } from './cli/generator.js';
19
+ import { printBanner, promptProjectName, promptOverwrite, printSuccess } from './cli/ui.js';
20
+
21
+ const __filename = fileURLToPath(import.meta.url);
22
+ const __dirname = path.dirname(__filename);
23
+ const require = createRequire(import.meta.url);
24
+ const { version: PACKAGE_VERSION } = require('./package.json');
25
+ const templatesDir = path.join(__dirname, 'templates');
26
+
27
+ program
28
+ .name('create-arktos')
29
+ .description('❄️ Arktos: Production-ready Node.js & TypeScript Backend Boilerplate Generator')
30
+ .version(PACKAGE_VERSION, '-v, --version')
31
+ .argument('[project-directory]', 'Target directory for the generated backend project')
32
+ .option('--skip-git', 'Skip git repository initialization')
33
+ .option('--skip-update-check', 'Skip checking for a newer version')
34
+ .option('--package-manager <pm>', 'Specify package manager (pnpm, bun, yarn, npm)')
35
+ .option('--verbose', 'Show detailed debug output')
36
+ .action(async (projectDirArg, options) => {
37
+ let updatePromise = null;
38
+ if (!options.skipUpdateCheck) {
39
+ updatePromise = checkForUpdates(PACKAGE_VERSION).catch(() => null);
40
+ }
41
+
42
+ try {
43
+ // 1. Resolve Project Name and Destination Directory
44
+ let projectName = null;
45
+ let targetDir = null;
46
+
47
+ if (projectDirArg) {
48
+ targetDir = path.resolve(process.cwd(), projectDirArg);
49
+ projectName = path.basename(targetDir);
50
+
51
+ const validation = validateProjectName(projectName);
52
+ if (!validation.isValid) {
53
+ console.error(
54
+ chalk.red(`\n✖ Invalid project name "${projectName}": ${validation.message}\n`)
55
+ );
56
+ process.exit(1);
57
+ }
58
+ if (validation.normalizedName && validation.normalizedName !== projectName) {
59
+ projectName = validation.normalizedName;
60
+ targetDir = path.join(path.dirname(targetDir), projectName);
61
+ }
62
+ } else {
63
+ printBanner(PACKAGE_VERSION);
64
+ const promptedName = await promptProjectName();
65
+ if (!promptedName) {
66
+ console.log(chalk.gray('\nOperation cancelled.\n'));
67
+ process.exit(0);
68
+ }
69
+ projectName = normalizeProjectName(promptedName);
70
+ targetDir = path.resolve(process.cwd(), projectName);
71
+ }
72
+
73
+ // 2. Check Directory Overwrite
74
+ if (await fs.pathExists(targetDir)) {
75
+ const existingFiles = await fs.readdir(targetDir);
76
+ if (existingFiles.length > 0) {
77
+ const shouldOverwrite = await promptOverwrite(projectName);
78
+ if (!shouldOverwrite) {
79
+ console.log(chalk.gray('\nOperation cancelled.\n'));
80
+ process.exit(0);
81
+ }
82
+ await fs.emptyDir(targetDir);
83
+ }
84
+ } else {
85
+ await fs.ensureDir(targetDir);
86
+ }
87
+
88
+ // 3. Scaffolding Pipeline
89
+ const spinner = ora({
90
+ text: `Creating ${chalk.cyan(projectName)}...`,
91
+ }).start();
92
+
93
+ // Copy template files
94
+ spinner.text = 'Scaffolding Arktos backend template...';
95
+ await copyTemplateFiles(templatesDir, targetDir);
96
+
97
+ // Normalize package.json
98
+ spinner.text = 'Configuring backend package.json...';
99
+ await customizePackageJson(targetDir, projectName);
100
+
101
+ // Initialize git repository
102
+ if (!options.skipGit) {
103
+ spinner.text = 'Initializing git repository...';
104
+ initializeGit(targetDir);
105
+ }
106
+
107
+ spinner.succeed(
108
+ chalk.green(`Arktos backend project "${chalk.bold(projectName)}" successfully created!`)
109
+ );
110
+
111
+ // 4. Detect Package Manager & Command Guidance
112
+ const packageManager = options.packageManager || (await detectPackageManager());
113
+ const commands = getPackageManagerCommands(packageManager);
114
+
115
+ printSuccess({
116
+ projectName,
117
+ targetDir,
118
+ packageManager,
119
+ commands,
120
+ });
121
+
122
+ // 5. Print Update Notification (if available)
123
+ if (updatePromise) {
124
+ const latestVersion = await updatePromise;
125
+ if (latestVersion) {
126
+ console.log(
127
+ chalk.yellow(
128
+ ` 💡 A new version (v${latestVersion}) is available! Upgrade with: npm install -g create-arktos@latest\n`
129
+ )
130
+ );
131
+ }
132
+ }
133
+ } catch (error) {
134
+ console.error(chalk.red(`\n✖ Project creation failed: ${error.message}\n`));
135
+ if (options.verbose && error.stack) {
136
+ console.error(chalk.dim(error.stack));
137
+ }
138
+ process.exit(1);
139
+ }
140
+ });
141
+
142
+ // Custom help layout
143
+ program.on('--help', () => {
144
+ console.log(`
145
+ ${chalk.bold('Examples:')}
146
+ $ npx create-arktos my-api
147
+ $ pnpm create arktos my-api
148
+ $ npm create arktos my-api --skip-git
149
+ $ npx create-arktos my-api --package-manager bun
150
+ `);
151
+ });
152
+
153
+ program.parse(process.argv);