@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.
@@ -0,0 +1,15 @@
1
+ interface ParsedArgv {
2
+ args: string[];
3
+ options: { [k: string]: string | boolean | number };
4
+ }
5
+ export declare function parseArgv(argv?: string[]): ParsedArgv;
6
+ export declare function parseArgs(argv?: string[]): string[];
7
+ interface CliOptions {
8
+ dryRun?: boolean;
9
+ quiet?: boolean;
10
+ verbose?: boolean;
11
+ [k: string]: string | boolean | number | undefined;
12
+ }
13
+ export declare function parseOptions(options?: CliOptions): CliOptions;
14
+ export declare function buddyOptions(options?: string[] | Record<string, any>): string;
15
+ export {};
package/dist/run.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { Ok, Result } from "@stacksjs/error-handling";
2
+ import type { CliOptions, CommandError, Readable, Subprocess, Writable } from "@stacksjs/types";
3
+ /**
4
+ * Run a command.
5
+ *
6
+ * @param command The command to run.
7
+ * @param options The options to pass to the command.
8
+ * @returns The result of the command.
9
+ * @example
10
+ * ```ts
11
+ * const result = await runCommand('ls')
12
+ *
13
+ * if (result.isErr())
14
+ * console.error(result.error)
15
+ * else
16
+ * console.log(result)
17
+ * ```
18
+ * @example
19
+ * ```ts
20
+ * const result = await runCommand('ls', { cwd: '/home' })
21
+ *
22
+ * if (result.isErr())
23
+ * console.error(result.error)
24
+ * else
25
+ * console.log(result)
26
+ * ```
27
+ */
28
+ export declare function runCommand(command: string, options?: CliOptions): Promise<Result<Subprocess, CommandError>>;
29
+ export declare function runProcess(command: string, options?: CliOptions): Promise<Result<Subprocess, CommandError>>;
30
+ /**
31
+ * Run a command.
32
+ *
33
+ * @param command The command to run.
34
+ * @param options The options to pass to the command.
35
+ * @returns The result of the command.
36
+ * @example
37
+ * ```ts
38
+ * const result = runCommandSync('ls')
39
+ *
40
+ * if (result.isErr())
41
+ * console.error(result.error)
42
+ * else
43
+ * console.log(result)
44
+ * ```
45
+ * @example
46
+ * ```ts
47
+ * const result = runCommandSync('ls', { cwd: '/home' })
48
+ *
49
+ * if (result.isErr())
50
+ * console.error(result.error)
51
+ * else
52
+ * console.log(result)
53
+ * ```
54
+ */
55
+ export declare function runCommandSync(command: string, options?: CliOptions): Promise<string>;
56
+ /**
57
+ * Run many commands.
58
+ *
59
+ * @param commands The command to run.
60
+ * @param options The options to pass to the command.
61
+ * @returns The result of the command.
62
+ */
63
+ export declare function runCommands(commands: string[], options?: CliOptions): Promise<Ok<Subprocess<Writable, Readable, Readable>, Error>[]>;
@@ -0,0 +1,2 @@
1
+ import ora from "ora";
2
+ export declare const spinner: typeof ora;
@@ -0,0 +1,5 @@
1
+ import { type Collection } from "@stacksjs/collections";
2
+ export { align, box, centerAlign, colorize, colors, getColor, leftAlign, rightAlign, stripAnsi } from "consola/utils";
3
+ export * as kolorist from "kolorist";
4
+ export { ansi256, 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, stripColors, trueColor, trueColorBg, underline, white, yellow } from "kolorist";
5
+ export declare const quotes: Collection<string>;
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@stacksjs/cli",
3
3
  "type": "module",
4
- "version": "0.64.6",
4
+ "version": "0.65.0",
5
5
  "description": "TypeScript framework for CLI artisans. Build beautiful console apps with ease.",
6
6
  "author": "Chris Breuer",
7
+ "contributors": ["Chris Breuer <chris@stacksjs.org>"],
7
8
  "license": "MIT",
8
9
  "funding": "https://github.com/sponsors/chrisbbreuer",
9
10
  "homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/cli#readme",
@@ -43,30 +44,24 @@
43
44
  },
44
45
  "module": "dist/index.js",
45
46
  "types": "dist/index.d.ts",
