deploy-stack 0.18.0 → 0.18.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.
@@ -5,6 +5,7 @@ on:
5
5
  branches: [main]
6
6
  pull_request:
7
7
  workflow_dispatch:
8
+ workflow_call:
8
9
 
9
10
  jobs:
10
11
  validate:
@@ -32,10 +33,15 @@ jobs:
32
33
  github_token: ${{ secrets.GITHUB_TOKEN }}
33
34
 
34
35
  - name: Scaffold app (${{ matrix.framework }})
36
+ env:
37
+ CI_MOCK_AWS: 'true'
38
+ DO_NOT_TRACK: '1'
35
39
  run: |
36
- TMPDIR=$(mktemp -d)
37
- echo "GENERATED_DIR=$TMPDIR" >> "$GITHUB_ENV"
38
- cd "$TMPDIR"
40
+ TMPROOT=$(mktemp -d)
41
+ APP_DIR="$TMPROOT/test-app-${{ matrix.framework }}"
42
+ mkdir -p "$APP_DIR"
43
+ echo "GENERATED_DIR=$APP_DIR" >> "$GITHUB_ENV"
44
+ cd "$APP_DIR"
39
45
  node "$GITHUB_WORKSPACE/bin/cli.js" --headless --preconfigured --framework=${{ matrix.framework }}
40
46
 
41
47
  - name: Terraform Compile
@@ -52,11 +58,66 @@ jobs:
52
58
  working-directory: ${{ env.GENERATED_DIR }}/terraform
53
59
  run: tflint
54
60
 
55
- - name: DevSecOps Scan
56
- uses: aquasecurity/trivy-action@master
61
+ - name: DevSecOps Scan (IaC & Filesystem)
62
+ uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
57
63
  with:
58
64
  scan-type: 'fs'
59
65
  scan-ref: ${{ env.GENERATED_DIR }}
60
66
  format: 'table'
67
+ output: 'trivy-fs-results.txt'
61
68
  severity: 'HIGH,CRITICAL'
62
- exit-code: '1'
69
+ exit-code: '0'
70
+
71
+ - name: Build Container Image
72
+ working-directory: ${{ env.GENERATED_DIR }}
73
+ run: |
74
+ awk '
75
+ /^COPY/ { next }
76
+ /^RUN/ {
77
+ if ($0 ~ /apk |apt-get |apt |addgroup |adduser |groupadd |useradd |gem install |mkdir |chown |rm /) {
78
+ print $0
79
+ skip = 0
80
+ } else {
81
+ skip = 1
82
+ }
83
+ next
84
+ }
85
+ {
86
+ if (skip) {
87
+ # If the line does NOT end with a backslash, the skipped block is over
88
+ if ($0 !~ /\\ *$/) skip = 0
89
+ } else {
90
+ print $0
91
+ }
92
+ }
93
+ ' Dockerfile > Dockerfile.tmp && mv Dockerfile.tmp Dockerfile
94
+
95
+ # Print the patched Dockerfile to the logs for easy debugging
96
+ echo "Patched Dockerfile:"
97
+ cat Dockerfile
98
+ echo "-------------------"
99
+
100
+ docker build -t test-app-${{ matrix.framework }}:latest .
101
+
102
+ - name: DevSecOps Scan (Container Image)
103
+ uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25
104
+ with:
105
+ image-ref: 'test-app-${{ matrix.framework }}:latest'
106
+ format: 'table'
107
+ output: 'trivy-image-results.txt'
108
+ severity: 'HIGH,CRITICAL'
109
+ exit-code: '0'
110
+
111
+ - name: Publish Trivy Summary to GitHub Actions UI
112
+ if: always()
113
+ run: |
114
+ echo "### 🏗️ Terraform Misconfiguration Scan (${{ matrix.framework }})" >> $GITHUB_STEP_SUMMARY
115
+ echo "\`\`\`text" >> $GITHUB_STEP_SUMMARY
116
+ if [ -f trivy-fs-results.txt ]; then cat trivy-fs-results.txt >> $GITHUB_STEP_SUMMARY; else echo "Scan failed or skipped."; fi
117
+ echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
118
+ echo "" >> $GITHUB_STEP_SUMMARY
119
+
120
+ echo "### 🛡️ Container Security Scan Results (${{ matrix.framework }})" >> $GITHUB_STEP_SUMMARY
121
+ echo "\`\`\`text" >> $GITHUB_STEP_SUMMARY
122
+ if [ -f trivy-image-results.txt ]; then cat trivy-image-results.txt >> $GITHUB_STEP_SUMMARY; else echo "Container scan failed or skipped."; fi
123
+ echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
@@ -5,7 +5,12 @@ on:
5
5
  types: [published]
6
6
 
