deploy-stack 0.1.0 → 0.2.2

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.
@@ -7,22 +7,27 @@ on:
7
7
  jobs:
8
8
  build-and-publish:
9
9
  runs-on: ubuntu-latest
10
+ permissions:
11
+ contents: read
12
+ id-token: write
13
+
10
14
  steps:
11
15
  - uses: actions/checkout@v4
12
16
 
13
17
  - name: Setup Node.js
14
18
  uses: actions/setup-node@v4
15
19
  with:
16
- node-version: '20.x'
20
+ node-version: '24.x'
17
21
  registry-url: 'https://registry.npmjs.org'
18
-
22
+
23
+ - name: Upgrade NPM for OIDC
24
+ run: npm install -g npm@latest
25
+
19
26
  - name: Inject Telemetry Key
20
- # This securely swaps the placeholder for the real key right before publishing
21
27
  run: sed -i "s/___POSTHOG_INGESTION_KEY___/${{ secrets.POSTHOG_KEY }}/g" src/core/telemetry.js
22
28
 
23
- - run: npm install
29
+ - name: Install dependencies
30
+ run: npm install
24
31
 
25
32
  - name: Publish to NPM
26
- run: npm publish
27
- env:
28
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
33
+ run: npm publish --provenance
package/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
+ ![deploy-stack CLI demonstration](./docs/demo.gif)
8
+
7
9
  ---
8
10
 
9
11
  ## The Problem
@@ -43,10 +45,9 @@ npx deploy-stack
43
45
 
44
46
  The interactive CLI will guide you through the setup:
45
47
  1. **Target Directory / Name:** (Type `.` to bootstrap your current directory)
46
- 2. **Setup Mode:** (Choose **Quickstart** for sensible defaults, or **Advanced** to customize health checks, scaling, and branch names)
47
- 3. **AWS Region:** (e.g., `us-east-2`)
48
- 4. **Compute Tier:** (Choose from Micro, Small, Medium, or Large with live monthly cost estimates)
49
- 5. **Target Port & Framework:** (Generates an optimized multi-stage `Dockerfile`)
48
+ 2. **Setup Mode:** (Choose **Quickstart** for sensible defaults, or **Advanced** for custom scaling and branch names)
49
+ 3. **Zero-Config Detection:** The CLI automatically scans your `package.json` or `requirements.txt` to detect your framework (Next.js, Express, FastAPI, etc.) and dynamically configures your container port.
50
+ 4. **AWS Region & Compute Tier:** (Select your target region and Fargate size with live cost estimates)
50
51
 
51
52
  ---
52
53
 
@@ -107,7 +108,7 @@ your-project/
107
108
 
108
109
  ---
109
110
 
110
- ### 🛡️ Telemetry & Privacy
111
+ ## 🛡️ Telemetry & Privacy
111
112
  By default, `deploy-stack` collects anonymous, hashed usage data to help improve the CLI (e.g., framework presets used, deployment success rates). **No codebase files, AWS credentials, or personal data are ever collected.**
112
113
 
113
114
  To opt out, simply append the flag:
@@ -119,11 +120,12 @@ npx deploy-stack --no-telemetry
119
120
 
120
121
  ## 🗺️ Roadmap
121
122
 
122
- - [x] **POC:** Interactive CLI, ECS Fargate + ALB Terraform generation, GitHub Actions CI/CD, and Secrets sync.
123
- - [x] **MVP:** CloudFront distribution, Remote S3 State locking, OIDC security, and CLI configuration flows.
124
- - [ ] **v0.9:** Zero-config framework detection (auto-discovering Next.js, Express, and FastAPI via `package.json` / `requirements.txt`).
125
- - [ ] **v1.0 (Pro):** Automated RDS PostgreSQL provisioning, custom domain Route 53 / ACM SSL configuration, and environment workspaces (`staging` vs `prod`).
126
- - [ ] **v2.0 (Enterprise):** Private VPC subnets with NAT gateways, AWS WAF integration, and automated compliance scanning.
123
+ - [x] **Core MVP:** Interactive CLI, ECS Fargate + ALB Terraform generation, CI/CD, and Secrets sync.
124
+ - [x] **Production Readiness:** CloudFront distribution, S3 State locking, and OIDC security.
125
+ - [x] **Smart Experience:** Zero-config framework auto-discovery for Next.js, Express, and FastAPI.
126
+ - [ ] **State & Storage:** Automated RDS PostgreSQL provisioning and persistent volumes.
127
+ - [ ] **Advanced Networking:** Custom domains (Route 53), ACM SSL configuration, and environment workspaces (staging/prod).
128
+ - [ ] **Security Hardening:** Private VPC subnets with NAT gateways, and AWS WAF integration.
127
129
 
128
130
  ---
129
131
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-stack",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "Provision production-ready AWS infrastructure and CI/CD pipelines in seconds.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,27 @@
14
14
  "type": "git",
