@kirschbaum-development/sst-laravel 0.0.6 → 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.
@@ -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.6",
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)) {
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,272 +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 own Laravel application.');
95
- 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.');
96
- } catch (error) {
97
- console.error('Error:', (error as Error).message);
98
- process.exit(1);
99
- }
100
- });
101
-
102
- program
103
- .command('ssh')
104
- .description('SSH into a running ECS task')
105
- .argument('[service]', 'Service to connect to (web, worker, or worker name) - optional')
106
- .option('-s, --stage <stage>', 'SST stage name (required)')
107
- .option('-c, --cluster <cluster>', 'ECS cluster name (optional, auto-detected from SST config)')
108
- .option('-r, --region <region>', 'AWS region', process.env.AWS_REGION || 'us-east-1')
109
- .action(async (service: string | undefined, options: SshOptions) => {
110
- try {
111
- const region = options.region;
112
- const stage = options.stage;
113
-
114
- if (!stage) {
115
- console.error('Error: Stage is required. Use --stage flag to specify the SST stage.');
116
- process.exit(1);
117
- }
118
-
119
- const ecsClient = new ECSClient({ region });
120
-
121
- let clusterArn = options.cluster;
122
-
123
- if (!clusterArn) {
124
- const configPath = findSstConfig();
125
- if (!configPath) {
126
- console.error('Error: Could not find sst.config.ts or sst.config.js in current directory.');
127
- console.error('Please use --cluster flag to specify cluster ARN manually.');
128
- process.exit(1);
129
- }
130
-
131
- const components = extractLaravelComponents(configPath);
132
-
133
- if (components.length === 0) {
134
- console.error('Error: No Laravel components found in SST config.');
135
- console.error('Please use --cluster flag to specify cluster ARN manually.');
136
- process.exit(1);
137
- }
138
-
139
- if (components.length > 1) {
140
- console.error('Error: Multiple Laravel components found in SST config.');
141
- console.error(`Found: ${components.join(', ')}`);
142
- console.error('Please use --cluster flag to specify which cluster to connect to.');
143
- process.exit(1);
144
- }
145
-
146
- const componentName = components[0].replace(/-/g, '');
147
- const clusterPattern = `${stage}-${componentName}Cluster`;
148
-
149
- console.log(`Looking for cluster matching pattern: *${clusterPattern}`);
150
-
151
- const listClustersCommand = new ListClustersCommand({});
152
- const listClustersResponse = await ecsClient.send(listClustersCommand);
153
-
154
- if (!listClustersResponse.clusterArns || listClustersResponse.clusterArns.length === 0) {
155
- console.error('Error: No ECS clusters found in this region.');
156
- process.exit(1);
157
- }
158
-
159
- const matchingCluster = listClustersResponse.clusterArns.find(arn => {
160
- const clusterName = arn.split('/').pop();
161
- return clusterName?.includes(stage) && clusterName?.includes(componentName);
162
- });
163
-
164
- if (!matchingCluster) {
165
- console.error(`Error: No cluster found matching stage "${stage}" and component "${components[0]}".`);
166
- console.error('Available clusters:');
167
- listClustersResponse.clusterArns.forEach(arn => {
168
- console.error(` - ${arn.split('/').pop()}`);
169
- });
170
- process.exit(1);
171
- }
172
-
173
- clusterArn = matchingCluster;
174
- console.log(`Auto-detected cluster: ${clusterArn.split('/').pop()}`);
175
- }
176
-
177
- console.log(`Cluster ARN: ${clusterArn}`);
178
-
179
- const listTasksCommand = new ListTasksCommand({
180
- cluster: clusterArn,
181
- desiredStatus: 'RUNNING'
182
- });
183
-
184
- const listTasksResponse = await ecsClient.send(listTasksCommand);
185
-
186
- if (!listTasksResponse.taskArns || listTasksResponse.taskArns.length === 0) {
187
- console.error('No running tasks found in cluster');
188
- process.exit(1);
189
- }
190
-
191
- const describeTasksCommand = new DescribeTasksCommand({
192
- cluster: clusterArn,
193
- tasks: listTasksResponse.taskArns
194
- });
195
-
196
- const describeTasksResponse = await ecsClient.send(describeTasksCommand);
197
-
198
- let matchingTask: Task | undefined;
199
-
200
- if (service) {
201
- let servicePrefix: string;
202
- if (service === 'web') {
203
- servicePrefix = '-web';
204
- } else if (service === 'worker') {
205
- servicePrefix = '-worker';
206
- } else {
207
- servicePrefix = `-${service}`;
208
- }
209
-
210
- matchingTask = describeTasksResponse.tasks?.find(task => {
211
- const containerName = task.containers?.[0]?.name || '';
212
- return containerName.toLowerCase().includes(servicePrefix.toLowerCase());
213
- });
214
- }
215
-
216
- if (!matchingTask) {
217
- if (service) {
218
- console.log(`\nNo running task found matching service: ${service}`);
219
- }
220
- console.log('Available tasks in cluster:\n');
221
-
222
- const choices = describeTasksResponse.tasks?.map(task => {
223
- const taskId = task.taskArn?.split('/').pop() || '';
224
- const containerName = task.containers?.[0]?.name || 'unknown';
225
- const status = task.lastStatus || 'unknown';
226
-
227
- return {
228
- name: `${containerName} (${taskId.substring(0, 8)}...) - ${status}`,
229
- value: task,
230
- description: `Task: ${taskId}`
231
- };
232
- }) || [];
233
-
234
- if (choices.length === 0) {
235
- console.error('No tasks available to select from.');
236
- process.exit(1);
237
- }
238
-
239
- matchingTask = await select({
240
- message: 'Select a task to connect to:',
241
- choices
242
- });
243
- }
244
-
245
- const taskId = matchingTask.taskArn?.split('/').pop();
246
-
247
- console.log(`Connecting to task: ${taskId}`);
248
-
249
- const awsCommand = spawn('aws', [
250
- 'ecs',
251
- 'execute-command',
252
- '--cluster', clusterArn,
253
- '--task', taskId!,
254
- '--container', matchingTask.containers?.[0]?.name || '',
255
- '--interactive',
256
- '--command', '/bin/bash'
257
- ], {
258
- stdio: 'inherit',
259
- env: { ...process.env, AWS_REGION: region }
260
- });
261
-
262
- awsCommand.on('exit', (code) => {
263
- process.exit(code || 0);
264
- });
265
-
266
- } catch (error) {
267
- console.error('Error:', (error as Error).message);
268
- process.exit(1);
269
- }
270
- });
271
-
272
- program.parse();