@factiii/stack 0.1.24 → 0.1.25

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.
Files changed (153) hide show
  1. package/README.md +81 -60
  2. package/bin/{factiii → stack} +12 -12
  3. package/dist/cli/check-config.js +1 -1
  4. package/dist/cli/check-config.js.map +1 -1
  5. package/dist/cli/deploy-secrets.d.ts.map +1 -1
  6. package/dist/cli/deploy-secrets.js +11 -11
  7. package/dist/cli/deploy-secrets.js.map +1 -1
  8. package/dist/cli/deploy.d.ts.map +1 -1
  9. package/dist/cli/deploy.js +19 -9
  10. package/dist/cli/deploy.js.map +1 -1
  11. package/dist/cli/dev-sync.d.ts.map +1 -1
  12. package/dist/cli/dev-sync.js +10 -9
  13. package/dist/cli/dev-sync.js.map +1 -1
  14. package/dist/cli/execute-plugin-command.d.ts.map +1 -1
  15. package/dist/cli/execute-plugin-command.js +7 -7
  16. package/dist/cli/execute-plugin-command.js.map +1 -1
  17. package/dist/cli/fix.d.ts.map +1 -1
  18. package/dist/cli/fix.js +24 -4
  19. package/dist/cli/fix.js.map +1 -1
  20. package/dist/cli/init.d.ts +3 -1
  21. package/dist/cli/init.d.ts.map +1 -1
  22. package/dist/cli/init.js +114 -71
  23. package/dist/cli/init.js.map +1 -1
  24. package/dist/cli/pr-check.d.ts.map +1 -1
  25. package/dist/cli/pr-check.js +5 -4
  26. package/dist/cli/pr-check.js.map +1 -1
  27. package/dist/cli/scan.d.ts +6 -6
  28. package/dist/cli/scan.d.ts.map +1 -1
  29. package/dist/cli/scan.js +20 -19
  30. package/dist/cli/scan.js.map +1 -1
  31. package/dist/cli/secrets.d.ts.map +1 -1
  32. package/dist/cli/secrets.js +17 -16
  33. package/dist/cli/secrets.js.map +1 -1
  34. package/dist/cli/undeploy.d.ts.map +1 -1
  35. package/dist/cli/undeploy.js +4 -4
  36. package/dist/cli/undeploy.js.map +1 -1
  37. package/dist/cli/upgrade.d.ts +1 -1
  38. package/dist/cli/upgrade.js +5 -5
  39. package/dist/cli/upgrade.js.map +1 -1
  40. package/dist/cli/validate.js +1 -1
  41. package/dist/cli/validate.js.map +1 -1
  42. package/dist/constants/config-files.d.ts +17 -0
  43. package/dist/constants/config-files.d.ts.map +1 -0
  44. package/dist/constants/config-files.js +73 -0
  45. package/dist/constants/config-files.js.map +1 -0
  46. package/dist/generators/{generate-factiii-auto.d.ts → generate-stack-auto.d.ts} +4 -4
  47. package/dist/generators/generate-stack-auto.d.ts.map +1 -0
  48. package/dist/generators/{generate-factiii-auto.js → generate-stack-auto.js} +15 -13
  49. package/dist/generators/generate-stack-auto.js.map +1 -0
  50. package/dist/generators/{generate-factiii-yml.d.ts → generate-stack-yml.d.ts} +5 -5
  51. package/dist/generators/generate-stack-yml.d.ts.map +1 -0
  52. package/dist/generators/{generate-factiii-yml.js → generate-stack-yml.js} +16 -15
  53. package/dist/generators/generate-stack-yml.js.map +1 -0
  54. package/dist/generators/index.d.ts +2 -2
  55. package/dist/generators/index.d.ts.map +1 -1
  56. package/dist/generators/index.js +5 -5
  57. package/dist/generators/index.js.map +1 -1
  58. package/dist/plugins/pipelines/aws/index.js +3 -3
  59. package/dist/plugins/pipelines/aws/index.js.map +1 -1
  60. package/dist/plugins/pipelines/aws/scanfix/credentials.d.ts.map +1 -1
  61. package/dist/plugins/pipelines/aws/scanfix/credentials.js +36 -44
  62. package/dist/plugins/pipelines/aws/scanfix/credentials.js.map +1 -1
  63. package/dist/plugins/pipelines/aws/scanfix/ec2.js +1 -1
  64. package/dist/plugins/pipelines/aws/scanfix/ec2.js.map +1 -1
  65. package/dist/plugins/pipelines/aws/scanfix/iam.js +2 -2
  66. package/dist/plugins/pipelines/aws/scanfix/iam.js.map +1 -1
  67. package/dist/plugins/pipelines/aws/scanfix/rds.js +2 -2
  68. package/dist/plugins/pipelines/aws/scanfix/rds.js.map +1 -1
  69. package/dist/plugins/pipelines/factiii/index.js +1 -1
  70. package/dist/plugins/pipelines/factiii/index.js.map +1 -1
  71. package/dist/plugins/pipelines/factiii/pr-check.d.ts +1 -1
  72. package/dist/plugins/pipelines/factiii/pr-check.js +1 -1
  73. package/dist/plugins/pipelines/factiii/prod.d.ts.map +1 -1
  74. package/dist/plugins/pipelines/factiii/prod.js +3 -3
  75. package/dist/plugins/pipelines/factiii/prod.js.map +1 -1
  76. package/dist/plugins/pipelines/factiii/scanfix/config.d.ts +1 -1
  77. package/dist/plugins/pipelines/factiii/scanfix/config.d.ts.map +1 -1
  78. package/dist/plugins/pipelines/factiii/scanfix/config.js +10 -11
  79. package/dist/plugins/pipelines/factiii/scanfix/config.js.map +1 -1
  80. package/dist/plugins/pipelines/factiii/scanfix/secrets.js +6 -6
  81. package/dist/plugins/pipelines/factiii/scanfix/secrets.js.map +1 -1
  82. package/dist/plugins/pipelines/factiii/scanfix/workflows.d.ts.map +1 -1
  83. package/dist/plugins/pipelines/factiii/scanfix/workflows.js +29 -25
  84. package/dist/plugins/pipelines/factiii/scanfix/workflows.js.map +1 -1
  85. package/dist/plugins/pipelines/factiii/staging.d.ts +1 -1
  86. package/dist/plugins/pipelines/factiii/staging.d.ts.map +1 -1
  87. package/dist/plugins/pipelines/factiii/staging.js +3 -2
  88. package/dist/plugins/pipelines/factiii/staging.js.map +1 -1
  89. package/dist/plugins/pipelines/factiii/utils/workflows.js +15 -15
  90. package/dist/plugins/pipelines/factiii/utils/workflows.js.map +1 -1
  91. package/dist/plugins/pipelines/factiii/workflows/factiii-cicd-prod.yml +11 -8
  92. package/dist/plugins/pipelines/factiii/workflows/factiii-cicd-staging.yml +11 -8
  93. package/dist/plugins/pipelines/factiii/workflows/factiii-command.yml +10 -8
  94. package/dist/plugins/pipelines/factiii/workflows/factiii-deploy.yml +17 -13
  95. package/dist/plugins/pipelines/factiii/workflows/factiii-dev-sync.yml +13 -11
  96. package/dist/plugins/pipelines/factiii/workflows/factiii-fix.yml +13 -11
  97. package/dist/plugins/pipelines/factiii/workflows/factiii-pr-check.yml +10 -7
  98. package/dist/plugins/pipelines/factiii/workflows/factiii-scan.yml +18 -11
  99. package/dist/plugins/pipelines/factiii/workflows/factiii-undeploy.yml +9 -8
  100. package/dist/plugins/pipelines/factiii/workflows/stack-cicd-prod.yml +115 -0
  101. package/dist/plugins/pipelines/factiii/workflows/stack-cicd-staging.yml +120 -0
  102. package/dist/plugins/pipelines/factiii/workflows/stack-command.yml +132 -0
  103. package/dist/plugins/pipelines/factiii/workflows/stack-deploy.yml +202 -0
  104. package/dist/plugins/pipelines/factiii/workflows/stack-dev-sync.yml +181 -0
  105. package/dist/plugins/pipelines/factiii/workflows/stack-fix.yml +177 -0
  106. package/dist/plugins/pipelines/factiii/workflows/stack-pr-check.yml +106 -0
  107. package/dist/plugins/pipelines/factiii/workflows/stack-scan.yml +182 -0
  108. package/dist/plugins/pipelines/factiii/workflows/stack-undeploy.yml +96 -0
  109. package/dist/plugins/servers/mac/scanfix/containers.js +1 -1
  110. package/dist/plugins/servers/mac/scanfix/containers.js.map +1 -1
  111. package/dist/scanfix/fixes/pnpm.d.ts.map +1 -1
  112. package/dist/scanfix/fixes/pnpm.js +2 -2
  113. package/dist/scanfix/fixes/pnpm.js.map +1 -1
  114. package/dist/scripts/generate-all.d.ts.map +1 -1
  115. package/dist/scripts/generate-all.js +9 -8
  116. package/dist/scripts/generate-all.js.map +1 -1
  117. package/dist/scripts/get-repo-name.d.ts +1 -1
  118. package/dist/scripts/get-repo-name.js +5 -3
  119. package/dist/scripts/get-repo-name.js.map +1 -1
  120. package/dist/scripts/validate-env-files.js +7 -1
  121. package/dist/scripts/validate-env-files.js.map +1 -1
  122. package/dist/scripts/validate-example-values.d.ts +1 -1
  123. package/dist/scripts/validate-example-values.js +3 -2
  124. package/dist/scripts/validate-example-values.js.map +1 -1
  125. package/dist/scripts/validate-stack-yml.d.ts +6 -0
  126. package/dist/scripts/validate-stack-yml.d.ts.map +1 -0
  127. package/dist/scripts/{validate-factiii-yml.js → validate-stack-yml.js} +15 -11
  128. package/dist/scripts/validate-stack-yml.js.map +1 -0
  129. package/dist/utils/config-validator.d.ts +2 -2
  130. package/dist/utils/config-validator.d.ts.map +1 -1
  131. package/dist/utils/config-validator.js +7 -6
  132. package/dist/utils/config-validator.js.map +1 -1
  133. package/dist/utils/config-writer.d.ts +2 -2
  134. package/dist/utils/config-writer.d.ts.map +1 -1
  135. package/dist/utils/config-writer.js +6 -6
  136. package/dist/utils/config-writer.js.map +1 -1
  137. package/dist/utils/deployment-report.js +4 -4
  138. package/dist/utils/deployment-report.js.map +1 -1
  139. package/dist/utils/ssh-helper.js +6 -6
  140. package/dist/utils/ssh-helper.js.map +1 -1
  141. package/dist/utils/template-generator.js +1 -1
  142. package/dist/utils/version-check.d.ts +1 -1
  143. package/dist/utils/version-check.d.ts.map +1 -1
  144. package/dist/utils/version-check.js +9 -8
  145. package/dist/utils/version-check.js.map +1 -1
  146. package/package.json +8 -2
  147. package/dist/generators/generate-factiii-auto.d.ts.map +0 -1
  148. package/dist/generators/generate-factiii-auto.js.map +0 -1
  149. package/dist/generators/generate-factiii-yml.d.ts.map +0 -1
  150. package/dist/generators/generate-factiii-yml.js.map +0 -1
  151. package/dist/scripts/validate-factiii-yml.d.ts +0 -6
  152. package/dist/scripts/validate-factiii-yml.d.ts.map +0 -1
  153. package/dist/scripts/validate-factiii-yml.js.map +0 -1
