@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.
Files changed (134) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +200 -0
  3. package/dist/adapter/custom.d.ts +47 -0
  4. package/dist/adapter/custom.js +53 -0
  5. package/dist/adapter/custom.js.map +1 -0
  6. package/dist/adapter/default.d.ts +40 -0
  7. package/dist/adapter/default.js +202 -0
  8. package/dist/adapter/default.js.map +1 -0
  9. package/dist/cli/build.d.ts +1 -0
  10. package/dist/cli/build.js +31 -0
  11. package/dist/cli/build.js.map +1 -0
  12. package/dist/cli/dev.d.ts +1 -0
  13. package/dist/cli/dev.js +26 -0
  14. package/dist/cli/dev.js.map +1 -0
  15. package/dist/cli/index.d.ts +12 -0
  16. package/dist/cli/index.js +49 -0
  17. package/dist/cli/index.js.map +1 -0
  18. package/dist/cli/init.d.ts +1 -0
  19. package/dist/cli/init.js +70 -0
  20. package/dist/cli/init.js.map +1 -0
  21. package/dist/cli/shared.d.ts +33 -0
  22. package/dist/cli/shared.js +278 -0
  23. package/dist/cli/shared.js.map +1 -0
  24. package/dist/cli/start.d.ts +1 -0
  25. package/dist/cli/start.js +24 -0
  26. package/dist/cli/start.js.map +1 -0
  27. package/dist/config-loader.d.ts +49 -0
  28. package/dist/config-loader.js +140 -0
  29. package/dist/define-config.d.ts +188 -0
  30. package/dist/define-config.js +21 -0
  31. package/dist/index.d.ts +11 -0
  32. package/dist/index.js +9 -0
  33. package/package.json +124 -0
  34. package/src/adapter/custom.ts +90 -0
  35. package/src/adapter/default.ts +241 -0
  36. package/src/cli/build.ts +38 -0
  37. package/src/cli/dev.ts +38 -0
  38. package/src/cli/index.ts +52 -0
  39. package/src/cli/init.ts +76 -0
  40. package/src/cli/shared.ts +306 -0
  41. package/src/cli/start.ts +28 -0
  42. package/src/config-loader.ts +190 -0
  43. package/src/define-config.ts +206 -0
  44. package/src/index.ts +17 -0
  45. package/src/next-app/app/[...asset]/route.ts +81 -0
  46. package/src/next-app/app/_user-global.css +6 -0
  47. package/src/next-app/app/_user-sources.css +9 -0
  48. package/src/next-app/app/a11y/[name]/route.ts +19 -0
  49. package/src/next-app/app/api/search-index/route.ts +19 -0
  50. package/src/next-app/app/components/[name]/page.tsx +61 -0
  51. package/src/next-app/app/components/layout.tsx +18 -0
  52. package/src/next-app/app/docs/[slug]/page.tsx +53 -0
  53. package/src/next-app/app/docs/layout.tsx +18 -0
  54. package/src/next-app/app/globals.css +329 -0
  55. package/src/next-app/app/layout.tsx +102 -0
  56. package/src/next-app/app/page.tsx +9 -0
  57. package/src/next-app/app/preview-snapshot/[name]/page.tsx +20 -0
  58. package/src/next-app/app/preview-snapshot/layout.tsx +17 -0
  59. package/src/next-app/app/props/[name]/route.ts +19 -0
  60. package/src/next-app/app/r/[name]/route.ts +14 -0
  61. package/src/next-app/app/tests/[name]/route.ts +19 -0
  62. package/src/next-app/components/a11y-info.tsx +287 -0
  63. package/src/next-app/components/a11y-provider.tsx +39 -0
  64. package/src/next-app/components/component-breadcrumb.tsx +55 -0
  65. package/src/next-app/components/component-icon.tsx +140 -0
  66. package/src/next-app/components/component-preview.tsx +13 -0
  67. package/src/next-app/components/component-tabs.tsx +209 -0
  68. package/src/next-app/components/docs-toc.tsx +86 -0
  69. package/src/next-app/components/global-mobile-sidebar.tsx +35 -0
  70. package/src/next-app/components/header.tsx +188 -0
  71. package/src/next-app/components/heading-anchor.tsx +52 -0
  72. package/src/next-app/components/homepage-demo.tsx +180 -0
  73. package/src/next-app/components/locale-toggle.tsx +35 -0
  74. package/src/next-app/components/localized-mdx-client.tsx +14 -0
  75. package/src/next-app/components/localized-mdx.tsx +27 -0
  76. package/src/next-app/components/mobile-sidebar.tsx +22 -0
  77. package/src/next-app/components/nav-data-provider.tsx +37 -0
  78. package/src/next-app/components/navigation-progress.tsx +62 -0
  79. package/src/next-app/components/preview-canvas.tsx +368 -0
  80. package/src/next-app/components/preview-controls.tsx +94 -0
  81. package/src/next-app/components/preview-layout.tsx +218 -0
  82. package/src/next-app/components/props-table.tsx +134 -0
  83. package/src/next-app/components/resizable-preview.tsx +101 -0
  84. package/src/next-app/components/search.tsx +177 -0
  85. package/src/next-app/components/settings-modal.tsx +98 -0
  86. package/src/next-app/components/shell-ui/accordion.tsx +70 -0
  87. package/src/next-app/components/shell-ui/backdrop.tsx +29 -0
  88. package/src/next-app/components/shell-ui/badge.tsx +55 -0
  89. package/src/next-app/components/shell-ui/breadcrumb.tsx +120 -0
  90. package/src/next-app/components/shell-ui/button.tsx +64 -0
  91. package/src/next-app/components/shell-ui/card.tsx +127 -0
  92. package/src/next-app/components/shell-ui/checkbox.tsx +33 -0
  93. package/src/next-app/components/shell-ui/dialog.tsx +171 -0
  94. package/src/next-app/components/shell-ui/empty-state.tsx +66 -0
  95. package/src/next-app/components/shell-ui/input.tsx +27 -0
  96. package/src/next-app/components/shell-ui/kbd.tsx +30 -0
  97. package/src/next-app/components/shell-ui/label.tsx +25 -0
  98. package/src/next-app/components/shell-ui/select.tsx +204 -0
  99. package/src/next-app/components/shell-ui/separator.tsx +32 -0
  100. package/src/next-app/components/shell-ui/skeleton.tsx +18 -0
  101. package/src/next-app/components/shell-ui/table.tsx +124 -0
  102. package/src/next-app/components/shell-ui/tabs.tsx +102 -0
  103. package/src/next-app/components/shell-ui/toggle.tsx +56 -0
  104. package/src/next-app/components/sidebar-layout.tsx +37 -0
  105. package/src/next-app/components/sidebar-provider.tsx +75 -0
  106. package/src/next-app/components/sidebar.tsx +222 -0
  107. package/src/next-app/components/snapshot-preview.tsx +28 -0
  108. package/src/next-app/components/test-info.tsx +155 -0
  109. package/src/next-app/components/theme-provider.tsx +16 -0
  110. package/src/next-app/components/theme-toggle.tsx +21 -0
  111. package/src/next-app/components/translated-text.tsx +8 -0
  112. package/src/next-app/fallback/homepage.tsx +112 -0
  113. package/src/next-app/fallback/previews.ts +17 -0
  114. package/src/next-app/hooks/use-active-section.ts +23 -0
  115. package/src/next-app/hooks/use-controls.ts +72 -0
  116. package/src/next-app/hooks/use-mobile.ts +19 -0
  117. package/src/next-app/lib/branding.ts +52 -0
  118. package/src/next-app/lib/components-nav.ts +8 -0
  119. package/src/next-app/lib/docs.ts +16 -0
  120. package/src/next-app/lib/github.ts +38 -0
  121. package/src/next-app/lib/i18n.tsx +630 -0
  122. package/src/next-app/lib/locales.ts +17 -0
  123. package/src/next-app/lib/preview-loader.ts +7 -0
  124. package/src/next-app/lib/registry-adapter.ts +199 -0
  125. package/src/next-app/lib/utils.ts +6 -0
  126. package/src/next-app/next-env.d.ts +6 -0
  127. package/src/next-app/next.config.ts +101 -0
  128. package/src/next-app/postcss.config.mjs +7 -0
  129. package/src/next-app/public/favicon.ico +0 -0
  130. package/src/next-app/public/favicon_dark.svg +3 -0
  131. package/src/next-app/public/favicon_light.svg +3 -0
  132. package/src/next-app/registry.config.ts +50 -0
  133. package/src/next-app/tsconfig.json +29 -0
  134. package/src/next-app/user-aliases.d.ts +17 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Loads the user's `registry-shell.config.ts` at server boot. Uses `jiti` so
