create-arktos 1.4.0 โ†’ 1.5.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/bin/cli.js CHANGED
@@ -3,13 +3,11 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { execSync } = require('child_process');
6
- const { input, select } = require('inquirer');
7
6
 
8
- const TEMPLATE_TS_DIR = path.join(__dirname, '../src');
9
- const TEMPLATE_JS_DIR = path.join(__dirname, '../src-js');
7
+ const TEMPLATE_DIR = path.join(__dirname, '../src');
10
8
  const ROOT_DIR = path.join(__dirname, '..');
11
9
 
12
- async function createProject(projectName) {
10
+ function createProject(projectName) {
13
11
  console.log(`๐Ÿš€ Creating Arktos project: ${projectName}`);
14
12
  console.log();
15
13
 
@@ -25,19 +23,8 @@ async function createProject(projectName) {
25
23
  process.exit(1);
26
24
  }
27
25
 
28
- // Ask user for language preference
29
- const language = await select({
30
- message: '๐Ÿ“ Which language would you like to use?',
31
- choices: [
32
- { name: 'TypeScript (Recommended)', value: 'typescript' },
33
- { name: 'JavaScript', value: 'javascript' }
34
- ],
35
- default: 'typescript'
36
- });
37
-
38
- const useTypeScript = language === 'typescript';
39
- const templateDir = useTypeScript ? TEMPLATE_TS_DIR : TEMPLATE_JS_DIR;
40
- const packageTemplate = useTypeScript ? 'template.package.json' : 'template.js.package.json';
26
+ // Always use TypeScript template
27
+ const packageTemplate = 'template.package.json';
41
28
 
42
29
  try {
43
30
  // Create project directory
@@ -45,9 +32,9 @@ async function createProject(projectName) {
45
32
  fs.mkdirSync(projectName);
46
33
 
47
34
  // Copy src directory
48
- console.log(`๐Ÿ“‹ Copying ${useTypeScript ? 'TypeScript' : 'JavaScript'} source files...`);
35
+ console.log('๐Ÿ“‹ Copying TypeScript source files...');
49
36
  const targetSrcDir = path.join(projectName, 'src');
50
- copyDir(templateDir, targetSrcDir);
37
+ copyDir(TEMPLATE_DIR, targetSrcDir);
51
38
 
52
39
  // Copy prisma directory
53
40
  console.log('๐Ÿ—„๏ธ Copying database schema...');
@@ -59,27 +46,22 @@ async function createProject(projectName) {
59
46
 
60
47
  // Copy template config files
61
48
  console.log('๐Ÿ“ Copying configuration files...');
62
- copyTemplateFiles(projectName, useTypeScript);
49
+ copyTemplateFiles(projectName);
63
50
 
64
51
  // Copy and update package.json
65
52
  console.log('๐Ÿ”ง Updating project configuration...');
66
53
  const templatePackageJsonPath = path.join(ROOT_DIR, packageTemplate);
67
54
  const targetPackageJsonPath = path.join(projectName, 'package.json');
68
-
55
+
69
56
  if (fs.existsSync(templatePackageJsonPath)) {
70
57
  const packageJson = JSON.parse(fs.readFileSync(templatePackageJsonPath, 'utf8'));
71
-
58
+
72
59
  // Update project name
73
60
  packageJson.name = projectName;
74
-
61
+
75
62
  fs.writeFileSync(targetPackageJsonPath, JSON.stringify(packageJson, null, 2));
76
63
  }
77
64
 
78
- // Clean up TypeScript specific files if JavaScript is selected
79
- if (!useTypeScript) {
80
- console.log('๐Ÿงน Cleaning up TypeScript specific files...');
81
- cleanupTypeScriptFiles(projectName);
82
- }
83
65
 
84
66
  console.log('โœ… Project created successfully!');
85
67
  console.log();
@@ -107,7 +89,7 @@ async function createProject(projectName) {
107
89
 
108
90
  } catch (error) {
109
91
  console.error('โŒ Error creating project:', error.message);
110
-
92
+
111
93
  // Cleanup on error
112
94
  try {
113
95
  if (fs.existsSync(projectName)) {
@@ -116,7 +98,7 @@ async function createProject(projectName) {
116
98
  } catch (cleanupError) {
117
99
  console.error('โŒ Error during cleanup:', cleanupError.message);
118
100
  }
119
-
101
+
120
102
  process.exit(1);
121
103
  }
122
104
  }
@@ -144,30 +126,7 @@ function copyDir(src, dest) {
144
126
  }
145
127
  }
146
128
 
147
- function cleanupTypeScriptFiles(projectPath) {
148
- try {
149
- // Remove TypeScript specific directories and files
150
- const tsSpecificPaths = [
151
- path.join(projectPath, 'src', 'types'),
152
- path.join(projectPath, 'tsconfig.json')
153
- ];
154
-
155
- for (const tsPath of tsSpecificPaths) {
156
- if (fs.existsSync(tsPath)) {
157
- const stats = fs.statSync(tsPath);
158
- if (stats.isDirectory()) {
159
- fs.rmSync(tsPath, { recursive: true, force: true });
160
- } else {
161
- fs.unlinkSync(tsPath);
162
- }
163
- }
164
- }
165
- } catch (error) {
166
- console.warn('โš ๏ธ Warning: Could not clean up some TypeScript files:', error.message);
167
- }
168
- }
169
-
170
- function copyTemplateFiles(dest, useTypeScript = true) {
129
+ function copyTemplateFiles(dest) {
171
130
  // Create common config files
172
131
  const configFiles = {
173
132
  '.gitignore': `# Dependencies
@@ -300,7 +259,7 @@ A modern Node.js backend API built with Arktos boilerplate.
300
259
  - \`npm run lint\` - Run ESLint
301
260
  - \`npm run db:studio\` - Open Prisma Studio
302
261
 
303
- Happy coding! ๐ŸŽ‰`
262
+ Happy coding! ๐ŸŽ‰`,
304
263
  };
305
264
 
306
265
  // Write config files
@@ -308,30 +267,18 @@ Happy coding! ๐ŸŽ‰`
308
267
  fs.writeFileSync(path.join(dest, fileName), content);
309
268
  }
310
269
 
311
- // Copy template files from root based on language choice
312
- const commonTemplateFiles = ['.env.example', 'vercel.json'];
313
- const templateFiles = useTypeScript
314
- ? [...commonTemplateFiles, 'tsconfig.json', 'eslint.config.js']
315
- : [...commonTemplateFiles];
316
-
270
+ // Copy template files from root
271
+ const templateFiles = ['.env.example', 'vercel.json', 'tsconfig.json', 'eslint.config.js'];
272
+
317
273
  for (const templateFile of templateFiles) {
318
274
  const srcPath = path.join(ROOT_DIR, templateFile);
319
275
  const destPath = path.join(dest, templateFile);
320
-
276
+
321
277
  if (fs.existsSync(srcPath)) {
322
278
  fs.copyFileSync(srcPath, destPath);
323
279
  }
324
280
  }
325
281
 
326
- // Copy appropriate ESLint config for JavaScript projects
327
- if (!useTypeScript) {
328
- const jsEslintConfigPath = path.join(ROOT_DIR, 'eslint.config.js.template');
329
- const destEslintConfigPath = path.join(dest, 'eslint.config.js');
330
-
331
- if (fs.existsSync(jsEslintConfigPath)) {
332
- fs.copyFileSync(jsEslintConfigPath, destEslintConfigPath);
333
- }
334
- }
335
282
  }
336
283
 
337
284
  function showHelp() {
@@ -382,7 +329,4 @@ if (args[0] === '-v' || args[0] === '--version') {
382
329
  }
383
330
 
384
331
  const projectName = args[0];
385
- createProject(projectName).catch((error) => {
386
- console.error('โŒ Error creating project:', error.message);
387
- process.exit(1);
388
- });
332
+ createProject(projectName);
package/eslint.config.js CHANGED
@@ -23,37 +23,52 @@ module.exports = [
23
23
  },
24
24
  },
25
25
  rules: {
26
- 'no-unused-vars': ['error', {
27
- argsIgnorePattern: '^_',
28
- varsIgnorePattern: '^_'
29
- }],
30
- 'no-console': ['warn', {
31
- allow: ['warn', 'error']
32
- }],
26
+ 'no-unused-vars': [
27
+ 'error',
28
+ {
29
+ argsIgnorePattern: '^_',
30
+ varsIgnorePattern: '^_',
31
+ },
32
+ ],
33
+ 'no-console': [
34
+ 'warn',
35
+ {
36
+ allow: ['warn', 'error'],
37
+ },
38
+ ],
33
39
  'prefer-const': 'error',
34
40
  'no-var': 'error',
35
- 'eqeqeq': ['error', 'always'],
36
- 'curly': ['error', 'all'],
41
+ eqeqeq: ['error', 'always'],
42
+ curly: ['error', 'all'],
37
43
  'brace-style': ['error', '1tbs'],
38
44
  'comma-dangle': ['error', 'always-multiline'],
39
- 'indent': ['error', 2],
40
- 'quotes': ['error', 'single'],
41
- 'semi': ['error', 'always'],
45
+ indent: ['error', 2],
46
+ quotes: ['error', 'single'],
47
+ semi: ['error', 'always'],
42
48
  'no-trailing-spaces': 'error',
43
- 'no-multiple-empty-lines': ['error', {
44
- max: 2,
45
- maxEOF: 1
46
- }],
49
+ 'no-multiple-empty-lines': [
50
+ 'error',
51
+ {
52
+ max: 2,
53
+ maxEOF: 1,
54
+ },
55
+ ],
47
56
  'object-curly-spacing': ['error', 'always'],
48
57
  'array-bracket-spacing': ['error', 'never'],
49
- 'key-spacing': ['error', {
50
- beforeColon: false,
51
- afterColon: true
52
- }],
53
- 'comma-spacing': ['error', {
54
- before: false,
55
- after: true
56
- }],
58
+ 'key-spacing': [
59
+ 'error',
60
+ {
61
+ beforeColon: false,
62
+ afterColon: true,
63
+ },
64
+ ],
65
+ 'comma-spacing': [
66
+ 'error',
67
+ {
68
+ before: false,
69
+ after: true,
70
+ },
71
+ ],
57
72
  'no-undef': 'error',
58
73
  'no-redeclare': 'error',
59
74
  'no-dupe-keys': 'error',
@@ -76,4 +91,4 @@ module.exports = [
76
91
  },
77
92
  },
78
93
  },
79
- ];
94
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-arktos",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "๐Ÿš€ A modern Node.js backend boilerplate with TypeScript, Express, JWT authentication, Prisma ORM, PostgreSQL, and Resend email service. Includes complete authentication flow, security middleware, and database management.",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -9,15 +9,12 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
- "src-js/",
13
12
  "prisma/",
14
13
  "template.package.json",
15
- "template.js.package.json",
16
14
  ".env.example",
17
15
  "vercel.json",
18
16
  "tsconfig.json",
19
17
  "eslint.config.js",
20
- "eslint.config.js.template",
21
18
  "README.md",
22
19
  "LICENSE"
23
20
  ],
@@ -1,31 +0,0 @@
1
- export default [
2
- {
3
- files: ['**/*.js'],
4
- languageOptions: {
5
- ecmaVersion: 'latest',
6
- sourceType: 'module',
7
- globals: {
8
- process: 'readonly',
9
- Buffer: 'readonly',
10
- __dirname: 'readonly',
11
- __filename: 'readonly',
12
- console: 'readonly',
13
- global: 'readonly',
14
- }
15
- },
16
- rules: {
17
- 'no-unused-vars': ['warn', {
18
- argsIgnorePattern: '^_',
19
- varsIgnorePattern: '^_'
20
- }],
21
- 'no-console': 'off',
22
- 'prefer-const': 'error',
23
- 'no-var': 'error',
24
- 'no-undef': 'error',
25
- 'semi': ['error', 'always'],
26
- 'quotes': ['error', 'single'],
27
- 'indent': ['error', 2],
28
- 'comma-dangle': ['error', 'always-multiline'],
29
- }
30
- }
31
- ];
package/src-js/app.js DELETED
@@ -1,97 +0,0 @@
1
- import express from 'express';
2
- import dotenv from 'dotenv';
3
- import routes from './routes/index.js';
4
- import { errorHandling, securityMiddleware, rateLimiter } from './middleware/index.js';
5
- import DatabaseService from './services/database.service.js';
6
- import logger from './config/logger.js';
7
-
8
- // Load environment variables
9
- dotenv.config();
10
-
11
- const app = express();
12
- const PORT = process.env.PORT || 3001;
13
-
14
- // Security middleware stack
15
- app.use(securityMiddleware);
16
-
17
- // Rate limiting
18
- app.use('/api', rateLimiter.general);
19
-
20
- // Body parsing middleware
21
- app.use(express.json({ limit: '10mb' }));
22
- app.use(express.urlencoded({ extended: true, limit: '10mb' }));
23
-
24
- // Trust proxy (for accurate IP addresses)
25
- app.set('trust proxy', 1);
26
-
27
- // API routes
28
- app.use('/api', routes);
29
-
30
- // Health check route (outside of rate limiting)
31
- app.get('/health', async (req, res) => {
32
- try {
33
- const dbService = DatabaseService.getInstance();
34
- const dbHealth = await dbService.healthCheck();
35
-
36
- res.json({
37
- status: dbHealth.connected ? 'ok' : 'error',
38
- timestamp: new Date().toISOString(),
39
- uptime: process.uptime(),
40
- database: dbHealth,
41
- });
42
- } catch (error) {
43
- res.status(503).json({
44
- status: 'error',
45
- message: 'Health check failed',
46
- });
47
- }
48
- });
49
-
50
- // 404 handler
51
- app.use('*', (req, res) => {
52
- res.status(404).json({
53
- success: false,
54
- message: 'Route not found',
55
- error: `Cannot ${req.method} ${req.originalUrl}`,
56
- });
57
- });
58
-
59
- // Global error handler
60
- app.use(errorHandling);
61
-
62
- // Graceful shutdown
63
- const gracefulShutdown = async (signal) => {
64
- logger.info(`Received ${signal}, shutting down gracefully...`);
65
-
66
- try {
67
- const dbService = DatabaseService.getInstance();
68
- await dbService.disconnect();
69
- logger.info('Database connection closed');
70
- } catch (error) {
71
- logger.error('Error during graceful shutdown:', error);
72
- }
73
-
74
- process.exit(0);
75
- };
76
-
77
- process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
78
- process.on('SIGINT', () => gracefulShutdown('SIGINT'));
79
-
80
- const startServer = async () => {
81
- try {
82
- // Initialize database connection
83
- const dbService = DatabaseService.getInstance();
84
- await dbService.connect();
85
-
86
- app.listen(PORT, () => {
87
- logger.info(`Server running on port ${PORT}`);
88
- logger.info(`Health check: http://localhost:${PORT}/health`);
89
- logger.info(`API base URL: http://localhost:${PORT}/api`);
90
- });
91
- } catch (error) {
92
- logger.error('Failed to start server:', error);
93
- process.exit(1);
94
- }
95
- };
96
-
97
- startServer();
@@ -1,15 +0,0 @@
1
- import logger from './logger.js';
2
- import { envSchema } from '../schemas/index.js';
3
-
4
- export function validateEnv() {
5
- try {
6
- const env = envSchema.parse(process.env);
7
- logger.info('Environment validation successful');
8
- return env;
9
- } catch (error) {
10
- logger.error('Environment validation failed:', error);
11
- process.exit(1);
12
- }
13
- }
14
-
15
- export const config = validateEnv();
@@ -1,76 +0,0 @@
1
- import winston from 'winston';
2
- import path from 'path';
3
- import fs from 'fs';
4
-
5
- const logLevel = process.env.LOG_LEVEL || 'info';
6
- const logFile = process.env.LOG_FILE || 'logs/app.log';
7
-
8
- // Ensure logs directory exists
9
- const logDir = path.dirname(logFile);
10
- if (!fs.existsSync(logDir)) {
11
- fs.mkdirSync(logDir, { recursive: true });
12
- }
13
-
14
- const logFormat = winston.format.combine(
15
- winston.format.timestamp({
16
- format: 'YYYY-MM-DD HH:mm:ss',
17
- }),
18
- winston.format.errors({ stack: true }),
19
- winston.format.json(),
20
- winston.format.prettyPrint()
21
- );
22
-
23
- const consoleFormat = winston.format.combine(
24
- winston.format.colorize(),
25
- winston.format.timestamp({
26
- format: 'HH:mm:ss',
27
- }),
28
- winston.format.printf(({ timestamp, level, message, ...meta }) => {
29
- return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;
30
- })
31
- );
32
-
33
- const logger = winston.createLogger({
34
- level: logLevel,
35
- format: logFormat,
36
- defaultMeta: { service: 'arktos-backend' },
37
- transports: [
38
- // File transport
39
- new winston.transports.File({
40
- filename: logFile,
41
- handleExceptions: true,
42
- maxsize: 5242880, // 5MB
43
- maxFiles: 5,
44
- }),
45
- // Error file transport
46
- new winston.transports.File({
47
- filename: path.join(logDir, 'error.log'),
48
- level: 'error',
49
- handleExceptions: true,
50
- maxsize: 5242880, // 5MB
51
- maxFiles: 5,
52
- }),
53
- ],
54
- });
55
-
56
- // Console transport for non-production environments
57
- if (process.env.NODE_ENV !== 'production') {
58
- logger.add(
59
- new winston.transports.Console({
60
- format: consoleFormat,
61
- handleExceptions: true,
62
- })
63
- );
64
- }
65
-
66
- // Handle uncaught exceptions and unhandled rejections
67
- process.on('uncaughtException', (error) => {
68
- logger.error('Uncaught Exception:', error);
69
- process.exit(1);
70
- });
71
-
72
- process.on('unhandledRejection', (reason, promise) => {
73
- logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
74
- });
75
-
76
- export default logger;
@@ -1,57 +0,0 @@
1
- export const ERROR_CODES = {
2
- // Authentication errors
3
- AUTH_INVALID_CREDENTIALS: 'AUTH_001',
4
- AUTH_TOKEN_EXPIRED: 'AUTH_002',
5
- AUTH_TOKEN_INVALID: 'AUTH_003',
6
- AUTH_USER_NOT_FOUND: 'AUTH_004',
7
- AUTH_EMAIL_NOT_VERIFIED: 'AUTH_005',
8
- AUTH_ACCOUNT_DISABLED: 'AUTH_006',
9
- AUTH_INSUFFICIENT_PERMISSIONS: 'AUTH_007',
10
- AUTH_USER_EXISTS: 'AUTH_008',
11
- AUTH_USERNAME_TAKEN: 'AUTH_009',
12
- AUTH_INVALID_PASSWORD: 'AUTH_010',
13
- AUTH_TOKEN_REQUIRED: 'AUTH_011',
14
- AUTH_INVALID_TOKEN: 'AUTH_003', // Alias for AUTH_TOKEN_INVALID
15
- AUTH_ACCOUNT_DEACTIVATED: 'AUTH_006', // Alias for AUTH_ACCOUNT_DISABLED
16
-
17
- // Validation errors
18
- VALIDATION_REQUIRED_FIELD: 'VAL_001',
19
- VALIDATION_INVALID_FORMAT: 'VAL_002',
20
- VALIDATION_INVALID_LENGTH: 'VAL_003',
21
- VALIDATION_INVALID_TYPE: 'VAL_004',
22
- VALIDATION_ERROR: 'VAL_005',
23
-
24
- // Database errors
25
- DB_CONNECTION_ERROR: 'DB_001',
26
- DB_QUERY_ERROR: 'DB_002',
27
- DB_CONSTRAINT_VIOLATION: 'DB_003',
28
- DB_RECORD_NOT_FOUND: 'DB_004',
29
- DB_DUPLICATE_ENTRY: 'DB_005',
30
- DATABASE_CONNECTION: 'DB_001', // Alias
31
- DATABASE_ERROR: 'DB_002', // Alias
32
- DATABASE_CONSTRAINT: 'DB_003', // Alias
33
- DATABASE_NOT_FOUND: 'DB_004', // Alias
34
- DATABASE_CONFLICT: 'DB_005', // Alias
35
-
36
- // File/Upload errors
37
- FILE_TOO_LARGE: 'FILE_001',
38
- FILE_INVALID_TYPE: 'FILE_002',
39
- FILE_UPLOAD_FAILED: 'FILE_003',
40
- FILE_NOT_FOUND: 'FILE_004',
41
-
42
- // Rate limiting errors
43
- RATE_LIMIT_EXCEEDED: 'RATE_001',
44
-
45
- // Server errors
46
- INTERNAL_SERVER_ERROR: 'SRV_001',
47
- SERVICE_UNAVAILABLE: 'SRV_002',
48
- EXTERNAL_SERVICE_ERROR: 'SRV_003',
49
- INTERNAL_ERROR: 'SRV_001', // Alias
50
- GENERIC_ERROR: 'SRV_004',
51
- ROUTE_NOT_FOUND: 'SRV_005',
52
-
53
- // Email service errors
54
- EMAIL_SEND_FAILED: 'EMAIL_001',
55
- EMAIL_TEMPLATE_NOT_FOUND: 'EMAIL_002',
56
- EMAIL_INVALID_RECIPIENT: 'EMAIL_003',
57
- };