@kirschbaum-development/sst-laravel 0.1.6 → 0.2.7

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/README.md CHANGED
@@ -38,6 +38,21 @@ To get started quickly, you can use the `init` command:
38
38
  npx sst-laravel init
39
39
  ```
40
40
 
41
+ Running `init` now also prompts you to install the SST Laravel Initial Setup AI skill. Accepting the prompt will automatically detect whether `laravel/boost` ≥ 2.0 is available via Composer; if so, the skill is copied into `.ai/skills/sst-laravel-initial-setup/SKILL.md` and `php artisan boost:update` is executed. Otherwise, the command falls back to `npx skills add` with the bundled skill file.
42
+
43
+ ## AI Skill for Guided Setup
44
+
45
+ Projects that rely on AI copilots (like OpenCode) can import the `skills/laravel-initial-setup/SKILL.md` file from this package. The skill walks an assistant through:
46
+
47
+ - Auditing prerequisites (Node, AWS CLI, credentials, `sst-laravel` CLI)
48
+ - Bootstrapping your repo by running `npx sst-laravel init` before any config changes
49
+ - Choosing the right environment strategy (`RemoteEnvVault`, SST Secrets, or `.env` files)
50
+ - Inspecting/creating VPC resources through the AWS CLI
51
+ - Iteratively editing `sst.config.ts` until your Laravel service is deployable
52
+ - Producing clear summaries after every step plus follow-up tasks and cautions
53
+
54
+ Point your assistant at that file to get a prescriptive, secure onboarding workflow tailored for SST Laravel.
55
+
41
56
  ## Usage
42
57
 
43
58
  To start using, you only need to import the component in your `sst.config.ts` file:
@@ -178,6 +193,90 @@ const app = new LaravelService('MyLaravelApp', {
178
193
 
179
194
  This will automatically inject the environment variables into the `.env` file of your Laravel application. Read more about SST Secrets [here](https://sst.dev/docs/component/secret/).
180
195
 
196
+ ### AWS Secrets Manager (RemoteEnvVault)
197
+
198
+ For a more robust environment variable management solution similar to Laravel Vapor, you can use the `RemoteEnvVault` component. This stores your environment variables in AWS Secrets Manager and provides CLI commands to push and pull secrets.
199
+
200
+ ```js
201
+ import { RemoteEnvVault, LaravelService } from "@kirschbaum-development/sst-laravel";
202
+
203
+ const env = new RemoteEnvVault("Env");
204
+ const app = new LaravelService('MyLaravelApp', {
205
+ // ...
206
+ config: {
207
+ environment: {
208
+ secrets: env,
209
+ }
210
+ }
211
+ });
212
+ ```
213
+
214
+ The secrets are stored in AWS Secrets Manager at the path `/{app-name}/{stage}/env`.
215
+
216
+ #### Large Environment Files
217
+
218
+ Large environment files that exceed AWS Secrets Manager's 64KB limit are automatically handled. The CLI will:
219
+ - Split large `.env` files into multiple chunks when pushing
220
+ - Automatically merge all chunks when pulling or deploying
221
+
222
+ This is completely transparent - you don't need to do anything special.
223
+
224
+ #### Pushing Secrets
225
+
226
+ To push your local `.env` file to AWS Secrets Manager:
227
+
228
+ ```bash
229
+ # Push .env.production to the production stage
230
+ npx sst-laravel env:push --stage production --input .env.production
231
+
232
+ # Push .env to staging (interactive)
233
+ npx sst-laravel env:push --stage staging
234
+ ```
235
+
236
+ #### Pulling Secrets
237
+
238
+ To pull secrets from AWS Secrets Manager to a local file:
239
+
240
+ ```bash
241
+ # Pull from production to .env.production (default)
242
+ npx sst-laravel env:pull --stage production
243
+
244
+ # Pull from staging to a custom file
245
+ npx sst-laravel env:pull --stage staging --output .env.local
246
+ ```
247
+
248
+ #### Deploying with Secrets
249
+
250
+ When using `RemoteEnvVault`, deploy using the `sst-laravel deploy` command which automatically fetches secrets before building:
251
+
252
+ ```bash
253
+ npx sst-laravel deploy --stage production
254
+ ```
255
+
256
+ #### Workflow Example
257
+
258
+ ```bash
259
+ # 1. Initial setup - push your environment file
260
+ npx sst-laravel env:push --stage production --input .env.production
261
+
262
+ # 2. Deploy (secrets are automatically fetched)
263
+ npx sst-laravel deploy --stage production
264
+
265
+ # 3. Update secrets later
266
+ npx sst-laravel env:pull --stage production # Creates .env.production
267
+ # Edit .env.production
268
+ npx sst-laravel env:push --stage production --input .env.production
269
+ npx sst-laravel deploy --stage production
270
+ ```
271
+
272
+ You can also use a custom path for the secrets:
273
+
274
+ ```js
275
+ const env = new RemoteEnvVault("Env", {
276
+ path: "/custom/path/env"
277
+ });
278
+ ```
279
+
181
280
  ### Resources
182
281
 
183
282
  In SST, you can [link resources](https://sst.dev/docs/linking). If you link resources to your Laravel component, SST Laravel will automatically inject and configure environment variables using sensible defaults for all the linked resources.
@@ -283,7 +382,7 @@ php artisan migrate --force
283
382
 
284
383
  ## Deploying
285
384
 
286
- To deploy your application, you can use the `sst deploy` command. You must be authenticated with AWS in your terminal session to deploy.
385
+ To deploy your application, you can use the `sst-laravel deploy` command. You must be authenticated with AWS in your terminal session to deploy.
287
386
 
288
387
  ```bash
