deploy-stack 0.1.0 → 0.2.1
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/.github/workflows/publish.yml +12 -7
- package/README.md +4 -5
- package/package.json +1 -1
- package/src/commands/init.js +62 -21
- package/src/core/telemetry.js +1 -1
- package/src/utils/detector.js +35 -0
|
@@ -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
|
+
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
|
-
-
|
|
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
|
@@ -43,10 +43,9 @@ npx deploy-stack
|
|
|
43
43
|
|
|
44
44
|
The interactive CLI will guide you through the setup:
|
|
45
45
|
1. **Target Directory / Name:** (Type `.` to bootstrap your current directory)
|
|
46
|
-
2. **Setup Mode:** (Choose **Quickstart** for sensible defaults, or **Advanced**
|
|
47
|
-
3. **
|
|
48
|
-
4. **Compute Tier:** (
|
|
49
|
-
5. **Target Port & Framework:** (Generates an optimized multi-stage `Dockerfile`)
|
|
46
|
+
2. **Setup Mode:** (Choose **Quickstart** for sensible defaults, or **Advanced** for custom scaling and branch names)
|
|
47
|
+
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.
|
|
48
|
+
4. **AWS Region & Compute Tier:** (Select your target region and Fargate size with live cost estimates)
|
|
50
49
|
|
|
51
50
|
---
|
|
52
51
|
|
|
@@ -107,7 +106,7 @@ your-project/
|
|
|
107
106
|
|
|
108
107
|
---
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
## 🛡️ Telemetry & Privacy
|
|
111
110
|
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
111
|
|
|
113
112
|
To opt out, simply append the flag:
|
package/package.json
CHANGED
package/src/commands/init.js
CHANGED
|
@@ -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:
|
|
71
|
-
defaultValue:
|
|
111
|
+
placeholder: defaultPort,
|
|
112
|
+
defaultValue: defaultPort,
|
|
72
113
|
}),
|
|
73
|
-
framework: () =>
|
|
74
|
-
|
|
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', `${
|
|
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:
|
|
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 (
|
|
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.') +
|
package/src/core/telemetry.js
CHANGED
|
@@ -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 = '
|
|
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
|
+
}
|