3
+ * the user doesn't need a build step or TS tooling of their own — the shell
4
+ * parses TS on the fly.
5
+ *
6
+ * Called once at Next.js server startup (see src/next-app/registry.config.ts).
7
+ * The returned `ResolvedShellConfig` contains absolute paths and defaults
8
+ * applied.
9
+ */
10
+ import "server-only";
11
+ import path from "node:path";
12
+ import fs from "node:fs";
13
+ import { createJiti } from "jiti";
14
+ // `skipBlocks`, `globalCss`, and `buildOutput` are excluded from the
15
+ // "must have a default" shape: skipBlocks is always an array (empty
16
+ // default); globalCss is purely opt-in; buildOutput is read by the CLI
17
+ // directly (with its own `.next` default) and doesn't flow through the
18
+ // server-side resolver.
19
+ const DEFAULT_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
+ a11y: "public/a11y",
26
+ tests: "public/tests",
27
+ props: "public/props",
28
+ skipBlocks: [],
29
+ };
30
+ const DEFAULT_BRANDING = {
31
+ siteName: "UI Registry",
32
+ shortName: "UI",
33
+ siteUrl: "",
34
+ logoAlt: "UI",
35
+ faviconDark: "/favicon_dark.svg",
36
+ faviconLight: "/favicon_light.svg",
37
+ faviconIco: "/favicon.ico",
38
+ };
39
+ /**
40
+ * Read env vars set by the CLI, load the config file, resolve paths. Returns
41
+ * `null` when no config is set (shell-only dev mode) — the Next app falls
42
+ * back to built-in shell docs.
43
+ */
44
+ export function loadResolvedConfig() {
45
+ const configPath = process.env.USER_CONFIG_PATH;
46
+ const root = process.env.USER_REGISTRY_ROOT;
47
+ if (!configPath || !root)
48
+ return null;
49
+ if (!fs.existsSync(configPath)) {
50
+ throw new Error(`[registry-shell] Config file not found: ${configPath}. ` +
51
+ `Check USER_CONFIG_PATH.`);
52
+ }
53
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
54
+ const loaded = jiti(configPath);
55
+ const config = extractDefault(loaded);
56
+ if (!config?.branding) {
57
+ throw new Error(`[registry-shell] Invalid config at ${configPath}: missing required \`branding\`.`);
58
+ }
59
+ if (config.multilocale && !config.defaultLocale) {
60
+ throw new Error(`[registry-shell] Invalid config at ${configPath}: \`multilocale\` is true but \`defaultLocale\` is missing.`);
61
+ }
62
+ const rootAbs = path.resolve(root);
63
+ const cfgPaths = config.paths ?? {};
64
+ return {
65
+ root: rootAbs,
66
+ configPath,
67
+ branding: applyBrandingDefaults(config.branding),
68
+ paths: {
69
+ components: path.resolve(rootAbs, cfgPaths.components ?? DEFAULT_PATHS.components),
70
+ blocks: path.resolve(rootAbs, cfgPaths.blocks ?? DEFAULT_PATHS.blocks),
71
+ previews: path.resolve(rootAbs, cfgPaths.previews ?? DEFAULT_PATHS.previews),
72
+ docs: path.resolve(rootAbs, cfgPaths.docs ?? DEFAULT_PATHS.docs),
73
+ registryJson: path.resolve(rootAbs, cfgPaths.registryJson ?? DEFAULT_PATHS.registryJson),
74
+ a11y: path.resolve(rootAbs, cfgPaths.a11y ?? DEFAULT_PATHS.a11y),
75
+ tests: path.resolve(rootAbs, cfgPaths.tests ?? DEFAULT_PATHS.tests),
76
+ props: path.resolve(rootAbs, cfgPaths.props ?? DEFAULT_PATHS.props),
77
+ skipBlocks: new Set(cfgPaths.skipBlocks ?? []),
78
+ globalCss: cfgPaths.globalCss ? path.resolve(rootAbs, cfgPaths.globalCss) : null,
79
+ },
80
+ homePage: config.homePage ? path.resolve(rootAbs, config.homePage) : null,
81
+ adapter: config.adapter ? path.resolve(rootAbs, config.adapter) : null,
82
+ extraTranslations: config.extraTranslations ?? {},
83
+ multilocale: Boolean(config.multilocale),
84
+ defaultLocale: config.defaultLocale ?? "",
85
+ locales: resolveLocales(rootAbs, cfgPaths.docs ?? DEFAULT_PATHS.docs, config),
86
+ };
87
+ }
88
+ /**
89
+ * Resolve the locale list for the locale toggle. In single-locale mode we
90
+ * return an empty array (the toggle hides itself). In multilocale mode we
91
+ * use the explicit `locales` config if present, otherwise auto-scan
92
+ * subfolders under `paths.docs`.
93
+ */
94
+ function resolveLocales(rootAbs, docsRel, config) {
95
+ if (!config.multilocale)
96
+ return [];
97
+ if (config.locales && config.locales.length > 0)
98
+ return [...config.locales];
99
+ const docsAbs = path.resolve(rootAbs, docsRel);
100
+ if (!fs.existsSync(docsAbs))
101
+ return config.defaultLocale ? [config.defaultLocale] : [];
102
+ const found = fs
103
+ .readdirSync(docsAbs, { withFileTypes: true })
104
+ .filter((d) => d.isDirectory())
105
+ .map((d) => d.name);
106
+ // Put the default locale first so the toggle cycles predictably.
107
+ if (config.defaultLocale && found.includes(config.defaultLocale)) {
108
+ return [config.defaultLocale, ...found.filter((l) => l !== config.defaultLocale)];
109
+ }
110
+ return found;
111
+ }
112
+ function extractDefault(loaded) {
113
+ if (loaded && typeof loaded === "object" && "default" in loaded) {
114
+ return loaded.default;
115
+ }
116
+ return loaded;
117
+ }
118
+ function applyBrandingDefaults(b) {
119
+ return {
120
+ siteName: b.siteName,
121
+ shortName: b.shortName,
122
+ siteUrl: b.siteUrl ?? "",
123
+ description: b.description ?? "",
124
+ ogImage: b.ogImage ?? "",
125
+ twitterHandle: b.twitterHandle ?? "",
126
+ github: b.github
127
+ ? {
128
+ owner: b.github.owner,
129
+ repo: b.github.repo,
130
+ label: b.github.label ?? "Github",
131
+ showStars: b.github.showStars ?? true,
132
+ }
133
+ : { owner: "", repo: "", label: "", showStars: false },
134
+ logoAlt: b.logoAlt ?? b.siteName,
135
+ faviconDark: b.faviconDark ?? DEFAULT_BRANDING.faviconDark,
136
+ faviconLight: b.faviconLight ?? DEFAULT_BRANDING.faviconLight,
137
+ faviconIco: b.faviconIco ?? DEFAULT_BRANDING.faviconIco,
138
+ };
139
+ }
140
+ //# sourceMappingURL=config-loader.js.map
@@ -0,0 +1,188 @@
1
+ /**
2
+ * `defineConfig` — the single entry point registry builders use.
3
+ *
4
+ * ```ts
5
+ * // registry-shell.config.ts
6
+ * import { defineConfig } from "@sntlr/registry-shell"
7
+ *
8
+ * export default defineConfig({
9
+ * branding: { siteName: "My UI", shortName: "UI", ... },
10
+ * // paths/homePage/adapter are optional
11
+ * })
12
+ * ```
13
+ */
14
+ export interface GithubConfig {
15
+ /** GitHub org or user that owns the repo. */
16
+ owner: string;
17
+ /** Repo name. */
18
+ repo: string;
19
+ /** Button label in the header. Default: `"Github"`. */
20
+ label?: string;
21
+ /**
22
+ * Show the public star count (fetched server-side, revalidated hourly).
23
+ * Default: `true`.
24
+ */
25
+ showStars?: boolean;
26
+ }
27
+ export interface BrandingConfig {
28
+ /** Full product name, e.g. "My UI". Used in HTML title. */
29
+ siteName: string;
30
+ /** Short breadcrumb label, e.g. "UI". */
31
+ shortName: string;
32
+ /** Canonical URL of the deployed registry, e.g. "https://ui.example.com". */
33
+ siteUrl?: string;
34
+ /** SEO meta description. Shown in search results + social cards. */
35
+ description?: string;
36
+ /** Path/URL to a 1200×630 Open Graph image. Relative to `siteUrl` if no scheme. */
37
+ ogImage?: string;
38
+ /** Twitter handle (without `@`) for Twitter card attribution. */
39
+ twitterHandle?: string;
40
+ /**
41
+ * Optional. Adds a GitHub link button to the header. Omit to hide the
42
+ * button entirely (default).
43
+ */
44
+ github?: GithubConfig;
45
+ /** Accessible alt text for the logo image. Default: siteName. */
46
+ logoAlt?: string;
47
+ /** Public path to the dark-theme SVG favicon. */
48
+ faviconDark?: string;
49
+ /** Public path to the light-theme SVG favicon. */
50
+ faviconLight?: string;
51
+ /** Public path to a fallback `.ico` favicon. */
52
+ faviconIco?: string;
53
+ }
54
+ /**
55
+ * Filesystem locations the default adapter scans. All paths are relative to
56
+ * the config file's directory. Any path can be omitted to use the default.
57
+ */
58
+ export interface ShellPaths {
59
+ /** Component source files. Default: "components/ui". */
60
+ components?: string;
61
+ /** Block directories (each block is a folder). Default: "registry/new-york/blocks". */
62
+ blocks?: string;
63
+ /** Preview index file. Default: "components/previews/index.ts" (or .tsx). */
64
+ previews?: string;
65
+ /** Doc MDX files. Default: "content/docs". */
66
+ docs?: string;
67
+ /** Built registry JSON files (served at /r/[name].json). Default: "public/r". */
68
+ registryJson?: string;
69
+ /** A11y JSON files (served at /a11y/[name].json). Default: "public/a11y". */
70
+ a11y?: string;
71
+ /** Test JSON files (served at /tests/[name].json). Default: "public/tests". */
72
+ tests?: string;
73
+ /** Props JSON files (served at /props/[name].json). Default: "public/props". */
74
+ props?: string;
75
+ /** Block names to omit from navigation (e.g. example blocks). */
76
+ skipBlocks?: string[];
77
+ /**
78
+ * Optional. Path to a `.css` file the shell imports AFTER its own
79
+ * globals. Use this for brand fonts (`@font-face`), token overrides
80
+ * (redefine `--primary` etc. on `:root` / `.dark`), extra `@source`
81
+ * directives, or any custom utilities.
82
+ *
83
+ * Imported at the very end of the shell's `globals.css` so your `:root`
84
+ * declarations win the cascade against the shell's defaults.
85
+ *
86
+ * Example: `globalCss: "./styles/theme.css"`.
87
+ */
88
+ globalCss?: string;
89
+ /**
90
+ * Optional. Directory (relative to the config file) where
91
+ * `registry-shell build` writes Next's build output, and where
92
+ * `registry-shell start` reads it back from. Default: `.next`.
93
+ *
94
+ * Override only if `.next` collides with something else in your
95
+ * project. Most Next.js hosts (Vercel, Netlify, self-hosted)
96
+ * auto-detect `.next` — if you change this, update your host's
97
+ * "Output Directory" setting to match.
98
+ */
99
+ buildOutput?: string;
100
+ }
101
+ /**
102
+ * Advanced: point at a custom adapter module. The module must default-export
103
+ * a factory `(resolved: ResolvedShellConfig) => RegistryAdapter`. When unset,
104
+ * the shell uses its built-in convention-based adapter.
105
+ */
106
+ export type CustomAdapterSpec = string;
107
+ export interface ShellConfig {
108
+ /**
109
+ * Required. Displayed in shell chrome.
110
+ */
111
+ branding: BrandingConfig;
112
+ /**
113
+ * When `true`, docs are organized under per-locale subfolders
114
+ * (e.g. `content/docs/en/foo.mdx`, `content/docs/fr/foo.mdx`) and each
115
+ * subfolder name is treated as a locale code. Requires `defaultLocale`.
116
+ *
117
+ * When `false` (default), docs live directly under `paths.docs` and
118
+ * locale variants use the file-extension convention `{slug}.{locale}.mdx`
119
+ * alongside the canonical `{slug}.mdx`.
120
+ */
121
+ multilocale?: boolean;
122
+ /**
123
+ * Required when `multilocale` is `true`. Locale code (e.g. `"en"`) of the
124
+ * subfolder containing the canonical doc set. Other locales are optional
125
+ * translations and fall back to this one when a slug is missing.
126
+ */
127
+ defaultLocale?: string;
128
+ /**
129
+ * Optional in multilocale mode. Explicit list of locale codes the shell
130
+ * should offer in its locale toggle (e.g. `["en", "fr", "ja"]`). When
131
+ * unset, the shell auto-discovers locales by scanning subfolders under
132
+ * `paths.docs`.
133
+ *
134
+ * Ignored in single-locale mode (the toggle is hidden).
135
+ */
136
+ locales?: string[];
137
+ /**
138
+ * Optional. Override filesystem layout. Defaults match the sntlr-registry
139
+ * convention.
140
+ */
141
+ paths?: ShellPaths;
142
+ /**
143
+ * Optional. Path to a custom homepage module, relative to config dir.
144
+ * The module must default-export a React component accepting
145
+ * `{ firstDocSlug?: string }` props.
146
+ */
147
+ homePage?: string;
148
+ /**
149
+ * Optional. Locale → key → value dictionaries merged into the shell's
150
+ * built-in i18n table. Use for marketing copy referenced by a custom
151
+ * homepage.
152
+ */
153
+ extraTranslations?: Record<string, Record<string, string>>;
154
+ /**
155
+ * Optional. Path to a custom adapter module. See `CustomAdapterSpec`.
156
+ */
157
+ adapter?: CustomAdapterSpec;
158
+ /**
159
+ * Optional. Pin the dev/start server to a specific port. Falls through to
160
+ * Next.js's default (3000, auto-incrementing if in use) when unset.
161
+ */
162
+ port?: number;
163
+ /**
164
+ * Optional. Extra npm package names the shell's Next.js build should
165
+ * transpile. Use this when your registry depends on another workspace
166
+ * package (e.g. a shared components library) whose TSX files should be
167
+ * compiled the same way as your own.
168
+ *
169
+ * Forwarded to Next's `transpilePackages`. The shell itself
170
+ * (`@sntlr/registry-shell`) is always transpiled regardless.
171
+ */
172
+ transpilePackages?: string[];
173
+ /**
174
+ * Optional. Template the shell uses to render the install command in the
175
+ * component "Install" tab. Supported placeholders:
176
+ * - `{name}` — the component/block slug (e.g. `"button"`)
177
+ * - `{siteUrl}` — `branding.siteUrl` (trailing slash stripped)
178
+ *
179
+ * Default: `"npx shadcn@latest add {siteUrl}/r/{name}.json"`. Set to an
180
+ * empty string to hide the install line entirely.
181
+ */
182
+ installCommandTemplate?: string;
183
+ }
184
+ /**
185
+ * Identity function with type inference — users call this purely for editor
186
+ * support. No runtime validation here; the shell validates at boot time.
187
+ */
188
+ export declare function defineConfig(config: ShellConfig): ShellConfig;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `defineConfig` — the single entry point registry builders use.
3
+ *
4
+ * ```ts
5
+ * // registry-shell.config.ts
6
+ * import { defineConfig } from "@sntlr/registry-shell"
7
+ *
8
+ * export default defineConfig({
9
+ * branding: { siteName: "My UI", shortName: "UI", ... },
10
+ * // paths/homePage/adapter are optional
11
+ * })
12
+ * ```
13
+ */
14
+ /**
15
+ * Identity function with type inference — users call this purely for editor
16
+ * support. No runtime validation here; the shell validates at boot time.
17
+ */
18
+ export function defineConfig(config) {
19
+ return config;
20
+ }
21
+ //# sourceMappingURL=define-config.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @sntlr/registry-shell — public API.
3
+ *
4
+ * Registry builders import `defineConfig` from here into their
5
+ * `registry-shell.config.ts`. Custom-adapter authors also import
6
+ * `ResolvedShellConfig` to type their factory's argument.
7
+ */
8
+ export { defineConfig } from "./define-config.js";
9
+ export type { ShellConfig, BrandingConfig, GithubConfig, ShellPaths, CustomAdapterSpec, } from "./define-config.js";
10
+ export type { ResolvedShellConfig } from "./config-loader.js";
11
+ export type { AdapterOverrides } from "./adapter/custom.js";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @sntlr/registry-shell — public API.
3
+ *
4
+ * Registry builders import `defineConfig` from here into their
5
+ * `registry-shell.config.ts`. Custom-adapter authors also import
6
+ * `ResolvedShellConfig` to type their factory's argument.
7
+ */
8
+ export { defineConfig } from "./define-config.js";
9
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,124 @@
1
+ {
2
+ "name": "@sntlr/registry-shell",
3
+ "version": "1.0.0",
4
+ "description": "Generic Next.js viewer for component registries. Drop a registry-shell.config.ts into your registry and get a full docs/components/blocks site on localhost:3000.",
5
+ "keywords": [
6
+ "shadcn",
7
+ "registry",
8
+ "components",
9
+ "ui",
10
+ "next",
11
+ "nextjs",
12
+ "docs",
13
+ "shell"
14
+ ],
15
+ "license": "MIT",
16
+ "author": "Scintillar",
17
+ "homepage": "https://github.com/scintillar-com/registry-shell#readme",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/scintillar-com/registry-shell.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/scintillar-com/registry-shell/issues"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "type": "module",
29
+ "bin": {
30
+ "registry-shell": "./dist/cli/index.js"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ },
37
+ "./shell/*": "./src/next-app/*",
38
+ "./package.json": "./package.json"
39
+ },
40
+ "files": [
41
+ "dist/adapter",
42
+ "dist/cli",
43
+ "dist/config-loader.js",
44
+ "dist/config-loader.d.ts",
45
+ "dist/define-config.js",
46
+ "dist/define-config.d.ts",
47
+ "dist/index.js",
48
+ "dist/index.d.ts",
49
+ "src/adapter",
50
+ "src/cli",
51
+ "src/config-loader.ts",
52
+ "src/define-config.ts",
53
+ "src/index.ts",
54
+ "src/next-app/app",
55
+ "src/next-app/components",
56
+ "src/next-app/fallback",
57
+ "src/next-app/hooks",
58
+ "src/next-app/lib",
59
+ "src/next-app/public",
60
+ "src/next-app/next.config.ts",
61
+ "src/next-app/next-env.d.ts",
62
+ "src/next-app/postcss.config.mjs",
63
+ "src/next-app/registry.config.ts",
64
+ "src/next-app/tsconfig.json",
65
+ "src/next-app/user-aliases.d.ts",
66
+ "README.md"
67
+ ],
68
+ "engines": {
69
+ "node": ">=18.18"
70
+ },
71
+ "packageManager": "pnpm@10.28.0",
72
+ "scripts": {
73
+ "dev:shell": "node dist/cli/index.js dev",
74
+ "build": "tsc -p tsconfig.cli.json",
75
+ "build:cli": "tsc -p tsconfig.cli.json",
76
+ "prepack": "tsc -p tsconfig.cli.json",
77
+ "lint": "eslint . --max-warnings 0",
78
+ "test": "vitest run",
79
+ "test:smoke": "pnpm build && playwright test"
80
+ },
81
+ "dependencies": {
82
+ "@dnd-kit/core": "^6.3.1",
83
+ "@dnd-kit/sortable": "^10.0.0",
84
+ "@dnd-kit/utilities": "^3.2.2",
85
+ "@tailwindcss/postcss": "^4.1.0",
86
+ "@tailwindcss/typography": "^0.5.19",
87
+ "class-variance-authority": "^0.7.1",
88
+ "clsx": "^2.1.1",
89
+ "cmdk": "^1.1.1",
90
+ "cross-env": "^7.0.3",
91
+ "date-fns": "^4.1.0",
92
+ "gray-matter": "^4.0.3",
93
+ "input-otp": "^1.4.2",
94
+ "jiti": "^2.4.2",
95
+ "lucide-react": "^0.503.0",
96
+ "next": "^15.5.14",
97
+ "next-mdx-remote": "^6.0.0",
98
+ "next-themes": "^0.4.6",
99
+ "radix-ui": "^1.4.3",
100
+ "react": "^19.1.0",
101
+ "react-day-picker": "^9.14.0",
102
+ "react-dom": "^19.1.0",
103
+ "remark-gfm": "^4.0.1",
104
+ "sonner": "^2.0.7",
105
+ "tailwind-merge": "^3.3.1",
106
+ "tailwindcss": "^4.1.0",
107
+ "tw-animate-css": "^1.3.6",
108
+ "yaml": "^2.8.3",
109
+ "zod": "^4.3.6"
110
+ },
111
+ "devDependencies": {
112
+ "@eslint/eslintrc": "^3.3.5",
113
+ "@playwright/test": "^1.59.1",
114
+ "@types/node": "^22.0.0",
115
+ "@types/react": "^19.0.0",
116
+ "@types/react-dom": "^19.0.0",
117
+ "eslint": "^9.39.4",
118
+ "eslint-config-next": "^15.5.15",
119
+ "eslint-plugin-react-hooks": "^7.0.1",
120
+ "tsx": "^4.21.0",
121
+ "typescript": "^5.7.0",
122
+ "vitest": "^4.1.3"
123
+ }
124
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Custom adapter loader.
3
+ *
4
+ * When a user's `registry-shell.config.ts` sets `adapter: "./my-adapter"`, the
5
+ * shell calls `loadCustomAdapterOverrides(resolved)` to resolve that module
6
+ * and extract method overrides. The overrides are then shallow-merged on top
7
+ * of the default convention-based adapter's methods.
8
+ *
9
+ * The user's module should default-export one of:
10
+ * • A factory `(resolved) => Partial<AdapterMethods>` — preferred. Gets the
11
+ * resolved config so it can build paths against `resolved.paths`, decide
12
+ * based on `resolved.root`, etc.
13
+ * • A plain `Partial<AdapterMethods>` object — also accepted for simple
14
+ * static overrides that don't need the resolved config.
15
+ *
16
+ * Only the methods a user supplies are taken; any method they omit falls
17
+ * through to the built-in default. That means you can override just
18
+ * `getAllComponents` to serve from a database and keep convention-based
19
+ * doc/registry-item loading for the rest.
20
+ *
21
+ * Loaded via jiti so the user can write the adapter in TypeScript without
22
+ * their own build step — same approach we use for the config file itself.
23
+ */
24
+ import "server-only"
25
+ import { createJiti } from "jiti"
26
+ import type { ResolvedShellConfig } from "../config-loader.js"
27
+
28
+ /**
29
+ * Shape of what a custom adapter module may override. Mirrors the public
30
+ * methods on RegistryAdapter (defined inside next-app/lib) minus the fields
31
+ * that are wired via other mechanisms (previewLoader via `@user/previews`
32
+ * alias, branding from config, homePage via `@user/homepage` alias).
33
+ *
34
+ * Typed with `unknown` args on the server side — the Next app re-applies
35
+ * its stricter RegistryAdapter types when consuming this.
36
+ */
37
+ export interface AdapterOverrides {
38
+ getAllComponents?: () => unknown[]
39
+ getAllDocs?: () => unknown[]
40
+ getDocBySlug?: (slug: string, locale?: string) => unknown | null
41
+ getDocAllLocales?: (slug: string) => Record<string, string>
42
+ getComponentSource?: (name: string) => string | null
43
+ getRegistryItem?: (name: string) => Promise<unknown | null>
44
+ getA11yData?: (name: string) => Promise<unknown | null>
45
+ getTestData?: (name: string) => Promise<unknown | null>
46
+ getPropsData?: (name: string) => Promise<unknown | null>
47
+ extraTranslations?: Record<string, Record<string, string>>
48
+ }
49
+
50
+ type AdapterFactory = (resolved: ResolvedShellConfig) => AdapterOverrides
51
+
52
+ export function loadCustomAdapterOverrides(
53
+ resolved: ResolvedShellConfig,
54
+ ): AdapterOverrides {
55
+ if (!resolved.adapter) return {}
56
+
57
+ const jiti = createJiti(import.meta.url, { interopDefault: true })
58
+ let loaded: unknown
59
+ try {
60
+ loaded = jiti(resolved.adapter)
61
+ } catch (err) {
62
+ throw new Error(
63
+ `[registry-shell] Failed to load custom adapter at ${resolved.adapter}: ` +
64
+ (err instanceof Error ? err.message : String(err)),
65
+ )
66
+ }
67
+
68
+ const extracted =
69
+ loaded && typeof loaded === "object" && "default" in loaded
70
+ ? (loaded as { default: unknown }).default
71
+ : loaded
72
+
73
+ if (typeof extracted === "function") {
74
+ const overrides = (extracted as AdapterFactory)(resolved)
75
+ if (!overrides || typeof overrides !== "object") {
76
+ throw new Error(
77
+ `[registry-shell] Custom adapter factory at ${resolved.adapter} did not return an object.`,
78
+ )
79
+ }
80
+ return overrides
81
+ }
82
+
83
+ if (extracted && typeof extracted === "object") {
84
+ return extracted as AdapterOverrides
85
+ }
86
+
87
+ throw new Error(
88
+ `[registry-shell] Custom adapter at ${resolved.adapter} must default-export a factory function or an object.`,
89
+ )
90
+ }