@brust/create-app 2.0.0-alpha.10 → 2.0.0-alpha.12

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/README.md CHANGED
@@ -4,6 +4,7 @@ Scaffold a [brust](https://github.com/AssetsArt/brust) v2 app.
4
4
 
5
5
  ```bash
6
6
  bun create @brust/app my-app # --template blank (default) | pokedex
7
+ bun create @brust/app my-app --shadcn=button,card,dialog # also installs and runs `brust shadcn init` (needs @brust/core 2.0.0-alpha.11+)
7
8
  cd my-app && bun install
8
9
  bun run dev # http://127.0.0.1:1337
9
10
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brust/create-app",
3
- "version": "2.0.0-alpha.10",
3
+ "version": "2.0.0-alpha.12",
4
4
  "description": "Scaffold a brust v2 app — bun create @brust/app <name>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://brust.dev",
package/src/args.ts CHANGED
@@ -4,8 +4,11 @@ import { isAbsolute, resolve } from 'node:path'
4
4
 
5
5
  const NAME_RE = /^[a-z0-9][a-z0-9_-]*$/
6
6
  const MAX_NAME_LEN = 50
7
+ // The same rule as `brust shadcn init` (R41.8): a component is a registry name or an https URL, checked before anything is written.
8
+ const COMPONENT_RE = /^[a-z0-9][a-z0-9-]*$/
9
+ const REGISTRY_URL_RE = /^https:\/\/[^\s,]+$/
7
10
 
8
- export const USAGE = 'usage: bun create @brust/app <name> [--template blank|pokedex] [--tailwind] [--dir <path>] [--yes] [--brust-dep <spec>] [--version]'
11
+ export const USAGE = 'usage: bun create @brust/app <name> [--template blank|pokedex] [--tailwind] [--dir <path>] [--yes] [--brust-dep <spec>] [--shadcn[=button,card,...]] [--version]'
9
12
 
10
13
  export interface ParsedArgs {
11
14
  projectName: string
@@ -16,6 +19,8 @@ export interface ParsedArgs {
16
19
  /** `--tailwind` (L21): accepted and ignored since L28: the blank template uses Tailwind CSS v4 by default. */
17
20
  tailwind: boolean
18
21
  help: boolean
22
+ /** `--shadcn[=a,b]` (F181): after scaffolding, install and run `brust shadcn init` with these components (`[]` = set up only). */
23
+ shadcn?: string[]
19
24
  /** `--version` (F131): print the bare version and exit; no short form (see the note above). */
20
25
  version: boolean
21
26
  }
@@ -29,6 +34,7 @@ export function parseArgs(args: string[]): ParsedArgs {
29
34
  let tailwind = false
30
35
  let help = false
31
36
  let version = false
37
+ let shadcn: string[] | undefined
32
38
  const need = (flag: string, v: string | undefined): string => {
33
39
  if (!v) throw new Error(`create-app: ${flag} requires a value`)
34
40
  return v
@@ -47,6 +53,14 @@ export function parseArgs(args: string[]): ParsedArgs {
47
53
  else if (a.startsWith('--brust-dep=')) brustDep = need('--brust-dep', a.slice('--brust-dep='.length))
48
54
  else if (a === '--yes' || a === '-y') yes = true
49
55
  else if (a === '--tailwind') tailwind = true
56
+ else if (a === '--shadcn') shadcn = shadcn ?? []
57
+ else if (a.startsWith('--shadcn=')) {
58
+ const names = a.slice('--shadcn='.length).split(',').filter(Boolean)
59
+ for (const c of names) {
60
+ if (!COMPONENT_RE.test(c) && !REGISTRY_URL_RE.test(c)) throw new Error(`create-app: --shadcn component "${c}" must match ^[a-z0-9][a-z0-9-]*$ or be an https:// registry URL (without commas)`)
61
+ }
62
+ shadcn = [...(shadcn ?? []), ...names]
63
+ }
50
64
  else if (a.startsWith('-')) throw new Error(`create-app: unknown flag "${a}"`)
51
65
  else if (name === undefined) name = a
52
66
  else throw new Error(`create-app: unexpected positional argument "${a}"`)
@@ -60,5 +74,5 @@ export function parseArgs(args: string[]): ParsedArgs {
60
74
  }
61
75
  const cwd = process.cwd()
62
76
  const targetDir = dir ? (isAbsolute(dir) ? dir : resolve(cwd, dir)) : resolve(cwd, name)
63
- return { projectName: name, targetDir, template, yes, brustDep, tailwind, help, version }
77
+ return { projectName: name, targetDir, template, yes, brustDep, tailwind, help, version, ...(shadcn && { shadcn }) }
64
78
  }
package/src/cli.ts CHANGED
@@ -5,6 +5,7 @@ import { copyTemplate } from './copy.ts'
5
5
  import { parseArgs, USAGE } from './args.ts'
6
6
  import { applyOverrides, monorepoRoot, resolveBrustDep } from './dep.ts'