@@ -35,12 +35,17 @@ jobs:
35
35
  - name: Determine environments
36
36
  id: set-matrix
37
37
  run: |
38
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
39
+ if [ ! -f "$CONFIG_FILE" ]; then
40
+ echo "matrix={\"environment\":[]}" >> $GITHUB_OUTPUT
41
+ echo "❌ stack.yml or factiii.yml not found"
42
+ exit 1
43
+ fi
38
44
  ENVS="[]"
39
45
 
40
46
  if [ "${{ inputs.environment }}" == "all" ]; then
41
- # Check which environments are configured
42
- HAS_STAGING=$(yq eval '.environments.staging != null' factiii.yml)
43
- HAS_PROD=$(yq eval '.environments.prod != null' factiii.yml)
47
+ HAS_STAGING=$(yq eval '.staging != null' "$CONFIG_FILE")
48
+ HAS_PROD=$(yq eval '.prod != null' "$CONFIG_FILE")
44
49
 
45
50
  if [ "$HAS_STAGING" == "true" ] && [ "$HAS_PROD" == "true" ]; then
46
51
  ENVS='["staging", "prod"]'
@@ -75,14 +80,15 @@ jobs:
75
80
  env:
76
81
  ENVIRONMENT: ${{ matrix.environment }}
77
82
  run: |
