@stacksjs/cli 0.64.6 → 0.65.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/src/exec.ts CHANGED
@@ -1,8 +1,8 @@
1
+ import type { CliOptions, ErrorLike, SpawnOptions, Subprocess } from '@stacksjs/types'
1
2
  import process from 'node:process'
2
- import { type Result, err, handleError, ok } from '@stacksjs/error-handling'
3
- import type { CliOptions, Subprocess } from '@stacksjs/types'
3
+ import { err, handleError, ok, type Result } from '@stacksjs/error-handling'
4
4
  import { ExitCode } from '@stacksjs/types'
5
- import { log } from './'
5
+ import { italic, log } from './'
6
6
 
7
7
  /**
8
8
  * Execute a command.
@@ -25,15 +25,16 @@ import { log } from './'
25
25
  * ```
26
26
  */
27
27
  export async function exec(command: string | string[], options?: CliOptions): Promise<Result<Subprocess, Error>> {
28
- const cmd = Array.isArray(command) ? command : command.match(/(?:[^\s"]+|"[^"]*")+/g)
28
+ const cmd = Array.isArray(command) ? command : command.match(/(?:[^\s"]|"[^"]*")+/g)
29
29
 
30
- if (!cmd) return err(handleError(`Failed to parse command: ${cmd}`, options))
30
+ if (!cmd)
31
+ return err(handleError(`Failed to parse command: ${cmd}`, options))
31
32
 
32
33
  log.debug('exec:', Array.isArray(command) ? command.join(' ') : command)
33
34
  log.debug('cmd:', cmd)
34
35
  log.debug('exec options:', options)
35
- const cwd = options?.cwd || process.cwd()
36
36
 
37
+ const cwd = options?.cwd ?? process.cwd()
37
38
  const proc = Bun.spawn(cmd, {
38
39
  ...options,
39
40
  stdout:
@@ -42,7 +43,12 @@ export async function exec(command: string | string[], options?: CliOptions): Pr
42
43
  detached: options?.background || false,
43
44
  cwd,
44
45
  // env: { ...e, ...options?.env },
45
- onExit(subprocess, exitCode, signalCode, error) {
46
+ onExit(
47
+ subprocess: Subprocess<SpawnOptions.Writable, SpawnOptions.Readable, SpawnOptions.Readable>,
48
+ exitCode: number | null,
49
+ signalCode: number | null,
50
+ error: ErrorLike | undefined,
51
+ ) {
46
52
  exitHandler('spawn', subprocess, exitCode, signalCode, error)
47
53
  },
48
54
  })
@@ -59,9 +65,10 @@ export async function exec(command: string | string[], options?: CliOptions): Pr
59
65
  }
60
66
 
61
67
  const exited = await proc.exited
62
- if (exited === ExitCode.Success) return ok(proc)
68
+ if (exited === ExitCode.Success)
69
+ return ok(proc)
63
70
 
64
- return err(handleError(`Failed to execute command: ${cmd.join(' ')}`))
71
+ return err(handleError(`Failed to execute command: ${italic(cmd.join(' '))} in ${italic(cwd)}`, options))
65
72
  }
66
73
 
67
74
  /**
@@ -84,7 +91,7 @@ export async function execSync(command: string | string[], options?: CliOptions)
84
91
  log.debug('Running ExecSync:', command)
85
92
  log.debug('ExecSync Options:', options)
86
93
 
87
- const cmd = Array.isArray(command) ? command : command.match(/(?:[^\s"]+|"[^"]*")+/g)
94
+ const cmd = Array.isArray(command) ? command : command.match(/(?:[^\s"]|"[^"]*")+/g)
88
95
 
89
96
  if (!cmd) {
90
97
  log.error(`Failed to parse command: ${cmd}`, options)
@@ -98,16 +105,26 @@ export async function execSync(command: string | string[], options?: CliOptions)
98
105
  stderr: options?.stderr ?? 'inherit',
99
106
  cwd: options?.cwd ?? process.cwd(),
100
107
  // env: { ...Bun.env, ...options?.env },
101
- onExit(subprocess, exitCode, signalCode, error) {
108
+ onExit(
109
+ subprocess: Subprocess<SpawnOptions.Writable, SpawnOptions.Readable, SpawnOptions.Readable>,
110
+ exitCode: number | null,
111
+ signalCode: number | null,
112
+ error: ErrorLike | undefined,
113
+ ) {
102
114
  exitHandler('spawnSync', subprocess, exitCode, signalCode, error)
103
115
  },
104
116
  })
105
117
 
106
- return proc.stdout.toString()
118
+ return proc.stdout?.toString() ?? ''
107
119
  }
108
120
 
109
- // @ts-expect-error - missing types is okay here but can be improved later on
110
- function exitHandler(type: 'spawn' | 'spawnSync', subprocess, exitCode, signalCode, error) {
121
+ function exitHandler(
122
+ type: 'spawn' | 'spawnSync',
123
+ subprocess: Subprocess,
124
+ exitCode: number | null,
125
+ signalCode: number | null,
126
+ error?: Error,
127
+ ) {
111
128
  log.debug(`exitHandler: ${type}`)
112
129
  log.debug('subprocess', subprocess)
113
130
  log.debug('exitCode', exitCode)
@@ -118,5 +135,6 @@ function exitHandler(type: 'spawn' | 'spawnSync', subprocess, exitCode, signalCo
118
135
  process.exit(ExitCode.FatalError)
119
136
  }
120
137
 
121
- if (exitCode !== ExitCode.Success && exitCode) process.exit(exitCode)
138
+ if (exitCode !== ExitCode.Success && exitCode)
139
+ process.exit(exitCode)
122
140
  }
package/src/helpers.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import type { IntroOptions, OutroOptions } from '@stacksjs/types'
1
2
  import { handleError } from '@stacksjs/error-handling'
2
3
  import { log } from '@stacksjs/logging'
3
- import type { IntroOptions, OutroOptions } from '@stacksjs/types'
4
4
  import { ExitCode } from '@stacksjs/types'
5
5
  import { bgCyan, bold, cyan, dim, gray, green, italic } from 'kolorist'
6
6
  import { version } from '../package.json'
@@ -18,7 +18,8 @@ export async function intro(command: string, options?: IntroOptions): Promise<nu
18
18
 
19
19
  log.info(`Running ${bgCyan(italic(bold(` ${command} `)))}`)
20
20
 
21
- if (options?.showPerformance === false || options?.quiet) return resolve(0)
21
+ if (options?.showPerformance === false || options?.quiet)
22
+ return resolve(0)
22
23
 
23
24
  return resolve(performance.now())
24
25
  })
@@ -27,7 +28,7 @@ export async function intro(command: string, options?: IntroOptions): Promise<nu
27
28
  /**
28
29
  * Prints the outro message.
29
30
  */
30
- export function outro(text: string, options?: OutroOptions, error?: Error | string) {
31
+ export function outro(text: string, options?: OutroOptions, error?: Error | string): Promise<number> {
31
32
  const opts = {
32
33
  type: 'success',
33
34
  useSeconds: true,
@@ -37,7 +38,8 @@ export function outro(text: string, options?: OutroOptions, error?: Error | stri
37
38
  opts.message = options?.message || text
38
39
 
39
40
  return new Promise((resolve) => {
40
- if (error) return handleError(error)
41
+ if (error)
42
+ return handleError(error)
41
43
 
42
44
  if (opts?.startTime) {
43
45
  let time = performance.now() - opts.startTime
@@ -47,21 +49,29 @@ export function outro(text: string, options?: OutroOptions, error?: Error | stri
47
49
  time = Math.round(time * 100) / 100 // https://stackoverflow.com/a/11832950/7811162
48
50
  }
49
51
 
50
- if (opts.quiet === true) return resolve(ExitCode.Success)
52
+ if (opts.quiet === true)
53
+ return resolve(ExitCode.Success)
51
54
 
52
- if (error) log.error(`[${time.toFixed(2)}${opts.useSeconds ? 's' : 'ms'}] Failed`)
53
- else if (opts.type === 'info')
55
+ if (error) {
56
+ log.error(`[${time.toFixed(2)}${opts.useSeconds ? 's' : 'ms'}] Failed`)
57
+ }
58
+ else if (opts.type === 'info') {
54
59
  log.info(`${dim(gray(`[${time.toFixed(2)}${opts.useSeconds ? 's' : 'ms'}]`))} ${opts.message ?? 'Complete'}`)
55
- else
60
+ }
61
+ else {
56
62
  log.success(
57
63
  `${dim(gray(bold(`[${time.toFixed(2)}${opts.useSeconds ? 's' : 'ms'}]`)))} ${bold(
58
64
  green(opts.message ?? 'Complete'),
59
65
  )}`,
60
66
  )
61
- } else {
62
- if (opts?.type === 'info') log.info(text)
67
+ }
68
+ }
69
+ else {
70
+ if (opts?.type === 'info')
71
+ log.info(text)
63
72
  // the following condition triggers in the case of "Cleaned up" messages
64
- else if (opts?.type === 'success' && opts?.quiet !== true) log.success(text)
73
+ else if (opts?.type === 'success' && opts?.quiet !== true)
74
+ log.success(text)
65
75
  }
66
76
 
67
77
  return resolve(ExitCode.Success)
package/src/index.ts CHANGED
@@ -2,9 +2,9 @@ export * from './actions'
2
2
  export * from './cli'
3
3
  export * from './command'
4
4
  export * from './console'
5
+ export * from './exec'
5
6
  export * from './helpers'
6
7
  export * from './parse'
7
- export * from './exec'
8
8
  export * from './run'
9
9
  export * from './spinner'
10
10
  export * from './utils'
package/src/parse.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import process from 'node:process'
2
- import { log } from '@stacksjs/logging'
3
2
 
4
3
  interface ParsedArgv {
5
4
  args: string[]
@@ -9,7 +8,8 @@ interface ParsedArgv {
9
8
  }
10
9
 
11
10
  function isLongOption(arg?: string): boolean {
12
- if (!arg) return false
11
+ if (!arg)
12
+ return false
13
13
 
14
14
  return arg.startsWith('--')
15
15
  }
@@ -19,12 +19,15 @@ function isShortOption(arg: string): boolean {
19
19
  }
20
20
 
21
21
  function parseValue(value: string): string | boolean | number {
22
- if (value === 'true') return true
22
+ if (value === 'true')
23
+ return true
23
24
 
24
- if (value === 'false') return false
25
+ if (value === 'false')
26
+ return false
25
27
 
26
28
  const numberValue = Number.parseFloat(value)
27
- if (!Number.isNaN(numberValue)) return numberValue
29
+ if (!Number.isNaN(numberValue))
30
+ return numberValue
28
31
 
29
32
  return value.replace(/"/g, '')
30
33
  }
@@ -38,10 +41,12 @@ function parseLongOption(
38
41
  const [key, value] = arg.slice(2).split('=')
39
42
  if (value !== undefined) {
40
43
  options[key as string] = parseValue(value)
41
- } else if (index + 1 < argv.length && !argv[index + 1]?.startsWith('-')) {
44
+ }
45
+ else if (index + 1 < argv.length && !argv[index + 1]?.startsWith('-')) {
42
46
  options[key as string] = argv[index + 1] as string
43
47
  index++
44
- } else {
48
+ }
49
+ else {
45
50
  options[key as string] = true
46
51
  }
47
52
  return index
@@ -56,16 +61,19 @@ function parseShortOption(
56
61
  const [key, value] = arg.slice(1).split('=')
57
62
 
58
63
  // Check if key is undefined and handle it
59
- if (key === undefined) return index
64
+ if (key === undefined)
65
+ return index
60
66
 
61
67
  if (value !== undefined && key !== undefined) {
62
68
  for (let j = 0; j < key.length; j++) options[key[j] as string] = parseValue(value)
63
- } else {
69
+ }
70
+ else {
64
71
  for (let j = 0; j < key.length; j++) {
65
72
  if (index + 1 < argv.length && j === key.length - 1 && !argv[index + 1]?.startsWith('-')) {
66
73
  options[key[j] as string] = parseValue(argv[index + 1] as string)
67
74
  index++
68
- } else {
75
+ }
76
+ else {
69
77
  options[key[j] as string] = true
70
78
  }
71
79
  }
@@ -75,16 +83,20 @@ function parseShortOption(
75
83
  }
76
84
 
77
85
  export function parseArgv(argv?: string[]): ParsedArgv {
78
- if (argv === undefined) argv = process.argv.slice(2)
86
+ if (argv === undefined)
87
+ argv = process.argv.slice(2)
79
88
 
80
89
  const args: string[] = []
81
90
  const options: { [k: string]: string | boolean | number } = {}
82
91
 
83
92
  for (let i = 0; i < argv.length; i++) {
84
93
  const arg = argv[i]
85
- if (!arg) continue
86
- if (isLongOption(arg)) i = parseLongOption(arg, argv, i, options)
87
- else if (isShortOption(arg)) i = parseShortOption(arg, argv, i, options)
94
+ if (!arg)
95
+ continue
96
+ if (isLongOption(arg))
97
+ i = parseLongOption(arg, argv, i, options)
98
+ else if (isShortOption(arg))
99
+ i = parseShortOption(arg, argv, i, options)
88
100
  else args.push(arg)
89
101
  }
90
102
 
@@ -92,7 +104,8 @@ export function parseArgv(argv?: string[]): ParsedArgv {
92
104
  }
93
105
 
94
106
  export function parseArgs(argv?: string[]): string[] {
95
- if (argv === undefined) argv = process.argv.slice(2)
107
+ if (argv === undefined)
108
+ argv = process.argv.slice(2)
96
109
 
97
110
  return parseArgv(argv).args
98
111
  }
@@ -106,6 +119,7 @@ interface CliOptions {
106
119
 
107
120
  export function parseOptions(options?: CliOptions): CliOptions {
108
121
  options = options || {}
122
+ const defaults = { dryRun: false, quiet: false, verbose: false }
109
123
  const args = process.argv.slice(2)
110
124
 
111
125
  for (let i = 0; i < args.length; i++) {
@@ -114,57 +128,55 @@ export function parseOptions(options?: CliOptions): CliOptions {
114
128
  const key = arg.substring(2) // remove the --
115
129
  const camelCaseKey = key.replace(
116
130
  /-([a-z])/gi,
117
- (g) => (g[1] ? g[1].toUpperCase() : ''), // convert kebab-case to camelCase
131
+ g => (g[1] ? g[1].toUpperCase() : ''), // convert kebab-case to camelCase
118
132
  )
119
133
 
120
- if (i + 1 < args.length) {
121
- // if the next arg exists
122
- if (args[i + 1] === 'true' || args[i + 1] === 'false') {
134
+ if (i + 1 < args.length && !args?.[i + 1]?.startsWith('--')) {
135
+ // if the next arg exists and is not an option
136
+ if (args?.[i + 1] === 'true' || args?.[i + 1] === 'false') {
123
137
  // if the next arg is a boolean
124
138
  options[camelCaseKey] = args[i + 1] === 'true' // set the value to the boolean
125
139
  i++
126
- } else {
140
+ }
141
+ else {
127
142
  options[camelCaseKey] = args[i + 1]
128
143
  i++
129
144
  }
130
- } else {
145
+ }
146
+ else {
131
147
  options[camelCaseKey] = true
132
148
  }
133
149
  }
134
150
  }
135
151
 
136
- // if options has no keys, return undefined, e.g. `buddy release`
137
- if (Object.keys(options).length === 0) return { dryRun: false, quiet: false, verbose: false }
138
-
139
- // convert the string 'true' or 'false' to a boolean
140
- Object.keys(options).forEach((key) => {
141
- if (!options) return { dryRun: false, quiet: false, verbose: false }
142
-
143
- const value = options[key]
152
+ if (Object.keys(options).length === 0)
153
+ // if options has no keys, return an empty object
154
+ return {}
144
155
 
145
- if (value === 'true' || value === 'false') options[key] = value === 'true'
146
- })
147
-
148
- return options
156
+ return { ...defaults, ...options }
149
157
  }
158
+
150
159
  // interface BuddyOptions {
151
160
  // dryRun?: boolean
152
161
  // verbose?: boolean
153
162
  // }
154
- export function buddyOptions(options?: any): string {
155
- if (!options) {
156
- options = process.argv.slice(2)
157
- options = Array.from(new Set(options))
158
- // delete the 0 element if it does not start with a -
159
- // e.g. is used when buddy changelog --dry-run is used
160
- if (options[0] && !options[0].startsWith('-')) options.shift()
163
+ export function buddyOptions(options?: string[] | Record<string, any>): string {
164
+ if (Array.isArray(options)) {
165
+ options = Array.from(new Set(options)) as string[]
166
+ if (Array.isArray(options) && options[0] && !options[0].startsWith('-'))
167
+ options.shift()
168
+ return options.join(' ')
161
169
  }
162
170
 
163
- if (options?.verbose) {
164
- log.debug('process.argv', process.argv)
165
- log.debug('process.argv.slice(2)', process.argv.slice(2))
166
- log.debug('options inside buddyOptions', options)
171
+ if (typeof options === 'object' && options !== null) {
172
+ return Object.entries(options)
173
+ .map(([key, value]) => {
174
+ if (value === true)
175
+ return `--${key}`
176
+ return `--${key} ${value}`
177
+ })
178
+ .join(' ')
167
179
  }
168
180
 
169
- return options.join(' ')
181
+ return buddyOptions(process.argv.slice(2))
170
182
  }
package/src/run.ts CHANGED
@@ -1,5 +1,6 @@
1
- import type { Result } from '@stacksjs/error-handling'
2
- import type { CliOptions, CommandError, Subprocess } from '@stacksjs/types'
1
+ import type { Ok, Result } from '@stacksjs/error-handling'
2
+ import type { CliOptions, CommandError, Readable, Subprocess, Writable } from '@stacksjs/types'
3
+ import process from 'node:process'
3
4
  import { ExitCode } from '@stacksjs/types'
4
5
  import { log } from './console'
5
6
  import { exec, execSync } from './exec'
@@ -90,7 +91,10 @@ export async function runCommandSync(command: string, options?: CliOptions): Pro
90
91
  * @param options The options to pass to the command.
91
92
  * @returns The result of the command.
92
93
  */
93
- export async function runCommands(commands: string[], options?: CliOptions) {
94
+ export async function runCommands(
95
+ commands: string[],
96
+ options?: CliOptions,
97
+ ): Promise<Ok<Subprocess<Writable, Readable, Readable>, Error>[]> {
94
98
  const results = []
95
99
 
96
100
  for (const command of commands) {
package/src/spinner.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import ora from 'ora'
2
2
 
3
- export const spinner = ora
3
+ export const spinner: typeof ora = ora
package/src/utils.ts CHANGED
@@ -1,20 +1,21 @@
1
- import { collect } from '@stacksjs/collections'
2
-
3
- export * as kolorist from 'kolorist'
1
+ import { collect, type Collection } from '@stacksjs/collections'
4
2
 
5
3
  export {
6
- stripAnsi,
7
- centerAlign,
8
- rightAlign,
9
- leftAlign,
10
4
  align,
11
5
  box,
6
+ centerAlign,
7
+ colorize,
12
8
  colors,
13
9
  getColor,
14
- colorize,
10
+ leftAlign,
11
+ rightAlign,
12
+ stripAnsi,
15
13
  } from 'consola/utils'
16
14
 
15
+ export * as kolorist from 'kolorist'
16
+
17
17
  export {
18
+ ansi256,
18
19
  ansi256Bg,
19
20
  bgBlack,
20
21
  bgBlue,
@@ -54,16 +55,15 @@ export {
54
55
  red,
55
56
  reset,
56
57
  strikethrough,
58
+ stripColors,
59
+ trueColor,
60
+ trueColorBg,
57
61
  underline,
58
62
  white,
59
63
  yellow,
60
- ansi256,
61
- trueColor,
62
- trueColorBg,
63
- stripColors,
64
64
  } from 'kolorist'
65
65
 
66
- export const quotes = collect([
66
+ export const quotes: Collection<string> = collect([
67
67
  // could be queried from any API or database
68
68
  'The best way to get started is to quit talking and begin doing.',
69
69
  'The pessimist sees difficulty in every opportunity. The optimist sees opportunity in every difficulty.',