deploy-stack 0.9.1 → 0.9.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-stack",
3
- "version": "0.9.1",
3
+ "version": "0.9.4",
4
4
  "description": "Provision production-ready AWS infrastructure and CI/CD pipelines in seconds.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,25 +27,13 @@ export async function mainStack() {
27
27
  // 1. Start the CLI
28
28
  intro(color.bgCyan(color.black(' deploy-stack ☁️ ')));
29
29
 
30
- // 2. Ask the user for project configuration
31
- const setupType = await select({
32
- message: 'Choose your setup mode:',
33
- options: [
34
- { value: 'quick', label: '⚡ Quickstart (Recommended)', hint: 'Production defaults, minimal prompts' },
35
- { value: 'advanced', label: '🛠️ Advanced Configuration', hint: 'Customize health checks, task count, branch, etc.' },
36
- ],
37
- });
38
-
39
- if (typeof setupType === 'symbol') {
40
- cancel('Operation cancelled.');
41
- process.exit(0);
42
- }
43
-
30
+ // 2. Ask for the target directory FIRST
44
31
  const projectName = await text({
45
- message: 'What is the name of your project? (Type "." to use current directory)',
46
- placeholder: 'my-web-app',
32
+ message: 'Where should we generate the infrastructure? (Type "." for current directory)',
33
+ placeholder: '.',
34
+ initialValue: '.',
47
35
  validate: (value) => {
48
- if (!value) return 'Please enter a name.';
36
+ if (!value) return 'Please enter a name or directory.';
49
37
  if (value !== '.' && value.includes(' ')) return 'Name cannot contain spaces.';
50
38
  },
51
39
  });
@@ -88,7 +76,32 @@ export async function mainStack() {
88
76
  }
89
77
  }
90
78
 
91
- // 2.7 Set intelligent defaults
79
+ // --- DJANGO SPECIFIC PROMPT ---
80
+ let djangoWsgi = 'core.wsgi';
81
+ if (finalFramework === 'django') {
82
+ djangoWsgi = await text({
83
+ message: 'What is the Python module path to your Django wsgi.py?',
84
+ placeholder: 'core.wsgi',
85
+ initialValue: 'core.wsgi',
86
+ });
87
+ if (typeof djangoWsgi === 'symbol') process.exit(0);
88
+ }
89
+
90
+ // 3. Prompt for Setup Mode
91
+ const setupType = await select({
92
+ message: 'Choose your setup mode:',
93
+ options: [
94
+ { value: 'quick', label: '⚡ Quickstart (Recommended)', hint: 'Production defaults, minimal prompts' },
95
+ { value: 'advanced', label: '🛠️ Advanced Configuration', hint: 'Customize health checks, task count, branch, etc.' },
96
+ ],
97
+ });
98
+
99
+ if (typeof setupType === 'symbol') {
100
+ cancel('Operation cancelled.');
101
+ process.exit(0);
102
+ }
103
+
104
+ // 4. Set intelligent defaults & check current Git branch
92
105
  let defaultPort = '3000';
93
106
 
94
107
  if (finalFramework === 'static') defaultPort = '8080';
