@factiii/stack 0.1.185 → 0.1.187

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 (35) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +441 -441
  3. package/bin/stack +300 -300
  4. package/dist/cli/dev-sync.js +16 -16
  5. package/dist/cli/init.d.ts.map +1 -1
  6. package/dist/cli/init.js +9 -1
  7. package/dist/cli/init.js.map +1 -1
  8. package/dist/plugins/addons/auth/index.js +7 -7
  9. package/dist/plugins/addons/vercel/index.js +9 -9
  10. package/dist/plugins/addons/vercel/scanfix/config.js +10 -10
  11. package/dist/plugins/addons/vercel/scanfix/token.js +15 -15
  12. package/dist/plugins/approved.json +13 -13
  13. package/dist/plugins/pipelines/aws/index.js +12 -12
  14. package/dist/plugins/pipelines/aws/policies/bootstrap-policy.json +135 -135
  15. package/dist/plugins/pipelines/aws/prod.js +1 -1
  16. package/dist/plugins/pipelines/factiii/prod.js +21 -21
  17. package/dist/plugins/pipelines/factiii/scanfix/bootstrap.d.ts.map +1 -1
  18. package/dist/plugins/pipelines/factiii/scanfix/bootstrap.js +10 -2
  19. package/dist/plugins/pipelines/factiii/scanfix/bootstrap.js.map +1 -1
  20. package/dist/plugins/pipelines/factiii/staging.js +23 -23
  21. package/dist/plugins/pipelines/factiii/workflows/stack-ci.yml +75 -75
  22. package/dist/plugins/pipelines/factiii/workflows/stack-cicd-prod.yml +73 -73
  23. package/dist/plugins/servers/amazon-linux/index.js +16 -16
  24. package/dist/plugins/servers/mac/index.js +12 -12
  25. package/dist/plugins/servers/mac/staging.js +2 -2
  26. package/dist/plugins/servers/ubuntu/index.js +23 -23
  27. package/dist/plugins/servers/windows/index.js +15 -15
  28. package/dist/scripts/generate-all.js +73 -73
  29. package/dist/utils/deployment-report.js +2 -2
  30. package/dist/utils/secret-prompts.js +34 -34
  31. package/dist/utils/ssh-helper.d.ts.map +1 -1
  32. package/dist/utils/ssh-helper.js +178 -25
  33. package/dist/utils/ssh-helper.js.map +1 -1
  34. package/dist/utils/template-generator.js +74 -74
  35. package/package.json +100 -93
package/bin/stack CHANGED
@@ -1,301 +1,301 @@
1
1
  #!/usr/bin/env node
