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.
@@ -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
- const awsRegionInput = await rl.question(`Enter AWS region [us-east-1]: `);
57
- const awsRegion = awsRegionInput.trim() || 'us-east-1';
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)');
@@ -44,7 +44,22 @@ function detectProject(projectRoot) {
44
44
  hasRedis: false,
45
45
  name: path.basename(projectRoot) || 'mysystem-app',
46
46
  };
47
- // 1. Read package.json if it exists
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 (pg, prisma, typeorm, sequelize, knex, sqlite3, mysql2)
60
- const dbDeps = ['pg', 'postgres', 'prisma', 'typeorm', 'sequelize', 'knex', 'sqlite3', 'mysql2'];
61
- if (Object.keys(deps).some(dep => dbDeps.includes(dep))) {
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 (deps['next']) {
71
- info.type = 'nextjs';
72
- info.port = 3000;
73
- }
74
- else if (deps['vite'] || deps['react']) {
75
- // A Vite React app (or standard React)
76
- info.type = 'react-vite';
77
- info.port = 80; // React SPAs get served on port 80 via Nginx in production
78
- }
79
- else if (deps['express'] || deps['koa'] || deps['fastify'] || deps['nest']) {
80
- info.type = 'node';
81
- info.port = 3000;
82
- }
83
- else {
84
- info.type = 'node';
85
- info.port = 3000;
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 and continue
109
+ // Ignore JSON parse errors
90
110
  }
91
111
  }
92
- // 2. Read requirements.txt or main.py if Python
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
- if (fs.existsSync(reqTxtPath) || fs.existsSync(mainPyPath)) {
96
- info.type = 'fastapi';
97
- info.port = 8000;
98
- if (fs.existsSync(reqTxtPath)) {
99
- const reqs = fs.readFileSync(reqTxtPath, 'utf8');
100
- const dbTerms = ['postgresql', 'psycopg2', 'sqlalchemy', 'tortoise-orm', 'peewee', 'asyncpg'];
101
- if (dbTerms.some(term => reqs.toLowerCase().includes(term))) {
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 => reqs.toLowerCase().includes(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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mysystem-cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Zero-config deployment standard and CLI for AI-generated applications on AWS Fargate",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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
- const awsRegionInput = await rl.question(`Enter AWS region [us-east-1]: `);
26
- const awsRegion = awsRegionInput.trim() || 'us-east-1';
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)');
@@ -18,7 +18,24 @@ export function detectProject(projectRoot: string): ProjectInfo {
18
18
  name: path.basename(projectRoot) || 'mysystem-app',
19
19
  };
20
20
 
21
- // 1. Read package.json if it exists
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 (pg, prisma, typeorm, sequelize, knex, sqlite3, mysql2)
36
- const dbDeps = ['pg', 'postgres', 'prisma', 'typeorm', 'sequelize', 'knex', 'sqlite3', 'mysql2'];
37
- if (Object.keys(deps).some(dep => dbDeps.includes(dep))) {
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 (deps['next']) {
49
- info.type = 'nextjs';
50
- info.port = 3000;
51
- } else if (deps['vite'] || deps['react']) {
52
- // A Vite React app (or standard React)
53
- info.type = 'react-vite';
54
- info.port = 80; // React SPAs get served on port 80 via Nginx in production
55
- } else if (deps['express'] || deps['koa'] || deps['fastify'] || deps['nest']) {
56
- info.type = 'node';
57
- info.port = 3000;
58
- } else {
59
- info.type = 'node';
60
- info.port = 3000;
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 and continue
85
+ // Ignore JSON parse errors
64
86
  }
65
87
  }
66
88
 
67
- // 2. Read requirements.txt or main.py if Python
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
- if (fs.existsSync(reqTxtPath) || fs.existsSync(mainPyPath)) {
71
- info.type = 'fastapi';
72
- info.port = 8000;
92
+ const pyProjectToml = path.join(projectRoot, 'pyproject.toml');
73
93
 
74
- if (fs.existsSync(reqTxtPath)) {
75
- const reqs = fs.readFileSync(reqTxtPath, 'utf8');
76
- const dbTerms = ['postgresql', 'psycopg2', 'sqlalchemy', 'tortoise-orm', 'peewee', 'asyncpg'];
77
- if (dbTerms.some(term => reqs.toLowerCase().includes(term))) {
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 => reqs.toLowerCase().includes(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