@kirschbaum-development/sst-laravel 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ Behind the scenes, we use the powerful PHP containers from [Serverside Up](https
26
26
  Pull in the package using npm:
27
27
 
28
28
  ```bash
29
- npm install @kirschbaum/sst-laravel --save
29
+ npm install @kirschbaum-development/sst-laravel --save
30
30
  ```
31
31
 
32
32
  ## Quick start
@@ -126,7 +126,7 @@ const app = new Laravel('MyLaravelApp', {
126
126
 
127
127
  There are multiple ways to configure environment variables. If you want SST Laravel to copy an environment file, you can configure the `config.environment.file` entry.
128
128
 
129
- The below configuration would copy a file named `.env.$STAGE` into the deployment containers as your `.env` file.
129
+ The below configuration would copy a file named `.env.$STAGE` (e.g. `.env.production`) into the deployment containers as your `.env` file.
130
130
 
131
131
  ```js
132
132
  const app = new Laravel('MyLaravelApp', {
@@ -253,6 +253,16 @@ echo "🚀 Running Laravel Migrations..."
253
253
  php artisan migrate --force
254
254
  ```
255
255
 
256
+ ## Deploying
257
+
258
+ To deploy your application, you can use the `sst deploy` command. You must be authenticated with AWS in your terminal session to deploy.
259
+
260
+ ```bash
261
+ npx sst deploy --stage {stage}
262
+ npx sst deploy --stage sandbox
263
+ npx sst deploy --stage production
264
+ ```
265
+
256
266
  ## Accessing Containers
257
267
 
258
268
  Using the `sst-laravel` CLI tool, you can easily connect to your running ECS containers for debugging and troubleshooting.
@@ -293,7 +303,7 @@ If you discover any security related issues, please email security@kirschbaumdev
293
303
 
294
304
  ## Sponsorship
295
305
 
296
- Development of this package is sponsored by Kirschbaum Development Group, a developer driven company focused on problem solving, team building, and community. Learn more [about us](https://kirschbaumdevelopment.com) or [join us](https://careers.kirschbaumdevelopment.com)!
306
+ Development of this package is developed and sponsored by Kirschbaum Development Group, a developer driven company focused on problem solving, team building, and community. Learn more [about us](https://kirschbaumdevelopment.com) or [join us](https://careers.kirschbaumdevelopment.com)!
297
307
 
298
308
  ## License
299
309
 
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { ECSClient, ListTasksCommand, DescribeTasksCommand, ListClustersCommand } from '@aws-sdk/client-ecs';
4
+ import { select } from '@inquirer/prompts';
5
+ import { spawn } from 'child_process';
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ function findSstConfig() {
9
+ const cwd = process.cwd();
10
+ const possiblePaths = [
11
+ path.join(cwd, 'sst.config.ts'),
12
+ path.join(cwd, 'sst.config.js'),
13
+ ];
14
+ for (const configPath of possiblePaths) {
15
+ if (fs.existsSync(configPath)) {
16
+ return configPath;
17
+ }
18
+ }
19
+ return null;
20
+ }
21
+ function extractLaravelComponents(configPath) {
22
+ const content = fs.readFileSync(configPath, 'utf-8');
23
+ const regex = /new\s+Laravel\s*\(\s*['"`]([^'"`]+)['"`]/g;
24
+ const components = [];
25
+ let match;
26
+ while ((match = regex.exec(content)) !== null) {
27
+ components.push(match[1]);
28
+ }
29
+ return components;
30
+ }
31
+ const program = new Command();
32
+ program
33
+ .name('sst-laravel')
34
+ .description('CLI tools for SST Laravel deployments')
35
+ .version('0.0.4');
36
+ program
37
+ .command('init')
38
+ .description('Initialize a new sst.config.ts file with Laravel boilerplate')
39
+ .action(() => {
40
+ try {
41
+ const cwd = process.cwd();
42
+ const targetPath = path.join(cwd, 'sst.config.ts');
43
+ if (fs.existsSync(targetPath)) {
44
+ console.error('Warning: sst.config.ts already exists in the current directory.');
45
+ console.error('Will not overwrite existing file.');
46
+ process.exit(1);
47
+ }
48
+ const templatePath = path.join(__dirname, '..', 'templates', 'sst.config.ts.template');
49
+ if (!fs.existsSync(templatePath)) {
50
+ console.error('Error: Template file not found.');
51
+ process.exit(1);
52
+ }
53
+ let templateContent = fs.readFileSync(templatePath, 'utf-8');
54
+ const envPath = path.join(cwd, '.env');
55
+ let appName = 'my-laravel-app';
56
+ if (fs.existsSync(envPath)) {
57
+ const envContent = fs.readFileSync(envPath, 'utf-8');
58
+ const appNameMatch = envContent.match(/^APP_NAME=(.+)$/m);
59
+ if (appNameMatch && appNameMatch[1]) {
60
+ const rawAppName = appNameMatch[1].trim().replace(/^["']|["']$/g, '');
61
+ appName = rawAppName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
62
+ console.log(`Using APP_NAME from .env: ${rawAppName}`);
63
+ }
64
+ }
65
+ templateContent = templateContent.replace('my-laravel-app', appName);
66
+ fs.writeFileSync(targetPath, templateContent, 'utf-8');
67
+ console.log('✅ Successfully created sst.config.ts');
68
+ console.log('💡 You can now customize the configuration for your own Laravel application.');
69
+ console.log('🔏 Your default configuration is set to look for a .env.{stage} file when deploying. You can customize this in the sst.config.ts file as needed.');
70
+ }
71
+ catch (error) {
72
+ console.error('Error:', error.message);
73
+ process.exit(1);
74
+ }
75
+ });
76
+ program
77
+ .command('ssh')
78
+ .description('SSH into a running ECS task')
79
+ .argument('[service]', 'Service to connect to (web, worker, or worker name) - optional')
80
+ .option('-s, --stage <stage>', 'SST stage name (required)')
81
+ .option('-c, --cluster <cluster>', 'ECS cluster name (optional, auto-detected from SST config)')
82
+ .option('-r, --region <region>', 'AWS region', process.env.AWS_REGION || 'us-east-1')
83
+ .action(async (service, options) => {
84
+ try {
85
+ const region = options.region;
86
+ const stage = options.stage;
87
+ if (!stage) {
88
+ console.error('Error: Stage is required. Use --stage flag to specify the SST stage.');
89
+ process.exit(1);
90
+ }
91
+ const ecsClient = new ECSClient({ region });
92
+ let clusterArn = options.cluster;
93
+ if (!clusterArn) {
94
+ const configPath = findSstConfig();
95
+ if (!configPath) {
96
+ console.error('Error: Could not find sst.config.ts or sst.config.js in current directory.');
97
+ console.error('Please use --cluster flag to specify cluster ARN manually.');
98
+ process.exit(1);
99
+ }
100
+ const components = extractLaravelComponents(configPath);
101
+ if (components.length === 0) {
102
+ console.error('Error: No Laravel components found in SST config.');
103
+ console.error('Please use --cluster flag to specify cluster ARN manually.');
104
+ process.exit(1);
105
+ }
106
+ if (components.length > 1) {
107
+ console.error('Error: Multiple Laravel components found in SST config.');
108
+ console.error(`Found: ${components.join(', ')}`);
109
+ console.error('Please use --cluster flag to specify which cluster to connect to.');
110
+ process.exit(1);
111
+ }
112
+ const componentName = components[0].replace(/-/g, '');
113
+ const clusterPattern = `${stage}-${componentName}Cluster`;
114
+ console.log(`Looking for cluster matching pattern: *${clusterPattern}`);
115
+ const listClustersCommand = new ListClustersCommand({});
116
+ const listClustersResponse = await ecsClient.send(listClustersCommand);
117
+ if (!listClustersResponse.clusterArns || listClustersResponse.clusterArns.length === 0) {
118
+ console.error('Error: No ECS clusters found in this region.');
119
+ process.exit(1);
120
+ }
121
+ const matchingCluster = listClustersResponse.clusterArns.find(arn => {
122
+ const clusterName = arn.split('/').pop();
123
+ return clusterName?.includes(stage) && clusterName?.includes(componentName);
124
+ });
125
+ if (!matchingCluster) {
126
+ console.error(`Error: No cluster found matching stage "${stage}" and component "${components[0]}".`);
127
+ console.error('Available clusters:');
128
+ listClustersResponse.clusterArns.forEach(arn => {
129
+ console.error(` - ${arn.split('/').pop()}`);
130
+ });
131
+ process.exit(1);
132
+ }
133
+ clusterArn = matchingCluster;
134
+ console.log(`Auto-detected cluster: ${clusterArn.split('/').pop()}`);
135
+ }
136
+ console.log(`Cluster ARN: ${clusterArn}`);
137
+ const listTasksCommand = new ListTasksCommand({
138
+ cluster: clusterArn,
139
+ desiredStatus: 'RUNNING'
140
+ });
141
+ const listTasksResponse = await ecsClient.send(listTasksCommand);
142
+ if (!listTasksResponse.taskArns || listTasksResponse.taskArns.length === 0) {
143
+ console.error('No running tasks found in cluster');
144
+ process.exit(1);
145
+ }
146
+ const describeTasksCommand = new DescribeTasksCommand({
147
+ cluster: clusterArn,
148
+ tasks: listTasksResponse.taskArns
149
+ });
150
+ const describeTasksResponse = await ecsClient.send(describeTasksCommand);
151
+ let matchingTask;
152
+ if (service) {
153
+ let servicePrefix;
154
+ if (service === 'web') {
155
+ servicePrefix = '-web';
156
+ }
157
+ else if (service === 'worker') {
158
+ servicePrefix = '-worker';
159
+ }
160
+ else {
161
+ servicePrefix = `-${service}`;
162
+ }
163
+ matchingTask = describeTasksResponse.tasks?.find(task => {
164
+ const containerName = task.containers?.[0]?.name || '';
165
+ return containerName.toLowerCase().includes(servicePrefix.toLowerCase());
166
+ });
167
+ }
168
+ if (!matchingTask) {
169
+ if (service) {
170
+ console.log(`\nNo running task found matching service: ${service}`);
171
+ }
172
+ console.log('Available tasks in cluster:\n');
173
+ const choices = describeTasksResponse.tasks?.map(task => {
174
+ const taskId = task.taskArn?.split('/').pop() || '';
175
+ const containerName = task.containers?.[0]?.name || 'unknown';
176
+ const status = task.lastStatus || 'unknown';
177
+ return {
178
+ name: `${containerName} (${taskId.substring(0, 8)}...) - ${status}`,
179
+ value: task,
180
+ description: `Task: ${taskId}`
181
+ };
182
+ }) || [];
183
+ if (choices.length === 0) {
184
+ console.error('No tasks available to select from.');
185
+ process.exit(1);
186
+ }
187
+ matchingTask = await select({
188
+ message: 'Select a task to connect to:',
189
+ choices
190
+ });
191
+ }
192
+ const taskId = matchingTask.taskArn?.split('/').pop();
193
+ console.log(`Connecting to task: ${taskId}`);
194
+ const awsCommand = spawn('aws', [
195
+ 'ecs',
196
+ 'execute-command',
197
+ '--cluster', clusterArn,
198
+ '--task', taskId,
199
+ '--container', matchingTask.containers?.[0]?.name || '',
200
+ '--interactive',
201
+ '--command', '/bin/bash'
202
+ ], {
203
+ stdio: 'inherit',
204
+ env: { ...process.env, AWS_REGION: region }
205
+ });
206
+ awsCommand.on('exit', (code) => {
207
+ process.exit(code || 0);
208
+ });
209
+ }
210
+ catch (error) {
211
+ console.error('Error:', error.message);
212
+ process.exit(1);
213
+ }
214
+ });
215
+ program.parse();
216
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,23 @@
1
1
  {
2
2
  "name": "@kirschbaum-development/sst-laravel",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
+ "type": "module",
4
5
  "description": "An unofficial extension of SST to deploy containerized Laravel applications to AWS Fargate.",
5
6
  "main": "laravel-sst.ts",
7
+ "files": [
8
+ "dist/bin",
9
+ "src",
10
+ "laravel-sst.ts",
11
+ "sst-env.d.ts",
12
+ "templates",
13
+ "conf",
14
+ "images",
15
+ "Dockerfile.web",
16
+ "Dockerfile.worker",
17
+ ".dockerignore"
18
+ ],
6
19
  "scripts": {
20
+ "build": "tsc",
7
21
  "publish": "npm publish --access public"
8
22
  },
9
23
  "repository": {
@@ -26,7 +40,7 @@
26
40
  },
27
41
  "homepage": "https://github.com/kirschbaum-development/sst-laravel#readme",
28
42
  "bin": {
29
- "sst-laravel": "./bin/cli.ts"
43
+ "sst-laravel": "./dist/bin/cli.js"
30
44
  },
31
45
  "dependencies": {
32
46
  "@aws-sdk/client-ecs": "^3.0.0",
@@ -79,7 +79,7 @@ export function applyLinkedResourcesEnv(links: LinkSupportedTypes[], callbacks?:
79
79
  }
80
80
 
81
81
  function applyDatabaseEnv(database: Database, callbacks?: EnvCallbacks): EnvType {
82
- let port: number;
82
+ let port: number | undefined;
83
83
  database.port.apply(value => port = value);
84
84
 
85
85
  if (database instanceof Postgres || (database instanceof Aurora && port === 5432)) {
@@ -12,9 +12,21 @@ export default $config({
12
12
  async run() {
13
13
  const { Laravel } = await import("@kirschbaum-development/sst-laravel");
14
14
  const vpc = new sst.aws.Vpc("MyVpc");
15
+ // you can also use an existing VPC
16
+ // const vpc = sst.aws.Vpc.get("DefaultVpc", "vpc-12345678901234567");
17
+
18
+ const database = new sst.aws.Postgres('MyDB', { vpc });
15
19
 
16
20
  const app = new Laravel("MyLaravelApp", {
17
21
  vpc,
22
+ link: [database],
23
+
24
+ config: {
25
+ php: 8.4,
26
+ environment: {
27
+ file: `.env.${$app.stage}`,
28
+ },
29
+ },
18
30
 
19
31
  web: {
20
32
  domain: $app.stage === "production"
package/.editorconfig DELETED
@@ -1,18 +0,0 @@
1
- root = true
2
-
3
- [*]
4
- charset = utf-8
5
- end_of_line = lf
6
- indent_size = 2
7
- indent_style = space
8
- insert_final_newline = true
9
- trim_trailing_whitespace = true
10
-
11
- [*.md]
12
- trim_trailing_whitespace = false
13
-
14
- [*.{yml,yaml}]
15
- indent_size = 2
16
-
17
- [docker-compose.yml]
18
- indent_size = 4
package/AGENTS.md DELETED
@@ -1,28 +0,0 @@
1
- # Agent Guidelines for SST Laravel
2
-
3
- This project is an NPM package, and it is an extension of SST to add some functionality on top of it, to help deploy Laravel applications to AWS Fargate using Docker containers.
4
-
5
- ## Build/Test Commands
6
- - **Publish**: `npm run publish` (publishes package to npm with public access)
7
- - No test suite or linting configured in this project
8
- - No build step required (TypeScript consumed directly by SST)
9
-
10
- ## Code Style & Conventions
11
- - **Formatting**: 2-space indentation, LF line endings, UTF-8 charset (see `.editorconfig`)
12
- - **Language**: TypeScript with SST/Pulumi types
13
- - **Imports**: Use relative paths for local modules (e.g., `./src/laravel-env.js`), absolute for SST platform (e.g., `../../../.sst/platform/...`)
14
- - **Types**: Use Input<T> for component props, Output<T> for Pulumi async values, explicit interfaces for public APIs
15
- - **Naming**: PascalCase for classes/interfaces/types/enums, camelCase for variables/functions, kebab-case for files
16
-
17
- ## Architecture Patterns
18
- - Component extends SST's `Component` base class
19
- - Use `all()` and `.apply()` for Pulumi Output transformations
20
- - File system operations use Node.js `fs` and `path` modules synchronously
21
- - Configuration defaults: PHP 8.4, opcache enabled, auto-inject env vars
22
- - Build artifacts go to `.sst/laravel` directory (managed via `pluginBuildPath`)
23
-
24
- ## Error Handling & Security
25
- - Validate paths with `path.resolve()` before file operations
26
- - Use `fs.existsSync()` checks before reading files
27
- - Never log or expose secrets/passwords
28
- - Set proper file permissions (0o755 for scripts, 0o777 for s6 executables)
package/bin/cli.ts DELETED
@@ -1,271 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import { ECSClient, ListTasksCommand, DescribeTasksCommand, ListClustersCommand, Task } from '@aws-sdk/client-ecs';
5
- import { select } from '@inquirer/prompts';
6
- import { spawn } from 'child_process';
7
- import * as fs from 'fs';
8
- import * as path from 'path';
9
-
10
- interface SshOptions {
11
- stage?: string;
12
- cluster?: string;
13
- region: string;
14
- }
15
-
16
- function findSstConfig(): string | null {
17
- const cwd = process.cwd();
18
- const possiblePaths = [
19
- path.join(cwd, 'sst.config.ts'),
20
- path.join(cwd, 'sst.config.js'),
21
- ];
22
-
23
- for (const configPath of possiblePaths) {
24
- if (fs.existsSync(configPath)) {
25
- return configPath;
26
- }
27
- }
28
-
29
- return null;
30
- }
31
-
32
- function extractLaravelComponents(configPath: string): string[] {
33
- const content = fs.readFileSync(configPath, 'utf-8');
34
- const regex = /new\s+Laravel\s*\(\s*['"`]([^'"`]+)['"`]/g;
35
- const components: string[] = [];
36
- let match: RegExpExecArray | null;
37
-
38
- while ((match = regex.exec(content)) !== null) {
39
- components.push(match[1]);
40
- }
41
-
42
- return components;
43
- }
44
-
45
- const program = new Command();
46
-
47
- program
48
- .name('sst-laravel')
49
- .description('CLI tools for SST Laravel deployments')
50
- .version('0.0.4');
51
-
52
- program
53
- .command('init')
54
- .description('Initialize a new sst.config.ts file with Laravel boilerplate')
55
- .action(() => {
56
- try {
57
- const cwd = process.cwd();
58
- const targetPath = path.join(cwd, 'sst.config.ts');
59
-
60
- if (fs.existsSync(targetPath)) {
61
- console.error('Warning: sst.config.ts already exists in the current directory.');
62
- console.error('Will not overwrite existing file.');
63
- process.exit(1);
64
- }
65
-
66
- const templatePath = path.join(__dirname, '..', 'templates', 'sst.config.ts.template');
67
-
68
- if (!fs.existsSync(templatePath)) {
69
- console.error('Error: Template file not found.');
70
- process.exit(1);
71
- }
72
-
73
- let templateContent = fs.readFileSync(templatePath, 'utf-8');
74
-
75
- const envPath = path.join(cwd, '.env');
76
- let appName = 'my-laravel-app';
77
-
78
- if (fs.existsSync(envPath)) {
79
- const envContent = fs.readFileSync(envPath, 'utf-8');
80
- const appNameMatch = envContent.match(/^APP_NAME=(.+)$/m);
81
-
82
- if (appNameMatch && appNameMatch[1]) {
83
- const rawAppName = appNameMatch[1].trim().replace(/^["']|["']$/g, '');
84
- appName = rawAppName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
85
- console.log(`Using APP_NAME from .env: ${rawAppName}`);
86
- }
87
- }
88
-
89
- templateContent = templateContent.replace('my-laravel-app', appName);
90
-
91
- fs.writeFileSync(targetPath, templateContent, 'utf-8');
92
-
93
- console.log('Successfully created sst.config.ts');
94
- console.log('You can now customize the configuration for your Laravel application.');
95
- } catch (error) {
96
- console.error('Error:', (error as Error).message);
97
- process.exit(1);
98
- }
99
- });
100
-
101
- program
102
- .command('ssh')
103
- .description('SSH into a running ECS task')
104
- .argument('[service]', 'Service to connect to (web, worker, or worker name) - optional')
105
- .option('-s, --stage <stage>', 'SST stage name (required)')
106
- .option('-c, --cluster <cluster>', 'ECS cluster name (optional, auto-detected from SST config)')
107
- .option('-r, --region <region>', 'AWS region', process.env.AWS_REGION || 'us-east-1')
108
- .action(async (service: string | undefined, options: SshOptions) => {
109
- try {
110
- const region = options.region;
111
- const stage = options.stage;
112
-
113
- if (!stage) {
114
- console.error('Error: Stage is required. Use --stage flag to specify the SST stage.');
115
- process.exit(1);
116
- }
117
-
118
- const ecsClient = new ECSClient({ region });
119
-
120
- let clusterArn = options.cluster;
121
-
122
- if (!clusterArn) {
123
- const configPath = findSstConfig();
124
- if (!configPath) {
125
- console.error('Error: Could not find sst.config.ts or sst.config.js in current directory.');
126
- console.error('Please use --cluster flag to specify cluster ARN manually.');
127
- process.exit(1);
128
- }
129
-
130
- const components = extractLaravelComponents(configPath);
131
-
132
- if (components.length === 0) {
133
- console.error('Error: No Laravel components found in SST config.');
134
- console.error('Please use --cluster flag to specify cluster ARN manually.');
135
- process.exit(1);
136
- }
137
-
138
- if (components.length > 1) {
139
- console.error('Error: Multiple Laravel components found in SST config.');
140
- console.error(`Found: ${components.join(', ')}`);
141
- console.error('Please use --cluster flag to specify which cluster to connect to.');
142
- process.exit(1);
143
- }
144
-
145
- const componentName = components[0].replace(/-/g, '');
146
- const clusterPattern = `${stage}-${componentName}Cluster`;
147
-
148
- console.log(`Looking for cluster matching pattern: *${clusterPattern}`);
149
-
150
- const listClustersCommand = new ListClustersCommand({});
151
- const listClustersResponse = await ecsClient.send(listClustersCommand);
152
-
153
- if (!listClustersResponse.clusterArns || listClustersResponse.clusterArns.length === 0) {
154
- console.error('Error: No ECS clusters found in this region.');
155
- process.exit(1);
156
- }
157
-
158
- const matchingCluster = listClustersResponse.clusterArns.find(arn => {
159
- const clusterName = arn.split('/').pop();
160
- return clusterName?.includes(stage) && clusterName?.includes(componentName);
161
- });
162
-
163
- if (!matchingCluster) {
164
- console.error(`Error: No cluster found matching stage "${stage}" and component "${components[0]}".`);
165
- console.error('Available clusters:');
166
- listClustersResponse.clusterArns.forEach(arn => {
167
- console.error(` - ${arn.split('/').pop()}`);
168
- });
169
- process.exit(1);
170
- }
171
-
172
- clusterArn = matchingCluster;
173
- console.log(`Auto-detected cluster: ${clusterArn.split('/').pop()}`);
174
- }
175
-
176
- console.log(`Cluster ARN: ${clusterArn}`);
177
-
178
- const listTasksCommand = new ListTasksCommand({
179
- cluster: clusterArn,
180
- desiredStatus: 'RUNNING'
181
- });
182
-
183
- const listTasksResponse = await ecsClient.send(listTasksCommand);
184
-
185
- if (!listTasksResponse.taskArns || listTasksResponse.taskArns.length === 0) {
186
- console.error('No running tasks found in cluster');
187
- process.exit(1);
188
- }
189
-
190
- const describeTasksCommand = new DescribeTasksCommand({
191
- cluster: clusterArn,
192
- tasks: listTasksResponse.taskArns
193
- });
194
-
195
- const describeTasksResponse = await ecsClient.send(describeTasksCommand);
196
-
197
- let matchingTask: Task | undefined;
198
-
199
- if (service) {
200
- let servicePrefix: string;
201
- if (service === 'web') {
202
- servicePrefix = '-web';
203
- } else if (service === 'worker') {
204
- servicePrefix = '-worker';
205
- } else {
206
- servicePrefix = `-${service}`;
207
- }
208
-
209
- matchingTask = describeTasksResponse.tasks?.find(task => {
210
- const containerName = task.containers?.[0]?.name || '';
211
- return containerName.toLowerCase().includes(servicePrefix.toLowerCase());
212
- });
213
- }
214
-
215
- if (!matchingTask) {
216
- if (service) {
217
- console.log(`\nNo running task found matching service: ${service}`);
218
- }
219
- console.log('Available tasks in cluster:\n');
220
-
221
- const choices = describeTasksResponse.tasks?.map(task => {
222
- const taskId = task.taskArn?.split('/').pop() || '';
223
- const containerName = task.containers?.[0]?.name || 'unknown';
224
- const status = task.lastStatus || 'unknown';
225
-
226
- return {
227
- name: `${containerName} (${taskId.substring(0, 8)}...) - ${status}`,
228
- value: task,
229
- description: `Task: ${taskId}`
230
- };
231
- }) || [];
232
-
233
- if (choices.length === 0) {
234
- console.error('No tasks available to select from.');
235
- process.exit(1);
236
- }
237
-
238
- matchingTask = await select({
239
- message: 'Select a task to connect to:',
240
- choices
241
- });
242
- }
243
-
244
- const taskId = matchingTask.taskArn?.split('/').pop();
245
-
246
- console.log(`Connecting to task: ${taskId}`);
247
-
248
- const awsCommand = spawn('aws', [
249
- 'ecs',
250
- 'execute-command',
251
- '--cluster', clusterArn,
252
- '--task', taskId!,
253
- '--container', matchingTask.containers?.[0]?.name || '',
254
- '--interactive',
255
- '--command', '/bin/bash'
256
- ], {
257
- stdio: 'inherit',
258
- env: { ...process.env, AWS_REGION: region }
259
- });
260
-
261
- awsCommand.on('exit', (code) => {
262
- process.exit(code || 0);
263
- });
264
-
265
- } catch (error) {
266
- console.error('Error:', (error as Error).message);
267
- process.exit(1);
268
- }
269
- });
270
-
271
- program.parse();