@iacmp/cli 1.1.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.
@@ -0,0 +1,351 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/commands/audit-dr.ts
31
+ var audit_dr_exports = {};
32
+ __export(audit_dr_exports, {
33
+ default: () => AuditDR
34
+ });
35
+ module.exports = __toCommonJS(audit_dr_exports);
36
+ var import_core = require("@oclif/core");
37
+ var import_chalk = __toESM(require("chalk"));
38
+
39
+ // src/audit.ts
40
+ var fs2 = __toESM(require("fs"));
41
+ var path = __toESM(require("path"));
42
+
43
+ // src/utils.ts
44
+ var fs = __toESM(require("fs"));
45
+ function readJsonFile(filePath) {
46
+ let content;
47
+ try {
48
+ content = fs.readFileSync(filePath, "utf-8");
49
+ } catch (e) {
50
+ throw new Error(`Falha ao ler '${filePath}': ${errMessage(e)}`);
51
+ }
52
+ try {
53
+ return JSON.parse(content);
54
+ } catch (e) {
55
+ throw new Error(`JSON inv\xE1lido em '${filePath}': ${errMessage(e)}`);
56
+ }
57
+ }
58
+ function errMessage(e) {
59
+ if (e instanceof Error) return e.message;
60
+ if (typeof e === "string") return e;
61
+ try {
62
+ return JSON.stringify(e);
63
+ } catch {
64
+ return String(e);
65
+ }
66
+ }
67
+
68
+ // src/audit.ts
69
+ function readConfig(cwd) {
70
+ const configPath = path.join(cwd, "iacmp.json");
71
+ if (!fs2.existsSync(configPath)) throw new Error("iacmp.json n\xE3o encontrado. Rode: iacmp init");
72
+ const config = readJsonFile(configPath);
73
+ return {
74
+ name: config.name ?? path.basename(cwd),
75
+ provider: config.provider ?? "aws"
76
+ };
77
+ }
78
+ function resolveTsNode(projectDir) {
79
+ const dirs = [];
80
+ let dir = projectDir;
81
+ for (let i = 0; i < 5; i++) {
82
+ dirs.push(dir);
83
+ const parent = path.dirname(dir);
84
+ if (parent === dir) break;
85
+ dir = parent;
86
+ }
87
+ for (const d of dirs) {
88
+ const tsNodePath = path.join(d, "node_modules", "ts-node");
89
+ if (fs2.existsSync(tsNodePath)) return tsNodePath;
90
+ }
91
+ return null;
92
+ }
93
+ function findStackFiles(dir) {
94
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
95
+ const files = [];
96
+ for (const entry of entries) {
97
+ const full = path.join(dir, entry.name);
98
+ if (entry.isDirectory()) {
99
+ files.push(...findStackFiles(full));
100
+ } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) {
101
+ files.push(full);
102
+ }
103
+ }
104
+ return files;
105
+ }
106
+ function loadStacks(cwd) {
107
+ const stacksDir = path.join(cwd, "stacks");
108
+ if (!fs2.existsSync(stacksDir)) throw new Error("Diret\xF3rio stacks/ n\xE3o encontrado.");
109
+ const stackFiles = findStackFiles(stacksDir);
110
+ if (stackFiles.length === 0) throw new Error("Nenhuma stack encontrada em stacks/");
111
+ const tsNodePath = resolveTsNode(cwd);
112
+ if (tsNodePath) {
113
+ require(tsNodePath).register({
114
+ transpileOnly: true,
115
+ skipProject: true,
116
+ compilerOptions: {
117
+ target: "ES2022",
118
+ module: "commonjs",
119
+ moduleResolution: "node",
120
+ esModuleInterop: true,
121
+ strict: false,
122
+ skipLibCheck: true
123
+ }
124
+ });
125
+ }
126
+ const result = [];
127
+ for (const stackPath of stackFiles) {
128
+ const stackName = path.basename(stackPath).replace(/\.(ts|js)$/, "");
129
+ try {
130
+ const mod = require(stackPath);
131
+ const raw = mod.default ?? mod.stack ?? mod;
132
+ if (!raw || typeof raw !== "object" || !("constructs" in raw)) {
133
+ console.warn(`[audit] ${path.basename(stackPath)} n\xE3o exporta uma Stack v\xE1lida \u2014 ignorado.`);
134
+ continue;
135
+ }
136
+ result.push({ name: stackName, stack: raw });
137
+ } catch (err) {
138
+ console.warn(`[audit] falha ao carregar ${path.basename(stackPath)}: ${errMessage(err)}`);
139
+ }
140
+ }
141
+ return result;
142
+ }
143
+ function saveReport(cwd, commandName, content) {
144
+ const auditDir = path.join(cwd, "audit");
145
+ if (!fs2.existsSync(auditDir)) fs2.mkdirSync(auditDir, { recursive: true });
146
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
147
+ const fileName = `${commandName}-${date}.md`;
148
+ const filePath = path.join(auditDir, fileName);
149
+ fs2.writeFileSync(filePath, content, "utf-8");
150
+ return path.join("audit", fileName);
151
+ }
152
+ function today() {
153
+ return (/* @__PURE__ */ new Date()).toLocaleDateString("pt-BR");
154
+ }
155
+
156
+ // src/commands/audit-dr.ts
157
+ function shouldFail(failOn, critical, warnings) {
158
+ if (failOn === "critical") return critical > 0;
159
+ if (failOn === "warning") return critical > 0 || warnings > 0;
160
+ return false;
161
+ }
162
+ function analyzeStack(stackName, stack) {
163
+ const constructs = stack.constructs;
164
+ const checks = [];
165
+ const details = [];
166
+ const buckets = constructs.filter((c) => c.type === "Storage.Bucket");
167
+ const dbs = constructs.filter((c) => c.type === "Database.SQL");
168
+ const vpcs = constructs.filter((c) => c.type === "Network.VPC");
169
+ const instances = constructs.filter((c) => c.type === "Compute.Instance");
170
+ const bucketsWithVersioning = buckets.filter((c) => c.props.versioning === true);
171
+ checks.push({
172
+ label: "Buckets with versioning enabled",
173
+ passed: buckets.length === 0 || bucketsWithVersioning.length === buckets.length
174
+ });
175
+ for (const b of buckets) {
176
+ if (b.props.versioning !== true) {
177
+ details.push({
178
+ level: "no-dr",
179
+ title: `Storage.Bucket '${b.id}' \u2014 no versioning`,
180
+ detail: "Without versioning there is no object history. Deletions or overwrites are unrecoverable.",
181
+ recommendation: "Set `versioning: true`."
182
+ });
183
+ }
184
+ }
185
+ const dbsMultiAz = dbs.filter((c) => c.props.multiAz === true);
186
+ checks.push({
187
+ label: "Multi-AZ database",
188
+ passed: dbs.length === 0 || dbsMultiAz.length === dbs.length
189
+ });
190
+ for (const db of dbs) {
191
+ if (db.props.multiAz !== true) {
192
+ details.push({
193
+ level: "no-dr",
194
+ title: `Database.SQL '${db.id}' \u2014 Single-AZ`,
195
+ detail: "Single-AZ database. In case of AZ failure, restore may take hours.",
196
+ recommendation: "Set `multiAz: true` for automatic failover."
197
+ });
198
+ }
199
+ if (db.props.instanceType === void 0 || db.props.instanceType === null) {
200
+ details.push({
201
+ level: "warning",
202
+ title: `Database.SQL '${db.id}' \u2014 instanceType not defined`,
203
+ detail: "Default instance type may be insufficient for fast restore in DR scenarios.",
204
+ recommendation: "Set `instanceType` explicitly to ensure adequate performance."
205
+ });
206
+ }
207
+ }
208
+ const vpcsMultiAz = vpcs.filter((c) => {
209
+ const maxAzs = c.props.maxAzs;
210
+ return maxAzs !== void 0 && maxAzs >= 2;
211
+ });
212
+ checks.push({
213
+ label: "Network with multiple AZs",
214
+ passed: vpcs.length === 0 || vpcsMultiAz.length === vpcs.length
215
+ });
216
+ for (const vpc of vpcs) {
217
+ const maxAzs = vpc.props.maxAzs;
218
+ if (maxAzs === void 0 || maxAzs < 2) {
219
+ details.push({
220
+ level: "no-dr",
221
+ title: `Network.VPC '${vpc.id}' \u2014 single AZ`,
222
+ detail: `maxAzs is ${maxAzs ?? "not defined"}. Single-AZ network compromises DR.`,
223
+ recommendation: "Set `maxAzs: 2` or more."
224
+ });
225
+ }
226
+ }
227
+ if (dbs.length === 0 && buckets.length === 0) {
228
+ details.push({
229
+ level: "info",
230
+ title: "No persistent state detected",
231
+ detail: "No Database.SQL or Storage.Bucket found. The stack may be stateless."
232
+ });
233
+ }
234
+ if (instances.length > 0 && buckets.length === 0) {
235
+ details.push({
236
+ level: "warning",
237
+ title: "Compute without storage detected",
238
+ detail: "Compute instances found but no storage bucket. Where will data be stored in a DR event?",
239
+ recommendation: "Consider adding a Storage.Bucket for persistent data."
240
+ });
241
+ }
242
+ const passed = checks.filter((c) => c.passed).length;
243
+ const total = checks.length;
244
+ const score = total > 0 ? Math.round(passed / total * 10) : 10;
245
+ return { stackName, checks, details, score };
246
+ }
247
+ function scoreLabel(score) {
248
+ if (score <= 3) return "Critical";
249
+ if (score <= 5) return "Below expectations";
250
+ if (score <= 7) return "Adequate";
251
+ return "Excellent";
252
+ }
253
+ var AuditDR = class _AuditDR extends import_core.Command {
254
+ static description = "Audit stacks for disaster recovery (DR) readiness";
255
+ static examples = [
256
+ "$ iacmp audit-dr",
257
+ "$ iacmp audit-dr --fail-on=critical"
258
+ ];
259
+ static flags = {
260
+ "fail-on": import_core.Flags.string({
261
+ description: "Sai com exit 1 quando h\xE1 achados no n\xEDvel indicado",
262
+ options: ["critical", "warning", "none"],
263
+ default: "none"
264
+ })
265
+ };
266
+ async run() {
267
+ const { flags } = await this.parse(_AuditDR);
268
+ const failOn = flags["fail-on"];
269
+ const cwd = process.cwd();
270
+ let config;
271
+ try {
272
+ config = readConfig(cwd);
273
+ } catch (err) {
274
+ this.error(err.message);
275
+ }
276
+ let stacks;
277
+ try {
278
+ stacks = loadStacks(cwd);
279
+ } catch (err) {
280
+ this.error(err.message);
281
+ }
282
+ const results = [];
283
+ for (const { name, stack } of stacks) {
284
+ results.push(analyzeStack(name, stack));
285
+ }
286
+ const globalScore = results.length > 0 ? Math.round(results.reduce((sum, r) => sum + r.score, 0) / results.length) : 10;
287
+ const scoreColor = globalScore <= 5 ? import_chalk.default.red : globalScore <= 7 ? import_chalk.default.yellow : import_chalk.default.green;
288
+ this.log(import_chalk.default.bold("\nDisaster Recovery (DR) Audit"));
289
+ this.log("\u2500".repeat(40));
290
+ this.log(`DR Score: ${scoreColor(`${globalScore}/10`)} \u2014 ${scoreLabel(globalScore)}`);
291
+ this.log("");
292
+ for (const r of results) {
293
+ for (const d of r.details) {
294
+ if (d.level === "no-dr") this.log(`${import_chalk.default.red("\u2717 [NO DR]")} ${d.title}`);
295
+ else if (d.level === "warning") this.log(`${import_chalk.default.yellow("\u26A0 [WARNING]")} ${d.title}`);
296
+ else if (d.level === "info") this.log(`${import_chalk.default.cyan("\u2139 [INFO]")} ${d.title}`);
297
+ else this.log(`${import_chalk.default.green("\u2713")} ${d.title}`);
298
+ }
299
+ }
300
+ let md = `# Disaster Recovery (DR) Audit Report \u2014 ${config.name}
301
+ `;
302
+ md += `Date: ${today()}
303
+
304
+ `;
305
+ md += `## DR Score
306
+ `;
307
+ md += `${globalScore}/10 \u2014 ${scoreLabel(globalScore)}
308
+
309
+ `;
310
+ for (const r of results) {
311
+ md += `## DR Checklist \u2014 Stack: ${r.stackName}
312
+ `;
313
+ for (const ch of r.checks) {
314
+ md += `- [${ch.passed ? "x" : " "}] ${ch.label}
315
+ `;
316
+ }
317
+ md += "\n";
318
+ }
319
+ md += `## Findings
320
+
321
+ `;
322
+ for (const r of results) {
323
+ for (const d of r.details) {
324
+ const label = d.level === "no-dr" ? "NO DR" : d.level === "warning" ? "WARNING" : d.level === "info" ? "INFO" : "OK";
325
+ md += `### [${label}] ${d.title}
326
+ `;
327
+ md += `Stack: ${r.stackName}
328
+ `;
329
+ md += `${d.detail}
330
+ `;
331
+ if (d.recommendation) md += `Recommendation: ${d.recommendation}
332
+ `;
333
+ md += "\n";
334
+ }
335
+ }
336
+ const relPath = saveReport(cwd, "dr", md);
337
+ this.log(`
338
+ Report saved to ${relPath}`);
339
+ let noDr = 0;
340
+ let warnings = 0;
341
+ for (const r of results) {
342
+ for (const d of r.details) {
343
+ if (d.level === "no-dr") noDr++;
344
+ else if (d.level === "warning") warnings++;
345
+ }
346
+ }
347
+ if (shouldFail(failOn, noDr, warnings)) {
348
+ this.exit(1);
349
+ }
350
+ }
351
+ };