@stacksjs/cli 0.58.48 → 0.58.49

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/index.js CHANGED
@@ -14,7 +14,7 @@ async function installStack(name, options) {
14
14
  // src/cli.ts
15
15
  import cac from "cac";
16
16
  // package.json
17
- var version = "0.58.48";
17
+ var version = "0.58.49";
18
18
 
19
19
  // src/cli.ts
20
20
  function cli(name, options) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/cli",
3
3
  "type": "module",
4
- "version": "0.58.48",
4
+ "version": "0.58.49",
5
5
  "description": "TypeScript framework for CLI artisans. Build beautiful console apps with ease.",
6
6
  "author": "Chris Breuer",
7
7
  "license": "MIT",
@@ -48,7 +48,8 @@
48
48
  ],
49
49
  "files": [
50
50
  "README.md",
51
- "dist"
51
+ "dist",
52
+ "src"
52
53
  ],
53
54
  "scripts": {
54
55
  "build": "bun --bun build.ts",
@@ -0,0 +1 @@
1
+ export * from './install'
@@ -0,0 +1,40 @@
1
+ import type { ExecaReturnValue } from 'execa'
2
+ import { installPackage as installPkg } from '@antfu/install-pkg'
3
+
4
+ interface InstallPackageOptions {
5
+ cwd?: string
6
+ dev?: boolean
7
+ silent?: boolean
8
+ packageManager?: string
9
+ packageManagerVersion?: string
10
+ preferOffline?: boolean
11
+ additionalArgs?: string[]
12
+ }
13
+
14
+ /**
15
+ * Install an npm package.
16
+ *
17
+ * @param name - The package name to install.
18
+ * @param options - The options to pass to the install.The options to pass to the install.
19
+ * @returns The result of the install.
20
+ */
21
+ export async function installPackage(name: string, options?: InstallPackageOptions): Promise<ExecaReturnValue<string>> {
22
+ if (options)
23
+ return await installPkg(name, options)
24
+
25
+ return await installPkg(name, { silent: true })
26
+ }
27
+
28
+ /**
29
+ * Install a Stack into your project.
30
+ *
31
+ * @param name - The Stack name to install.
32
+ * @param options - The options to pass to the install.
33
+ * @returns The result of the install.
34
+ */
35
+ export async function installStack(name: string, options?: InstallPackageOptions) {
36
+ if (options)
37
+ return await installPkg(`@stacksjs/${name}`, options)
38
+
39
+ return await installPkg(`@stacksjs/${name}`, { silent: true })
40
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,52 @@
1
+ // import type { CliOptions } from '@stacksjs/types'
2
+ import cac from 'cac'
3
+ import { version } from '../package.json'
4
+
5
+ interface ParsedArgv {
6
+ args: ReadonlyArray<string>
7
+ options: {
8
+ [k: string]: any
9
+ }
10
+ }
11
+
12
+ interface CliOptions {
13
+ name?: string
14
+ version: string
15
+ description: string
16
+ }
17
+
18
+ export function cli(name?: string | CliOptions, options?: CliOptions) {
19
+ if (typeof name === 'object') {
20
+ options = name
21
+ name = options.name
22
+ }
23
+
24
+ const cli = cac(name)
25
+
26
+ cli.help()
27
+ cli.version(options?.version || version)
28
+
29
+ return cli
30
+ }
31
+
32
+ export function command(name: string, description: string, options?: CliOptions) {
33
+ return cli(options).command(name, description)
34
+ }
35
+
36
+ export function parseArgs() {
37
+ return cli().parse().args
38
+ }
39
+
40
+ export function parseOptions(): ParsedArgv['options'] {
41
+ const options = cli().parse().options
42
+
43
+ // Iterate over the options and convert "true" and "false" strings to boolean
44
+ for (const key in options) {
45
+ if (options[key] === 'true')
46
+ options[key] = true
47
+ else if (options[key] === 'false')
48
+ options[key] = false
49
+ }
50
+
51
+ return options
52
+ }
package/src/command.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { CliOptions } from '@stacksjs/types'
2
+ import { runCommand } from './run'
3
+
4
+ type CommandOptionTuple = [string, string, { default: boolean }]
5
+ interface CommandOptionObject {
6
+ name: string
7
+ description: string
8
+ default: boolean | string
9
+ }
10
+ type CommandOptions = CommandOptionTuple | CommandOptionObject[]
11
+ interface Options {
12
+ name: string
13
+ description: string
14
+ active: boolean
15
+ options: CommandOptions
16
+ run: (options?: CliOptions) => Promise<any>
17
+ onFail: (error: Error) => void
18
+ onSuccess: () => void
19
+ }
20
+
21
+ export class Command {
22
+ name: Options['name']
23
+ description: Options['description']
24
+ options: Options['options']
25
+ run: Options['run']
26
+ onFail: Options['onFail']
27
+ onSuccess: Options['onSuccess']
28
+
29
+ constructor({ name, description, options, run, onFail, onSuccess }: Options) {
30
+ this.name = name
31
+ this.description = description
32
+ this.options = options
33
+ this.run = run
34
+ this.onFail = onFail
35
+ this.onSuccess = onSuccess
36
+ }
37
+ }
38
+
39
+ export const command = {
40
+ run: async (command: string, options?: CliOptions) => {
41
+ return await runCommand(command, options)
42
+ },
43
+
44
+ runSync: async (command: string, options?: CliOptions) => {
45
+ return await runCommand(command, options)
46
+ },
47
+ }
package/src/console.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { log } from '@stacksjs/logging'
2
+ import prompts from 'prompts'
3
+
4
+ export class Prompt {
5
+ private required: boolean
6
+
7
+ constructor() {
8
+ this.required = false
9
+ }
10
+
11
+ require() {
12
+ this.required = true
13
+ return this
14
+ }
15
+
16
+ isRequired() {
17
+ return this.required
18
+ }
19
+
20
+ async select(message: any, options: any) {
21
+ if (this.isRequired())
22
+ return log.prompt(message, { ...options, type: 'select', required: true })
23
+
24
+ return log.prompt(message, { ...options, type: 'select' })
25
+ }
26
+
27
+ async checkbox(message: any, options: any) {
28
+ if (this.isRequired())
29
+ return log.prompt(message, { ...options, type: 'multiselect', required: true })
30
+
31
+ return log.prompt(message, { ...options, type: 'multiselect' })
32
+ }
33
+
34
+ async confirm(message: any, options: any) {
35
+ if (this.isRequired())
36
+ return log.prompt(message, { ...options, type: 'confirm', required: true })
37
+
38
+ return log.prompt(message, { ...options, type: 'confirm' })
39
+ }
40
+
41
+ async input(message: any, options: any) {
42
+ if (this.isRequired())
43
+ return log.prompt(message, { ...options, type: 'text', required: true })
44
+
45
+ return log.prompt(message, { ...options, type: 'text' })
46
+ }
47
+
48
+ async password(message: any, options: any) {
49
+ if (this.isRequired())
50
+ return log.prompt(message, { ...options, type: 'password', required: true })
51
+
52
+ return log.prompt(message, { ...options, type: 'password' })
53
+ }
54
+
55
+ async number(message: any, options: any) {
56
+ if (this.isRequired())
57
+ return log.prompt(message, { ...options, type: 'numeral', required: true })
58
+
59
+ return log.prompt(message, { ...options, type: 'numeral' })
60
+ }
61
+
62
+ async multiselect(message: any, options: any) {
63
+ if (this.isRequired())
64
+ return log.prompt(message, { ...options, type: 'multiselect', required: true })
65
+
66
+ return log.prompt(message, { ...options, type: 'multiselect' })
67
+ }
68
+
69
+ async autocomplete(message: any, options: any) {
70
+ if (this.isRequired())
71
+ return log.prompt(message, { ...options, type: 'autocomplete', required: true })
72
+
73
+ return log.prompt(message, { ...options, type: 'autocomplete' })
74
+ }
75
+ }
76
+
77
+ export { prompts, log }
78
+
79
+ export const prompt = new Prompt()
package/src/exec.ts ADDED
@@ -0,0 +1,89 @@
1
+ import process from 'node:process'
2
+ import { type Result, err, handleError, ok } from '@stacksjs/error-handling'
3
+ import type { CliOptions, StacksError, Subprocess } from '@stacksjs/types'
4
+ import { ExitCode } from '@stacksjs/types'
5
+
6
+ /**
7
+ * Execute a command.
8
+ *
9
+ * @param command The command to execute.
10
+ * @param options The options to pass to the command.
11
+ * @returns The result of the command.
12
+ * @example
13
+ * ```ts
14
+ * const result = await exec('ls')
15
+ *
16
+ * if (result.isErr())
17
+ * console.error(result.error)
18
+ * else
19
+ * console.log(result)
20
+ * ```
21
+ * @example
22
+ * ```ts
23
+ * const result = await exec('ls', { cwd: '/home' })
24
+ * ```
25
+ */
26
+ export async function exec(command: string | string[], options?: CliOptions): Promise<Result<Subprocess, StacksError>> {
27
+ const cmd = Array.isArray(command) ? command : command.match(/(?:[^\s"]+|"[^"]*")+/g)
28
+
29
+ if (!cmd)
30
+ return err(handleError(`Failed to parse command: ${cmd}`, options))
31
+
32
+ if (options?.verbose)
33
+ // eslint-disable-next-line no-console
34
+ console.log('exec', { command, cmd, options })
35
+
36
+ const proc = Bun.spawn(cmd, {
37
+ ...options,
38
+ stdout: options?.silent ? 'ignore' : (options?.stdin ? options.stdin : (options?.stdout || 'inherit')),
39
+ stderr: options?.silent ? 'ignore' : (options?.stderr || 'inherit'),
40
+ detached: options?.background || false,
41
+ cwd: options?.cwd || import.meta.dir,
42
+ // env: { ...e, ...options?.env },
43
+ onExit(_subprocess, exitCode, _signalCode, _error) {
44
+ if (exitCode && exitCode !== ExitCode.Success)
45
+ process.exit(exitCode)
46
+ },
47
+ })
48
+
49
+ const exited = await proc.exited
50
+ if (exited === ExitCode.Success)
51
+ return ok(proc)
52
+
53
+ return err(handleError(`Failed to execute command: ${cmd.join(' ')}`))
54
+ }
55
+
56
+ /**
57
+ * Execute a command and return result.
58
+ *
59
+ * @param command The command to execute.
60
+ * @returns The result of the command.
61
+ * @example
62
+ * ```ts
63
+ * const output = execSync('ls')
64
+ *
65
+ * console.log(output)
66
+ * ```
67
+ * @example
68
+ * ```ts
69
+ * const output = execSync('ls', { cwd: '/home' })
70
+ * ```
71
+ */
72
+ export async function execSync(command: string | string[], options?: CliOptions): Promise<string> {
73
+ const cmd = Array.isArray(command) ? command : command.split(' ')
74
+ const proc = Bun.spawnSync(cmd, {
75
+ ...options,
76
+ // stdin: 'inherit',
77
+ stdout: options?.stdout ?? 'pipe',
78
+ stderr: options?.stderr ?? 'inherit',
79
+ cwd: options?.cwd ?? import.meta.dir,
80
+ // env: { ...Bun.env, ...options?.env },
81
+ onExit(_subprocess, exitCode, _signalCode, _error) {
82
+ // console.log('onExit', { subprocess, exitCode, signalCode, error })
83
+ if (exitCode !== ExitCode.Success && exitCode)
84
+ process.exit(exitCode)
85
+ },
86
+ })
87
+
88
+ return proc.stdout.toString()
89
+ }
package/src/helpers.ts ADDED
@@ -0,0 +1,80 @@
1
+ /* eslint-disable no-console */
2
+ import { config } from '@stacksjs/config'
3
+ import { handleError } from '@stacksjs/error-handling'
4
+ import { log } from '@stacksjs/logging'
5
+ import type { IntroOptions, OutroOptions } from '@stacksjs/types'
6
+ import { ExitCode } from '@stacksjs/types'
7
+ import { bgCyan, bold, cyan, dim, gray, green, italic } from 'kolorist'
8
+ import { version } from '../package.json'
9
+
10
+ /**
11
+ * Prints the intro message.
12
+ */
13
+ export async function intro(command: string, options?: IntroOptions): Promise<number> {
14
+ return new Promise((resolve) => {
15
+ if (options?.quiet === false) {
16
+ console.log()
17
+ console.log(cyan(bold('Stacks CLI')) + dim(` v${version}`))
18
+ console.log()
19
+ }
20
+
21
+ let msg = `Running ${bgCyan(italic(bold(` ${command} `)))}`
22
+ if (command === 'buddy deploy')
23
+ msg = `Running ${bgCyan(italic(bold(` ${command} `)))} for ${bold(`${config.app.name}`)} ${italic(`via ${config.app.url}`)}`
24
+
25
+ log.info(msg)
26
+
27
+ if (options?.showPerformance === false || options?.quiet)
28
+ return resolve(0)
29
+
30
+ return resolve(performance.now())
31
+ })
32
+ }
33
+
34
+ /**
35
+ * Prints the outro message.
36
+ */
37
+ export function outro(text: string, options?: OutroOptions, error?: Error | string) {
38
+ const opts = {
39
+ type: 'success',
40
+ useSeconds: true,
41
+ ...options,
42
+ }
43
+
44
+ opts.message = options?.message || text
45
+
46
+ return new Promise((resolve) => {
47
+ if (error)
48
+ return handleError(error)
49
+
50
+ if (opts?.startTime) {
51
+ let time = performance.now() - opts.startTime
52
+
53
+ if (opts.useSeconds) {
54
+ time = time / 1000
55
+ time = Math.round(time * 100) / 100 // https://stackoverflow.com/a/11832950/7811162
56
+ }
57
+
58
+ if (opts.quiet === true)
59
+ return resolve(ExitCode.Success)
60
+
61
+ if (error)
62
+ log.error(`[${time.toFixed(2)}${opts.useSeconds ? 's' : 'ms'}] Failed`)
63
+ else if (opts.type === 'info')
64
+ console.log(`${dim(gray(`[${time.toFixed(2)}${opts.useSeconds ? 's' : 'ms'}]`))} ${opts.message ?? 'Complete'}`)
65
+ else
66
+ console.log(`${dim(gray(bold(`[${time.toFixed(2)}${opts.useSeconds ? 's' : 'ms'}]`)))} ${bold(green(opts.message ?? 'Complete'))}`)
67
+ }
68
+
69
+ else {
70
+ if (opts?.type === 'info')
71
+ console.log(text)
72
+
73
+ // the following condition triggers in the case of "Cleaned up" messages
74
+ else if (opts?.type === 'success' && opts?.quiet !== true)
75
+ log.success(text)
76
+ }
77
+
78
+ return resolve(ExitCode.Success)
79
+ })
80
+ }
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ export * from './actions'
2
+ export * from './cli'
3
+ export * from './command'
4
+ export * from './console'
5
+ export * from './helpers'
6
+ export * from './parse'
7
+ export * from './exec'
8
+ export * from './run'
9
+ export * from './spinner'
10
+ export * from './utilities'
package/src/parse.ts ADDED
@@ -0,0 +1,101 @@
1
+ import process from 'node:process'
2
+
3
+ interface ParsedArgv {
4
+ args: string[]
5
+ options: {
6
+ [k: string]: string | boolean | number
7
+ }
8
+ }
9
+
10
+ function isLongOption(arg: string): boolean {
11
+ return arg.startsWith('--')
12
+ }
13
+
14
+ function isShortOption(arg: string): boolean {
15
+ return arg.startsWith('-') && !isLongOption(arg)
16
+ }
17
+
18
+ function parseValue(value: string): string | boolean | number {
19
+ if (value === 'true')
20
+ return true
21
+
22
+ if (value === 'false')
23
+ return false
24
+
25
+ const numberValue = Number.parseFloat(value)
26
+ if (!Number.isNaN(numberValue))
27
+ return numberValue
28
+
29
+ return value.replace(/"/g, '')
30
+ }
31
+
32
+ function parseLongOption(arg: string, argv: string[], index: number, options: { [k: string]: string | boolean | number }): number {
33
+ const [key, value] = arg.slice(2).split('=')
34
+ if (value !== undefined) {
35
+ options[key] = parseValue(value)
36
+ }
37
+ else if (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
38
+ options[key] = argv[index + 1]
39
+ index++
40
+ }
41
+ else {
42
+ options[key] = true
43
+ }
44
+ return index
45
+ }
46
+
47
+ function parseShortOption(arg: string, argv: string[], index: number, options: { [k: string]: string | boolean | number }): number {
48
+ const [key, value] = arg.slice(1).split('=')
49
+
50
+ if (value !== undefined) {
51
+ for (let j = 0; j < key.length; j++)
52
+ options[key[j]] = parseValue(value)
53
+ }
54
+ else {
55
+ for (let j = 0; j < key.length; j++) {
56
+ if (index + 1 < argv.length && j === key.length - 1 && !argv[index + 1].startsWith('-')) {
57
+ options[key[j]] = parseValue(argv[index + 1])
58
+ index++
59
+ }
60
+ else {
61
+ options[key[j]] = true
62
+ }
63
+ }
64
+ }
65
+
66
+ return index
67
+ }
68
+
69
+ export function parseArgv(argv?: string[]): ParsedArgv {
70
+ if (argv === undefined)
71
+ argv = process.argv.slice(2)
72
+
73
+ const args: string[] = []
74
+ const options: { [k: string]: string | boolean | number } = {}
75
+
76
+ for (let i = 0; i < argv.length; i++) {
77
+ const arg = argv[i]
78
+ if (isLongOption(arg))
79
+ i = parseLongOption(arg, argv, i, options)
80
+ else if (isShortOption(arg))
81
+ i = parseShortOption(arg, argv, i, options)
82
+ else
83
+ args.push(arg)
84
+ }
85
+
86
+ return { args, options }
87
+ }
88
+
89
+ // export function parseOptions(argv?: string[]): { [k: string]: string | boolean | number } {
90
+ // if (argv === undefined)
91
+ // argv = process.argv.slice(2)
92
+
93
+ // return parseArgv(argv).options
94
+ // }
95
+
96
+ export function parseArgs(argv?: string[]): string[] {
97
+ if (argv === undefined)
98
+ argv = process.argv.slice(2)
99
+
100
+ return parseArgv(argv).args
101
+ }
package/src/run.ts ADDED
@@ -0,0 +1,99 @@
1
+ import type { CliOptions, CommandError, Subprocess } from '@stacksjs/types'
2
+ import type { Result } from '@stacksjs/error-handling'
3
+ import { exec, execSync } from './exec'
4
+ import { italic, underline } from './utilities'
5
+ import { log } from './console'
6
+
7
+ /**
8
+ * Run a command.
9
+ *
10
+ * @param command The command to run.
11
+ * @param options The options to pass to the command.
12
+ * @returns The result of the command.
13
+ * @example
14
+ * ```ts
15
+ * const result = await runCommand('ls')
16
+ *
17
+ * if (result.isErr())
18
+ * console.error(result.error)
19
+ * else
20
+ * console.log(result)
21
+ * ```
22
+ * @example
23
+ * ```ts
24
+ * const result = await runCommand('ls', { cwd: '/home' })
25
+ *
26
+ * if (result.isErr())
27
+ * console.error(result.error)
28
+ * else
29
+ * console.log(result)
30
+ * ```
31
+ */
32
+ export async function runCommand(command: string, options?: CliOptions): Promise<Result<Subprocess, CommandError>> {
33
+ if (options?.verbose)
34
+ log.debug('Running command:', underline(italic(command)), 'with options:', options)
35
+
36
+ return await exec(command, options)
37
+ }
38
+
39
+ export async function runProcess(command: string, options?: CliOptions): Promise<Result<Subprocess, CommandError>> {
40
+ if (options?.verbose)
41
+ log.debug('Running command:', underline(italic(command)), 'with options:', options)
42
+
43
+ return await exec(command, options)
44
+ }
45
+
46
+ /**
47
+ * Run a command.
48
+ *
49
+ * @param command The command to run.
50
+ * @param options The options to pass to the command.
51
+ * @returns The result of the command.
52
+ * @example
53
+ * ```ts
54
+ * const result = runCommandSync('ls')
55
+ *
56
+ * if (result.isErr())
57
+ * console.error(result.error)
58
+ * else
59
+ * console.log(result)
60
+ * ```
61
+ * @example
62
+ * ```ts
63
+ * const result = runCommandSync('ls', { cwd: '/home' })
64
+ *
65
+ * if (result.isErr())
66
+ * console.error(result.error)
67
+ * else
68
+ * console.log(result)
69
+ * ```
70
+ */
71
+ export async function runCommandSync(command: string, options?: CliOptions): Promise<string> {
72
+ if (options?.verbose)
73
+ log.debug('Running command:', underline(italic(command)), 'with options:', options)
74
+
75
+ const result = await execSync(command, options)
76
+
77
+ // if (result.isErr())
78
+ // return err(result.error)
79
+
80
+ // return ok(result.value)
81
+
82
+ return result
83
+ }
84
+
85
+ /**
86
+ * Run many commands.
87
+ *
88
+ * @param commands The command to run.
89
+ * @param options The options to pass to the command.
90
+ * @returns The result of the command.
91
+ */
92
+ export async function runCommands(commands: string[], options?: CliOptions) {
93
+ const results = []
94
+
95
+ for (const command of commands)
96
+ results.push(await runCommand(command, options))
97
+
98
+ return results
99
+ }
package/src/spinner.ts ADDED
@@ -0,0 +1,3 @@
1
+ import ora from 'ora'
2
+
3
+ export const spinner = ora
@@ -0,0 +1,2 @@
1
+ export * as kolorist from 'kolorist'
2
+ export { ansi256Bg, bgBlack, bgBlue, bgCyan, bgGray, bgGreen, bgLightBlue, bgLightCyan, bgLightGray, bgLightGreen, bgLightMagenta, bgLightRed, bgLightYellow, bgMagenta, bgRed, bgWhite, bgYellow, black, blue, bold, cyan, dim, gray, green, hidden, inverse, italic, lightBlue, lightCyan, lightGray, lightGreen, lightMagenta, lightRed, lightYellow, link, magenta, red, reset, strikethrough, underline, white, yellow, ansi256, trueColor, trueColorBg, stripColors } from 'kolorist'