15
15
  "url": "git+https://github.com/anton-codes-iac/deploy-stack.git"
16
16
  },
17
+ "homepage": "https://github.com/anton-codes-iac/deploy-stack#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/anton-codes-iac/deploy-stack/issues"
20
+ },
21
+ "keywords": [
22
+ "aws",
23
+ "terraform",
24
+ "github-actions",
25
+ "ci-cd",
26
+ "ecs",
27
+ "fargate",
28
+ "nextjs-deployment",
29
+ "infrastructure-as-code",
30
+ "oidc",
31
+ "deploy-stack",
32
+ "expressjs",
33
+ "fastapi",
34
+ "nextjs",
35
+ "static-site",
36
+ "nginx"
37
+ ],
17
38
  "license": "MIT",
18
39
  "dependencies": {
19
40
  "@aws-sdk/client-s3": "3.1115.0",
@@ -4,10 +4,11 @@ import fsSync from 'fs';
4
4
  import fs from 'fs/promises';
5
5
  import path from 'path';
6
6
  import { fileURLToPath } from 'url';
7
- import { intro, outro, group, text, select, spinner, cancel, confirm } from '@clack/prompts';
7
+ import { intro, outro, group, text, select, spinner, cancel, confirm, log } from '@clack/prompts';
8
8
  import color from 'picocolors';
9
9
 
10
10
  import { checkDependency } from '../utils/system.js';
11
+ import { detectFramework } from '../utils/detector.js';
11
12
  import { trackEvent } from '../core/telemetry.js';
12
13
 
13
14
  const __filename = fileURLToPath(import.meta.url);
@@ -41,18 +42,58 @@ export async function mainStack() {
41
42
  process.exit(0);
42
43
  }
43
44
 
45
+ const projectName = await text({
46
+ message: 'What is the name of your project? (Type "." to use current directory)',
47
+ placeholder: 'my-web-app',
48
+ validate: (value) => {
49
+ if (!value) return 'Please enter a name.';
50
+ if (value !== '.' && value.includes(' ')) return 'Name cannot contain spaces.';
51
+ },
52
+ });
53
+
54
+ if (typeof projectName === 'symbol') {
55
+ cancel('Operation cancelled.');
56
+ process.exit(0);
57
+ }
58
+
59
+ const actualProjectName = projectName === '.' ? path.basename(process.cwd()) : projectName;
60
+ const targetDir = projectName === '.' ? process.cwd() : path.join(process.cwd(), projectName);
61
+
62
+ // 2.5 Run the scanner
63
+ const detectedFramework = detectFramework(targetDir);
64
+ if (detectedFramework) {
65
+ log.success(`Auto-detected framework: ${detectedFramework.name}`);
66
+ }
67
+
68
+ // 2.6 Resolve the framework
69
+ let finalFramework = detectedFramework ? detectedFramework.id : null;
70
+
71
+ if (!finalFramework) {
72
+ finalFramework = await select({
73
+ message: 'Which framework preset should we configure?',
74
+ options: [
75
+ { value: 'node', label: 'Node.js / Express' },
76
+ { value: 'nextjs', label: 'Next.js (Standalone)' },
77
+ { value: 'python', label: 'Python FastAPI' },
78
+ { value: 'static', label: 'Static Site (Gatsby, React, plain HTML via Nginx)' },
79
+ ],
80
+ });
81
+
82
+ if (typeof finalFramework === 'symbol') {
83
+ cancel('Operation cancelled.');
84
+ process.exit(0);
85
+ }
86
+ }
87
+
88
+ // 2.7 Set intelligent defaults
89
+ let defaultPort = '3000';
90
+
91
+ if (finalFramework === 'static') defaultPort = '80';
92
+ if (finalFramework === 'python') defaultPort = '8000';
93
+
44
94
  // 3. Prompt Configuration Group
45
95
  const project = await group(
46
96
  {
47
- name: () =>
48
- text({
49
- message: 'What is the name of your project? (Type "." to use current directory)',
50
- placeholder: 'my-web-app',
51
- validate: (value) => {
52
- if (!value) return 'Please enter a name.';
53
- if (value !== '.' && value.includes(' ')) return 'Name cannot contain spaces.';
54
- },
55
- }),
56
97
  region: () =>
57
98
  select({
58
99
  message: 'Which AWS region do you want to deploy to?',
@@ -67,11 +108,13 @@ export async function mainStack() {
67
108
  port: () =>
68
109
  text({
69
110
  message: 'What port does your container expose?',
70
- placeholder: '3000',
71
- defaultValue: '3000',
111
+ placeholder: defaultPort,
112
+ defaultValue: defaultPort,
72
113
  }),
73
- framework: () =>
74
- select({
114
+ framework: () => {
115
+ if (detectedFramework) return undefined;
116
+
117
+ return select({
75
118
  message: 'Which framework preset should we configure?',
76
119
  options: [
77
120
  { value: 'node', label: 'Node.js / Express' },
@@ -79,7 +122,8 @@ export async function mainStack() {
79
122
  { value: 'python', label: 'Python FastAPI' },
80
123
  { value: 'static', label: 'Static Site (Gatsby, React, plain HTML via Nginx)' },
81
124
  ],
82
- }),
125
+ });
126
+ },
83
127
  size: () =>
84
128
  select({
85
129
  message: 'Select your Fargate compute size:',
@@ -126,9 +170,6 @@ export async function mainStack() {
126
170
  );
127
171
 
128
172
  // 4. Map the user's choices and update the variables
129
- const actualProjectName = project.name === '.' ? path.basename(process.cwd()) : project.name;
130
- const targetDir = project.name === '.' ? process.cwd() : path.join(process.cwd(), project.name);
131
-
132
173
  const cpu = project.size === 'small' ? '512' : '256';
133
174
  const memory = project.size === 'small' ? '1024' : '512';
134
175
 
@@ -212,7 +253,7 @@ export async function mainStack() {
212
253
  const tfOidcPath = path.join(__dirname, '../../templates', 'terraform', 'oidc.tf');
213
254
  const tfBackendPath = path.join(__dirname, '../../templates', 'terraform', 'backend.tf');
214
255
  const tfCloudfrontPath = path.join(__dirname, '../../templates', 'terraform', 'cloudfront.tf');
215
- const dockerTemplatePath = path.join(__dirname, '../../templates', 'docker', `${project.framework}.Dockerfile`);
256
+ const dockerTemplatePath = path.join(__dirname, '../../templates', 'docker', `${finalFramework}.Dockerfile`);
216
257
  const githubActionPath = path.join(__dirname, '../../templates', 'github', 'deploy.yml');
217
258
  const readmePath = path.join(__dirname, '../../templates', 'README.md');
218
259
  const gitignorePath = path.join(__dirname, '../../templates', '_gitignore');
@@ -293,7 +334,7 @@ export async function mainStack() {
293
334
  // 11. Track the event in telemetry
294
335
  trackEvent('project_provisioned', {
295
336
  projectName: actualProjectName,
296
- framework: project.framework,
337
+ framework: finalFramework,
297
338
  region: project.region,
298
339
  size: project.size,
299
340
  setup_mode: setupType,
@@ -306,7 +347,7 @@ export async function mainStack() {
306
347
  // 12. Provide the Outro, Framework Warnings and Next Steps
307
348
  let frameworkWarnings = '';
308
349
 
309
- if (project.framework === 'nextjs') {
350
+ if (finalFramework === 'nextjs') {
310
351
  frameworkWarnings =
311
352
  color.bgYellow(color.black(' ⚠️ IMPORTANT: NEXT.JS SETUP REQUIRED ')) +
312
353
  color.yellow('\n You must modify your next.config file and create a health check route before deploying.') +
@@ -2,7 +2,7 @@ import crypto from 'crypto';
2
2
 
3
3
  const TELEMETRY_ENDPOINT = 'https://app.posthog.com/capture';
4
4
 
5
- const POSTHOG_API_KEY = '___POSTHOG_INGESTION_KEY___';
5
+ const POSTHOG_API_KEY = 'phc_o2wgA3jVT9rVDiGSDzFAR42zZeiVGhhCY53HXVHUcYGT';
6
6
 
7
7
  export function trackEvent(eventName, properties) {
8
8
  // 1. Respect privacy standards
@@ -0,0 +1,35 @@
1
+ import fsSync from 'fs';
2
+ import path from 'path';
3
+
4
+ export function detectFramework(targetDir) {
5
+ const packageJsonPath = path.join(targetDir, 'package.json');
6
+ const requirementsTxtPath = path.join(targetDir, 'requirements.txt');
7
+
8
+ // 1. Detect Node.js Frameworks
9
+ if (fsSync.existsSync(packageJsonPath)) {
10
+ try {
11
+ const pkg = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf-8'));
12
+ // Merge dependencies and devDependencies to check both
13
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
14
+
15
+ if (deps['next']) return { id: 'nextjs', name: 'Next.js' };
16
+ if (deps['gatsby'] || deps['react-scripts'] || deps['vue']) return { id: 'static', name: 'Static Site' };
17
+ if (deps['express']) return { id: 'node', name: 'Node.js / Express' };
18
+ } catch (e) {
19
+ // Silently fail if package.json is malformed
20
+ }
21
+ }
22
+
23
+ // 2. Detect Python Frameworks
24
+ if (fsSync.existsSync(requirementsTxtPath)) {
25
+ try {
26
+ const reqs = fsSync.readFileSync(requirementsTxtPath, 'utf-8').toLowerCase();
27
+ if (reqs.includes('fastapi')) return { id: 'python', name: 'Python FastAPI' };
28
+ } catch (e) {
29
+ // Silently fail
30
+ }
31
+ }
32
+
33
+ // 3. Fallback
34
+ return null;
35
+ }