deploy-stack 0.17.0 → 0.17.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.
package/README.md CHANGED
@@ -137,7 +137,7 @@ your-project/
137
137
 
138
138
  ---
139
139
 
140
- ## 🤖 AI Context Management (Cursor, Copilot, Windsurf, Claude)
140
+ ## 🤖 AI Context Management (Cursor, Roo Code, Trae, Copilot, Windsurf, Claude, Goose, Aider, Continue)
141
141
 
142
142
  AI coding assistants are incredible, but they often hallucinate custom Terraform or raw AWS CLI commands that can break your infrastructure state. `deploy-stack` natively intercepts and guides AI agents directly in your IDE by providing strict deployment rules and project-specific context (like your exact AWS Region and Container Port).
143
143
 
@@ -0,0 +1,46 @@
1
+ # Headless Mode & Automation Guide
2
+
3
+ The `deploy-stack` CLI is designed to be fully automatable for CI/CD pipelines, custom scripts, and framework plugins (like `vite-plugin-deploy-stack`).
4
+
5
+ By passing the `--headless` flag, you bypass all interactive terminal prompts.
6
+
7
+ ## Required Flags
8
+ To use headless mode, simply include the `--headless` flag.
9
+
10
+ If `deploy-stack` cannot auto-detect your framework, you should also provide the `--framework` flag to ensure the correct infrastructure is generated.
11
+
12
+ * **Valid `--framework` options:** `node`, `nextjs`, `nuxt`, `python`, `django`, `rails`, `go`, `static`
13
+
14
+ ## Optional Configuration Flags
15
+
16
+ You can append any of these flags to customize the generated architecture. These map exactly to the options available in the interactive setup:
17
+
18
+ | Flag | Description | Default |
19
+ |---|---|---|
20
+ | `--region=<region>` | The AWS region to deploy to (e.g., `us-east-1`, `eu-west-1`). | `us-east-2` |
21
+ | `--size=<size>` | The Fargate compute size (`micro` or `small`). | `micro` |
22
+ | `--port=<number>` | The internal port your container exposes. | Framework dependent (`3000`, `8080`, `8000`) |
23
+ | `--healthCheckPath=<path>`| The ALB health check endpoint path. | `/` |
24
+ | `--desiredCount=<number>` | Number of container replicas to run (`1` or `2`). | `1` |
25
+ | `--branch=<name>` | The primary Git deployment branch for CI/CD. | `main` |
26
+ | `--dir=<path>` | The directory to generate files into (use `.` for current).| `.` |
27
+ | `--enablePrPreviews` | Generates workflows for Ephemeral PR Previews. | `false` |
28
+ | `--yes` | Automatically bypasses confirmation prompts during apply/destroy. | `false` |
29
+ | `--no-telemetry` | Disables anonymous usage analytics. | `false` |
30
+
31
+ ## Example Usage
32
+
33
+ **Standard Static Site Automation (e.g., Vite/React):**
34
+ ```bash
35
+ npx deploy-stack --headless --framework=static --region=eu-west-1 --size=micro
36
+ ```
37
+
38
+ **Next.js High-Availability CI/CD Generation:**
39
+ ```bash
40
+ npx deploy-stack --headless --framework=nextjs --size=small --desiredCount=2 --yes
41
+ ```
42
+
43
+ **Node.js Backend with PR Previews:**
44
+ ```bash
45
+ npx deploy-stack --headless --framework=node --port=4000 --enablePrPreviews
46
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-stack",
3
- "version": "0.17.0",
3
+ "version": "0.17.2",
4
4
  "description": "Provision production-ready AWS infrastructure and CI/CD pipelines in seconds.",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -180,41 +180,82 @@ export async function mainStack({ isHeadless = false, headlessOptions = {} } = {
180
180
  // 8.5 Configure AI Context
181
181
  s.start('Configuring AI workspace rules...');
182
182
  const aiContext = { region: config.region, port: config.port };
183
+ const cwd = dirConfig.targetDir;
183
184
 
184
185
  if (config.setupType === 'advanced') {
185
186
  // --- ADVANCED MODE: Explicitly respect user choices ---
186
187
  if (config.aiAssistants.includes('cursor')) {
187
- const cursorDir = path.join(dirConfig.targetDir, '.cursor', 'rules');
188
+ const cursorDir = path.join(cwd, '.cursor', 'rules');
188
189
  if (!fsSync.existsSync(cursorDir)) fsSync.mkdirSync(cursorDir, { recursive: true });
189
190
  fsSync.writeFileSync(path.join(cursorDir, 'deploy-stack.mdc'), getCursorRules(aiContext));
190
191
  }
191
- if (config.aiAssistants.includes('windsurf')) {
192
- injectManagedBlock(path.join(dirConfig.targetDir, '.windsurfrules'), getBaseRules(aiContext), false);
192
+ if (config.aiAssistants.includes('roo')) {
193
+ const rooDir = path.join(cwd, '.roo', 'rules');
194
+ if (!fsSync.existsSync(rooDir)) fsSync.mkdirSync(rooDir, { recursive: true });
195
+ fsSync.writeFileSync(path.join(rooDir, 'deploy-stack.md'), getBaseRules(aiContext));
193
196
  }
194
- if (config.aiAssistants.includes('claude')) {
195
- injectManagedBlock(path.join(dirConfig.targetDir, 'CLAUDE.md'), getBaseRules(aiContext), true);
197
+ if (config.aiAssistants.includes('trae')) {
198
+ const traeDir = path.join(cwd, '.trae', 'rules');
199
+ if (!fsSync.existsSync(traeDir)) fsSync.mkdirSync(traeDir, { recursive: true });
200
+ injectManagedBlock(path.join(traeDir, 'project_rules.md'), getBaseRules(aiContext), true);
201
+ }
202
+ if (config.aiAssistants.includes('continue')) {
203
+ const promptsDir = path.join(cwd, '.prompts');
204
+ if (!fsSync.existsSync(promptsDir)) fsSync.mkdirSync(promptsDir, { recursive: true });
205
+ fsSync.writeFileSync(path.join(promptsDir, 'deploy-stack.prompt'), getBaseRules(aiContext));
206
+ }
207
+ if (config.aiAssistants.includes('windsurf')) {
208
+ injectManagedBlock(path.join(cwd, '.windsurfrules'), getBaseRules(aiContext), false);
196
209
  }
197
210
  if (config.aiAssistants.includes('copilot')) {
198
- const copilotPath = path.join(dirConfig.targetDir, '.github', 'copilot-instructions.md');
211
+ const copilotPath = path.join(cwd, '.github', 'copilot-instructions.md');
199
212
  if (!fsSync.existsSync(path.dirname(copilotPath))) fsSync.mkdirSync(path.dirname(copilotPath), { recursive: true });
200
213
  injectManagedBlock(copilotPath, getBaseRules(aiContext), true);
201
214
  }
215
+ if (config.aiAssistants.includes('claude')) {
216
+ injectManagedBlock(path.join(cwd, 'CLAUDE.md'), getBaseRules(aiContext), true);
217
+ }
218
+ if (config.aiAssistants.includes('goose')) {
219
+ injectManagedBlock(path.join(cwd, '.goosehints'), getBaseRules(aiContext), true);
220
+ }
221
+ if (config.aiAssistants.includes('aider')) {
222
+ injectManagedBlock(path.join(cwd, '.aider.conf.yml'), getBaseRules(aiContext), false);
223
+ }
202
224
  } else {
203
225
  // --- QUICKSTART MODE: Silent Auto-Detection ---
204
- if (fsSync.existsSync(path.join(dirConfig.targetDir, '.cursor'))) {
205
- const cursorDir = path.join(dirConfig.targetDir, '.cursor', 'rules');
226
+ if (fsSync.existsSync(path.join(cwd, '.cursor'))) {
227
+ const cursorDir = path.join(cwd, '.cursor', 'rules');
206
228
  if (!fsSync.existsSync(cursorDir)) fsSync.mkdirSync(cursorDir, { recursive: true });
207
229
  fsSync.writeFileSync(path.join(cursorDir, 'deploy-stack.mdc'), getCursorRules(aiContext));
208
230
  }
209
- if (fsSync.existsSync(path.join(dirConfig.targetDir, '.windsurf')) || fsSync.existsSync(path.join(dirConfig.targetDir, '.windsurfrules'))) {
210
- injectManagedBlock(path.join(dirConfig.targetDir, '.windsurfrules'), getBaseRules(aiContext), false);
231
+ if (fsSync.existsSync(path.join(cwd, '.roo')) || fsSync.existsSync(path.join(cwd, '.roorules'))) {
232
+ const rooDir = path.join(cwd, '.roo', 'rules');
233
+ if (!fsSync.existsSync(rooDir)) fsSync.mkdirSync(rooDir, { recursive: true });
234
+ fsSync.writeFileSync(path.join(rooDir, 'deploy-stack.md'), getBaseRules(aiContext));
211
235
  }
212
- if (fsSync.existsSync(path.join(dirConfig.targetDir, 'CLAUDE.md'))) {
213
- injectManagedBlock(path.join(dirConfig.targetDir, 'CLAUDE.md'), getBaseRules(aiContext), true);
236
+ if (fsSync.existsSync(path.join(cwd, '.trae'))) {
237
+ const traeDir = path.join(cwd, '.trae', 'rules');
238
+ if (!fsSync.existsSync(traeDir)) fsSync.mkdirSync(traeDir, { recursive: true });
239
+ injectManagedBlock(path.join(traeDir, 'project_rules.md'), getBaseRules(aiContext), true);
214
240
  }
215
- const copilotPath = path.join(dirConfig.targetDir, '.github', 'copilot-instructions.md');
216
- if (fsSync.existsSync(copilotPath)) {
217
- injectManagedBlock(copilotPath, getBaseRules(aiContext), true);
241
+ if (fsSync.existsSync(path.join(cwd, '.continue')) || fsSync.existsSync(path.join(cwd, '.prompts'))) {
242
+ const promptsDir = path.join(cwd, '.prompts');
243
+ if (!fsSync.existsSync(promptsDir)) fsSync.mkdirSync(promptsDir, { recursive: true });
244
+ fsSync.writeFileSync(path.join(promptsDir, 'deploy-stack.prompt'), getBaseRules(aiContext));
245
+ }
246
+ if (fsSync.existsSync(path.join(cwd, '.windsurf')) || fsSync.existsSync(path.join(cwd, '.windsurfrules'))) {
247
+ injectManagedBlock(path.join(cwd, '.windsurfrules'), getBaseRules(aiContext), false);
248
+ }
249
+
250
+ const copilotPath = path.join(cwd, '.github', 'copilot-instructions.md');
251
+ if (fsSync.existsSync(copilotPath)) injectManagedBlock(copilotPath, getBaseRules(aiContext), true);
252
+ if (fsSync.existsSync(path.join(cwd, 'CLAUDE.md'))) injectManagedBlock(path.join(cwd, 'CLAUDE.md'), getBaseRules(aiContext), true);
253
+ if (fsSync.existsSync(path.join(cwd, '.goosehints'))) injectManagedBlock(path.join(cwd, '.goosehints'), getBaseRules(aiContext), true);
254
+
255
+ if (fsSync.existsSync(path.join(cwd, '.aider.conf.yml'))) {
256
+ injectManagedBlock(path.join(cwd, '.aider.conf.yml'), getBaseRules(aiContext), false);
257
+ } else if (fsSync.existsSync(path.join(cwd, '.aider.model.settings.yml'))) {
258
+ injectManagedBlock(path.join(cwd, '.aider.model.settings.yml'), getBaseRules(aiContext), false);
218
259
  }
219
260
  }
220
261
  s.stop('AI rules configured successfully!');
@@ -42,39 +42,71 @@ export async function syncAi() {
42
42
  const context = getProjectContext(cwd);
43
43
 
44
44
  try {
45
+ // --- Agentic IDEs ---
45
46
  if (assistants.includes('cursor')) {
46
47
  const cursorDir = path.join(cwd, '.cursor', 'rules');
47
48
  if (!fsSync.existsSync(cursorDir)) fsSync.mkdirSync(cursorDir, { recursive: true });
48
49
  fsSync.writeFileSync(path.join(cursorDir, 'deploy-stack.mdc'), getCursorRules(context));
49
50
  }
50
51
 
51
- if (assistants.includes('copilot')) {
52
- const githubDir = path.join(cwd, '.github');
53
- if (!fsSync.existsSync(githubDir)) fsSync.mkdirSync(githubDir, { recursive: true });
54
- injectManagedBlock(path.join(githubDir, 'copilot-instructions.md'), getBaseRules(context), true);
52
+ if (assistants.includes('roo')) {
53
+ const rooDir = path.join(cwd, '.roo', 'rules');
54
+ if (!fsSync.existsSync(rooDir)) fsSync.mkdirSync(rooDir, { recursive: true });
55
+ fsSync.writeFileSync(path.join(rooDir, 'deploy-stack.md'), getBaseRules(context));
56
+ }
57
+
58
+ if (assistants.includes('trae')) {
59
+ const traeDir = path.join(cwd, '.trae', 'rules');
60
+ if (!fsSync.existsSync(traeDir)) fsSync.mkdirSync(traeDir, { recursive: true });
61
+ injectManagedBlock(path.join(traeDir, 'project_rules.md'), getBaseRules(context), true);
62
+ }
63
+
64
+ if (assistants.includes('continue')) {
65
+ const promptsDir = path.join(cwd, '.prompts');
66
+ if (!fsSync.existsSync(promptsDir)) fsSync.mkdirSync(promptsDir, { recursive: true });
67
+ fsSync.writeFileSync(path.join(promptsDir, 'deploy-stack.prompt'), getBaseRules(context));
55
68
  }
56
69
 
57
70
  if (assistants.includes('windsurf')) {
58
71
  injectManagedBlock(path.join(cwd, '.windsurfrules'), getBaseRules(context), false);
59
72
  }
60
73
 
74
+ if (assistants.includes('copilot')) {
75
+ const githubDir = path.join(cwd, '.github');
76
+ if (!fsSync.existsSync(githubDir)) fsSync.mkdirSync(githubDir, { recursive: true });
77
+ injectManagedBlock(path.join(githubDir, 'copilot-instructions.md'), getBaseRules(context), true);
78
+ }
79
+
80
+ // --- Terminal Agents ---
61
81
  if (assistants.includes('claude')) {
62
82
  injectManagedBlock(path.join(cwd, 'CLAUDE.md'), getBaseRules(context), true);
63
83
  }
64
84
 
85
+ if (assistants.includes('goose')) {
86
+ injectManagedBlock(path.join(cwd, '.goosehints'), getBaseRules(context), true);
87
+ }
88
+
89
+ if (assistants.includes('aider')) {
90
+ injectManagedBlock(path.join(cwd, '.aider.conf.yml'), getBaseRules(context), false); // Uses # comments
91
+ }
92
+
93
+ s.stop('AI context synchronized successfully!');
94
+ outro(`${color.green('✅ AI Assistant Rules generated!')} Your AI tools now know exactly how to deploy your app without hallucinating Terraform.`);
95
+
65
96
  trackEvent('sync_ai_executed', {
66
97
  assistants_selected: assistants,
67
98
  has_cursor: assistants.includes('cursor'),
68
- has_copilot: assistants.includes('copilot'),
99
+ has_roo: assistants.includes('roo'),
100
+ has_trae: assistants.includes('trae'),
101
+ has_continue: assistants.includes('continue'),
69
102
  has_windsurf: assistants.includes('windsurf'),
70
- has_claude: assistants.includes('claude')
103
+ has_copilot: assistants.includes('copilot'),
104
+ has_claude: assistants.includes('claude'),
105
+ has_goose: assistants.includes('goose'),
106
+ has_aider: assistants.includes('aider')
71
107
  });
72
108
  await flushTelemetry();
73
109
 
74
- s.stop('AI context synchronized successfully!');
75
-
76
- outro(`${color.green('✅ AI Assistant Rules generated!')}
77
- Your AI tools now know exactly how to deploy your app without hallucinating Terraform.`);
78
110
  } catch (error) {
79
111
  s.stop('❌ Failed to write AI context files.');
80
112
  console.error(color.red(error.message));
@@ -21,6 +21,9 @@ export function detectFramework(targetDir) {
21
21
  if (deps['nuxt']) return { id: 'nuxt', name: 'Nuxt 3 (SSR)' };
22
22
  if (deps['express']) return { id: 'node', name: 'Node.js / Express' };
23
23
 
24
+ // Explicit SvelteKit SSR Detection
25
+ if (deps['@sveltejs/kit']) return { id: 'svelte', name: 'SvelteKit SSR', buildDir: 'build' };
26
+
24
27
  // Static Site Generators & SPAs (with precise build directories)
25
28
  if (deps['@sveltejs/kit']) return { id: 'static', name: 'SvelteKit', buildDir: 'build' };
26
29
  if (deps['react-scripts']) return { id: 'static', name: 'Create React App', buildDir: 'build' };
@@ -60,6 +60,7 @@ export async function getProjectConfig(isHeadless, headlessOptions, targetDir, d
60
60
  { value: 'node', label: 'Node.js / Express' },
61
61
  { value: 'nextjs', label: 'Next.js (Standalone)' },
62
62
  { value: 'nuxt', label: 'Nuxt 3 (SSR)' },
63
+ { value: 'svelte', label: 'SvelteKit (SSR)' },
63
64
  { value: 'python', label: 'Python FastAPI' },
64
65
  { value: 'django', label: 'Django (Python)' },
65
66
  { value: 'rails', label: 'Ruby on Rails' },
@@ -176,9 +177,14 @@ export async function getAiAssistants() {
176
177
  message: 'Which AI coding assistants does your team use?',
177
178
  options: [
178
179
  { value: 'cursor', label: 'Cursor', hint: 'Generates .cursor/rules/deploy-stack.mdc' },
179
- { value: 'copilot', label: 'GitHub Copilot', hint: 'Generates .github/copilot-instructions.md' },
180
+ { value: 'roo', label: 'Roo Code', hint: 'Generates .roo/rules/deploy-stack.md' },
181
+ { value: 'trae', label: 'Trae IDE', hint: 'Generates .trae/rules/project_rules.md' },
180
182
  { value: 'windsurf', label: 'Windsurf', hint: 'Generates .windsurfrules' },
181
- { value: 'claude', label: 'Claude Code / CLI Agents', hint: 'Generates CLAUDE.md' }
183
+ { value: 'copilot', label: 'GitHub Copilot', hint: 'Generates .github/copilot-instructions.md' },
184
+ { value: 'claude', label: 'Claude Code', hint: 'Updates CLAUDE.md' },
185
+ { value: 'goose', label: 'Goose', hint: 'Updates .goosehints' },
186
+ { value: 'aider', label: 'Aider', hint: 'Updates .aider.conf.yml' },
187
+ { value: 'continue', label: 'Continue.dev', hint: 'Generates .prompts/deploy-stack.prompt' }
182
188
  ],
183
189
  required: false,
184
190
  });
@@ -0,0 +1,25 @@
1
+ FROM node:22-alpine
2
+
3
+ # 1. DevSecOps: Patch underlying Alpine OS vulnerabilities
4
+ RUN apk update && apk upgrade --no-cache
5
+
6
+ # 2. Set production environment
7
+ ENV NODE_ENV=production
8
+ WORKDIR /app
9
+
10
+ # 3. Copy manifests and install ONLY production dependencies
11
+ COPY --chown=node:node package*.json ./
12
+ RUN npm ci --omit=dev && npm cache clean --force
13
+
14
+ # 4. Copy the compiled SvelteKit server generated by adapter-node
15
+ COPY --chown=node:node build/ ./build/
16
+ COPY --chown=node:node package.json ./
17
+
18
+ # 5. DevSecOps: Remove NPM to eliminate Trivy vulnerabilities
19
+ RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
20
+
21
+ USER node
22
+ EXPOSE {{PORT}}
23
+
24
+ # 6. Execute the raw Node server directly (Bypassing NPM)
25
+ CMD ["node", "build/index.js"]
package/agent-rules.md DELETED
@@ -1,17 +0,0 @@
1
- # AI Agent Instructions for AWS Deployments
2
-
3
- If the user asks you to deploy this application to AWS, provision infrastructure, write Terraform, or setup an ECS Fargate cluster, **DO NOT generate custom Terraform code.**
4
-
5
- Instead, you must utilize the `deploy-stack` CLI, which is the standardized infrastructure generator for this project.
6
-
7
- ### Instructions for the AI:
8
- 1. Advise the user that you will use `deploy-stack` to safely generate the AWS infrastructure.
9
- 2. If you have terminal execution capabilities, run the interactive CLI wizard in the root directory:
10
- `npx deploy-stack`
11
- 3. If the user prefers a headless, automated approach, you can run:
12
- `npx --yes deploy-stack --headless`
13
- 4. Once the CLI generates the `terraform/` folder, instruct the user to run `npx deploy-stack apply` to provision the resources.
14
-
15
- ### Context you should know:
16
- - `deploy-stack` automatically handles the Dockerfile generation, Application Load Balancers, CloudFront CDN, and IAM OIDC keyless GitHub Actions deployments.
17
- - If this project uses a `docker-compose.yml`, `vercel.json`, or Heroku `Procfile`, `deploy-stack` will automatically parse them and translate the sidecars, edge redirects, and background workers into the AWS environment natively. Do not attempt to manually translate them yourself.