@kirschbaum-development/sst-laravel 0.2.11 → 0.2.14

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/laravel-sst.ts CHANGED
@@ -2,716 +2,878 @@
2
2
 
3
3
  import * as path from 'path';
4
4
  import * as fs from 'fs';
5
- import { Component } from "../../../.sst/platform/src/components/component.js";
6
- import { FunctionArgs } from "../../../.sst/platform/src/components/aws/function.js";;
7
- import { ComponentResourceOptions, Input as PulumiInput, Output, all, output } from "@pulumi/pulumi";
8
- import { Input } from "../../../.sst/platform/src/components/input.js";
9
- import { ClusterArgs } from "../../../.sst/platform/src/components/aws/cluster.js";
10
- import { ServiceArgs } from "../../../.sst/platform/src/components/aws/service.js";
11
- import { Dns } from "../../../.sst/platform/src/components/dns.js";
12
- import { applyLinkedResourcesEnv, EnvCallback, EnvCallbacks, extractSecrets } from "./src/laravel-env";
13
- import { RemoteEnvVault, RemoteEnvVaultArgs } from "./src/laravel-env-manager";
14
- import { getPackagePath } from "./src/config";
15
- import { RemoteEnvFile } from "./src/remote-env-file";
16
- import { getSecretsFingerprint } from "./src/secrets-manager";
5
+ import { Component } from '../../../.sst/platform/src/components/component.js';
6
+ import { FunctionArgs } from '../../../.sst/platform/src/components/aws/function.js';
7
+ import {
8
+ ComponentResourceOptions,
9
+ Input as PulumiInput,
10
+ Output,
11
+ all,
12
+ output,
13
+ runtime,
14
+ } from '@pulumi/pulumi';
15
+ import { Input } from '../../../.sst/platform/src/components/input.js';
16
+ import { ClusterArgs } from '../../../.sst/platform/src/components/aws/cluster.js';
17
+ import { ServiceArgs } from '../../../.sst/platform/src/components/aws/service.js';
18
+ import { Dns } from '../../../.sst/platform/src/components/dns.js';
19
+ import {
20
+ applyLinkedResourcesEnv,
21
+ EnvCallback,
22
+ EnvCallbacks,
23
+ extractSecrets,
24
+ } from './src/laravel-env';
25
+ import { RemoteEnvVault, RemoteEnvVaultArgs } from './src/laravel-env-manager';
26
+ import { getPackagePath } from './src/config';
27
+ import { RemoteEnvFile } from './src/remote-env-file';
28
+ import { getSecretsFingerprint } from './src/secrets-manager';
17
29
 
18
30
  // Re-export RemoteEnvVault for external use
19
31
  export { RemoteEnvVault, RemoteEnvVaultArgs };
20
32
 
21
33
  // duplicate from cluster.ts
22
- type Port = `${number}/${"http" | "https" | "tcp" | "udp" | "tcp_udp" | "tls"}`;
34
+ type Port = `${number}/${'http' | 'https' | 'tcp' | 'udp' | 'tcp_udp' | 'tls'}`;
23
35
 
24
36
  type Ports = {
25
- listen: Port,
26
- forward: Port
37
+ listen: Port;
38
+ forward: Port;
27
39
  }[];
28
40
 
29
41
  enum ImageType {
30
- Web = 'web',
31
- Worker = 'worker',
32
- Cli = 'cli',
42
+ Web = 'web',
43
+ Worker = 'worker',
44
+ Cli = 'cli',
33
45
  }
34
46
 
35
47
  export interface LaravelServiceArgs {
36
- architecture?: ServiceArgs["architecture"];
37
- cpu?: ServiceArgs["cpu"];
38
- memory?: ServiceArgs["memory"];
39
- storage?: ServiceArgs["storage"];
40
- loadBalancer?: ServiceArgs["loadBalancer"];
41
- scaling?: ServiceArgs["scaling"];
42
- logging?: ServiceArgs["logging"];
43
- health?: ServiceArgs["health"];
44
- executionRole?: ServiceArgs["executionRole"];
45
- permissions?: ServiceArgs["permissions"];
48
+ architecture?: ServiceArgs['architecture'];
49
+ cpu?: ServiceArgs['cpu'];
50
+ memory?: ServiceArgs['memory'];
51
+ storage?: ServiceArgs['storage'];
52
+ loadBalancer?: ServiceArgs['loadBalancer'];
53
+ scaling?: ServiceArgs['scaling'];
54
+ logging?: ServiceArgs['logging'];
55
+ health?: ServiceArgs['health'];
56
+ executionRole?: ServiceArgs['executionRole'];
57
+ permissions?: ServiceArgs['permissions'];
46
58
  }
47
59
 
48
60
  export interface LaravelWebArgs extends LaravelServiceArgs {
49
- /**
50
- * 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))
51
- */
52
- domain?: Input<
53
- string
54
- | {
55
- /**
56
- * Domain name. You are able to use variables from the SST config file here.
57
- *
58
- * @example
59
- * ```js
60
- * domain: {
61
- * name: `${$app.stage}.example.com`,
62
- * }
63
- * ```
64
- */
65
- name: Input<string>;
66
-
67
- /**
68
- * Certificate ARN. Use this in case you are manually setting up the SSL certificate.
69
- * This is usually needed when your DNS is not in the same AWS account or is outside of AWS.
70
- *
71
- * @example
72
- * ```js
73
- * domain: {
74
- * cert: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
75
- * }
76
- * ```
77
- */
78
- cert?: Input<string>;
79
-
80
- /**
81
- * SST DNS configuration. You can use this configuration if your DNS is in Cloudflare or another AWS account.
82
- *
83
- * @see https://sst.dev/docs/component/cloudflare/dns/
84
- * @see https://sst.dev/docs/component/aws/dns/
85
- * @example
86
- * ```js
87
- * domain: {
88
- * dns: sst.cloudflare.dns(),
89
- * }
90
- * ```
91
- */
92
- dns?: Input<false | (Dns & {})>;
93
- }
94
- >;
61
+ /**
62
+ * 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))
63
+ */
64
+ domain?: Input<
65
+ | string
66
+ | {
67
+ /**
68
+ * Domain name. You are able to use variables from the SST config file here.
69
+ *
70
+ * @example
71
+ * ```js
72
+ * domain: {
73
+ * name: `${$app.stage}.example.com`,
74
+ * }
75
+ * ```
76
+ */
77
+ name: Input<string>;
78
+
79
+ /**
80
+ * Certificate ARN. Use this in case you are manually setting up the SSL certificate.
81
+ * This is usually needed when your DNS is not in the same AWS account or is outside of AWS.
82
+ *
83
+ * @example
84
+ * ```js
85
+ * domain: {
86
+ * cert: 'arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012',
87
+ * }
88
+ * ```
89
+ */
90
+ cert?: Input<string>;
91
+
92
+ /**
93
+ * SST DNS configuration. You can use this configuration if your DNS is in Cloudflare or another AWS account.
94
+ *
95
+ * @see https://sst.dev/docs/component/cloudflare/dns/
96
+ * @see https://sst.dev/docs/component/aws/dns/
97
+ * @example
98
+ * ```js
99
+ * domain: {
100
+ * dns: sst.cloudflare.dns(),
101
+ * }
102
+ * ```
103
+ */
104
+ dns?: Input<false | (Dns & {})>;
105
+ }
106
+ >;
95
107
  }
