@factiii/stack 0.7.3 → 0.9.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-commands.d.ts","sourceRoot":"","sources":["../../src/cli/plugin-commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EACV,aAAa,EACb,KAAK,EACL,aAAa,EACb,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,UAAU,mBAAmB;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY,CAAC;CAC7D;AAsCD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,mBAAmB,GAClC,IAAI,CAsDN"}
1
+ {"version":3,"file":"plugin-commands.d.ts","sourceRoot":"","sources":["../../src/cli/plugin-commands.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,KAAK,EACV,aAAa,EACb,KAAK,EACL,aAAa,EACb,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAE3B;;GAEG;AACH,UAAU,mBAAmB;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,CAAC,EAAE,aAAa,EAAE,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,GAAG,YAAY,CAAC;CAC7D;AAsCD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,mBAAmB,GAClC,IAAI,CA2EN"}
@@ -117,5 +117,25 @@ function registerPluginCommands(program, pipelinePlugin) {
117
117
  registerCommand(backupCmd, cmd, pipelinePlugin);
118
118
  }
119
119
  }
120
+ // Register 'aws' as a top-level command with a positional AWS command string.
121
+ // Simpler than a subcommand group: `npx stack aws --prod "s3 ls"`.
122
+ const awsCommands = byCategory.get('aws');
123
+ if (awsCommands && awsCommands.length > 0) {
124
+ const awsCmd = awsCommands[0];
125
+ if (awsCmd) {
126
+ program
127
+ .command('aws [command...]')
128
+ .description('Run an AWS CLI command with stage-appropriate credentials')
129
+ .option('--dev', 'Run on dev environment')
130
+ .option('--staging', 'Run on staging environment')
131
+ .option('--prod', 'Run on production environment')
132
+ .allowUnknownOption()
133
+ .action(async (commandParts, options) => {
134
+ options.cmd = (commandParts ?? []).join(' ');
135
+ const { executePluginCommand } = await Promise.resolve().then(() => __importStar(require('./execute-plugin-command.js')));
136
+ await executePluginCommand(awsCmd, options, pipelinePlugin);
137
+ });
138
+ }
139
+ }
120
140
  }
121
141
  //# sourceMappingURL=plugin-commands.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-commands.js","sourceRoot":"","sources":["../../src/cli/plugin-commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin-commands.js","sourceRoot":"","sources":["../../src/cli/plugin-commands.ts"],"names":[],"mappings":"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"}
