deploy-stack 0.6.0 → 0.8.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/package.json +3 -2
- package/src/commands/init.js +3 -15
- package/src/utils/backup.js +60 -0
- package/templates/terraform/main.tf +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deploy-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Provision production-ready AWS infrastructure and CI/CD pipelines in seconds.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"@aws-sdk/client-dynamodb": "3.1119.0",
|
|
40
41
|
"@aws-sdk/client-s3": "3.1115.0",
|
|
41
42
|
"@aws-sdk/client-secrets-manager": "3.1112.0",
|
|
42
43
|
"@aws-sdk/client-sts": "3.1115.0",
|
|
@@ -44,4 +45,4 @@
|
|
|
44
45
|
"dotenv": "17.4.2",
|
|
45
46
|
"picocolors": "1.1.1"
|
|
46
47
|
}
|
|
47
|
-
}
|
|
48
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -11,6 +11,7 @@ import { trackEvent, flushTelemetry } from '../core/telemetry.js';
|
|
|
11
11
|
import { getFrameworkWarning } from '../utils/warnings.js';
|
|
12
12
|
import { provisionStateBucket } from '../utils/aws.js';
|
|
13
13
|
import { generateTemplates } from '../utils/generator.js';
|
|
14
|
+
import { handleExistingFiles } from '../utils/backup.js';
|
|
14
15
|
|
|
15
16
|
export async function mainStack() {
|
|
16
17
|
const startTime = Date.now();
|
|
@@ -175,21 +176,8 @@ export async function mainStack() {
|
|
|
175
176
|
|
|
176
177
|
const buildDir = detectedFramework?.buildDir || 'dist';
|
|
177
178
|
|
|
178
|
-
// 5.
|
|
179
|
-
|
|
180
|
-
const tfDirPath = path.join(targetDir, 'terraform');
|
|
181
|
-
|
|
182
|
-
if (fsSync.existsSync(dockerfilePath) || fsSync.existsSync(tfDirPath)) {
|
|
183
|
-
const overwrite = await confirm({
|
|
184
|
-
message: color.yellow('⚠️ A Dockerfile or terraform/ folder already exists here. Overwrite them?'),
|
|
185
|
-
initialValue: false,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (!overwrite) {
|
|
189
|
-
cancel('Provisioning cancelled to protect existing files.');
|
|
190
|
-
process.exit(0);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
179
|
+
// 5. Safely handle existing files (Backup and auto-prune)
|
|
180
|
+
await handleExistingFiles(targetDir);
|
|
193
181
|
|
|
194
182
|
const s = spinner();
|
|
195
183
|
s.start('Provisioning infrastructure...');
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { select, isCancel, cancel } from '@clack/prompts';
|
|
4
|
+
import color from 'picocolors';
|
|
5
|
+
|
|
6
|
+
export async function handleExistingFiles(targetDir) {
|
|
7
|
+
const tfPath = path.join(targetDir, 'terraform');
|
|
8
|
+
const dockerfilePath = path.join(targetDir, 'Dockerfile');
|
|
9
|
+
const wfDir = path.join(targetDir, '.github', 'workflows');
|
|
10
|
+
const wfPath = path.join(wfDir, 'deploy.yml');
|
|
11
|
+
|
|
12
|
+
const tfExists = fs.existsSync(tfPath);
|
|
13
|
+
const dockerfileExists = fs.existsSync(dockerfilePath);
|
|
14
|
+
const wfExists = fs.existsSync(wfPath);
|
|
15
|
+
|
|
16
|
+
if (!tfExists && !dockerfileExists && !wfExists) {
|
|
17
|
+
return; // Clean slate
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const overwriteDecision = await select({
|
|
21
|
+
message: color.yellow('⚠️ deploy-stack configurations already exist. What would you like to do?'),
|
|
22
|
+
options: [
|
|
23
|
+
{ value: 'cancel', label: 'Cancel', hint: 'Exit without making changes' },
|
|
24
|
+
{ value: 'backup', label: 'Backup & Regenerate', hint: 'Move old configs to .bak (ignored by Git) and regenerate' }
|
|
25
|
+
]
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (isCancel(overwriteDecision) || overwriteDecision === 'cancel') {
|
|
29
|
+
cancel('Operation cancelled to protect existing files.');
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Execute the safe backup
|
|
34
|
+
const timestamp = Date.now();
|
|
35
|
+
|
|
36
|
+
if (tfExists) fs.renameSync(tfPath, `${tfPath}.bak.${timestamp}`);
|
|
37
|
+
if (dockerfileExists) fs.renameSync(dockerfilePath, `${dockerfilePath}.bak.${timestamp}`);
|
|
38
|
+
if (wfExists) fs.renameSync(wfPath, `${wfPath}.bak.${timestamp}`);
|
|
39
|
+
|
|
40
|
+
ensureGitIgnore(targetDir);
|
|
41
|
+
|
|
42
|
+
console.log(color.cyan(`ℹ️ Existing configuration files were safely backed up locally.`));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Appends backup patterns to .gitignore so local clutter never reaches GitHub.
|
|
47
|
+
*/
|
|
48
|
+
function ensureGitIgnore(targetDir) {
|
|
49
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
50
|
+
const ignoreRules = '\n# deploy-stack backups\n*.bak.*\n';
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(gitignorePath)) {
|
|
53
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
54
|
+
if (!content.includes('*.bak.*')) {
|
|
55
|
+
fs.appendFileSync(gitignorePath, ignoreRules);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
fs.writeFileSync(gitignorePath, ignoreRules);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
# deploy-stack generated infrastructure
|
|
2
2
|
provider "aws" {
|
|
3
3
|
region = "{{REGION}}"
|
|
4
|
+
|
|
5
|
+
default_tags {
|
|
6
|
+
tags = {
|
|
7
|
+
ManagedBy = "deploy-stack"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
4
10
|
}
|
|
5
11
|
|
|
6
12
|
locals {
|
|
@@ -191,4 +197,21 @@ output "website_url" {
|
|
|
191
197
|
output "ecr_repository_url" {
|
|
192
198
|
description = "The URL of the ECR repository"
|
|
193
199
|
value = aws_ecr_repository.app.repository_url
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# --- CloudWatch Alarms ---
|
|
203
|
+
resource "aws_cloudwatch_metric_alarm" "alb_5xx_errors" {
|
|
204
|
+
alarm_name = "{{PROJECT_NAME}}-high-5xx-errors"
|
|
205
|
+
comparison_operator = "GreaterThanThreshold"
|
|
206
|
+
evaluation_periods = "2"
|
|
207
|
+
metric_name = "HTTPCode_Target_5XX_Count"
|
|
208
|
+
namespace = "AWS/ApplicationELB"
|
|
209
|
+
period = "60"
|
|
210
|
+
statistic = "Sum"
|
|
211
|
+
threshold = "10"
|
|
212
|
+
alarm_description = "Triggers if the ALB receives more than 10 5XX errors in 2 minutes."
|
|
213
|
+
|
|
214
|
+
dimensions = {
|
|
215
|
+
LoadBalancer = aws_lb.main.arn_suffix
|
|
216
|
+
}
|
|
194
217
|
}
|