46
- "contributors": [
47
- "Chris Breuer <chris@stacksjs.org>"
48
- ],
49
- "files": [
50
- "README.md",
51
- "dist",
52
- "src"
53
- ],
47
+ "files": ["README.md", "dist", "src"],
54
48
  "scripts": {
55
- "build": "bun --bun build.ts",
56
- "typecheck": "bun --bun tsc --noEmit",
49
+ "build": "bun build.ts",
50
+ "typecheck": "bun tsc --noEmit",
51
+ "test": "bun test",
57
52
  "prepublishOnly": "bun run build"
58
53
  },
59
54
  "dependencies": {
60
55
  "@antfu/install-pkg": "^0.4.1",
61
56
  "@clack/core": "^0.3.4",
62
- "@stacksjs/collections": "latest",
63
- "@stacksjs/config": "latest",
64
- "@stacksjs/error-handling": "latest",
65
- "@stacksjs/logging": "latest",
66
- "@stacksjs/path": "latest",
67
- "@stacksjs/types": "latest",
68
- "@stacksjs/utils": "latest",
69
- "@stacksjs/validation": "latest",
57
+ "@stacksjs/collections": "0.64.6",
58
+ "@stacksjs/config": "0.64.6",
59
+ "@stacksjs/error-handling": "0.64.6",
60
+ "@stacksjs/logging": "0.64.6",
61
+ "@stacksjs/path": "0.64.6",
62
+ "@stacksjs/types": "0.64.6",
63
+ "@stacksjs/utils": "0.64.6",
64
+ "@stacksjs/validation": "0.64.6",
70
65
  "cac": "^6.7.14",
71
66
  "consola": "^3.2.3",
72
67
  "kolorist": "1.8.0",
@@ -74,7 +69,7 @@
74
69
  "prompts": "^2.4.2"
75
70
  },
76
71
  "devDependencies": {
77
- "@stacksjs/development": "latest",
72
+ "@stacksjs/development": "0.64.6",
78
73
  "@types/prompts": "^2.4.9"
79
74
  }
80
75
  }
@@ -1,5 +1,4 @@
1
1
  import { installPackage as installPkg } from '@antfu/install-pkg'
2
- import type { ExecaReturnValue } from 'execa'
3
2
 
