@brightbase/blocks 0.1.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/README.md +51 -0
- package/index.mjs +195 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @brightbase/blocks
|
|
2
|
+
|
|
3
|
+
CLI for installing blocks from the bbai-blocks registry into a consumer bbai project.
|
|
4
|
+
|
|
5
|
+
## Install (once)
|
|
6
|
+
|
|
7
|
+
The package is **private** on npm. You need:
|
|
8
|
+
1. An npm Pro/Teams account on the `@brightbase` org
|
|
9
|
+
2. To be logged in: `npm login`
|
|
10
|
+
|
|
11
|
+
Then in your consumer project, blocks install with:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
BBAI_BLOCKS_TOKEN=<your-token> npx @brightbase/blocks add <slug>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The token also gates the catalog UI at https://bbai-blocks.vercel.app — same value for both.
|
|
18
|
+
|
|
19
|
+
Tip: put `BBAI_BLOCKS_TOKEN` in your shell rc or `.env.local` and source it so you don't have to type it every install.
|
|
20
|
+
|
|
21
|
+
## What it does
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
npx @brightbase/blocks add bbase-hero47
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
1. Fetches metadata + source from `bbai-blocks.vercel.app/r/<slug>` (with auth)
|
|
28
|
+
2. Runs `npx shadcn@latest add` to write the component file and any `_shared/` deps
|
|
29
|
+
3. Appends a lazy loader to `src/components/preview/preview-registry.ts`
|
|
30
|
+
4. Upserts the slug into the `custom` category of `design/block-manifest.json`
|
|
31
|
+
|
|
32
|
+
After install, the block appears under "Custom" in the Studio block library.
|
|
33
|
+
|
|
34
|
+
## Publish (maintainers only)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd cli
|
|
38
|
+
npm login # if not already
|
|
39
|
+
npm publish # publishes as restricted (requires npm Pro)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
To cut a new version, bump `version` in `package.json` and re-publish.
|
|
43
|
+
|
|
44
|
+
## Options
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
npx @brightbase/blocks add <slug>
|
|
48
|
+
npx @brightbase/blocks add <slug> --registry <url> # use a different registry origin
|
|
49
|
+
npx @brightbase/blocks --help
|
|
50
|
+
npx @brightbase/blocks --version
|
|
51
|
+
```
|
package/index.mjs
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bbai-blocks — install blocks from the bbai-blocks registry.
|
|
4
|
+
*
|
|
5
|
+
* Wraps `npx shadcn@latest add` with post-install wiring:
|
|
6
|
+
* 1. Fetches metadata from the registry
|
|
7
|
+
* 2. Runs shadcn to write component + _shared deps
|
|
8
|
+
* 3. Appends lazy loader to preview-registry.ts
|
|
9
|
+
* 4. Upserts slug into the 'custom' category of block-manifest.json
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* npx bbai-blocks add <slug>
|
|
13
|
+
* npx bbai-blocks add <slug> --registry <url>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFile, writeFile } from 'fs/promises'
|
|
17
|
+
import { execSync } from 'child_process'
|
|
18
|
+
import { join } from 'path'
|
|
19
|
+
|
|
20
|
+
const REGISTRY_DEFAULT = 'https://bbai-blocks.vercel.app'
|
|
21
|
+
const PREVIEW_REGISTRY_PATH = 'src/components/preview/preview-registry.ts'
|
|
22
|
+
const MANIFEST_PATH = 'design/block-manifest.json'
|
|
23
|
+
|
|
24
|
+
const VERSION = '0.1.0'
|
|
25
|
+
|
|
26
|
+
// ── Arg parsing ──────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const argv = process.argv.slice(2)
|
|
29
|
+
|
|
30
|
+
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
|
|
31
|
+
printHelp()
|
|
32
|
+
process.exit(0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
36
|
+
console.log(VERSION)
|
|
37
|
+
process.exit(0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const command = argv[0]
|
|
41
|
+
const rest = argv.slice(1)
|
|
42
|
+
|
|
43
|
+
if (command !== 'add') {
|
|
44
|
+
console.error(`Unknown command: ${command}`)
|
|
45
|
+
printHelp()
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const slug = rest.find((a) => !a.startsWith('--'))
|
|
50
|
+
const registryFlag = rest.indexOf('--registry')
|
|
51
|
+
const registryBase = registryFlag !== -1 ? rest[registryFlag + 1] : REGISTRY_DEFAULT
|
|
52
|
+
// --auth=<token> overrides BBAI_BLOCKS_TOKEN env var
|
|
53
|
+
const authArg = rest.find((a) => a.startsWith('--auth='))
|
|
54
|
+
const authToken = authArg ? authArg.slice('--auth='.length) : undefined
|
|
55
|
+
|
|
56
|
+
if (!slug) {
|
|
57
|
+
console.error('Missing slug. Usage: bbai-blocks add <slug>')
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!/^[a-z0-9-]+$/.test(slug)) {
|
|
62
|
+
console.error(`Invalid slug: ${slug}`)
|
|
63
|
+
process.exit(1)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
await runAdd(slug, registryBase, authToken)
|
|
67
|
+
|
|
68
|
+
// ── Commands ─────────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
async function runAdd(slug, registryBase, authToken) {
|
|
71
|
+
// --auth flag wins; falls back to BBAI_BLOCKS_TOKEN env var
|
|
72
|
+
const token = authToken || process.env.BBAI_BLOCKS_TOKEN
|
|
73
|
+
const baseUrl = `${registryBase}/r/${slug}`
|
|
74
|
+
// Token in query param so shadcn's plain fetch can authenticate
|
|
75
|
+
const registryUrl = token ? `${baseUrl}?token=${encodeURIComponent(token)}` : baseUrl
|
|
76
|
+
|
|
77
|
+
console.log(`\nFetching ${baseUrl}${token ? ' (with token)' : ''}…`)
|
|
78
|
+
let meta
|
|
79
|
+
try {
|
|
80
|
+
const res = await fetch(baseUrl, {
|
|
81
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
82
|
+
})
|
|
83
|
+
if (res.status === 401) {
|
|
84
|
+
console.error('Unauthorized. Pass --auth=<token> or set BBAI_BLOCKS_TOKEN.')
|
|
85
|
+
process.exit(1)
|
|
86
|
+
}
|
|
87
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
|
88
|
+
meta = await res.json()
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(`Failed to fetch ${baseUrl}: ${err.message}`)
|
|
91
|
+
process.exit(1)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const label = meta.title ?? slug
|
|
95
|
+
const mainFile = meta.files?.[0]
|
|
96
|
+
const exportName = mainFile?.content ? parseExportName(mainFile.content, slug) : toExportName(slug)
|
|
97
|
+
console.log(` label: ${label}`)
|
|
98
|
+
console.log(` exportName: ${exportName}`)
|
|
99
|
+
|
|
100
|
+
console.log(`\nInstalling via shadcn…`)
|
|
101
|
+
try {
|
|
102
|
+
execSync(`npx shadcn@latest add "${registryUrl}" --overwrite`, { stdio: 'inherit' })
|
|
103
|
+
} catch {
|
|
104
|
+
// shadcn exits non-zero on some prompts even when successful — continue
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await wirePreviewRegistry(slug, exportName, mainFile?.path)
|
|
108
|
+
await wireBlockManifest(slug, label)
|
|
109
|
+
|
|
110
|
+
console.log(`\nDone. ${slug} is installed and wired.\n`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function wirePreviewRegistry(slug, exportName, installPath) {
|
|
114
|
+
console.log(`\nUpdating ${PREVIEW_REGISTRY_PATH}…`)
|
|
115
|
+
const path = join(process.cwd(), PREVIEW_REGISTRY_PATH)
|
|
116
|
+
let source = await readFile(path, 'utf-8')
|
|
117
|
+
|
|
118
|
+
if (source.includes(`'${slug}'`)) {
|
|
119
|
+
console.log(` already present — skipping`)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const filePath = installPath ?? `src/components/_preview/_bbase/${slug}.tsx`
|
|
124
|
+
const importPath = filePath
|
|
125
|
+
.replace(/^src\/components\//, '../')
|
|
126
|
+
.replace(/\.tsx$/, '')
|
|
127
|
+
|
|
128
|
+
const entry = ` '${slug}': () => import('${importPath}').then(m => m.${exportName}),\n`
|
|
129
|
+
|
|
130
|
+
const closingBrace = source.lastIndexOf('}')
|
|
131
|
+
if (closingBrace === -1) {
|
|
132
|
+
console.error(' Could not find closing brace — add entry manually')
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
source = source.slice(0, closingBrace) + entry + source.slice(closingBrace)
|
|
137
|
+
await writeFile(path, source, 'utf-8')
|
|
138
|
+
console.log(` added: ${entry.trim()}`)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function wireBlockManifest(slug, label) {
|
|
142
|
+
console.log(`\nUpdating ${MANIFEST_PATH}…`)
|
|
143
|
+
const path = join(process.cwd(), MANIFEST_PATH)
|
|
144
|
+
const manifest = JSON.parse(await readFile(path, 'utf-8'))
|
|
145
|
+
|
|
146
|
+
let customCat = manifest.categories?.find((c) => c.name === 'custom')
|
|
147
|
+
if (!customCat) {
|
|
148
|
+
customCat = { name: 'custom', label: 'Custom', description: 'Project-specific blocks', blocks: [] }
|
|
149
|
+
manifest.categories = manifest.categories ?? []
|
|
150
|
+
manifest.categories.push(customCat)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (customCat.blocks.some((b) => b.slug === slug)) {
|
|
154
|
+
console.log(` already present in custom category — skipping`)
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
customCat.blocks.push({ slug, label, free: true, source: 'bbase' })
|
|
159
|
+
await writeFile(path, JSON.stringify(manifest, null, 2) + '\n', 'utf-8')
|
|
160
|
+
console.log(` added ${slug} to custom category`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
function toExportName(s) {
|
|
166
|
+
return s.split('-').map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join('')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function parseExportName(source, slug) {
|
|
170
|
+
const m = source.match(/export\s+(?:default\s+)?function\s+([A-Z][A-Za-z0-9]+)/)
|
|
171
|
+
return m ? m[1] : toExportName(slug)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function printHelp() {
|
|
175
|
+
console.log(`bbai-blocks v${VERSION}
|
|
176
|
+
|
|
177
|
+
Install blocks from the bbai-blocks registry.
|
|
178
|
+
|
|
179
|
+
Usage:
|
|
180
|
+
npx bbai-blocks add <slug>
|
|
181
|
+
npx bbai-blocks add <slug> --registry <url>
|
|
182
|
+
|
|
183
|
+
Options:
|
|
184
|
+
--auth=<token> Auth token. Overrides BBAI_BLOCKS_TOKEN env var.
|
|
185
|
+
Common shell-expansion form: --auth=$BBAI_BLOCKS_TOKEN
|
|
186
|
+
--registry <url> Override the registry origin (default: ${REGISTRY_DEFAULT})
|
|
187
|
+
--version, -v Print version
|
|
188
|
+
--help, -h Print this help
|
|
189
|
+
|
|
190
|
+
Env vars:
|
|
191
|
+
BBAI_BLOCKS_TOKEN Used when --auth is not passed. Sent as Authorization:
|
|
192
|
+
Bearer header (direct fetch) and ?token= query param
|
|
193
|
+
(shadcn's fetch).
|
|
194
|
+
`)
|
|
195
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@brightbase/blocks",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for installing blocks from the bbai-blocks registry into a bbai project",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bbai-blocks": "./index.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"index.mjs",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"bbai",
|
|
18
|
+
"shadcn",
|
|
19
|
+
"blocks",
|
|
20
|
+
"registry"
|
|
21
|
+
],
|
|
22
|
+
"license": "UNLICENSED",
|
|
23
|
+
"private": false,
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "restricted"
|
|
26
|
+
}
|
|
27
|
+
}
|