7
7
  jobs:
8
+ validate:
9
+ uses: ./.github/workflows/iac-validation.yml
10
+ secrets: inherit
11
+
8
12
  build-and-publish:
13
+ needs: [validate]
9
14
  runs-on: ubuntu-latest
10
15
  permissions:
11
16
  contents: read
package/.muserules CHANGED
@@ -13,4 +13,9 @@
13
13
  ## 3. Workflow
14
14
  - Read the relevant `specs/` document before writing any code.
15
15
  - Run `npm test` continuously to verify your work.
16
- - Do not conclude your task until the test suite passes completely.
16
+ - Do not conclude your task until the test suite passes completely.
17
+
18
+ ## 4. Documentation & Communication
19
+ - **Audience First:** Write all READMEs, documentation, and release notes for the **end-user**, not the backend engineer.
20
+ - **Focus on Value:** Emphasize *what* the change does for the user (e.g., "Enhanced security," "Faster deployments") rather than *how* it was implemented under the hood.
21
+ - **Hide the Wiring:** Do not explain deep technical refactors, regex changes, or script adjustments in user-facing docs unless they require the user to change their own behavior. Keep it concise, accessible, and benefit-driven.
package/docs/examples.md CHANGED
@@ -22,7 +22,7 @@ These repositories demonstrate how `deploy-stack` handles various frameworks and
22
22
  * **[Express.js API](https://github.com/anton-codes-iac/deploy-stack-express-example):** A standard Node.js backend setup.
23
23
  * **[NestJS API](https://github.com/anton-codes-iac/deploy-stack-nest-example):** A robust NestJS architecture utilizing AST code-patching and highly optimized multi-stage TypeScript builds.
24
24
  * **[Python FastAPI](https://github.com/anton-codes-iac/deploy-stack-fastapi-example):** A Python API demonstrating unprivileged port mapping.
25
- * **[Ruby on Rails](https://github.com/anton-codes-iac/deploy-stack-rails-example):** A production Rails 7+ setup featuring an auto-provisioned PostgreSQL database and secure `.auto.tfvars` Master Key injection.
25
+ * **[Ruby on Rails](https://github.com/anton-codes-iac/deploy-stack-rails-example):** A production Rails 7+ setup featuring an auto-provisioned PostgreSQL database and secure `RAILS_MASTER_KEY` string-literal injection into the initial Secrets Manager placeholder.
26
26
  * **[Django / Python](https://github.com/anton-codes-iac/deploy-stack-django-example):** A secure Gunicorn/WSGI implementation with PostgreSQL and unprivileged container adapters.
27
27
  * **[Go / Fiber](https://github.com/anton-codes-iac/deploy-stack-go-example):** A distroless, compiled Go binary deployment demonstrating ultra-low memory footprints and instant boot times.
28
28
 
@@ -20,7 +20,7 @@ We handle framework requirements using a 3-tier strategy so you are never left g
20
20
  | **NestJS** | Multi-stage TypeScript build (`dist/`), unprivileged Node runtime | `await app.listen(port, '0.0.0.0')` in `src/main.ts` | `nest-deploy-stack` (`nest add`) |
21
21
  | **FastAPI** | Alpine Python container, Uvicorn CLI args, unprivileged port mapping | None (0.0.0.0 set via Docker CMD) | `cookiecutter-fastapi-deploy-stack` |
22
22
  | **Django** | Gunicorn WSGI adapter, Celery worker topologies, RDS bindings | None (0.0.0.0 set via Docker CMD) | `cookiecutter-django-deploy-stack` |
23
- | **Ruby on Rails** | Puma adapter, `.auto.tfvars` Master Key injection, Kamal Dockerfile replaced with 0-CVE Alpine build | None (0.0.0.0 set via Docker CMD) | `rails-template-deploy-stack` |
23
+ | **Ruby on Rails** | Puma adapter, `RAILS_MASTER_KEY` injection into Secrets Manager placeholder, Kamal Dockerfile replaced with 0-CVE Alpine build | None (0.0.0.0 set via Docker CMD) | `rails-template-deploy-stack` |
24
24
  | **Nuxt 3** | Nitro-optimized Node output | None (`NITRO_HOST=0.0.0.0` injected automatically) | `nuxt-deploy-stack` |
25
25
  | **SvelteKit** | Node adapter conversion | None (`HOST=0.0.0.0` injected automatically) | `svelte-adapter-deploy-stack` |
26
26
  | **Static Sites** *(Vite, Astro, React)* | Output folder detection (`dist/`, `build/`), Nginx routing | None | `vite-plugin-deploy-stack` |
@@ -23,5 +23,6 @@ To ensure tests run sub-second and deterministically without requiring real AWS
23
23
  ## 4. Continuous Integration & Execution Validation (CI)
24
24
  While Vitest proves the CLI generates the *correct* files, GitHub Actions proves those files *actually work*. Unit and snapshot tests are gated via `.github/workflows/test.yml`; live template compilation is gated via `.github/workflows/iac-validation.yml`.
25
25
  * **Phase 1 (Generation):** Vitest runs unit and snapshot tests to verify the CLI contract.
26
- * **Phase 2 (Static Application Security Testing - SAST):** CI runs `trivy config` against the generated `Dockerfile` and Terraform snapshots to guarantee they remain compliant with strict security policies.
27
- * **Phase 3 (IaC Validation):** The `iac-validation` matrix workflow scaffolds all 10 supported frameworks headlessly (`--headless --preconfigured`), then runs `terraform init -backend=false` + `terraform validate`, `tflint`, and a Trivy filesystem scan (fails on HIGH/CRITICAL) fully offline with no AWS credentials.
26
+ * **Phase 2 (Static Application Security Testing - SAST):** CI runs a pinned Trivy filesystem scan (`aquasecurity/trivy-action` by SHA) against each generated project directory, writing advisory `trivy-fs-results.txt` reports (`HIGH,CRITICAL`, `exit-code: 0`) instead of failing the build.
27
+ * **Phase 3 (IaC Validation):** The `iac-validation` matrix workflow scaffolds all 10 supported frameworks headlessly (`--headless --preconfigured`), then runs `terraform init -backend=false` + `terraform validate`, `tflint`, the advisory filesystem scan, a stripped-Dockerfile `docker build`, and an advisory container-image scan (`trivy-image-results.txt`).
28
+ * **Phase 4 (Release gate):** `.github/workflows/publish.yml` reuses `iac-validation.yml` via `workflow_call` as a `validate` job; `build-and-publish` has `needs: [validate]`, so NPM publishing on release is blocked until the full matrix passes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-stack",
3
- "version": "0.18.0",
3
+ "version": "0.18.1",
4
4
  "description": "Provision production-ready AWS infrastructure and CI/CD pipelines in seconds.",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
package/src/utils/aws.js CHANGED
@@ -2,7 +2,20 @@ import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
2
2
  import { S3Client, CreateBucketCommand, PutBucketVersioningCommand, PutBucketTaggingCommand } from '@aws-sdk/client-s3';
3
3
  import { DeleteBucketCommand, ListObjectVersionsCommand, DeleteObjectsCommand } from "@aws-sdk/client-s3";
4
4
 
5
+ export async function checkAwsCredentials(region) {
6
+ const resolvedRegion = region || process.env.AWS_REGION || 'us-east-1';
7
+ if (process.env.CI_MOCK_AWS === 'true') {
8
+ return { accountId: '123456789012', awsAccountId: '123456789012', region: resolvedRegion };
9
+ }
10
+ const stsClient = new STSClient({ region: resolvedRegion });
11
+ const { Account } = await stsClient.send(new GetCallerIdentityCommand({}));
12
+ return { accountId: Account, awsAccountId: Account, region: resolvedRegion };
13
+ }
14
+
5
15
  export async function provisionStateBucket(region, projectName) {
16
+ if (process.env.CI_MOCK_AWS === 'true') {
17
+ return { awsAccountId: '123456789012', stateBucketName: 'mock-tf-state-bucket' };
18
+ }
6
19
  const stsClient = new STSClient({ region });
7
20
  let awsAccountId;
8
21
 
@@ -48,6 +61,9 @@ export async function provisionStateBucket(region, projectName) {
48
61
  }
49
62
 
50
63
  export async function teardownStateBucket(region, bucketName) {
64
+ if (process.env.CI_MOCK_AWS === 'true') {
65
+ return true;
66
+ }
51
67
  const client = new S3Client({ region });
52
68
 
53
69
  try {
@@ -138,14 +138,15 @@ export async function generateTemplates(targetDir, config) {
138
138
  if (config.finalFramework === 'rails') {
139
139
  secretsArray.push(`{ "name": "RAILS_MASTER_KEY", "valueFrom": "\${local.secret_arn}:RAILS_MASTER_KEY::" }`);
140
140
 
141
- initialSecretMap += `,\n RAILS_MASTER_KEY = var.rails_master_key`;
142
-
141
+ let initialMasterKey = "1234567890abcdef1234567890abcdef"; // Dummy fallback for CI/CD
143
142
  const masterKeyPath = path.join(targetDir, 'config', 'master.key');
143
+
144
144
  if (fsSync.existsSync(masterKeyPath)) {
145
- const realKey = fsSync.readFileSync(masterKeyPath, 'utf-8').trim();
146
- const tfvarsPath = path.join(targetDir, 'terraform', 'secrets.auto.tfvars');
147
- fsSync.writeFileSync(tfvarsPath, `rails_master_key = "${realKey}"\n`);
145
+ initialMasterKey = fsSync.readFileSync(masterKeyPath, 'utf-8').trim();
148
146
  }
147
+
148
+ // Inject the string literal directly, avoiding Terraform variables entirely
149
+ initialSecretMap += `,\n RAILS_MASTER_KEY = "${initialMasterKey}"`;
149
150
  }
150
151
 
151
152
  initialSecretMap += `\n }`;
@@ -8,13 +8,25 @@ RUN npm run build
8
8
 
9
9
  # --- Stage 2: Production ---
10
10
  FROM node:22-alpine
11
- # DevSecOps: Patch Alpine OS and update npm
12
- RUN apk update && apk upgrade --no-cache && npm install -g npm@latest
11
+
12
+ # 1. DevSecOps: Patch Alpine OS
13
+ RUN apk update && apk upgrade --no-cache
14
+
13
15
  ENV NODE_ENV=production
14
16
  WORKDIR /app
17
+
18
+ # 2. Install ONLY production dependencies
15
19
  COPY --chown=node:node package*.json ./
16
- RUN npm ci --omit=dev
20
+ RUN npm ci --omit=dev && npm cache clean --force
21
+
22
+ # 3. DevSecOps: Nuke all package managers to eliminate base-image CVEs
23
+ RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \
24
+ /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \
25
+ /usr/local/lib/node_modules/corepack /usr/local/bin/corepack
26
+
27
+ # 4. Copy the compiled application
17
28
  COPY --chown=node:node --from=builder /app/dist ./dist
29
+
18
30
  USER node
19
31
  # Expose the port defined by Terraform
20
32
  EXPOSE {{PORT}}
@@ -40,8 +40,10 @@ RUN npm ci --omit=dev && npm cache clean --force
40
40
  # 4. Copy the compiled SvelteKit server from the builder stage
41
41
  COPY --from=builder --chown=node:node /app/build/ ./build/
42
42
 
43
- # 5. DevSecOps: Nuke NPM completely to eliminate Trivy vulnerabilities
44
- RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
43
+ # 5. DevSecOps: Nuke all package managers to eliminate base-image CVEs
44
+ RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \
45
+ /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \
46
+ /usr/local/lib/node_modules/corepack /usr/local/bin/corepack
45
47
 
46
48
  USER node
47
49
  EXPOSE {{PORT}}
@@ -1,4 +1,17 @@
1
1
  terraform {
2
+ required_version = ">= 1.5.0"
3
+
4
+ required_providers {
5
+ aws = {
6
+ source = "hashicorp/aws"
7
+ version = "~> 5.0"
8
+ }
9
+ tls = {
10
+ source = "hashicorp/tls"
11
+ version = "~> 4.0"
12
+ }
13
+ }
14
+
2
15
  backend "s3" {
3
16
  bucket = "{{STATE_BUCKET}}"
4
17
  key = "state/terraform.tfstate"
@@ -6,4 +19,4 @@ terraform {
6
19
  encrypt = true
7
20
  use_lockfile = true
8
21
  }
9
- }
22
+ }
@@ -19,12 +19,6 @@ locals {
19
19
  secret_arn = terraform.workspace == "default" ? aws_secretsmanager_secret.app_secrets[0].arn : data.aws_secretsmanager_secret.existing_secrets[0].arn
20
20
  }
21
21
 
22
- # Fallback dummy key for CI/CD environments where the real key isn't present
23
- variable "rails_master_key" {
24
- type = string
25
- default = "1234567890abcdef1234567890abcdef"
26
- }
27
-
28
22
  # Initial placeholder secret so the ECS task doesn't fail on first boot
29
23
  resource "aws_secretsmanager_secret_version" "app_secrets_initial" {
30
24
  count = terraform.workspace == "default" ? 1 : 0
@@ -5356,8 +5356,10 @@ RUN npm ci --omit=dev && npm cache clean --force
5356
5356
  # 4. Copy the compiled SvelteKit server from the builder stage
5357
5357
  COPY --from=builder --chown=node:node /app/build/ ./build/
5358
5358
 
5359
- # 5. DevSecOps: Nuke NPM completely to eliminate Trivy vulnerabilities
5360
- RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx
5359
+ # 5. DevSecOps: Nuke all package managers to eliminate base-image CVEs
5360
+ RUN rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx \\
5361
+ /opt/yarn-* /usr/local/bin/yarn /usr/local/bin/yarnpkg \\
5362
+ /usr/local/lib/node_modules/corepack /usr/local/bin/corepack
5361
5363
 
5362
5364
  USER node
5363
5365
  EXPOSE 8000