@sntlr/registry-shell 1.0.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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/adapter/custom.d.ts +47 -0
- package/dist/adapter/custom.js +53 -0
- package/dist/adapter/custom.js.map +1 -0
- package/dist/adapter/default.d.ts +40 -0
- package/dist/adapter/default.js +202 -0
- package/dist/adapter/default.js.map +1 -0
- package/dist/cli/build.d.ts +1 -0
- package/dist/cli/build.js +31 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/dev.d.ts +1 -0
- package/dist/cli/dev.js +26 -0
- package/dist/cli/dev.js.map +1 -0
- package/dist/cli/index.d.ts +12 -0
- package/dist/cli/index.js +49 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +70 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/shared.d.ts +33 -0
- package/dist/cli/shared.js +278 -0
- package/dist/cli/shared.js.map +1 -0
- package/dist/cli/start.d.ts +1 -0
- package/dist/cli/start.js +24 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/config-loader.d.ts +49 -0
- package/dist/config-loader.js +140 -0
- package/dist/define-config.d.ts +188 -0
- package/dist/define-config.js +21 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/package.json +124 -0
- package/src/adapter/custom.ts +90 -0
- package/src/adapter/default.ts +241 -0
- package/src/cli/build.ts +38 -0
- package/src/cli/dev.ts +38 -0
- package/src/cli/index.ts +52 -0
- package/src/cli/init.ts +76 -0
- package/src/cli/shared.ts +306 -0
- package/src/cli/start.ts +28 -0
- package/src/config-loader.ts +190 -0
- package/src/define-config.ts +206 -0
- package/src/index.ts +17 -0
- package/src/next-app/app/[...asset]/route.ts +81 -0
- package/src/next-app/app/_user-global.css +6 -0
- package/src/next-app/app/_user-sources.css +9 -0
- package/src/next-app/app/a11y/[name]/route.ts +19 -0
- package/src/next-app/app/api/search-index/route.ts +19 -0
- package/src/next-app/app/components/[name]/page.tsx +61 -0
- package/src/next-app/app/components/layout.tsx +18 -0
- package/src/next-app/app/docs/[slug]/page.tsx +53 -0
- package/src/next-app/app/docs/layout.tsx +18 -0
- package/src/next-app/app/globals.css +329 -0
- package/src/next-app/app/layout.tsx +102 -0
- package/src/next-app/app/page.tsx +9 -0
- package/src/next-app/app/preview-snapshot/[name]/page.tsx +20 -0
- package/src/next-app/app/preview-snapshot/layout.tsx +17 -0
- package/src/next-app/app/props/[name]/route.ts +19 -0
- package/src/next-app/app/r/[name]/route.ts +14 -0
- package/src/next-app/app/tests/[name]/route.ts +19 -0
- package/src/next-app/components/a11y-info.tsx +287 -0
- package/src/next-app/components/a11y-provider.tsx +39 -0
- package/src/next-app/components/component-breadcrumb.tsx +55 -0
- package/src/next-app/components/component-icon.tsx +140 -0
- package/src/next-app/components/component-preview.tsx +13 -0
- package/src/next-app/components/component-tabs.tsx +209 -0
- package/src/next-app/components/docs-toc.tsx +86 -0
- package/src/next-app/components/global-mobile-sidebar.tsx +35 -0
- package/src/next-app/components/header.tsx +188 -0
- package/src/next-app/components/heading-anchor.tsx +52 -0
- package/src/next-app/components/homepage-demo.tsx +180 -0
- package/src/next-app/components/locale-toggle.tsx +35 -0
- package/src/next-app/components/localized-mdx-client.tsx +14 -0
- package/src/next-app/components/localized-mdx.tsx +27 -0
- package/src/next-app/components/mobile-sidebar.tsx +22 -0
- package/src/next-app/components/nav-data-provider.tsx +37 -0
- package/src/next-app/components/navigation-progress.tsx +62 -0
- package/src/next-app/components/preview-canvas.tsx +368 -0
- package/src/next-app/components/preview-controls.tsx +94 -0
- package/src/next-app/components/preview-layout.tsx +218 -0
- package/src/next-app/components/props-table.tsx +134 -0
- package/src/next-app/components/resizable-preview.tsx +101 -0
- package/src/next-app/components/search.tsx +177 -0
- package/src/next-app/components/settings-modal.tsx +98 -0
- package/src/next-app/components/shell-ui/accordion.tsx +70 -0
- package/src/next-app/components/shell-ui/backdrop.tsx +29 -0
- package/src/next-app/components/shell-ui/badge.tsx +55 -0
- package/src/next-app/components/shell-ui/breadcrumb.tsx +120 -0
- package/src/next-app/components/shell-ui/button.tsx +64 -0
- package/src/next-app/components/shell-ui/card.tsx +127 -0
- package/src/next-app/components/shell-ui/checkbox.tsx +33 -0
- package/src/next-app/components/shell-ui/dialog.tsx +171 -0
- package/src/next-app/components/shell-ui/empty-state.tsx +66 -0
- package/src/next-app/components/shell-ui/input.tsx +27 -0
- package/src/next-app/components/shell-ui/kbd.tsx +30 -0
- package/src/next-app/components/shell-ui/label.tsx +25 -0
- package/src/next-app/components/shell-ui/select.tsx +204 -0
- package/src/next-app/components/shell-ui/separator.tsx +32 -0
- package/src/next-app/components/shell-ui/skeleton.tsx +18 -0
- package/src/next-app/components/shell-ui/table.tsx +124 -0
- package/src/next-app/components/shell-ui/tabs.tsx +102 -0
- package/src/next-app/components/shell-ui/toggle.tsx +56 -0
- package/src/next-app/components/sidebar-layout.tsx +37 -0
- package/src/next-app/components/sidebar-provider.tsx +75 -0
- package/src/next-app/components/sidebar.tsx +222 -0
- package/src/next-app/components/snapshot-preview.tsx +28 -0
- package/src/next-app/components/test-info.tsx +155 -0
- package/src/next-app/components/theme-provider.tsx +16 -0
- package/src/next-app/components/theme-toggle.tsx +21 -0
- package/src/next-app/components/translated-text.tsx +8 -0
- package/src/next-app/fallback/homepage.tsx +112 -0
- package/src/next-app/fallback/previews.ts +17 -0
- package/src/next-app/hooks/use-active-section.ts +23 -0
- package/src/next-app/hooks/use-controls.ts +72 -0
- package/src/next-app/hooks/use-mobile.ts +19 -0
- package/src/next-app/lib/branding.ts +52 -0
- package/src/next-app/lib/components-nav.ts +8 -0
- package/src/next-app/lib/docs.ts +16 -0
- package/src/next-app/lib/github.ts +38 -0
- package/src/next-app/lib/i18n.tsx +630 -0
- package/src/next-app/lib/locales.ts +17 -0
- package/src/next-app/lib/preview-loader.ts +7 -0
- package/src/next-app/lib/registry-adapter.ts +199 -0
- package/src/next-app/lib/utils.ts +6 -0
- package/src/next-app/next-env.d.ts +6 -0
- package/src/next-app/next.config.ts +101 -0
- package/src/next-app/postcss.config.mjs +7 -0
- package/src/next-app/public/favicon.ico +0 -0
- package/src/next-app/public/favicon_dark.svg +3 -0
- package/src/next-app/public/favicon_light.svg +3 -0
- package/src/next-app/registry.config.ts +50 -0
- package/src/next-app/tsconfig.json +29 -0
- package/src/next-app/user-aliases.d.ts +17 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default convention-based RegistryAdapter implementation.
|
|
3
|
+
*
|
|
4
|
+
* Builds a RegistryAdapter from a `ResolvedShellConfig`: scans the filesystem
|
|
5
|
+
* paths declared in the user's config, parses MDX frontmatter, and reads
|
|
6
|
+
* registry JSON. The `previewLoader` and `homePage` are wired separately via
|
|
7
|
+
* Next.js aliases (see next-app/next.config.ts) because Next's `dynamic()`
|
|
8
|
+
* needs string literal imports.
|
|
9
|
+
*/
|
|
10
|
+
import "server-only"
|
|
11
|
+
import fs from "node:fs"
|
|
12
|
+
import path from "node:path"
|
|
13
|
+
import matter from "gray-matter"
|
|
14
|
+
import type { ResolvedShellConfig } from "../config-loader.js"
|
|
15
|
+
|
|
16
|
+
// The RegistryAdapter interface lives inside the Next app (next-app/lib).
|
|
17
|
+
// This file is compiled to dist/ separately and consumed at Next runtime,
|
|
18
|
+
// so we re-declare the types it needs here.
|
|
19
|
+
|
|
20
|
+
export interface ComponentMeta {
|
|
21
|
+
name: string
|
|
22
|
+
label: string
|
|
23
|
+
kind: "component" | "block"
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface DocMeta {
|
|
27
|
+
slug: string
|
|
28
|
+
title: string
|
|
29
|
+
description: string
|
|
30
|
+
order: number
|
|
31
|
+
titles: Record<string, string>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface DocContent {
|
|
35
|
+
meta: Omit<DocMeta, "titles">
|
|
36
|
+
content: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function titleCase(slug: string) {
|
|
40
|
+
return slug
|
|
41
|
+
.split("-")
|
|
42
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
43
|
+
.join(" ")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createDefaultAdapter(resolved: ResolvedShellConfig) {
|
|
47
|
+
const { paths } = resolved
|
|
48
|
+
const isMulti = resolved.multilocale
|
|
49
|
+
const defaultLocale = resolved.defaultLocale
|
|
50
|
+
|
|
51
|
+
/** In multilocale mode, return all locale subfolder names. */
|
|
52
|
+
function listLocales(): string[] {
|
|
53
|
+
if (!isMulti || !fs.existsSync(paths.docs)) return []
|
|
54
|
+
return fs
|
|
55
|
+
.readdirSync(paths.docs, { withFileTypes: true })
|
|
56
|
+
.filter((d) => d.isDirectory())
|
|
57
|
+
.map((d) => d.name)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Path to the directory holding `.mdx` files for a given locale (multi mode). */
|
|
61
|
+
function localeDir(locale: string): string {
|
|
62
|
+
return path.join(paths.docs, locale)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getAllComponents(): ComponentMeta[] {
|
|
66
|
+
const items: ComponentMeta[] = []
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(paths.components)) {
|
|
69
|
+
for (const filename of fs.readdirSync(paths.components).filter((f) => f.endsWith(".tsx"))) {
|
|
70
|
+
const name = filename.replace(/\.tsx$/, "")
|
|
71
|
+
items.push({ name, label: titleCase(name), kind: "component" })
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (fs.existsSync(paths.blocks)) {
|
|
76
|
+
for (const dir of fs.readdirSync(paths.blocks, { withFileTypes: true })) {
|
|
77
|
+
if (!dir.isDirectory() || paths.skipBlocks.has(dir.name)) continue
|
|
78
|
+
items.push({ name: dir.name, label: titleCase(dir.name), kind: "block" })
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return items.sort((a, b) => a.label.localeCompare(b.label))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getAllDocs(): DocMeta[] {
|
|
86
|
+
if (!fs.existsSync(paths.docs)) return []
|
|
87
|
+
|
|
88
|
+
if (isMulti) {
|
|
89
|
+
const dir = localeDir(defaultLocale)
|
|
90
|
+
if (!fs.existsSync(dir)) return []
|
|
91
|
+
const otherLocales = listLocales().filter((l) => l !== defaultLocale)
|
|
92
|
+
|
|
93
|
+
return fs
|
|
94
|
+
.readdirSync(dir)
|
|
95
|
+
.filter((f) => f.endsWith(".mdx"))
|
|
96
|
+
.map((filename) => {
|
|
97
|
+
const slug = filename.replace(/\.mdx$/, "")
|
|
98
|
+
const { data } = matter(fs.readFileSync(path.join(dir, filename), "utf-8"))
|
|
99
|
+
const titles: Record<string, string> = { [defaultLocale]: data.title ?? slug }
|
|
100
|
+
|
|
101
|
+
for (const loc of otherLocales) {
|
|
102
|
+
const p = path.join(localeDir(loc), filename)
|
|
103
|
+
if (!fs.existsSync(p)) continue
|
|
104
|
+
const { data: locData } = matter(fs.readFileSync(p, "utf-8"))
|
|
105
|
+
if (locData.title) titles[loc] = locData.title
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
slug,
|
|
110
|
+
title: data.title ?? slug,
|
|
111
|
+
description: data.description ?? "",
|
|
112
|
+
order: data.order ?? 999,
|
|
113
|
+
titles,
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
.sort((a, b) => a.order - b.order)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Single-folder mode: one locale, no variant scanning.
|
|
120
|
+
return fs
|
|
121
|
+
.readdirSync(paths.docs)
|
|
122
|
+
.filter((f) => f.endsWith(".mdx"))
|
|
123
|
+
.map((filename) => {
|
|
124
|
+
const slug = filename.replace(/\.mdx$/, "")
|
|
125
|
+
const { data } = matter(fs.readFileSync(path.join(paths.docs, filename), "utf-8"))
|
|
126
|
+
return {
|
|
127
|
+
slug,
|
|
128
|
+
title: data.title ?? slug,
|
|
129
|
+
description: data.description ?? "",
|
|
130
|
+
order: data.order ?? 999,
|
|
131
|
+
titles: { en: data.title ?? slug },
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
.sort((a, b) => a.order - b.order)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getDocBySlug(slug: string, locale?: string): DocContent | null {
|
|
138
|
+
if (isMulti) {
|
|
139
|
+
const want = locale && locale !== defaultLocale ? locale : defaultLocale
|
|
140
|
+
const candidates = [
|
|
141
|
+
path.join(localeDir(want), `${slug}.mdx`),
|
|
142
|
+
path.join(localeDir(defaultLocale), `${slug}.mdx`),
|
|
143
|
+
]
|
|
144
|
+
return readDocFile(slug, candidates)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Single-folder mode: `locale` is ignored, only `{slug}.mdx` matters.
|
|
148
|
+
return readDocFile(slug, [path.join(paths.docs, `${slug}.mdx`)])
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function getDocAllLocales(slug: string): Record<string, string> {
|
|
152
|
+
if (!fs.existsSync(paths.docs)) return {}
|
|
153
|
+
|
|
154
|
+
if (isMulti) {
|
|
155
|
+
const out: Record<string, string> = {}
|
|
156
|
+
for (const loc of listLocales()) {
|
|
157
|
+
const p = path.join(localeDir(loc), `${slug}.mdx`)
|
|
158
|
+
if (fs.existsSync(p)) {
|
|
159
|
+
out[loc] = matter(fs.readFileSync(p, "utf-8")).content
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return out
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Single-folder mode: exactly one file, keyed under `en` so the client
|
|
166
|
+
// renderer's fallback chain works without needing a special case.
|
|
167
|
+
const p = path.join(paths.docs, `${slug}.mdx`)
|
|
168
|
+
if (!fs.existsSync(p)) return {}
|
|
169
|
+
return { en: matter(fs.readFileSync(p, "utf-8")).content }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getComponentSource(name: string): string | null {
|
|
173
|
+
const candidates = [
|
|
174
|
+
path.join(paths.components, `${name}.tsx`),
|
|
175
|
+
path.join(paths.blocks, name, `${name}.tsx`),
|
|
176
|
+
]
|
|
177
|
+
for (const p of candidates) {
|
|
178
|
+
if (fs.existsSync(p)) return fs.readFileSync(p, "utf-8")
|
|
179
|
+
}
|
|
180
|
+
return null
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function getRegistryItem(name: string): Promise<unknown | null> {
|
|
184
|
+
return readJson(paths.registryJson, name)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function getA11yData(name: string): Promise<unknown | null> {
|
|
188
|
+
return readJson(paths.a11y, name)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function getTestData(name: string): Promise<unknown | null> {
|
|
192
|
+
return readJson(paths.tests, name)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function getPropsData(name: string): Promise<unknown | null> {
|
|
196
|
+
return readJson(paths.props, name)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
getAllComponents,
|
|
201
|
+
getAllDocs,
|
|
202
|
+
getDocBySlug,
|
|
203
|
+
getDocAllLocales,
|
|
204
|
+
getComponentSource,
|
|
205
|
+
getRegistryItem,
|
|
206
|
+
getA11yData,
|
|
207
|
+
getTestData,
|
|
208
|
+
getPropsData,
|
|
209
|
+
branding: resolved.branding,
|
|
210
|
+
extraTranslations: resolved.extraTranslations,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function readJson(dir: string, name: string): Promise<unknown | null> {
|
|
215
|
+
const base = name.replace(/\.json$/, "")
|
|
216
|
+
const filePath = path.join(dir, `${base}.json`)
|
|
217
|
+
try {
|
|
218
|
+
const data = await fs.promises.readFile(filePath, "utf-8")
|
|
219
|
+
return JSON.parse(data)
|
|
220
|
+
} catch {
|
|
221
|
+
return null
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Try each candidate path; return the first that parses to a DocContent. */
|
|
226
|
+
function readDocFile(slug: string, candidates: string[]): DocContent | null {
|
|
227
|
+
for (const p of candidates) {
|
|
228
|
+
if (!fs.existsSync(p)) continue
|
|
229
|
+
const { data, content } = matter(fs.readFileSync(p, "utf-8"))
|
|
230
|
+
return {
|
|
231
|
+
meta: {
|
|
232
|
+
slug,
|
|
233
|
+
title: data.title ?? slug,
|
|
234
|
+
description: data.description ?? "",
|
|
235
|
+
order: data.order ?? 999,
|
|
236
|
+
},
|
|
237
|
+
content,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return null
|
|
241
|
+
}
|
package/src/cli/build.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `registry-shell build` — run `next build` against the user's project.
|
|
3
|
+
*
|
|
4
|
+
* Writes the Next build output to `<user-project>/.next` via `distDir` so
|
|
5
|
+
* generic Next.js hosts (Vercel, self-hosted) find it where they expect.
|
|
6
|
+
* Without this, the output would land inside
|
|
7
|
+
* `node_modules/@sntlr/registry-shell/src/next-app/.next` and no external
|
|
8
|
+
* host could discover it.
|
|
9
|
+
*/
|
|
10
|
+
import path from "node:path"
|
|
11
|
+
import { spawn } from "node:child_process"
|
|
12
|
+
import {
|
|
13
|
+
NEXT_BIN,
|
|
14
|
+
buildEnvVars,
|
|
15
|
+
clearStaleNextCacheIfModeChanged,
|
|
16
|
+
loadUserConfig,
|
|
17
|
+
nextAppDir,
|
|
18
|
+
writeUserSourcesCss,
|
|
19
|
+
} from "./shared.js"
|
|
20
|
+
|
|
21
|
+
export async function run(args: string[]): Promise<void> {
|
|
22
|
+
const loaded = loadUserConfig()
|
|
23
|
+
clearStaleNextCacheIfModeChanged(loaded)
|
|
24
|
+
writeUserSourcesCss(loaded)
|
|
25
|
+
const userDistDir = loaded
|
|
26
|
+
? path.resolve(loaded.root, loaded.config.paths?.buildOutput ?? ".next")
|
|
27
|
+
: undefined
|
|
28
|
+
const env = {
|
|
29
|
+
...process.env,
|
|
30
|
+
...buildEnvVars(loaded),
|
|
31
|
+
...(userDistDir ? { USER_DIST_DIR: userDistDir } : {}),
|
|
32
|
+
}
|
|
33
|
+
const child = spawn(process.execPath, [NEXT_BIN, "build", nextAppDir(), ...args], {
|
|
34
|
+
stdio: "inherit",
|
|
35
|
+
env,
|
|
36
|
+
})
|
|
37
|
+
child.on("exit", (code) => process.exit(code ?? 0))
|
|
38
|
+
}
|
package/src/cli/dev.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `registry-shell dev` — run `next dev` against the user's project.
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process"
|
|
5
|
+
import {
|
|
6
|
+
NEXT_BIN,
|
|
7
|
+
buildEnvVars,
|
|
8
|
+
clearStaleNextCacheIfModeChanged,
|
|
9
|
+
loadUserConfig,
|
|
10
|
+
nextAppDir,
|
|
11
|
+
writeUserSourcesCss,
|
|
12
|
+
} from "./shared.js"
|
|
13
|
+
|
|
14
|
+
export async function run(args: string[]): Promise<void> {
|
|
15
|
+
const loaded = loadUserConfig()
|
|
16
|
+
if (loaded) {
|
|
17
|
+
console.log(`[registry-shell] Using config: ${loaded.configPath}`)
|
|
18
|
+
} else {
|
|
19
|
+
console.log(`[registry-shell] No registry-shell.config.ts found — running in shell-only mode.`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
clearStaleNextCacheIfModeChanged(loaded)
|
|
23
|
+
writeUserSourcesCss(loaded)
|
|
24
|
+
const env = { ...process.env, ...buildEnvVars(loaded) }
|
|
25
|
+
const portArgs = loaded?.config.port ? ["-p", String(loaded.config.port)] : []
|
|
26
|
+
// Webpack by default. Turbopack currently can't compile files reached via
|
|
27
|
+
// the `@user/*` cross-project aliases — it treats them as native Node ESM
|
|
28
|
+
// and crashes on `next/dynamic`. Set UI_SHELL_TURBOPACK=1 to opt in once
|
|
29
|
+
// Turbopack supports this (track https://github.com/vercel/next.js).
|
|
30
|
+
const turbopackArgs = process.env.UI_SHELL_TURBOPACK ? ["--turbopack"] : []
|
|
31
|
+
const child = spawn(
|
|
32
|
+
process.execPath,
|
|
33
|
+
[NEXT_BIN, "dev", nextAppDir(), ...turbopackArgs, ...portArgs, ...args],
|
|
34
|
+
{ stdio: "inherit", env },
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
child.on("exit", (code) => process.exit(code ?? 0))
|
|
38
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @sntlr/registry-shell CLI entry.
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
* init Scaffold registry-shell.config.ts and add a script to package.json.
|
|
7
|
+
* dev Run `next dev` pointed at the user's current directory.
|
|
8
|
+
* build Run `next build` pointed at the user's current directory.
|
|
9
|
+
* start Run `next start` for a built shell.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const USAGE = `Usage: registry-shell <command> [args]
|
|
13
|
+
|
|
14
|
+
Commands:
|
|
15
|
+
init Scaffold registry-shell.config.ts in the current project.
|
|
16
|
+
dev Start the shell in dev mode on http://localhost:3000.
|
|
17
|
+
build Build the shell for production.
|
|
18
|
+
start Start the production server against a prior build.
|
|
19
|
+
`
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
const [, , cmd, ...args] = process.argv
|
|
23
|
+
|
|
24
|
+
if (!cmd || cmd === "--help" || cmd === "-h") {
|
|
25
|
+
console.log(USAGE)
|
|
26
|
+
process.exit(0)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch (cmd) {
|
|
30
|
+
case "init":
|
|
31
|
+
await (await import("./init.js")).run(args)
|
|
32
|
+
break
|
|
33
|
+
case "dev":
|
|
34
|
+
await (await import("./dev.js")).run(args)
|
|
35
|
+
break
|
|
36
|
+
case "build":
|
|
37
|
+
await (await import("./build.js")).run(args)
|
|
38
|
+
break
|
|
39
|
+
case "start":
|
|
40
|
+
await (await import("./start.js")).run(args)
|
|
41
|
+
break
|
|
42
|
+
default:
|
|
43
|
+
console.error(`Unknown command: ${cmd}\n`)
|
|
44
|
+
console.log(USAGE)
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
main().catch((err) => {
|
|
50
|
+
console.error(err instanceof Error ? err.message : err)
|
|
51
|
+
process.exit(1)
|
|
52
|
+
})
|
package/src/cli/init.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `registry-shell init` — scaffold `registry-shell.config.ts` in the current
|
|
3
|
+
* directory and add a `shell` script to the project's package.json.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs"
|
|
6
|
+
import path from "node:path"
|
|
7
|
+
|
|
8
|
+
const TEMPLATE = `import { defineConfig } from "@sntlr/registry-shell"
|
|
9
|
+
|
|
10
|
+
export default defineConfig({
|
|
11
|
+
branding: {
|
|
12
|
+
siteName: "My UI",
|
|
13
|
+
shortName: "UI",
|
|
14
|
+
// siteUrl: "https://ui.example.com",
|
|
15
|
+
// github: { owner: "my-org", repo: "my-ui" },
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
// All path overrides are optional — these are the defaults:
|
|
19
|
+
// paths: {
|
|
20
|
+
// components: "components/ui",
|
|
21
|
+
// blocks: "registry/new-york/blocks",
|
|
22
|
+
// previews: "components/previews/index.ts",
|
|
23
|
+
// docs: "content/docs",
|
|
24
|
+
// registryJson: "public/r",
|
|
25
|
+
// skipBlocks: [],
|
|
26
|
+
// // Optional: your own global CSS (brand fonts, token overrides,
|
|
27
|
+
// // extra @source directives). Imported after the shell's globals so
|
|
28
|
+
// // your :root { --primary: ... } wins the cascade.
|
|
29
|
+
// // globalCss: "./styles/theme.css",
|
|
30
|
+
// },
|
|
31
|
+
|
|
32
|
+
// Custom homepage component (optional):
|
|
33
|
+
// homePage: "./components/homepage",
|
|
34
|
+
})
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
export async function run(_args: string[]): Promise<void> {
|
|
38
|
+
const cwd = process.cwd()
|
|
39
|
+
const configPath = path.join(cwd, "registry-shell.config.ts")
|
|
40
|
+
|
|
41
|
+
if (fs.existsSync(configPath)) {
|
|
42
|
+
console.log(`[registry-shell] Config already exists: ${configPath}`)
|
|
43
|
+
} else {
|
|
44
|
+
fs.writeFileSync(configPath, TEMPLATE, "utf-8")
|
|
45
|
+
console.log(`[registry-shell] Wrote ${configPath}`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add "shell" script to package.json if missing.
|
|
49
|
+
const pkgPath = path.join(cwd, "package.json")
|
|
50
|
+
if (fs.existsSync(pkgPath)) {
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")) as Record<string, unknown>
|
|
52
|
+
pkg.scripts = (pkg.scripts as Record<string, string> | undefined) ?? {}
|
|
53
|
+
const scripts = pkg.scripts as Record<string, string>
|
|
54
|
+
let changed = false
|
|
55
|
+
if (!scripts.shell) {
|
|
56
|
+
scripts.shell = "registry-shell dev"
|
|
57
|
+
changed = true
|
|
58
|
+
}
|
|
59
|
+
if (!scripts["shell:build"]) {
|
|
60
|
+
scripts["shell:build"] = "registry-shell build"
|
|
61
|
+
changed = true
|
|
62
|
+
}
|
|
63
|
+
if (changed) {
|
|
64
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8")
|
|
65
|
+
console.log(`[registry-shell] Added "shell" / "shell:build" scripts to package.json`)
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
console.log(
|
|
69
|
+
`[registry-shell] No package.json found — skipped script injection. Run \`npm init\` first.`,
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(
|
|
74
|
+
`\nNext steps:\n 1. Edit registry-shell.config.ts (branding at minimum)\n 2. Run: npm run shell`,
|
|
75
|
+
)
|
|
76
|
+
}
|