@@ -96,7 +109,6 @@ export async function mainStack() {
96
109
  if (finalFramework === 'rails') defaultPort = '3000';
97
110
  if (finalFramework === 'go') defaultPort = '8080';
98
111
 
99
- // 2.8 Check current Git branch
100
112
  let currentGitBranch = 'main';
101
113
  try {
102
114
  currentGitBranch = execSync('git symbolic-ref --short HEAD', { cwd: targetDir, stdio: 'pipe' }).toString().trim();
@@ -104,7 +116,7 @@ export async function mainStack() {
104
116
  // Not a git repo yet, fallback to 'main'
105
117
  }
106
118
 
107
- // 2.9 Ask for Managed Database (Only for Backend/Fullstack Frameworks)
119
+ // 5. Ask for Managed Database (Only for Backend/Fullstack Frameworks)
108
120
  let needsDatabase = false;
109
121
  const isBackendFramework = ['node', 'nextjs', 'nuxt', 'python', 'django', 'rails', 'go'].includes(finalFramework);
110
122
 
@@ -121,7 +133,7 @@ export async function mainStack() {
121
133
  needsDatabase = dbChoice;
122
134
  }
123
135
 
124
- // 3. Prompt Configuration Group
136
+ // 6. Prompt Configuration Group
125
137
  const project = await group(
126
138
  {
127
139
  region: () =>
@@ -186,7 +198,7 @@ export async function mainStack() {
186
198
  }
187
199
  );
188
200
 
189
- // 4. Map the user's choices and update the variables
201
+ // 7. Map the user's choices and update the variables
190
202
  const cpu = project.size === 'small' ? '512' : '256';
191
203
  const memory = project.size === 'small' ? '1024' : '512';
192
204
 
@@ -202,7 +214,7 @@ export async function mainStack() {
202
214
  const deployBranch = project.branch || currentGitBranch;
203
215
  const buildDir = detectedFramework?.buildDir || 'dist';
204
216
 
205
- // 4.5. The Pre-Flight Cost Estimator
217
+ // 7.5. The Pre-Flight Cost Estimator
206
218
  // We explicitly ask for financial consent to eliminate AWS billing anxiety.
207
219
  console.log(''); // Add a blank line for visual pacing
208
220
  const costConsent = await confirm({
@@ -215,13 +227,13 @@ export async function mainStack() {
215
227
  process.exit(0);
216
228
  }
217
229
 
218
- // 5. Safely handle existing files (Backup and auto-prune)
230
+ // 8. Safely handle existing files (Backup and auto-prune)
219
231
  await handleExistingFiles(targetDir);
220
232
 
221
233
  const s = spinner();
222
234
  s.start('Provisioning infrastructure...');
223
235
 
224
- // 6. Provision S3 bucket for Terraform state & enable versioning
236
+ // 9. Provision S3 bucket for Terraform state & enable versioning
225
237
  let awsAccountId, stateBucketName;
226
238
  try {
227
239
  const bucketData = await provisionStateBucket(project.region, actualProjectName);
@@ -237,7 +249,7 @@ export async function mainStack() {
237
249
 
238
250
  s.message('Synthesizing Terraform templates...');
239
251
 
240
- // 7. Generate all templates and directories
252
+ // 10. Generate all templates and directories
241
253
  await generateTemplates(targetDir, {
242
254
  PROJECT_NAME: actualProjectName,
243
255
  REGION: project.region,
@@ -253,10 +265,11 @@ export async function mainStack() {
253
265
  DEPLOY_BRANCH: deployBranch,
254
266
  BUILD_DIR: buildDir,
255
267
  finalFramework: finalFramework,
256
- NEEDS_DATABASE: needsDatabase
268
+ NEEDS_DATABASE: needsDatabase,
269
+ DJANGO_WSGI: djangoWsgi
257
270
  });
258
271
 
259
- // 8. Track the event in telemetry
272
+ // 11. Track the event in telemetry
260
273
  trackEvent('project_provisioned', {
261
274
  projectName: actualProjectName,
262
275
  framework: finalFramework,
@@ -270,7 +283,7 @@ export async function mainStack() {
270
283
 
271
284
  s.stop('Infrastructure provisioned successfully!');
272
285
 
273
- // 9. Provide the Outro, Framework Warnings and Next Steps
286
+ // 12. Provide the Outro, Framework Warnings and Next Steps
274
287
  let frameworkWarnings = '';
275
288
  if (!(finalFramework === 'static' && detectedFramework?.buildDir)) {
276
289
  frameworkWarnings = getFrameworkWarning(finalFramework);
@@ -310,6 +323,6 @@ export async function mainStack() {
310
323
  ${color.underline('https://calendly.com/anton-codes-iac/15min')}
311
324
  `);
312
325
 
313
- // 10.Ensure all analytics are sent before the CLI terminates
326
+ // 13.Ensure all analytics are sent before the CLI terminates
314
327
  await flushTelemetry();
315
328
  }
@@ -65,7 +65,7 @@ export async function generateTemplates(targetDir, config) {
65
65
  } else {
66
66
  const existingGitignore = await fs.readFile(targetGitignore, 'utf-8');
67
67
  if (!existingGitignore.includes('terraform/.terraform/')) {
68
- const appendIgnore = '\n# Added by deploy-stack (Terraform)\nterraform/.terraform/\nterraform/*.tfstate\nterraform/*.tfstate.backup\nterraform/.terraform.lock.hcl\nterraform/secret_keys.json\nterraform/.terraform.*\n.env\n';
68
+ const appendIgnore = '\n# Added by deploy-stack (Terraform)\n**/.terraform/*\n**/*.tfstate\n**/*.tfstate.backup\n**/.terraform.lock.hcl\n**/secret_keys.json\n.env\n';
69
69
  await fs.appendFile(targetGitignore, appendIgnore);
70
70
  }
71
71
  }
@@ -74,12 +74,11 @@ export async function generateTemplates(targetDir, config) {
74
74
  function getGitignoreContent(framework) {
75
75
  const baseIgnore = `
76
76
  # Infrastructure (deploy-stack)
77
- terraform/.terraform/
78
- terraform/*.tfstate
79
- terraform/*.tfstate.backup
80
- terraform/.terraform.lock.hcl
81
- terraform/secret_keys.json
82
- terraform/.terraform.*
77
+ **/.terraform/*
78
+ **/*.tfstate
79
+ **/*.tfstate.backup
80
+ **/.terraform.lock.hcl
81
+ **/secret_keys.json
83
82
  .env
84
83
 
85
84
  # OS
@@ -8,6 +8,9 @@ RUN groupadd -g 1001 appgroup && \
8
8
  useradd -u 1001 -g appgroup -s /bin/sh -m appuser
9
9
 
10
10
  COPY requirements.txt .
11
+
12
+ # Patch core python packaging tools to resolve CVEs
13
+ RUN pip install --upgrade pip setuptools wheel jaraco.context
11
14
  RUN pip install --no-cache-dir -r requirements.txt gunicorn
12
15
 
13
16
  # Copy code with explicit ownership
@@ -16,4 +19,5 @@ COPY --chown=appuser:appgroup . .
16
19
  USER appuser
17
20
  EXPOSE {{PORT}}
18
21
 
19
- CMD ["gunicorn", "--bind", "0.0.0.0:{{PORT}}", "--workers", "3", "your_project.wsgi:application"]
22
+ # Run Gunicorn using the dynamically injected WSGI module
23
+ CMD ["gunicorn", "--bind", "0.0.0.0:{{PORT}}", "--workers", "3", "{{DJANGO_WSGI}}:application"]
@@ -24,7 +24,7 @@ jobs:
24
24
 
25
25
  steps:
26
26
  - name: Checkout Code
27
- uses: actions/checkout@v5
27
+ uses: actions/checkout@v4
28
28
 
29
29
  # --- 1. IaC SECURITY SCAN ---
30
30
  - name: Scan Terraform for Misconfigurations
@@ -59,7 +59,7 @@ jobs:
59
59
  # --- 3. BUILD & PUSH ---
60
60
  - name: Login to Amazon ECR
61
61
  id: login-ecr
62
- uses: aws-actions/amazon-ecr-login@v3
62
+ uses: aws-actions/amazon-ecr-login@v2
63
63
 
64
64
  - name: Build Docker image
65
65
  id: build-image