78
- if [ ! -f "factiii.yml" ]; then
79
- echo "❌ factiii.yml not found"
83
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
84
+ if [ ! -f "$CONFIG_FILE" ]; then
85
+ echo "❌ stack.yml or factiii.yml not found"
80
86
  exit 1
81
87
  fi
82
88
 
83
- REPO_NAME=$(yq eval '.name' factiii.yml)
84
- HOST=$(yq eval ".environments.$ENVIRONMENT.domain // \"\"" factiii.yml)
85
- SSH_USER=$(yq eval ".environments.$ENVIRONMENT.ssh_user // \"ubuntu\"" factiii.yml)
89
+ REPO_NAME=$(yq eval '.name' "$CONFIG_FILE")
90
+ HOST=$(yq eval ".$ENVIRONMENT.domain // \"\"" "$CONFIG_FILE")
91
+ SSH_USER=$(yq eval ".$ENVIRONMENT.ssh_user // \"ubuntu\"" "$CONFIG_FILE")
86
92
 
87
93
  echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT
88
94
  echo "host=$HOST" >> $GITHUB_OUTPUT
@@ -93,11 +99,12 @@ jobs:
93
99
  env:
94
100
  ENVIRONMENT: ${{ matrix.environment }}
95
101
  run: |
96
- HAS_ENV=$(yq eval ".environments.$ENVIRONMENT != null" factiii.yml)
102
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
103
+ HAS_ENV=$(yq eval ".$ENVIRONMENT != null" "$CONFIG_FILE")
97
104
  echo "has_env=$HAS_ENV" >> $GITHUB_OUTPUT
98
105
 
99
106
  if [ "$HAS_ENV" != "true" ]; then
100
- echo "⏭️ $ENVIRONMENT environment not configured in factiii.yml"
107
+ echo "⏭️ $ENVIRONMENT not configured"
101
108
  exit 0
102
109
  fi
103
110
 
@@ -153,7 +160,7 @@ jobs:
153
160
  ENVIRONMENT: ${{ matrix.environment }}
154
161
  run: |
155
162
  if [ -z "$HOST" ]; then
156
- echo "❌ Missing domain in factiii.yml: environments.$ENVIRONMENT.domain"
163
+ echo "❌ Missing domain in config: $ENVIRONMENT.domain"
157
164
  exit 1
158
165
  fi
159
166
 
@@ -30,19 +30,20 @@ jobs:
30
30
  - name: Read config
31
31
  id: config
32
32
  run: |
33
- if [ ! -f "factiii.yml" ]; then
34
- echo "❌ factiii.yml not found"
33
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
34
+ if [ ! -f "$CONFIG_FILE" ]; then
35
+ echo "❌ stack.yml or factiii.yml not found"
35
36
  exit 1
36
37
  fi
37
38
 
38
- REPO_NAME=$(yq eval '.name' factiii.yml)
39
+ REPO_NAME=$(yq eval '.name' "$CONFIG_FILE")
39
40
 
40
41
  if [ "${{ inputs.environment }}" == "staging" ]; then
41
- HOST=$(yq eval '.environments.staging.domain // ""' factiii.yml)
42
- SSH_USER=$(yq eval '.environments.staging.ssh_user // "ubuntu"' factiii.yml)
42
+ HOST=$(yq eval '.staging.domain // ""' "$CONFIG_FILE")
43
+ SSH_USER=$(yq eval '.staging.ssh_user // "ubuntu"' "$CONFIG_FILE")
43
44
  else
