Skip to content

Commit 08a4782

Browse files
authored
chore: wip
1 parent 062a983 commit 08a4782

File tree

21 files changed

+469
-214
lines changed

21 files changed

+469
-214
lines changed

.gitignore

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,3 @@ custom-cli
1515
.fleet
1616
.idea
1717
.vscode
18-
19-
!storage/framework/ide/jetbrains/.fleet
20-
!storage/framework/ide/jetbrains/.idea
21-
!storage/framework/ide/vscode/.vscode

app/Commands/Inspire.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// triggered via `$your-cli inspire` and `buddy inspire`
2+
import process from 'node:process'
3+
import { log } from '@stacksjs/cli'
4+
import { collect } from '@stacksjs/collections'
5+
import { ExitCode } from '@stacksjs/types'
6+
import type { CLI } from '@stacksjs/types'
7+
8+
// for enhanced type-safety & autocompletion, you may want to define the options interface
9+
interface InspireOptions {
10+
two: boolean
11+
}
12+
13+
// could be queried from any API or a database
14+
const quotes = collect([
15+
'The best way to get started is to quit talking and begin doing.',
16+
'The pessimist sees difficulty in every opportunity. The optimist sees opportunity in every difficulty.',
17+
'Don’t let yesterday take up too much of today.',
18+
'You learn more from failure than from success. Don’t let it stop you. Failure builds character.',
19+
'It’s not whether you get knocked down, it’s whether you get up.',
20+
'If you are working on something that you really care about, you don’t have to be pushed. The vision pulls you.',
21+
'People who are crazy enough to think they can change the world, are the ones who do.',
22+
'Failure will never overtake me if my determination to succeed is strong enough.',
23+
'Entrepreneurs are great at dealing with uncertainty and also very good at minimizing risk. That’s the classic entrepreneur.',
24+
'We may encounter many defeats but we must not be defeated.',
25+
'Knowing is not enough; we must apply. Wishing is not enough; we must do.',
26+
'Imagine your life is perfect in every respect; what would it look like?',
27+
'We generate fears while we sit. We overcome them by action.',
28+
'Whether you think you can or think you can’t, you’re right.',
29+
'Security is mostly a superstition. Life is either a daring adventure or nothing.',
30+
])
31+
32+
export default function (cli: CLI) {
33+
return cli
34+
.command('inspire', 'Inspire yourself with a random quote')
35+
.option('--two, -t', 'Inspire yourself with two random quotes', { default: false })
36+
.alias('insp')
37+
.action((options: InspireOptions) => {
38+
if (options.two)
39+
quotes.random(2).map((quote, index) => log.info(`${index + 1}. ${quote}`))
40+
else
41+
log.info(quotes.random())
42+
43+
log.success('Have a great day!')
44+
process.exit(ExitCode.Success)
45+
})
46+
}

app/Jobs/DummyJob.ts

Lines changed: 0 additions & 16 deletions
This file was deleted.

app/Jobs/ExampleJob.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Job } from '@stacksjs/queue'
2+
import { Every } from '@stacksjs/types'
3+
import { log } from '@stacksjs/cli'
4+
5+
export default new Job({
6+
name: 'Send Welcome Email', // optional, defaults to the file name
7+
description: 'A demo cron job that runs every minute', // optional
8+
queue: 'default', // optional, defaults to 'default'
9+
tries: 3, // optional, defaults to 3, in case of failures
10+
backoff: 3, // optional, defaults to 3-second delays between retries
11+
rate: Every.Minute, // optional, '* * * * *' in cron syntax (overwrites the Scheduler's definition)
12+
handle: () => { // action: 'SendWelcomeEmail', // instead of handle, you may target an action or `action: () => {`
13+
log.info('This cron job log this message every minute')
14+
},
15+
})

app/Middleware/Logger.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { log } from '@stacksjs/cli'
2+
import { Middleware } from '@stacksjs/router'
3+
4+
export default new Middleware({
5+
name: 'logger',
6+
priority: 1,
7+
handle() {
8+
log.info('logger middleware')
9+
},
10+
})