7
7
  import { TAILWIND_DEFAULT_NOTE } from './tailwind.ts'
8
+ import { runShadcn, shq, spawnRunner } from './shadcn.ts'
8
9
  import { selectTemplate } from './templates.ts'
9
10
 
10
11
  // F131: read at run time from the package.json that `bun publish` ships, so release-bump.ts stays the only place versions change.
@@ -55,15 +56,21 @@ export async function main(argv: string[]): Promise<void> {
55
56
  if (!targetExisted) await rm(targetDir, { recursive: true, force: true }).catch(() => {})
56
57
  return fail(`create-app: failed to scaffold (${msg(e)})`)
57
58
  }
59
+ if (parsed.shadcn) {
60
+ const code = await runShadcn({ targetDir, components: parsed.shadcn, workspace: dep.kind === 'workspace', run: spawnRunner, out: (l) => console.log(l) })
61
+ if (code !== 0) process.exit(code)
62
+ printNextSteps(projectName, targetDir, tmpl.name, dep.spec, dep.kind === 'workspace', true)
63
+ return
64
+ }
58
65
  printNextSteps(projectName, targetDir, tmpl.name, dep.spec, dep.kind === 'workspace')
59
66
  }
60
67
 
61
- function printNextSteps(name: string, targetDir: string, template: string, spec: string, workspace: boolean): void {
68
+ function printNextSteps(name: string, targetDir: string, template: string, spec: string, workspace: boolean, installed = false): void {
62
69
  const cwd = process.cwd()
63
70
  const displayPath = targetDir.startsWith(`${cwd}/`) ? `./${targetDir.slice(cwd.length + 1)}` : targetDir
64
71
  console.log(`Created ${name} at ${targetDir} (template: ${template}, @brust/core ${spec})\n`)
65
72
  console.log('Next:')
66
- console.log(` cd ${displayPath}`)
67
- console.log(workspace ? ' bun install # run it at the brust repo root: this app is a workspace member' : ' bun install')
73
+ console.log(` cd ${shq(displayPath)}`)
74
+ if (!installed) console.log(workspace ? ' bun install # run it at the brust repo root: this app is a workspace member' : ' bun install')
68
75
  console.log(' bun run dev')
69
76
  }
package/src/shadcn.ts ADDED
@@ -0,0 +1,41 @@
1
+ // `--shadcn[=a,b]` (F181): after the scaffold, install the project and run its own `brust shadcn init`.
2
+ // Every child is an argv array (no shell). The project's own `node_modules/.bin/brust` is run by path, never `bunx brust`,
3
+ // which could fetch an unrelated package named `brust` from the registry when the local one is missing.
4
+ import { join } from 'node:path'
5
+
6
+ /** Runs `argv` in `cwd` with the terminal attached; resolves to the exit code. */
7
+ export type Runner = (argv: string[], cwd: string) => Promise<number>
8
+
9
+ export const spawnRunner: Runner = async (argv, cwd) => {
10
+ try {
11
+ const p = Bun.spawn(argv, { cwd, stdin: 'inherit', stdout: 'inherit', stderr: 'inherit' })
12
+ return await p.exited
13
+ } catch {
14
+ return 127
15
+ }
16
+ }
17
+
18
+ /** POSIX single-quoting: nothing inside expands when the printed command is pasted into a shell. */
19
+ export const shq = (s: string): string => (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`)
20
+
21
+ /** Returns 0 when done, else the failing step's exit code (the project is left in place; `lines` says what to run). */
22
+ export async function runShadcn(o: { targetDir: string; components: string[]; workspace: boolean; run: Runner; out: (line: string) => void }): Promise<number> {
23
+ const { targetDir, components, out } = o
24
+ const by = `cd ${shq(targetDir)} && bun install && node_modules/.bin/brust shadcn init${components.map((c) => ` ${shq(c)}`).join('')}`
25
+ if (o.workspace) {
26
+ out('--shadcn: this app is a workspace member of a brust checkout; run `bun install` at the repo root, then in the app:')
27
+ out(` node_modules/.bin/brust shadcn init${components.map((c) => ` ${shq(c)}`).join('')}`)
28
+ return 0
29
+ }
30
+ out('\n--shadcn: installing, then running `brust shadcn init` (needs @brust/core 2.0.0-alpha.11 or later)')
31
+ const install = await o.run(['bun', 'install'], targetDir)
32
+ if (install !== 0) {
33
+ out(`create-app: bun install failed (exit ${install}); the project is created. Finish with: ${by}`)
34
+ return install
35
+ }
36
+ const init = await o.run([join(targetDir, 'node_modules/.bin/brust'), 'shadcn', 'init', ...components], targetDir)
37
+ if (init !== 0) {
38
+ out(`create-app: brust shadcn init failed (exit ${init}); the project is created. Run again: cd ${shq(targetDir)} && node_modules/.bin/brust shadcn init${components.map((c) => ` ${shq(c)}`).join('')}`)
39
+ }
40
+ return init
41
+ }