@kirschbaum-development/sst-laravel 0.2.10 → 0.2.12

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