96
108
 
97
109
  export interface LaravelWorkerConfig extends LaravelServiceArgs {
98
- name?: Input<string>;
99
- /**
100
- * Running horizon?
101
- */
102
- horizon?: Input<boolean>;
103
-
104
- /**
105
- * Running scheduler?
106
- */
107
- scheduler?: Input<boolean>;
108
-
109
- /**
110
- * Multiple tasks can be run in the worker.
111
- */
112
- tasks?: Input<{
113
- [key: string]: Input<{
114
- command: Input<string>;
115
- dependencies?: Input<string[]>;
116
- }>
117
- }>
118
- }
119
-
120
- export interface LaravelArgs extends ClusterArgs {
121
- // dev?: false | DevArgs["dev"];
122
- path?: Input<string>;
123
- link?: Array<
124
- | any
125
- | {
126
- resource: any;
127
- environment?: EnvCallback;
128
- }
129
- >;
130
-
131
- permissions?: Array<{
132
- actions: string[];
133
- resources: string[];
134
- }>;
135
-
136
- /**
137
- * If enabled, a container will be created to handle HTTP traffic.
138
- */
139
- web?: LaravelWebArgs;
140
-
141
- /**
142
- * Multiple workers settings.
143
- */
144
- workers?: LaravelWorkerConfig[];
145
-
146
- /**
147
- * Config settings.
148
- */
149
- config?: {
110
+ name?: Input<string>;
150
111
  /**
151
- * PHP version.
152
- * Available versions: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5
153
- *
154
- * @default `8.4`
112
+ * Running horizon?
155
113
  */
156
- php?: Input<Number>;
114
+ horizon?: Input<boolean>;
157
115
 
158
116
  /**
159
- * PHP Opcache should be enabled?
160
- *
161
- * @default `true`
117
+ * Running scheduler?
162
118
  */
163
- opcache?: Input<boolean>;
164
-
165
- environment?: {
166
- /**
167
- * 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.
168
- *
169
- * @example
170
- * ```js
171
- * # Use use a fila named .env.$stage as your .env file
172
- * environment: {
173
- * file: `.env.${$app.stage}`,
174
- * }
175
- * OR
176
- * environment: {
177
- * file: `.env`,
178
- * }
179
- * ```
180
- */
181
- file?: Input<string>,
182
-
183
- /**
184
- * Set this to false in case you don't want to auto inject environment variables from your linked resources.
185
- *
186
- * @default `true`
187
- */
188
- autoInject?: Input<boolean>,
189
-
190
- /**
191
- * Custom environment variables that will be automatically injected into your application.
192
- *
193
- * @example
194
- * ```js
195
- * environment: {
196
- * vars: {
197
- * SESSION_DRIVER: 'redis',
198
- * QUEUE_CONNECTION: 'redis',
199
- * }
200
- * }
201
- * ```
202
- */
203
- vars?: FunctionArgs["environment"],
204
-
205
- /**
206
- * Use a `RemoteEnvVault` component to manage environment variables in AWS Secrets Manager.
207
- * When provided, secrets will be fetched from AWS Secrets Manager at build time.
208
- *
209
- * @example
210
- * ```js
211
- * const env = new RemoteEnvVault("Env");
212
- *
213
- * new LaravelService("Laravel", {
214
- * config: {
215
- * environment: {
216
- * secrets: env,
217
- * },
218
- * },
219
- * });
220
- * ```
221
- */
222
- secrets?: RemoteEnvVault,
223
- };
119
+ scheduler?: Input<boolean>;
224
120
 
225
121
  /**
226
- * Custom deployment configurations.
122
+ * Multiple tasks can be run in the worker.
227
123
  */
228
- deployment?: {
229
- // migrate?: Input<boolean>;
230
- // optimize?: Input<boolean>;
231
- script?: Input<string>;
232
- };
233
- }
124
+ tasks?: Input<{
125
+ [key: string]: Input<{
126
+ command: Input<string>;
127
+ dependencies?: Input<string[]>;
128
+ }>;
129
+ }>;
234
130
  }
235
131
 
