stacks 0.62.0 → 0.63.0

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/buddy/build.ts CHANGED
@@ -4,6 +4,7 @@ await Bun.build({
4
4
  outdir: './dist',
5
5
  format: 'esm',
6
6
  target: 'bun',
7
+ sourcemap: 'linked',
7
8
  splitting: true,
8
9
 
9
10
  external: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/buddy",
3
3
  "type": "module",
4
- "version": "0.62.0",
4
+ "version": "0.63.0",
5
5
  "description": "Meet Buddy. The Stacks runtime.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -50,24 +50,25 @@
50
50
  },
51
51
  "module": "dist/index.js",
52
52
  "types": "dist/index.d.ts",
53
- "contributors": [
54
- "Chris Breuer <chris@stacksjs.org>"
55
- ],
53
+ "contributors": ["Chris Breuer <chris@stacksjs.org>"],
56
54
  "bin": {
57
55
  "stacks": "dist/cli.mjs",
58
56
  "stx": "dist/cli.mjs",
59
57
  "buddy": "dist/cli.mjs",
60
58
  "bud": "dist/cli.mjs"
61
59
  },
62
- "files": [
63
- "README.md",
64
- "dist",
65
- "src"
66
- ],
60
+ "files": ["README.md", "dist", "src"],
67
61
  "scripts": {
68
62
  "buddy": "bunx ./src/cli.ts",
69
63
  "build": "bun --bun build.ts && bun run compile",
70
64
  "compile": "bun build ./bin/cli.ts --compile --minify --sourcemap --outfile dist/stacks",
65
+ "compile:linux:x64-modern": "bun build ./bin/cli.ts --compile --target=bun-linux-x64-modern --minify --sourcemap --outfile dist/stacks",
66
+ "compile:linux:x64-baseline": "bun build ./bin/cli.ts --compile --target=bun-linux-x64-baseline --minify --sourcemap --outfile dist/stacks",
67
+ "compile:linux:arm64": "bun build ./bin/cli.ts --compile --target=bun-linux-arm64 --minify --sourcemap --outfile dist/stacks",
68
+ "compile:windows:x64-modern": "bun build ./bin/cli.ts --compile --target=bun-windows-x64-modern --minify --sourcemap --outfile dist/stacks",
69
+ "compile:windows:x64-baseline": "bun build ./bin/cli.ts --compile --target=bun-windows-x64-baseline --minify --sourcemap --outfile dist/stacks",
70
+ "compile:mac:arm64": "bun build ./bin/cli.ts --compile --target=bun-darwin-arm64 --minify --sourcemap --outfile dist/stacks",
71
+ "compile:mac:x64": "bun build ./bin/cli.ts --compile --target=bun-darwin-x64 --minify --sourcemap --outfile dist/stacks",
71
72
  "typecheck": "bun --bun tsc --noEmit",
72
73
  "prepublishOnly": "bun run build"
73
74
  },
@@ -1,5 +1,6 @@
1
1
  import process from 'node:process'
2
- import { intro, italic, log, outro, prompts, runCommand, runCommandSync, underline } from '@stacksjs/cli'
2
+ import { CloudFrontClient, CreateInvalidationCommand } from '@aws-sdk/client-cloudfront'
3
+ import { intro, italic, log, outro, prompts, runCommand, underline } from '@stacksjs/cli'
3
4
  import {
4
5
  addJumpBox,
5
6
  deleteCdkRemnants,
@@ -9,6 +10,7 @@ import {
9
10
  deleteParameterStore,
10
11
  deleteStacksBuckets,
11
12
  deleteStacksFunctions,
13
+ getCloudFrontDistributionId,
12
14
  getJumpBoxInstanceId,
13
15
  } from '@stacksjs/cloud'
14
16
  import { path as p } from '@stacksjs/path'
@@ -21,9 +23,12 @@ export function cloud(buddy: CLI) {
21
23
  cloud: 'Interact with the Stacks Cloud',
22
24
  ssh: 'SSH into the Stacks Cloud',
23
25
  add: 'Add a resource to the Stacks Cloud',
24
- remove: 'Removes the Stacks Cloud. In case it fails, try again',
25
- optimizeCost: 'Removes certain resources that may be re-applied at a later time',
26
- cleanUp: 'Removes all resources that were retained during the cloud deletion',
26
+ remove: 'Remove the Stacks Cloud. In case it fails, try again',
27
+ optimizeCost: 'Remove certain resources that may be re-applied at a later time',
28
+ cleanUp: 'Remove all resources that were retained during the cloud deletion',
29
+ invalidateCache: 'Invalidate the CloudFront cache',
30
+ diff: 'Show the diff of the current, undeployed cloud changes ',
31
+ paths: 'The paths to invalidate',
27
32
  project: 'Target a specific project',
28
33
  verbose: 'Enable verbose output',
29
34
  }
@@ -32,13 +37,16 @@ export function cloud(buddy: CLI) {
32
37
  .command('cloud', descriptions.cloud)
33
38
  .option('--ssh', descriptions.ssh, { default: false })
34
39
  .option('--connect', descriptions.ssh, { default: false })
40
+ .option('--invalidate-cache', descriptions.invalidateCache, { default: false })
41
+ .option('--paths [paths]', descriptions.paths)
42
+ .option('--diff', descriptions.diff, { default: false })
35
43
  .option('-p, --project [project]', descriptions.project, { default: false })
36
44
  .option('--verbose', descriptions.verbose, { default: false })
37
45
  .action(async (options: CloudCliOptions) => {
38
46
  log.debug('Running `buddy cloud` ...', options)
47
+ const startTime = performance.now()
39
48
 
40
49
  if (options.ssh || options.connect) {
41
- const startTime = performance.now()
42
50
  const jumpBoxId = await getJumpBoxInstanceId()
43
51
  const result = await runCommand(`aws ssm start-session --target ${jumpBoxId}`, {
44
52
  ...options,
@@ -59,7 +67,69 @@ export function cloud(buddy: CLI) {
59
67
  process.exit(ExitCode.Success)
60
68
  }
61
69
 
62
- log.info('Not implemented yet. Please use the --ssh (or --connect) flag to connect to the Stacks Cloud.')
70
+ if (options.invalidateCache) {
71
+ const { confirm } = await prompts({
72
+ name: 'confirm',
73
+ type: 'confirm',
74
+ message: 'Would you like to invalidate the CDN (CloudFront) cache?',
75
+ })
76
+
77
+ if (!confirm) {
78
+ await outro('Exited', { startTime, useSeconds: true })
79
+ process.exit(ExitCode.Success)
80
+ }
81
+
82
+ log.info('Invalidating the CloudFront cache...')
83
+
84
+ const cloudfront = new CloudFrontClient()
85
+ const distributionId = await getCloudFrontDistributionId()
86
+
87
+ const params = {
88
+ DistributionId: distributionId,
89
+ InvalidationBatch: {
90
+ CallerReference: `${Date.now()}`,
91
+ Paths: {
92
+ Quantity: 1,
93
+ Items: [
94
+ '/*',
95
+ /* more items */
96
+ ],
97
+ },
98
+ },
99
+ }
100
+
101
+ const command = new CreateInvalidationCommand(params)
102
+
103
+ cloudfront.send(command).then(
104
+ (data) => console.log(data),
105
+ (err) => console.log(err, err.stack),
106
+ )
107
+
108
+ await outro('Exited', { startTime, useSeconds: true })
109
+ process.exit(ExitCode.Success)
110
+ }
111
+
112
+ if (options.diff) {
113
+ const result = await runCommand('npx cdk diff', {
114
+ cwd: p.frameworkCloudPath(),
115
+ stdin: 'pipe',
116
+ })
117
+
118
+ if (result.isErr()) {
119
+ await outro(
120
+ 'While running the cloud diff command, there was an issue',
121
+ { startTime, useSeconds: true },
122
+ result.error,
123
+ )
124
+ process.exit(ExitCode.FatalError)
125
+ }
126
+
127
+ await outro('Showing diff of the current, undeployed cloud changes', { startTime, useSeconds: true })
128
+ console.log(result.value)
129
+ process.exit(ExitCode.Success)
130
+ }
131
+
132
+ log.info('Not implemented yet. Read more about `buddy cloud` here: https://stacksjs.org/docs/cloud')
63
133
  process.exit(ExitCode.Success)
64
134
  })
65
135
 
@@ -168,9 +238,9 @@ export function cloud(buddy: CLI) {
168
238
  // sleep for 2 seconds to get the user to read the message
169
239
  await new Promise((resolve) => setTimeout(resolve, 2000))
170
240
 
171
- const result = await runCommand(`bunx cdk destroy`, {
241
+ const result = await runCommand(`npx cdk destroy`, {
172
242
  ...options,
173
- cwd: p.cloudPath(),
243
+ cwd: p.frameworkCloudPath(),
174
244
  stdin: 'inherit',
175
245
  })
176
246
 
@@ -196,8 +266,9 @@ export function cloud(buddy: CLI) {
196
266
  try {
197
267
  log.info('Finalizing the removal of your cloud resources.')
198
268
  log.info('This will take a few moments...')
199
- loop(7, () => {
200
- runCommandSync('buddy cloud:cleanup', {
269
+ // sometimes, this fails, so we need to retry it until all resources are deleted -> weird workaround, and would love a more stable alternative
270
+ await loop(7, async () => {
271
+ await runCommand('buddy cloud:cleanup', {
201
272
  ...options,
202
273
  cwd: p.projectPath(),
203
274
  stdout: 'ignore',
@@ -373,6 +444,89 @@ export function cloud(buddy: CLI) {
373
444
  process.exit(ExitCode.Success)
374
445
  })
375
446
 
447
+ buddy
448
+ .command('cloud:invalidate-cache', descriptions.invalidateCache)
449
+ .option('--paths [paths]', descriptions.paths, { default: false })
450
+ .option('-p, --project [project]', descriptions.project, { default: false })
451
+ .option('--verbose', descriptions.verbose, { default: false })
452
+ .action(async (options: CloudCliOptions) => {
453
+ log.debug('Running `buddy cloud:invalidate-cache` ...', options)
454
+
455
+ const startTime = await intro('buddy cloud:invalidate-cache')
456
+
457
+ const { confirm } = await prompts({
458
+ name: 'confirm',
459
+ type: 'confirm',
460
+ message: 'Would you like to invalidate the CloudFront cache?',
461
+ })
462
+
463
+ if (!confirm) {
464
+ await outro('Exited', { startTime, useSeconds: true })
465
+ process.exit(ExitCode.Success)
466
+ }
467
+
468
+ log.info('Invalidating the CloudFront cache...')
469
+ // const result = await runCommand('aws cloudfront create-invalidation --distribution-id E1U4Z2E9NJW9J --paths "/*"', {
470
+ // ...options,
471
+ // cwd: p.projectPath(),
472
+ // stdin: 'pipe',
473
+ // })
474
+
475
+ if (options.paths) {
476
+ const result = await runCommand(
477
+ `aws cloudfront create-invalidation --distribution-id E1U4Z2E9NJW9J --paths ${options.paths}`,
478
+ {
479
+ ...options,
480
+ cwd: p.projectPath(), // TODO: this should be the cloud path
481
+ stdin: 'pipe',
482
+ },
483
+ ) // TODO: this should be the cloud path
484
+
485
+ if (result.isErr()) {
486
+ await outro(
487
+ 'While running the cloud command, there was an issue',
488
+ { startTime, useSeconds: true },
489
+ result.error,
490
+ )
491
+ process.exit(ExitCode.FatalError)
492
+ }
493
+
494
+ await outro('Exited', { startTime, useSeconds: true })
495
+ process.exit(ExitCode.Success)
496
+ }
497
+
498
+ log.info('Not implemented yet. Read more about `buddy cloud` here: https://stacksjs.org/docs/cloud')
499
+ process.exit(ExitCode.Success)
500
+ })
501
+
502
+ buddy
503
+ .command('cloud:diff', descriptions.diff)
504
+ .option('-p, --project [project]', descriptions.project, { default: false })
505
+ .option('--verbose', descriptions.verbose, { default: false })
506
+ .action(async (options: CloudCliOptions) => {
507
+ log.debug('Running `buddy cloud:diff` ...', options)
508
+
509
+ const startTime = await intro('buddy cloud:diff')
510
+
511
+ const result = await runCommand('npx cdk diff', {
512
+ cwd: p.frameworkCloudPath(),
513
+ stdin: 'pipe',
514
+ })
515
+
516
+ if (result.isErr()) {
517
+ await outro(
518
+ 'While running the cloud diff command, there was an issue',
519
+ { startTime, useSeconds: true },
520
+ result.error,
521
+ )
522
+ process.exit(ExitCode.FatalError)
523
+ }
524
+
525
+ await outro('Showing diff of the current, undeployed cloud changes', { startTime, useSeconds: true })
526
+ console.log(result.value)
527
+ process.exit(ExitCode.Success)
528
+ })
529
+
376
530
  buddy.on('cloud:*', () => {
377
531
  console.error('Invalid command: %s\nSee --help for a list of available commands.', buddy.args.join(' '))
378
532
  process.exit(1)
@@ -62,7 +62,8 @@ async function configureAws(options?: ConfigureOptions) {
62
62
  const defaultRegion = 'us-east-1' // we only support `us-east-1` for now
63
63
  const defaultOutputFormat = options?.output ?? 'json'
64
64
 
65
- const command = `aws configure --profile ${options?.profile ?? process.env.AWS_PROFILE}`
65
+ const profile = process.env.AWS_PROFILE ?? options?.profile
66
+ const command = profile ? `aws configure --profile ${profile}` : `aws configure`
66
67
  const input = `${awsAccessKeyId}\n${awsSecretAccessKey}\n${defaultRegion}\n${defaultOutputFormat}\n`
67
68
 
68
69
  const result = await runCommand(command, {
@@ -4,27 +4,38 @@ import { intro, italic, log, outro, runCommand } from '@stacksjs/cli'
4
4
  import { app } from '@stacksjs/config'
5
5
  import { addDomain, hasUserDomainBeenAddedToCloud } from '@stacksjs/dns'
6
6
  import { Action } from '@stacksjs/enums'
7
+ import { path as p } from '@stacksjs/path'
7
8
  import { ExitCode } from '@stacksjs/types'
8
9
  import type { CLI, DeployOptions } from '@stacksjs/types'
10
+ import { $ } from 'bun'
9
11
 
10
12
  export function deploy(buddy: CLI) {
11
13
  const descriptions = {
12
- deploy: 'Re-installs your npm dependencies',
14
+ deploy: 'Deploy your project',
13
15
  project: 'Target a specific project',
16
+ production: 'Deploy to production',
17
+ development: 'Deploy to development',
18
+ staging: 'Deploy to staging',
19
+ domain: 'Specify a domain to deploy to',
14
20
  verbose: 'Enable verbose output',
15
21
  }
16
22
 
17
23
  buddy
18
- .command('deploy', descriptions.deploy)
19
- .option('--domain', 'Specify a domain to deploy to', { default: undefined })
24
+ .command('deploy [env]', descriptions.deploy)
25
+ .option('--domain', descriptions.domain, { default: undefined })
20
26
  .option('-p, --project [project]', descriptions.project, { default: false })
27
+ .option('--prod', descriptions.production, { default: true })
28
+ .option('--dev', descriptions.development, { default: false })
29
+ .option('--staging', descriptions.staging, { default: false })
21
30
  .option('--verbose', descriptions.verbose, { default: false })
22
- .action(async (options: DeployOptions) => {
31
+ .action(async (env: string | undefined, options: DeployOptions) => {
23
32
  log.debug('Running `buddy deploy` ...', options)
24
33
 
25
34
  const startTime = await intro('buddy deploy')
26
35
  const domain = options.domain || app.url
27
36
 
37
+ if (options.prod || env === 'production' || env === 'prod') await confirmProductionDeployment()
38
+
28
39
  if (!domain) {
29
40
  log.info('No domain found in your .env or ./config/app.ts')
30
41
  log.info('Please ensure your domain is properly configured.')
@@ -35,6 +46,7 @@ export function deploy(buddy: CLI) {
35
46
  log.info(`Deploying to ${italic(domain)}`)
36
47
 
37
48
  await checkIfAwsIsConfigured()
49
+ await checkIfAwsIsBootstrapped()
38
50
 
39
51
  options.domain = await configureDomain(domain, options, startTime)
40
52
 
@@ -58,6 +70,15 @@ export function deploy(buddy: CLI) {
58
70
  })
59
71
  }
60
72
 
73
+ async function confirmProductionDeployment() {
74
+ const answer = await log.prompt.require().confirm('Are you sure you want to deploy to production?')
75
+
76
+ if (!answer) {
77
+ log.info('Aborting deployment...')
78
+ process.exit(ExitCode.InvalidArgument)
79
+ }
80
+ }
81
+
61
82
  async function configureDomain(domain: string, options: DeployOptions, startTime: number) {
62
83
  if (!domain) {
63
84
  log.info('We could not identify a domain to deploy to.')
@@ -130,3 +151,33 @@ async function checkIfAwsIsConfigured() {
130
151
 
131
152
  log.success('AWS is configured')
132
153
  }
154
+
155
+ async function checkIfAwsIsBootstrapped() {
156
+ try {
157
+ log.info('Ensuring AWS is bootstrapped...')
158
+ // const toolkitName = 'stacks-toolkit'
159
+ await runCommand('aws cloudformation describe-stacks --stack-name stacks-toolkit', { silent: true })
160
+ // await $`aws cloudformation describe-stacks --stack-name stacks-toolkit`.quiet()
161
+ log.success('AWS is bootstrapped')
162
+
163
+ return true
164
+ } catch (err: any) {
165
+ log.debug(`Not yet bootstrapped. Failed with code ${err.exitCode}`)
166
+ log.debug(err.stdout.toString())
167
+ log.debug(err.stderr.toString())
168
+
169
+ log.info('AWS is not bootstrapped yet')
170
+ log.info('Bootstrapping. This may take a few moments...')
171
+
172
+ try {
173
+ $.cwd(p.frameworkCloudPath())
174
+ const result = await $`bun run bootstrap`
175
+ console.log(result)
176
+ return true
177
+ } catch (error) {
178
+ log.error('Failed to bootstrap AWS')
179
+ console.error(error)
180
+ process.exit(ExitCode.FatalError)
181
+ }
182
+ }
183
+ }
@@ -17,8 +17,8 @@ import type { CLI, DevOptions } from '@stacksjs/types'
17
17
 
18
18
  export function dev(buddy: CLI) {
19
19
  const descriptions = {
20
- dev: 'Starts development server',
21
- frontend: 'Starts the frontend development server',
20
+ dev: 'Start development server',
21
+ frontend: 'Start the frontend development server',
22
22
  components: 'Start the Components development server',
23
23
  desktop: 'Start the Desktop App development server',
24
24
  dashboard: 'Start the Dashboard development server',
@@ -327,12 +327,11 @@ export function make(buddy: CLI) {
327
327
  log.debug('Running `buddy make:certificate` ...', options)
328
328
 
329
329
  const domain = await localUrl()
330
- log.info(`Creating SSL certificate...`)
331
330
 
331
+ log.info(`Creating SSL certificate...`)
332
332
  await runCommand(`tlsx ${domain}`, {
333
333
  cwd: p.projectStoragePath('keys'),
334
334
  })
335
-
336
335
  log.success('Certificate created')
337
336
 
338
337
  log.info(`Installing SSL certificate...`)
@@ -62,7 +62,7 @@ export function migrate(buddy: CLI) {
62
62
  process.exit()
63
63
  }
64
64
 
65
- await outro(`All tables dropped successfully`, {
65
+ await outro(`All tables dropped successfully & migrated successfully`, {
66
66
  startTime: perf,
67
67
  useSeconds: true,
68
68
  })
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "stacks",
3
3
  "type": "module",
4
- "version": "0.62.0",
4
+ "version": "0.63.0",
5
5
  "description": "The Stacks framework.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",