mysystem-cli 1.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.
- package/dist/commands/audit.d.ts +1 -0
- package/dist/commands/audit.js +194 -0
- package/dist/commands/destroy.d.ts +1 -0
- package/dist/commands/destroy.js +92 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +255 -0
- package/dist/commands/logs.d.ts +1 -0
- package/dist/commands/logs.js +76 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +64 -0
- package/dist/utils/detector.d.ts +8 -0
- package/dist/utils/detector.js +111 -0
- package/dist/utils/installer.d.ts +1 -0
- package/dist/utils/installer.js +122 -0
- package/package.json +34 -0
- package/src/commands/audit.ts +176 -0
- package/src/commands/destroy.ts +65 -0
- package/src/commands/init.ts +252 -0
- package/src/commands/logs.ts +48 -0
- package/src/index.ts +66 -0
- package/src/utils/detector.ts +89 -0
- package/src/utils/installer.ts +89 -0
- package/tsconfig.json +16 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const init_1 = require("./commands/init");
|
|
5
|
+
const audit_1 = require("./commands/audit");
|
|
6
|
+
const destroy_1 = require("./commands/destroy");
|
|
7
|
+
const logs_1 = require("./commands/logs");
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const command = args[0];
|
|
10
|
+
function printHelp() {
|
|
11
|
+
console.log(`
|
|
12
|
+
\x1b[1mMySystem CLI - Production Deployment for Vibecoders\x1b[0m
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
npx mysystem-cli <command> [options]
|
|
16
|
+
|
|
17
|
+
Commands:
|
|
18
|
+
\x1b[36minit\x1b[0m Initialize AWS Terraform configs, Dockerfiles, and GitHub workflows.
|
|
19
|
+
\x1b[36maudit\x1b[0m Audit local files for production-readiness, security, and compliance.
|
|
20
|
+
\x1b[36mlogs\x1b[0m Stream container logs from AWS CloudWatch directly to your terminal.
|
|
21
|
+
\x1b[36mdestroy\x1b[0m Teardown all AWS infrastructure resources for this application.
|
|
22
|
+
\x1b[36mhelp\x1b[0m Print this help menu.
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
$ npx mysystem-cli init
|
|
26
|
+
$ npx mysystem-cli audit
|
|
27
|
+
$ npx mysystem-cli logs
|
|
28
|
+
$ npx mysystem-cli destroy
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
async function main() {
|
|
32
|
+
switch (command) {
|
|
33
|
+
case 'init':
|
|
34
|
+
await (0, init_1.runInit)(process.cwd());
|
|
35
|
+
break;
|
|
36
|
+
case 'audit':
|
|
37
|
+
(0, audit_1.runAudit)(process.cwd());
|
|
38
|
+
break;
|
|
39
|
+
case 'logs':
|
|
40
|
+
await (0, logs_1.runLogs)(process.cwd());
|
|
41
|
+
break;
|
|
42
|
+
case 'destroy':
|
|
43
|
+
await (0, destroy_1.runDestroy)(process.cwd());
|
|
44
|
+
break;
|
|
45
|
+
case 'help':
|
|
46
|
+
case '--help':
|
|
47
|
+
case '-h':
|
|
48
|
+
printHelp();
|
|
49
|
+
break;
|
|
50
|
+
default:
|
|
51
|
+
if (!command) {
|
|
52
|
+
printHelp();
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.error(`\x1b[31mUnknown command: ${command}\x1b[0m`);
|
|
56
|
+
printHelp();
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
main().catch(err => {
|
|
62
|
+
console.error('\x1b[31mFatal error occurred:\x1b[0m', err);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.detectProject = detectProject;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
function detectProject(projectRoot) {
|
|
40
|
+
const info = {
|
|
41
|
+
type: 'unknown',
|
|
42
|
+
port: 3000,
|
|
43
|
+
hasDatabase: false,
|
|
44
|
+
hasRedis: false,
|
|
45
|
+
name: path.basename(projectRoot) || 'mysystem-app',
|
|
46
|
+
};
|
|
47
|
+
// 1. Read package.json if it exists
|
|
48
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
49
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
50
|
+
try {
|
|
51
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
52
|
+
if (packageJson.name) {
|
|
53
|
+
info.name = packageJson.name;
|
|
54
|
+
}
|
|
55
|
+
const deps = {
|
|
56
|
+
...packageJson.dependencies,
|
|
57
|
+
...packageJson.devDependencies,
|
|
58
|
+
};
|
|
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))) {
|
|
62
|
+
info.hasDatabase = true;
|
|
63
|
+
}
|
|
64
|
+
// Detect Redis dependencies
|
|
65
|
+
const redisDeps = ['redis', 'ioredis', 'bull', 'bullmq', 'handy-redis', 'keyv'];
|
|
66
|
+
if (Object.keys(deps).some(dep => redisDeps.includes(dep))) {
|
|
67
|
+
info.hasRedis = true;
|
|
68
|
+
}
|
|
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;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
// Ignore JSON parse errors and continue
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// 2. Read requirements.txt or main.py if Python
|
|
93
|
+
const reqTxtPath = path.join(projectRoot, 'requirements.txt');
|
|
94
|
+
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))) {
|
|
102
|
+
info.hasDatabase = true;
|
|
103
|
+
}
|
|
104
|
+
const redisTerms = ['redis', 'django-redis', 'celery'];
|
|
105
|
+
if (redisTerms.some(term => reqs.toLowerCase().includes(term))) {
|
|
106
|
+
info.hasRedis = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return info;
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureAwsCli(): Promise<boolean>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ensureAwsCli = ensureAwsCli;
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const readline = __importStar(require("readline/promises"));
|
|
39
|
+
async function ensureAwsCli() {
|
|
40
|
+
// 1. Check if AWS CLI is already installed
|
|
41
|
+
try {
|
|
42
|
+
(0, child_process_1.execSync)('aws --version', { stdio: 'ignore' });
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
// AWS CLI not installed
|
|
47
|
+
}
|
|
48
|
+
console.log('\n\x1b[33m⚠️ AWS CLI is not installed on your system.\x1b[0m');
|
|
49
|
+
console.log('The AWS CLI is required to stream container logs and manage local credentials.');
|
|
50
|
+
const rl = readline.createInterface({
|
|
51
|
+
input: process.stdin,
|
|
52
|
+
output: process.stdout,
|
|
53
|
+
});
|
|
54
|
+
const answer = await rl.question('\nWould you like MySystem to install the AWS CLI automatically? (y/n) [y]: ');
|
|
55
|
+
rl.close();
|
|
56
|
+
if (answer.trim().toLowerCase() === 'n') {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
const platform = process.platform;
|
|
60
|
+
console.log(`\n⚙️ Installing AWS CLI for \x1b[36m${platform}\x1b[0m...`);
|
|
61
|
+
try {
|
|
62
|
+
if (platform === 'win32') {
|
|
63
|
+
// Windows: Use winget (native Windows Package Manager)
|
|
64
|
+
console.log('Running winget installer...');
|
|
65
|
+
const res = (0, child_process_1.spawnSync)('winget', ['install', '--id', 'Amazon.AWSCLI', '--silent', '--accept-source-agreements', '--accept-package-agreements'], {
|
|
66
|
+
stdio: 'inherit',
|
|
67
|
+
shell: true,
|
|
68
|
+
});
|
|
69
|
+
if (res.status === 0) {
|
|
70
|
+
console.log('\x1b[32m✅ AWS CLI installed successfully! You may need to restart your terminal for changes to take effect.\x1b[0m');
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
else if (platform === 'darwin') {
|
|
75
|
+
// macOS: Try homebrew first
|
|
76
|
+
let hasBrew = false;
|
|
77
|
+
try {
|
|
78
|
+
(0, child_process_1.execSync)('brew --version', { stdio: 'ignore' });
|
|
79
|
+
hasBrew = true;
|
|
80
|
+
}
|
|
81
|
+
catch (e) { }
|
|
82
|
+
if (hasBrew) {
|
|
83
|
+
console.log('Running: brew install awscli...');
|
|
84
|
+
const res = (0, child_process_1.spawnSync)('brew', ['install', 'awscli'], { stdio: 'inherit' });
|
|
85
|
+
if (res.status === 0) {
|
|
86
|
+
console.log('\x1b[32m✅ AWS CLI installed successfully via Homebrew!\x1b[0m');
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// Fallback: Download pkg installer
|
|
92
|
+
console.log('Downloading AWS CLI macOS package...');
|
|
93
|
+
(0, child_process_1.execSync)('curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "/tmp/AWSCLIV2.pkg"', { stdio: 'inherit' });
|
|
94
|
+
console.log('Installing package (requires sudo privileges)...');
|
|
95
|
+
const res = (0, child_process_1.spawnSync)('sudo', ['installer', '-pkg', '/tmp/AWSCLIV2.pkg', '-target', '/'], { stdio: 'inherit' });
|
|
96
|
+
if (res.status === 0) {
|
|
97
|
+
console.log('\x1b[32m✅ AWS CLI installed successfully!\x1b[0m');
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else if (platform === 'linux') {
|
|
103
|
+
// Linux install
|
|
104
|
+
console.log('Downloading AWS CLI Linux package...');
|
|
105
|
+
(0, child_process_1.execSync)('curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "/tmp/awscliv2.zip"', { stdio: 'inherit' });
|
|
106
|
+
(0, child_process_1.execSync)('unzip -q /tmp/awscliv2.zip -d /tmp', { stdio: 'inherit' });
|
|
107
|
+
console.log('Installing package (requires sudo privileges)...');
|
|
108
|
+
const res = (0, child_process_1.spawnSync)('sudo', ['/tmp/aws/install', '--update'], { stdio: 'inherit' });
|
|
109
|
+
if (res.status === 0) {
|
|
110
|
+
console.log('\x1b[32m✅ AWS CLI installed successfully!\x1b[0m');
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error(`\x1b[31mInstallation failed: ${err.message}\x1b[0m`);
|
|
117
|
+
}
|
|
118
|
+
console.log('\n\x1b[31m❌ Automatic installation failed.\x1b[0m');
|
|
119
|
+
console.log('Please install the AWS CLI manually:');
|
|
120
|
+
console.log('👉 \x1b[36mhttps://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html\x1b[0m\n');
|
|
121
|
+
return false;
|
|
122
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mysystem-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zero-config deployment standard and CLI for AI-generated applications on AWS Fargate",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mysystem": "dist/index.js",
|
|
8
|
+
"mysystem-cli": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"aws",
|
|
16
|
+
"fargate",
|
|
17
|
+
"ecs",
|
|
18
|
+
"deploy",
|
|
19
|
+
"terraform",
|
|
20
|
+
"docker",
|
|
21
|
+
"cursor",
|
|
22
|
+
"ai",
|
|
23
|
+
"agents"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"typescript": "^5.0.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
|
|
4
|
+
interface AuditItem {
|
|
5
|
+
name: string;
|
|
6
|
+
passed: boolean;
|
|
7
|
+
type: 'error' | 'warning';
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function runAudit(projectRoot: string) {
|
|
12
|
+
console.log('\n🔍 Auditing project for production readiness...\n');
|
|
13
|
+
|
|
14
|
+
const items: AuditItem[] = [];
|
|
15
|
+
|
|
16
|
+
// 1. Check Dockerfile
|
|
17
|
+
const dockerfilePath = path.join(projectRoot, 'Dockerfile');
|
|
18
|
+
if (!fs.existsSync(dockerfilePath)) {
|
|
19
|
+
items.push({
|
|
20
|
+
name: 'Dockerfile Exists',
|
|
21
|
+
passed: false,
|
|
22
|
+
type: 'error',
|
|
23
|
+
message: 'No Dockerfile found. Run `npx mysystem init` to generate one.',
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
items.push({
|
|
27
|
+
name: 'Dockerfile Exists',
|
|
28
|
+
passed: true,
|
|
29
|
+
type: 'error',
|
|
30
|
+
message: 'Dockerfile found.',
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const dockerfileContent = fs.readFileSync(dockerfilePath, 'utf8');
|
|
34
|
+
|
|
35
|
+
// Check for non-root USER
|
|
36
|
+
const hasUser = dockerfileContent.includes('USER ');
|
|
37
|
+
items.push({
|
|
38
|
+
name: 'Secure Container User (Non-Root)',
|
|
39
|
+
passed: hasUser,
|
|
40
|
+
type: 'error',
|
|
41
|
+
message: hasUser
|
|
42
|
+
? 'Dockerfile specifies a non-root USER.'
|
|
43
|
+
: 'Dockerfile executes as root. Add a USER instruction for security container hardening.',
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Check for healthcheck
|
|
47
|
+
const hasHealthcheck = dockerfileContent.includes('HEALTHCHECK ');
|
|
48
|
+
items.push({
|
|
49
|
+
name: 'Container Health Check',
|
|
50
|
+
passed: hasHealthcheck,
|
|
51
|
+
type: 'warning',
|
|
52
|
+
message: hasHealthcheck
|
|
53
|
+
? 'Dockerfile specifies a HEALTHCHECK instruction.'
|
|
54
|
+
: 'No HEALTHCHECK found in Dockerfile. ECS needs health checks to detect failing tasks.',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Check CI/CD Workflows
|
|
59
|
+
const workflowPath = path.join(projectRoot, '.github', 'workflows', 'mysystem-deploy.yml');
|
|
60
|
+
const hasWorkflow = fs.existsSync(workflowPath);
|
|
61
|
+
items.push({
|
|
62
|
+
name: 'GitHub Actions CI/CD Pipeline',
|
|
63
|
+
passed: hasWorkflow,
|
|
64
|
+
type: 'error',
|
|
65
|
+
message: hasWorkflow
|
|
66
|
+
? 'GitHub Actions deploy workflow configured.'
|
|
67
|
+
: 'Deployment workflow missing. Make sure `.github/workflows/mysystem-deploy.yml` exists.',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 3. Check Terraform Infrastructure as Code
|
|
71
|
+
const terraformPath = path.join(projectRoot, 'terraform');
|
|
72
|
+
const hasTerraform = fs.existsSync(terraformPath) && fs.readdirSync(terraformPath).some(file => file.endsWith('.tf'));
|
|
73
|
+
items.push({
|
|
74
|
+
name: 'Infrastructure as Code (Terraform)',
|
|
75
|
+
passed: hasTerraform,
|
|
76
|
+
type: 'error',
|
|
77
|
+
message: hasTerraform
|
|
78
|
+
? 'Terraform modules found in `/terraform` directory.'
|
|
79
|
+
: 'Terraform configuration files not found in `/terraform`. Run `npx mysystem init`.',
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 4. Check AGENTS.md rulebook
|
|
83
|
+
const agentsPath = path.join(projectRoot, 'AGENTS.md');
|
|
84
|
+
const hasAgents = fs.existsSync(agentsPath);
|
|
85
|
+
items.push({
|
|
86
|
+
name: 'AI Agent Guidelines (AGENTS.md)',
|
|
87
|
+
passed: hasAgents,
|
|
88
|
+
type: 'warning',
|
|
89
|
+
message: hasAgents
|
|
90
|
+
? 'AGENTS.md rules file configured in root.'
|
|
91
|
+
: 'No AGENTS.md rules found. AI agents won\'t have constraints for production-readiness.',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 5. Secret Scanning (Simple checks for common API Keys/Credentials)
|
|
95
|
+
let foundSecrets = false;
|
|
96
|
+
const filesToScan = scanDirForCodeFiles(projectRoot);
|
|
97
|
+
for (const file of filesToScan) {
|
|
98
|
+
// Skip node_modules, git, terraform, dist, etc.
|
|
99
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
100
|
+
const hasSmdPattern = /aws_access_key_id\s*=\s*['"][A-Z0-9]{20}['"]/gi.test(content) ||
|
|
101
|
+
/aws_secret_access_key\s*=\s*['"][A-Za-z0-9/+=]{40}['"]/gi.test(content) ||
|
|
102
|
+
/db_password\s*=\s*['"][^'"]{6,}['"]/gi.test(content);
|
|
103
|
+
if (hasSmdPattern) {
|
|
104
|
+
foundSecrets = true;
|
|
105
|
+
console.log(`\x1b[31m⚠️ Potential secret/credential leak in file: ${path.relative(projectRoot, file)}\x1b[0m`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
items.push({
|
|
110
|
+
name: 'No Hardcoded Secrets',
|
|
111
|
+
passed: !foundSecrets,
|
|
112
|
+
type: 'error',
|
|
113
|
+
message: !foundSecrets
|
|
114
|
+
? 'No plain-text credentials found in repository files.'
|
|
115
|
+
: 'Potential plain-text credentials detected in codebase. Clean secrets and use environment variables.',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Output Audit Dashboard
|
|
119
|
+
console.log('----------------------------------------------------');
|
|
120
|
+
console.log('\x1b[1mAUDIT REPORT RESULTS\x1b[0m');
|
|
121
|
+
console.log('----------------------------------------------------');
|
|
122
|
+
|
|
123
|
+
let passedCount = 0;
|
|
124
|
+
let errorCount = 0;
|
|
125
|
+
let warningCount = 0;
|
|
126
|
+
|
|
127
|
+
for (const item of items) {
|
|
128
|
+
if (item.passed) {
|
|
129
|
+
console.log(` ✅ \x1b[32m[PASS]\x1b[0m \x1b[1m${item.name}\x1b[0m`);
|
|
130
|
+
console.log(` ${item.message}`);
|
|
131
|
+
passedCount++;
|
|
132
|
+
} else {
|
|
133
|
+
const color = item.type === 'error' ? '\x1b[31m[FAIL]\x1b[0m' : '\x1b[33m[WARN]\x1b[0m';
|
|
134
|
+
console.log(` ❌ ${color} \x1b[1m${item.name}\x1b[0m`);
|
|
135
|
+
console.log(` ${item.message}`);
|
|
136
|
+
if (item.type === 'error') errorCount++;
|
|
137
|
+
else warningCount++;
|
|
138
|
+
}
|
|
139
|
+
console.log('');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const score = Math.round((passedCount / items.length) * 100);
|
|
143
|
+
|
|
144
|
+
console.log('----------------------------------------------------');
|
|
145
|
+
console.log(`Readiness Score: \x1b[1m${score === 100 ? '\x1b[32m' : score >= 70 ? '\x1b[33m' : '\x1b[31m'}${score}%\x1b[0m`);
|
|
146
|
+
console.log(`Summary: ${passedCount} passed | ${errorCount} errors | ${warningCount} warnings`);
|
|
147
|
+
console.log('----------------------------------------------------');
|
|
148
|
+
|
|
149
|
+
if (errorCount > 0) {
|
|
150
|
+
console.log('\x1b[31m❌ Fix all errors before deploying to production.\x1b[0m\n');
|
|
151
|
+
} else if (warningCount > 0) {
|
|
152
|
+
console.log('\x1b[33m⚠️ Resolving warnings is recommended for full production readiness.\x1b[0m\n');
|
|
153
|
+
} else {
|
|
154
|
+
console.log('\x1b[32m🚀 Your application is 100% production ready! Ready to deploy to AWS.\x1b[0m\n');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function scanDirForCodeFiles(dir: string, fileList: string[] = []): string[] {
|
|
159
|
+
const files = fs.readdirSync(dir);
|
|
160
|
+
for (const file of files) {
|
|
161
|
+
const filePath = path.join(dir, file);
|
|
162
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
163
|
+
// Exclude build, node, git, terraform folders
|
|
164
|
+
if (['node_modules', '.git', 'terraform', 'dist', 'build', '.next', 'out'].includes(file)) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
scanDirForCodeFiles(filePath, fileList);
|
|
168
|
+
} else {
|
|
169
|
+
// Only scan code files
|
|
170
|
+
if (/\.(js|ts|tsx|jsx|json|py|env|tfvars)$/.test(file) && file !== 'package-lock.json') {
|
|
171
|
+
fileList.push(filePath);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return fileList;
|
|
176
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as readline from 'readline/promises';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
export async function runDestroy(projectRoot: string) {
|
|
7
|
+
const configPath = path.join(projectRoot, 'mysystem.json');
|
|
8
|
+
if (!fs.existsSync(configPath)) {
|
|
9
|
+
console.error('\x1b[31mError: MySystem is not initialized in this directory.\x1b[0m');
|
|
10
|
+
console.error('Run `npx mysystem init` first.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
15
|
+
|
|
16
|
+
console.log(`\n\x1b[31m⚠️ WARNING: You are about to DESTROY all AWS infrastructure for "${config.name}".\x1b[0m`);
|
|
17
|
+
console.log('This will delete the database (RDS), cache (Redis), server (Fargate), and load balancers.');
|
|
18
|
+
console.log('\x1b[1mALL DATA WILL BE PERMANENTLY LOST.\x1b[0m\n');
|
|
19
|
+
|
|
20
|
+
const rl = readline.createInterface({
|
|
21
|
+
input: process.stdin,
|
|
22
|
+
output: process.stdout,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const confirm = await rl.question('Are you absolutely sure? Type the application name to confirm: ');
|
|
26
|
+
rl.close();
|
|
27
|
+
|
|
28
|
+
if (confirm.trim() !== config.name) {
|
|
29
|
+
console.log('\n❌ Confirmation failed. Destruction cancelled.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('\n🔥 Initiating infrastructure destruction. Please wait...');
|
|
34
|
+
|
|
35
|
+
const tfDir = path.join(projectRoot, 'terraform');
|
|
36
|
+
if (!fs.existsSync(tfDir)) {
|
|
37
|
+
console.error('\x1b[31mError: terraform directory not found.\x1b[0m');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Execute terraform destroy
|
|
42
|
+
// Set stdio: 'inherit' to stream Terraform output directly to user's terminal
|
|
43
|
+
const tf = spawn('terraform', ['destroy', '-auto-approve'], {
|
|
44
|
+
cwd: tfDir,
|
|
45
|
+
stdio: 'inherit',
|
|
46
|
+
shell: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
tf.on('close', (code) => {
|
|
50
|
+
if (code === 0) {
|
|
51
|
+
console.log('\n\x1b[32m✅ Successfully destroyed all AWS resources for this application.\x1b[0m');
|
|
52
|
+
console.log('Your AWS billing for this project has been stopped.');
|
|
53
|
+
|
|
54
|
+
// Remove local generated files if they want, or keep configuration
|
|
55
|
+
try {
|
|
56
|
+
if (fs.existsSync(path.join(projectRoot, 'mysystem.json'))) {
|
|
57
|
+
fs.unlinkSync(path.join(projectRoot, 'mysystem.json'));
|
|
58
|
+
}
|
|
59
|
+
console.log('Deleted local mysystem.json configuration.');
|
|
60
|
+
} catch (e) {}
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`\n\x1b[31m❌ Terraform destroy failed with exit code ${code}.\x1b[0m`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|