236
- export class LaravelService extends Component {
237
- private readonly services: Record<string, sst.aws.Service>;
238
- private readonly _messages: string[] = [];
239
-
240
- constructor(
241
- name: string,
242
- args: LaravelArgs,
243
- opts: ComponentResourceOptions = {},
244
- ) {
245
- super(__pulumiType, name, args, opts);
246
-
247
- this.services = {};
248
-
249
- args.config = args.config ?? {};
250
- const sitePath = args.path ?? '.';
251
- const absSitePath = path.resolve(sitePath.toString());
252
- const nodeModulePath = getPackagePath();
253
-
254
- // Determine the path where our plugin will save build files.
255
- // SST sets __dirname to the .sst/platform directory.
256
- const pluginBuildPath = path.resolve(__dirname, '../laravel');
132
+ export interface LaravelArgs extends ClusterArgs {
133
+ // dev?: false | DevArgs["dev"];
134
+ path?: Input<string>;
135
+ link?: Array<
136
+ | any
137
+ | {
138
+ resource: any;
139
+ environment?: EnvCallback;
140
+ }
141
+ >;
257
142
 
258
- if (!fs.existsSync(pluginBuildPath)) {
259
- fs.mkdirSync(pluginBuildPath, { recursive: true });
260
- }
143
+ permissions?: Array<{
144
+ actions: string[];
145
+ resources: string[];
146
+ }>;
261
147
 
262
- if (!fs.existsSync(pluginBuildPath + '/deploy')) {
263
- fs.mkdirSync(pluginBuildPath + '/deploy', { recursive: true });
264
- }
148
+ /**
149
+ * If enabled, a container will be created to handle HTTP traffic.
150
+ */
151
+ web?: LaravelWebArgs;
265
152
 
266
- const envFilePath = path.resolve(pluginBuildPath, 'deploy', '.env');
153
+ /**
154
+ * Multiple workers settings.
155
+ */
156
+ workers?: LaravelWorkerConfig[];
267
157
 
268
- const envFileHasVariable = (variableName: string): boolean => {
269
- const content = fs.readFileSync(envFilePath, 'utf-8');
270
- return content.split('\n').some(line => line.trim().startsWith(`${variableName}=`));
271
- }
158
+ /**
159
+ * Config settings.
160
+ */
161
+ config?: {
162
+ /**
163
+ * PHP version.
164
+ * Available versions: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5
165
+ *
166
+ * @default `8.4`
167
+ */
168
+ php?: Input<Number>;
272
169
 
273
- const envFileSetVariable = (variableName: string, value: string) => {
274
- fs.appendFileSync(envFilePath, `\n${variableName}=${value}\n`);
275
- this._messages.push(`Added ${variableName} to environment file: ${value}`);
276
- }
170
+ /**
171
+ * PHP Opcache should be enabled?
172
+ *
173
+ * @default `true`
174
+ */
175
+ opcache?: Input<boolean>;
176
+
177
+ environment?: {
178
+ /**
179
+ * 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.
180
+ *
181
+ * @example
182
+ * ```js
183
+ * # Use use a fila named .env.$stage as your .env file
184
+ * environment: {
185
+ * file: `.env.${$app.stage}`,
186
+ * }
187
+ * OR
188
+ * environment: {
189
+ * file: `.env`,
190
+ * }
191
+ * ```
192
+ */
193
+ file?: Input<string>;
194
+
195
+ /**
196
+ * Set this to false in case you don't want to auto inject environment variables from your linked resources.
197
+ *
198
+ * @default `true`
199
+ */
200
+ autoInject?: Input<boolean>;
201
+
202
+ /**
203
+ * Custom environment variables that will be automatically injected into your application.
204
+ *
205
+ * @example
206
+ * ```js
207
+ * environment: {
208
+ * vars: {
209
+ * SESSION_DRIVER: 'redis',
210
+ * QUEUE_CONNECTION: 'redis',
211
+ * }
212
+ * }
213
+ * ```
214
+ */
215
+ vars?: FunctionArgs['environment'];
216
+
217
+ /**
218
+ * Use a `RemoteEnvVault` component to manage environment variables in AWS Secrets Manager.
219
+ * When provided, secrets will be fetched from AWS Secrets Manager at build time.
220
+ *
221
+ * @example
222
+ * ```js
223
+ * const env = new RemoteEnvVault("Env");
224
+ *
225
+ * new LaravelService("Laravel", {
226
+ * config: {
227
+ * environment: {
228
+ * secrets: env,
229
+ * },
230
+ * },
231
+ * });
232
+ * ```
233
+ */
234
+ secrets?: RemoteEnvVault;
235
+ };
277
236
 
278
- const envFileSetVariableIfMissing = (variableName: string, value: string) => {
279
- if (envFileHasVariable(variableName)) {
280
- return;
281
- }
237
+ /**
238
+ * Custom deployment configurations.
239
+ */
240
+ deployment?: {
241
+ // migrate?: Input<boolean>;
242
+ // optimize?: Input<boolean>;
243
+ script?: Input<string>;
244
+ };
245
+ };
246
+ }
282
247
 
283
- envFileSetVariable(variableName, value);
284
- }
248
+ export class LaravelService extends Component {
249
+ private readonly services: Record<string, sst.aws.Service>;
250
+ private readonly _messages: string[] = [];
285
251
 
286
- const environmentFileDependency = prepareEnvironmentFile();
287
- prepareDeploymentScript();
252
+ constructor(
253
+ name: string,
254
+ args: LaravelArgs,
255
+ opts: ComponentResourceOptions = {},
256
+ ) {
257
+ super(__pulumiType, name, args, opts);
288
258
 
289
- const cluster = new sst.aws.Cluster(`${name}-Cluster`, {
290
- vpc: args.vpc
291
- });
259
+ this.services = {};
292
260
 
293
- const addWebService = () => {
294
- const envVariables = getEnvironmentVariables();
261
+ args.config = args.config ?? {};
262
+ const sitePath = args.path ?? '.';
263
+ const absSitePath = path.resolve(sitePath.toString());
264
+ const nodeModulePath = getPackagePath();
295
265
 
296
- this.services['web'] = new sst.aws.Service(`${name}-Web`, {
297
- cluster,
298
- link: getLinks(),
299
- permissions: args.permissions,
266
+ // Determine the path where our plugin will save build files.
267
+ // SST sets __dirname to the .sst/platform directory.
268
+ const pluginBuildPath = path.resolve(__dirname, '../laravel');
300
269
 
301
- /**
302
- * Image passed or use our default provided image.
303
- */
304
- image: getImage(ImageType.Web),
305
- environment: envVariables,
306
- scaling: args.web?.scaling,
307
-
308
- loadBalancer: args.web && args.web.loadBalancer ? args.web.loadBalancer : {
309
- domain: args.web?.domain,
310
- ports: getDefaultPublicPorts(),
311
- },
312
-
313
- dev: {
314
- command: `php ${sitePath}/artisan serve`,
315
- },
316
-
317
- transform: {
318
- taskDefinition: (args) => {
319
- args.containerDefinitions = (args.containerDefinitions as $util.Output<string>).apply(a => {
320
- return JSON.stringify([{
321
- ...JSON.parse(a)[0],
322
- linuxParameters: {
323
- initProcessEnabled: false,
324
- }
325
- }]);
326
- })
327
- }
270
+ if (!fs.existsSync(pluginBuildPath)) {
271
+ fs.mkdirSync(pluginBuildPath, { recursive: true });
328
272
  }
329
- }, {
330
- dependsOn: environmentFileDependency ? [environmentFileDependency] : [],
331
- });
332
- }
333
273
 
334
- function createWorkerTasks(workerConfig: LaravelWorkerConfig, workerBuildPath: string) {
335
- const s6RcDPath = path.resolve(workerBuildPath, 'etc/s6-overlay/s6-rc.d');
336
- const s6UserContentsPath = path.resolve(s6RcDPath, 'user/contents.d');
337
-
338
- fs.mkdirSync(s6UserContentsPath, { recursive: true });
274
+ if (!fs.existsSync(pluginBuildPath + '/deploy')) {
275
+ fs.mkdirSync(pluginBuildPath + '/deploy', { recursive: true });
276
+ }
339
277
 
340
- const tasks: Record<string, { command: string; dependencies?: string[] }> = {
341
- ...((workerConfig.tasks as any) ?? {}),
342
- };
278
+ const envFilePath = path.resolve(pluginBuildPath, 'deploy', '.env');
343
279
 
344
- if (workerConfig.horizon) {
345
- tasks['laravel-horizon'] = {
346
- command: 'php artisan horizon',
280
+ const envFileHasVariable = (variableName: string): boolean => {
281
+ const content = fs.readFileSync(envFilePath, 'utf-8');
282
+ return content
283
+ .split('\n')
284
+ .some((line) => line.trim().startsWith(`${variableName}=`));
347
285
  };
348
- }
349
286
 
350
- if (workerConfig.scheduler) {
351
- tasks['laravel-scheduler'] = {
352
- command: 'php artisan schedule:work',
287
+ const envFileSetVariable = (variableName: string, value: string) => {
288
+ fs.appendFileSync(envFilePath, `\n${variableName}=${value}\n`);
289
+ this._messages.push(
290
+ `Added ${variableName} to environment file: ${value}`,
291
+ );
353
292
  };
354
- }
355
-
356
- Object.entries(tasks).forEach(([taskName, config]) => {
357
- const tasksDir = path.resolve(s6RcDPath, `${taskName}`);
358
- fs.mkdirSync(tasksDir, { recursive: true });
359
-
360
- const scriptSrcPath = path.join(tasksDir, 'script');
361
-
362
- fs.writeFileSync(scriptSrcPath, `#!/command/with-contenv bash\ncd /var/www/html\n${config.command}`, { mode: 0o777 });
363
- fs.writeFileSync(path.join(tasksDir, 'run'), `#!/command/execlineb -P\n/etc/s6-overlay/s6-rc.d/${taskName}/script`, { mode: 0o777 });
364
- fs.writeFileSync(path.join(tasksDir, 'type'), 'longrun');
365
- fs.writeFileSync(path.join(tasksDir, 'dependencies'), (config.dependencies || []).join('\n'));
366
- fs.writeFileSync(path.join(s6UserContentsPath, taskName), '');
367
- });
368
- }
369
293
 
370
- const createWorkerService = (workerConfig: LaravelWorkerConfig, serviceName: string, workerBuildPath: string) => {
371
- createWorkerTasks(workerConfig, workerBuildPath);
372
-
373
- const imgBuildArgs = {
374
- 'CONF_PATH': path.resolve(nodeModulePath, 'conf').replace(absSitePath, ''),
375
- 'CUSTOM_CONF_PATH': workerBuildPath.replace(absSitePath, ''),
376
- };
377
-
378
- this.services[serviceName] = new sst.aws.Service(serviceName, {
379
- cluster,
380
- link: getLinks(),
381
- permissions: args.permissions,
382
-
383
- image: getImage(ImageType.Worker, imgBuildArgs),
384
- scaling: workerConfig.scaling,
385
- environment: getEnvironmentVariables(),
386
-
387
- dev: {
388
- command: `php ${sitePath}/artisan horizon`,
389
- },
390
-
391
- transform: {
392
- taskDefinition: (args) => {
393
- args.containerDefinitions = (args.containerDefinitions as $util.Output<string>).apply(a => {
394
- return JSON.stringify([{
395
- ...JSON.parse(a)[0],
396
- linuxParameters: {
397
- initProcessEnabled: false,
398
- }
399
- }]);
400
- })
401
- }
402
- }
403
- }, {
404
- dependsOn: environmentFileDependency ? [environmentFileDependency] : [],
405
- });
406
- }
407
-
408
- function addWorkerServices() {
409
- args.workers?.forEach((workerConfig, index) => {
410
- const workerName = workerConfig.name || `worker-${index + 1}`;
411
- const absWorkerBuildPath = path.resolve(pluginBuildPath, `worker-${workerName}`);
294
+ const envFileSetVariableIfMissing = (
295
+ variableName: string,
296
+ value: string,
297
+ ) => {
298
+ if (envFileHasVariable(variableName)) {
299
+ return;
300
+ }
412
301
 
413
- createWorkerService(workerConfig, `${name}-${workerName}`, absWorkerBuildPath);
414
- });
415
- }
302
+ envFileSetVariable(variableName, value);
303
+ };
416
304
 
417
- if (args.web) {
418
- addWebService();
419
- }
305
+ const environmentFileDependency = prepareEnvironmentFile();
306
+ prepareDeploymentScript();
420
307
 
421
- if (args.workers) {
422
- addWorkerServices();
423
- }
308
+ const addEnvironmentFileImageDependency = (
309
+ _args: unknown,
310
+ opts: $util.CustomResourceOptions,
311
+ _name: string,
312
+ ) => {
313
+ if (!environmentFileDependency) {
314
+ return undefined;
315
+ }
424
316
 
425
- function getDefaultPublicPorts(): Ports {
426
- let ports;
427
- const forwardPort: Port = "8080/http";
428
- const portHttp: Port = "80/http";
429
- const portHttps: Port = "443/https";
430
-
431
- if (args.web?.domain) {
432
- ports = [
433
- { listen: portHttp, forward: forwardPort },
434
- { listen: portHttps, forward: forwardPort },
435
- ];
436
- } else {
437
- ports = [
438
- { listen: portHttp, forward: forwardPort },
439
- ];
440
- }
441
-
442
- return ports;
443
- }
317
+ opts.dependsOn = [environmentFileDependency];
444
318
 
445
- // TODO: We have to test if it works when a custom image is provided in sst.config.js
446
- function getImage(imgType: ImageType, extraArgs: object = {}) {
447
- const img = getDefaultImage(imgType, extraArgs);
319
+ return undefined;
320
+ };
448
321
 
449
- const context = typeof img === 'string'
450
- ? sitePath.toString()
451
- : (img as { context: string }).context.toString();
322
+ const cluster = new sst.aws.Cluster(`${name}-Cluster`, {
323
+ vpc: normalizeClusterVpc(args.vpc),
324
+ });
452
325
 
453
- const dockerfile = typeof img === 'string'
454
- ? 'Dockerfile'
455
- : (img as { dockerfile: string }).dockerfile;
326
+ const addWebService = () => {
327
+ const envVariables = getEnvironmentVariables();
328
+
329
+ this.services['web'] = new sst.aws.Service(
330
+ `${name}-Web`,
331
+ {
332
+ cluster,
333
+ link: getLinks(),
334
+ permissions: args.permissions,
335
+
336
+ /**
337
+ * Image passed or use our default provided image.
338
+ */
339
+ image: getImage(ImageType.Web),
340
+ environment: envVariables,
341
+ scaling: args.web?.scaling,
342
+
343
+ loadBalancer:
344
+ args.web && args.web.loadBalancer
345
+ ? args.web.loadBalancer
346
+ : {
347
+ domain: args.web?.domain,
348
+ ports: getDefaultPublicPorts(),
349
+ },
350
+
351
+ dev: {
352
+ command: `php ${sitePath}/artisan serve`,
353
+ },
354
+
355
+ transform: {
356
+ image: addEnvironmentFileImageDependency,
357
+ taskDefinition: (args) => {
358
+ args.containerDefinitions = (
359
+ args.containerDefinitions as $util.Output<string>
360
+ ).apply((a) => {
361
+ return JSON.stringify([
362
+ {
363
+ ...JSON.parse(a)[0],
364
+ linuxParameters: {
365
+ initProcessEnabled: false,
366
+ },
367
+ },
368
+ ]);
369
+ });
370
+ },
371
+ },
372
+ },
373
+ {
374
+ dependsOn: environmentFileDependency
375
+ ? [environmentFileDependency]
376
+ : [],
377
+ },
378
+ );
379
+ };
456
380
 
457
- // add .sst/laravel to .dockerignore if not exist
458
- const dockerIgnore = (() => {
459
- let filePath = path.join(context, `${dockerfile}.dockerignore`);
460
- if (fs.existsSync(filePath)) return filePath;
381
+ function createWorkerTasks(
382
+ workerConfig: LaravelWorkerConfig,
383
+ workerBuildPath: string,
384
+ ) {
385
+ const s6RcDPath = path.resolve(
386
+ workerBuildPath,
387
+ 'etc/s6-overlay/s6-rc.d',
388
+ );
389
+ const s6UserContentsPath = path.resolve(
390
+ s6RcDPath,
391
+ 'user/contents.d',
392
+ );
393
+
394
+ fs.mkdirSync(s6UserContentsPath, { recursive: true });
395
+
396
+ const tasks: Record<
397
+ string,
398
+ { command: string; dependencies?: string[] }
399
+ > = {
400
+ ...((workerConfig.tasks as any) ?? {}),
401
+ };
402
+
403
+ if (workerConfig.horizon) {
404
+ tasks['laravel-horizon'] = {
405
+ command: 'php artisan horizon',
406
+ };
407
+ }
408
+
409
+ if (workerConfig.scheduler) {
410
+ tasks['laravel-scheduler'] = {
411
+ command: 'php artisan schedule:work',
412
+ };
413
+ }
414
+
415
+ Object.entries(tasks).forEach(([taskName, config]) => {
416
+ const tasksDir = path.resolve(s6RcDPath, `${taskName}`);
417
+ fs.mkdirSync(tasksDir, { recursive: true });
418
+
419
+ const scriptSrcPath = path.join(tasksDir, 'script');
420
+
421
+ fs.writeFileSync(
422
+ scriptSrcPath,
423
+ `#!/command/with-contenv bash\ncd /var/www/html\n${config.command}`,
424
+ { mode: 0o777 },
425
+ );
426
+ fs.writeFileSync(
427
+ path.join(tasksDir, 'run'),
428
+ `#!/command/execlineb -P\n/etc/s6-overlay/s6-rc.d/${taskName}/script`,
429
+ { mode: 0o777 },
430
+ );
431
+ fs.writeFileSync(path.join(tasksDir, 'type'), 'longrun');
432
+ fs.writeFileSync(
433
+ path.join(tasksDir, 'dependencies'),
434
+ (config.dependencies || []).join('\n'),
435
+ );
436
+ fs.writeFileSync(path.join(s6UserContentsPath, taskName), '');
437
+ });
438
+ }
461
439
 
462
- filePath = path.join(context, ".dockerignore");
463
- if (fs.existsSync(filePath)) return filePath;
464
- })();
440
+ const createWorkerService = (
441
+ workerConfig: LaravelWorkerConfig,
442
+ serviceName: string,
443
+ workerBuildPath: string,
444
+ ) => {
445
+ createWorkerTasks(workerConfig, workerBuildPath);
446
+
447
+ const imgBuildArgs = {
448
+ CONF_PATH: path
449
+ .resolve(nodeModulePath, 'conf')
450
+ .replace(absSitePath, ''),
451
+ CUSTOM_CONF_PATH: workerBuildPath.replace(absSitePath, ''),
452
+ };
453
+
454
+ this.services[serviceName] = new sst.aws.Service(
455
+ serviceName,
456
+ {
457
+ cluster,
458
+ link: getLinks(),
459
+ permissions: args.permissions,
460
+
461
+ image: getImage(ImageType.Worker, imgBuildArgs),
462
+ scaling: workerConfig.scaling,
463
+ environment: getEnvironmentVariables(),
464
+
465
+ dev: {
466
+ command: `php ${sitePath}/artisan horizon`,
467
+ },
468
+
469
+ transform: {
470
+ image: addEnvironmentFileImageDependency,
471
+ taskDefinition: (args) => {
472
+ args.containerDefinitions = (
473
+ args.containerDefinitions as $util.Output<string>
474
+ ).apply((a) => {
475
+ return JSON.stringify([
476
+ {
477
+ ...JSON.parse(a)[0],
478
+ linuxParameters: {
479
+ initProcessEnabled: false,
480
+ },
481
+ },
482
+ ]);
483
+ });
484
+ },
485
+ },
486
+ },
487
+ {
488
+ dependsOn: environmentFileDependency
489
+ ? [environmentFileDependency]
490
+ : [],
491
+ },
492
+ );
493
+ };
465
494
 
466
- const content = dockerIgnore ? fs.readFileSync(dockerIgnore).toString() : "";
495
+ function addWorkerServices() {
496
+ args.workers?.forEach((workerConfig, index) => {
497
+ const workerName = workerConfig.name || `worker-${index + 1}`;
498
+ const absWorkerBuildPath = path.resolve(
499
+ pluginBuildPath,
500
+ `worker-${workerName}`,
501
+ );
502
+
503
+ createWorkerService(
504
+ workerConfig,
505
+ `${name}-${workerName}`,
506
+ absWorkerBuildPath,
507
+ );
508
+ });
509
+ }
467
510
 
468
- const lines = content.split("\n");
511
+ if (args.web) {
512
+ addWebService();
513
+ }
469
514
 
470
- // SST adds it later, so we need to add it here to ensure .sst/laravel is after it and is not ignored
471
- if (dockerIgnore) {
472
- if (!lines.find((line) => line === ".sst")) {
473
- fs.writeFileSync(
474
- dockerIgnore,
475
- [...lines, "", "# sst", "!.sst/laravel"].join("\n"),
476
- );
515
+ if (args.workers) {
516
+ addWorkerServices();
477
517
  }
478
518
 
479
- if (!lines.find((line) => line === "!.sst/laravel")) {
480
- fs.writeFileSync(
481
- dockerIgnore,
482
- [...lines, "", "# sst-laravel", "!.sst/laravel"].join("\n"),
483
- );
519
+ function normalizeClusterVpc(
520
+ vpc: LaravelArgs['vpc'],
521
+ ): LaravelArgs['vpc'] {
522
+ if (
523
+ !vpc ||
524
+ typeof vpc !== 'object' ||
525
+ !('publicSubnets' in vpc) ||
526
+ !('nodes' in vpc)
527
+ ) {
528
+ return vpc;
529
+ }
530
+
531
+ const cloudmapNamespace = vpc.nodes?.cloudmapNamespace;
532
+
533
+ if (!cloudmapNamespace) {
534
+ return vpc;
535
+ }
536
+
537
+ return {
538
+ id: vpc.id,
539
+ securityGroups: vpc.securityGroups,
540
+ containerSubnets: vpc.publicSubnets,
541
+ loadBalancerSubnets: vpc.publicSubnets,
542
+ cloudmapNamespaceId: cloudmapNamespace.id,
543
+ cloudmapNamespaceName: cloudmapNamespace.name,
544
+ };
484
545
  }
485
- }
486
546
 
487
- return img;
488
- }
547
+ function getDefaultPublicPorts(): Ports {
548
+ let ports;
549
+ const forwardPort: Port = '8080/http';
550
+ const portHttp: Port = '80/http';
551
+ const portHttps: Port = '443/https';
552
+
553
+ if (args.web?.domain) {
554
+ ports = [
555
+ { listen: portHttp, forward: forwardPort },
556
+ { listen: portHttps, forward: forwardPort },
557
+ ];
558
+ } else {
559
+ ports = [{ listen: portHttp, forward: forwardPort }];
560
+ }
561
+
562
+ return ports;
563
+ }
489
564
 
490
- function getDefaultImage(imageType: ImageType, extraArgs: object = {}) {
491
- return {
492
- context: sitePath,
493
- dockerfile: path.resolve(nodeModulePath, `Dockerfile.${imageType}`).replace(absSitePath, '.'),
494
- args: {
495
- 'PHP_VERSION': getPhpVersion().toString(),
496
- 'PHP_OPCACHE_ENABLE': args.config?.opcache? '1' : '0',
497
- 'AUTORUN_LARAVEL_MIGRATION': imageType === ImageType.Web ? 'true' : 'false',
498
- 'CONTAINER_TYPE': imageType,
499
- stage: "deploy",
500
- platform: "linux/amd64",
501
- ...extraArgs
502
- },
503
- };
504
- };
565
+ // TODO: We have to test if it works when a custom image is provided in sst.config.js
566
+ function getImage(imgType: ImageType, extraArgs: object = {}) {
567
+ const img = getDefaultImage(imgType, extraArgs);
568
+
569
+ const context =
570
+ typeof img === 'string'
571
+ ? sitePath.toString()
572
+ : (img as { context: string }).context.toString();
573
+
574
+ const dockerfile =
575
+ typeof img === 'string'
576
+ ? 'Dockerfile'
577
+ : (img as { dockerfile: string }).dockerfile;
578
+
579
+ // add .sst/laravel to .dockerignore if not exist
580
+ const dockerIgnore = (() => {
581
+ let filePath = path.join(context, `${dockerfile}.dockerignore`);
582
+ if (fs.existsSync(filePath)) return filePath;
583
+
584
+ return path.join(context, '.dockerignore');
585
+ })();
586
+
587
+ const content = fs.existsSync(dockerIgnore)
588
+ ? fs.readFileSync(dockerIgnore).toString()
589
+ : '';
590
+
591
+ const lines = content.split('\n');
592
+
593
+ const normalizedLines = [
594
+ ...lines.filter(
595
+ (line) =>
596
+ line !== '.sst' &&
597
+ line !== '!.sst/laravel' &&
598
+ line !== '# sst' &&
599
+ line !== '# sst-laravel',
600
+ ),
601
+ '',
602
+ '# sst',
603
+ '.sst',
604
+ '',
605
+ '# sst-laravel',
606
+ '!.sst/laravel',
607
+ ];
608
+
609
+ if (normalizedLines.join('\n') !== lines.join('\n')) {
610
+ fs.writeFileSync(dockerIgnore, normalizedLines.join('\n'));
611
+ }
612
+
613
+ return img;
614
+ }
505
615
 
506
- function getPhpVersion() {
507
- return args.config?.php ?? 8.4;
508
- }
616
+ function getDefaultImage(imageType: ImageType, extraArgs: object = {}) {
617
+ return {
618
+ context: sitePath,
619
+ dockerfile: path
620
+ .resolve(nodeModulePath, `Dockerfile.${imageType}`)
621
+ .replace(absSitePath, '.'),
622
+ args: {
623
+ PHP_VERSION: getPhpVersion().toString(),
624
+ PHP_OPCACHE_ENABLE: args.config?.opcache ? '1' : '0',
625
+ AUTORUN_LARAVEL_MIGRATION:
626
+ imageType === ImageType.Web ? 'true' : 'false',
627
+ CONTAINER_TYPE: imageType,
628
+ stage: 'deploy',
629
+ platform: 'linux/amd64',
630
+ ...extraArgs,
631
+ },
632
+ };
633
+ }
509
634
 
510
- function getEnvironmentVariables() {
511
- const env = args.config?.environment?.vars || {};
635
+ function getPhpVersion() {
636
+ return args.config?.php ?? 8.4;
637
+ }
512
638
 
513
- return env;
514
- }
639
+ function getEnvironmentVariables() {
640
+ const env = args.config?.environment?.vars || {};
515
641
 
516
- function getLinkedEnvironmentData() {
517
- const links = (args.link || []);
518
- const resources: any[] = [];
519
- const customEnv: Record<string, string | Output<string>> = {};
520
-
521
- links.forEach(link => {
522
- if (link && typeof link === 'object' && 'resource' in link) {
523
- // Link is an object with resource and optional envCallback
524
- resources.push(link.resource);
525
-
526
- // If there's an envCallback, call it and merge the result
527
- const callback = (link as { environment?: EnvCallback; envCallback?: EnvCallback }).environment
528
- || (link as { environment?: EnvCallback; envCallback?: EnvCallback }).envCallback;
529
- if (callback) {
530
- const callbackResult = callback(link.resource);
531
- Object.assign(customEnv, callbackResult);
532
- }
533
- } else {
534
- // Link is just a resource
535
- resources.push(link);
642
+ return env;
536
643
  }
537
- });
538
-
539
- return {
540
- linkedEnvironment: {
541
- ...applyLinkedResourcesEnv(resources),
542
- ...customEnv,
543
- },
544
- linkedSecrets: extractSecrets(resources).map(secret => ({
545
- name: secret.name,
546
- value: secret.value,
547
- })),
548
- };
549
- }
550
644
 
551
- function applyLinkedResourcesToEnvironment() {
552
- const { linkedEnvironment, linkedSecrets } = getLinkedEnvironmentData();
645
+ function getLinkedEnvironmentData() {
646
+ const links = args.link || [];
647
+ const resources: any[] = [];
648
+ const customEnv: Record<string, string | Output<string>> = {};
649
+
650
+ links.forEach((link) => {
651
+ if (link && typeof link === 'object' && 'resource' in link) {
652
+ // Link is an object with resource and optional envCallback
653
+ resources.push(link.resource);
654
+
655
+ // If there's an envCallback, call it and merge the result
656
+ const callback =
657
+ (
658
+ link as {
659
+ environment?: EnvCallback;
660
+ envCallback?: EnvCallback;
661
+ }
662
+ ).environment ||
663
+ (
664
+ link as {
665
+ environment?: EnvCallback;
666
+ envCallback?: EnvCallback;
667
+ }
668
+ ).envCallback;
669
+ if (callback) {
670
+ const callbackResult = callback(link.resource);
671
+ Object.assign(customEnv, callbackResult);
672
+ }
673
+ } else {
674
+ // Link is just a resource
675
+ resources.push(link);
676
+ }
677
+ });
678
+
679
+ return {
680
+ linkedEnvironment: {
681
+ ...applyLinkedResourcesEnv(resources),
682
+ ...customEnv,
683
+ },
684
+ linkedSecrets: extractSecrets(resources).map((secret) => ({
685
+ name: secret.name,
686
+ value: secret.value,
687
+ })),
688
+ };
689
+ }
553
690
 
554
- // Apply default environment variables for all resources
555
- if (!args.config) args.config = {};
556
- if (!args.config.environment) args.config.environment = {};
691
+ function applyLinkedResourcesToEnvironment() {
692
+ const { linkedEnvironment, linkedSecrets } =
693
+ getLinkedEnvironmentData();
557
694
 
558
- fs.appendFileSync(envFilePath, '\n' + "# --- SST-LARAVEL AUTO-INJECTED VARIABLES ---" + '\n');
695
+ // Apply default environment variables for all resources
696
+ if (!args.config) args.config = {};
697
+ if (!args.config.environment) args.config.environment = {};
559
698
 
560
- addAppUrlIfMissing();
561
- envFileSetVariableIfMissing('LOG_CHANNEL', 'stderr');
699
+ fs.appendFileSync(
700
+ envFilePath,
701
+ '\n' + '# --- SST-LARAVEL AUTO-INJECTED VARIABLES ---' + '\n',
702
+ );
562
703
 
563
- all(Object.entries(linkedEnvironment)).apply(entries => {
564
- const envContent = entries
565
- .map(([key, value]) => `${key}=${value}`)
566
- .join('\n');
704
+ addAppUrlIfMissing();
705
+ envFileSetVariableIfMissing('LOG_CHANNEL', 'stderr');
567
706
 
568
- if (envContent) {
569
- fs.appendFileSync(envFilePath, '\n' + envContent);
570
- }
571
- });
707
+ all(Object.entries(linkedEnvironment)).apply((entries) => {
708
+ const envContent = entries
709
+ .map(([key, value]) => `${key}=${value}`)
710
+ .join('\n');
572
711
 
573
- linkedSecrets.forEach(secret => {
574
- all([secret.name, secret.value]).apply(([name, value]) => {
575
- fs.appendFileSync(envFilePath, `\n${name}=${value}`);
576
- });
577
- });
578
- };
712
+ if (envContent) {
713
+ fs.appendFileSync(envFilePath, '\n' + envContent);
714
+ }
715
+ });
579
716
 
580
- /**
581
- * Return the links as an array of resources in the original SST format.
582
- */
583
- function getLinks(): any[] {
584
- return (args.link || []).map(link => {
585
- if (link && typeof link === 'object' && 'resource' in link) {
586
- return link.resource;
717
+ linkedSecrets.forEach((secret) => {
718
+ all([secret.name, secret.value]).apply(([name, value]) => {
719
+ fs.appendFileSync(envFilePath, `\n${name}=${value}`);
720
+ });
721
+ });
587
722
  }
588
723
 
589
- return link;
590
- });
591
- }
724
+ /**
725
+ * Return the links as an array of resources in the original SST format.
726
+ */
727
+ function getLinks(): any[] {
728
+ return (args.link || []).map((link) => {
729
+ if (link && typeof link === 'object' && 'resource' in link) {
730
+ return link.resource;
731
+ }
592
732
 
593
- function prepareEnvironmentFile() {
594
- const envFile = args.config?.environment?.file as string | undefined;
595
- const secrets = args.config?.environment?.secrets;
733
+ return link;
734
+ });
735
+ }
596
736
 
597
- if (secrets) {
598
- return prepareRemoteEnvironmentFile(secrets);
599
- }
737
+ function prepareEnvironmentFile() {
738
+ const envFile = args.config?.environment?.file as
739
+ | string
740
+ | undefined;
741
+ const secrets = args.config?.environment?.secrets;
742
+
743
+ if (secrets) {
744
+ return prepareRemoteEnvironmentFile(secrets);
745
+ }
746
+
747
+ // Handle traditional env file configuration
748
+ if (!envFile) {
749
+ return;
750
+ }
751
+
752
+ const src = path.resolve(absSitePath, envFile);
753
+
754
+ if (fs.existsSync(src)) {
755
+ fs.copyFileSync(src, envFilePath);
756
+ fs.chmodSync(envFilePath, 0o755);
757
+ } else {
758
+ fs.writeFileSync(envFilePath, '');
759
+ }
760
+
761
+ if (args.config?.environment?.autoInject !== false) {
762
+ applyLinkedResourcesToEnvironment();
763
+ }
764
+ }
600
765
 
601
- // Handle traditional env file configuration
602
- if (!envFile) {
603
- return;
604
- }
766
+ function prepareRemoteEnvironmentFile(secrets: RemoteEnvVault) {
767
+ if (runtime.isDryRun() && !fs.existsSync(envFilePath)) {
768
+ fs.writeFileSync(
769
+ envFilePath,
770
+ '# WARNING: RemoteEnvVault secrets are loaded during deployment. Preview uses a placeholder file.\n',
771
+ );
772
+ fs.chmodSync(envFilePath, 0o755);
773
+ }
774
+
775
+ const { linkedEnvironment, linkedSecrets } =
776
+ getLinkedEnvironmentData();
777
+
778
+ return new RemoteEnvFile(
779
+ `${name}-RemoteEnv`,
780
+ {
781
+ secretPath: secrets.path,
782
+ envFilePath,
783
+ fingerprint: output(secrets.path).apply((secretPath) =>
784
+ getSecretsFingerprint(secretPath),
785
+ ),
786
+ autoInject: args.config?.environment?.autoInject !== false,
787
+ appUrl: getAppUrl(),
788
+ linkedEnvironment,
789
+ linkedSecrets,
790
+ },
791
+ {
792
+ parent: this,
793
+ },
794
+ );
795
+ }
605
796
 
606
- const src = path.resolve(absSitePath, envFile);
797
+ function addAppUrlIfMissing() {
798
+ if (envFileHasVariable('APP_URL')) {
799
+ return;
800
+ }
607
801
 
608
- if (fs.existsSync(src)) {
609
- fs.copyFileSync(src, envFilePath);
610
- fs.chmodSync(envFilePath, 0o755);
611
- } else {
612
- fs.writeFileSync(envFilePath, '');
613
- }
802
+ const appUrl = getAppUrl();
614
803
 
615
- if (args.config?.environment?.autoInject !== false) {
616
- applyLinkedResourcesToEnvironment();
617
- }
618
- }
804
+ if (typeof appUrl === 'string') {
805
+ envFileSetVariable('APP_URL', appUrl);
806
+ }
807
+ }
619
808
 
620
- function prepareRemoteEnvironmentFile(secrets: RemoteEnvVault) {
621
- fs.writeFileSync(envFilePath, '# WARNING: RemoteEnvVault secrets are loaded during deployment. Preview uses a placeholder file.\n');
622
- fs.chmodSync(envFilePath, 0o755);
623
-
624
- if ($cli.command !== 'deploy') {
625
- return;
626
- }
627
-
628
- const { linkedEnvironment, linkedSecrets } = getLinkedEnvironmentData();
629
-
630
- return new RemoteEnvFile(`${name}-RemoteEnv`, {
631
- secretPath: secrets.path,
632
- envFilePath,
633
- fingerprint: output(secrets.path).apply((secretPath) => getSecretsFingerprint(secretPath)),
634
- autoInject: args.config?.environment?.autoInject !== false,
635
- appUrl: getAppUrl(),
636
- linkedEnvironment,
637
- linkedSecrets,
638
- }, {
639
- parent: this,
640
- });
641
- }
809
+ function getAppUrl(): PulumiInput<string | undefined> | undefined {
810
+ if (!args.web?.domain) {
811
+ return undefined;
812
+ }
813
+
814
+ if (typeof args.web.domain === 'string') {
815
+ return `https://${args.web.domain}`;
816
+ }
817
+
818
+ if (
819
+ typeof args.web.domain === 'object' &&
820
+ 'name' in args.web.domain
821
+ ) {
822
+ return output(
823
+ (args.web.domain as { name: Input<string> }).name,
824
+ ).apply((domainName) =>
825
+ domainName ? `https://${domainName}` : undefined,
826
+ );
827
+ }
828
+
829
+ return undefined;
830
+ }
642
831
 
643
- function addAppUrlIfMissing() {
644
- if (envFileHasVariable('APP_URL')) {
645
- return;
646
- }
832
+ function prepareDeploymentScript() {
833
+ const deployDir = path.resolve(pluginBuildPath, 'deploy');
834
+ const dst = path.resolve(deployDir, '60-deploy.sh');
835
+
836
+ fs.mkdirSync(deployDir, { recursive: true });
837
+
838
+ const script = args.config?.deployment?.script as
839
+ | string
840
+ | undefined;
841
+ if (script) {
842
+ const src = path.resolve(absSitePath, script);
843
+ if (fs.existsSync(src)) {
844
+ fs.copyFileSync(src, dst);
845
+ fs.chmodSync(dst, 0o755);
846
+ return;
847
+ }
848
+ }
647
849
 
648
- const appUrl = getAppUrl();
850
+ fs.writeFileSync(dst, '#!/bin/sh\nexit 0\n');
851
+ fs.chmodSync(dst, 0o755);
852
+ }
649
853
 
650
- if (typeof appUrl === 'string') {
651
- envFileSetVariable('APP_URL', appUrl);
652
- }
854
+ this.registerOutputs({ _hint: this.messages });
653
855
  }
654
856
 
655
- function getAppUrl(): PulumiInput<string | undefined> | undefined {
656
- if (!args.web?.domain) {
657
- return undefined;
658
- }
659
-
660
- if (typeof args.web.domain === 'string') {
661
- return `https://${args.web.domain}`;
662
- }
663
-
664
- if (typeof args.web.domain === 'object' && 'name' in args.web.domain) {
665
- return output((args.web.domain as { name: Input<string> }).name)
666
- .apply(domainName => domainName ? `https://${domainName}` : undefined);
667
- }
668
-
669
- return undefined;
857
+ /**
858
+ * The URL of the service.
859
+ *
860
+ * If `public.domain` is set, this is the URL with the custom domain.
861
+ * Otherwise, it's the auto-generated load balancer URL.
862
+ */
863
+ public get url() {
864
+ return this.services['web'].url;
670
865
  }
671
866
 
672
- function prepareDeploymentScript() {
673
- const deployDir = path.resolve(pluginBuildPath, 'deploy');
674
- const dst = path.resolve(deployDir, '60-deploy.sh');
675
-
676
- fs.mkdirSync(deployDir, { recursive: true });
677
-
678
- const script = args.config?.deployment?.script as string | undefined;
679
- if (script) {
680
- const src = path.resolve(absSitePath, script);
681
- if (fs.existsSync(src)) {
682
- fs.copyFileSync(src, dst);
683
- fs.chmodSync(dst, 0o755);
684
- return;
685
- }
686
- }
687
-
688
- fs.writeFileSync(dst, "#!/bin/sh\nexit 0\n");
689
- fs.chmodSync(dst, 0o755);
867
+ /**
868
+ * The messages from the service.
869
+ *
870
+ * This is useful for debugging and troubleshooting.
871
+ */
872
+ public get messages() {
873
+ return this._messages;
690
874
  }
691
-
692
- this.registerOutputs({ _hint: this.messages });
693
- };
694
-
695
- /**
696
- * The URL of the service.
697
- *
698
- * If `public.domain` is set, this is the URL with the custom domain.
699
- * Otherwise, it's the auto-generated load balancer URL.
700
- */
701
- public get url() {
702
- return this.services['web'].url;
703
- }
704
-
705
- /**
706
- * The messages from the service.
707
- *
708
- * This is useful for debugging and troubleshooting.
709
- */
710
- public get messages() {
711
- return this._messages;
712
- }
713
875
  }
714
876
 
715
- const __pulumiType = "sst:aws:LaravelService";
877
+ const __pulumiType = 'sst:aws:LaravelService';
716
878
  // @ts-expect-error
717
879
  LaravelService.__pulumiType = __pulumiType;