@stacksjs/defaults 0.72.76 → 0.72.78
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/ai/skills/stacks-buddy/SKILL.md +52 -23
- package/ai/skills/stacks-cli/SKILL.md +73 -21
- package/app/Actions/Dashboard/Infrastructure/CommandIndexAction.ts +20 -21
- package/app/Commands/Inspire.ts +8 -7
- package/app/Commands/README.md +53 -42
- package/ide/vscode/package.json +1 -1
- package/package.json +2 -2
- package/project/buddy +23 -9
- package/app/Commands.ts +0 -38
|
@@ -17,8 +17,8 @@ The complete CLI runtime for the Stacks framework with 50+ commands, lazy-loaded
|
|
|
17
17
|
- Lazy command registry: `storage/framework/core/buddy/src/lazy-commands.ts`
|
|
18
18
|
- Config system: `storage/framework/core/buddy/src/config.ts`
|
|
19
19
|
- Shell entry: `buddy` (shell script at project root that invokes `bun run ./storage/framework/core/buddy/src/cli.ts`)
|
|
20
|
-
- Application commands: `app/Commands/`
|
|
21
|
-
-
|
|
20
|
+
- Application commands: `app/Commands/` (auto-discovered; no registration step)
|
|
21
|
+
- Optional registry: `app/Commands.ts`
|
|
22
22
|
- Make templates: `storage/framework/defaults/`
|
|
23
23
|
|
|
24
24
|
## CLI Aliases
|
|
@@ -250,7 +250,7 @@ buddy make:certificate # generate SSL certificate (alias: make:cert)
|
|
|
250
250
|
buddy make:command [name] # create CLI command in app/Commands/
|
|
251
251
|
--signature [sig] # CLI command name
|
|
252
252
|
--description [desc] # command description
|
|
253
|
-
--
|
|
253
|
+
--register # also add an entry to app/Commands.ts (optional)
|
|
254
254
|
buddy make:component [name] # create STX component
|
|
255
255
|
buddy make:database [name] # create database
|
|
256
256
|
buddy make:factory [name] # create model factory (stub)
|
|
@@ -622,34 +622,63 @@ buddy stacks # Stacks framework commands (registered as 'stack'
|
|
|
622
622
|
|
|
623
623
|
## Adding Custom Commands
|
|
624
624
|
|
|
625
|
-
### Method 1: Commands
|
|
625
|
+
### Method 1: Drop a file in `app/Commands/` (preferred)
|
|
626
|
+
|
|
627
|
+
Every `.ts` file there is a command - no registration, no generated file.
|
|
628
|
+
Nested directories work too (`app/Commands/Archive/Run.ts`).
|
|
626
629
|
|
|
627
630
|
```typescript
|
|
628
|
-
// app/Commands.ts
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
631
|
+
// app/Commands/Inspire.ts
|
|
632
|
+
import { defineCommand } from '@stacksjs/cli'
|
|
633
|
+
|
|
634
|
+
// Declarative: `options` is inferred from the flags declared above it.
|
|
635
|
+
export default defineCommand({
|
|
636
|
+
name: 'inspire',
|
|
637
|
+
description: 'Display inspirational quote',
|
|
638
|
+
aliases: ['insp'],
|
|
639
|
+
options: {
|
|
640
|
+
'--two, -t': { description: 'Show two quotes', default: false },
|
|
641
|
+
},
|
|
642
|
+
handle(options) {
|
|
643
|
+
console.log(randomQuote())
|
|
644
|
+
if (options.two)
|
|
645
|
+
console.log(randomQuote())
|
|
646
|
+
},
|
|
647
|
+
})
|
|
634
648
|
```
|
|
635
649
|
|
|
636
650
|
```typescript
|
|
637
|
-
//
|
|
638
|
-
import
|
|
651
|
+
// Imperative, for a file that registers several commands or uses cli.on()
|
|
652
|
+
import { defineCommand } from '@stacksjs/cli'
|
|
639
653
|
|
|
640
|
-
export default
|
|
641
|
-
buddy
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
.action(async (options: { two?: boolean }) => {
|
|
645
|
-
console.log(randomQuote())
|
|
646
|
-
if (options.two) console.log(randomQuote())
|
|
647
|
-
})
|
|
648
|
-
}
|
|
654
|
+
export default defineCommand((buddy) => {
|
|
655
|
+
buddy.command('inspire', 'Display inspirational quote').action(() => {})
|
|
656
|
+
buddy.command('inspire:two', 'Two quotes').action(() => {})
|
|
657
|
+
})
|
|
649
658
|
```
|
|
650
659
|
|
|
651
|
-
|
|
652
|
-
|
|
660
|
+
A command file can also configure itself with named exports:
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
export const aliases = ['emails', 'mail'] // extra aliases
|
|
664
|
+
export const enabled = false // keep the file, hide the command
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Method 2: `app/Commands.ts` (optional overlay)
|
|
668
|
+
|
|
669
|
+
Only worth keeping to control listing order, or to alias/disable a command
|
|
670
|
+
without editing its file. Files it does not mention still load.
|
|
671
|
+
|
|
672
|
+
```typescript
|
|
673
|
+
// app/Commands.ts
|
|
674
|
+
import { defineCommands } from '@stacksjs/cli'
|
|
675
|
+
|
|
676
|
+
export default defineCommands({
|
|
677
|
+
'inspire': 'Inspire', // maps to app/Commands/Inspire.ts
|
|
678
|
+
'deploy-hooks': { file: 'DeployHooks', enabled: true, aliases: ['dh'] }, // with options
|
|
679
|
+
'disabled-cmd': { file: 'Disabled', enabled: false }, // disabled command
|
|
680
|
+
})
|
|
681
|
+
```
|
|
653
682
|
|
|
654
683
|
### Method 3: buddy.config.ts
|
|
655
684
|
```typescript
|
|
@@ -14,40 +14,92 @@ The `@stacksjs/cli` package provides the foundation for building CLI commands, u
|
|
|
14
14
|
- Core package: `storage/framework/core/cli/src/`
|
|
15
15
|
- CLI configuration: `config/cli.ts`
|
|
16
16
|
- Application commands: `app/Commands/`
|
|
17
|
-
-
|
|
17
|
+
- Optional registry: `app/Commands.ts` (not needed - see below)
|
|
18
18
|
|
|
19
19
|
## Creating Commands
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Every `.ts` file in `app/Commands/` is a command. There is no registration step
|
|
22
|
+
and no generated registry file: drop the file in and it is live, nested
|
|
23
|
+
directories included (`app/Commands/Archive/Run.ts`).
|
|
24
|
+
|
|
25
|
+
### Declarative form (preferred)
|
|
26
|
+
|
|
27
|
+
`defineCommand()` infers the handler's `options` from the flags declared above
|
|
28
|
+
it, so there is no hand-written options interface to drift out of step.
|
|
29
|
+
|
|
22
30
|
```typescript
|
|
23
31
|
// app/Commands/Greet.ts
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
import { defineCommand, log } from '@stacksjs/cli'
|
|
33
|
+
|
|
34
|
+
export default defineCommand({
|
|
35
|
+
name: 'greet <who>',
|
|
26
36
|
description: 'Greet a user',
|
|
27
|
-
|
|
37
|
+
aliases: ['g'],
|
|
28
38
|
options: {
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
'--loud, -l': { description: 'Shout the greeting', default: false },
|
|
40
|
+
'--title <title>': 'Prefix the name',
|
|
41
|
+
'--times <n>': { description: 'How often', default: 1, type: [Number] },
|
|
31
42
|
},
|
|
32
|
-
handle
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
43
|
+
handle(options, who) {
|
|
44
|
+
// options.loud -> boolean
|
|
45
|
+
// options.title -> string | undefined
|
|
46
|
+
// options.times -> number
|
|
47
|
+
const greeting = `Hello, ${options.title ?? ''}${who}!`
|
|
48
|
+
|
|
49
|
+
for (let i = 0; i < options.times; i++)
|
|
50
|
+
log.info(options.loud ? greeting.toUpperCase() : greeting)
|
|
51
|
+
},
|
|
52
|
+
})
|
|
37
53
|
```
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
Flag to property: `--dry-run` -> `dryRun`, `--two, -t` -> `two`, `--no-cache`
|
|
56
|
+
-> `cache`. Value type: `<x>` -> `string`, `[x]` -> `string | true`, `<x...>`
|
|
57
|
+
-> `string[]`, no argument -> `boolean`. A `default` makes the property always
|
|
58
|
+
present; a `type: [Number]` makes it a `number`.
|
|
59
|
+
|
|
60
|
+
### Imperative form
|
|
61
|
+
|
|
62
|
+
For a file that registers several commands or needs `cli.on()`:
|
|
63
|
+
|
|
40
64
|
```typescript
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
65
|
+
import { defineCommand, log } from '@stacksjs/cli'
|
|
66
|
+
|
|
67
|
+
export default defineCommand((cli) => {
|
|
68
|
+
cli.command('inspire', 'Inspire yourself').alias('insp').action(() => {})
|
|
69
|
+
cli.command('inspire:two', 'Two quotes').action(() => {})
|
|
70
|
+
|
|
71
|
+
cli.on('inspire:*', () => log.error('Invalid command'))
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Per-file configuration
|
|
76
|
+
|
|
77
|
+
Named exports beside the default one, so a command owns its own configuration:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
export const aliases = ['emails', 'mail'] // extra aliases
|
|
81
|
+
export const enabled = false // keep the file, hide the command
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### The optional registry (`app/Commands.ts`)
|
|
85
|
+
|
|
86
|
+
Only worth keeping when you want to control the order commands are listed in,
|
|
87
|
+
or to alias/disable a command without editing its file. A command file the
|
|
88
|
+
registry never mentions still loads.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { defineCommands } from '@stacksjs/cli'
|
|
92
|
+
|
|
93
|
+
export default defineCommands({
|
|
94
|
+
'send-emails <type>': { file: 'SendEmails', aliases: ['emails'] },
|
|
95
|
+
'legacy': { file: 'Legacy', enabled: false },
|
|
96
|
+
})
|
|
45
97
|
```
|
|
46
98
|
|
|
47
|
-
###
|
|
48
|
-
1. **app/Commands
|
|
99
|
+
### Registration Methods
|
|
100
|
+
1. **Drop a file in `app/Commands/`** — auto-discovered, nothing else to do
|
|
49
101
|
2. **Event listeners** — CLI events in `app/Listeners/Console.ts`
|
|
50
|
-
3.
|
|
102
|
+
3. **`app/Commands.ts`** — optional, for ordering / aliases / disabling
|
|
51
103
|
|
|
52
104
|
## CLI Event Listeners
|
|
53
105
|
|
|
@@ -104,7 +156,7 @@ buddy build:cli # build CLI binary
|
|
|
104
156
|
|
|
105
157
|
## Gotchas
|
|
106
158
|
- Application commands go in `app/Commands/`, framework commands in `storage/framework/core/buddy/src/commands/`
|
|
107
|
-
- Commands are auto-discovered from `app/Commands.ts`
|
|
159
|
+
- Commands are auto-discovered from `app/Commands/`; `app/Commands.ts` is optional and additive
|
|
108
160
|
- CLI events support wildcards (`*`) and default handlers (`!`)
|
|
109
161
|
- The buddy CLI lazy-loads commands — not all load at startup
|
|
110
162
|
- Output formatting uses ANSI colors from `@stacksjs/utils`
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { resolveCommands } from '@stacksjs/cli'
|
|
2
3
|
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
3
4
|
import { join, relative } from 'node:path'
|
|
4
5
|
import process from 'node:process'
|
|
5
6
|
import { parseCommandSource } from '../Source/source-inventory'
|
|
6
7
|
import { dashboardOperationalError } from '../dashboard-response'
|
|
7
8
|
|
|
8
|
-
interface CommandConfig {
|
|
9
|
-
file: string
|
|
10
|
-
enabled?: boolean
|
|
11
|
-
aliases?: string[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
9
|
export default new Action({
|
|
15
10
|
name: 'CommandIndexAction',
|
|
16
11
|
description: 'Lists commands registered by the application.',
|
|
@@ -18,23 +13,27 @@ export default new Action({
|
|
|
18
13
|
async handle() {
|
|
19
14
|
try {
|
|
20
15
|
const projectRoot = process.cwd()
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
const commandsDir = join(projectRoot, 'app/Commands')
|
|
17
|
+
|
|
18
|
+
// Every file under app/Commands is a command; app/Commands.ts is an
|
|
19
|
+
// optional overlay. Listing only registry entries used to hide any
|
|
20
|
+
// command the project never bothered to register - which, now that
|
|
21
|
+
// registering is unnecessary, would have been most of them.
|
|
22
|
+
const commands = await resolveCommands({
|
|
23
|
+
commandsDir,
|
|
24
|
+
registryPath: join(projectRoot, 'app/Commands.ts'),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const items = commands.flatMap((command) => {
|
|
28
|
+
if (!existsSync(command.path))
|
|
30
29
|
return []
|
|
31
30
|
|
|
32
31
|
return [parseCommandSource(
|
|
33
|
-
readFileSync(
|
|
34
|
-
relative(projectRoot,
|
|
35
|
-
signature,
|
|
36
|
-
|
|
37
|
-
statSync(
|
|
32
|
+
readFileSync(command.path, 'utf8'),
|
|
33
|
+
relative(projectRoot, command.path),
|
|
34
|
+
command.signature ?? command.file,
|
|
35
|
+
command.aliases,
|
|
36
|
+
statSync(command.path).mtime.toISOString(),
|
|
38
37
|
)]
|
|
39
38
|
})
|
|
40
39
|
|
|
@@ -44,7 +43,7 @@ export default new Action({
|
|
|
44
43
|
total: items.length,
|
|
45
44
|
aliases: items.reduce((sum, item) => sum + (item.aliases?.length || 0), 0),
|
|
46
45
|
options: items.reduce((sum, item) => sum + (item.options?.length || 0), 0),
|
|
47
|
-
registered:
|
|
46
|
+
registered: commands.filter(command => command.source === 'registry').length,
|
|
48
47
|
},
|
|
49
48
|
}
|
|
50
49
|
}
|
package/app/Commands/Inspire.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import type { CLI } from '@stacksjs/types'
|
|
2
1
|
// triggered via `$your-cli inspire` and `buddy inspire`
|
|
3
2
|
import process from 'node:process'
|
|
4
|
-
import { log, quotes } from '@stacksjs/cli'
|
|
3
|
+
import { defineCommand, log, quotes } from '@stacksjs/cli'
|
|
5
4
|
import { ExitCode } from '@stacksjs/types'
|
|
6
5
|
|
|
7
|
-
//
|
|
8
|
-
//
|
|
6
|
+
// Every file in this directory is a command - there is nothing to register.
|
|
7
|
+
// `defineCommand` types the CLI it hands you; the declarative form
|
|
8
|
+
// (`defineCommand({ name, options, handle })`) additionally infers the
|
|
9
|
+
// handler's options from the flags it declares.
|
|
9
10
|
interface InspireOptions {
|
|
10
11
|
two: boolean
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export default
|
|
14
|
+
export default defineCommand((cli) => {
|
|
14
15
|
cli
|
|
15
16
|
.command('inspire', 'Inspire yourself with a random quote')
|
|
16
17
|
.option('--two, -t', 'Inspire yourself with two random quotes', {
|
|
@@ -53,5 +54,5 @@ export default function (cli: CLI) {
|
|
|
53
54
|
process.exit(1)
|
|
54
55
|
})
|
|
55
56
|
|
|
56
|
-
return cli
|
|
57
|
-
}
|
|
57
|
+
return cli
|
|
58
|
+
})
|
package/app/Commands/README.md
CHANGED
|
@@ -7,60 +7,71 @@ Stacks allows you to easily create & manage CLIs. This is done through the use o
|
|
|
7
7
|
|
|
8
8
|
## Get Started
|
|
9
9
|
|
|
10
|
-
The following command will bootstrap a new
|
|
10
|
+
The following command will bootstrap a new command file in the `app/Commands` directory.
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
13
|
buddy make:command SendEmails
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
Every `.ts` file in this directory is a command. There is no registration step and no generated
|
|
17
|
+
registry file - drop the file in and it is live, nested directories included
|
|
18
|
+
(`app/Commands/Archive/Run.ts`).
|
|
19
19
|
|
|
20
20
|
### Example
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
`defineCommand()` infers the handler's `options` from the flags declared above it, so there is no
|
|
23
|
+
hand-written options interface to keep in step.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { defineCommand, log } from '@stacksjs/cli'
|
|
27
|
+
|
|
28
|
+
export default defineCommand({
|
|
29
|
+
name: 'send-emails <type>',
|
|
30
|
+
description: 'Send the queued emails of one type',
|
|
31
|
+
aliases: ['emails'],
|
|
32
|
+
options: {
|
|
33
|
+
'--dry-run': { description: 'Report what would be sent, send nothing', default: false },
|
|
34
|
+
'--limit <n>': { description: 'Stop after this many', default: 100, type: [Number] },
|
|
35
|
+
},
|
|
36
|
+
async handle(options, type) {
|
|
37
|
+
// options.dryRun -> boolean, options.limit -> number, type -> string
|
|
38
|
+
log.info(`Sending ${options.limit} ${type} emails${options.dryRun ? ' (dry run)' : ''}`)
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For a file that registers several commands, or needs `cli.on()`, take the CLI directly - it is typed
|
|
44
|
+
either way:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { defineCommand, log } from '@stacksjs/cli'
|
|
48
|
+
|
|
49
|
+
export default defineCommand((cli) => {
|
|
50
|
+
cli.command('inspire', 'Inspire yourself with a random quote').alias('insp').action(() => {})
|
|
51
|
+
cli.command('inspire:two', 'Inspire yourself with two random quotes').action(() => {})
|
|
52
|
+
|
|
53
|
+
cli.on('inspire:*', () => log.error('Invalid command.'))
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Configuring a command
|
|
58
|
+
|
|
59
|
+
Named exports beside the default one, so a command owns its own configuration:
|
|
23
60
|
|
|
24
61
|
```ts
|
|
62
|
+
export const aliases = ['emails', 'mail'] // extra aliases
|
|
63
|
+
export const enabled = false // keep the file, hide the command
|
|
64
|
+
```
|
|
25
65
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
cli
|
|
32
|
-
.command('inspire', 'Inspire yourself with a random quote')
|
|
33
|
-
.option('--two, -t', 'Inspire yourself with two random quotes', { default: false })
|
|
34
|
-
.alias('insp')
|
|
35
|
-
.action((options: InspireOptions) => {
|
|
36
|
-
if (options.two)
|
|
37
|
-
// @ts-expect-error - this is safe because we hard-coded the quotes
|
|
38
|
-
quotes.random(2).map((quote, index) => log.info(`${index + 1}. ${quote}`))
|
|
39
|
-
else
|
|
40
|
-
log.info(quotes.random())
|
|
41
|
-
|
|
42
|
-
log.success('Have a great day!')
|
|
43
|
-
process.exit(ExitCode.Success)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
cli
|
|
47
|
-
.command('inspire:two', 'Inspire yourself with two random quotes')
|
|
48
|
-
.action(() => {
|
|
49
|
-
// @ts-expect-error - this is safe because we hard-coded the quotes
|
|
50
|
-
quotes.random(2).map((quote, index) => log.info(`${index + 1}. ${quote}`))
|
|
51
|
-
|
|
52
|
-
log.success('Have a great day!')
|
|
53
|
-
process.exit(ExitCode.Success)
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
cli.on('inspire:*', () => {
|
|
57
|
-
log.error('Invalid command: %s\nSee --help for a list of available commands.', cli.args.join(' '))
|
|
58
|
-
process.exit(1)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
return cli
|
|
62
|
-
}
|
|
66
|
+
`app/Commands.ts` is optional. Keep one only to control the order commands are listed in, or to
|
|
67
|
+
alias or disable a command without editing its file - a command it never mentions still loads.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { defineCommands } from '@stacksjs/cli'
|
|
63
71
|
|
|
72
|
+
export default defineCommands({
|
|
73
|
+
'send-emails <type>': { file: 'SendEmails', aliases: ['emails'] },
|
|
74
|
+
})
|
|
64
75
|
```
|
|
65
76
|
|
|
66
77
|
## 🚜 Contributing
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.72.
|
|
5
|
+
"version": "0.72.78",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@iconify-json/f7": "^1.2.2",
|
|
53
53
|
"@iconify-json/hugeicons": "^1.2.27",
|
|
54
|
-
"@stacksjs/mobile": "^0.72.
|
|
54
|
+
"@stacksjs/mobile": "^0.72.78",
|
|
55
55
|
"@stacksjs/sanitizer": "^0.2.113"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
package/project/buddy
CHANGED
|
@@ -6,7 +6,6 @@ PANTRY_LOCAL_BIN="${ROOT_DIR}/pantry/.bin"
|
|
|
6
6
|
PANTRY_DIR="${ROOT_DIR}/pantry"
|
|
7
7
|
USER_LOCAL_BIN="${HOME}/.local/bin"
|
|
8
8
|
PANTRY_INSTALLER="${ROOT_DIR}/storage/framework/scripts/pantry-install"
|
|
9
|
-
PANTRY_SETUP_DONE="${PANTRY_DIR}/.postsetup-done"
|
|
10
9
|
PANTRY_BOOTSTRAP_TIMEOUT_SECONDS="${PANTRY_BOOTSTRAP_TIMEOUT_SECONDS:-600}"
|
|
11
10
|
PANTRY_DEPENDENCIES_TIMEOUT_SECONDS="${PANTRY_DEPENDENCIES_TIMEOUT_SECONDS:-1200}"
|
|
12
11
|
|
|
@@ -16,9 +15,13 @@ PANTRY_DEPENDENCIES_TIMEOUT_SECONDS="${PANTRY_DEPENDENCIES_TIMEOUT_SECONDS:-1200
|
|
|
16
15
|
# 1. `storage/framework/core/buddy/src/cli.ts` — userland override (legacy
|
|
17
16
|
# "framework lives in the project" layout). Kept FIRST so projects that
|
|
18
17
|
# have published buddy via `buddy publish:core buddy` still win.
|
|
19
|
-
# 2.
|
|
18
|
+
# 2. `<deps>/@stacksjs/buddy/src/cli.ts` — installed package, TS source
|
|
20
19
|
# (workspace symlink or a real npm install with `src/` shipped).
|
|
21
|
-
# 3.
|
|
20
|
+
# 3. `<deps>/@stacksjs/buddy/dist/cli.js` — published JS-only build.
|
|
21
|
+
#
|
|
22
|
+
# `<deps>` is node_modules or pantry: an app installed with pantry has no
|
|
23
|
+
# node_modules at all, so a node_modules-only list finds nothing and the shim
|
|
24
|
+
# refuses to run the CLI that is sitting right there in ./pantry.
|
|
22
25
|
#
|
|
23
26
|
# If none of these are present the script bails with a clear message instead
|
|
24
27
|
# of `bun` exploding on a missing file.
|
|
@@ -27,6 +30,8 @@ resolve_buddy_entry() {
|
|
|
27
30
|
"${ROOT_DIR}/storage/framework/core/buddy/src/cli.ts" \
|
|
28
31
|
"${ROOT_DIR}/node_modules/@stacksjs/buddy/src/cli.ts" \
|
|
29
32
|
"${ROOT_DIR}/node_modules/@stacksjs/buddy/dist/cli.js" \
|
|
33
|
+
"${PANTRY_DIR}/@stacksjs/buddy/src/cli.ts" \
|
|
34
|
+
"${PANTRY_DIR}/@stacksjs/buddy/dist/cli.js" \
|
|
30
35
|
; do
|
|
31
36
|
if [ -f "${candidate}" ]; then
|
|
32
37
|
echo "${candidate}"
|
|
@@ -38,8 +43,8 @@ resolve_buddy_entry() {
|
|
|
38
43
|
|
|
39
44
|
if ! BUDDY_ENTRY=$(resolve_buddy_entry); then
|
|
40
45
|
echo "buddy: could not locate CLI entrypoint." >&2
|
|
41
|
-
echo " Tried: storage/framework/core/buddy/src/cli.ts, node_modules/@stacksjs/buddy/{src/cli.ts,dist/cli.js}." >&2
|
|
42
|
-
echo " Run \`bun install\` or \`buddy publish:core buddy
|
|
46
|
+
echo " Tried: storage/framework/core/buddy/src/cli.ts, {node_modules,pantry}/@stacksjs/buddy/{src/cli.ts,dist/cli.js}." >&2
|
|
47
|
+
echo " Run \`bun install\` (or \`pantry install\`), or \`buddy publish:core buddy\`, to make the CLI available." >&2
|
|
43
48
|
exit 1
|
|
44
49
|
fi
|
|
45
50
|
|
|
@@ -106,17 +111,26 @@ run_buddy() {
|
|
|
106
111
|
# preload not found "@stacksjs/env/plugin.js"`, then Cannot-find-module for
|
|
107
112
|
# every other import). In that case omit the flag and let bun/import/default
|
|
108
113
|
# resolve the dist.
|
|
114
|
+
# The probe has to look wherever this project actually installs: pantry-only
|
|
115
|
+
# apps have no node_modules, so a node_modules-only probe reports "no src"
|
|
116
|
+
# for a vendored checkout and drops the flag — and then every import resolves
|
|
117
|
+
# to a ./dist the vendored source has never built.
|
|
109
118
|
CONDITIONS=""
|
|
110
|
-
if [ -d "${ROOT_DIR}/storage/framework/core" ]
|
|
119
|
+
if [ -d "${ROOT_DIR}/storage/framework/core" ] \
|
|
120
|
+
&& { [ -f "${ROOT_DIR}/node_modules/@stacksjs/env/src/index.ts" ] || [ -f "${PANTRY_DIR}/@stacksjs/env/src/index.ts" ]; }; then
|
|
111
121
|
CONDITIONS="--conditions development"
|
|
112
122
|
fi
|
|
113
123
|
|
|
114
124
|
# Installed dependencies are authoritative; Pantry is a fallback for tools
|
|
115
125
|
# the app does not install. Keeping node_modules first prevents a stale local
|
|
116
126
|
# Pantry snapshot from changing migrations, builds, or production commands.
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
|
|
127
|
+
#
|
|
128
|
+
# Add Pantry only once it holds packages. An empty (or absent) directory on
|
|
129
|
+
# NODE_PATH is what a half-finished bootstrap leaves behind, and pointing at
|
|
130
|
+
# it buys nothing; a directory with packages in it is the whole module root
|
|
131
|
+
# for a pantry-installed app, and leaving it off is what makes `buddy` die on
|
|
132
|
+
# `Cannot find module '@stacksjs/env'` from a preload.
|
|
133
|
+
if [ -d "${PANTRY_DIR}" ] && [ -n "$(ls -A "${PANTRY_DIR}" 2>/dev/null)" ]; then
|
|
120
134
|
exec env PATH="${PANTRY_LOCAL_BIN}:${USER_LOCAL_BIN}:${PATH}" NODE_PATH="${ROOT_DIR}/node_modules:${PANTRY_DIR}${NODE_PATH:+:${NODE_PATH}}" "${BUN_CMD}" ${CONDITIONS} "${BUDDY_ENTRY}" "$@"
|
|
121
135
|
fi
|
|
122
136
|
exec env PATH="${PANTRY_LOCAL_BIN}:${USER_LOCAL_BIN}:${PATH}" "${BUN_CMD}" ${CONDITIONS} "${BUDDY_ENTRY}" "$@"
|
package/app/Commands.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export interface CommandConfig {
|
|
2
|
-
/** The command file name (without .ts extension) */
|
|
3
|
-
file: string
|
|
4
|
-
/** Whether the command is enabled */
|
|
5
|
-
enabled?: boolean
|
|
6
|
-
/** Command aliases */
|
|
7
|
-
aliases?: string[]
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type CommandRegistry = Record<string, string | CommandConfig>
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The application's command registry.
|
|
14
|
-
*
|
|
15
|
-
* Commands listed here will be auto-loaded by the CLI.
|
|
16
|
-
* You can use a simple string (file name) or a config object for more control.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* // Simple registration
|
|
20
|
-
* 'inspire': 'Inspire',
|
|
21
|
-
*
|
|
22
|
-
* // With config
|
|
23
|
-
* 'send-emails': {
|
|
24
|
-
* file: 'SendEmails',
|
|
25
|
-
* enabled: true,
|
|
26
|
-
* aliases: ['emails', 'mail'],
|
|
27
|
-
* },
|
|
28
|
-
*/
|
|
29
|
-
export default {
|
|
30
|
-
'inspire': 'Inspire',
|
|
31
|
-
// Add more commands here
|
|
32
|
-
// 'my-command': 'MyCommand',
|
|
33
|
-
// 'send-emails': {
|
|
34
|
-
// file: 'SendEmails',
|
|
35
|
-
// enabled: true,
|
|
36
|
-
// aliases: ['emails'],
|
|
37
|
-
// },
|
|
38
|
-
} satisfies CommandRegistry
|