@@ -124,6 +124,30 @@ declare class FactiiiPipeline {
124
124
  */
125
125
  static changeVaultPassword(config: FactiiiConfig, rootDir: string): Promise<CommandResult>;
126
126
  static readonly commands: PluginCommand[];
127
+ /**
128
+ * Resolve SSH target (host, user, key) for a stage.
129
+ * Shared by localOnly commands that manually SSH to run remote commands.
130
+ */
131
+ static resolveSSHTarget(stage: Stage, config: FactiiiConfig): Promise<{
132
+ success: true;
133
+ host: string;
134
+ user: string;
135
+ keyPath: string;
136
+ } | {
137
+ success: false;
138
+ error: string;
139
+ }>;
140
+ /**
141
+ * Run a command on a remote server via SSH with a login shell.
142
+ * Uses bash -lc to ensure PATH includes docker/colima/etc.
143
+ */
144
+ static sshExecCommand(keyPath: string, user: string, host: string, remoteCmd: string, options?: {
145
+ interactive?: boolean;
146
+ }): {
147
+ stdout: string;
148
+ stderr: string;
149
+ status: number | null;
150
+ };
127
151
  /**
128
152
  * Auto-detect pipeline configuration
129
153
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/pipelines/factiii/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/plugins/pipelines/factiii/index.ts"],"names":[],"mappings":"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"}
@@ -96,7 +96,7 @@ const port_convention_js_1 = require("./scanfix/port-convention.js");
96
96
  const start_sh_js_1 = require("./scanfix/start-sh.js");
97
97
  const db_seed_js_1 = require("./scanfix/db-seed.js");
98
98
  const ssh_verify_js_1 = require("./scanfix/ssh-verify.js");
99
- const prod_check_skill_js_1 = require("./scanfix/prod-check-skill.js");
99
+ const claude_skills_js_1 = require("./scanfix/claude-skills.js");
100
100
  // Import AWS scanfix arrays (AWS provisioning runs as part of factiii pipeline)
101
101
  const config_js_2 = require("../aws/scanfix/config.js");
102
102
  const credentials_js_1 = require("../aws/scanfix/credentials.js");
@@ -296,7 +296,7 @@ class FactiiiPipeline {
296
296
  ...port_convention_js_1.portConventionFixes,
297
297
  ...start_sh_js_1.startShFixes,
298
298
  ...db_seed_js_1.dbSeedFixes,
299
- ...prod_check_skill_js_1.prodCheckSkillFixes,
299
+ ...claude_skills_js_1.claudeSkillFixes,
300
300
  // AWS infrastructure provisioning (guarded by isAwsConfigured())
301
301
  ...config_js_2.configFixes,
302
302
  ...credentials_js_1.credentialsFixes,
@@ -614,7 +614,7 @@ class FactiiiPipeline {
614
614
  prodSafety: 'safe',
615
615
  options: [
616
616
  { flags: '-f, --follow', description: 'Follow log output' },
617
- { flags: '-n, --lines <number>', description: 'Number of lines to show', defaultValue: '100' },
617
+ { flags: '-n, --lines <number>', description: 'Number of lines to show', defaultValue: '30' },
618
618
  { flags: '-s, --service <name>', description: 'Service name (default: app container)' },
619
619
  { flags: '-t, --timestamps', description: 'Show timestamps on each log line' },
620
620
  { flags: '--grep <pattern>', description: 'Filter logs by pattern' },
@@ -624,7 +624,7 @@ class FactiiiPipeline {
624
624
  execute: async (stage, options, config, _rootDir) => {
625
625
  const serviceName = options.service ?? config.name + '-' + stage;
626
626
  const followFlag = options.follow ? '-f' : '';
627
- const lines = options.lines ?? '100';
627
+ const lines = options.lines ?? '30';
628
628
  const timestampFlag = options.timestamps ? '--timestamps' : '';
629
629
  const sinceFlag = options.since ? '--since ' + String(options.since) : '';
630
630
  const grepPattern = options.grep ? String(options.grep) : '';
@@ -729,36 +729,13 @@ class FactiiiPipeline {
729
729
  }
730
730
  }
731
731
  // On dev machine: SSH to server and run docker exec
732
- const { getEnvironmentsForStage } = await Promise.resolve().then(() => __importStar(require('../../../utils/config-helpers.js')));
733
- const environments = getEnvironmentsForStage(config, stage);
734
- const envNames = Object.keys(environments);
735
- if (envNames.length === 0) {
736
- return { success: false, error: 'No ' + stage + ' environment found in stack.yml' };
737
- }
738
- const envName = envNames[0];
739
- const envConfig = environments[envName];
740
- const host = envConfig.domain;
741
- const user = envConfig.ssh_user ?? 'ubuntu';
742
- if (!host) {
743
- return { success: false, error: 'No domain configured for ' + envName + ' in stack.yml' };
744
- }
745
- const keyPath = (0, ssh_helper_js_1.findSshKeyForStage)(stage, config.name);
746
- if (!keyPath) {
747
- return { success: false, error: 'No SSH key found for ' + stage + '. Run: npx stack fix --secrets' };
748
- }
749
- const sshArgs = [
750
- '-tt',
751
- '-i', keyPath,
752
- '-o', 'StrictHostKeyChecking=no',
753
- '-o', 'ServerAliveInterval=60',
754
- '-o', 'ServerAliveCountMax=5',
755
- user + '@' + host,
756
- 'docker exec -it ' + serviceName + ' /bin/sh',
757
- ];
758
- console.log('Connecting to ' + stage + ' container (' + user + '@' + host + ')...');
732
+ const target = await this.resolveSSHTarget(stage, config);
733
+ if (!target.success)
734
+ return target;
735
+ console.log('Connecting to ' + stage + ' container (' + target.user + '@' + target.host + ')...');
759
736
  console.log('Type "exit" or press Ctrl+D to close the shell.');
760
737
  console.log('');
761
- const result = (0, child_process_1.spawnSync)('ssh', sshArgs, { stdio: 'inherit' });
738
+ const result = this.sshExecCommand(target.keyPath, target.user, target.host, 'docker exec -it ' + serviceName + ' /bin/sh', { interactive: true });
762
739
  if (result.status !== 0 && result.status !== null) {
763
740
  return { success: false, error: 'SSH exited with code ' + result.status };
764
741
  }
@@ -917,8 +894,323 @@ class FactiiiPipeline {
917
894
  };
918
895
  },
919
896
  },
897
+ // ────────────────────────────────────────────────────────────
898
+ // OPS: API QUERY (safe — hits existing API routes)
899
+ // ────────────────────────────────────────────────────────────
900
+ {
901
+ name: 'api-query',
902
+ description: 'Query server API routes for data analysis (safe, read-only)',
903
+ category: 'ops',
904
+ stages: ['dev', 'staging', 'prod'],
905
+ prodSafety: 'safe',
906
+ localOnly: true,
907
+ options: [
908
+ { flags: '--url <path>', description: 'API route path (e.g., /api/health)' },
909
+ { flags: '--method <method>', description: 'HTTP method (GET, POST)', defaultValue: 'GET' },
910
+ { flags: '--body <json>', description: 'JSON request body for POST/PUT' },
911
+ { flags: '--header <header...>', description: 'Additional headers (key:value)' },
912
+ ],
913
+ execute: async (stage, options, config, _rootDir) => {
914
+ const urlPath = options.url;
915
+ if (!urlPath) {
916
+ return {
917
+ success: false,
918
+ error: 'API route required (--url /api/...)\n\n' +
919
+ 'Example:\n' +
920
+ ' npx stack ops api-query --' + stage + ' --url /api/health\n' +
921
+ ' npx stack ops api-query --' + stage + ' --url /api/users/count --method GET',
922
+ };
923
+ }
924
+ const { getEnvironmentsForStage } = await Promise.resolve().then(() => __importStar(require('../../../utils/config-helpers.js')));
925
+ const environments = getEnvironmentsForStage(config, stage);
926
+ const envNames = Object.keys(environments);
927
+ if (envNames.length === 0) {
928
+ return { success: false, error: 'No ' + stage + ' environment found in stack.yml' };
929
+ }
930
+ const envName = envNames[0];
931
+ const envConfig = environments[envName];
932
+ let domain = envConfig.domain;
933
+ if (!domain) {
934
+ return { success: false, error: 'No domain configured for ' + envName + ' in stack.yml' };
935
+ }
936
+ // Dev uses localhost
937
+ if (stage === 'dev') {
938
+ const port = envConfig.port ?? '3000';
939
+ domain = 'http://localhost:' + port;
940
+ }
941
+ else if (!domain.startsWith('http')) {
942
+ domain = 'https://' + domain;
943
+ }
944
+ const fullUrl = domain + urlPath;
945
+ const method = (options.method ?? 'GET').toUpperCase();
946
+ console.log('── API Query (' + stage + ') ──');
947
+ console.log(method + ' ' + fullUrl);
948
+ console.log('');
949
+ try {
950
+ const headers = { 'Content-Type': 'application/json' };
951
+ const rawHeaders = options.header;
952
+ if (rawHeaders) {
953
+ for (const h of rawHeaders) {
954
+ const [key, ...rest] = h.split(':');
955
+ if (key && rest.length > 0)
956
+ headers[key.trim()] = rest.join(':').trim();
957
+ }
958
+ }
959
+ const fetchOpts = { method, headers };
960
+ if (options.body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
961
+ fetchOpts.body = options.body;
962
+ }
963
+ const resp = await fetch(fullUrl, fetchOpts);
964
+ const text = await resp.text();
965
+ console.log('Status: ' + resp.status + ' ' + resp.statusText);
966
+ console.log('');
967
+ // Try pretty-print JSON, fall back to raw text
968
+ try {
969
+ const json = JSON.parse(text);
970
+ console.log(JSON.stringify(json, null, 2));
971
+ }
972
+ catch {
973
+ console.log(text);
974
+ }
975
+ return { success: resp.ok };
976
+ }
977
+ catch (error) {
978
+ return { success: false, error: 'Request failed: ' + (error instanceof Error ? error.message : String(error)) };
979
+ }
980
+ },
981
+ },
982
+ // ────────────────────────────────────────────────────────────
983
+ // OPS: DB QUERY (dangerous — direct SQL via SSH)
984
+ // ────────────────────────────────────────────────────────────
985
+ {
986
+ name: 'db-query',
987
+ description: 'Run a read-only SQL query against the database via SSH',
988
+ category: 'ops',
989
+ stages: ['staging', 'prod'],
990
+ prodSafety: 'caution',
991
+ localOnly: true,
992
+ options: [
993
+ { flags: '--sql <query>', description: 'SQL query to execute' },
994
+ { flags: '--dangerous', description: 'Acknowledge direct DB access (required)' },
995
+ { flags: '--limit <rows>', description: 'Max rows to return', defaultValue: '100' },
996
+ ],
997
+ execute: async (stage, options, config, _rootDir) => {
998
+ const sql = options.sql;
999
+ const dangerous = options.dangerous;
1000
+ const rowLimit = parseInt(options.limit ?? '100', 10);
1001
+ if (!sql) {
1002
+ return { success: false, error: 'SQL query required (--sql "SELECT ...")\n\nFor safe data analysis, prefer:\n npx stack ops api-query --' + stage + ' --url /api/...' };
1003
+ }
1004
+ if (!dangerous) {
1005
+ console.error('');
1006
+ console.error('================================================================');
1007
+ console.error(' DIRECT DATABASE ACCESS');
1008
+ console.error('================================================================');
1009
+ console.error('');
1010
+ console.error(' This command runs SQL directly against the ' + stage + ' database.');
1011
+ console.error(' You must acknowledge by adding --dangerous.');
1012
+ console.error('');
1013
+ console.error(' RULES:');
1014
+ console.error(' - READ ONLY — write queries are blocked');
1015
+ console.error(' - Avoid selecting secrets/passwords/tokens');
1016
+ console.error(' - Results are auto-limited to ' + rowLimit + ' rows');
1017
+ console.error('');
1018
+ console.error(' Example:');
1019
+ console.error(' npx stack ops db-query --' + stage + ' --dangerous --sql "SELECT id, email FROM users LIMIT 10"');
1020
+ console.error('');
1021
+ console.error(' For safe analysis, prefer API routes:');
1022
+ console.error(' npx stack ops api-query --' + stage + ' --url /api/...');
1023
+ console.error('');
1024
+ console.error('================================================================');
1025
+ return { success: false, error: 'Add --dangerous to acknowledge direct DB access' };
1026
+ }
1027
+ // === BLOCK DESTRUCTIVE SQL ===
1028
+ const upperSql = sql.toUpperCase().replace(/\s+/g, ' ').trim();
1029
+ const destructivePatterns = [
1030
+ /\bINSERT\b/, /\bUPDATE\b/, /\bDELETE\b/,
1031
+ /\bDROP\b/, /\bALTER\b/, /\bTRUNCATE\b/,
1032
+ /\bCREATE\b/, /\bGRANT\b/, /\bREVOKE\b/,
1033
+ /\bEXEC\b/, /\bCALL\b/,
1034
+ ];
1035
+ const isDestructive = destructivePatterns.some(p => p.test(upperSql));
1036
+ if (isDestructive) {
1037
+ console.error('');
1038
+ console.error('================================================================');
1039
+ console.error(' BLOCKED: DESTRUCTIVE SQL QUERY');
1040
+ console.error('================================================================');
1041
+ console.error('');
1042
+ console.error(' The query contains write/modify operations.');
1043
+ console.error(' db-query is READ ONLY — no INSERT, UPDATE, DELETE, DROP,');
1044
+ console.error(' ALTER, TRUNCATE, CREATE, GRANT, REVOKE, EXEC, or CALL.');
1045
+ console.error('');
1046
+ console.error('================================================================');
1047
+ return { success: false, error: 'Destructive queries are blocked in db-query' };
1048
+ }
1049
+ // === WARN ON SENSITIVE COLUMNS ===
1050
+ const sensitivePattern = /\b(password|secret|token|api_key|private_key|credential|ssn|credit_card|hash)\b/i;
1051
+ if (sensitivePattern.test(sql) && !/\bCOUNT\b/i.test(sql)) {
1052
+ console.warn('');
1053
+ console.warn('WARNING: Query may touch sensitive columns (password/secret/token/key).');
1054
+ console.warn('Consider selecting only the columns you need.');
1055
+ console.warn('');
1056
+ }
1057
+ // === AUTO-APPEND LIMIT ===
1058
+ let finalSql = sql.trim().replace(/;$/, '');
1059
+ if (!/\bLIMIT\b/i.test(finalSql)) {
1060
+ finalSql = finalSql + ' LIMIT ' + rowLimit;
1061
+ }
1062
+ const target = await FactiiiPipeline.resolveSSHTarget(stage, config);
1063
+ if (!target.success)
1064
+ return target;
1065
+ const appContainer = config.name + '-' + stage;
1066
+ const dbContainer = 'factiii_postgres';
1067
+ // Escape single quotes for shell
1068
+ const escapedSql = finalSql.replace(/'/g, "'\\''");
1069
+ console.log('');
1070
+ console.log('── DB Query (' + stage + ') ──');
1071
+ console.log('READ ONLY — destructive queries are blocked');
1072
+ console.log('App: ' + appContainer + ' | DB: ' + dbContainer);
1073
+ console.log('');
1074
+ console.log('SQL: ' + finalSql);
1075
+ console.log('');
1076
+ // Grab DATABASE_URL from the app container, then run psql in the postgres container
1077
+ const dockerCmd = 'DB_URL=$(docker exec ' + appContainer + ' printenv DATABASE_URL) && docker exec ' + dbContainer + ' psql "$DB_URL" -c "' + escapedSql.replace(/"/g, '\\"') + '"';
1078
+ const result = FactiiiPipeline.sshExecCommand(target.keyPath, target.user, target.host, dockerCmd);
1079
+ if (result.stdout) {
1080
+ console.log(result.stdout);
1081
+ }
1082
+ if (result.stderr) {
1083
+ const stderr = result.stderr.trim();
1084
+ if (stderr)
1085
+ console.error(stderr);
1086
+ }
1087
+ if (result.status !== 0 && result.status !== null) {
1088
+ return { success: false, error: 'Query failed (exit code ' + result.status + ')' };
1089
+ }
1090
+ return { success: true };
1091
+ },
1092
+ },
1093
+ // ────────────────────────────────────────────────────────────
1094
+ // AWS COMMANDS
1095
+ // ────────────────────────────────────────────────────────────
1096
+ {
1097
+ name: 'aws',
1098
+ description: 'Run an AWS CLI command with stage-appropriate credentials',
1099
+ category: 'aws',
1100
+ stages: ['staging', 'prod'],
1101
+ prodSafety: 'caution',
1102
+ localOnly: true,
1103
+ execute: async (stage, options, config, _rootDir) => {
1104
+ const awsCmd = (options.cmd ?? '').trim();
1105
+ if (!awsCmd) {
1106
+ return {
1107
+ success: false,
1108
+ error: 'AWS command required\n\n' +
1109
+ 'Examples:\n' +
1110
+ ' npx stack aws --' + stage + ' "s3 ls"\n' +
1111
+ ' npx stack aws --' + stage + ' "ec2 describe-instances"\n' +
1112
+ ' npx stack aws --' + stage + ' "rds describe-db-instances"',
1113
+ };
1114
+ }
1115
+ // Resolve AWS credentials from vault via the credentials file
1116
+ // The aws scanfix/credentials.ts writes ~/.aws/credentials from vault
1117
+ // We rely on that being synced — check if credentials exist
1118
+ const awsCredPath = (process.env.HOME ?? '') + '/.aws/credentials';
1119
+ let hasCredentials = false;
1120
+ try {
1121
+ const content = fs.readFileSync(awsCredPath, 'utf8');
1122
+ hasCredentials = /aws_access_key_id\s*=\s*\S+/.test(content);
1123
+ }
1124
+ catch {
1125
+ // no credentials file
1126
+ }
1127
+ if (!hasCredentials) {
1128
+ // Try to sync credentials from vault
1129
+ console.log('AWS credentials not found locally. Run scan to sync from vault:');
1130
+ console.log(' npx stack scan --' + stage);
1131
+ console.log('');
1132
+ return { success: false, error: 'AWS credentials not available. Run npx stack scan --' + stage + ' first to sync from vault.' };
1133
+ }
1134
+ // Get region from config
1135
+ const { getAwsConfig } = await Promise.resolve().then(() => __importStar(require('../aws/utils/aws-helpers.js')));
1136
+ const awsConfig = getAwsConfig(config);
1137
+ const region = awsConfig.region;
1138
+ console.log('── AWS CLI (' + stage + ') ──');
1139
+ console.log('Region: ' + region);
1140
+ console.log('Command: aws ' + awsCmd);
1141
+ console.log('');
1142
+ // Split the command string into args for spawn
1143
+ const args = awsCmd.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? [];
1144
+ // Strip quotes from args
1145
+ const cleanArgs = args.map(a => a.replace(/^["']|["']$/g, ''));
1146
+ // Add --region if not already specified and not a global command
1147
+ if (!cleanArgs.includes('--region')) {
1148
+ cleanArgs.push('--region', region);
1149
+ }
1150
+ const result = (0, child_process_1.spawnSync)('aws', cleanArgs, {
1151
+ stdio: 'inherit',
1152
+ env: { ...process.env, AWS_DEFAULT_REGION: region },
1153
+ });
1154
+ if (result.status !== 0 && result.status !== null) {
1155
+ return { success: false, error: 'AWS command exited with code ' + result.status };
1156
+ }
1157
+ return { success: true };
1158
+ },
1159
+ },
920
1160
  ];
921
1161
  // ============================================================
1162
+ // SSH HELPERS
1163
+ // ============================================================
1164
+ /**
1165
+ * Resolve SSH target (host, user, key) for a stage.
1166
+ * Shared by localOnly commands that manually SSH to run remote commands.
1167
+ */
1168
+ static async resolveSSHTarget(stage, config) {
1169
+ const { getEnvironmentsForStage } = await Promise.resolve().then(() => __importStar(require('../../../utils/config-helpers.js')));
1170
+ const environments = getEnvironmentsForStage(config, stage);
1171
+ const envNames = Object.keys(environments);
1172
+ if (envNames.length === 0) {
1173
+ return { success: false, error: 'No ' + stage + ' environment found in stack.yml' };
1174
+ }
1175
+ const envName = envNames[0];
1176
+ const envConfig = environments[envName];
1177
+ const host = envConfig.domain;
1178
+ const user = envConfig.ssh_user ?? 'ubuntu';
1179
+ if (!host) {
1180
+ return { success: false, error: 'No domain configured for ' + envName + ' in stack.yml' };
1181
+ }
1182
+ const keyPath = (0, ssh_helper_js_1.findSshKeyForStage)(stage, config.name);
1183
+ if (!keyPath) {
1184
+ return { success: false, error: 'No SSH key found for ' + stage + '. Run: npx stack fix --secrets' };
1185
+ }
1186
+ return { success: true, host, user, keyPath };
1187
+ }
1188
+ /**
1189
+ * Run a command on a remote server via SSH with a login shell.
1190
+ * Uses bash -lc to ensure PATH includes docker/colima/etc.
1191
+ */
1192
+ static sshExecCommand(keyPath, user, host, remoteCmd, options) {
1193
+ const sshArgs = [];
1194
+ if (options?.interactive) {
1195
+ sshArgs.push('-tt');
1196
+ }
1197
+ sshArgs.push('-i', keyPath, '-o', 'StrictHostKeyChecking=no', '-o', options?.interactive ? 'ServerAliveInterval=60' : 'ConnectTimeout=10');
1198
+ if (options?.interactive) {
1199
+ sshArgs.push('-o', 'ServerAliveCountMax=5');
1200
+ }
1201
+ sshArgs.push(user + '@' + host, 'bash -lc ' + JSON.stringify(remoteCmd));
1202
+ if (options?.interactive) {
1203
+ const result = (0, child_process_1.spawnSync)('ssh', sshArgs, { stdio: 'inherit' });
1204
+ return { stdout: '', stderr: '', status: result.status };
1205
+ }
1206
+ const result = (0, child_process_1.spawnSync)('ssh', sshArgs, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
1207
+ return {
1208
+ stdout: result.stdout ?? '',
1209
+ stderr: result.stderr ?? '',
1210
+ status: result.status,
1211
+ };
1212
+ }
1213
+ // ============================================================
922
1214
  // STATIC METHODS
923
1215
  // ============================================================
924
1216
  /**