289
388
  npx sst-laravel deploy --stage {stage}
@@ -291,6 +390,8 @@ npx sst-laravel deploy --stage sandbox
291
390
  npx sst-laravel deploy --stage production
292
391
  ```
293
392
 
393
+ > **Note:** If you're using `RemoteEnvVault` for secrets management, you should use `sst-laravel deploy` instead of `sst deploy` directly. This ensures secrets are fetched from AWS Secrets Manager before the Docker build.
394
+
294
395
  ## Accessing Containers
295
396
 
296
397
  Using the `sst-laravel` CLI tool, you can easily connect to your running ECS containers for debugging and troubleshooting.
@@ -333,10 +434,6 @@ To send logs to AWS CloudWatch, you need to set the `LOG_CHANNEL` environment va
333
434
 
334
435
  ## Troubleshooting
335
436
 
336
- **APP_URL**
337
-
338
- In case your specified environment file does not contain the `APP_URL` variable, SST Laravel will automatically add it to the environment file with the value of the `web.domain` property.
339
-
340
437
  **Load Balancer and trusted proxies**
341
438
 
342
439
  SST Laravel puts the container behind a load balancer, so you must configure your Laravel application to trust the load balancer's IP addresses. You can do this by configuring the trusted proxies in `bootstrap/app.php`. If you deployed your app and it's trying to load assets using HTTP instead of HTTPS, this is likely the issue.
@@ -344,12 +441,6 @@ SST Laravel puts the container behind a load balancer, so you must configure you
344
441
  ```php
345
442
  ->withMiddleware(function (Middleware $middleware) {
346
443
  $middleware->trustProxies(at: '*');
347
- $middleware->trustProxies(headers: Request::HEADER_X_FORWARDED_FOR |
348
- Request::HEADER_X_FORWARDED_HOST |
349
- Request::HEADER_X_FORWARDED_PORT |
350
- Request::HEADER_X_FORWARDED_PROTO |
351
- Request::HEADER_X_FORWARDED_AWS_ELB
352
- );
353
444
  })
354
445
  ```
355
446
 
@@ -372,11 +463,15 @@ If you are getting the following error when deploying (usually via CI/CD), the i
372
463
  aws: failed to refresh cached credentials, no EC2 IMDS role found, operation error ec2imds: GetMetadata, failed to get API token, operation error ec2imds: getToken, http response error StatusCode: 400, request to EC2 IMDS failed
373
464
  ```
374
465
 
466
+ **APP_URL**
467
+
468
+ In case your specified environment file does not contain the `APP_URL` variable, SST Laravel will automatically add it to the environment file with the value of the `web.domain` property.
469
+
375
470
  ***
376
471
 
377
472
  ### Roadmap
378
473
 
379
- * Extend base Docker images;
474
+ * Ability to extend base Docker images;
380
475
  * Add support for Inertia SSR;
