@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.
- package/LICENSE +21 -0
- package/bin/run.js +73 -0
- package/dist/chat.js +4068 -0
- package/dist/commands/ai.js +3985 -0
- package/dist/commands/audit-all.js +1039 -0
- package/dist/commands/audit-dr.js +351 -0
- package/dist/commands/audit-ha.js +375 -0
- package/dist/commands/audit-improvements.js +373 -0
- package/dist/commands/audit-security.js +351 -0
- package/dist/commands/dashboard.js +417 -0
- package/dist/commands/deploy.js +188 -0
- package/dist/commands/destroy.js +194 -0
- package/dist/commands/diagram.js +896 -0
- package/dist/commands/diff.js +4420 -0
- package/dist/commands/doctor.js +191 -0
- package/dist/commands/init.js +507 -0
- package/dist/commands/ls.js +75 -0
- package/dist/commands/registry.js +170 -0
- package/dist/commands/registry.json +29 -0
- package/dist/commands/synth.js +4458 -0
- package/dist/commands/watch.js +133 -0
- package/dist/index.js +30 -0
- package/oclif.manifest.json +727 -0
- package/package.json +95 -0
|
@@ -0,0 +1,191 @@
|
|
|
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/doctor.ts
|
|
31
|
+
var doctor_exports = {};
|
|
32
|
+
__export(doctor_exports, {
|
|
33
|
+
default: () => Doctor
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(doctor_exports);
|
|
36
|
+
var import_core = require("@oclif/core");
|
|
37
|
+
var import_child_process = require("child_process");
|
|
38
|
+
var path = __toESM(require("path"));
|
|
39
|
+
var fs2 = __toESM(require("fs"));
|
|
40
|
+
|
|
41
|
+
// src/utils.ts
|
|
42
|
+
var fs = __toESM(require("fs"));
|
|
43
|
+
function readJsonFile(filePath) {
|
|
44
|
+
let content;
|
|
45
|
+
try {
|
|
46
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
47
|
+
} catch (e) {
|
|
48
|
+
throw new Error(`Falha ao ler '${filePath}': ${errMessage(e)}`);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(content);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
throw new Error(`JSON inv\xE1lido em '${filePath}': ${errMessage(e)}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function errMessage(e) {
|
|
57
|
+
if (e instanceof Error) return e.message;
|
|
58
|
+
if (typeof e === "string") return e;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.stringify(e);
|
|
61
|
+
} catch {
|
|
62
|
+
return String(e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// src/commands/doctor.ts
|
|
67
|
+
function tryExec(cmd) {
|
|
68
|
+
try {
|
|
69
|
+
return (0, import_child_process.execSync)(cmd, { stdio: "pipe" }).toString().trim();
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function checkNode() {
|
|
75
|
+
const out = tryExec("node --version");
|
|
76
|
+
if (!out) return { label: "Node.js", ok: false, required: true, hint: "Instale em: https://nodejs.org" };
|
|
77
|
+
const version = out.replace("v", "");
|
|
78
|
+
const major = parseInt(version.split(".")[0], 10);
|
|
79
|
+
return {
|
|
80
|
+
label: "Node.js",
|
|
81
|
+
ok: major >= 20,
|
|
82
|
+
required: true,
|
|
83
|
+
value: out,
|
|
84
|
+
hint: major < 20 ? "Node.js 20+ \xE9 necess\xE1rio." : void 0
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function checkNpm() {
|
|
88
|
+
const out = tryExec("npm --version");
|
|
89
|
+
if (!out) return { label: "npm", ok: false, required: true, hint: "Instale Node.js (npm vem junto)." };
|
|
90
|
+
return { label: "npm", ok: true, required: true, value: `v${out}` };
|
|
91
|
+
}
|
|
92
|
+
function checkIacmp() {
|
|
93
|
+
const pkgPath = path.resolve(__dirname, "../../package.json");
|
|
94
|
+
try {
|
|
95
|
+
const pkg = readJsonFile(pkgPath);
|
|
96
|
+
return { label: "iacmp", ok: true, required: false, value: `v${pkg.version}` };
|
|
97
|
+
} catch {
|
|
98
|
+
return { label: "iacmp", ok: false, required: false, hint: "package.json n\xE3o encontrado" };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function checkAwsCli() {
|
|
102
|
+
const out = tryExec("aws --version");
|
|
103
|
+
if (!out) return { label: "AWS CLI", ok: false, required: false, hint: "Instale com: brew install awscli" };
|
|
104
|
+
const version = out.split("/")[1]?.split(" ")[0] ?? out;
|
|
105
|
+
return { label: "AWS CLI", ok: true, required: false, value: version };
|
|
106
|
+
}
|
|
107
|
+
function checkAnthropicKey() {
|
|
108
|
+
const key = process.env.ANTHROPIC_API_KEY;
|
|
109
|
+
return {
|
|
110
|
+
label: "ANTHROPIC_API_KEY",
|
|
111
|
+
ok: true,
|
|
112
|
+
required: false,
|
|
113
|
+
value: key ? "configurado" : "n\xE3o configurado (necess\xE1rio para iacmp ai)"
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
var Doctor = class _Doctor extends import_core.Command {
|
|
117
|
+
static description = "Verifica o ambiente e depend\xEAncias do iacmp";
|
|
118
|
+
static examples = [
|
|
119
|
+
"$ iacmp doctor",
|
|
120
|
+
"$ iacmp doctor --strict"
|
|
121
|
+
];
|
|
122
|
+
static flags = {
|
|
123
|
+
strict: import_core.Flags.boolean({
|
|
124
|
+
description: "Falha (exit 1) tamb\xE9m para checagens opcionais (AWS CLI, etc.)",
|
|
125
|
+
default: false
|
|
126
|
+
})
|
|
127
|
+
};
|
|
128
|
+
async run() {
|
|
129
|
+
const { flags } = await this.parse(_Doctor);
|
|
130
|
+
this.log("Verificando ambiente...\n");
|
|
131
|
+
const checks = [
|
|
132
|
+
checkNode(),
|
|
133
|
+
checkNpm(),
|
|
134
|
+
checkIacmp(),
|
|
135
|
+
checkAwsCli(),
|
|
136
|
+
checkAnthropicKey()
|
|
137
|
+
];
|
|
138
|
+
for (const check of checks) {
|
|
139
|
+
const icon = check.ok ? "\u2713" : "\u2717";
|
|
140
|
+
const status = check.value ? `${check.label} ${check.value}` : check.label;
|
|
141
|
+
if (check.ok) {
|
|
142
|
+
this.log(` ${icon} ${status}`);
|
|
143
|
+
} else {
|
|
144
|
+
this.log(` ${icon} ${check.label} nao encontrado`);
|
|
145
|
+
if (check.hint) {
|
|
146
|
+
this.log(` ${check.hint}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.log("");
|
|
151
|
+
const allOk = checks.every((c) => c.ok);
|
|
152
|
+
if (allOk) {
|
|
153
|
+
this.log("Ambiente OK. Pronto para uso.");
|
|
154
|
+
} else {
|
|
155
|
+
this.log("Alguns itens precisam de aten\xE7\xE3o.");
|
|
156
|
+
}
|
|
157
|
+
const cwd = process.cwd();
|
|
158
|
+
const configPath = path.join(cwd, "iacmp.json");
|
|
159
|
+
if (fs2.existsSync(configPath)) {
|
|
160
|
+
let config = {};
|
|
161
|
+
try {
|
|
162
|
+
config = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
|
|
163
|
+
} catch {
|
|
164
|
+
}
|
|
165
|
+
if (config.plugins && config.plugins.length > 0) {
|
|
166
|
+
this.log("\nPlugins do projeto:");
|
|
167
|
+
for (const pluginName of config.plugins) {
|
|
168
|
+
let found = false;
|
|
169
|
+
let providerNames = [];
|
|
170
|
+
try {
|
|
171
|
+
const pluginPath = require.resolve(pluginName, { paths: [cwd] });
|
|
172
|
+
const mod = require(pluginPath);
|
|
173
|
+
if (Array.isArray(mod.providers)) {
|
|
174
|
+
found = true;
|
|
175
|
+
providerNames = mod.providers.map((p) => p.name);
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
}
|
|
179
|
+
const icon = found ? "\u2713" : "\u2717";
|
|
180
|
+
const detail = found ? `(providers: ${providerNames.join(", ")})` : "n\xE3o encontrado \u2014 rode npm install";
|
|
181
|
+
this.log(` ${icon} ${pluginName} ${detail}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const requiredFailed = checks.some((c) => c.required && !c.ok);
|
|
186
|
+
const optionalFailed = checks.some((c) => !c.required && !c.ok);
|
|
187
|
+
if (requiredFailed || flags.strict && optionalFailed) {
|
|
188
|
+
this.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
@@ -0,0 +1,507 @@
|
|
|
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/init.ts
|
|
31
|
+
var init_exports = {};
|
|
32
|
+
__export(init_exports, {
|
|
33
|
+
default: () => Init
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(init_exports);
|
|
36
|
+
var import_core = require("@oclif/core");
|
|
37
|
+
var import_child_process = require("child_process");
|
|
38
|
+
var fs = __toESM(require("fs"));
|
|
39
|
+
var path = __toESM(require("path"));
|
|
40
|
+
var TEMPLATES = {
|
|
41
|
+
default: {
|
|
42
|
+
description: "Lambda Hello World exposta via API Gateway REST (arquivos separados)",
|
|
43
|
+
constructs: ["Fn.Lambda", "Fn.ApiGateway"],
|
|
44
|
+
stackSubDir: "stacks/compute",
|
|
45
|
+
stackContent: (name) => `import { Stack, Fn } from '@iacmp/core';
|
|
46
|
+
|
|
47
|
+
const stack = new Stack('${name}-lambda');
|
|
48
|
+
|
|
49
|
+
new Fn.Lambda(stack, 'HelloWorldFn', {
|
|
50
|
+
runtime: 'nodejs20',
|
|
51
|
+
handler: 'index.handler',
|
|
52
|
+
code: 'dist/',
|
|
53
|
+
memory: 128,
|
|
54
|
+
timeout: 10,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export default stack;
|
|
58
|
+
`,
|
|
59
|
+
extraFiles: [
|
|
60
|
+
{
|
|
61
|
+
path: "stacks/network/api-gateway-stack.ts",
|
|
62
|
+
content: (name) => `import { Stack, Fn } from '@iacmp/core';
|
|
63
|
+
|
|
64
|
+
const stack = new Stack('${name}-api');
|
|
65
|
+
|
|
66
|
+
new Fn.ApiGateway(stack, 'HelloWorldApi', {
|
|
67
|
+
name: '${name}-api',
|
|
68
|
+
type: 'REST',
|
|
69
|
+
stageName: 'prod',
|
|
70
|
+
cors: true,
|
|
71
|
+
authType: 'NONE',
|
|
72
|
+
routes: [
|
|
73
|
+
{
|
|
74
|
+
method: 'GET',
|
|
75
|
+
path: '/hello',
|
|
76
|
+
lambdaId: 'HelloWorldFn',
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
export default stack;
|
|
82
|
+
`
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
rds: {
|
|
87
|
+
description: "Banco de dados RDS (postgres) com VPC Multi-AZ e r\xE9plica de leitura",
|
|
88
|
+
constructs: ["Network.VPC", "Database.SQL (principal)", "Database.SQL (replica)"],
|
|
89
|
+
stackContent: (name) => `import { Stack, Network, Database } from '@iacmp/core';
|
|
90
|
+
|
|
91
|
+
const stack = new Stack('${name}');
|
|
92
|
+
|
|
93
|
+
new Network.VPC(stack, 'VPC', {
|
|
94
|
+
cidr: '10.0.0.0/16',
|
|
95
|
+
maxAzs: 3,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
new Database.SQL(stack, 'Principal', {
|
|
99
|
+
engine: 'postgres',
|
|
100
|
+
instanceType: 'medium',
|
|
101
|
+
multiAz: true,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
new Database.SQL(stack, 'Replica', {
|
|
105
|
+
engine: 'postgres',
|
|
106
|
+
instanceType: 'small',
|
|
107
|
+
multiAz: false,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export default stack;
|
|
111
|
+
`
|
|
112
|
+
},
|
|
113
|
+
webapp: {
|
|
114
|
+
description: "Site est\xE1tico com VPC, bucket p\xFAblico e bucket privado de assets",
|
|
115
|
+
constructs: ["Network.VPC", "Storage.Bucket (site p\xFAblico)", "Storage.Bucket (assets privados)"],
|
|
116
|
+
stackContent: (name) => `import { Stack, Network, Storage } from '@iacmp/core';
|
|
117
|
+
|
|
118
|
+
const stack = new Stack('${name}');
|
|
119
|
+
|
|
120
|
+
new Network.VPC(stack, 'Rede', {
|
|
121
|
+
cidr: '10.0.0.0/16',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
new Storage.Bucket(stack, 'SiteBucket', {
|
|
125
|
+
versioning: false,
|
|
126
|
+
publicAccess: true,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
new Storage.Bucket(stack, 'AssetsBucket', {
|
|
130
|
+
versioning: true,
|
|
131
|
+
publicAccess: false,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export default stack;
|
|
135
|
+
`
|
|
136
|
+
},
|
|
137
|
+
network: {
|
|
138
|
+
description: "Infraestrutura de rede completa com VPC multi-AZ, bastion e app server",
|
|
139
|
+
constructs: ["Network.VPC", "Compute.Instance (bastion)", "Compute.Instance (app server)"],
|
|
140
|
+
stackContent: (name) => `import { Stack, Network, Compute } from '@iacmp/core';
|
|
141
|
+
|
|
142
|
+
const stack = new Stack('${name}');
|
|
143
|
+
|
|
144
|
+
new Network.VPC(stack, 'VpcPrincipal', {
|
|
145
|
+
cidr: '10.0.0.0/8',
|
|
146
|
+
maxAzs: 3,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
new Compute.Instance(stack, 'Bastion', {
|
|
150
|
+
instanceType: 'small',
|
|
151
|
+
image: 'ubuntu-22.04',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
new Compute.Instance(stack, 'AppServer', {
|
|
155
|
+
instanceType: 'large',
|
|
156
|
+
image: 'ubuntu-22.04',
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export default stack;
|
|
160
|
+
`
|
|
161
|
+
},
|
|
162
|
+
serverless: {
|
|
163
|
+
description: "API serverless com m\xFAltiplas Lambdas e API Gateway",
|
|
164
|
+
constructs: ["Fn.Lambda", "Fn.ApiGateway"],
|
|
165
|
+
stackSubDir: "stacks/compute",
|
|
166
|
+
stackContent: (name) => `import { Stack, Fn } from '@iacmp/core';
|
|
167
|
+
|
|
168
|
+
const stack = new Stack('${name}');
|
|
169
|
+
|
|
170
|
+
new Fn.Lambda(stack, 'HelloFn', {
|
|
171
|
+
runtime: 'nodejs20',
|
|
172
|
+
handler: 'index.handler',
|
|
173
|
+
code: 'dist/',
|
|
174
|
+
memory: 256,
|
|
175
|
+
timeout: 30,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
new Fn.Lambda(stack, 'UsersFn', {
|
|
179
|
+
runtime: 'nodejs20',
|
|
180
|
+
handler: 'index.handler',
|
|
181
|
+
code: 'dist/',
|
|
182
|
+
memory: 256,
|
|
183
|
+
timeout: 30,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
export default stack;
|
|
187
|
+
`,
|
|
188
|
+
extraFiles: [
|
|
189
|
+
{
|
|
190
|
+
path: "stacks/network/api-gateway-stack.ts",
|
|
191
|
+
content: (name) => `import { Stack, Fn } from '@iacmp/core';
|
|
192
|
+
|
|
193
|
+
const stack = new Stack('${name}-api');
|
|
194
|
+
|
|
195
|
+
new Fn.ApiGateway(stack, 'Api', {
|
|
196
|
+
name: '${name}-api',
|
|
197
|
+
type: 'REST',
|
|
198
|
+
stageName: 'prod',
|
|
199
|
+
cors: true,
|
|
200
|
+
authType: 'NONE',
|
|
201
|
+
routes: [
|
|
202
|
+
{ method: 'GET', path: '/hello', lambdaId: 'HelloFn' },
|
|
203
|
+
{ method: 'GET', path: '/users', lambdaId: 'UsersFn' },
|
|
204
|
+
{ method: 'POST', path: '/users', lambdaId: 'UsersFn' },
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
export default stack;
|
|
209
|
+
`
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
fullstack: {
|
|
214
|
+
description: "Aplica\xE7\xE3o completa: VPC, compute, banco postgres e bucket",
|
|
215
|
+
constructs: ["Network.VPC", "Compute.Instance", "Database.SQL", "Storage.Bucket"],
|
|
216
|
+
stackContent: (name) => `import { Stack, Network, Compute, Database, Storage } from '@iacmp/core';
|
|
217
|
+
|
|
218
|
+
const stack = new Stack('${name}');
|
|
219
|
+
|
|
220
|
+
new Network.VPC(stack, 'VPC', {
|
|
221
|
+
cidr: '10.0.0.0/16',
|
|
222
|
+
maxAzs: 3,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
new Compute.Instance(stack, 'App', {
|
|
226
|
+
instanceType: 'medium',
|
|
227
|
+
image: 'ubuntu-22.04',
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
new Database.SQL(stack, 'DB', {
|
|
231
|
+
engine: 'postgres',
|
|
232
|
+
instanceType: 'medium',
|
|
233
|
+
multiAz: true,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
new Storage.Bucket(stack, 'Uploads', {
|
|
237
|
+
versioning: true,
|
|
238
|
+
publicAccess: false,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
export default stack;
|
|
242
|
+
`
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
function testContent(projectName, stackFile) {
|
|
246
|
+
return `import { Stack } from '@iacmp/core';
|
|
247
|
+
import stack from '../stacks/${stackFile}';
|
|
248
|
+
|
|
249
|
+
test('${projectName} stack tem pelo menos um construct', () => {
|
|
250
|
+
expect(stack).toBeInstanceOf(Stack);
|
|
251
|
+
expect(stack.constructs.length).toBeGreaterThan(0);
|
|
252
|
+
});
|
|
253
|
+
`;
|
|
254
|
+
}
|
|
255
|
+
function packageJson(projectName, coreVersion) {
|
|
256
|
+
return JSON.stringify({
|
|
257
|
+
name: projectName,
|
|
258
|
+
version: "0.1.0",
|
|
259
|
+
private: true,
|
|
260
|
+
scripts: {
|
|
261
|
+
build: "tsc",
|
|
262
|
+
watch: "tsc -w",
|
|
263
|
+
test: "jest",
|
|
264
|
+
synth: "iacmp synth",
|
|
265
|
+
deploy: "iacmp deploy"
|
|
266
|
+
},
|
|
267
|
+
dependencies: {
|
|
268
|
+
"@iacmp/core": `^${coreVersion}`
|
|
269
|
+
},
|
|
270
|
+
devDependencies: {
|
|
271
|
+
"@types/jest": "^30",
|
|
272
|
+
"@types/node": "^22",
|
|
273
|
+
jest: "^30",
|
|
274
|
+
"ts-jest": "^29",
|
|
275
|
+
"ts-node": "^10",
|
|
276
|
+
typescript: "~5.5.0"
|
|
277
|
+
},
|
|
278
|
+
jest: {
|
|
279
|
+
preset: "ts-jest",
|
|
280
|
+
testEnvironment: "node"
|
|
281
|
+
}
|
|
282
|
+
}, null, 2) + "\n";
|
|
283
|
+
}
|
|
284
|
+
function tsConfig() {
|
|
285
|
+
return JSON.stringify({
|
|
286
|
+
compilerOptions: {
|
|
287
|
+
target: "ES2022",
|
|
288
|
+
module: "CommonJS",
|
|
289
|
+
moduleResolution: "node",
|
|
290
|
+
lib: ["es2022"],
|
|
291
|
+
declaration: true,
|
|
292
|
+
strict: true,
|
|
293
|
+
noImplicitAny: true,
|
|
294
|
+
strictNullChecks: true,
|
|
295
|
+
noImplicitReturns: true,
|
|
296
|
+
experimentalDecorators: true,
|
|
297
|
+
strictPropertyInitialization: false,
|
|
298
|
+
skipLibCheck: true,
|
|
299
|
+
outDir: "dist",
|
|
300
|
+
rootDir: "."
|
|
301
|
+
// @iacmp/core é resolvido via node_modules após `npm install`
|
|
302
|
+
},
|
|
303
|
+
exclude: ["node_modules", "dist"]
|
|
304
|
+
}, null, 2) + "\n";
|
|
305
|
+
}
|
|
306
|
+
function gitignore() {
|
|
307
|
+
return ["node_modules/", "dist/", "synth-out/", "audit/", "*.js.map", "*.d.ts", ".DS_Store", ".env", ".iacmp/"].join("\n") + "\n";
|
|
308
|
+
}
|
|
309
|
+
function dotenv() {
|
|
310
|
+
return `# Chave da API Anthropic \u2014 necess\xE1ria para usar iacmp ai
|
|
311
|
+
ANTHROPIC_API_KEY=
|
|
312
|
+
|
|
313
|
+
# Token do GitHub Copilot (alternativa ao Anthropic)
|
|
314
|
+
# GITHUB_TOKEN=
|
|
315
|
+
`;
|
|
316
|
+
}
|
|
317
|
+
function githubActionsYml() {
|
|
318
|
+
return `name: iacmp
|
|
319
|
+
|
|
320
|
+
on:
|
|
321
|
+
push:
|
|
322
|
+
branches: [main, master]
|
|
323
|
+
pull_request:
|
|
324
|
+
branches: [main, master]
|
|
325
|
+
|
|
326
|
+
jobs:
|
|
327
|
+
synth:
|
|
328
|
+
name: Synth & Validate
|
|
329
|
+
runs-on: ubuntu-latest
|
|
330
|
+
steps:
|
|
331
|
+
- uses: actions/checkout@v4
|
|
332
|
+
- uses: actions/setup-node@v4
|
|
333
|
+
with:
|
|
334
|
+
node-version: '20'
|
|
335
|
+
cache: 'npm'
|
|
336
|
+
- run: npm ci
|
|
337
|
+
- run: npm install -g iacmp
|
|
338
|
+
- run: iacmp synth
|
|
339
|
+
- run: npm test
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
function gitlabCiYml() {
|
|
343
|
+
return `image: node:20
|
|
344
|
+
|
|
345
|
+
stages:
|
|
346
|
+
- validate
|
|
347
|
+
|
|
348
|
+
synth:
|
|
349
|
+
stage: validate
|
|
350
|
+
cache:
|
|
351
|
+
paths:
|
|
352
|
+
- node_modules/
|
|
353
|
+
script:
|
|
354
|
+
- npm ci
|
|
355
|
+
- npm install -g iacmp
|
|
356
|
+
- iacmp synth
|
|
357
|
+
- npm test
|
|
358
|
+
`;
|
|
359
|
+
}
|
|
360
|
+
var PYTHON_PLACEHOLDER = `# iacmp \u2014 Stack Python (suporte completo dispon\xEDvel na Fase 4)
|
|
361
|
+
#
|
|
362
|
+
# from iacmp_core import Stack, Compute, Storage
|
|
363
|
+
#
|
|
364
|
+
# stack = Stack("minha-stack")
|
|
365
|
+
# Compute.Instance(stack, "Web", { "instanceType": "small", "image": "ubuntu-22.04" })
|
|
366
|
+
`;
|
|
367
|
+
var Init = class _Init extends import_core.Command {
|
|
368
|
+
static description = "Inicializa um novo projeto iacmp. Se um nome for passado, cria a pasta do projeto.";
|
|
369
|
+
static args = {
|
|
370
|
+
name: import_core.Args.string({ description: "Nome do projeto (cria a pasta automaticamente)", required: false })
|
|
371
|
+
};
|
|
372
|
+
static flags = {
|
|
373
|
+
language: import_core.Flags.string({ char: "l", description: "Linguagem (typescript, python)", default: "typescript" }),
|
|
374
|
+
provider: import_core.Flags.string({ char: "p", description: "Provider padr\xE3o (aws, azure, gcp, terraform)", default: "aws" }),
|
|
375
|
+
template: import_core.Flags.string({
|
|
376
|
+
char: "t",
|
|
377
|
+
description: `Template de stack a usar (default, rds, webapp, network, serverless, fullstack)`,
|
|
378
|
+
default: "default"
|
|
379
|
+
}),
|
|
380
|
+
list: import_core.Flags.boolean({ description: "Lista os templates dispon\xEDveis", default: false })
|
|
381
|
+
};
|
|
382
|
+
static examples = [
|
|
383
|
+
"$ iacmp init meu-projeto",
|
|
384
|
+
"$ iacmp init meu-projeto --template rds",
|
|
385
|
+
"$ iacmp init meu-projeto --template webapp --provider azure",
|
|
386
|
+
"$ iacmp init meu-projeto --template serverless",
|
|
387
|
+
"$ iacmp init meu-projeto --template fullstack",
|
|
388
|
+
"$ iacmp init --list"
|
|
389
|
+
];
|
|
390
|
+
async run() {
|
|
391
|
+
const { args, flags } = await this.parse(_Init);
|
|
392
|
+
if (flags.list) {
|
|
393
|
+
this.log("\nTemplates dispon\xEDveis:\n");
|
|
394
|
+
const nameWidth = Math.max(...Object.keys(TEMPLATES).map((k) => k.length)) + 2;
|
|
395
|
+
for (const [name, tpl] of Object.entries(TEMPLATES)) {
|
|
396
|
+
this.log(` ${name.padEnd(nameWidth)} ${tpl.description}`);
|
|
397
|
+
for (const c of tpl.constructs) {
|
|
398
|
+
this.log(` ${" ".repeat(nameWidth)} \xB7 ${c}`);
|
|
399
|
+
}
|
|
400
|
+
this.log("");
|
|
401
|
+
}
|
|
402
|
+
this.log(`Uso: iacmp init meu-projeto --template <nome>`);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const validLanguages = ["typescript", "python"];
|
|
406
|
+
if (!validLanguages.includes(flags.language)) {
|
|
407
|
+
this.error(`Linguagem '${flags.language}' n\xE3o suportada. Use: ${validLanguages.join(", ")}`);
|
|
408
|
+
}
|
|
409
|
+
const validProviders = ["aws", "azure", "gcp", "terraform"];
|
|
410
|
+
if (!validProviders.includes(flags.provider)) {
|
|
411
|
+
this.error(`Provider '${flags.provider}' n\xE3o suportado. Use: ${validProviders.join(", ")}`);
|
|
412
|
+
}
|
|
413
|
+
const template = TEMPLATES[flags.template];
|
|
414
|
+
if (!template) {
|
|
415
|
+
const available = Object.keys(TEMPLATES).join(", ");
|
|
416
|
+
this.error(`Template '${flags.template}' n\xE3o encontrado. Dispon\xEDveis: ${available}
|
|
417
|
+
|
|
418
|
+
Use 'iacmp init --list' para ver todos os templates.`);
|
|
419
|
+
}
|
|
420
|
+
const cwd = process.cwd();
|
|
421
|
+
const projectName = args.name ?? path.basename(cwd);
|
|
422
|
+
const projectDir = args.name ? path.join(cwd, args.name) : cwd;
|
|
423
|
+
if (args.name) {
|
|
424
|
+
if (fs.existsSync(projectDir)) {
|
|
425
|
+
this.error(`A pasta '${args.name}' j\xE1 existe.`);
|
|
426
|
+
}
|
|
427
|
+
fs.mkdirSync(projectDir, { recursive: true });
|
|
428
|
+
} else {
|
|
429
|
+
if (fs.existsSync(path.join(projectDir, "iacmp.json"))) {
|
|
430
|
+
this.error(`Projeto j\xE1 inicializado em ${path.join(projectDir, "iacmp.json")}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const config = { name: projectName, provider: flags.provider, region: "us-east-1", language: flags.language };
|
|
434
|
+
fs.writeFileSync(path.join(projectDir, "iacmp.json"), JSON.stringify(config, null, 2) + "\n");
|
|
435
|
+
fs.writeFileSync(path.join(projectDir, ".gitignore"), gitignore());
|
|
436
|
+
fs.writeFileSync(path.join(projectDir, ".env"), dotenv());
|
|
437
|
+
const stacksDir = path.join(projectDir, "stacks");
|
|
438
|
+
fs.mkdirSync(stacksDir, { recursive: true });
|
|
439
|
+
const stackFileName = `${projectName}-stack.ts`;
|
|
440
|
+
if (flags.language === "typescript") {
|
|
441
|
+
const coreVersion = (() => {
|
|
442
|
+
try {
|
|
443
|
+
const corePkgJson = require.resolve("@iacmp/core/package.json");
|
|
444
|
+
return JSON.parse(fs.readFileSync(corePkgJson, "utf-8")).version;
|
|
445
|
+
} catch {
|
|
446
|
+
return "1.0.0";
|
|
447
|
+
}
|
|
448
|
+
})();
|
|
449
|
+
fs.writeFileSync(path.join(projectDir, "package.json"), packageJson(projectName, coreVersion));
|
|
450
|
+
fs.writeFileSync(path.join(projectDir, "tsconfig.json"), tsConfig());
|
|
451
|
+
const stackSubDir = template.stackSubDir ? path.join(projectDir, template.stackSubDir) : stacksDir;
|
|
452
|
+
fs.mkdirSync(stackSubDir, { recursive: true });
|
|
453
|
+
fs.writeFileSync(path.join(stackSubDir, stackFileName), template.stackContent(projectName));
|
|
454
|
+
if (template.extraFiles) {
|
|
455
|
+
for (const extra of template.extraFiles) {
|
|
456
|
+
const extraPath = path.join(projectDir, extra.path);
|
|
457
|
+
fs.mkdirSync(path.dirname(extraPath), { recursive: true });
|
|
458
|
+
fs.writeFileSync(extraPath, extra.content(projectName));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
const testDir = path.join(projectDir, "test");
|
|
462
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
463
|
+
fs.writeFileSync(
|
|
464
|
+
path.join(testDir, `${projectName}.test.ts`),
|
|
465
|
+
testContent(projectName, `${projectName}-stack`)
|
|
466
|
+
);
|
|
467
|
+
const githubWorkflowsDir = path.join(projectDir, ".github", "workflows");
|
|
468
|
+
fs.mkdirSync(githubWorkflowsDir, { recursive: true });
|
|
469
|
+
fs.writeFileSync(path.join(githubWorkflowsDir, "iacmp.yml"), githubActionsYml());
|
|
470
|
+
fs.writeFileSync(path.join(projectDir, ".gitlab-ci.yml"), gitlabCiYml());
|
|
471
|
+
} else {
|
|
472
|
+
fs.writeFileSync(path.join(stacksDir, "exemplo_stack.py"), PYTHON_PLACEHOLDER);
|
|
473
|
+
}
|
|
474
|
+
try {
|
|
475
|
+
(0, import_child_process.execSync)("git init", { cwd: projectDir, stdio: "pipe" });
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
const rel = args.name ?? ".";
|
|
479
|
+
const isDefault = flags.template === "default";
|
|
480
|
+
const templateLabel = isDefault ? "" : ` (template: ${flags.template})`;
|
|
481
|
+
this.log(`
|
|
482
|
+
Projeto '${projectName}' inicializado${templateLabel}.
|
|
483
|
+
`);
|
|
484
|
+
this.log(` ${rel}/iacmp.json`);
|
|
485
|
+
this.log(` ${rel}/.env`);
|
|
486
|
+
if (flags.language === "typescript") {
|
|
487
|
+
this.log(` ${rel}/package.json`);
|
|
488
|
+
this.log(` ${rel}/tsconfig.json`);
|
|
489
|
+
const stackRelPath = template.stackSubDir ? `${template.stackSubDir}/${stackFileName}` : `stacks/${stackFileName}`;
|
|
490
|
+
this.log(` ${rel}/${stackRelPath}`);
|
|
491
|
+
this.log(` ${rel}/test/${projectName}.test.ts`);
|
|
492
|
+
this.log(` ${rel}/.github/workflows/iacmp.yml`);
|
|
493
|
+
this.log(` ${rel}/.gitlab-ci.yml`);
|
|
494
|
+
}
|
|
495
|
+
if (!isDefault) {
|
|
496
|
+
this.log(`
|
|
497
|
+
Recursos inclu\xEDdos:`);
|
|
498
|
+
for (const c of template.constructs) {
|
|
499
|
+
this.log(` \xB7 ${c}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
this.log("\nPr\xF3ximos passos:");
|
|
503
|
+
if (args.name) this.log(` cd ${args.name}`);
|
|
504
|
+
this.log(" npm install");
|
|
505
|
+
this.log(" iacmp synth");
|
|
506
|
+
}
|
|
507
|
+
};
|