44
- HOST=$(yq eval '.environments.prod.domain // ""' factiii.yml)
45
- SSH_USER=$(yq eval '.environments.prod.ssh_user // "ubuntu"' factiii.yml)
45
+ HOST=$(yq eval '.prod.domain // ""' "$CONFIG_FILE")
46
+ SSH_USER=$(yq eval '.prod.ssh_user // "ubuntu"' "$CONFIG_FILE")
46
47
  fi
47
48
 
48
49
  echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT
@@ -70,7 +71,7 @@ jobs:
70
71
  ENVIRONMENT: ${{ inputs.environment }}
71
72
  run: |
72
73
  if [ -z "$HOST" ]; then
73
- echo "❌ Missing domain in factiii.yml: environments.$ENVIRONMENT.domain"
74
+ echo "❌ Missing domain in config: $ENVIRONMENT.domain"
74
75
  exit 1
75
76
  fi
76
77
 
@@ -0,0 +1,115 @@
1
+ name: Stack CI/CD Prod
2
+
3
+ # Generated by @factiii/stack v{VERSION}
4
+ # CI/CD: Auto-deploy to prod on push to prod branch
5
+ # This is the app's CI/CD pipeline, NOT infrastructure management.
6
+ # For manual infrastructure operations, use stack-deploy.yml
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - prod
12
+
13
+ jobs:
14
+ deploy:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Install yq
21
+ run: |
22
+ sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
23
+ sudo chmod +x /usr/local/bin/yq
24
+
25
+ - name: Read config
26
+ id: config
27
+ run: |
28
+ CONFIG_FILE="stack.yml"
29
+ if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
30
+ if [ ! -f "$CONFIG_FILE" ]; then
31
+ echo "❌ stack.yml or factiii.yml not found"
32
+ exit 1
33
+ fi
34
+
35
+ REPO_NAME=$(yq eval '.name' "$CONFIG_FILE")
36
+ HOST=$(yq eval '.prod.domain // ""' "$CONFIG_FILE")
37
+ SSH_USER=$(yq eval '.prod.ssh_user // "ubuntu"' "$CONFIG_FILE")
38
+
39
+ echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT
40
+ echo "host=$HOST" >> $GITHUB_OUTPUT
41
+ echo "ssh_user=$SSH_USER" >> $GITHUB_OUTPUT
42
+
43
+ - name: Check if prod configured
44
+ id: check_prod
45
+ run: |
46
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
47
+ HAS_PROD=$(yq eval '.prod != null' "$CONFIG_FILE")
48
+ echo "has_prod=$HAS_PROD" >> $GITHUB_OUTPUT
49
+
50
+ if [ "$HAS_PROD" != "true" ]; then
51
+ echo "⏭️ Prod not configured - skipping deployment"
52
+ exit 0
53
+ fi
54
+
55
+ - name: Setup SSH
56
+ if: steps.check_prod.outputs.has_prod == 'true'
57
+ env:
58
+ SSH_KEY: ${{ secrets.PROD_SSH }}
59
+ run: |
60
+ if [ -z "$SSH_KEY" ]; then
61
+ echo "❌ Missing PROD_SSH secret"
62
+ exit 1
63
+ fi
64
+
65
+ mkdir -p ~/.ssh
66
+ echo "$SSH_KEY" > ~/.ssh/deploy_key
67
+ chmod 600 ~/.ssh/deploy_key
68
+
69
+ - name: Deploy via CLI
70
+ if: steps.check_prod.outputs.has_prod == 'true'
71
+ env:
72
+ HOST: ${{ steps.config.outputs.host }}
73
+ USER: ${{ steps.config.outputs.ssh_user }}
74
+ REPO_NAME: ${{ steps.config.outputs.repo_name }}
75
+ COMMIT_HASH: ${{ github.sha }}
76
+ BRANCH: ${{ github.ref_name }}
77
+ GITHUB_REPO: ${{ github.repository }}
78
+ PROD_ENVS: ${{ secrets.PROD_ENVS }}
79
+ run: |
80
+ if [ -z "$HOST" ]; then
81
+ echo "❌ Missing prod.domain in config"
82
+ exit 1
83
+ fi
84
+
85
+ echo "🚀 Deploying to prod ($HOST)..."
86
+
87
+ # Prepare environment variables for SSH (base64 encode to handle special characters)
88
+ ENV_VARS_EXPORT=""
89
+ if [ -n "$PROD_ENVS" ]; then
90
+ ENV_VARS_B64=$(echo -n "$PROD_ENVS" | base64 -w 0)
91
+ ENV_VARS_EXPORT="PROD_ENVS=\$(echo '$ENV_VARS_B64' | base64 -d) && export PROD_ENVS && "
92
+ fi
93
+
94
+ ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no "$USER@$HOST" \
95
+ "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
96
+ REPO_DIR=\"\$HOME/.factiii/$REPO_NAME\" && \
97
+ if [ -d \"\$REPO_DIR\" ]; then \
98
+ cd \"\$REPO_DIR\" && \
99
+ $ENV_VARS_EXPORT GITHUB_ACTIONS=true COMMIT_HASH=$COMMIT_HASH BRANCH=$BRANCH GITHUB_REPO=$GITHUB_REPO \
100
+ npx stack deploy --prod --commit $COMMIT_HASH --branch $BRANCH; \
101
+ else \
102
+ echo \"❌ Repo directory not found at \$REPO_DIR\"; \
103
+ echo \"Run deployment first to clone the repository\"; \
104
+ exit 1; \
105
+ fi"
106
+
107
+ DEPLOY_EXIT_CODE=$?
108
+ rm -f ~/.ssh/deploy_key
109
+
110
+ if [ $DEPLOY_EXIT_CODE -eq 0 ]; then
111
+ echo "✅ Prod deployment complete!"
112
+ else
113
+ echo "❌ Prod deployment failed with exit code $DEPLOY_EXIT_CODE"
114
+ exit $DEPLOY_EXIT_CODE
115
+ fi
@@ -0,0 +1,120 @@
1
+ name: Stack CI/CD Staging
2
+
3
+ # Generated by @factiii/stack v{VERSION}
4
+ # CI/CD: Auto-deploy to staging on push to main branch
5
+ # This is the app's CI/CD pipeline, NOT infrastructure management.
6
+ # For manual infrastructure operations, use stack-deploy.yml
7
+
8
+ on:
9
+ push:
10
+ branches:
11
+ - main
12
+
13
+ jobs:
14
+ deploy:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - name: Checkout code
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Install yq
21
+ run: |
22
+ sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
23
+ sudo chmod +x /usr/local/bin/yq
24
+
25
+ - name: Read config
26
+ id: config
27
+ run: |
28
+ CONFIG_FILE="stack.yml"
29
+ if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
30
+ if [ ! -f "$CONFIG_FILE" ]; then
31
+ echo "❌ stack.yml or factiii.yml not found"
32
+ exit 1
33
+ fi
34
+
35
+ REPO_NAME=$(yq eval '.name' "$CONFIG_FILE")
36
+ HOST=$(yq eval '.staging.domain // ""' "$CONFIG_FILE")
37
+ SSH_USER=$(yq eval '.staging.ssh_user // "ubuntu"' "$CONFIG_FILE")
38
+
39
+ echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT
40
+ echo "host=$HOST" >> $GITHUB_OUTPUT
41
+ echo "ssh_user=$SSH_USER" >> $GITHUB_OUTPUT
42
+
43
+ - name: Check if staging configured
44
+ id: check_staging
45
+ run: |
46
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
47
+ HAS_STAGING=$(yq eval '.staging != null' "$CONFIG_FILE")
48
+ echo "has_staging=$HAS_STAGING" >> $GITHUB_OUTPUT
49
+
50
+ if [ "$HAS_STAGING" != "true" ]; then
51
+ echo "⏭️ Staging not configured - skipping deployment"
52
+ exit 0
53
+ fi
54
+
55
+ - name: Setup SSH
56
+ if: steps.check_staging.outputs.has_staging == 'true'
57
+ env:
58
+ SSH_KEY: ${{ secrets.STAGING_SSH }}
59
+ run: |
60
+ if [ -z "$SSH_KEY" ]; then
61
+ echo "❌ Missing STAGING_SSH secret"
62
+ exit 1
63
+ fi
64
+
65
+ mkdir -p ~/.ssh
66
+ echo "$SSH_KEY" > ~/.ssh/deploy_key
67
+ chmod 600 ~/.ssh/deploy_key
68
+
69
+ - name: Deploy via CLI
70
+ if: steps.check_staging.outputs.has_staging == 'true'
71
+ env:
72
+ HOST: ${{ steps.config.outputs.host }}
73
+ USER: ${{ steps.config.outputs.ssh_user }}
74
+ REPO_NAME: ${{ steps.config.outputs.repo_name }}
75
+ COMMIT_HASH: ${{ github.sha }}
76
+ BRANCH: ${{ github.ref_name }}
77
+ GITHUB_REPO: ${{ github.repository }}
78
+ STAGING_ENVS: ${{ secrets.STAGING_ENVS }}
79
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
80
+ run: |
81
+ if [ -z "$HOST" ]; then
82
+ echo "❌ Missing staging.domain in config"
83
+ exit 1
84
+ fi
85
+
86
+ echo "🚀 Deploying to staging ($HOST)..."
87
+
88
+ # Prepare environment variables for SSH (base64 encode to handle special characters)
89
+ ENV_VARS_EXPORT=""
90
+ if [ -n "$STAGING_ENVS" ]; then
91
+ ENV_VARS_B64=$(echo -n "$STAGING_ENVS" | base64 -w 0)
92
+ ENV_VARS_EXPORT="STAGING_ENVS=\$(echo '$ENV_VARS_B64' | base64 -d) && export STAGING_ENVS && "
93
+ fi
94
+
95
+ # Base64 encode GITHUB_TOKEN for safe SSH transport
96
+ GITHUB_TOKEN_B64=$(echo -n "$GITHUB_TOKEN" | base64 -w 0)
97
+
98
+ ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no "$USER@$HOST" \
99
+ "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
100
+ export GITHUB_TOKEN=\$(echo \"$GITHUB_TOKEN_B64\" | base64 -d) && \
101
+ REPO_DIR=\"\$HOME/.factiii/$REPO_NAME\" && \
102
+ if [ -d \"\$REPO_DIR\" ]; then \
103
+ cd \"\$REPO_DIR\" && \
104
+ $ENV_VARS_EXPORT GITHUB_ACTIONS=true COMMIT_HASH=$COMMIT_HASH BRANCH=$BRANCH GITHUB_REPO=$GITHUB_REPO \
105
+ npx stack deploy --staging --commit $COMMIT_HASH --branch $BRANCH; \
106
+ else \
107
+ echo \"❌ Repo directory not found at \$REPO_DIR\"; \
108
+ echo \"Run deployment first to clone the repository\"; \
109
+ exit 1; \
110
+ fi"
111
+
112
+ DEPLOY_EXIT_CODE=$?
113
+ rm -f ~/.ssh/deploy_key
114
+
115
+ if [ $DEPLOY_EXIT_CODE -eq 0 ]; then
116
+ echo "✅ Staging deployment complete!"
117
+ else
118
+ echo "❌ Staging deployment failed with exit code $DEPLOY_EXIT_CODE"
119
+ exit $DEPLOY_EXIT_CODE
120
+ fi
@@ -0,0 +1,132 @@
1
+ name: Stack Command
2
+
3
+ # Generated by @factiii/stack v{VERSION}
4
+ # INFRASTRUCTURE: Run plugin commands on remote servers
5
+ # Run: npx stack db seed --staging (triggers this workflow)
6
+
7
+ on:
8
+ workflow_dispatch:
9
+ inputs:
10
+ category:
11
+ description: 'Command category (db, ops, backup)'
12
+ required: true
13
+ type: string
14
+ command:
15
+ description: 'Command to run (e.g., seed, migrate, logs)'
16
+ required: true
17
+ type: string
18
+ stage:
19
+ description: 'Target environment'
20
+ required: true
21
+ type: choice
22
+ options:
23
+ - staging
24
+ - prod
25
+ options:
26
+ description: 'Command options (JSON)'
27
+ required: false
28
+ default: '{}'
29
+
30
+ jobs:
31
+ run-command:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - name: Checkout code
35
+ uses: actions/checkout@v4
36
+
37
+ - name: Install yq
38
+ run: |
39
+ sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
40
+ sudo chmod +x /usr/local/bin/yq
41
+
42
+ - name: Read config
43
+ id: config
44
+ env:
45
+ STAGE: ${{ inputs.stage }}
46
+ run: |
47
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
48
+ if [ ! -f "$CONFIG_FILE" ]; then
49
+ echo "❌ stack.yml or factiii.yml not found"
50
+ exit 1
51
+ fi
52
+
53
+ REPO_NAME=$(yq eval '.name' "$CONFIG_FILE")
54
+ HOST=$(yq eval ".$STAGE.domain // \"\"" "$CONFIG_FILE")
55
+ SSH_USER=$(yq eval ".$STAGE.ssh_user // \"ubuntu\"" "$CONFIG_FILE")
56
+
57
+ echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT
58
+ echo "host=$HOST" >> $GITHUB_OUTPUT
59
+ echo "ssh_user=$SSH_USER" >> $GITHUB_OUTPUT
60
+
61
+ - name: Check environment configured
62
+ env:
63
+ STAGE: ${{ inputs.stage }}
64
+ run: |
65
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
66
+ HAS_ENV=$(yq eval ".$STAGE != null" "$CONFIG_FILE")
67
+ if [ "$HAS_ENV" != "true" ]; then
68
+ echo "$STAGE not configured in config"
69
+ exit 1
70
+ fi
71
+
72
+ - name: Check SSH secret
73
+ env:
74
+ STAGE: ${{ inputs.stage }}
75
+ SSH_KEY: ${{ inputs.stage == 'staging' && secrets.STAGING_SSH || secrets.PROD_SSH }}
76
+ run: |
77
+ if [ -z "$SSH_KEY" ]; then
78
+ SECRET_NAME="${STAGE^^}_SSH"
79
+ echo "${SECRET_NAME} secret not found"
80
+ echo "Add it at: https://github.com/${{ github.repository }}/settings/secrets/actions"
81
+ exit 1
82
+ fi
83
+
84
+ - name: Setup SSH
85
+ env:
86
+ SSH_KEY: ${{ inputs.stage == 'staging' && secrets.STAGING_SSH || secrets.PROD_SSH }}
87
+ run: |
88
+ mkdir -p ~/.ssh
89
+ echo "$SSH_KEY" > ~/.ssh/deploy_key
90
+ chmod 600 ~/.ssh/deploy_key
91
+
92
+ - name: Run command via SSH
93
+ env:
94
+ HOST: ${{ steps.config.outputs.host }}
95
+ USER: ${{ steps.config.outputs.ssh_user }}
96
+ REPO_NAME: ${{ steps.config.outputs.repo_name }}
97
+ CATEGORY: ${{ inputs.category }}
98
+ COMMAND: ${{ inputs.command }}
99
+ STAGE: ${{ inputs.stage }}
100
+ run: |
101
+ if [ -z "$HOST" ]; then
102
+ echo "Missing domain in config: $STAGE.domain"
103
+ exit 1
104
+ fi
105
+
106
+ echo "Running: stack $CATEGORY $COMMAND --$STAGE"
107
+ echo "Server: $USER@$HOST"
108
+ echo ""
109
+
110
+ ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no -o ConnectTimeout=10 -o ServerAliveInterval=60 -o ServerAliveCountMax=5 "$USER@$HOST" \
111
+ "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
112
+ REPO_DIR=\"\$HOME/.factiii/$REPO_NAME\" && \
113
+ if [ -d \"\$REPO_DIR\" ]; then \
114
+ cd \"\$REPO_DIR\" && \
115
+ GITHUB_ACTIONS=true npx stack $CATEGORY $COMMAND --$STAGE; \
116
+ else \
117
+ echo \"Repo directory not found at \$REPO_DIR\"; \
118
+ echo \"Run deployment first to clone the repository\"; \
119
+ exit 1; \
120
+ fi"
121
+
122
+ EXIT_CODE=$?
123
+ rm -f ~/.ssh/deploy_key
124
+
125
+ if [ $EXIT_CODE -eq 0 ]; then
126
+ echo ""
127
+ echo "Command completed successfully"
128
+ else
129
+ echo ""
130
+ echo "Command failed with exit code $EXIT_CODE"
131
+ exit $EXIT_CODE
132
+ fi
@@ -0,0 +1,202 @@
1
+ name: Stack Deploy
2
+
3
+ # Generated by @factiii/stack v{VERSION}
4
+ # INFRASTRUCTURE: Manual deployment triggered via CLI
5
+ # Run: npx stack deploy --staging or npx stack deploy --prod
6
+ # For auto-deploy on push, see stack-cicd-staging.yml / stack-cicd-prod.yml
7
+
8
+ on:
9
+ workflow_dispatch:
10
+ inputs:
11
+ environment:
12
+ description: 'Environment to deploy'
13
+ required: true
14
+ type: choice
15
+ options:
16
+ - staging
17
+ - prod
18
+
19
+ jobs:
20
+ deploy:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - name: Checkout code
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Install yq
27
+ run: |
28
+ sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
29
+ sudo chmod +x /usr/local/bin/yq
30
+
31
+ - name: Read config
32
+ id: config
33
+ run: |
34
+ CONFIG_FILE="stack.yml"
35
+ if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
36
+ if [ ! -f "$CONFIG_FILE" ]; then
37
+ echo "❌ stack.yml or factiii.yml not found"
38
+ exit 1
39
+ fi
40
+
41
+ REPO_NAME=$(yq eval '.name' "$CONFIG_FILE")
42
+
43
+ if [ "${{ inputs.environment }}" == "staging" ]; then
44
+ HOST=$(yq eval '.staging.domain // ""' "$CONFIG_FILE")
45
+ SSH_USER=$(yq eval '.staging.ssh_user // "ubuntu"' "$CONFIG_FILE")
46
+ else
47
+ HOST=$(yq eval '.prod.domain // ""' "$CONFIG_FILE")
48
+ SSH_USER=$(yq eval '.prod.ssh_user // "ubuntu"' "$CONFIG_FILE")
49
+ fi
50
+
51
+ echo "repo_name=$REPO_NAME" >> $GITHUB_OUTPUT
52
+ echo "host=$HOST" >> $GITHUB_OUTPUT
53
+ echo "ssh_user=$SSH_USER" >> $GITHUB_OUTPUT
54
+
55
+ - name: Check if environment configured
56
+ id: check_env
57
+ run: |
58
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
59
+ if [ "${{ inputs.environment }}" == "staging" ]; then
60
+ HAS_ENV=$(yq eval '.staging != null' "$CONFIG_FILE")
61
+ else
62
+ HAS_ENV=$(yq eval '.prod != null' "$CONFIG_FILE")
63
+ fi
64
+
65
+ echo "has_env=$HAS_ENV" >> $GITHUB_OUTPUT
66
+
67
+ if [ "$HAS_ENV" != "true" ]; then
68
+ echo "⏭️ ${{ inputs.environment }} not configured in config"
69
+ exit 1
70
+ fi
71
+
72
+ - name: Read staging config (for prod builds)
73
+ if: steps.check_env.outputs.has_env == 'true' && inputs.environment == 'prod'
74
+ id: staging_config
75
+ run: |
76
+ CONFIG_FILE="stack.yml"; if [ ! -f "$CONFIG_FILE" ]; then CONFIG_FILE="factiii.yml"; fi
77
+ STAGING_HOST=$(yq eval '.staging.domain // ""' "$CONFIG_FILE")
78
+ STAGING_SSH_USER=$(yq eval '.staging.ssh_user // "ubuntu"' "$CONFIG_FILE")
79
+
80
+ echo "staging_host=$STAGING_HOST" >> $GITHUB_OUTPUT
81
+ echo "staging_ssh_user=$STAGING_SSH_USER" >> $GITHUB_OUTPUT
82
+
83
+ - name: Setup Node.js
84
+ if: steps.check_env.outputs.has_env == 'true'
85
+ uses: actions/setup-node@v4
86
+ with:
87
+ node-version: '20'
88
+
89
+ - name: Load SSH keys from Ansible Vault
90
+ if: steps.check_env.outputs.has_env == 'true'
91
+ env:
92
+ ANSIBLE_VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
93
+ run: |
94
+ if [ -z "$ANSIBLE_VAULT_PASSWORD" ]; then
95
+ echo "❌ Missing ANSIBLE_VAULT_PASSWORD secret (vault password)"
96
+ exit 1
97
+ fi
98
+
99
+ npx stack secrets write-ssh-keys
100
+
101
+ # Create deploy_key symlink for current environment
102
+ if [ "${{ inputs.environment }}" == "staging" ]; then
103
+ ln -sf ~/.ssh/staging_deploy_key ~/.ssh/deploy_key
104
+ else
105
+ ln -sf ~/.ssh/prod_deploy_key ~/.ssh/deploy_key
106
+ fi
107
+
108
+ - name: Build production image on staging (prod only)
109
+ if: steps.check_env.outputs.has_env == 'true' && inputs.environment == 'prod'
110
+ env:
111
+ STAGING_HOST: ${{ steps.staging_config.outputs.staging_host }}
112
+ STAGING_USER: ${{ steps.staging_config.outputs.staging_ssh_user }}
113
+ REPO_NAME: ${{ steps.config.outputs.repo_name }}
114
+ COMMIT_HASH: ${{ github.sha }}
115
+ BRANCH: ${{ github.ref_name }}
116
+ GITHUB_REPO: ${{ github.repository }}
117
+ run: |
118
+ if [ -z "$STAGING_HOST" ]; then
119
+ echo "⚠️ Staging host not configured, skipping build step"
120
+ exit 0
121
+ fi
122
+
123
+ echo "🔨 Building production image on staging server ($STAGING_HOST)..."
124
+
125
+ ssh -i ~/.ssh/staging_deploy_key -o StrictHostKeyChecking=no "$STAGING_USER@$STAGING_HOST" \
126
+ "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
127
+ REPO_DIR=\"\$HOME/.factiii/$REPO_NAME\" && \
128
+ if [ -d \"\$REPO_DIR\" ]; then \
129
+ cd \"\$REPO_DIR\" && \
130
+ GITHUB_ACTIONS=true COMMIT_HASH=$COMMIT_HASH BRANCH=$BRANCH GITHUB_REPO=$GITHUB_REPO \
131
+ npx stack deploy --prod --commit $COMMIT_HASH --branch $BRANCH; \
132
+ else \
133
+ echo \"❌ Repo directory not found at \$REPO_DIR\"; \
134
+ exit 1; \
135
+ fi"
136
+
137
+ BUILD_EXIT_CODE=$?
138
+ if [ $BUILD_EXIT_CODE -ne 0 ]; then
139
+ echo "❌ Build step failed with exit code $BUILD_EXIT_CODE"
140
+ exit $BUILD_EXIT_CODE
141
+ fi
142
+
143
+ echo "✅ Production image built and pushed to ECR"
144
+
145
+ - name: Deploy via CLI
146
+ if: steps.check_env.outputs.has_env == 'true'
147
+ env:
148
+ HOST: ${{ steps.config.outputs.host }}
149
+ USER: ${{ steps.config.outputs.ssh_user }}
150
+ REPO_NAME: ${{ steps.config.outputs.repo_name }}
151
+ ENVIRONMENT: ${{ inputs.environment }}
152
+ COMMIT_HASH: ${{ github.sha }}
153
+ BRANCH: ${{ github.ref_name }}
154
+ GITHUB_REPO: ${{ github.repository }}
155
+ STAGING_ENVS: ${{ inputs.environment == 'staging' && secrets.STAGING_ENVS || '' }}
156
+ PROD_ENVS: ${{ inputs.environment == 'prod' && secrets.PROD_ENVS || '' }}
157
+ run: |
158
+ if [ -z "$HOST" ]; then
159
+ echo "❌ Missing domain in config: $ENVIRONMENT.domain"
160
+ exit 1
161
+ fi
162
+
163
+ echo "🚀 Deploying to $ENVIRONMENT ($HOST)..."
164
+
165
+ # For prod, skip build step (already done in previous step)
166
+ SKIP_BUILD_FLAG=""
167
+ if [ "$ENVIRONMENT" == "prod" ]; then
168
+ SKIP_BUILD_FLAG="SKIP_BUILD=true"
169
+ fi
170
+
171
+ # Prepare environment variables for SSH (base64 encode to handle special characters)
172
+ ENV_VARS_EXPORT=""
173
+ if [ "$ENVIRONMENT" == "staging" ] && [ -n "$STAGING_ENVS" ]; then
174
+ ENV_VARS_B64=$(echo -n "$STAGING_ENVS" | base64 -w 0)
175
+ ENV_VARS_EXPORT="STAGING_ENVS=\$(echo '$ENV_VARS_B64' | base64 -d) && export STAGING_ENVS && "
176
+ elif [ "$ENVIRONMENT" == "prod" ] && [ -n "$PROD_ENVS" ]; then
177
+ ENV_VARS_B64=$(echo -n "$PROD_ENVS" | base64 -w 0)
178
+ ENV_VARS_EXPORT="PROD_ENVS=\$(echo '$ENV_VARS_B64' | base64 -d) && export PROD_ENVS && "
179
+ fi
180
+
181
+ ssh -i ~/.ssh/deploy_key -o StrictHostKeyChecking=no "$USER@$HOST" \
182
+ "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
183
+ REPO_DIR=\"\$HOME/.factiii/$REPO_NAME\" && \
184
+ if [ -d \"\$REPO_DIR\" ]; then \
185
+ cd \"\$REPO_DIR\" && \
186
+ $ENV_VARS_EXPORT$SKIP_BUILD_FLAG GITHUB_ACTIONS=true COMMIT_HASH=$COMMIT_HASH BRANCH=$BRANCH GITHUB_REPO=$GITHUB_REPO \
187
+ npx stack deploy --$ENVIRONMENT --commit $COMMIT_HASH --branch $BRANCH; \
188
+ else \
189
+ echo \"❌ Repo directory not found at \$REPO_DIR\"; \
190
+ echo \"Run deployment first to clone the repository\"; \
191
+ exit 1; \
192
+ fi"
193
+
194
+ DEPLOY_EXIT_CODE=$?
195
+ rm -f ~/.ssh/deploy_key ~/.ssh/staging_deploy_key ~/.ssh/prod_deploy_key
196
+
197
+ if [ $DEPLOY_EXIT_CODE -eq 0 ]; then
198
+ echo "✅ Deployment complete!"
199
+ else
200
+ echo "❌ Deployment failed with exit code $DEPLOY_EXIT_CODE"
201
+ exit $DEPLOY_EXIT_CODE
202
+ fi