@kirschbaum-development/sst-laravel 0.2.14 → 0.2.16

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/docs/api.md ADDED
@@ -0,0 +1,597 @@
1
+ # Laravel Component API Reference
2
+
3
+ ## RemoteEnvVault
4
+
5
+ The `RemoteEnvVault` component manages environment variables for your Laravel application using AWS Secrets Manager. This provides a secure way to store and manage sensitive configuration values.
6
+
7
+ ### Large Environment Files
8
+
9
+ Environment files that exceed AWS Secrets Manager's 64KB limit are automatically split into multiple chunks. This is handled transparently by the CLI commands - you don't need to do anything special.
10
+
11
+ When pushing a large `.env` file:
12
+ - The file is automatically split into multiple secrets (e.g., `/{app}/{stage}/env/1`, `/{app}/{stage}/env/2`, etc.)
13
+ - A metadata secret at `/{app}/{stage}/env` tracks the chunk count
14
+ - When pulling or deploying, all chunks are automatically merged back together
15
+
16
+ ### Constructor
17
+
18
+ ```typescript
19
+ new RemoteEnvVault(name: string, args?: RemoteEnvVaultArgs, opts?: ComponentResourceOptions)
20
+ ```
21
+
22
+ ### RemoteEnvVaultArgs
23
+
24
+ #### `path`
25
+ - **Type:** `Input<string>`
26
+ - **Default:** `/{app-name}/{stage}/env`
27
+ - **Description:** The path in AWS Secrets Manager where environment variables will be stored.
28
+
29
+ **Example:**
30
+ ```typescript
31
+ const env = new RemoteEnvVault("Env", {
32
+ path: "/my-app/production/env"
33
+ });
34
+ ```
35
+
36
+ ### Properties
37
+
38
+ #### `path`
39
+ - **Type:** `Output<string>`
40
+ - **Description:** The path in AWS Secrets Manager where environment variables are stored.
41
+
42
+ ### CLI Commands
43
+
44
+ The following CLI commands are available for managing environment variables:
45
+
46
+ #### `env:push`
47
+
48
+ Push environment variables from a local `.env` file to AWS Secrets Manager.
49
+
50
+ ```bash
51
+ sst-laravel env:push [options]
52
+ ```
53
+
54
+ **Options:**
55
+ - `-s, --stage <stage>` - SST stage name
56
+ - `-i, --input <file>` - Input file path (default: `.env`)
57
+ - `-f, --force` - Push without confirmation
58
+
59
+ **Example:**
60
+ ```bash
61
+ # Push .env.production to the production stage
62
+ sst-laravel env:push --stage production --input .env.production
63
+
64
+ # Push .env to staging with confirmation
65
+ sst-laravel env:push --stage staging
66
+ ```
67
+
68
+ #### `env:pull`
69
+
70
+ Pull environment variables from AWS Secrets Manager to a local `.env` file.
71
+
72
+ ```bash
73
+ sst-laravel env:pull [options]
74
+ ```
75
+
76
+ **Options:**
77
+ - `-s, --stage <stage>` - SST stage name
78
+ - `-o, --output <file>` - Output file path (default: `.env.{stage}`)
79
+ - `-f, --force` - Overwrite existing file without confirmation
80
+
81
+ **Example:**
82
+ ```bash
83
+ # Pull from production to .env.production
84
+ sst-laravel env:pull --stage production
85
+
86
+ # Pull from staging to a custom file
87
+ sst-laravel env:pull --stage staging --output .env.local
88
+ ```
89
+
90
+ ### Usage with LaravelService
91
+
92
+ ```typescript
93
+ const env = new RemoteEnvVault("Env");
94
+
95
+ new LaravelService("Laravel", {
96
+ vpc,
97
+ web: {
98
+ domain: "example.com"
99
+ },
100
+ config: {
101
+ environment: {
102
+ secrets: env
103
+ }
104
+ }
105
+ });
106
+ ```
107
+
108
+ When using `RemoteEnvVault`, deploy your application using the `sst-laravel deploy` command, which will automatically fetch secrets from AWS Secrets Manager before building the Docker image:
109
+
110
+ ```bash
111
+ sst-laravel deploy --stage production
112
+ ```
113
+
114
+ ---
115
+
116
+ ## LaravelService
117
+
118
+ ### Constructor
119
+
120
+ ```typescript
121
+ new LaravelService(name: string, args: LaravelArgs, opts?: ComponentResourceOptions)
122
+ ```
123
+
124
+ Creates a new Laravel component for deploying Laravel applications to AWS Fargate.
125
+
126
+ ## LaravelArgs
127
+
128
+ ### `path`
129
+ - **Type:** `Input<string>`
130
+ - **Default:** `'.'`
131
+ - **Description:** Path to the Laravel application directory.
132
+
133
+ ### `link`
134
+ - **Type:** `Array<Resource | { resource: Resource; environment?: EnvCallback }>`
135
+ - **Description:** Resources to link to the Laravel application. Supports SST resources like databases, Redis, email services, queues, and S3 buckets. When linked, environment variables are automatically configured.
136
+
137
+ Supported resources with automatic environment variable injection:
138
+ - `Postgres` - Sets `DB_CONNECTION`, `DB_HOST`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, `DB_PORT`
139
+ - `Mysql` - Sets `DB_CONNECTION`, `DB_HOST`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, `DB_PORT`
140
+ - `Aurora` - Sets database variables based on port (5432 for Postgres, 3306 for MySQL)
141
+ - `Redis` - Sets `REDIS_HOST`, `REDIS_PORT`, `REDIS_PASSWORD`
142
+ - `Email` - Sets `MAIL_MAILER` to 'ses'
143
+ - `Queue` - Sets `SQS_QUEUE`
144
+ - `Bucket` - Sets `FILESYSTEM_DISK` to 's3', `AWS_BUCKET`
145
+
146
+ You can provide a custom `environment` callback function to override or extend the default environment variables:
147
+
148
+ ```typescript
149
+ link: [
150
+ {
151
+ resource: myDatabase,
152
+ environment: (resource) => ({
153
+ CUSTOM_DB_VAR: resource.host
154
+ })
155
+ }
156
+ ]
157
+ ```
158
+
159
+ ### `permissions`
160
+ - **Type:** `Array<{ actions: string[]; resources: string[] }>`
161
+ - **Description:** IAM permissions to grant to the Laravel application containers.
162
+
163
+ **Example:**
164
+ ```typescript
165
+ permissions: [
166
+ {
167
+ actions: ["s3:GetObject", "s3:PutObject"],
168
+ resources: ["arn:aws:s3:::my-bucket/*"]
169
+ }
170
+ ]
171
+ ```
172
+
173
+ ### `vpc`
174
+ - **Type:** `ClusterArgs["vpc"]`
175
+ - **Description:** VPC configuration for the ECS cluster. Inherited from SST's Cluster component.
176
+
177
+ ### `web`
178
+ - **Type:** `LaravelWebArgs`
179
+ - **Description:** Configuration for the web service that handles HTTP traffic.
180
+
181
+ #### `web.domain`
182
+ - **Type:** `Input<string | { name: Input<string>; cert?: Input<string>; dns?: Input<false | Dns> }>`
183
+ - **Description:** Custom domain for the web layer. If you don't provide a domain name, you will be able to use the load balancer domain for testing (http only).
184
+
185
+ **Example (simple string):**
186
+ ```typescript
187
+ web: {
188
+ domain: "example.com"
189
+ }
190
+ ```
191
+
192
+ **Example (with stage variable):**
193
+ ```typescript
194
+ web: {
195
+ domain: {
196
+ name: `${$app.stage}.example.com`
197
+ }
198
+ }
199
+ ```
200
+
201
+ **Example (with custom certificate):**
202
+ ```typescript
203
+ web: {
204
+ domain: {
205
+ name: "example.com",
206
+ cert: "arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012"
207
+ }
208
+ }
209
+ ```
210
+
211
+ **Example (with custom DNS provider):**
212
+ ```typescript
213
+ web: {
214
+ domain: {
215
+ name: "example.com",
216
+ dns: sst.cloudflare.dns()
217
+ }
218
+ }
219
+ ```
220
+
221
+ #### `web.architecture`
222
+ - **Type:** `ServiceArgs["architecture"]`
223
+ - **Description:** The CPU architecture for the web service.
224
+
225
+ #### `web.cpu`
226
+ - **Type:** `ServiceArgs["cpu"]`
227
+ - **Description:** CPU units for the web service.
228
+
229
+ #### `web.memory`
230
+ - **Type:** `ServiceArgs["memory"]`
231
+ - **Description:** Memory allocation for the web service.
232
+
233
+ #### `web.storage`
234
+ - **Type:** `ServiceArgs["storage"]`
235
+ - **Description:** Storage configuration for the web service.
236
+
237
+ #### `web.scaling`
238
+ - **Type:** `ServiceArgs["scaling"]`
239
+ - **Description:** Auto-scaling configuration for the web service.
240
+
241
+ **Example:**
242
+ ```typescript
243
+ web: {
244
+ scaling: {
245
+ min: 2,
246
+ max: 10,
247
+ cpuUtilization: 70,
248
+ memoryUtilization: 80
249
+ }
250
+ }
251
+ ```
252
+
253
+ #### `web.logging`
254
+ - **Type:** `ServiceArgs["logging"]`
255
+ - **Description:** Logging configuration for the web service.
256
+
257
+ #### `web.health`
258
+ - **Type:** `ServiceArgs["health"]`
259
+ - **Description:** Health check configuration for the web service.
260
+
261
+ #### `web.executionRole`
262
+ - **Type:** `ServiceArgs["executionRole"]`
263
+ - **Description:** Execution role for the web service.
264
+
265
+ #### `web.permissions`
266
+ - **Type:** `ServiceArgs["permissions"]`
267
+ - **Description:** IAM permissions specific to the web service.
268
+
269
+ ### `workers`
270
+ - **Type:** `LaravelWorkerConfig[]`
271
+ - **Description:** Configuration for worker services (Horizon, scheduler, or custom tasks).
272
+
273
+ #### `workers[].name`
274
+ - **Type:** `Input<string>`
275
+ - **Description:** Name of the worker service. If not provided, defaults to `worker-{index}`.
276
+
277
+ #### `workers[].horizon`
278
+ - **Type:** `Input<boolean>`
279
+ - **Default:** `false`
280
+ - **Description:** Running horizon?
281
+
282
+ #### `workers[].scheduler`
283
+ - **Type:** `Input<boolean>`
284
+ - **Default:** `false`
285
+ - **Description:** Running scheduler?
286
+
287
+ #### `workers[].tasks`
288
+ - **Type:** `Input<{ [key: string]: Input<{ command: Input<string>; dependencies?: Input<string[]> }> }>`
289
+ - **Description:** Multiple tasks can be run in the worker.
290
+
291
+ **Example:**
292
+ ```typescript
293
+ workers: [
294
+ {
295
+ name: "main-worker",
296
+ horizon: true,
297
+ scheduler: true,
298
+ scaling: {
299
+ min: 1,
300
+ max: 5
301
+ }
302
+ },
303
+ {
304
+ name: "custom-worker",
305
+ tasks: {
306
+ "my-task": {
307
+ command: "php artisan my:command",
308
+ dependencies: ["laravel-horizon"]
309
+ }
310
+ }
311
+ }
312
+ ]
313
+ ```
314
+
315
+ #### `workers[].architecture`
316
+ - **Type:** `ServiceArgs["architecture"]`
317
+ - **Description:** The CPU architecture for the worker service.
318
+
319
+ #### `workers[].cpu`
320
+ - **Type:** `ServiceArgs["cpu"]`
321
+ - **Description:** CPU units for the worker service.
322
+
323
+ #### `workers[].memory`
324
+ - **Type:** `ServiceArgs["memory"]`
325
+ - **Description:** Memory allocation for the worker service.
326
+
327
+ #### `workers[].storage`
328
+ - **Type:** `ServiceArgs["storage"]`
329
+ - **Description:** Storage configuration for the worker service.
330
+
331
+ #### `workers[].scaling`
332
+ - **Type:** `ServiceArgs["scaling"]`
333
+ - **Description:** Auto-scaling configuration for the worker service.
334
+
335
+ #### `workers[].logging`
336
+ - **Type:** `ServiceArgs["logging"]`
337
+ - **Description:** Logging configuration for the worker service.
338
+
339
+ #### `workers[].health`
340
+ - **Type:** `ServiceArgs["health"]`
341
+ - **Description:** Health check configuration for the worker service.
342
+
343
+ #### `workers[].executionRole`
344
+ - **Type:** `ServiceArgs["executionRole"]`
345
+ - **Description:** Execution role for the worker service.
346
+
347
+ #### `workers[].permissions`
348
+ - **Type:** `ServiceArgs["permissions"]`
349
+ - **Description:** IAM permissions specific to this worker.
350
+
351
+ ### `config`
352
+ - **Type:** `object`
353
+ - **Description:** Config settings.
354
+
355
+ #### `config.php`
356
+ - **Type:** `Input<Number>`
357
+ - **Default:** `8.4`
358
+ - **Description:** PHP version. Available versions: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5
359
+
360
+ #### `config.opcache`
361
+ - **Type:** `Input<boolean>`
362
+ - **Default:** `true`
363
+ - **Description:** PHP Opcache should be enabled?
364
+
365
+ #### `config.environment`
366
+ - **Type:** `object`
367
+ - **Description:** Environment variable configuration.
368
+
369
+ ##### `config.environment.file`
370
+ - **Type:** `Input<string>`
371
+ - **Description:** Use this option if you want to import an .env file during build. By default, SST Laravel won't use your .env file since that might be the wrong file when deploying from your local machine.
372
+
373
+ **Example:**
374
+ ```typescript
375
+ config: {
376
+ environment: {
377
+ file: `.env.${$app.stage}`
378
+ }
379
+ }
380
+ ```
381
+
382
+ ##### `config.environment.autoInject`
383
+ - **Type:** `Input<boolean>`
384
+ - **Default:** `true`
385
+ - **Description:** Set this to false in case you don't want to auto inject environment variables from your linked resources.
386
+
387
+ ##### `config.environment.vars`
388
+ - **Type:** `FunctionArgs["environment"]`
389
+ - **Description:** Custom environment variables that will be automatically injected into your application.
390
+
391
+ **Example:**
392
+ ```typescript
393
+ config: {
394
+ environment: {
395
+ vars: {
396
+ SESSION_DRIVER: 'redis',
397
+ QUEUE_CONNECTION: 'redis',
398
+ LOG_CHANNEL: 'stderr'
399
+ }
400
+ }
401
+ }
402
+ ```
403
+
404
+ ##### `config.environment.secrets`
405
+ - **Type:** `RemoteEnvVault`
406
+ - **Description:** Use a `RemoteEnvVault` component to manage environment variables in AWS Secrets Manager. When provided, secrets will be fetched from AWS Secrets Manager at build time using the `sst-laravel deploy` command.
407
+
408
+ **Example:**
409
+ ```typescript
410
+ const env = new RemoteEnvVault("Env");
411
+
412
+ new LaravelService("Laravel", {
413
+ config: {
414
+ environment: {
415
+ secrets: env
416
+ }
417
+ }
418
+ });
419
+ ```
420
+
421
+ > **Note:** When using `secrets`, you should deploy using `sst-laravel deploy --stage <stage>` instead of `sst deploy` directly. This ensures secrets are fetched from AWS Secrets Manager before the Docker build.
422
+
423
+ #### `config.deployment`
424
+ - **Type:** `object`
425
+ - **Description:** Custom deployment configurations.
426
+
427
+ ##### `config.deployment.script`
428
+ - **Type:** `Input<string>`
429
+ - **Description:** Path to a custom deployment script to run during container startup.
430
+
431
+ **Example:**
432
+ ```typescript
433
+ config: {
434
+ deployment: {
435
+ script: "./deploy.sh"
436
+ }
437
+ }
438
+ ```
439
+
440
+ ## Properties
441
+
442
+ ### `url`
443
+ - **Type:** `Output<string>`
444
+ - **Description:** The URL of the web service. If `web.domain` is set, returns the custom domain URL. Otherwise, returns the auto-generated load balancer URL.
445
+
446
+ **Example:**
447
+ ```typescript
448
+ const app = new LaravelService("MyApp", { ... });
449
+ console.log(app.url); // https://example.com or https://xyz.elb.amazonaws.com
450
+ ```
451
+
452
+ ## Complete Example
453
+
454
+ ```typescript
455
+ const vpc = new sst.aws.Vpc("MyVpc");
456
+ const database = new sst.aws.Postgres("MyDatabase", { vpc });
457
+ const redis = new sst.aws.Redis("MyRedis", { vpc });
458
+ const bucket = new sst.aws.Bucket("MyBucket");
459
+
460
+ const app = new LaravelService("MyApp", {
461
+ path: "./",
462
+ vpc,
463
+
464
+ link: [database, redis, bucket],
465
+
466
+ permissions: [
467
+ {
468
+ actions: ["s3:*"],
469
+ resources: [bucket.arn, `${bucket.arn}/*`]
470
+ }
471
+ ],
472
+
473
+ web: {
474
+ domain: "example.com",
475
+ scaling: {
476
+ min: 2,
477
+ max: 10
478
+ }
479
+ },
480
+
481
+ workers: [
482
+ {
483
+ name: "queue-worker",
484
+ horizon: true,
485
+ scheduler: true,
486
+ scaling: {
487
+ min: 1,
488
+ max: 5
489
+ }
490
+ }
491
+ ],
492
+
493
+ config: {
494
+ php: 8.4,
495
+ opcache: true,
496
+
497
+ environment: {
498
+ file: `.env.${$app.stage}`,
499
+ autoInject: true,
500
+ vars: {
501
+ SESSION_DRIVER: 'redis',
502
+ QUEUE_CONNECTION: 'redis'
503
+ }
504
+ },
505
+
506
+ deployment: {
507
+ script: "./deploy.sh"
508
+ }
509
+ }
510
+ });
511
+
512
+ return {
513
+ url: app.url
514
+ };
515
+ ```
516
+
517
+ ## Example with RemoteEnvVault (Secrets Manager)
518
+
519
+ ```typescript
520
+ const vpc = new sst.aws.Vpc("MyVpc");
521
+ const database = new sst.aws.Postgres("MyDatabase", { vpc });
522
+ const redis = new sst.aws.Redis("MyRedis", { vpc });
523
+
524
+ // Create environment secrets manager
525
+ const env = new RemoteEnvVault("Env");
526
+
527
+ const app = new LaravelService("MyApp", {
528
+ path: "./",
529
+ vpc,
530
+
531
+ link: [database, redis],
532
+
533
+ web: {
534
+ domain: "example.com",
535
+ scaling: {
536
+ min: 2,
537
+ max: 10
538
+ }
539
+ },
540
+
541
+ workers: [
542
+ {
543
+ name: "queue-worker",
544
+ horizon: true,
545
+ scheduler: true
546
+ }
547
+ ],
548
+
549
+ config: {
550
+ php: 8.4,
551
+
552
+ environment: {
553
+ // Use secrets from AWS Secrets Manager
554
+ secrets: env,
555
+ // Auto-inject linked resource variables (database, redis)
556
+ autoInject: true,
557
+ // Additional runtime variables
558
+ vars: {
559
+ SESSION_DRIVER: 'redis',
560
+ QUEUE_CONNECTION: 'redis'
561
+ }
562
+ }
563
+ }
564
+ });
565
+
566
+ return {
567
+ url: app.url,
568
+ secretsPath: env.path
569
+ };
570
+ ```
571
+
572
+ ### Workflow with RemoteEnvVault
573
+
574
+ 1. **Initial setup** - Push your `.env` file to AWS Secrets Manager:
575
+ ```bash
576
+ sst-laravel env:push --stage production --input .env.production
577
+ ```
578
+
579
+ 2. **Deploy** - Use the sst-laravel CLI to deploy (automatically fetches secrets):
580
+ ```bash
581
+ sst-laravel deploy --stage production
582
+ ```
583
+
584
+ 3. **Update secrets** - When you need to update environment variables:
585
+ ```bash
586
+ # Pull current secrets (creates .env.production by default)
587
+ sst-laravel env:pull --stage production
588
+
589
+ # Edit the file
590
+ nano .env.production
591
+
592
+ # Push updated secrets
593
+ sst-laravel env:push --stage production --input .env.production
594
+
595
+ # Redeploy to apply changes
596
+ sst-laravel deploy --stage production
597
+ ```
package/laravel-sst.ts CHANGED
@@ -537,7 +537,7 @@ export class LaravelService extends Component {
537
537
  return {
538
538
  id: vpc.id,
539
539
  securityGroups: vpc.securityGroups,
540
- containerSubnets: vpc.publicSubnets,
540
+ containerSubnets: vpc.privateSubnets,
541
541
  loadBalancerSubnets: vpc.publicSubnets,
542
542
  cloudmapNamespaceId: cloudmapNamespace.id,
543
543
  cloudmapNamespaceName: cloudmapNamespace.name,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirschbaum-development/sst-laravel",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "type": "module",
5
5
  "description": "An unofficial extension of SST to deploy containerized Laravel applications to AWS Fargate.",
6
6
  "main": "laravel-sst.ts",
@@ -19,6 +19,8 @@
19
19
  "templates",
20
20
  "conf",
21
21
  "images",
22
+ "docs",
23
+ "README.md",
22
24
  "Dockerfile.web",
23
25
  "Dockerfile.worker",
24
26
  ".dockerignore",
@@ -26,6 +28,7 @@
26
28
  ],
27
29
  "scripts": {
28
30
  "build": "tsc",
31
+ "test": "vitest run",
29
32
  "release": "./scripts/publish.sh"
30
33
  },
31
34
  "repository": {
@@ -63,7 +66,8 @@
63
66
  },
64
67
  "devDependencies": {
65
68
  "@types/node": "^20.0.0",
66
- "typescript": "^5.0.0"
69
+ "typescript": "^5.0.0",
70
+ "vitest": "4.1.5"
67
71
  },
68
72
  "publishConfig": {
69
73
  "access": "public",
@@ -213,19 +213,38 @@ function buildEnvFileContent(
213
213
  ].filter(Boolean).join('\n\n');
214
214
  }
215
215
 
216
- function toEnvFileContent(vars: Record<string, string>): string {
216
+ export function toEnvFileContent(vars: Record<string, string>): string {
217
217
  const sortedKeys = Object.keys(vars).sort();
218
218
 
219
219
  return sortedKeys
220
220
  .map((key) => {
221
221
  const value = vars[key];
222
+ const needsQuoting =
223
+ value.includes(' ') ||
224
+ value.includes('"') ||
225
+ value.includes("'") ||
226
+ value.includes('\n') ||
227
+ value.includes('$') ||
228
+ value.includes('\\') ||
229
+ value.includes('#');
230
+
231
+ if (!needsQuoting) {
232
+ return `${key}=${value}`;
233
+ }
222
234
 
223
- if (value.includes(' ') || value.includes('"') || value.includes("'") || value.includes('\n')) {
224
- const escaped = value.replace(/"/g, '\\"');
225
- return `${key}="${escaped}"`;
235
+ // Single quotes are phpdotenv "raw literal" mode — no $ expansion, no escapes.
236
+ // Use them whenever possible so randomly-generated secrets round-trip safely.
237
+ if (!value.includes("'") && !value.includes('\n')) {
238
+ return `${key}='${value}'`;
226
239
  }
227
240
 
228
- return `${key}=${value}`;
241
+ // Fall back to double quotes when the value itself contains a single quote
242
+ // or newline. Escape \, $, and " so phpdotenv reads the literal value.
243
+ const escaped = value
244
+ .replace(/\\/g, '\\\\')
245
+ .replace(/\$/g, '\\$')
246
+ .replace(/"/g, '\\"');
247
+ return `${key}="${escaped}"`;
229
248
  })
230
249
  .join('\n');
231
250
  }