mysystem-cli 1.0.1 → 1.0.2
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/commands/init.js +15 -2
- package/dist/utils/detector.js +84 -32
- package/package.json +1 -1
- package/src/commands/init.ts +15 -2
- package/src/utils/detector.ts +86 -29
package/dist/commands/init.js
CHANGED
|
@@ -53,8 +53,21 @@ async function runInit(projectRoot) {
|
|
|
53
53
|
try {
|
|
54
54
|
const appNameInput = await rl.question(`Enter application name [${detected.name}]: `);
|
|
55
55
|
const appName = appNameInput.trim() || detected.name;
|
|
56
|
-
|
|
57
|
-
const
|
|
56
|
+
let awsRegion = 'us-east-1';
|
|
57
|
+
const regionRegex = /^[a-z]{2,}-[a-z]+-[0-9]+$/;
|
|
58
|
+
while (true) {
|
|
59
|
+
const awsRegionInput = await rl.question(`Enter AWS region [us-east-1]: `);
|
|
60
|
+
const val = awsRegionInput.trim();
|
|
61
|
+
if (!val) {
|
|
62
|
+
awsRegion = 'us-east-1';
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
if (regionRegex.test(val)) {
|
|
66
|
+
awsRegion = val;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
console.log('\x1b[31mInvalid AWS region format. Example: us-east-1, eu-west-1, ap-south-1. Please try again.\x1b[0m');
|
|
70
|
+
}
|
|
58
71
|
console.log('Select your AWS hosting tier:');
|
|
59
72
|
console.log(' \x1b[36m1. Production\x1b[0m [ECS Fargate + RDS + ALB + WAF] (~$17/mo free-tier, ~$51/mo standard)');
|
|
60
73
|
console.log(' \x1b[36m2. Hobbyist\x1b[0m [Single EC2 + Docker Compose + Postgres] ($0/mo free-tier, ~$3.20/mo standard)');
|
package/dist/utils/detector.js
CHANGED
|
@@ -44,7 +44,22 @@ function detectProject(projectRoot) {
|
|
|
44
44
|
hasRedis: false,
|
|
45
45
|
name: path.basename(projectRoot) || 'mysystem-app',
|
|
46
46
|
};
|
|
47
|
-
// 1.
|
|
47
|
+
// 1. Scan for framework config files directly first
|
|
48
|
+
const hasNextConfig = ['next.config.js', 'next.config.mjs', 'next.config.ts'].some(f => fs.existsSync(path.join(projectRoot, f)));
|
|
49
|
+
const hasViteConfig = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs'].some(f => fs.existsSync(path.join(projectRoot, f)));
|
|
50
|
+
const hasPrismaSchema = fs.existsSync(path.join(projectRoot, 'prisma', 'schema.prisma')) || fs.existsSync(path.join(projectRoot, 'schema.prisma'));
|
|
51
|
+
if (hasNextConfig) {
|
|
52
|
+
info.type = 'nextjs';
|
|
53
|
+
info.port = 3000;
|
|
54
|
+
}
|
|
55
|
+
else if (hasViteConfig) {
|
|
56
|
+
info.type = 'react-vite';
|
|
57
|
+
info.port = 80; // Served via Nginx in production container
|
|
58
|
+
}
|
|
59
|
+
if (hasPrismaSchema) {
|
|
60
|
+
info.hasDatabase = true;
|
|
61
|
+
}
|
|
62
|
+
// 2. Read package.json if it exists
|
|
48
63
|
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
49
64
|
if (fs.existsSync(packageJsonPath)) {
|
|
50
65
|
try {
|
|
@@ -56,55 +71,92 @@ function detectProject(projectRoot) {
|
|
|
56
71
|
...packageJson.dependencies,
|
|
57
72
|
...packageJson.devDependencies,
|
|
58
73
|
};
|
|
59
|
-
// Detect database dependencies (
|
|
60
|
-
const dbDeps = [
|
|
61
|
-
|
|
74
|
+
// Detect database dependencies (including MongoDB, SQLite, Postgres, MySQL, MariaDB, SQL Server, Drizzle, etc.)
|
|
75
|
+
const dbDeps = [
|
|
76
|
+
'pg', 'postgres', 'prisma', 'typeorm', 'sequelize', 'knex', 'sqlite3',
|
|
77
|
+
'mysql2', 'mongodb', 'mongoose', 'mssql', 'mariadb', 'pg-promise',
|
|
78
|
+
'drizzle-orm', 'better-sqlite3'
|
|
79
|
+
];
|
|
80
|
+
if (Object.keys(deps).some(dep => dbDeps.includes(dep) || dep.startsWith('@prisma/'))) {
|
|
62
81
|
info.hasDatabase = true;
|
|
63
82
|
}
|
|
64
83
|
// Detect Redis dependencies
|
|
65
|
-
const redisDeps = ['redis', 'ioredis', 'bull', 'bullmq', 'handy-redis', 'keyv'];
|
|
84
|
+
const redisDeps = ['redis', 'ioredis', 'bull', 'bullmq', 'handy-redis', 'keyv', 'redis-om'];
|
|
66
85
|
if (Object.keys(deps).some(dep => redisDeps.includes(dep))) {
|
|
67
86
|
info.hasRedis = true;
|
|
68
87
|
}
|
|
69
|
-
// Framework detection
|
|
70
|
-
if (
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
// Fallback Framework detection if config files weren't matched
|
|
89
|
+
if (info.type === 'unknown') {
|
|
90
|
+
if (deps['next']) {
|
|
91
|
+
info.type = 'nextjs';
|
|
92
|
+
info.port = 3000;
|
|
93
|
+
}
|
|
94
|
+
else if (deps['vite'] || deps['react']) {
|
|
95
|
+
info.type = 'react-vite';
|
|
96
|
+
info.port = 80;
|
|
97
|
+
}
|
|
98
|
+
else if (deps['express'] || deps['koa'] || deps['fastify'] || deps['nest'] || deps['hapi']) {
|
|
99
|
+
info.type = 'node';
|
|
100
|
+
info.port = 3000;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
info.type = 'node';
|
|
104
|
+
info.port = 3000;
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
108
|
catch (e) {
|
|
89
|
-
// Ignore JSON parse errors
|
|
109
|
+
// Ignore JSON parse errors
|
|
90
110
|
}
|
|
91
111
|
}
|
|
92
|
-
//
|
|
112
|
+
// 3. Read requirements.txt or main.py if Python
|
|
93
113
|
const reqTxtPath = path.join(projectRoot, 'requirements.txt');
|
|
94
114
|
const mainPyPath = path.join(projectRoot, 'main.py');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
info.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
const pyProjectToml = path.join(projectRoot, 'pyproject.toml');
|
|
116
|
+
if (fs.existsSync(reqTxtPath) || fs.existsSync(mainPyPath) || fs.existsSync(pyProjectToml)) {
|
|
117
|
+
if (info.type === 'unknown') {
|
|
118
|
+
info.type = 'fastapi';
|
|
119
|
+
info.port = 8000;
|
|
120
|
+
}
|
|
121
|
+
const checkPythonDeps = (content) => {
|
|
122
|
+
const dbTerms = ['postgresql', 'psycopg2', 'sqlalchemy', 'tortoise-orm', 'peewee', 'asyncpg', 'pymongo', 'mongoengine', 'mysqlclient'];
|
|
123
|
+
if (dbTerms.some(term => content.toLowerCase().includes(term))) {
|
|
102
124
|
info.hasDatabase = true;
|
|
103
125
|
}
|
|
104
126
|
const redisTerms = ['redis', 'django-redis', 'celery'];
|
|
105
|
-
if (redisTerms.some(term =>
|
|
127
|
+
if (redisTerms.some(term => content.toLowerCase().includes(term))) {
|
|
106
128
|
info.hasRedis = true;
|
|
107
129
|
}
|
|
130
|
+
};
|
|
131
|
+
if (fs.existsSync(reqTxtPath)) {
|
|
132
|
+
checkPythonDeps(fs.readFileSync(reqTxtPath, 'utf8'));
|
|
133
|
+
}
|
|
134
|
+
if (fs.existsSync(pyProjectToml)) {
|
|
135
|
+
checkPythonDeps(fs.readFileSync(pyProjectToml, 'utf8'));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// 4. Scan .env, .env.example, .env.local for database/redis keywords (CRITICAL fallback)
|
|
139
|
+
const envFiles = ['.env', '.env.example', '.env.local', '.env.development', '.env.production'];
|
|
140
|
+
for (const file of envFiles) {
|
|
141
|
+
const filePath = path.join(projectRoot, file);
|
|
142
|
+
if (fs.existsSync(filePath)) {
|
|
143
|
+
try {
|
|
144
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
145
|
+
const dbKeywords = [
|
|
146
|
+
'DATABASE_URL', 'DATABASE_URI', 'POSTGRES_', 'MONGODB_URI', 'MONGO_URI',
|
|
147
|
+
'DB_HOST', 'DB_PASSWORD', 'DB_CONNECTION', 'MYSQL_URL', 'DATABASE_NAME'
|
|
148
|
+
];
|
|
149
|
+
const redisKeywords = ['REDIS_URL', 'REDIS_HOST', 'REDIS_PORT', 'REDIS_PASSWORD'];
|
|
150
|
+
if (dbKeywords.some(kw => content.includes(kw))) {
|
|
151
|
+
info.hasDatabase = true;
|
|
152
|
+
}
|
|
153
|
+
if (redisKeywords.some(kw => content.includes(kw))) {
|
|
154
|
+
info.hasRedis = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// Ignore file read errors
|
|
159
|
+
}
|
|
108
160
|
}
|
|
109
161
|
}
|
|
110
162
|
return info;
|
package/package.json
CHANGED
package/src/commands/init.ts
CHANGED
|
@@ -22,8 +22,21 @@ export async function runInit(projectRoot: string) {
|
|
|
22
22
|
const appNameInput = await rl.question(`Enter application name [${detected.name}]: `);
|
|
23
23
|
const appName = appNameInput.trim() || detected.name;
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
const
|
|
25
|
+
let awsRegion = 'us-east-1';
|
|
26
|
+
const regionRegex = /^[a-z]{2,}-[a-z]+-[0-9]+$/;
|
|
27
|
+
while (true) {
|
|
28
|
+
const awsRegionInput = await rl.question(`Enter AWS region [us-east-1]: `);
|
|
29
|
+
const val = awsRegionInput.trim();
|
|
30
|
+
if (!val) {
|
|
31
|
+
awsRegion = 'us-east-1';
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (regionRegex.test(val)) {
|
|
35
|
+
awsRegion = val;
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
console.log('\x1b[31mInvalid AWS region format. Example: us-east-1, eu-west-1, ap-south-1. Please try again.\x1b[0m');
|
|
39
|
+
}
|
|
27
40
|
|
|
28
41
|
console.log('Select your AWS hosting tier:');
|
|
29
42
|
console.log(' \x1b[36m1. Production\x1b[0m [ECS Fargate + RDS + ALB + WAF] (~$17/mo free-tier, ~$51/mo standard)');
|
package/src/utils/detector.ts
CHANGED
|
@@ -18,7 +18,24 @@ export function detectProject(projectRoot: string): ProjectInfo {
|
|
|
18
18
|
name: path.basename(projectRoot) || 'mysystem-app',
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
-
// 1.
|
|
21
|
+
// 1. Scan for framework config files directly first
|
|
22
|
+
const hasNextConfig = ['next.config.js', 'next.config.mjs', 'next.config.ts'].some(f => fs.existsSync(path.join(projectRoot, f)));
|
|
23
|
+
const hasViteConfig = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs'].some(f => fs.existsSync(path.join(projectRoot, f)));
|
|
24
|
+
const hasPrismaSchema = fs.existsSync(path.join(projectRoot, 'prisma', 'schema.prisma')) || fs.existsSync(path.join(projectRoot, 'schema.prisma'));
|
|
25
|
+
|
|
26
|
+
if (hasNextConfig) {
|
|
27
|
+
info.type = 'nextjs';
|
|
28
|
+
info.port = 3000;
|
|
29
|
+
} else if (hasViteConfig) {
|
|
30
|
+
info.type = 'react-vite';
|
|
31
|
+
info.port = 80; // Served via Nginx in production container
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (hasPrismaSchema) {
|
|
35
|
+
info.hasDatabase = true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. Read package.json if it exists
|
|
22
39
|
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
23
40
|
if (fs.existsSync(packageJsonPath)) {
|
|
24
41
|
try {
|
|
@@ -32,56 +49,96 @@ export function detectProject(projectRoot: string): ProjectInfo {
|
|
|
32
49
|
...packageJson.devDependencies,
|
|
33
50
|
};
|
|
34
51
|
|
|
35
|
-
// Detect database dependencies (
|
|
36
|
-
const dbDeps = [
|
|
37
|
-
|
|
52
|
+
// Detect database dependencies (including MongoDB, SQLite, Postgres, MySQL, MariaDB, SQL Server, Drizzle, etc.)
|
|
53
|
+
const dbDeps = [
|
|
54
|
+
'pg', 'postgres', 'prisma', 'typeorm', 'sequelize', 'knex', 'sqlite3',
|
|
55
|
+
'mysql2', 'mongodb', 'mongoose', 'mssql', 'mariadb', 'pg-promise',
|
|
56
|
+
'drizzle-orm', 'better-sqlite3'
|
|
57
|
+
];
|
|
58
|
+
if (Object.keys(deps).some(dep => dbDeps.includes(dep) || dep.startsWith('@prisma/'))) {
|
|
38
59
|
info.hasDatabase = true;
|
|
39
60
|
}
|
|
40
61
|
|
|
41
62
|
// Detect Redis dependencies
|
|
42
|
-
const redisDeps = ['redis', 'ioredis', 'bull', 'bullmq', 'handy-redis', 'keyv'];
|
|
63
|
+
const redisDeps = ['redis', 'ioredis', 'bull', 'bullmq', 'handy-redis', 'keyv', 'redis-om'];
|
|
43
64
|
if (Object.keys(deps).some(dep => redisDeps.includes(dep))) {
|
|
44
65
|
info.hasRedis = true;
|
|
45
66
|
}
|
|
46
67
|
|
|
47
|
-
// Framework detection
|
|
48
|
-
if (
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
68
|
+
// Fallback Framework detection if config files weren't matched
|
|
69
|
+
if (info.type === 'unknown') {
|
|
70
|
+
if (deps['next']) {
|
|
71
|
+
info.type = 'nextjs';
|
|
72
|
+
info.port = 3000;
|
|
73
|
+
} else if (deps['vite'] || deps['react']) {
|
|
74
|
+
info.type = 'react-vite';
|
|
75
|
+
info.port = 80;
|
|
76
|
+
} else if (deps['express'] || deps['koa'] || deps['fastify'] || deps['nest'] || deps['hapi']) {
|
|
77
|
+
info.type = 'node';
|
|
78
|
+
info.port = 3000;
|
|
79
|
+
} else {
|
|
80
|
+
info.type = 'node';
|
|
81
|
+
info.port = 3000;
|
|
82
|
+
}
|
|
61
83
|
}
|
|
62
84
|
} catch (e) {
|
|
63
|
-
// Ignore JSON parse errors
|
|
85
|
+
// Ignore JSON parse errors
|
|
64
86
|
}
|
|
65
87
|
}
|
|
66
88
|
|
|
67
|
-
//
|
|
89
|
+
// 3. Read requirements.txt or main.py if Python
|
|
68
90
|
const reqTxtPath = path.join(projectRoot, 'requirements.txt');
|
|
69
91
|
const mainPyPath = path.join(projectRoot, 'main.py');
|
|
70
|
-
|
|
71
|
-
info.type = 'fastapi';
|
|
72
|
-
info.port = 8000;
|
|
92
|
+
const pyProjectToml = path.join(projectRoot, 'pyproject.toml');
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
94
|
+
if (fs.existsSync(reqTxtPath) || fs.existsSync(mainPyPath) || fs.existsSync(pyProjectToml)) {
|
|
95
|
+
if (info.type === 'unknown') {
|
|
96
|
+
info.type = 'fastapi';
|
|
97
|
+
info.port = 8000;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const checkPythonDeps = (content: string) => {
|
|
101
|
+
const dbTerms = ['postgresql', 'psycopg2', 'sqlalchemy', 'tortoise-orm', 'peewee', 'asyncpg', 'pymongo', 'mongoengine', 'mysqlclient'];
|
|
102
|
+
if (dbTerms.some(term => content.toLowerCase().includes(term))) {
|
|
78
103
|
info.hasDatabase = true;
|
|
79
104
|
}
|
|
80
105
|
|
|
81
106
|
const redisTerms = ['redis', 'django-redis', 'celery'];
|
|
82
|
-
if (redisTerms.some(term =>
|
|
107
|
+
if (redisTerms.some(term => content.toLowerCase().includes(term))) {
|
|
83
108
|
info.hasRedis = true;
|
|
84
109
|
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (fs.existsSync(reqTxtPath)) {
|
|
113
|
+
checkPythonDeps(fs.readFileSync(reqTxtPath, 'utf8'));
|
|
114
|
+
}
|
|
115
|
+
if (fs.existsSync(pyProjectToml)) {
|
|
116
|
+
checkPythonDeps(fs.readFileSync(pyProjectToml, 'utf8'));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 4. Scan .env, .env.example, .env.local for database/redis keywords (CRITICAL fallback)
|
|
121
|
+
const envFiles = ['.env', '.env.example', '.env.local', '.env.development', '.env.production'];
|
|
122
|
+
for (const file of envFiles) {
|
|
123
|
+
const filePath = path.join(projectRoot, file);
|
|
124
|
+
if (fs.existsSync(filePath)) {
|
|
125
|
+
try {
|
|
126
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
127
|
+
const dbKeywords = [
|
|
128
|
+
'DATABASE_URL', 'DATABASE_URI', 'POSTGRES_', 'MONGODB_URI', 'MONGO_URI',
|
|
129
|
+
'DB_HOST', 'DB_PASSWORD', 'DB_CONNECTION', 'MYSQL_URL', 'DATABASE_NAME'
|
|
130
|
+
];
|
|
131
|
+
const redisKeywords = ['REDIS_URL', 'REDIS_HOST', 'REDIS_PORT', 'REDIS_PASSWORD'];
|
|
132
|
+
|
|
133
|
+
if (dbKeywords.some(kw => content.includes(kw))) {
|
|
134
|
+
info.hasDatabase = true;
|
|
135
|
+
}
|
|
136
|
+
if (redisKeywords.some(kw => content.includes(kw))) {
|
|
137
|
+
info.hasRedis = true;
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// Ignore file read errors
|
|
141
|
+
}
|
|
85
142
|
}
|
|
86
143
|
}
|
|
87
144
|
|