deploy-stack 0.9.2 → 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
package/src/commands/init.js
CHANGED
|
@@ -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
|
|
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: '
|
|
46
|
-
placeholder: '
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
326
|
+
// 13.Ensure all analytics are sent before the CLI terminates
|
|
314
327
|
await flushTelemetry();
|
|
315
328
|
}
|
package/src/utils/generator.js
CHANGED
|
@@ -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)\
|
|
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
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
terraform
|
|
81
|
-
|
|
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
|
-
|
|
22
|
+
# Run Gunicorn using the dynamically injected WSGI module
|
|
23
|
+
CMD ["gunicorn", "--bind", "0.0.0.0:{{PORT}}", "--workers", "3", "{{DJANGO_WSGI}}:application"]
|