4
3
  interface InstallPackageOptions {
5
4
  cwd?: string
@@ -11,6 +10,8 @@ interface InstallPackageOptions {
11
10
  additionalArgs?: string[]
12
11
  }
13
12
 
13
+ // TODO: improve return types here
14
+
14
15
  /**
15
16
  * Install an npm package.
16
17
  *
@@ -18,8 +19,9 @@ interface InstallPackageOptions {
18
19
  * @param options - The options to pass to the install.The options to pass to the install.
19
20
  * @returns The result of the install.
20
21
  */
21
- export async function installPackage(name: string, options?: InstallPackageOptions) {
22
- if (options) return await installPkg(name, options)
22
+ export async function installPackage(name: string, options?: InstallPackageOptions): Promise<any> {
23
+ if (options)
24
+ return await installPkg(name, options)
23
25
 
24
26
  return await installPkg(name, { silent: true })
25
27
  }
@@ -31,8 +33,9 @@ export async function installPackage(name: string, options?: InstallPackageOptio
31
33
  * @param options - The options to pass to the install.
32
34
  * @returns The result of the install.
33
35
  */
34
- export async function installStack(name: string, options?: InstallPackageOptions) {
35
- if (options) return await installPkg(`@stacksjs/${name}`, options)
36
+ export async function installStack(name: string, options?: InstallPackageOptions): Promise<any> {
37
+ if (options)
38
+ return await installPkg(`@stacksjs/${name}`, options)
36
39
 
37
40
  return await installPkg(`@stacksjs/${name}`, { silent: true })
38
41
  }
package/src/app.ts CHANGED
@@ -1,16 +1,18 @@
1
+ /* eslint-disable ts/no-use-before-define, regexp/no-useless-non-capturing-group, regexp/no-trivially-nested-quantifier, regexp/no-useless-quantifier, regexp/prefer-w,regexp/no-useless-escape */
2
+ import process from 'node:process'
1
3
  // slightly modified version of @clack/prompts
2
4
  // many thanks to bombshell-dev for the original work
3
5
  import {
6
+ block,
4
7
  ConfirmPrompt,
5
8
  GroupMultiSelectPrompt,
9
+ isCancel,
6
10
  MultiSelectPrompt,
7
11
  PasswordPrompt,
8
12
  SelectKeyPrompt,
9
13
  SelectPrompt,
10
14
  type State,
11
15
  TextPrompt,
12
- block,
13
- isCancel,
14
16
  } from '@clack/core'
15
17
  import isUnicodeSupported from 'is-unicode-supported'
16
18
  import color from 'picocolors'
@@ -41,7 +43,7 @@ const S_CORNER_TOP_RIGHT = s('╮', '+')
41
43
  const S_CONNECT_LEFT = s('├', '+')
42
44
  const S_CORNER_BOTTOM_RIGHT = s('╯', '+')
43
45
 
44
- const symbol = (state: State) => {
46
+ function symbol(state: State) {
45
47
  switch (state) {
46
48
  case 'initial':
47
49
  case 'active':
@@ -62,7 +64,7 @@ interface LimitOptionsParams<TOption> {
62
64
  style: (option: TOption, active: boolean) => string
63
65
  }
64
66
 
65
- const limitOptions = <TOption>(params: LimitOptionsParams<TOption>): string[] => {
67
+ function limitOptions<TOption>(params: LimitOptionsParams<TOption>): string[] {
66
68
  const { cursor, options, style } = params
67
69
 
68
70
  const paramMaxItems = params.maxItems ?? Number.POSITIVE_INFINITY
@@ -73,7 +75,8 @@ const limitOptions = <TOption>(params: LimitOptionsParams<TOption>): string[] =>
73
75
 
74
76
  if (cursor >= slidingWindowLocation + maxItems - 3) {
75
77
  slidingWindowLocation = Math.max(Math.min(cursor - maxItems + 3, options.length - maxItems), 0)
76
- } else if (cursor < slidingWindowLocation + 2) {
78
+ }
79
+ else if (cursor < slidingWindowLocation + 2) {
77
80
  slidingWindowLocation = Math.max(cursor - 2, 0)
78
81
  }
79
82
 
@@ -92,10 +95,9 @@ export interface TextOptions {
92
95
  placeholder?: string
93
96
  defaultValue?: string
94
97
  initialValue?: string
95
- // biome-ignore lint/suspicious/noConfusingVoidType: originally shipped this
96
98
  validate?: (value: string) => string | void
97
99
  }
98
- export const text = (opts: TextOptions) => {
100
+ export function text(opts: TextOptions) {
99
101
  return new TextPrompt({
100
102
  validate: opts.validate,
101
103
  placeholder: opts.placeholder,
@@ -129,10 +131,9 @@ export const text = (opts: TextOptions) => {
129
131
  export interface PasswordOptions {
130
132
  message: string
131
133
  mask?: string
132
- // biome-ignore lint/suspicious/noConfusingVoidType: originally shipped this
133
134
  validate?: (value: string) => string | void
134
135
  }
135
- export const password = (opts: PasswordOptions) => {
136
+ export function password(opts: PasswordOptions) {
136
137
  return new PasswordPrompt({
137
138
  validate: opts.validate,
138
139
  mask: opts.mask ?? S_PASSWORD_MASK,
@@ -165,7 +166,7 @@ export interface ConfirmOptions {
165
166
  inactive?: string
166
167
  initialValue?: boolean
167
168
  }
168
- export const confirm = (opts: ConfirmOptions) => {
169
+ export function confirm(opts: ConfirmOptions) {
169
170
  const active = opts.active ?? 'Yes'
170
171
  const inactive = opts.inactive ?? 'No'
171
172
  return new ConfirmPrompt({
@@ -200,8 +201,8 @@ export const confirm = (opts: ConfirmOptions) => {
200
201
  type Primitive = Readonly<string | boolean | number>
201
202
 
202
203
  type Option<Value> = Value extends Primitive
203
- ? { value: Value; label?: string; hint?: string }
204
- : { value: Value; label: string; hint?: string }
204
+ ? { value: Value, label?: string, hint?: string }
205
+ : { value: Value, label: string, hint?: string }
205
206
 
206
207
  export interface SelectOptions<Value> {
207
208
  message: string
@@ -210,7 +211,7 @@ export interface SelectOptions<Value> {
210
211
  maxItems?: number
211
212
  }
212
213
 
213
- export const select = <Value>(opts: SelectOptions<Value>) => {
214
+ export function select<Value>(opts: SelectOptions<Value>) {
214
215
  const opt = (option: Option<Value>, state: 'inactive' | 'active' | 'selected' | 'cancelled') => {
215
216
  const label = option.label ?? String(option.value)
216
217
  switch (state) {
@@ -233,10 +234,8 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
233
234
 
234
235
  switch (this.state) {
235
236
  case 'submit':
236
- // biome-ignore lint/style/noNonNullAssertion: will be set
237
237
  return `${title}${color.gray(S_BAR)} ${opt(this.options[this.cursor]!, 'selected')}`
238
238
  case 'cancel':
239
- // biome-ignore lint/style/noNonNullAssertion: will be set
240
239
  return `${title}${color.gray(S_BAR)} ${opt(this.options[this.cursor]!, 'cancelled')}\n${color.gray(S_BAR)}`
241
240
  default: {
242
241
  return `${title}${color.cyan(S_BAR)} ${limitOptions({
@@ -251,7 +250,7 @@ export const select = <Value>(opts: SelectOptions<Value>) => {
251
250
  }).prompt() as Promise<Value | symbol>
252
251
  }
253
252
 
254
- export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
253
+ export function selectKey<Value extends string>(opts: SelectOptions<Value>) {
255
254
  const opt = (option: Option<Value>, state: 'inactive' | 'active' | 'selected' | 'cancelled' = 'inactive') => {
256
255
  const label = option.label ?? String(option.value)
257
256
 
@@ -283,12 +282,10 @@ export const selectKey = <Value extends string>(opts: SelectOptions<Value>) => {
283
282
  switch (this.state) {
284
283
  case 'submit':
285
284
  return `${title}${color.gray(S_BAR)} ${opt(
286
- // biome-ignore lint/style/noNonNullAssertion: will be set
287
- this.options.find((opt) => opt.value === this.value)!,
285
+ this.options.find(opt => opt.value === this.value)!,
288
286
  'selected',
289
287
  )}`
290
288
  case 'cancel':
291
- // biome-ignore lint/style/noNonNullAssertion: will be set
292
289
  return `${title}${color.gray(S_BAR)} ${opt(this.options[0]!, 'cancelled')}\n${color.gray(S_BAR)}`
293
290
  default: {
294
291
  return `${title}${color.cyan(S_BAR)} ${this.options
@@ -308,7 +305,7 @@ export interface MultiSelectOptions<Value> {
308
305
  required?: boolean
309
306
  cursorAt?: Value
310
307
  }
311
- export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
308
+ export function multiselect<Value>(opts: MultiSelectOptions<Value>) {
312
309
  const opt = (
313
310
  option: Option<Value>,
314
311
  state: 'inactive' | 'active' | 'selected' | 'active-selected' | 'submitted' | 'cancelled',
@@ -344,7 +341,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
344
341
  required: opts.required ?? true,
345
342
  cursorAt: opts.cursorAt,
346
343
  validate(selected: Value[]) {
347
- if (this.required && selected.length === 0)
344
+ if (this.required && selected.length === 0) {
348
345
  return `Please select at least one option.\n${color.reset(
349
346
  color.dim(
350
347
  `Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray(
@@ -352,6 +349,7 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
352
349
  )} to submit`,
353
350
  ),
354
351
  )}`
352
+ }
355
353
  },
356
354
  render() {
357
355
  const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`
@@ -372,14 +370,14 @@ export const multiselect = <Value>(opts: MultiSelectOptions<Value>) => {
372
370
  return `${title}${color.gray(S_BAR)} ${
373
371
  this.options
374
372
  .filter(({ value }) => this.value.includes(value))
375
- .map((option) => opt(option, 'submitted'))
373
+ .map(option => opt(option, 'submitted'))
376
374
  .join(color.dim(', ')) || color.dim('none')
377
375
  }`
378
376
  }
379
377
  case 'cancel': {
380
378
  const label = this.options
381
379
  .filter(({ value }) => this.value.includes(value))
382
- .map((option) => opt(option, 'cancelled'))
380
+ .map(option => opt(option, 'cancelled'))
383
381
  .join(color.dim(', '))
384
382
  return `${title}${color.gray(S_BAR)} ${label.trim() ? `${label}\n${color.gray(S_BAR)}` : ''}`
385
383
  }
@@ -419,7 +417,7 @@ export interface GroupMultiSelectOptions<Value> {
419
417
  required?: boolean
420
418
  cursorAt?: Value
421
419
  }
422
- export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) => {
420
+ export function groupMultiselect<Value>(opts: GroupMultiSelectOptions<Value>) {
423
421
  const opt = (
424
422
  option: Option<Value>,
425
423
  state:
@@ -480,7 +478,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
480
478
  required: opts.required ?? true,
481
479
  cursorAt: opts.cursorAt,
482
480
  validate(selected: Value[]) {
483
- if (this.required && selected.length === 0)
481
+ if (this.required && selected.length === 0) {
484
482
  return `Please select at least one option.\n${color.reset(
485
483
  color.dim(
486
484
  `Press ${color.gray(color.bgWhite(color.inverse(' space ')))} to select, ${color.gray(
@@ -488,6 +486,7 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
488
486
  )} to submit`,
489
487
  ),
490
488
  )}`
489
+ }
491
490
  },
492
491
  render() {
493
492
  const title = `${color.gray(S_BAR)}\n${symbol(this.state)} ${opts.message}\n`
@@ -496,13 +495,13 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
496
495
  case 'submit': {
497
496
  return `${title}${color.gray(S_BAR)} ${this.options
498
497
  .filter(({ value }) => this.value.includes(value))
499
- .map((option) => opt(option, 'submitted'))
498
+ .map(option => opt(option, 'submitted'))
500
499
  .join(color.dim(', '))}`
501
500
  }
502
501
  case 'cancel': {
503
502
  const label = this.options
504
503
  .filter(({ value }) => this.value.includes(value))
505
- .map((option) => opt(option, 'cancelled'))
504
+ .map(option => opt(option, 'cancelled'))
506
505
  .join(color.dim(', '))
507
506
  return `${title}${color.gray(S_BAR)} ${label.trim() ? `${label}\n${color.gray(S_BAR)}` : ''}`
508
507
  }
@@ -513,12 +512,11 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
513
512
  .join('\n')
514
513
  return `${title}${color.yellow(S_BAR)} ${this.options
515
514
  .map((option, i, options) => {
516
- const selected =
517
- this.value.includes(option.value) || (option.group === true && this.isGroupSelected(`${option.value}`))
515
+ const selected
516
+ = this.value.includes(option.value) || (option.group === true && this.isGroupSelected(`${option.value}`))
518
517
  const active = i === this.cursor
519
- const groupActive =
520
- // biome-ignore lint/style/noNonNullAssertion: will be set
521
- !active && typeof option.group === 'string' && this.options[this.cursor]!.value === option.group
518
+ const groupActive
519
+ = !active && typeof option.group === 'string' && this.options[this.cursor]!.value === option.group
522
520
  if (groupActive) {
523
521
  return opt(option, selected ? 'group-active-selected' : 'group-active', options)
524
522
  }
@@ -535,12 +533,11 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
535
533
  default: {
536
534
  return `${title}${color.cyan(S_BAR)} ${this.options
537
535
  .map((option, i, options) => {
538
- const selected =
539
- this.value.includes(option.value) || (option.group === true && this.isGroupSelected(`${option.value}`))
536
+ const selected
537
+ = this.value.includes(option.value) || (option.group === true && this.isGroupSelected(`${option.value}`))
540
538
  const active = i === this.cursor
541
- const groupActive =
542
- // biome-ignore lint/style/noNonNullAssertion: will be set
543
- !active && typeof option.group === 'string' && this.options[this.cursor]!.value === option.group
539
+ const groupActive
540
+ = !active && typeof option.group === 'string' && this.options[this.cursor]!.value === option.group
544
541
  if (groupActive) {
545
542
  return opt(option, selected ? 'group-active-selected' : 'group-active', options)
546
543
  }
@@ -563,11 +560,11 @@ export const groupMultiselect = <Value>(opts: GroupMultiSelectOptions<Value>) =>
563
560
  }
564
561
 
565
562
  const strip = (str: string) => str.replace(ansiRegex(), '')
566
- export const note = (message = '', title = '') => {
563
+ export function note(message = '', title = ''): void {
567
564
  const lines = `\n${message}\n`.split('\n')
568
565
  const titleLen = strip(title).length
569
- const len =
570
- Math.max(
566
+ const len
567
+ = Math.max(
571
568
  lines.reduce((sum, ln) => {
572
569
  ln = strip(ln)
573
570
  return ln.length > sum ? ln.length : sum
@@ -575,7 +572,7 @@ export const note = (message = '', title = '') => {
575
572
  titleLen,
576
573
  ) + 2
577
574
  const msg = lines
578
- .map((ln) => `${color.gray(S_BAR)} ${color.dim(ln)}${' '.repeat(len - strip(ln).length)}${color.gray(S_BAR)}`)
575
+ .map(ln => `${color.gray(S_BAR)} ${color.dim(ln)}${' '.repeat(len - strip(ln).length)}${color.gray(S_BAR)}`)
579
576
  .join('\n')
580
577
  process.stdout.write(
581
578
  `${color.gray(S_BAR)}\n${color.green(S_STEP_SUBMIT)} ${color.reset(title)} ${color.gray(
@@ -584,19 +581,25 @@ export const note = (message = '', title = '') => {
584
581
  )
585
582
  }
586
583
 
587
- export const cancel = (message = '') => {
584
+ export function cancel(message = ''): void {
588
585
  process.stdout.write(`${color.gray(S_BAR_END)} ${color.red(message)}\n\n`)
589
586
  }
590
587
 
591
- export const intro = (title = '') => {
588
+ export function intro(title = ''): void {
592
589
  process.stdout.write(`${color.gray(S_BAR_START)} ${title}\n`)
593
590
  }
594
591
 
595
- export const outro = (message = '') => {
592
+ export function outro(message = ''): void {
596
593
  process.stdout.write(`${color.gray(S_BAR)}\n${color.gray(S_BAR_END)} ${message}\n\n`)
597
594
  }
598
595
 
599
- export const spinner = () => {
596
+ interface Spinner {
597
+ start: (msg: string) => void
598
+ stop: (msg: string, code: number) => void
599
+ message: (msg: string) => void
600
+ }
601
+
602
+ export function spinner(): Spinner {
600
603
  const frames = unicode ? ['◒', '◐', '◓', '◑'] : ['•', 'o', 'O', '0']
601
604
  const delay = unicode ? 80 : 120
602
605
 
@@ -607,7 +610,8 @@ export const spinner = () => {
607
610
 
608
611
  const handleExit = (code: number) => {
609
612
  const msg = code > 1 ? 'Something went wrong' : 'Canceled'
610
- if (isSpinnerActive) stop(msg, code)
613
+ if (isSpinnerActive)
614
+ stop(msg, code)
611
615
  }
612
616
 
613
617
  const errorEventHandler = () => handleExit(2)
@@ -633,15 +637,16 @@ export const spinner = () => {
633
637
  }
634
638
 
635
639
  const start = (msg = ''): void => {
636
- let loop: ReturnType<typeof setInterval>
637
640
  isSpinnerActive = true
638
641
  unblock = block()
639
642
  _message = msg.replace(/\.+$/, '')
640
643
  process.stdout.write(`${color.gray(S_BAR)}\n`)
641
644
  let frameIndex = 0
642
645
  let dotsTimer = 0
646
+
643
647
  registerHooks()
644
- loop = setInterval(() => {
648
+
649
+ setInterval(() => {
645
650
  const frame = color.magenta(frames[frameIndex])
646
651
  const loadingDots = '.'.repeat(Math.floor(dotsTimer)).slice(0, 3)
647
652
  process.stdout.write(cursor.move(-999, 0))
@@ -656,8 +661,8 @@ export const spinner = () => {
656
661
  _message = msg ?? _message
657
662
  isSpinnerActive = false
658
663
  clearInterval(loop as NodeJS.Timeout)
659
- const step =
660
- code === 0 ? color.green(S_STEP_SUBMIT) : code === 1 ? color.red(S_STEP_CANCEL) : color.red(S_STEP_ERROR)
664
+ const step
665
+ = code === 0 ? color.green(S_STEP_SUBMIT) : code === 1 ? color.red(S_STEP_CANCEL) : color.red(S_STEP_ERROR)
661
666
  process.stdout.write(cursor.move(-999, 0))
662
667
  process.stdout.write(erase.down(1))
663
668
  process.stdout.write(`${step} ${_message}\n`)
@@ -669,11 +674,13 @@ export const spinner = () => {
669
674
  _message = msg ?? _message
670
675
  }
671
676
 
672
- return {
677
+ const spinner = {
673
678
  start,
674
679
  stop,
675
680
  message,
676
681
  }
682
+
683
+ return spinner
677
684
  }
678
685
 
679
686
  // Adapted from https://github.com/chalk/ansi-regex
@@ -706,7 +713,6 @@ type Prettify<T> = {
706
713
  export type PromptGroup<T> = {
707
714
  [P in keyof T]: (opts: {
708
715
  results: Prettify<Partial<PromptGroupAwaitedReturn<Omit<T, P>>>>
709
- // biome-ignore lint/suspicious/noConfusingVoidType: originally shipped this
710
716
  }) => void | Promise<T[P] | void>
711
717
  }
712
718
 
@@ -714,10 +720,7 @@ export type PromptGroup<T> = {
714
720
  * Define a group of prompts to be displayed
715
721
  * and return a results of objects within the group
716
722
  */
717
- export const group = async <T>(
718
- prompts: PromptGroup<T>,
719
- opts?: PromptGroupOptions<T>,
720
- ): Promise<Prettify<PromptGroupAwaitedReturn<T>>> => {
723
+ export async function group<T>(prompts: PromptGroup<T>, opts?: PromptGroupOptions<T>): Promise<Prettify<PromptGroupAwaitedReturn<T>>> {
721
724
  const results = {} as any
722
725
  const promptNames = Object.keys(prompts)
723
726
 
@@ -742,7 +745,7 @@ export const group = async <T>(
742
745
  return results
743
746
  }
744
747
 
745
- export type Task = {
748
+ export interface Task {
746
749
  /**
747
750
  * Task title
748
751
  */
@@ -761,13 +764,14 @@ export type Task = {
761
764
  /**
762
765
  * Define a group of tasks to be executed
763
766
  */
764
- export const tasks = async (tasks: Task[]) => {
767
+ export async function tasks(tasks: Task[]): Promise<void> {
765
768
  for (const task of tasks) {
766
- if (task.enabled === false) continue
769
+ if (task.enabled === false)
770
+ continue
767
771
 
768
772
  const s = spinner()
769
773
  s.start(task.title)
770
774
  const result = await task.task(s.message)
771
- s.stop(result || task.title)
775
+ s.stop(result || task.title, 0)
772
776
  }
773
777
  }
package/src/cli.ts CHANGED
@@ -14,7 +14,7 @@ interface CliOptions {
14
14
  // description: string
15
15
  }
16
16
 
17
- export function cli(name?: string | CliOptions, options?: CliOptions) {
17
+ export function cli(name?: string | CliOptions, options?: CliOptions): CAC {
18
18
  if (typeof name === 'object') {
19
19
  options = name
20
20
  name = options.name
package/src/command.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { CliOptions } from '@stacksjs/types'
1
+ import type { Result } from '@stacksjs/error-handling'
2
+ import type { CliOptions, Readable, Subprocess, Writable } from '@stacksjs/types'
2
3
  import { runCommand } from './run'
3
4
 
4
5
  type CommandOptionTuple = [string, string, { default: boolean }]
@@ -37,11 +38,17 @@ export class Command {
37
38
  }
38
39
 
39
40
  export const command = {
40
- run: async (command: string, options?: CliOptions) => {
41
+ run: async (
42
+ command: string,
43
+ options?: CliOptions,
44
+ ): Promise<Result<Subprocess<Writable, Readable, Readable>, Error>> => {
41
45
  return await runCommand(command, options)
42
46
  },
43
47
 
44
- runSync: async (command: string, options?: CliOptions) => {
48
+ runSync: async (
49
+ command: string,
50
+ options?: CliOptions,
51
+ ): Promise<Result<Subprocess<Writable, Readable, Readable>, Error>> => {
45
52
  return await runCommand(command, options)
46
53
  },
47
54
  }
package/src/console.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  import { log } from '@stacksjs/logging'
2
2
  import prompts from 'prompts'
3
3
 
4
- export { prompts, log }
4
+ export { log, prompts }