2
-
3
- const { program } = require('commander');
4
- const path = require('path');
5
-
6
- // Import plugin command registration
7
- const { registerPluginCommands } = require('../dist/cli/plugin-commands');
8
-
9
- // Import CLI commands from compiled TypeScript
10
- const { init } = require('../dist/cli/init');
11
- const { scan } = require('../dist/cli/scan');
12
- const { fix } = require('../dist/cli/fix');
13
- const { deploy } = require('../dist/cli/deploy');
14
- const { prCheck } = require('../dist/cli/pr-check');
15
- const { undeploy } = require('../dist/cli/undeploy');
16
- const { secrets } = require('../dist/cli/secrets');
17
- const { upgrade } = require('../dist/cli/upgrade');
18
- const { validate } = require('../dist/cli/validate');
19
- const { checkConfig } = require('../dist/cli/check-config');
20
- const { devSync } = require('../dist/cli/dev-sync');
21
- const { devReset } = require('../dist/cli/dev-reset');
22
-
23
- // Read version from package.json
24
- const packageJson = require('../package.json');
25
-
26
- program
27
- .name('stack')
28
- .description('Stack - Infrastructure management CLI')
29
- .version(packageJson.version);
30
-
31
- // Init command (primary setup command)
32
- program
33
- .command('init')
34
- .description('Initialize Stack in your project (run this first)')
35
- .option('-f, --force', 'Overwrite existing config files')
36
- .action(async (options) => {
37
- await init(options);
38
- });
39
-
40
- // Default action: run scan when no subcommand provided (self-bootstrapping)
41
- program
42
- .action(async (options, command) => {
43
- if (command.args.length === 0) {
44
- await scan({});
45
- }
46
- });
47
-
48
- // Scan command (explicit)
49
- program
50
- .command('scan')
51
- .description('Scan all environments for issues')
52
- .option('--dev', 'Scan dev environment only')
53
- .option('--staging', 'Scan staging environment only')
54
- .option('--prod', 'Scan production environment only')
55
- .option('--secrets', 'Scan secrets only')
56
- .option('--commit <hash>', 'Commit hash to verify (for deployment verification)')
57
- .action(async (options) => {
58
- await scan(options);
59
- });
60
-
61
- // Fix command
62
- program
63
- .command('fix')
64
- .description('Fix all environments including uploading secrets to GitHub and installing server dependencies')
65
- .option('--dev', 'Fix dev environment only')
66
- .option('--staging', 'Fix staging environment only')
67
- .option('--prod', 'Fix production environment only')
68
- .option('--secrets', 'Fix secrets only')
69
- .option('--token <token>', 'GitHub token (or set GITHUB_TOKEN env var)')
70
- .option('--continue-on-error', 'Continue even if some fixes fail')
71
- .action(async (options) => {
72
- await fix(options);
73
- });
74
-
75
- // PR Check command (validates builds before merge)
76
- program
77
- .command('pr-check')
78
- .description('Run server/client/mobile builds and report to GitHub (for PR validation)')
79
- .option('--staging', 'Run on staging server')
80
- .action(async (options) => {
81
- const result = await prCheck(options);
82
- if (!result.success) {
83
- process.exit(1);
84
- }
85
- });
86
-
87
- // Deploy command (also handles secrets management via --secrets [action])
88
- program
89
- .command('deploy [secretName]')
90
- .description('Deploy to environments, or manage secrets with --secrets <action>')
91
- .option('--dev', 'Deploy dev environment only (local containers)')
92
- .option('--staging', 'Deploy staging environment only')
93
- .option('--prod', 'Deploy production environment only')
94
- .option('-e, --environment <env>', 'Environment (staging|prod)')
95
- .option('-b, --branch <branch>', 'Branch to deploy from (defaults to current branch)')
96
- .option('--commit <hash>', 'Commit hash to deploy (passed by workflow)')
97
- .option('--token <token>', 'GitHub token (or set GITHUB_TOKEN env var)')
98
- .option('--secrets [action]', 'Manage or deploy secrets (list|set|check|set-env|list-env|deploy|write-ssh-keys)')
99
- .option('--value <value>', 'Secret value (for scripting)')
100
- .option('--restart', 'Restart containers after deploying secrets')
101
- .option('--dry-run', 'Show deployment plan without actually deploying')
102
- .action(async (secretName, options) => {
103
- // --secrets with an action string = vault management mode
104
- if (typeof options.secrets === 'string') {
105
- const action = options.secrets;
106
- const validActions = ['list', 'set', 'check', 'set-env', 'list-env', 'deploy', 'write-ssh-keys'];
107
-
108
- if (!validActions.includes(action)) {
109
- console.log('');
110
- console.log('Unknown secrets action: ' + action);
111
- console.log('');
112
- console.log('Usage: npx stack deploy --secrets <action> [name] [options]');
113
- console.log('');
114
- console.log('Actions:');
115
- console.log(' list List all secrets (SSH keys + env vars)');
116
- console.log(' set <name> Set a secret (STAGING_SSH, PROD_SSH, etc.)');
117
- console.log(' check [name] Check if secrets exist');
118
- console.log(' set-env <name> Set environment variable for a stage');
119
- console.log(' list-env List environment variable keys for a stage');
120
- console.log(' deploy Deploy secrets to staging/prod servers');
121
- console.log(' write-ssh-keys Write SSH keys to ~/.ssh/ (for workflows)');
122
- console.log('');
123
- console.log('Examples:');
124
- console.log(' npx stack deploy --secrets list');
125
- console.log(' npx stack deploy --secrets set STAGING_SSH');
126
- console.log(' npx stack deploy --secrets set-env DATABASE_URL --staging');
127
- console.log(' npx stack deploy --secrets deploy --staging');
128
- console.log('');
129
- return;
130
- }
131
-
132
- await secrets(action, secretName, options);
133
- return;
134
- }
135
-
136
- // --secrets as boolean flag = deploy secrets before app deploy
137
- // Determine which environment to deploy
138
- let environment = options.environment;
139
- if (options.dev) environment = 'dev';
140
- if (options.staging) environment = 'staging';
141
- if (options.prod) environment = 'prod';
142
-
143
- if (!environment) {
144
- console.log('Please specify an environment: --dev, --staging, or --prod');
145
- process.exit(1);
146
- }
147
-
148
- // Map --secrets boolean flag to deploySecrets option
149
- if (options.secrets === true) {
150
- options.deploySecrets = true;
151
- }
152
-
153
- const result = await deploy(environment, options);
154
- if (!result.success) {
155
- process.exit(1);
156
- }
157
- });
158
-
159
- // Undeploy command
160
- program
161
- .command('undeploy')
162
- .description('Remove deployment from servers')
163
- .option('--dev', 'Undeploy dev environment only')
164
- .option('--staging', 'Undeploy staging environment only')
165
- .option('--prod', 'Undeploy production environment only')
166
- .option('-e, --environment <env>', 'Environment (staging|prod|all)', 'all')
167
- .action(async (options) => {
168
- // Determine which environment to undeploy
169
- let environment = options.environment;
170
- if (options.dev) environment = 'dev';
171
- if (options.staging) environment = 'staging';
172
- if (options.prod) environment = 'prod';
173
-
174
- await undeploy(environment, options);
175
- });
176
-
177
- // Generate workflows command
178
- program
179
- .command('generate-workflows')
180
- .description('Generate GitHub workflow files')
181
- .option('-o, --output <dir>', 'Output directory', '.github/workflows')
182
- .action(async (options) => {
183
- const FactiiiPipeline = require('../dist/plugins/pipelines/factiii').default;
184
- await FactiiiPipeline.generateWorkflows(process.cwd());
185
- });
186
-
187
- // Upgrade command
188
- program
189
- .command('upgrade')
190
- .description('Check for updates and regenerate configs for new version')
191
- .option('--check', 'Only check for updates, do not upgrade')
192
- .action(async (options) => {
193
- await upgrade(options);
194
- });
195
-
196
- // Dev sync command - only available in dev mode
197
- if (process.env.DEV_MODE === 'true') {
198
- program
199
- .command('dev-sync')
200
- .description('Sync locally built infrastructure to servers')
201
- .option('--staging', 'Sync to staging environment only')
202
- .option('--prod', 'Sync to production environment only')
203
- .option('--deploy', 'Deploy after syncing')
204
- .action(async (options) => {
205
- await devSync(options);
206
- });
207
- }
208
-
209
- // Dev reset command
210
- program
211
- .command('dev-reset')
212
- .description('Reset local config/secrets to test 0-to-deployed flow')
213
- .option('--dry-run', 'Show what would be deleted without doing it')
214
- .action(async (options) => {
215
- await devReset(options);
216
- });
217
-
218
- // Legacy commands (for backwards compatibility)
219
- program
220
- .command('validate')
221
- .description('[Legacy] Validate stack.yml config file')
222
- .option('-c, --config <path>', 'Path to config file', 'stack.yml')
223
- .action(async (options) => {
224
- await validate(options);
225
- });
226
-
227
- program
228
- .command('check-config')
229
- .description('[Legacy] Check and regenerate configs on servers')
230
- .option('-e, --environment <env>', 'Environment (staging|prod|all)', 'all')
231
- .action(async (options) => {
232
- await checkConfig(options);
233
- });
234
-
235
- // Register plugin commands (db, ops, backup)
236
- // Pipeline plugin provides these commands via static commands array
237
- try {
238
- const FactiiiPipeline = require('../dist/plugins/pipelines/factiii').default;
239
- if (FactiiiPipeline.commands && FactiiiPipeline.commands.length > 0) {
240
- registerPluginCommands(program, FactiiiPipeline);
241
- }
242
- } catch (e) {
243
- // Plugin commands not available - continue without them
244
- }
245
-
246
- // Add detailed help text after Commander's default output
247
- program.addHelpText('after', '\n' +
248
- 'CORE COMMANDS\n' +
249
- ' npx stack Scan all environments (default command)\n' +
250
- ' npx stack init First-time setup — creates stack.yml, configures vault\n' +
251
- ' npx stack scan [--stage] Detect issues across environments (read-only, never modifies files)\n' +
252
- ' npx stack fix [--stage] Auto-fix detected issues (installs tools, creates configs)\n' +
253
- ' npx stack deploy --<stage> Build and deploy to an environment\n' +
254
- ' npx stack undeploy --<stage> Remove deployment from a server\n' +
255
- ' npx stack dev-reset [--dry-run] Reset local config/secrets for fresh bootstrap\n' +
256
- '\n' +
257
- 'SECRETS — npx stack deploy --secrets <action>\n' +
258
- ' list Show all stored vault secrets\n' +
259
- ' set <name> Set a secret (STAGING_SSH, PROD_SSH, etc.)\n' +
260
- ' check [name] Check if secrets exist\n' +
261
- ' set-env <name> --<stage> Set environment variable for a stage\n' +
262
- ' list-env --<stage> List environment variable keys for a stage\n' +
263
- ' deploy --<stage> Push secrets to server\n' +
264
- ' write-ssh-keys Write SSH keys to ~/.ssh/\n' +
265
- '\n' +
266
- 'DATABASE — npx stack db <command> --<stage>\n' +
267
- ' seed --staging Run db:seed script to populate initial data\n' +
268
- ' migrate --prod Apply pending Prisma migrations (safe, non-destructive)\n' +
269
- ' reset --staging Drop all tables, re-run all migrations (DESTRUCTIVE)\n' +
270
- ' status --prod List pending and applied migrations\n' +
271
- '\n' +
272
- 'OPERATIONS — npx stack ops <command> --<stage>\n' +
273
- ' logs --staging [-f] [-n 50] View container logs (-f to follow, -n for line count)\n' +
274
- ' restart --prod [-s service] Restart containers without rebuilding images\n' +
275
- ' shell --staging Open /bin/sh inside the app container\n' +
276
- ' status --staging Show docker compose container status\n' +
277
- ' change-vault-password Change the Ansible Vault encryption password\n' +
278
- '\n' +
279
- 'BACKUP — npx stack backup <command> --<stage>\n' +
280
- ' create --prod [-o backup.sql] Export database via pg_dump\n' +
281
- ' restore --staging -i backup.sql Restore database from SQL dump (DESTRUCTIVE)\n' +
282
- ' health --prod Check container and database connectivity\n' +
283
- '\n' +
284
- 'STAGES\n' +
285
- ' --dev Local development environment\n' +
286
- ' --secrets Ansible Vault secrets (SSH keys, credentials)\n' +
287
- ' --staging Staging server (accessed via SSH)\n' +
288
- ' --prod Production server (via SSH or AWS provisioning)\n' +
289
- '\n' +
290
- 'EXAMPLES\n' +
291
- ' npx stack Scan and bootstrap a new project\n' +
292
- ' npx stack fix --prod Provision AWS infrastructure or fix server issues\n' +
293
- ' npx stack deploy --staging Build and deploy to staging\n' +
294
- ' npx stack deploy --secrets list Show all stored vault secrets\n' +
295
- ' npx stack deploy --secrets deploy --prod Push secrets to production server\n' +
296
- ' npx stack db migrate --prod Run production database migrations\n' +
297
- ' npx stack backup create --prod Create production database backup\n' +
298
- ' npx stack ops logs --staging -f Follow staging container logs\n'
299
- );
300
-
301
- program.parse();
2
+
3
+ const { program } = require('commander');
4
+ const path = require('path');
5
+
6
+ // Import plugin command registration
7
+ const { registerPluginCommands } = require('../dist/cli/plugin-commands');
8
+
9
+ // Import CLI commands from compiled TypeScript
10
+ const { init } = require('../dist/cli/init');
11
+ const { scan } = require('../dist/cli/scan');
12
+ const { fix } = require('../dist/cli/fix');
13
+ const { deploy } = require('../dist/cli/deploy');
14
+ const { prCheck } = require('../dist/cli/pr-check');
15
+ const { undeploy } = require('../dist/cli/undeploy');
16
+ const { secrets } = require('../dist/cli/secrets');
17
+ const { upgrade } = require('../dist/cli/upgrade');
18
+ const { validate } = require('../dist/cli/validate');
19
+ const { checkConfig } = require('../dist/cli/check-config');
20
+ const { devSync } = require('../dist/cli/dev-sync');
21
+ const { devReset } = require('../dist/cli/dev-reset');
22
+
23
+ // Read version from package.json
24
+ const packageJson = require('../package.json');
25
+
26
+ program
27
+ .name('stack')
28
+ .description('Stack - Infrastructure management CLI')
29
+ .version(packageJson.version);
30
+
31
+ // Init command (primary setup command)
32
+ program
33
+ .command('init')
34
+ .description('Initialize Stack in your project (run this first)')
35
+ .option('-f, --force', 'Overwrite existing config files')
36
+ .action(async (options) => {
37
+ await init(options);
38
+ });
39
+
40
+ // Default action: run scan when no subcommand provided (self-bootstrapping)
41
+ program
42
+ .action(async (options, command) => {
43
+ if (command.args.length === 0) {
44
+ await scan({});
45
+ }
46
+ });
47
+
48
+ // Scan command (explicit)
49
+ program
50
+ .command('scan')
51
+ .description('Scan all environments for issues')
52
+ .option('--dev', 'Scan dev environment only')
53
+ .option('--staging', 'Scan staging environment only')
54
+ .option('--prod', 'Scan production environment only')
55
+ .option('--secrets', 'Scan secrets only')
56
+ .option('--commit <hash>', 'Commit hash to verify (for deployment verification)')
57
+ .action(async (options) => {
58
+ await scan(options);
59
+ });
60
+
61
+ // Fix command
62
+ program
63
+ .command('fix')
64
+ .description('Fix all environments including uploading secrets to GitHub and installing server dependencies')
65
+ .option('--dev', 'Fix dev environment only')
66
+ .option('--staging', 'Fix staging environment only')
67
+ .option('--prod', 'Fix production environment only')
68
+ .option('--secrets', 'Fix secrets only')
69
+ .option('--token <token>', 'GitHub token (or set GITHUB_TOKEN env var)')
70
+ .option('--continue-on-error', 'Continue even if some fixes fail')
71
+ .action(async (options) => {
72
+ await fix(options);
73
+ });
74
+
75
+ // PR Check command (validates builds before merge)
76
+ program
77
+ .command('pr-check')
78
+ .description('Run server/client/mobile builds and report to GitHub (for PR validation)')
79
+ .option('--staging', 'Run on staging server')
80
+ .action(async (options) => {
81
+ const result = await prCheck(options);
82
+ if (!result.success) {
83
+ process.exit(1);
84
+ }
85
+ });
86
+
87
+ // Deploy command (also handles secrets management via --secrets [action])
88
+ program
89
+ .command('deploy [secretName]')
90
+ .description('Deploy to environments, or manage secrets with --secrets <action>')
91
+ .option('--dev', 'Deploy dev environment only (local containers)')
92
+ .option('--staging', 'Deploy staging environment only')
93
+ .option('--prod', 'Deploy production environment only')
94
+ .option('-e, --environment <env>', 'Environment (staging|prod)')
95
+ .option('-b, --branch <branch>', 'Branch to deploy from (defaults to current branch)')
96
+ .option('--commit <hash>', 'Commit hash to deploy (passed by workflow)')
97
+ .option('--token <token>', 'GitHub token (or set GITHUB_TOKEN env var)')
98
+ .option('--secrets [action]', 'Manage or deploy secrets (list|set|check|set-env|list-env|deploy|write-ssh-keys)')
99
+ .option('--value <value>', 'Secret value (for scripting)')
100
+ .option('--restart', 'Restart containers after deploying secrets')
101
+ .option('--dry-run', 'Show deployment plan without actually deploying')
102
+ .action(async (secretName, options) => {
103
+ // --secrets with an action string = vault management mode
104
+ if (typeof options.secrets === 'string') {
105
+ const action = options.secrets;
106
+ const validActions = ['list', 'set', 'check', 'set-env', 'list-env', 'deploy', 'write-ssh-keys'];
107
+
108
+ if (!validActions.includes(action)) {
109
+ console.log('');
110
+ console.log('Unknown secrets action: ' + action);
111
+ console.log('');
112
+ console.log('Usage: npx stack deploy --secrets <action> [name] [options]');
113
+ console.log('');
114
+ console.log('Actions:');
115
+ console.log(' list List all secrets (SSH keys + env vars)');
116
+ console.log(' set <name> Set a secret (STAGING_SSH, PROD_SSH, etc.)');
117
+ console.log(' check [name] Check if secrets exist');
118
+ console.log(' set-env <name> Set environment variable for a stage');
119
+ console.log(' list-env List environment variable keys for a stage');
120
+ console.log(' deploy Deploy secrets to staging/prod servers');
121
+ console.log(' write-ssh-keys Write SSH keys to ~/.ssh/ (for workflows)');
122
+ console.log('');
123
+ console.log('Examples:');
124
+ console.log(' npx stack deploy --secrets list');
125
+ console.log(' npx stack deploy --secrets set STAGING_SSH');
126
+ console.log(' npx stack deploy --secrets set-env DATABASE_URL --staging');
127
+ console.log(' npx stack deploy --secrets deploy --staging');
128
+ console.log('');
129
+ return;
130
+ }
131
+
132
+ await secrets(action, secretName, options);
133
+ return;
134
+ }
135
+
136
+ // --secrets as boolean flag = deploy secrets before app deploy
137
+ // Determine which environment to deploy
138
+ let environment = options.environment;
139
+ if (options.dev) environment = 'dev';
140
+ if (options.staging) environment = 'staging';
141
+ if (options.prod) environment = 'prod';
142
+
143
+ if (!environment) {
144
+ console.log('Please specify an environment: --dev, --staging, or --prod');
145
+ process.exit(1);
146
+ }
147
+
148
+ // Map --secrets boolean flag to deploySecrets option
149
+ if (options.secrets === true) {
150
+ options.deploySecrets = true;
151
+ }
152
+
153
+ const result = await deploy(environment, options);
154
+ if (!result.success) {
155
+ process.exit(1);
156
+ }
157
+ });
158
+
159
+ // Undeploy command
160
+ program
161
+ .command('undeploy')
162
+ .description('Remove deployment from servers')
163
+ .option('--dev', 'Undeploy dev environment only')
164
+ .option('--staging', 'Undeploy staging environment only')
165
+ .option('--prod', 'Undeploy production environment only')
166
+ .option('-e, --environment <env>', 'Environment (staging|prod|all)', 'all')
167
+ .action(async (options) => {
168
+ // Determine which environment to undeploy
169
+ let environment = options.environment;
170
+ if (options.dev) environment = 'dev';
171
+ if (options.staging) environment = 'staging';
172
+ if (options.prod) environment = 'prod';
173
+
174
+ await undeploy(environment, options);
175
+ });
176
+
177
+ // Generate workflows command
178
+ program
179
+ .command('generate-workflows')
180
+ .description('Generate GitHub workflow files')
181
+ .option('-o, --output <dir>', 'Output directory', '.github/workflows')
182
+ .action(async (options) => {
183
+ const FactiiiPipeline = require('../dist/plugins/pipelines/factiii').default;
184
+ await FactiiiPipeline.generateWorkflows(process.cwd());
185
+ });
186
+
187
+ // Upgrade command
188
+ program
189
+ .command('upgrade')
190
+ .description('Check for updates and regenerate configs for new version')
191
+ .option('--check', 'Only check for updates, do not upgrade')
192
+ .action(async (options) => {
193
+ await upgrade(options);
194
+ });
195
+
196
+ // Dev sync command - only available in dev mode
197
+ if (process.env.DEV_MODE === 'true') {
198
+ program
199
+ .command('dev-sync')
200
+ .description('Sync locally built infrastructure to servers')
201
+ .option('--staging', 'Sync to staging environment only')
202
+ .option('--prod', 'Sync to production environment only')
203
+ .option('--deploy', 'Deploy after syncing')
204
+ .action(async (options) => {
205
+ await devSync(options);
206
+ });
207
+ }
208
+
209
+ // Dev reset command
210
+ program
211
+ .command('dev-reset')
212
+ .description('Reset local config/secrets to test 0-to-deployed flow')
213
+ .option('--dry-run', 'Show what would be deleted without doing it')
214
+ .action(async (options) => {
215
+ await devReset(options);
216
+ });
217
+
218
+ // Legacy commands (for backwards compatibility)
219
+ program
220
+ .command('validate')
221
+ .description('[Legacy] Validate stack.yml config file')
222
+ .option('-c, --config <path>', 'Path to config file', 'stack.yml')
223
+ .action(async (options) => {
224
+ await validate(options);
225
+ });
226
+
227
+ program
228
+ .command('check-config')
229
+ .description('[Legacy] Check and regenerate configs on servers')
230
+ .option('-e, --environment <env>', 'Environment (staging|prod|all)', 'all')
231
+ .action(async (options) => {
232
+ await checkConfig(options);
233
+ });
234
+
235
+ // Register plugin commands (db, ops, backup)
236
+ // Pipeline plugin provides these commands via static commands array
237
+ try {
238
+ const FactiiiPipeline = require('../dist/plugins/pipelines/factiii').default;
239
+ if (FactiiiPipeline.commands && FactiiiPipeline.commands.length > 0) {
240
+ registerPluginCommands(program, FactiiiPipeline);
241
+ }
242
+ } catch (e) {
243
+ // Plugin commands not available - continue without them
244
+ }
245
+
246
+ // Add detailed help text after Commander's default output
247
+ program.addHelpText('after', '\n' +
248
+ 'CORE COMMANDS\n' +
249
+ ' npx stack Scan all environments (default command)\n' +
250
+ ' npx stack init First-time setup — creates stack.yml, configures vault\n' +
251
+ ' npx stack scan [--stage] Detect issues across environments (read-only, never modifies files)\n' +
252
+ ' npx stack fix [--stage] Auto-fix detected issues (installs tools, creates configs)\n' +
253
+ ' npx stack deploy --<stage> Build and deploy to an environment\n' +
254
+ ' npx stack undeploy --<stage> Remove deployment from a server\n' +
255
+ ' npx stack dev-reset [--dry-run] Reset local config/secrets for fresh bootstrap\n' +
256
+ '\n' +
257
+ 'SECRETS — npx stack deploy --secrets <action>\n' +
258
+ ' list Show all stored vault secrets\n' +
259
+ ' set <name> Set a secret (STAGING_SSH, PROD_SSH, etc.)\n' +
260
+ ' check [name] Check if secrets exist\n' +
261
+ ' set-env <name> --<stage> Set environment variable for a stage\n' +
262
+ ' list-env --<stage> List environment variable keys for a stage\n' +
263
+ ' deploy --<stage> Push secrets to server\n' +
264
+ ' write-ssh-keys Write SSH keys to ~/.ssh/\n' +
265
+ '\n' +
266
+ 'DATABASE — npx stack db <command> --<stage>\n' +
267
+ ' seed --staging Run db:seed script to populate initial data\n' +
268
+ ' migrate --prod Apply pending Prisma migrations (safe, non-destructive)\n' +
269
+ ' reset --staging Drop all tables, re-run all migrations (DESTRUCTIVE)\n' +
270
+ ' status --prod List pending and applied migrations\n' +
271
+ '\n' +
272
+ 'OPERATIONS — npx stack ops <command> --<stage>\n' +
273
+ ' logs --staging [-f] [-n 50] View container logs (-f to follow, -n for line count)\n' +
274
+ ' restart --prod [-s service] Restart containers without rebuilding images\n' +
275
+ ' shell --staging Open /bin/sh inside the app container\n' +
276
+ ' status --staging Show docker compose container status\n' +
277
+ ' change-vault-password Change the Ansible Vault encryption password\n' +
278
+ '\n' +
279
+ 'BACKUP — npx stack backup <command> --<stage>\n' +
280
+ ' create --prod [-o backup.sql] Export database via pg_dump\n' +
281
+ ' restore --staging -i backup.sql Restore database from SQL dump (DESTRUCTIVE)\n' +
282
+ ' health --prod Check container and database connectivity\n' +
283
+ '\n' +
284
+ 'STAGES\n' +
285
+ ' --dev Local development environment\n' +
286
+ ' --secrets Ansible Vault secrets (SSH keys, credentials)\n' +
287
+ ' --staging Staging server (accessed via SSH)\n' +
288
+ ' --prod Production server (via SSH or AWS provisioning)\n' +
289
+ '\n' +
290
+ 'EXAMPLES\n' +
291
+ ' npx stack Scan and bootstrap a new project\n' +
292
+ ' npx stack fix --prod Provision AWS infrastructure or fix server issues\n' +
293
+ ' npx stack deploy --staging Build and deploy to staging\n' +
294
+ ' npx stack deploy --secrets list Show all stored vault secrets\n' +
295
+ ' npx stack deploy --secrets deploy --prod Push secrets to production server\n' +
296
+ ' npx stack db migrate --prod Run production database migrations\n' +
297
+ ' npx stack backup create --prod Create production database backup\n' +
298
+ ' npx stack ops logs --staging -f Follow staging container logs\n'
299
+ );
300
+
301
+ program.parse();
@@ -361,19 +361,19 @@ async function syncToServer(tarPath, environment, config, sshKeyPath) {
361
361
  await exec(`scp -i "${sshKeyPath}" -o StrictHostKeyChecking=no "${tarPath}" "${user}@${host}:/tmp/infrastructure.tar.gz"`, { maxBuffer: 50 * 1024 * 1024 });
362
362
  // SSH to server and extract
363
363
  console.log(' Extracting on server...');
364
- await exec(`ssh -i "${sshKeyPath}" -o StrictHostKeyChecking=no "${user}@${host}" \
365
- "mkdir -p ~/.factiii/infrastructure && \
366
- cd ~/.factiii/infrastructure && \
367
- tar -xzf /tmp/infrastructure.tar.gz && \
368
- rm /tmp/infrastructure.tar.gz && \
369
- echo 'Installing infrastructure dependencies...' && \
370
- export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
371
- if [ -f 'pnpm-lock.yaml' ]; then \
372
- command -v pnpm >/dev/null 2>&1 || npm install -g pnpm && \
373
- pnpm install; \
374
- else \
375
- npm install; \
376
- fi && \
364
+ await exec(`ssh -i "${sshKeyPath}" -o StrictHostKeyChecking=no "${user}@${host}" \
365
+ "mkdir -p ~/.factiii/infrastructure && \
366
+ cd ~/.factiii/infrastructure && \
367
+ tar -xzf /tmp/infrastructure.tar.gz && \
368
+ rm /tmp/infrastructure.tar.gz && \
369
+ echo 'Installing infrastructure dependencies...' && \
370
+ export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
371
+ if [ -f 'pnpm-lock.yaml' ]; then \
372
+ command -v pnpm >/dev/null 2>&1 || npm install -g pnpm && \
373
+ pnpm install; \
374
+ else \
375
+ npm install; \
376
+ fi && \
377
377
  echo '[OK] Infrastructure synced successfully'"`);
378
378
  console.log(` [OK] Synced to ${environment}\n`);
379
379
  }
@@ -400,9 +400,9 @@ async function deployAfterSync(environment, config, sshKeyPath) {
400
400
  const repoName = config.name || 'app';
401
401
  console.log(`Deploying to ${environment}...`);
402
402
  try {
403
- await exec(`ssh -i "${sshKeyPath}" -o StrictHostKeyChecking=no "${user}@${host}" \
404
- "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
405
- cd ~/.factiii/${repoName} && \
403
+ await exec(`ssh -i "${sshKeyPath}" -o StrictHostKeyChecking=no "${user}@${host}" \
404
+ "export PATH=\"/opt/homebrew/bin:/usr/local/bin:\$PATH\" && \
405
+ cd ~/.factiii/${repoName} && \
406
406
  GITHUB_ACTIONS=true node ~/.factiii/infrastructure/bin/factiii deploy --${environment}"`);
407
407
  console.log(` [OK] Deployed to ${environment}\n`);
408
408
  }