@kirschbaum-development/sst-laravel 0.0.3 → 0.0.5

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/AGENTS.md ADDED
@@ -0,0 +1,28 @@
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/README.md CHANGED
@@ -1,21 +1,25 @@
1
1
  # SST Laravel
2
2
 
3
- This is an unofficial extension of SST to deploy your Laravel application to AWS behind a robust, reliable and scalable infrastructure, with all the power the SST provides.
3
+ SST Laravel is an unofficial extension of [SST](https://sst.dev) created by [Kirschbaum Development](https://kirschbaumdevelopment.com) to deploy your Laravel application to AWS behind a robust, reliable and scalable infrastructure, with all the power of SST.
4
4
 
5
- **TODO: Add explanation about what exactly SST is.**
5
+ SST is a framework that makes it easy to build modern full-stack applications on your own infrastructure.
6
6
 
7
- ## What it deploys
7
+ ## What gets deployed
8
8
 
9
- Behind the scenes, this extension uses the SST Cluster + Service component, which runs in AWS Fargate using pre-built Docker containers. This all gets deployed on your own AWS account, and you have full control over the infrastructure. Behind the scenes, we use the powerful PHP containers from Serverside Up.
9
+ Behind the scenes, this extension uses the SST Cluster + Service component, which deploys custom Docker containers to AWS Fargate. It all gets deployed on your own AWS account, and you have full control over the infrastructure and which services are connected to your application.
10
10
 
11
11
  This package deploys a full-blown infrastructure in AWS, with zero downtime deployments, as it can be seeing in the image below.
12
12
 
13
- ![](./images/diagram.png)
13
+ Behind the scenes, we use the powerful PHP containers from [Serverside Up](https://serversideup.net/open-source/docker-php/).
14
+
15
+ ![](https://github.com/kirschbaum-development/sst-laravel/raw/main/images/diagram.png)
14
16
 
15
17
  ## Pre-requisites
16
18
 
17
19
  1. NodeJS.
18
20
  1. Have [SST](https://sst.dev) installed and configured.
21
+ 1. Have [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed and configured.
22
+ * Guide on how to set up IAM Credentials [here](https://sst.dev/docs/iam-credentials/).
19
23
 
20
24
  ## Installation instructions
21
25
 
@@ -25,6 +29,14 @@ Pull in the package using npm:
25
29
  npm install @kirschbaum/sst-laravel --save
26
30
  ```
27
31
 
32
+ ## Quick start
33
+
34
+ To get started quickly, you can use the `init` command:
35
+
36
+ ```bash
37
+ npx sst-laravel init
38
+ ```
39
+
28
40
  ## Usage
29
41
 
30
42
  To start using, you only need to import the component in your `sst.config.ts` file:
@@ -44,13 +56,13 @@ Setting up your app to receive HTTP requests, on the `laravel-sst-demo.kdg.dev`
44
56
 
45
57
  ```js
46
58
  const app = new Laravel('MyLaravelApp', {
47
- web: {
48
- domain: 'laravel-sst-demo.kdg.dev',
49
- scaling: {
50
- min: 1,
51
- max: 3,
52
- }
53
- },
59
+ web: {
60
+ domain: 'laravel-sst-demo.kdg.dev',
61
+ scaling: {
62
+ min: 1,
63
+ max: 3,
64
+ }
65
+ },
54
66
  });
55
67
  ```
56
68
 
@@ -65,12 +77,12 @@ SST Laravel will automatically deploy and configure worker containers running yo
65
77
 
66
78
  ```js
67
79
  const app = new Laravel('MyLaravelApp', {
68
- workers: [
69
- {
70
- name: 'scheduler',
71
- scheduler: true,
72
- },
73
- ],
80
+ workers: [
81
+ {
82
+ name: 'scheduler',
83
+ scheduler: true,
84
+ },
85
+ ],
74
86
  });
75
87
  ```
76
88
 
@@ -78,12 +90,12 @@ const app = new Laravel('MyLaravelApp', {
78
90
 
79
91
  ```js
80
92
  const app = new Laravel('MyLaravelApp', {
81
- workers: [
82
- {
83
- name: 'horizon',
84
- horizon: true,
85
- },
86
- ],
93
+ workers: [
94
+ {
95
+ name: 'horizon',
96
+ horizon: true,
97
+ },
98
+ ],
87
99
  });
88
100
  ```
89
101
 
@@ -91,22 +103,22 @@ const app = new Laravel('MyLaravelApp', {
91
103
 
92
104
  ```js
93
105
  const app = new Laravel('MyLaravelApp', {
94
- workers: [
95
- {
96
- name: 'worker',
97
- tasks: {
98
- 'scheduler': {
99
- command: 'php artisan schedule:work',
100
- },
101
- 'queue': {
102
- command: 'php artisan queue:work',
103
- },
104
- 'pulse': {
105
- command: 'php artisan pulse:work',
106
- },
107
- },
106
+ workers: [
107
+ {
108
+ name: 'worker',
109
+ tasks: {
110
+ 'scheduler': {
111
+ command: 'php artisan schedule:work',
108
112
  },
109
- ],
113
+ 'queue': {
114
+ command: 'php artisan queue:work',
115
+ },
116
+ 'pulse': {
117
+ command: 'php artisan pulse:work',
118
+ },
119
+ },
120
+ },
121
+ ],
110
122
  });
111
123
  ```
112
124
 
@@ -152,7 +164,7 @@ const redis = new sst.aws.Redis("MyRedis", { vpc });
152
164
  const bucket = new sst.aws.Bucket("MyBucket");
153
165
 
154
166
  const app = new Laravel('MyLaravelApp', {
155
- link: [database, redis, bucket],
167
+ link: [database, redis, bucket],
156
168
  });
157
169
  ```
158
170
 
@@ -164,27 +176,27 @@ If you need to customize the environment variable names for your resources, you
164
176
 
165
177
  ```js
166
178
  const app = new Laravel('MyLaravelApp', {
167
- link: [
168
- email,
169
- {
170
- resource: database,
171
- environment: (database: sst.aws.Postgres) => ({
172
- CUSTOM_DB_HOST: database.host.apply(host => host.toString()),
173
- CUSTOM_DB_NAME: database.database.apply(database => database.toString()),
174
- CUSTOM_DB_USER: database.username.apply(username => username.toString()),
175
- CUSTOM_DB_PASSWORD: database.password.apply(password => password.toString()),
176
- })
177
- },
178
- {
179
- resource: redis,
180
- environment: (redis: sst.aws.Redis) => ({
181
- QUEUE_CONNECTION: 'redis',
182
- QUEUE_REDIS_HOST: redis.host.apply(host => host ? `tls://${host}` : ''),
183
- QUEUE_REDIS_PORT: redis.port.apply(port => port.toString()),
184
- })
185
- }
186
- ],
187
- web: {}
179
+ link: [
180
+ email,
181
+ {
182
+ resource: database,
183
+ environment: (database: sst.aws.Postgres) => ({
184
+ CUSTOM_DB_HOST: database.host.apply(host => host.toString()),
185
+ CUSTOM_DB_NAME: database.database.apply(database => database.toString()),
186
+ CUSTOM_DB_USER: database.username.apply(username => username.toString()),
187
+ CUSTOM_DB_PASSWORD: database.password.apply(password => password.toString()),
188
+ })
189
+ },
190
+ {
191
+ resource: redis,
192
+ environment: (redis: sst.aws.Redis) => ({
193
+ QUEUE_CONNECTION: 'redis',
194
+ QUEUE_REDIS_HOST: redis.host.apply(host => host ? `tls://${host}` : ''),
195
+ QUEUE_REDIS_PORT: redis.port.apply(port => port.toString()),
196
+ })
197
+ }
198
+ ],
199
+ web: {}
188
200
  });
189
201
  ```
190
202
 
@@ -212,13 +224,13 @@ You can configure the PHP version, custom environment variables and a custom dep
212
224
 
213
225
  ```js
214
226
  const app = new Laravel('MyLaravelApp', {
215
- config: {
216
- php: 8.4,
217
- opcache: true,
218
- deployment: {
219
- script: './infra/deploy.sh'
220
- },
227
+ config: {
228
+ php: 8.4,
229
+ opcache: true,
230
+ deployment: {
231
+ script: './infra/deploy.sh'
221
232
  },
233
+ },
222
234
  });
223
235
  ```
224
236
 
@@ -241,9 +253,29 @@ echo "🚀 Running Laravel Migrations..."
241
253
  php artisan migrate --force
242
254
  ```
243
255
 
244
- ## Debugging Containers
256
+ ## Accessing Containers
257
+
258
+ Using the `sst-laravel` CLI tool, you can easily connect to your running ECS containers for debugging and troubleshooting.
259
+
260
+ ```bash
261
+ npx sst-laravel ssh --stage production
262
+ ```
263
+
264
+ This will list all running tasks in your cluster and let you choose which one to connect to.
265
+
266
+ **Connect to a specific service:**
267
+
268
+ ```bash
269
+ npx sst-laravel ssh web --stage production
270
+ npx sst-laravel ssh worker --stage production
271
+ ```
272
+
273
+ If you are naming your workers differently, you can specify the worker name:
245
274
 
246
- TODO: Add documentation on how to SSH to debug containers.
275
+ ```bash
276
+ npx sst-laravel ssh {worker-name} --stage production
277
+ npx sst-laravel ssh worker --stage production
278
+ ```
247
279
 
248
280
  ***
249
281
 
package/bin/cli.ts ADDED
@@ -0,0 +1,271 @@
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();
Binary file
Binary file
package/laravel-sst.ts CHANGED
@@ -3,16 +3,12 @@
3
3
  import * as path from 'path';
4
4
  import * as fs from 'fs';
5
5
  import { Component } from "../../../.sst/platform/src/components/component.js";
6
- import { FunctionArgs } from "../../../.sst/platform/src/components/aws/function.js";
6
+ import { FunctionArgs } from "../../../.sst/platform/src/components/aws/function.js";;
7
7
  import { ComponentResourceOptions, Output, all, output } from "../../../.sst/platform/node_modules/@pulumi/pulumi/index.js";
8
8
  import { Input } from "../../../.sst/platform/src/components/input.js";
9
- import { Link } from "../../../.sst/platform/src/components/link.js";
10
9
  import { ClusterArgs } from "../../../.sst/platform/src/components/aws/cluster.js";
11
10
  import { ServiceArgs } from "../../../.sst/platform/src/components/aws/service.js";
12
11
  import { Dns } from "../../../.sst/platform/src/components/dns.js";
13
- import { Postgres } from "../../../.sst/platform/src/components/aws/postgres.js";
14
- import { Redis } from "../../../.sst/platform/src/components/aws/redis.js";
15
- import { Email } from "../../../.sst/platform/src/components/aws/email.js";
16
12
  import { applyLinkedResourcesEnv, EnvCallback, EnvCallbacks } from "./src/laravel-env.js";
17
13
 
18
14
  // duplicate from cluster.ts
@@ -164,6 +160,8 @@ export interface LaravelArgs extends ClusterArgs {
164
160
  }
165
161
 
166
162
  export class Laravel extends Component {
163
+ private readonly services: Record<string, sst.aws.Service>;
164
+
167
165
  constructor(
168
166
  name: string,
169
167
  args: LaravelArgs,
@@ -171,13 +169,15 @@ export class Laravel extends Component {
171
169
  ) {
172
170
  super(__pulumiType, name, args, opts);
173
171
 
172
+ this.services = {};
173
+
174
174
  args.config = args.config ?? {};
175
175
  const sitePath = args.path ?? '.';
176
176
  const absSitePath = path.resolve(sitePath.toString());
177
- // TODO: We need to update sst-laravel to whatever the real package name will be.
178
- const nodeModulePath = path.resolve(__dirname, '../../node_modules/sst-laravel');
177
+ const nodeModulePath = path.resolve(__dirname, '../../node_modules/@kirschbaum-development/sst-laravel');
179
178
 
180
- // Determine the path where our plugin will save build files. SST sets __dirname to the .sst/platform directory.
179
+ // Determine the path where our plugin will save build files.
180
+ // SST sets __dirname to the .sst/platform directory.
181
181
  const pluginBuildPath = path.resolve(__dirname, '../laravel');
182
182
 
183
183
  prepareEnvironmentFile();
@@ -187,18 +187,10 @@ export class Laravel extends Component {
187
187
  vpc: args.vpc
188
188
  });
189
189
 
190
- if (args.web) {
191
- addWebService();
192
- }
193
-
194
- if (args.workers) {
195
- addWorkerServices();
196
- }
197
-
198
- function addWebService() {
190
+ const addWebService = () => {
199
191
  const envVariables = getEnvironmentVariables();
200
192
 
201
- const webService = new sst.aws.Service(`${name}-Web`, {
193
+ this.services['web'] = new sst.aws.Service(`${name}-Web`, {
202
194
  cluster,
203
195
  link: getLinks(),
204
196
  permissions: args.permissions,
@@ -257,7 +249,7 @@ export class Laravel extends Component {
257
249
  });
258
250
  }
259
251
 
260
- function createWorkerService(workerConfig: LaravelWorkerConfig, serviceName: string, workerBuildPath: string) {
252
+ const createWorkerService = (workerConfig: LaravelWorkerConfig, serviceName: string, workerBuildPath: string) => {
261
253
  createWorkerTasks(workerConfig, workerBuildPath);
262
254
 
263
255
  const imgBuildArgs = {
@@ -265,7 +257,7 @@ export class Laravel extends Component {
265
257
  'CUSTOM_CONF_PATH': workerBuildPath.replace(absSitePath, ''),
266
258
  };
267
259
 
268
- return new sst.aws.Service(serviceName, {
260
+ this.services[serviceName] = new sst.aws.Service(serviceName, {
269
261
  cluster,
270
262
  link: getLinks(),
271
263
  permissions: args.permissions,
@@ -304,6 +296,14 @@ export class Laravel extends Component {
304
296
  });
305
297
  }
306
298
 
299
+ if (args.web) {
300
+ addWebService();
301
+ }
302
+
303
+ if (args.workers) {
304
+ addWorkerServices();
305
+ }
306
+
307
307
  function getDefaultPublicPorts(): Ports {
308
308
  let ports;
309
309
  const forwardPort: Port = "8080/http";
@@ -324,7 +324,7 @@ export class Laravel extends Component {
324
324
  return ports;
325
325
  }
326
326
 
327
- // TODO: We have to test if it works when an image is provided in sst.config.js
327
+ // TODO: We have to test if it works when a custom image is provided in sst.config.js
328
328
  function getImage(imgFromConfig: LaravelWebArgs["image"] | null | undefined, imgType: ImageType, extraArgs: object = {}) {
329
329
  const img = imgFromConfig
330
330
  ? imgFromConfig
@@ -380,6 +380,7 @@ export class Laravel extends Component {
380
380
  'PHP_OPCACHE_ENABLE': args.config?.opcache? '1' : '0',
381
381
  'AUTORUN_LARAVEL_MIGRATION': imageType === ImageType.Web ? 'true' : 'false',
382
382
  'CONTAINER_TYPE': imageType,
383
+ 'ENV_FILENAME': args.config?.environment?.file ?? '.env',
383
384
  stage: "deploy",
384
385
  platform: "linux/amd64",
385
386
  ...extraArgs
@@ -493,6 +494,16 @@ export class Laravel extends Component {
493
494
  fs.chmodSync(dst, 0o755);
494
495
  }
495
496
  };
497
+
498
+ /**
499
+ * The URL of the service.
500
+ *
501
+ * If `public.domain` is set, this is the URL with the custom domain.
502
+ * Otherwise, it's the auto-generated load balancer URL.
503
+ */
504
+ public get url() {
505
+ return this.services['web'].url;
506
+ }
496
507
  }
497
508
 
498
509
  const __pulumiType = "sst:aws:Laravel";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirschbaum-development/sst-laravel",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "An unofficial extension of SST to deploy containerized Laravel applications to AWS Fargate.",
5
5
  "main": "laravel-sst.ts",
6
6
  "scripts": {
@@ -25,5 +25,16 @@
25
25
  "url": "https://github.com/kirschbaum-development/sst-laravel/issues"
26
26
  },
27
27
  "homepage": "https://github.com/kirschbaum-development/sst-laravel#readme",
28
- "dependencies": {}
28
+ "bin": {
29
+ "sst-laravel": "./bin/cli.ts"
30
+ },
31
+ "dependencies": {
32
+ "@aws-sdk/client-ecs": "^3.0.0",
33
+ "@inquirer/prompts": "^7.0.0",
34
+ "commander": "^12.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.0.0",
38
+ "typescript": "^5.0.0"
39
+ }
29
40
  }
package/sst-env.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  /* eslint-disable */
4
4
  /* deno-fmt-ignore-file */
5
5
 
6
- /// <reference path="../../sst-env.d.ts" />
6
+ /// <reference path="../../../sst-env.d.ts" />
7
7
 
8
8
  import "sst"
9
9
  export {}
@@ -0,0 +1,35 @@
1
+ /// <reference path="./.sst/platform/config.d.ts" />
2
+
3
+ export default $config({
4
+ app(input) {
5
+ return {
6
+ name: "my-laravel-app",
7
+ removal: input?.stage === "production" ? "retain" : "remove",
8
+ protect: ["production"].includes(input?.stage),
9
+ home: "aws",
10
+ };
11
+ },
12
+ async run() {
13
+ const { Laravel } = await import("@kirschbaum-development/sst-laravel");
14
+ const vpc = new sst.aws.Vpc("MyVpc");
15
+
16
+ const app = new Laravel("MyLaravelApp", {
17
+ vpc,
18
+
19
+ web: {
20
+ domain: $app.stage === "production"
21
+ ? "example.com"
22
+ : `${$app.stage}.example.com`,
23
+ },
24
+
25
+ workers: [{
26
+ horizon: true,
27
+ scheduler: true,
28
+ }]
29
+ });
30
+
31
+ return {
32
+ url: app.url,
33
+ };
34
+ },
35
+ });