@sntlr/registry-shell 1.1.3 → 2.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/README.md +26 -28
- package/dist/adapter/default.d.ts +0 -10
- package/dist/adapter/default.js +0 -1
- package/dist/adapter/default.js.map +1 -1
- package/dist/cli/build.js +80 -33
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev.js +38 -1
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/generate-search-index.d.ts +8 -0
- package/dist/cli/generate-search-index.js +52 -0
- package/dist/cli/generate-search-index.js.map +1 -0
- package/dist/cli/index.d.ts +8 -5
- package/dist/cli/index.js +10 -8
- package/dist/cli/index.js.map +1 -1
- package/dist/config-loader.d.ts +0 -10
- package/dist/config-loader.js +6 -2
- package/package.json +1 -1
- package/src/adapter/default.ts +0 -1
- package/src/cli/build.ts +96 -35
- package/src/cli/dev.ts +42 -1
- package/src/cli/generate-search-index.ts +82 -0
- package/src/cli/index.ts +12 -8
- package/src/config-loader.ts +6 -2
- package/src/next-app/components/search.tsx +1 -1
- package/src/next-app/next.config.ts +13 -8
- package/dist/cli/start.d.ts +0 -1
- package/dist/cli/start.js +0 -47
- package/dist/cli/start.js.map +0 -1
- package/src/cli/start.ts +0 -57
- package/src/next-app/app/[...asset]/route.ts +0 -81
- package/src/next-app/app/a11y/[name]/route.ts +0 -19
- package/src/next-app/app/api/search-index/route.ts +0 -19
- package/src/next-app/app/props/[name]/route.ts +0 -19
- package/src/next-app/app/r/[name]/route.ts +0 -14
- package/src/next-app/app/tests/[name]/route.ts +0 -19
package/src/cli/build.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `registry-shell build` —
|
|
2
|
+
* `registry-shell build` — produces a Next.js static export tree in
|
|
3
|
+
* `<user-project>/out/`.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
5
|
+
* Pipeline:
|
|
6
|
+
* 1. Overlay user's public/ onto the shell's bundled public/ (so Next
|
|
7
|
+
* sees user's registry manifests at /r/*.json, /a11y/*.json, etc.).
|
|
8
|
+
* 2. Run build-time generators that write derived JSON into the merged
|
|
9
|
+
* public/ — currently just `api/search-index.json`.
|
|
10
|
+
* 3. Run `next build` with `output: "export"` (set in next.config.ts).
|
|
11
|
+
* Next writes static HTML/JS/CSS to `<shell>/out/`.
|
|
12
|
+
* 4. Copy `<shell>/out/` → `<user-project>/out/`.
|
|
13
|
+
* 5. Restore shell's public/ to its pristine state (remove anything we
|
|
14
|
+
* added in step 1/2) so repeated builds are idempotent.
|
|
13
15
|
*/
|
|
14
16
|
import fs from "node:fs"
|
|
15
17
|
import path from "node:path"
|
|
@@ -22,53 +24,112 @@ import {
|
|
|
22
24
|
nextAppDir,
|
|
23
25
|
writeUserSourcesCss,
|
|
24
26
|
} from "./shared.js"
|
|
27
|
+
import { generateSearchIndex } from "./generate-search-index.js"
|
|
25
28
|
|
|
26
29
|
export async function run(args: string[]): Promise<void> {
|
|
27
30
|
const loaded = loadUserConfig()
|
|
31
|
+
if (!loaded) {
|
|
32
|
+
console.error(
|
|
33
|
+
"[registry-shell] No registry-shell.config.ts found — `build` requires a registry.",
|
|
34
|
+
)
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
clearStaleNextCacheIfModeChanged(loaded)
|
|
29
39
|
writeUserSourcesCss(loaded)
|
|
30
40
|
|
|
31
41
|
const shellNextApp = nextAppDir()
|
|
42
|
+
const shellPublic = path.join(shellNextApp, "public")
|
|
43
|
+
const userPublic = path.join(loaded.root, "public")
|
|
44
|
+
|
|
45
|
+
// Step 1: Snapshot shell's public/ before overlay so we can restore it.
|
|
46
|
+
const shellOwnEntries: Set<string> = new Set(
|
|
47
|
+
fs.existsSync(shellPublic) ? fs.readdirSync(shellPublic) : [],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
// Step 2: Overlay user's public/ onto shell's public/ (user files win).
|
|
51
|
+
const overlaidEntries: string[] = []
|
|
52
|
+
if (fs.existsSync(userPublic)) {
|
|
53
|
+
for (const entry of fs.readdirSync(userPublic)) {
|
|
54
|
+
const src = path.join(userPublic, entry)
|
|
55
|
+
const dest = path.join(shellPublic, entry)
|
|
56
|
+
fs.cpSync(src, dest, { recursive: true, force: true })
|
|
57
|
+
overlaidEntries.push(entry)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Apply the shell env vars to THIS process so build-time generators can
|
|
62
|
+
// call `loadResolvedConfig()` (which reads USER_REGISTRY_ROOT etc.).
|
|
63
|
+
// The same env is also forwarded to the spawn child below.
|
|
32
64
|
const env = { ...process.env, ...buildEnvVars(loaded) }
|
|
33
|
-
|
|
65
|
+
Object.assign(process.env, buildEnvVars(loaded))
|
|
66
|
+
|
|
67
|
+
// Step 3: Pre-build generators (writes into shell's public/ so Next picks up).
|
|
68
|
+
try {
|
|
69
|
+
await generateSearchIndex(loaded, shellPublic)
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.warn(
|
|
72
|
+
`[registry-shell] search-index generation failed: ${(err as Error).message}`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Step 4: next build (static export — writes to <shellNextApp>/out/).
|
|
77
|
+
const buildChild = spawn(
|
|
34
78
|
process.execPath,
|
|
35
79
|
[NEXT_BIN, "build", shellNextApp, ...args],
|
|
36
80
|
{ stdio: "inherit", env },
|
|
37
81
|
)
|
|
38
82
|
|
|
39
|
-
|
|
83
|
+
buildChild.on("exit", (code) => {
|
|
40
84
|
if (code !== 0) {
|
|
85
|
+
restoreShellPublic(shellPublic, shellOwnEntries, overlaidEntries)
|
|
41
86
|
process.exit(code ?? 1)
|
|
42
87
|
}
|
|
43
|
-
|
|
88
|
+
|
|
89
|
+
// Step 5: Copy out/ to user project root.
|
|
90
|
+
const src = path.join(shellNextApp, "out")
|
|
91
|
+
const dest = path.resolve(loaded.root, "out")
|
|
92
|
+
if (fs.existsSync(src)) {
|
|
93
|
+
if (fs.existsSync(dest)) {
|
|
94
|
+
fs.rmSync(dest, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
95
|
+
}
|
|
96
|
+
fs.cpSync(src, dest, { recursive: true })
|
|
97
|
+
// Remove the build output from inside node_modules so it doesn't
|
|
98
|
+
// accumulate stale copies across releases.
|
|
99
|
+
fs.rmSync(src, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 })
|
|
100
|
+
console.log(`[registry-shell] Static build ready at ${dest}`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Step 6: Restore shell's public/ (remove overlay).
|
|
104
|
+
restoreShellPublic(shellPublic, shellOwnEntries, overlaidEntries)
|
|
44
105
|
process.exit(0)
|
|
45
106
|
})
|
|
46
107
|
}
|
|
47
108
|
|
|
48
109
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* same path.
|
|
110
|
+
* Removes anything from `shellPublic` that wasn't there before the overlay.
|
|
111
|
+
* Keeps shell's bundled favicons + anything else that was part of the
|
|
112
|
+
* shipped package. Runs on both success and failure paths.
|
|
53
113
|
*/
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
114
|
+
function restoreShellPublic(
|
|
115
|
+
shellPublic: string,
|
|
116
|
+
shellOwnEntries: Set<string>,
|
|
117
|
+
overlaidEntries: string[],
|
|
58
118
|
): void {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
119
|
+
if (!fs.existsSync(shellPublic)) return
|
|
120
|
+
for (const entry of overlaidEntries) {
|
|
121
|
+
if (!shellOwnEntries.has(entry)) {
|
|
122
|
+
// Entry didn't exist before overlay — remove cleanly.
|
|
123
|
+
fs.rmSync(path.join(shellPublic, entry), {
|
|
124
|
+
recursive: true,
|
|
125
|
+
force: true,
|
|
126
|
+
maxRetries: 5,
|
|
127
|
+
retryDelay: 200,
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
// Entry existed in shell's own public/ — we overwrote it, can't
|
|
131
|
+
// cleanly restore without reinstalling. Leave it; on next build the
|
|
132
|
+
// user's version replaces it again. Mostly affects favicon.* files
|
|
133
|
+
// the user might customize.
|
|
71
134
|
}
|
|
72
|
-
fs.renameSync(src, dest)
|
|
73
|
-
console.log(`[registry-shell] Build output moved to ${dest}`)
|
|
74
135
|
}
|
package/src/cli/dev.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `registry-shell dev` — run `next dev` against the user's project.
|
|
3
|
+
*
|
|
4
|
+
* Dev mode runs Next in its normal dynamic-rendering mode (so hot reload
|
|
5
|
+
* / live MDX / interactive previews all work). The `output: "export"`
|
|
6
|
+
* config only affects `next build`, so dev behaves identically to pre-v2.
|
|
7
|
+
*
|
|
8
|
+
* We still overlay the user's `public/` onto the shell's bundled `public/`
|
|
9
|
+
* before starting Next, so dynamic registry assets (`/r/*.json`,
|
|
10
|
+
* `/a11y/*.json`, `/props/*.json`, `/tests/*.json`, `/api/search-index.json`,
|
|
11
|
+
* plus any user-custom static files) are served the same way they will be
|
|
12
|
+
* in a production build. Note: changes to the user's `public/` during a
|
|
13
|
+
* dev session require a restart to refresh the overlay.
|
|
3
14
|
*/
|
|
15
|
+
import fs from "node:fs"
|
|
16
|
+
import path from "node:path"
|
|
4
17
|
import { spawn } from "node:child_process"
|
|
5
18
|
import {
|
|
6
19
|
NEXT_BIN,
|
|
@@ -10,6 +23,7 @@ import {
|
|
|
10
23
|
nextAppDir,
|
|
11
24
|
writeUserSourcesCss,
|
|
12
25
|
} from "./shared.js"
|
|
26
|
+
import { generateSearchIndex } from "./generate-search-index.js"
|
|
13
27
|
|
|
14
28
|
export async function run(args: string[]): Promise<void> {
|
|
15
29
|
const loaded = loadUserConfig()
|
|
@@ -21,6 +35,22 @@ export async function run(args: string[]): Promise<void> {
|
|
|
21
35
|
|
|
22
36
|
clearStaleNextCacheIfModeChanged(loaded)
|
|
23
37
|
writeUserSourcesCss(loaded)
|
|
38
|
+
|
|
39
|
+
const shellNextApp = nextAppDir()
|
|
40
|
+
|
|
41
|
+
// Overlay user's public/ onto shell's public/ so dev-mode URLs match
|
|
42
|
+
// what production will serve statically. Skipped in shell-only mode.
|
|
43
|
+
if (loaded) {
|
|
44
|
+
overlayUserPublic(loaded.root, shellNextApp)
|
|
45
|
+
try {
|
|
46
|
+
await generateSearchIndex(loaded, path.join(shellNextApp, "public"))
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.warn(
|
|
49
|
+
`[registry-shell] search-index generation failed: ${(err as Error).message}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
24
54
|
const env = { ...process.env, ...buildEnvVars(loaded) }
|
|
25
55
|
const portArgs = loaded?.config.port ? ["-p", String(loaded.config.port)] : []
|
|
26
56
|
// Webpack by default. Turbopack currently can't compile files reached via
|
|
@@ -30,9 +60,20 @@ export async function run(args: string[]): Promise<void> {
|
|
|
30
60
|
const turbopackArgs = process.env.UI_SHELL_TURBOPACK ? ["--turbopack"] : []
|
|
31
61
|
const child = spawn(
|
|
32
62
|
process.execPath,
|
|
33
|
-
[NEXT_BIN, "dev",
|
|
63
|
+
[NEXT_BIN, "dev", shellNextApp, ...turbopackArgs, ...portArgs, ...args],
|
|
34
64
|
{ stdio: "inherit", env },
|
|
35
65
|
)
|
|
36
66
|
|
|
37
67
|
child.on("exit", (code) => process.exit(code ?? 0))
|
|
38
68
|
}
|
|
69
|
+
|
|
70
|
+
function overlayUserPublic(userRoot: string, shellNextApp: string): void {
|
|
71
|
+
const userPublic = path.join(userRoot, "public")
|
|
72
|
+
const shellPublic = path.join(shellNextApp, "public")
|
|
73
|
+
if (!fs.existsSync(userPublic)) return
|
|
74
|
+
for (const entry of fs.readdirSync(userPublic)) {
|
|
75
|
+
const src = path.join(userPublic, entry)
|
|
76
|
+
const dest = path.join(shellPublic, entry)
|
|
77
|
+
fs.cpSync(src, dest, { recursive: true, force: true })
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-build generator: writes `public/api/search-index.json` into the
|
|
3
|
+
* user's public tree. Replaces the former `/api/search-index` route
|
|
4
|
+
* handler — the shell's static-export mode can't serve dynamic route
|
|
5
|
+
* handlers, so we bake the search index at build time instead.
|
|
6
|
+
*
|
|
7
|
+
* The generator runs in a child `tsx` process so the shell's own build
|
|
8
|
+
* pipeline (which ships as compiled `.js`) can invoke user-registry-side
|
|
9
|
+
* .ts files (docs loaders, adapters) without the user's `node_modules/`
|
|
10
|
+
* needing to be on our resolution path.
|
|
11
|
+
*/
|
|
12
|
+
import fs from "node:fs"
|
|
13
|
+
import path from "node:path"
|
|
14
|
+
import { createJiti } from "jiti"
|
|
15
|
+
import type { LoadedConfig } from "./shared.js"
|
|
16
|
+
import type { ResolvedShellConfig } from "../config-loader.js"
|
|
17
|
+
|
|
18
|
+
interface SearchItem {
|
|
19
|
+
label: string
|
|
20
|
+
href: string
|
|
21
|
+
group: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Writes the search index JSON into `targetPublicDir/api/search-index.json`.
|
|
26
|
+
* `targetPublicDir` is expected to be the shell's bundled `public/` dir
|
|
27
|
+
* (inside node_modules). The build pipeline overlays the user's public/
|
|
28
|
+
* onto this dir so the merged tree is what `next build` sees.
|
|
29
|
+
*/
|
|
30
|
+
export async function generateSearchIndex(
|
|
31
|
+
loaded: LoadedConfig,
|
|
32
|
+
targetPublicDir: string,
|
|
33
|
+
): Promise<void> {
|
|
34
|
+
const { config } = loaded
|
|
35
|
+
void config
|
|
36
|
+
|
|
37
|
+
// Load the shell's registry-adapter + config-loader via jiti so we can
|
|
38
|
+
// evaluate the same filesystem-walking logic the server used to do at
|
|
39
|
+
// request time.
|
|
40
|
+
const jiti = createJiti(import.meta.url, { interopDefault: true })
|
|
41
|
+
const resolved: ResolvedShellConfig | null = (
|
|
42
|
+
jiti("../config-loader.js") as {
|
|
43
|
+
loadResolvedConfig: () => ResolvedShellConfig | null
|
|
44
|
+
}
|
|
45
|
+
).loadResolvedConfig()
|
|
46
|
+
|
|
47
|
+
if (!resolved) {
|
|
48
|
+
console.warn("[registry-shell] generate-search-index: no resolved config, skipping")
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const { createDefaultAdapter } = jiti("../adapter/default.js") as {
|
|
53
|
+
createDefaultAdapter: (r: ResolvedShellConfig) => {
|
|
54
|
+
getAllComponents: () => { name: string; label: string }[]
|
|
55
|
+
getAllDocs: () => { slug: string; title: string }[]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const adapter = createDefaultAdapter(resolved)
|
|
59
|
+
|
|
60
|
+
const docs: SearchItem[] = adapter.getAllDocs().map((doc) => ({
|
|
61
|
+
label: doc.title,
|
|
62
|
+
href: `/docs/${doc.slug}/`,
|
|
63
|
+
group: "Documentation",
|
|
64
|
+
}))
|
|
65
|
+
|
|
66
|
+
const components: SearchItem[] = adapter.getAllComponents().map((comp) => ({
|
|
67
|
+
label: comp.label,
|
|
68
|
+
href: `/components/${comp.name}/`,
|
|
69
|
+
group: "Components",
|
|
70
|
+
}))
|
|
71
|
+
|
|
72
|
+
const items = [...docs, ...components]
|
|
73
|
+
|
|
74
|
+
const outDir = path.join(targetPublicDir, "api")
|
|
75
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
76
|
+
const outPath = path.join(outDir, "search-index.json")
|
|
77
|
+
fs.writeFileSync(outPath, JSON.stringify(items), "utf-8")
|
|
78
|
+
|
|
79
|
+
console.log(
|
|
80
|
+
`[registry-shell] Wrote search index (${items.length} items) → ${outPath}`,
|
|
81
|
+
)
|
|
82
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* @sntlr/registry-shell CLI entry.
|
|
4
4
|
*
|
|
5
5
|
* Commands:
|
|
6
|
-
* init Scaffold registry-shell.config.ts and add
|
|
7
|
-
* dev Run `next dev`
|
|
8
|
-
* build
|
|
9
|
-
*
|
|
6
|
+
* init Scaffold registry-shell.config.ts and add scripts to package.json.
|
|
7
|
+
* dev Run `next dev` for local iteration on the registry.
|
|
8
|
+
* build Produce a static export in `<user-project>/out/` (Storybook-style).
|
|
9
|
+
*
|
|
10
|
+
* `start` was removed in v2.0.0 — `next build` now produces a static site,
|
|
11
|
+
* so there's no server to "start". Serve `out/` with any static host
|
|
12
|
+
* (`npx serve out`, Vercel, Netlify, S3, etc.).
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
15
|
const USAGE = `Usage: registry-shell <command> [args]
|
|
@@ -14,8 +17,7 @@ const USAGE = `Usage: registry-shell <command> [args]
|
|
|
14
17
|
Commands:
|
|
15
18
|
init Scaffold registry-shell.config.ts in the current project.
|
|
16
19
|
dev Start the shell in dev mode on http://localhost:3000.
|
|
17
|
-
build
|
|
18
|
-
start Start the production server against a prior build.
|
|
20
|
+
build Produce a static export in ./out (deploy anywhere).
|
|
19
21
|
`
|
|
20
22
|
|
|
21
23
|
async function main() {
|
|
@@ -37,8 +39,10 @@ async function main() {
|
|
|
37
39
|
await (await import("./build.js")).run(args)
|
|
38
40
|
break
|
|
39
41
|
case "start":
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
console.error(
|
|
43
|
+
"[registry-shell] `start` was removed in v2.0.0. `build` now produces a static site — serve `./out` with any static host (e.g. `npx serve out`).",
|
|
44
|
+
)
|
|
45
|
+
process.exit(1)
|
|
42
46
|
default:
|
|
43
47
|
console.error(`Unknown command: ${cmd}\n`)
|
|
44
48
|
console.log(USAGE)
|
package/src/config-loader.ts
CHANGED
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
* the user doesn't need a build step or TS tooling of their own — the shell
|
|
4
4
|
* parses TS on the fly.
|
|
5
5
|
*
|
|
6
|
-
* Called
|
|
6
|
+
* Called from two places:
|
|
7
|
+
* • Next.js server startup (src/next-app/registry.config.ts) — runtime.
|
|
8
|
+
* • The shell CLI's build pipeline (src/cli/generate-search-index.ts) —
|
|
9
|
+
* pre-build. The CLI runs in plain Node, not inside Next, so we don't
|
|
10
|
+
* use Next's `server-only` guard here.
|
|
11
|
+
*
|
|
7
12
|
* The returned `ResolvedShellConfig` contains absolute paths and defaults
|
|
8
13
|
* applied.
|
|
9
14
|
*/
|
|
10
|
-
import "server-only"
|
|
11
15
|
import path from "node:path"
|
|
12
16
|
import fs from "node:fs"
|
|
13
17
|
import { createJiti } from "jiti"
|
|
@@ -24,7 +24,7 @@ let fetchPromise: Promise<void> | null = null
|
|
|
24
24
|
|
|
25
25
|
function preloadSearchIndex() {
|
|
26
26
|
if (cachedItems || fetchPromise) return
|
|
27
|
-
fetchPromise = fetch("/api/search-index")
|
|
27
|
+
fetchPromise = fetch("/api/search-index.json")
|
|
28
28
|
.then((r) => r.json())
|
|
29
29
|
.then((data) => { cachedItems = data })
|
|
30
30
|
.catch(() => {})
|
|
@@ -46,15 +46,20 @@ function resolveUserModule(relativePath: string, fallback: string): string {
|
|
|
46
46
|
const USER_PREVIEWS = resolveUserModule("components/previews", "fallback/previews.ts")
|
|
47
47
|
|
|
48
48
|
const nextConfig: NextConfig = {
|
|
49
|
-
|
|
49
|
+
// Static export (Storybook model) — the shell produces a pure static
|
|
50
|
+
// HTML/JS/CSS tree under `out/`, deployable to any static host (Vercel,
|
|
51
|
+
// Netlify, S3, GitHub Pages). No serverless functions, no runtime file
|
|
52
|
+
// tracing, no pnpm symlink hazards. Dev mode (`next dev`) still runs
|
|
53
|
+
// normally; export only affects `next build`.
|
|
54
|
+
output: "export",
|
|
50
55
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
56
|
+
// `next/image` runtime optimization requires a server. Disable for
|
|
57
|
+
// static export — images are served as-is from `public/`.
|
|
58
|
+
images: { unoptimized: true },
|
|
59
|
+
|
|
60
|
+
// URLs end with `/` (e.g. `/components/button/`). Makes static hosts
|
|
61
|
+
// serve `components/button/index.html` correctly.
|
|
62
|
+
trailingSlash: true,
|
|
58
63
|
|
|
59
64
|
// When installed as an external package (link:../registry-shell, npm, etc.),
|
|
60
65
|
// the shell's TSX lives under the user's node_modules and Next won't
|
package/dist/cli/start.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function run(args: string[]): Promise<void>;
|
package/dist/cli/start.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `registry-shell start` — run `next start` for a prior `registry-shell build`.
|
|
3
|
-
*
|
|
4
|
-
* Build writes `.next/` to the user's project root (so Vercel / Netlify /
|
|
5
|
-
* generic Next hosts find it). `next start` expects it at its default
|
|
6
|
-
* location inside the Next project dir. Move it back before starting.
|
|
7
|
-
* Safe to run even if the move has already happened on a prior start.
|
|
8
|
-
*/
|
|
9
|
-
import fs from "node:fs";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { spawn } from "node:child_process";
|
|
12
|
-
import { NEXT_BIN, buildEnvVars, loadUserConfig, nextAppDir } from "./shared.js";
|
|
13
|
-
export async function run(args) {
|
|
14
|
-
const loaded = loadUserConfig();
|
|
15
|
-
const env = { ...process.env, ...buildEnvVars(loaded) };
|
|
16
|
-
const portArgs = loaded?.config.port ? ["-p", String(loaded.config.port)] : [];
|
|
17
|
-
const shellNextApp = nextAppDir();
|
|
18
|
-
if (loaded) {
|
|
19
|
-
restoreBuildOutput(shellNextApp, loaded.root, loaded.config.paths?.buildOutput);
|
|
20
|
-
}
|
|
21
|
-
const child = spawn(process.execPath, [NEXT_BIN, "start", shellNextApp, ...portArgs, ...args], { stdio: "inherit", env });
|
|
22
|
-
child.on("exit", (code) => process.exit(code ?? 0));
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Inverse of build.ts's relocation: moves `<userRoot>/<buildOutput>` back
|
|
26
|
-
* to `<shellNextApp>/.next` so `next start` can find it. Noop when the
|
|
27
|
-
* user-side `.next` is missing — we assume they ran `registry-shell start`
|
|
28
|
-
* against a shell-side build or are otherwise in a good state.
|
|
29
|
-
*/
|
|
30
|
-
function restoreBuildOutput(shellNextApp, userRoot, outputName) {
|
|
31
|
-
const dest = path.join(shellNextApp, ".next");
|
|
32
|
-
const src = path.resolve(userRoot, outputName ?? ".next");
|
|
33
|
-
if (src === dest)
|
|
34
|
-
return;
|
|
35
|
-
if (!fs.existsSync(src))
|
|
36
|
-
return;
|
|
37
|
-
if (fs.existsSync(dest)) {
|
|
38
|
-
fs.rmSync(dest, {
|
|
39
|
-
recursive: true,
|
|
40
|
-
force: true,
|
|
41
|
-
maxRetries: 5,
|
|
42
|
-
retryDelay: 200,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
fs.renameSync(src, dest);
|
|
46
|
-
}
|
|
47
|
-
//# sourceMappingURL=start.js.map
|
package/dist/cli/start.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/cli/start.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhF,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc;IACtC,MAAM,MAAM,GAAG,cAAc,EAAE,CAAA;IAC/B,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,MAAM,CAAC,EAAE,CAAA;IACvD,MAAM,QAAQ,GAAG,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC9E,MAAM,YAAY,GAAG,UAAU,EAAE,CAAA;IAEjC,IAAI,MAAM,EAAE,CAAC;QACX,kBAAkB,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IACjF,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CACjB,OAAO,CAAC,QAAQ,EAChB,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC,EACvD,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAC1B,CAAA;IACD,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAA;AACrD,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB,CACzB,YAAoB,EACpB,QAAgB,EAChB,UAA8B;IAE9B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,UAAU,IAAI,OAAO,CAAC,CAAA;IACzD,IAAI,GAAG,KAAK,IAAI;QAAE,OAAM;IACxB,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAM;IAE/B,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE;YACd,SAAS,EAAE,IAAI;YACf,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,GAAG;SAChB,CAAC,CAAA;IACJ,CAAC;IACD,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;AAC1B,CAAC"}
|
package/src/cli/start.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `registry-shell start` — run `next start` for a prior `registry-shell build`.
|
|
3
|
-
*
|
|
4
|
-
* Build writes `.next/` to the user's project root (so Vercel / Netlify /
|
|
5
|
-
* generic Next hosts find it). `next start` expects it at its default
|
|
6
|
-
* location inside the Next project dir. Move it back before starting.
|
|
7
|
-
* Safe to run even if the move has already happened on a prior start.
|
|
8
|
-
*/
|
|
9
|
-
import fs from "node:fs"
|
|
10
|
-
import path from "node:path"
|
|
11
|
-
import { spawn } from "node:child_process"
|
|
12
|
-
import { NEXT_BIN, buildEnvVars, loadUserConfig, nextAppDir } from "./shared.js"
|
|
13
|
-
|
|
14
|
-
export async function run(args: string[]): Promise<void> {
|
|
15
|
-
const loaded = loadUserConfig()
|
|
16
|
-
const env = { ...process.env, ...buildEnvVars(loaded) }
|
|
17
|
-
const portArgs = loaded?.config.port ? ["-p", String(loaded.config.port)] : []
|
|
18
|
-
const shellNextApp = nextAppDir()
|
|
19
|
-
|
|
20
|
-
if (loaded) {
|
|
21
|
-
restoreBuildOutput(shellNextApp, loaded.root, loaded.config.paths?.buildOutput)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const child = spawn(
|
|
25
|
-
process.execPath,
|
|
26
|
-
[NEXT_BIN, "start", shellNextApp, ...portArgs, ...args],
|
|
27
|
-
{ stdio: "inherit", env },
|
|
28
|
-
)
|
|
29
|
-
child.on("exit", (code) => process.exit(code ?? 0))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Inverse of build.ts's relocation: moves `<userRoot>/<buildOutput>` back
|
|
34
|
-
* to `<shellNextApp>/.next` so `next start` can find it. Noop when the
|
|
35
|
-
* user-side `.next` is missing — we assume they ran `registry-shell start`
|
|
36
|
-
* against a shell-side build or are otherwise in a good state.
|
|
37
|
-
*/
|
|
38
|
-
function restoreBuildOutput(
|
|
39
|
-
shellNextApp: string,
|
|
40
|
-
userRoot: string,
|
|
41
|
-
outputName: string | undefined,
|
|
42
|
-
): void {
|
|
43
|
-
const dest = path.join(shellNextApp, ".next")
|
|
44
|
-
const src = path.resolve(userRoot, outputName ?? ".next")
|
|
45
|
-
if (src === dest) return
|
|
46
|
-
if (!fs.existsSync(src)) return
|
|
47
|
-
|
|
48
|
-
if (fs.existsSync(dest)) {
|
|
49
|
-
fs.rmSync(dest, {
|
|
50
|
-
recursive: true,
|
|
51
|
-
force: true,
|
|
52
|
-
maxRetries: 5,
|
|
53
|
-
retryDelay: 200,
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
fs.renameSync(src, dest)
|
|
57
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Catch-all route that serves arbitrary files from the user registry's
|
|
3
|
-
* `public/` directory. Fires only when no other app route matches, so it
|
|
4
|
-
* doesn't interfere with static routes like `/`, `/docs/...`, `/r/...`,
|
|
5
|
-
* etc. The shell's own `public/` assets are served first by Next.js's
|
|
6
|
-
* built-in static handler; this route handles the user's project's assets
|
|
7
|
-
* since Next only serves the bundled package's `public/`.
|
|
8
|
-
*
|
|
9
|
-
* Examples of what this enables:
|
|
10
|
-
* - `` inside user MDX resolving to
|
|
11
|
-
* `{userRoot}/public/my-image.png`
|
|
12
|
-
* - Custom favicons referenced in `branding.faviconDark` that live under
|
|
13
|
-
* `{userRoot}/public/`
|
|
14
|
-
*/
|
|
15
|
-
import "server-only"
|
|
16
|
-
import fs from "node:fs"
|
|
17
|
-
import path from "node:path"
|
|
18
|
-
import { NextRequest } from "next/server"
|
|
19
|
-
|
|
20
|
-
const MIME: Record<string, string> = {
|
|
21
|
-
".png": "image/png",
|
|
22
|
-
".jpg": "image/jpeg",
|
|
23
|
-
".jpeg": "image/jpeg",
|
|
24
|
-
".gif": "image/gif",
|
|
25
|
-
".webp": "image/webp",
|
|
26
|
-
".avif": "image/avif",
|
|
27
|
-
".svg": "image/svg+xml",
|
|
28
|
-
".ico": "image/x-icon",
|
|
29
|
-
".woff": "font/woff",
|
|
30
|
-
".woff2": "font/woff2",
|
|
31
|
-
".ttf": "font/ttf",
|
|
32
|
-
".otf": "font/otf",
|
|
33
|
-
".json": "application/json",
|
|
34
|
-
".txt": "text/plain; charset=utf-8",
|
|
35
|
-
".xml": "application/xml",
|
|
36
|
-
".pdf": "application/pdf",
|
|
37
|
-
".mp4": "video/mp4",
|
|
38
|
-
".webm": "video/webm",
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function GET(
|
|
42
|
-
_request: NextRequest,
|
|
43
|
-
{ params }: { params: Promise<{ asset: string[] }> },
|
|
44
|
-
) {
|
|
45
|
-
const { asset } = await params
|
|
46
|
-
const userRoot = process.env.USER_REGISTRY_ROOT
|
|
47
|
-
if (!userRoot || asset.length === 0) {
|
|
48
|
-
return new Response("Not found", { status: 404 })
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Reject traversal.
|
|
52
|
-
if (asset.some((seg) => seg.includes("..") || seg.includes("\0"))) {
|
|
53
|
-
return new Response("Forbidden", { status: 403 })
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const publicDir = path.join(userRoot, "public")
|
|
57
|
-
const filePath = path.join(publicDir, ...asset)
|
|
58
|
-
|
|
59
|
-
// Defense-in-depth: make sure the resolved path is still under public/.
|
|
60
|
-
const resolved = path.resolve(filePath)
|
|
61
|
-
if (!resolved.startsWith(path.resolve(publicDir) + path.sep)) {
|
|
62
|
-
return new Response("Forbidden", { status: 403 })
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const stat = await fs.promises.stat(resolved)
|
|
67
|
-
if (!stat.isFile()) return new Response("Not found", { status: 404 })
|
|
68
|
-
const body = await fs.promises.readFile(resolved)
|
|
69
|
-
const type = MIME[path.extname(resolved).toLowerCase()] ?? "application/octet-stream"
|
|
70
|
-
return new Response(new Uint8Array(body), {
|
|
71
|
-
status: 200,
|
|
72
|
-
headers: {
|
|
73
|
-
"Content-Type": type,
|
|
74
|
-
"Content-Length": String(stat.size),
|
|
75
|
-
"Cache-Control": "public, max-age=0, must-revalidate",
|
|
76
|
-
},
|
|
77
|
-
})
|
|
78
|
-
} catch {
|
|
79
|
-
return new Response("Not found", { status: 404 })
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { NextRequest, NextResponse } from "next/server"
|
|
2
|
-
import { registry } from "@shell/registry.config"
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Serves the user registry's `public/a11y/{name}.json` files. The shell's
|
|
6
|
-
* own `public/` doesn't hold these (they live in the user's project), so a
|
|
7
|
-
* route handler proxies the read through the adapter.
|
|
8
|
-
*/
|
|
9
|
-
export async function GET(
|
|
10
|
-
_request: NextRequest,
|
|
11
|
-
{ params }: { params: Promise<{ name: string }> },
|
|
12
|
-
) {
|
|
13
|
-
const { name } = await params
|
|
14
|
-
const data = registry?.getA11yData ? await registry.getA11yData(name) : null
|
|
15
|
-
if (!data) {
|
|
16
|
-
return NextResponse.json({ error: "A11y data not found" }, { status: 404 })
|
|
17
|
-
}
|
|
18
|
-
return NextResponse.json(data)
|
|
19
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { NextResponse } from "next/server"
|
|
2
|
-
import { getAllDocs } from "@shell/lib/docs"
|
|
3
|
-
import { getAllComponents } from "@shell/lib/components-nav"
|
|
4
|
-
|
|
5
|
-
export function GET() {
|
|
6
|
-
const docs = getAllDocs().map((doc) => ({
|
|
7
|
-
label: doc.title,
|
|
8
|
-
href: `/docs/${doc.slug}`,
|
|
9
|
-
group: "Documentation",
|
|
10
|
-
}))
|
|
11
|
-
|
|
12
|
-
const components = getAllComponents().map((comp) => ({
|
|
13
|
-
label: comp.label,
|
|
14
|
-
href: `/components/${comp.name}`,
|
|
15
|
-
group: "Components",
|
|
16
|
-
}))
|
|
17
|
-
|
|
18
|
-
return NextResponse.json([...docs, ...components])
|
|
19
|
-
}
|