381
476
  * Add support for Octane with FrankedPHP;
382
477
  * Add support for Laravel Reverb;
@@ -389,7 +484,7 @@ If you discover any security related issues, please email security@kirschbaumdev
389
484
 
390
485
  ## Sponsorship
391
486
 
392
- Development of this package is developed and sponsored by Kirschbaum Development Group, a developer driven company focused on problem solving, team building, and community. Learn more [about us](https://kirschbaumdevelopment.com) or [join us](https://careers.kirschbaumdevelopment.com)!
487
+ Development of this package is sponsored by Kirschbaum Development Group, a developer driven company focused on problem solving, team building, and community. Learn more [about us](https://kirschbaumdevelopment.com) or [join us](https://careers.kirschbaumdevelopment.com)!
393
488
 
394
489
  ## License
395
490
 
package/dist/bin/cli.js CHANGED
@@ -8,6 +8,8 @@ import { sshCommand } from './commands/ssh.js';
8
8
  import { logsCommand } from './commands/logs.js';
9
9
  import { githubIamCommand } from './commands/github-iam.js';
10
10
  import { installCommand } from './commands/install.js';
11
+ import { envPullCommand } from './commands/env-pull.js';
12
+ import { envPushCommand } from './commands/env-push.js';
11
13
  import { getPackageRoot } from './utils/sst-config.js';
12
14
  const packageJsonPath = path.join(getPackageRoot(), 'package.json');
13
15
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
@@ -23,5 +25,7 @@ program.addCommand(sshCommand);
23
25
  program.addCommand(logsCommand);
24
26
  program.addCommand(githubIamCommand);
25
27
  program.addCommand(installCommand);
28
+ program.addCommand(envPullCommand);
29
+ program.addCommand(envPushCommand);
26
30
  program.parse();
27
31
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,cAAc,CAAC,CAAC;AACpE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;AAC1E,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;AAEpC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,uCAAuC,CAAC;KACpD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAEnC,OAAO,CAAC,KAAK,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../bin/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,cAAc,CAAC,CAAC;AACpE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;AAC1E,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;AAEpC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,uCAAuC,CAAC;KACpD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AAC/B,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;AAChC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AAEnC,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -1,16 +1,62 @@
1
1
  import { Command } from 'commander';
2
2
  import { spawn } from 'child_process';
3
- import { validateDeployment } from '../utils/sst-config.js';
3
+ import * as fs from 'fs';
4
+ import * as path from 'path';
5
+ import { validateDeployment, findSstConfig, extractSstProjectName, extractSecretsConfig, getPackageRoot } from '../utils/sst-config.js';
6
+ import { pullSecrets, getSecretPath, getSecretInfo, toEnvFileContent } from '../utils/secrets-manager.js';
4
7
  export const deployCommand = new Command('deploy')
5
8
  .description('Deploy the application using SST')
6
9
  .requiredOption('-s, --stage <stage>', 'SST stage name')
7
10
  .action(async (options) => {
8
11
  try {
9
12
  validateDeployment(options.stage);
13
+ const configPath = findSstConfig();
14
+ if (configPath) {
15
+ const appName = extractSstProjectName(configPath);
16
+ const secretsConfig = extractSecretsConfig(configPath);
17
+ if (appName && secretsConfig) {
18
+ console.log('RemoteEnvVault detected, fetching secrets from AWS Secrets Manager...');
19
+ // Determine the secret path
20
+ const secretPath = secretsConfig.path || getSecretPath(appName, options.stage);
21
+ try {
22
+ // Get info about the secret structure first
23
+ const secretInfo = await getSecretInfo(secretPath);
24
+ if (!secretInfo) {
25
+ console.warn(`Warning: No secrets found at ${secretPath}`);
26
+ console.log('Continuing with deployment without secrets...');
27
+ }
28
+ else {
29
+ if (secretInfo.chunked) {
30
+ console.log(`Found ${secretInfo.totalKeys} variables in ${secretInfo.chunks} chunks`);
31
+ }
32
+ const secrets = await pullSecrets(secretPath);
33
+ if (secrets) {
34
+ // Ensure the .sst/laravel/deploy directory exists
35
+ const deployDir = path.join(process.cwd(), '.sst', 'laravel', 'deploy');
36
+ fs.mkdirSync(deployDir, { recursive: true });
37
+ // Write secrets to .env file
38
+ const envFilePath = path.join(deployDir, '.env');
39
+ const envContent = toEnvFileContent(secrets);
40
+ fs.writeFileSync(envFilePath, envContent + '\n');
41
+ fs.chmodSync(envFilePath, 0o755);
42
+ console.log(`Fetched ${Object.keys(secrets).length} variables from ${secretPath}`);
43
+ }
44
+ }
45
+ }
46
+ catch (error) {
47
+ console.error(`Warning: Failed to fetch secrets from ${secretPath}:`, error.message);
48
+ console.log('Continuing with deployment without secrets...');
49
+ }
50
+ }
51
+ }
10
52
  const deployProcess = spawn('npx', ['sst', 'deploy', '--stage', options.stage], {
11
53
  cwd: process.cwd(),
12
54
  stdio: 'inherit',
13
- shell: true
55
+ shell: true,
56
+ env: {
57
+ ...process.env,
58
+ SST_LARAVEL_PACKAGE_ROOT: getPackageRoot(),
59
+ },
14
60
  });
15
61
  await new Promise((resolve, reject) => {
16
62
  deployProcess.on('exit', (code) => {
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../bin/commands/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAE5D,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,kCAAkC,CAAC;KAC/C,cAAc,CAAC,qBAAqB,EAAE,gBAAgB,CAAC;KACvD,MAAM,CAAC,KAAK,EAAE,OAA0B,EAAE,EAAE;IAC3C,IAAI,CAAC;QACH,kBAAkB,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAElC,MAAM,aAAa,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE;YAC9E,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;YAClB,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,IAAI;SACZ,CAAC,CAAC;QAEH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC1C,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;gBAChC,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,OAAO,EAAE,CAAC;gBACZ,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC5D,CAAC;YACH,CAAC,CAAC,CAAC;YACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAG,KAAe,CAAC,OAAO,CAAC,CAAC;QAClD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../bin/commands/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const envPullCommand: Command;
@@ -0,0 +1,81 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { select, confirm } from '@inquirer/prompts';
5
+ import { findSstConfig, extractSstProjectName } from '../utils/sst-config.js';
6
+ import { pullSecrets, getSecretPath, getSecretInfo, toEnvFileContent, listAvailableStages } from '../utils/secrets-manager.js';
7
+ export const envPullCommand = new Command('env:pull')
8
+ .description('Pull environment variables from AWS Secrets Manager')
9
+ .option('-s, --stage <stage>', 'SST stage name')
10
+ .option('-o, --output <file>', 'Output file path (default: .env.{stage})')
11
+ .option('-f, --force', 'Overwrite existing file without confirmation')
12
+ .action(async (options) => {
13
+ try {
14
+ const configPath = findSstConfig();
15
+ if (!configPath) {
16
+ console.error('Error: Could not find sst.config.ts or sst.config.js in current directory.');
17
+ process.exit(1);
18
+ }
19
+ const appName = extractSstProjectName(configPath);
20
+ if (!appName) {
21
+ console.error('Error: Could not extract app name from SST config.');
22
+ process.exit(1);
23
+ }
24
+ // Determine stage
25
+ let stage = options.stage;
26
+ if (!stage) {
27
+ const availableStages = await listAvailableStages(appName);
28
+ if (availableStages.length === 0) {
29
+ console.log('No stages found in AWS Secrets Manager for this app.');
30
+ console.log('Run this command again with the --stage <stage> flag to create the environment file.');
31
+ process.exit(1);
32
+ }
33
+ stage = await select({
34
+ message: 'Select the stage to pull from:',
35
+ choices: availableStages.map(s => ({ name: s, value: s })),
36
+ });
37
+ }
38
+ const secretPath = getSecretPath(appName, stage);
39
+ const outputFile = options.output || `.env.${stage}`;
40
+ const outputPath = path.resolve(process.cwd(), outputFile);
41
+ console.log(`Pulling environment variables from: ${secretPath}`);
42
+ // Get info about the secret structure
43
+ const secretInfo = await getSecretInfo(secretPath);
44
+ if (!secretInfo) {
45
+ console.error(`Error: No secrets found at ${secretPath}`);
46
+ console.log('');
47
+ console.log('To create secrets for this environment, run:');
48
+ console.log(` sst-laravel env:push --stage ${stage} --input .env.example`);
49
+ process.exit(1);
50
+ }
51
+ if (secretInfo.chunked) {
52
+ console.log(`Found ${secretInfo.totalKeys} variables in ${secretInfo.chunks} chunks`);
53
+ }
54
+ // Check if output file exists
55
+ if (fs.existsSync(outputPath) && !options.force) {
56
+ const shouldOverwrite = await confirm({
57
+ message: `File ${outputFile} already exists. Overwrite?`,
58
+ default: false,
59
+ });
60
+ if (!shouldOverwrite) {
61
+ console.log('Aborted.');
62
+ process.exit(0);
63
+ }
64
+ }
65
+ // Pull secrets from AWS
66
+ const secrets = await pullSecrets(secretPath);
67
+ if (!secrets) {
68
+ console.error(`Error: Failed to pull secrets from ${secretPath}`);
69
+ process.exit(1);
70
+ }
71
+ // Convert to .env format and write
72
+ const envContent = toEnvFileContent(secrets);
73
+ fs.writeFileSync(outputPath, envContent + '\n');
74
+ console.log(`Successfully pulled ${Object.keys(secrets).length} variables to ${outputFile}`);
75
+ }
76
+ catch (error) {
77
+ console.error('Error:', error.message);
78
+ process.exit(1);
79
+ }
80
+ });
81
+ //# sourceMappingURL=env-pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-pull.js","sourceRoot":"","sources":["../../../bin/commands/env-pull.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const envPushCommand: Command;
@@ -0,0 +1,93 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { select, confirm } from '@inquirer/prompts';
5
+ import { findSstConfig, extractSstProjectName } from '../utils/sst-config.js';
6
+ import { pushSecrets, getSecretInfo, getSecretPath, parseEnvFile, needsChunking, listAvailableStages } from '../utils/secrets-manager.js';
7
+ export const envPushCommand = new Command('env:push')
8
+ .description('Push environment variables to AWS Secrets Manager')
9
+ .option('-s, --stage <stage>', 'SST stage name')
10
+ .option('-i, --input <file>', 'Input file path (default: .env)')
11
+ .option('-f, --force', 'Push without confirmation')
12
+ .action(async (options) => {
13
+ try {
14
+ const configPath = findSstConfig();
15
+ if (!configPath) {
16
+ console.error('Error: Could not find sst.config.ts or sst.config.js in current directory.');
17
+ process.exit(1);
18
+ }
19
+ const appName = extractSstProjectName(configPath);
20
+ if (!appName) {
21
+ console.error('Error: Could not extract app name from SST config.');
22
+ process.exit(1);
23
+ }
24
+ // Determine stage
25
+ let stage = options.stage;
26
+ if (!stage) {
27
+ const availableStages = await listAvailableStages(appName);
28
+ if (availableStages.length === 0) {
29
+ console.log('No stages found in AWS Secrets Manager for this app.');
30
+ console.log('Run this command again with the --stage <stage> flag so the environment file can be created.');
31
+ process.exit(1);
32
+ }
33
+ stage = await select({
34
+ message: 'Select the stage to push to:',
35
+ choices: availableStages.map(s => ({ name: s, value: s })),
36
+ });
37
+ }
38
+ const inputFile = options.input || '.env';
39
+ const inputPath = path.resolve(process.cwd(), inputFile);
40
+ // Check if input file exists
41
+ if (!fs.existsSync(inputPath)) {
42
+ console.error(`Error: Input file ${inputFile} not found.`);
43
+ process.exit(1);
44
+ }
45
+ const secretPath = getSecretPath(appName, stage);
46
+ // Parse the .env file
47
+ const content = fs.readFileSync(inputPath, 'utf-8');
48
+ const vars = parseEnvFile(content);
49
+ const varCount = Object.keys(vars).length;
50
+ if (varCount === 0) {
51
+ console.error('Error: No variables found in the input file.');
52
+ process.exit(1);
53
+ }
54
+ console.log(`Found ${varCount} variables in ${inputFile}`);
55
+ console.log(`Target: ${secretPath}`);
56
+ // Check if chunking will be needed
57
+ if (needsChunking(vars)) {
58
+ console.log(`\nNote: Environment file exceeds AWS Secrets Manager limit and will be split into multiple chunks.`);
59
+ }
60
+ // Check if secrets already exist
61
+ const existingInfo = await getSecretInfo(secretPath);
62
+ if (existingInfo && !options.force) {
63
+ if (existingInfo.chunked) {
64
+ console.log(`\nWarning: ${existingInfo.totalKeys} variables already exist at this path (in ${existingInfo.chunks} chunks).`);
65
+ }
66
+ else {
67
+ console.log(`\nWarning: ${existingInfo.totalKeys} variables already exist at this path.`);
68
+ }
69
+ const shouldOverwrite = await confirm({
70
+ message: 'Do you want to overwrite the existing secrets?',
71
+ default: false,
72
+ });
73
+ if (!shouldOverwrite) {
74
+ console.log('Aborted.');
75
+ process.exit(0);
76
+ }
77
+ }
78
+ // Push secrets to AWS
79
+ console.log('\nPushing secrets to AWS Secrets Manager...');
80
+ const result = await pushSecrets(secretPath, vars);
81
+ if (result.chunked) {
82
+ console.log(`Successfully pushed ${varCount} variables to ${secretPath} (split into ${result.chunks} chunks)`);
83
+ }
84
+ else {
85
+ console.log(`Successfully pushed ${varCount} variables to ${secretPath}`);
86
+ }
87
+ }
88
+ catch (error) {
89
+ console.error('Error:', error.message);
90
+ process.exit(1);
91
+ }
92
+ });
93
+ //# sourceMappingURL=env-push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-push.js","sourceRoot":"","sources":["../../../bin/commands/env-push.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,106 @@
1
1
  import { Command } from 'commander';
2
- import { spawn } from 'child_process';
2
+ import { spawn, execSync } from 'child_process';
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
- import { getTemplatePath } from '../utils/sst-config.js';
5
+ import { fileURLToPath } from 'url';
6
+ import { confirm } from '@inquirer/prompts';
7
+ import { getTemplatePath, getPackageRoot } from '../utils/sst-config.js';
8
+ const SKILL_FILE_PATH = fileURLToPath(new URL('../../skills/laravel-initial-setup/SKILL.md', import.meta.url));
9
+ const runProcess = (command, args, cwd) => {
10
+ return new Promise((resolve, reject) => {
11
+ const child = spawn(command, args, {
12
+ cwd,
13
+ stdio: 'inherit',
14
+ shell: true
15
+ });
16
+ child.on('exit', (code) => {
17
+ if (code === 0) {
18
+ resolve();
19
+ }
20
+ else {
21
+ reject(new Error(`${command} ${args.join(' ')} exited with code ${code}`));
22
+ }
23
+ });
24
+ child.on('error', reject);
25
+ });
26
+ };
27
+ const detectLaravelBoostVersion = (cwd) => {
28
+ try {
29
+ const output = execSync('composer show laravel/boost --no-ansi --no-interaction', {
30
+ cwd,
31
+ stdio: ['ignore', 'pipe', 'pipe']
32
+ }).toString();
33
+ const versionMatch = output.match(/versions?\s*:\s*\*?\s*v?([0-9][^\s]*)/i);
34
+ if (!versionMatch) {
35
+ return null;
36
+ }
37
+ return versionMatch[1].replace(/^v/, '');
38
+ }
39
+ catch (error) {
40
+ return null;
41
+ }
42
+ };
43
+ const isVersionAtLeast = (version, minimum) => {
44
+ const normalize = (input) => input.split('.').map((segment) => parseInt(segment, 10) || 0);
45
+ const versionParts = normalize(version);
46
+ const minParts = normalize(minimum);
47
+ for (let i = 0; i < Math.max(versionParts.length, minParts.length); i++) {
48
+ const current = versionParts[i] ?? 0;
49
+ const min = minParts[i] ?? 0;
50
+ if (current > min)
51
+ return true;
52
+ if (current < min)
53
+ return false;
54
+ }
55
+ return true;
56
+ };
57
+ const installSkillWithBoost = async (cwd) => {
58
+ const aiSkillsDir = path.join(cwd, '.ai', 'skills', 'sst-laravel-initial-setup');
59
+ fs.mkdirSync(aiSkillsDir, { recursive: true });
60
+ const targetPath = path.join(aiSkillsDir, 'SKILL.md');
61
+ fs.copyFileSync(SKILL_FILE_PATH, targetPath);
62
+ console.log(`Copied skill file to ${path.relative(cwd, targetPath)}`);
63
+ console.log('Running boost:update to refresh Laravel Boost skills...');
64
+ await runProcess('php', ['artisan', 'boost:update'], cwd);
65
+ };
66
+ const installSkillViaNpx = async (cwd) => {
67
+ console.log('Installing skill via `npx skills add`...');
68
+ await runProcess('npx', ['skills', 'add', SKILL_FILE_PATH], cwd);
69
+ };
70
+ const maybeInstallSkill = async (cwd) => {
71
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
72
+ console.log('Skipping AI skill installation prompt (non-interactive terminal).');
73
+ return;
74
+ }
75
+ const shouldInstallSkill = await confirm({
76
+ message: 'Install the SST Laravel Initial Setup AI skill in this project?',
77
+ default: true
78
+ });
79
+ if (!shouldInstallSkill) {
80
+ console.log('Skipping AI skill installation. You can add it later from skills/laravel-initial-setup.');
81
+ return;
82
+ }
83
+ const boostVersion = detectLaravelBoostVersion(cwd);
84
+ if (boostVersion) {
85
+ console.log(`Detected laravel/boost version ${boostVersion}`);
86
+ }
87
+ else {
88
+ console.log('laravel/boost package not detected or Composer unavailable.');
89
+ }
90
+ if (boostVersion && isVersionAtLeast(boostVersion, '2.0.0')) {
91
+ await installSkillWithBoost(cwd);
92
+ }
93
+ else {
94
+ if (boostVersion) {
95
+ console.log('laravel/boost version is below 2.0. Falling back to npx skills.');
96
+ }
97
+ await installSkillViaNpx(cwd);
98
+ }
99
+ console.log('\n');
100
+ console.log('\n');
101
+ console.log('🤖 SST Laravel skill installed successfully');
102
+ console.log('Run "Please help me set up the deployment config of my application using SST Laravel" in your AI agent to get started');
103
+ };
6
104
  export const initCommand = new Command('init')
7
105
  .description('Initialize SST and SST Laravel, creating a new sst.config.ts file to deploy your Laravel application')
8
106
  .action(async () => {
@@ -67,7 +165,11 @@ export const initCommand = new Command('init')
67
165
  const sstInstallProcess = spawn('npx', ['sst', 'install'], {
68
166
  cwd,
69
167
  stdio: 'inherit',
70
- shell: true
168
+ shell: true,
169
+ env: {
170
+ ...process.env,
171
+ SST_LARAVEL_PACKAGE_ROOT: getPackageRoot(),
172
+ },
71
173
  });
72
174
  await new Promise((resolve, reject) => {
73
175
  sstInstallProcess.on('exit', (code) => {
@@ -102,16 +204,23 @@ export const initCommand = new Command('init')
102
204
  fs.chmodSync(deployScriptPath, 0o755);
103
205
  console.log('Created infra/deploy.sh script');
104
206
  }
207
+ try {
208
+ await maybeInstallSkill(cwd);
209
+ }
210
+ catch (skillError) {
211
+ console.warn('Failed to install AI skill automatically:', skillError.message);
212
+ console.warn('You can manually add it later from skills/laravel-initial-setup.');
213
+ }
105
214
  console.log('\n');
106
215
  console.log('\n');
107
- console.log('Successfully configured sst.config.ts with Laravel boilerplate');
216
+ console.log('Successfully configured sst.config.ts with Laravel boilerplate');
108
217
  console.log('You can now customize the configuration for your own Laravel application.');
109
218
  console.log('\n');
110
219
  console.log('Your default configuration is set to look for a .env.{stage} file when deploying. You can customize this in the sst.config.ts file as needed.');
111
220
  console.log('\n');
112
221
  console.log('A deploy.sh script has been created with example deployment tasks (migrations, caching, etc.). Customize it as needed.');
113
222
  console.log('\n');
114
- console.log('Run `npx sst deploy --stage {stage}` to deploy your application.');
223
+ console.log('Run `npx sst-laravel deploy --stage {stage}` to deploy your application.');
115
224
  }
116
225
  catch (error) {
117
226
  console.error('Error:', error.message);