app/Notifications/Welcome.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// export default new Email({ // or Sms or PushNotification or Webhook or Chat or Notification
2+
// name: 'welcome',
3+
// subject: 'Welcome to Stacks',
4+
// to: ({ user }) => user.email,
5+
// from: ({ config }) => config.email.from,
6+
// template: 'welcome', // resolves the ./resources/mails/welcome.ts mail template
7+
// })
8+
9+
// export default defineEmail({ // or defineSms or definePushNotification or defineWebhook or defineChat or defineNotification
10+
// name: 'welcome',
11+
// subject: 'Welcome to Stacks',
12+
// to: ({ user }) => user.email,
13+
// from: ({ config }) => config.email.from,
14+
// template: 'welcome', // resolves the ./resources/mails/welcome.ts mail template
15+
// async run({ user }) {
16+
// // ...
17+
// },
18+
// })

app/Schedule.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import run from '@stacksjs/scheduler'
33
export default function () {
44
run.command('bun /home/some/script.js').everySecond()
55
run.command('bun /home/some/other/script.ts').everyMinute()
6-
run.job('DummyJob.ts').everyTenMinutes() // scans ./app/Jobs/*
6+
run.job('ExampleJob.ts').everyTenMinutes() // scans ./app/Jobs/*
77
// schedule.action('SomeAction.ts').everyFiveMinutes() // you may also trigger an action - scans ./app/Actions/*
88
run.exec('bun /home/some/script.ts').everyMinute()
99
run.call(() => {

bun.lockb

-488 Bytes
Binary file not shown.

storage/framework/core/buddy/src/commands/configure.ts

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import process from 'node:process'
22
import type { CLI, ConfigureOptions } from '@stacksjs/types'
33
import { log, outro, runCommand } from '@stacksjs/cli'
44
import { path as p } from '@stacksjs/path'
5-
import { config } from '@stacksjs/config'
65
import { ExitCode } from '@stacksjs/types'
76

87
export function configure(buddy: CLI) {
98
const descriptions = {
109
configure: 'Configure options',
1110
aws: 'Configure the AWS connection',
1211
project: 'Target a specific project',
12+
profile: 'The AWS profile to use',
1313
verbose: 'Enable verbose output',
1414
}
1515

@@ -20,19 +20,7 @@ export function configure(buddy: CLI) {
2020
.option('--verbose', descriptions.verbose, { default: false })
2121
.action(async (options?: ConfigureOptions) => {
2222
if (options?.aws) {
23-
const startTime = performance.now()
24-
const result = await runCommand('aws configure', {
25-
...options,
26-
cwd: p.projectPath(),
27-
stdin: 'inherit',
28-
})
29-
30-
if (result.isErr()) {
31-
await outro('While running the configure command, there was an issue', { startTime, useSeconds: true }, result.error)
32-
process.exit(ExitCode.FatalError)
33-
}
34-
35-
await outro('Exited', { startTime, useSeconds: true })
23+
await configureAws(options)
3624
process.exit(ExitCode.Success)
3725
}
3826

@@ -43,21 +31,15 @@ export function configure(buddy: CLI) {
4331
buddy
4432
.command('configure:aws', descriptions.aws)
4533
.option('-p, --project', descriptions.project, { default: false })
34+
.option('--profile', descriptions.profile, { default: process.env.AWS_PROFILE })
4635
.option('--verbose', descriptions.verbose, { default: false })
36+
.option('--access-key-id', 'The AWS access key')
37+
.option('--secret-access-key', 'The AWS secret access key')
38+
.option('--region', 'The AWS region')
39+
.option('--output', 'The AWS output format')
40+
.option('--quiet', 'Suppress output')
4741
.action(async (options?: ConfigureOptions) => {
48-
const startTime = performance.now()
49-
const result = await runCommand(`aws configure --profile ${config.app.url}`, {
50-
...options,
51-
cwd: p.cloudPath(),
52-
stdin: 'inherit',
53-
})
54-
55-
if (result.isErr()) {
56-
await outro('While running the cloud command, there was an issue', { startTime, useSeconds: true }, result.error)
57-
process.exit(ExitCode.FatalError)
58-
}
59-
60-
await outro('Exited', { startTime, useSeconds: true })
42+
await configureAws(options)
6143
process.exit(ExitCode.Success)
6244
})
6345

@@ -66,3 +48,32 @@ export function configure(buddy: CLI) {
6648
process.exit(ExitCode.FatalError)
6749
})
6850
}
51+
52+
async function configureAws(options: ConfigureOptions) {
53+
const startTime = performance.now()
54+
55+
const awsAccessKeyId = options?.accessKeyId ?? process.env.AWS_ACCESS_KEY_ID
56+
const awsSecretAccessKey = options?.secretAccessKey ?? process.env.AWS_SECRET_ACCESS_KEY
57+
const defaultRegion = 'us-east-1' // we only support `us-east-1` for now
58+
const defaultOutputFormat = options?.output ?? 'json'
59+
60+
const command = `aws configure --profile ${options.profile ?? process.env.AWS_PROFILE}`
61+
const input = `${awsAccessKeyId}\n${awsSecretAccessKey}\n${defaultRegion}\n${defaultOutputFormat}\n`
62+
63+
const result = await runCommand(command, {
64+
...options,
65+
cwd: p.projectPath(),
66+
stdin: 'pipe', // set stdin mode to 'pipe' to write to it
67+
input, // the actual input to write
68+
})
69+
70+
if (result.isErr()) {
71+
await outro('While running the cloud command, there was an issue', { startTime, useSeconds: true }, result.error)
72+
process.exit(ExitCode.FatalError)
73+
}
74+
75+
if (options.quiet)
76+
return
77+
78+
await outro('Exited', { startTime, useSeconds: true })
79+
}

storage/framework/core/buddy/src/commands/deploy.ts

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import process from 'node:process'
33
import { ExitCode } from '@stacksjs/types'
44
import type { CLI, DeployOptions } from '@stacksjs/types'
55
import { runAction } from '@stacksjs/actions'
6-
import { intro, italic, log, outro } from '@stacksjs/cli'
6+
import { intro, italic, log, outro, runCommand } from '@stacksjs/cli'
77
import { Action } from '@stacksjs/enums'
88
import { app } from '@stacksjs/config'
99
import { addDomain, hasUserDomainBeenAddedToCloud } from '@stacksjs/dns'
@@ -24,77 +24,95 @@ export function deploy(buddy: CLI) {
2424
const startTime = await intro('buddy deploy')
2525
const domain = options.domain || app.url
2626

27-
if (!domain) {
28-
log.info('We could not identify a domain to deploy to.')
29-
log.info('Please set your .env or ./config/app.ts properly.')
30-
console.log('')
31-
log.info('Alternatively, specify a domain to deploy via the `--domain` flag.')
32-
console.log('')
33-
log.info(' ➡️ Example: `buddy deploy --domain example.com`')
34-
console.log('')
35-
process.exit(ExitCode.FatalError)
36-
}
37-
38-
// TODO: we can improve this check at some point, otherwise domains that legitimately include the word localhost will fail
39-
// TODO: add check for whether the local APP_ENV is getting deployed, if so, ask if the user meant to deploy `dev`
40-
if (domain.includes('localhost')) {
41-
log.info('You are deploying to a local environment.')
42-
log.info('Please set your .env or ./config/app.ts properly.')
43-
console.log('')
44-
log.info('Alternatively, specify a domain to deploy via the `--domain` flag.')
45-
console.log('')
46-
log.info(' ➡️ Example: `buddy deploy --domain example.com`')
47-
console.log('')
48-
process.exit(ExitCode.FatalError)
49-
}
50-
51-
if (await hasUserDomainBeenAddedToCloud(domain)) {
52-
log.info('Domain is properly configured')
53-
log.info('Your cloud is deploying...')
54-
55-
log.info(`${italic('This may take a while...')}`)
56-
options.domain = domain
57-
58-
// now that we know the domain has been added to the users (AWS) cloud, we can deploy
59-
const result = await runAction(Action.Deploy, options)
27+
await checkIfAwsIsConfigured()
6028

61-
if (result.isErr()) {
62-
await outro('While running the `buddy deploy`, there was an issue', { startTime, useSeconds: true }, result.error)
63-
process.exit(ExitCode.FatalError)
64-
}
29+
options.domain = await configureDomain(domain)
6530

66-
await outro('Deployment succeeded.', { startTime, useSeconds: true })
67-
68-
process.exit(ExitCode.Success)
69-
}
70-
71-
// if the domain hasn't been added to the user's (AWS) cloud, we will add it for them
72-
// and then exit the process with prompts for the user to update their nameservers
73-
console.log('')
74-
log.info(` 👋 It appears to be your first ${italic(domain)} deployment.`)
75-
console.log('')
76-
log.info(italic('Let’s ensure it is all connected properly.'))
77-
log.info(italic('One moment...'))
78-
console.log('')
79-
80-
options.domain = domain
81-
const result = await addDomain({
82-
...options,
83-
deploy: true,
84-
startTime,
85-
})
31+
const result = await runAction(Action.Deploy, options)
8632

8733
if (result.isErr()) {
8834
await outro('While running the `buddy deploy`, there was an issue', { startTime, useSeconds: true }, result.error)
8935
process.exit(ExitCode.FatalError)
9036
}
9137

92-
await outro('Added your domain.', { startTime, useSeconds: true })
93-
process.exit(ExitCode.Success)
38+
await outro('Project deployed.', { startTime, useSeconds: true })
9439
})
9540

9641
buddy.on('deploy:*', () => {
9742
log.error('Invalid command: %s\nSee --help for a list of available commands.', buddy.args.join(' '))
9843
process.exit(1)
9944
})
10045
}
46+
47+
async function configureDomain(domain: string) {
48+
if (!domain) {
49+
log.info('We could not identify a domain to deploy to.')
50+
log.info('Please set your .env or ./config/app.ts properly.')
51+
console.log('')
52+
log.info('Alternatively, specify a domain to deploy via the `--domain` flag.')
53+
console.log('')
54+
log.info(' ➡️ Example: `buddy deploy --domain example.com`')
55+
console.log('')
56+
process.exit(ExitCode.FatalError)
57+
}
58+
59+
// TODO: we can improve this check at some point, otherwise domains that legitimately include the word localhost will fail
60+
// TODO: add check for whether the local APP_ENV is getting deployed, if so, ask if the user meant to deploy `dev`
61+
if (domain.includes('localhost')) {
62+
log.info('You are deploying to a local environment.')
63+
log.info('Please set your .env or ./config/app.ts properly.')
64+
console.log('')
65+
log.info('Alternatively, specify a domain to deploy via the `--domain` flag.')
66+
console.log('')
67+
log.info(' ➡️ Example: `buddy deploy --domain example.com`')
68+
console.log('')
69+
process.exit(ExitCode.FatalError)
70+
}
71+
72+
if (await hasUserDomainBeenAddedToCloud(domain)) {
73+
log.info('Domain is properly configured')
74+
log.info('Your cloud is deploying...')
75+
76+
log.info(`${italic('This may take a while...')}`)
77+
78+
return domain
79+
}
80+
81+
// if the domain hasn't been added to the user's (AWS) cloud, we will add it for them
82+
// and then exit the process with prompts for the user to update their nameservers
83+
console.log('')
84+
log.info(` 👋 It appears to be your first ${italic(domain)} deployment.`)
85+
console.log('')
86+
log.info(italic('Let’s ensure it is all connected properly.'))
87+
log.info(italic('One moment...'))
88+
console.log('')
89+
90+
const result = await addDomain({
91+
...options,
92+
deploy: true,
93+
startTime,
94+
})
95+
96+
if (result.isErr()) {
97+
await outro('While running the `buddy deploy`, there was an issue', { startTime, useSeconds: true }, result.error)
98+
process.exit(ExitCode.FatalError)
99+
}
100+
101+
await outro('Added your domain.', { startTime, useSeconds: true })
102+
process.exit(ExitCode.Success)
103+
}
104+
105+
async function checkIfAwsIsConfigured() {
106+
log.info('Checking if AWS is configured...')
107+
const result = await runCommand('buddy configure:aws --quiet', {
108+
silent: true,
109+
})
110+
111+
if (result.isErr()) {
112+
log.error('AWS is not configured properly.')
113+
log.error('Please run `buddy configure:aws` to set up your AWS credentials.')
114+
process.exit(ExitCode.FatalError)
115+
}
116+
117+
log.info('AWS is configured')
118+
}

0 commit comments

Comments
 (0)