@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/dist/bin/commands/deploy.js +1 -43
- package/dist/bin/commands/deploy.js.map +1 -1
- package/dist/bin/commands/init.js +1 -1
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/utils/secrets-manager.d.ts +4 -0
- package/dist/bin/utils/secrets-manager.js +21 -0
- package/dist/bin/utils/secrets-manager.js.map +1 -1
- package/laravel-sst.ts +759 -587
- package/package.json +1 -1
- package/src/laravel-env-manager.ts +1 -1
- package/src/remote-env-file.ts +235 -0
- package/src/secrets-manager.ts +544 -0
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
|
|
6
|
-
import { FunctionArgs } from
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import {
|
|
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}/${
|
|
33
|
+
type Port = `${number}/${'http' | 'https' | 'tcp' | 'udp' | 'tcp_udp' | 'tls'}`;
|
|
21
34
|
|
|
22
35
|
type Ports = {
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
listen: Port;
|
|
37
|
+
forward: Port;
|
|
25
38
|
}[];
|
|
26
39
|
|
|
27
40
|
enum ImageType {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
Web = 'web',
|
|
42
|
+
Worker = 'worker',
|
|
43
|
+
Cli = 'cli',
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
export interface LaravelServiceArgs {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
113
|
+
horizon?: Input<boolean>;
|
|
155
114
|
|
|
156
115
|
/**
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* @default `true`
|
|
116
|
+
* Running scheduler?
|
|
160
117
|
*/
|
|
161
|
-
|
|
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
|
-
*
|
|
121
|
+
* Multiple tasks can be run in the worker.
|
|
225
122
|
*/
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
142
|
+
permissions?: Array<{
|
|
143
|
+
actions: string[];
|
|
144
|
+
resources: string[];
|
|
145
|
+
}>;
|
|
259
146
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
147
|
+
/**
|
|
148
|
+
* If enabled, a container will be created to handle HTTP traffic.
|
|
149
|
+
*/
|
|
150
|
+
web?: LaravelWebArgs;
|
|
263
151
|
|
|
264
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Multiple workers settings.
|
|
154
|
+
*/
|
|
155
|
+
workers?: LaravelWorkerConfig[];
|
|
265
156
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
282
|
-
|
|
247
|
+
export class LaravelService extends Component {
|
|
248
|
+
private readonly services: Record<string, sst.aws.Service>;
|
|
249
|
+
private readonly _messages: string[] = [];
|
|
283
250
|
|
|
284
|
-
|
|
285
|
-
|
|
251
|
+
constructor(
|
|
252
|
+
name: string,
|
|
253
|
+
args: LaravelArgs,
|
|
254
|
+
opts: ComponentResourceOptions = {},
|
|
255
|
+
) {
|
|
256
|
+
super(__pulumiType, name, args, opts);
|
|
286
257
|
|
|
287
|
-
|
|
288
|
-
vpc: args.vpc
|
|
289
|
-
});
|
|
258
|
+
this.services = {};
|
|
290
259
|
|
|
291
|
-
|
|
292
|
-
|
|
260
|
+
args.config = args.config ?? {};
|
|
261
|
+
const sitePath = args.path ?? '.';
|
|
262
|
+
const absSitePath = path.resolve(sitePath.toString());
|
|
263
|
+
const nodeModulePath = getPackagePath();
|
|
293
264
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
273
|
+
if (!fs.existsSync(pluginBuildPath + '/deploy')) {
|
|
274
|
+
fs.mkdirSync(pluginBuildPath + '/deploy', { recursive: true });
|
|
275
|
+
}
|
|
333
276
|
|
|
334
|
-
|
|
277
|
+
const envFilePath = path.resolve(pluginBuildPath, 'deploy', '.env');
|
|
335
278
|
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
fs.mkdirSync(tasksDir, { recursive: true });
|
|
304
|
+
const environmentFileDependency = prepareEnvironmentFile();
|
|
305
|
+
prepareDeploymentScript();
|
|
355
306
|
|
|
356
|
-
const
|
|
307
|
+
const cluster = new sst.aws.Cluster(`${name}-Cluster`, {
|
|
308
|
+
vpc: normalizeClusterVpc(args.vpc),
|
|
309
|
+
});
|
|
357
310
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
494
|
+
if (args.web) {
|
|
495
|
+
addWebService();
|
|
496
|
+
}
|
|
416
497
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
498
|
+
if (args.workers) {
|
|
499
|
+
addWorkerServices();
|
|
500
|
+
}
|
|
420
501
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
let filePath = path.join(context, `${dockerfile}.dockerignore`);
|
|
456
|
-
if (fs.existsSync(filePath)) return filePath;
|
|
596
|
+
return img;
|
|
597
|
+
}
|
|
457
598
|
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
618
|
+
function getPhpVersion() {
|
|
619
|
+
return args.config?.php ?? 8.4;
|
|
620
|
+
}
|
|
463
621
|
|
|
464
|
-
|
|
622
|
+
function getEnvironmentVariables() {
|
|
623
|
+
const env = args.config?.environment?.vars || {};
|
|
465
624
|
|
|
466
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
484
|
-
|
|
674
|
+
function applyLinkedResourcesToEnvironment() {
|
|
675
|
+
const { linkedEnvironment, linkedSecrets } =
|
|
676
|
+
getLinkedEnvironmentData();
|
|
485
677
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
503
|
-
|
|
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
|
-
|
|
510
|
-
|
|
687
|
+
addAppUrlIfMissing();
|
|
688
|
+
envFileSetVariableIfMissing('LOG_CHANNEL', 'stderr');
|
|
511
689
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
690
|
+
all(Object.entries(linkedEnvironment)).apply((entries) => {
|
|
691
|
+
const envContent = entries
|
|
692
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
693
|
+
.join('\n');
|
|
516
694
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
695
|
+
if (envContent) {
|
|
696
|
+
fs.appendFileSync(envFilePath, '\n' + envContent);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
521
699
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
553
|
-
|
|
716
|
+
return link;
|
|
717
|
+
});
|
|
554
718
|
}
|
|
555
|
-
});
|
|
556
719
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
-
|
|
575
|
-
|
|
576
|
-
|
|
782
|
+
function addAppUrlIfMissing() {
|
|
783
|
+
if (envFileHasVariable('APP_URL')) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
577
786
|
|
|
578
|
-
|
|
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
|
-
|
|
597
|
-
|
|
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
|
-
|
|
628
|
-
|
|
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
|
-
|
|
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
|
-
|
|
634
|
-
|
|
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
|
-
|
|
643
|
-
envFileSetVariable('APP_URL', `https://${domainName}`);
|
|
644
|
-
}
|
|
839
|
+
this.registerOutputs({ _hint: this.messages });
|
|
645
840
|
}
|
|
646
841
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
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 =
|
|
862
|
+
const __pulumiType = 'sst:aws:LaravelService';
|
|
691
863
|
// @ts-expect-error
|
|
692
864
|
LaravelService.__pulumiType = __pulumiType;
|