@brett_lamy/docstream 1.1.1 → 1.2.1

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.
@@ -1,4 +1,4 @@
1
- import type { Block, DocumentNode, Inline, ListItemNode } from "./ast"
1
+ import type { Block, DemoInlineFile, DemoNode, DocumentNode, Inline, ListItemNode } from "./ast"
2
2
  import { serializeInline, serializeReference } from "./inline"
3
3
 
4
4
  export function serializeMarkdown(doc: DocumentNode): string {
@@ -49,10 +49,26 @@ function serializeBlock(b: Block): string {
49
49
  case "hint":
50
50
  return `{% hint style="${b.style}" %}\n${serializeBlocks(b.children)}\n{% endhint %}`
51
51
 
52
- case "tabs":
53
- return `{% tabs${b.sync ? ` sync="${b.sync}"` : ""} %}\n${b.tabs
52
+ case "tabs": {
53
+ const attrs = [
54
+ b.title ? ` title="${b.title}"` : "",
55
+ b.title && b.level && b.level !== 2 ? ` level="${b.level}"` : "",
56
+ b.sync ? ` sync="${b.sync}"` : "",
57
+ ].join("")
58
+ return `{% tabs${attrs} %}\n${b.tabs
54
59
  .map((t) => `{% tab title="${t.title}" %}\n${serializeBlocks(t.children)}\n{% endtab %}`)
55
60
  .join("\n\n")}\n{% endtabs %}`
61
+ }
62
+
63
+ case "command": {
64
+ const attrs = [
65
+ ...(["pnpm", "yarn", "bun"] as const).map((pm) => (b.overrides?.[pm] ? ` ${pm}="${b.overrides[pm]}"` : "")),
66
+ b.sync && b.sync !== "pm" ? ` sync="${b.sync}"` : "",
67
+ ].join("")
68
+ return b.command.includes("\n")
69
+ ? `{% command${attrs} %}\n${b.command}\n{% endcommand %}`
70
+ : `{% command${attrs} %}${b.command}{% endcommand %}`
71
+ }
56
72
 
57
73
  case "expandable":
58
74
  return `<details>\n\n<summary>${b.summary}</summary>\n\n${serializeBlocks(b.children)}\n\n</details>`
@@ -103,8 +119,9 @@ function serializeBlock(b: Block): string {
103
119
  ? ` variants="${b.variants.map((v) => (v.label === v.id ? v.id : `${v.id}:${v.label}`)).join(",")}"`
104
120
  : "",
105
121
  b.viewport ? ` viewport="${b.viewport}"` : "",
122
+ b.entry ? ` entry="${b.entry}"` : "",
106
123
  ].join("")
107
- return `{% demo${attrs} %}`
124
+ return `{% demo${attrs} %}${serializeDemoFiles(b)}`
108
125
  }
109
126
 
110
127
  case "columns":
@@ -197,3 +214,22 @@ function serializeListItem(item: ListItemNode, ordered: boolean, task: boolean,
197
214
  const restText = rest.length ? "\n" + indent(serializeBlocks(rest), " ") : ""
198
215
  return `${bullet} ${check}${firstText}${restText}`
199
216
  }
217
+
218
+ /** A fence longer than any backtick run in the content, so nested fences survive. */
219
+ export function fenceFor(content: string): string {
220
+ let longest = 2
221
+ for (const m of content.matchAll(/`+/g)) longest = Math.max(longest, m[0].length)
222
+ return "`".repeat(longest + 1)
223
+ }
224
+
225
+ /** One inline demo file as a titled fence. */
226
+ export function serializeDemoFile(file: DemoInlineFile): string {
227
+ const fence = fenceFor(file.content)
228
+ return `${fence}${file.language ?? ""} title="${file.path}"\n${file.content}\n${fence}`
229
+ }
230
+
231
+ /** The body of a block-form `{% demo %}`: files as titled fences, then `{% enddemo %}`. Empty without files. */
232
+ function serializeDemoFiles(node: DemoNode): string {
233
+ if (!node.files?.length) return ""
234
+ return `\n${node.files.map(serializeDemoFile).join("\n")}\n{% enddemo %}`
235
+ }
package/src/index.ts CHANGED
@@ -13,25 +13,35 @@ export {
13
13
  DemoFullscreen,
14
14
  DemoViewer,
15
15
  DocstreamDemoContext,
16
+ DocstreamDemoRuntimeContext,
16
17
  createGlobDemoResolver,
17
18
  defaultDemoMarkdown,
19
+ inlineDemoMarkdown,
20
+ inlineDemoNode,
18
21
  demoComponentFrom,
19
22
  demoFromSearch,
20
23
  demoHref,
24
+ preloadDemos,
21
25
  resolveDemosToMarkdown,
22
26
  sortDemoFiles,
27
+ toInlineFiles,
23
28
  useDemoResolver,
29
+ useDemoRuntime,
24
30
  } from "./demo"
25
31
  export type {
26
32
  DemoComponent,
27
33
  DemoComponentProps,
28
34
  DemoFile,
29
35
  DemoFullscreenProps,
36
+ DemoMarkdownFormat,
30
37
  DemoMeta,
31
38
  DemoResolver,
39
+ DemoRuntimeOptions,
32
40
  DemoVariant,
33
41
  DemoViewerProps,
34
42
  GlobDemoResolverOptions,
43
+ InlineDemoRuntime,
44
+ InlineDemoRuntimeProps,
35
45
  ResolveDemosOptions,
36
46
  ResolvedDemo,
37
47
  } from "./demo"
@@ -52,8 +62,12 @@ export { VizEmbed } from "./viz"
52
62
  export type { VizEmbedProps } from "./viz"
53
63
  export { OpenApiOperation } from "./openapi/OpenApiOperation"
54
64
  export {
65
+ InlineDemoPreview,
55
66
  ReactCodePreview,
56
67
  ReactDemo,
68
+ almostNodeDemoRuntime,
69
+ createAlmostNodeDemoRuntime,
70
+ createInlineDemoProject,
57
71
  createReactDemoFiles,
58
72
  createAlmostNodeFilesystem,
59
73
  createAlmostNodeWorkspace,
@@ -65,6 +79,10 @@ export type {
65
79
  AlmostNodeFilesystemOptions,
66
80
  AlmostNodeWorkspace,
67
81
  AlmostNodeWorkspaceOptions,
82
+ AlmostNodeDemoRuntimeOptions,
83
+ InlineDemoPreviewProps,
84
+ InlineDemoProject,
85
+ InlineDemoProjectOptions,
68
86
  ReactCodePreviewProps,
69
87
  ReactDemoProps,
70
88
  } from "./playground"
@@ -79,7 +97,7 @@ export type {
79
97
  SourceReferenceClient,
80
98
  } from "./source"
81
99
  export { parseDemoVariants, parseMarkdown, parseBlocks, trimPartialInlineToken } from "./gitbook/parse"
82
- export { serializeBlocks, serializeMarkdown } from "./gitbook/serialize"
100
+ export { fenceFor, serializeBlocks, serializeDemoFile, serializeMarkdown } from "./gitbook/serialize"
83
101
  export {
84
102
  footnoteDefinitions,
85
103
  parseInline,
@@ -91,3 +109,7 @@ export {
91
109
  export { Streamdown } from "streamdown"
92
110
  export type { StreamdownProps } from "streamdown"
93
111
  export type * from "./gitbook/ast"
112
+ export { PACKAGE_MANAGERS, packageManagerCommands } from "./gitbook/package-managers"
113
+ export { flattenBlocks, flattenForPlainMarkdown } from "./gitbook/flatten"
114
+ export { documentOutline, slugify } from "./gitbook/outline"
115
+ export type { OutlineEntry } from "./gitbook/outline"
@@ -0,0 +1,77 @@
1
+ /* Runs a `{% demo %}` block's inline files in almost-node — the Preview of a pasted,
2
+ self-contained demo when no resolver knows it. Just the iframe: the DemoViewer around it
3
+ supplies the header, variants, viewports, Code view and fullscreen. */
4
+ import { useMemo, type ReactNode } from "react"
5
+
6
+ import type { DemoFile, InlineDemoRuntime, InlineDemoRuntimeProps } from "../demo/types"
7
+ import { createInlineDemoProject, type AlmostNodeWorkspaceOptions } from "./filesystem"
8
+ import { useAlmostNodeServer } from "./ReactCodePreview"
9
+
10
+ export interface InlineDemoPreviewProps extends InlineDemoRuntimeProps {
11
+ /** Preferred virtual port. almost-node picks the next free one if taken. */
12
+ port?: number
13
+ /** iframe sandbox value (same-origin lets the almost-node service worker route it). */
14
+ sandbox?: string
15
+ /** Extra options for `createAlmostNodeWorkspace` (e.g. `basePath` for static hosting). */
16
+ workspaceOptions?: Partial<AlmostNodeWorkspaceOptions>
17
+ }
18
+
19
+ function filesKey(files: DemoFile[], entry: string, dependencies: Record<string, string> | undefined) {
20
+ return JSON.stringify([entry, dependencies ?? null, files.map((f) => [f.path, f.content])])
21
+ }
22
+
23
+ export function InlineDemoPreview({
24
+ title,
25
+ files,
26
+ entry,
27
+ variant,
28
+ dependencies,
29
+ port = 4173,
30
+ sandbox = "allow-scripts allow-same-origin allow-forms allow-modals",
31
+ workspaceOptions,
32
+ }: InlineDemoPreviewProps) {
33
+ // Content-keyed so re-renders (variant switches, parent state) never restart Vite.
34
+ const key = filesKey(files, entry, dependencies)
35
+ const project = useMemo(
36
+ () => {
37
+ try {
38
+ return { value: createInlineDemoProject(files, { entry, dependencies }) }
39
+ } catch (error) {
40
+ return { error: error instanceof Error ? error.message : String(error) }
41
+ }
42
+ },
43
+ // eslint-disable-next-line react-hooks/exhaustive-deps
44
+ [key],
45
+ )
46
+ const { state, url, error } = useAlmostNodeServer({
47
+ files: project.value?.files ?? {},
48
+ port,
49
+ run: project.value ? 1 : 0,
50
+ workspaceOptions,
51
+ })
52
+
53
+ const message = project.error ?? error
54
+ if (message) return <pre className="docs-demo-error" role="alert">{message}</pre>
55
+ if (state !== "ready" || !url) {
56
+ return <div className="docs-demo-runtime-status" role="status">Starting almost-node…</div>
57
+ }
58
+ const src = project.value?.wrapped && variant !== undefined ? `${url}?variant=${encodeURIComponent(variant)}` : url
59
+ return <iframe key={src} title={title} className="docs-demo-runtime-frame" src={src} sandbox={sandbox} />
60
+ }
61
+
62
+ export interface AlmostNodeDemoRuntimeOptions {
63
+ port?: number
64
+ sandbox?: string
65
+ workspaceOptions?: Partial<AlmostNodeWorkspaceOptions>
66
+ }
67
+
68
+ /**
69
+ * An `InlineDemoRuntime` backed by almost-node (`@agent-wasm/core`, loaded lazily on first
70
+ * use). Pass it as `demoRuntime`; `PlaygroundStreamdown` uses one with default options.
71
+ */
72
+ export function createAlmostNodeDemoRuntime(options: AlmostNodeDemoRuntimeOptions = {}): InlineDemoRuntime {
73
+ return (props): ReactNode => <InlineDemoPreview {...props} {...options} />
74
+ }
75
+
76
+ /** The default almost-node runtime (no base path, port 4173+). */
77
+ export const almostNodeDemoRuntime: InlineDemoRuntime = createAlmostNodeDemoRuntime()
@@ -1,7 +1,9 @@
1
- import { useMemo } from "react"
1
+ import { useContext, useMemo } from "react"
2
2
 
3
3
  import { DocsRenderer, type DocRenderOptions, type LivePreviewRenderer, type SourceReferenceRenderer } from "../docs/DocsRenderer"
4
4
  import { parseMarkdown, trimPartialInlineToken } from "../gitbook/parse"
5
+ import { DocstreamDemoRuntimeContext } from "../demo/context"
6
+ import { almostNodeDemoRuntime } from "./InlineDemoPreview"
5
7
  import { ReactCodePreview } from "./ReactCodePreview"
6
8
 
7
9
  export interface PlaygroundStreamdownProps extends DocRenderOptions {
@@ -16,7 +18,8 @@ export interface PlaygroundStreamdownProps extends DocRenderOptions {
16
18
  /**
17
19
  * The full renderer variant. It keeps the optional almost-node playground
18
20
  * behind an explicit import boundary; markdown-only consumers should use the
19
- * `@brett_lamy/docstream/streamdown` entry instead.
21
+ * `@brett_lamy/docstream/streamdown` entry instead. Live code fences and
22
+ * `{% demo %}` blocks with inline files (and no resolver) run in almost-node.
20
23
  */
21
24
  export function PlaygroundStreamdown({
22
25
  children,
@@ -26,9 +29,14 @@ export function PlaygroundStreamdown({
26
29
  markdown,
27
30
  sourceRenderer,
28
31
  demoResolver,
32
+ demoRuntime,
33
+ demoDependencies,
29
34
  pageActions,
30
35
  }: PlaygroundStreamdownProps) {
31
36
  const content = markdown ?? children ?? ""
37
+ // Inline-file demos run in almost-node unless the host (or an outer renderer) chose a runtime.
38
+ const inheritedRuntime = useContext(DocstreamDemoRuntimeContext).runtime
39
+ const runtime = demoRuntime ?? inheritedRuntime ?? almostNodeDemoRuntime
32
40
  const doc = useMemo(
33
41
  () => parseMarkdown(isStreaming ? trimPartialInlineToken(content) : content),
34
42
  [content, isStreaming]
@@ -56,6 +64,8 @@ export function PlaygroundStreamdown({
56
64
  liveRenderer={liveRenderer}
57
65
  sourceRenderer={sourceRenderer}
58
66
  demoResolver={demoResolver}
67
+ demoRuntime={runtime}
68
+ demoDependencies={demoDependencies}
59
69
  pageActions={isStreaming ? false : pageActions}
60
70
  />
61
71
  </div>
@@ -53,7 +53,7 @@ export interface ReactDemoProps {
53
53
 
54
54
  export type ReactCodePreviewProps = ReactDemoProps
55
55
 
56
- type PreviewState = "idle" | "starting" | "ready" | "error"
56
+ export type PreviewState = "idle" | "starting" | "ready" | "error"
57
57
 
58
58
  const DETACHED_SERVER_ENV = { ALMOSTNODE_DETACH_DEV_SERVERS: "1" }
59
59
 
@@ -79,43 +79,30 @@ function sourceText(files: AlmostNodeFiles, requestedPath: string): string | nul
79
79
  return typeof file.content === "string" ? file.content : new TextDecoder().decode(file.content)
80
80
  }
81
81
 
82
- /** Render a multi-file React/JSX/TSX project through almost-node's Vite server. */
83
- const NO_FILES: AlmostNodeFiles = {}
82
+ export interface AlmostNodeServerOptions {
83
+ /** The complete project (see `createReactDemoFiles`). Keep its identity stable between renders. */
84
+ files: AlmostNodeFiles
85
+ port?: number
86
+ /** Start when > 0; bump to restart. 0 stops (and disposes) the server. */
87
+ run: number
88
+ workspaceOptions?: Partial<AlmostNodeWorkspaceOptions>
89
+ onReady?: (url: string) => void
90
+ onError?: (error: Error) => void
91
+ }
84
92
 
85
- export function ReactDemo({
86
- files = NO_FILES,
87
- preview,
88
- code: codeProp,
89
- language,
90
- status,
91
- actions,
92
- entry = "/src/main.jsx",
93
- port = 4173,
94
- height = 360,
95
- codeFile,
96
- collapsedCodeLines = 3,
97
- expandedCodeLines = 30,
98
- title = "Live React preview",
99
- autoStart = true,
100
- sandbox = "allow-scripts allow-same-origin allow-forms allow-modals",
101
- workspaceOptions,
102
- className,
103
- onReady,
104
- onError,
105
- }: ReactDemoProps) {
106
- const inline = preview !== undefined
107
- const [run, setRun] = useState(autoStart && !inline ? 1 : 0)
108
- const [state, setState] = useState<PreviewState>(inline ? "ready" : autoStart ? "starting" : "idle")
93
+ export interface AlmostNodeServerState {
94
+ state: PreviewState
95
+ url: string | null
96
+ error: string | null
97
+ }
98
+
99
+ /** Boots `vite` for `files` in an almost-node container; disposes it on change/unmount. */
100
+ export function useAlmostNodeServer({ files, port = 4173, run, workspaceOptions, onReady, onError }: AlmostNodeServerOptions): AlmostNodeServerState {
101
+ const [state, setState] = useState<PreviewState>(run ? "starting" : "idle")
109
102
  const [url, setUrl] = useState<string | null>(null)
110
103
  const [error, setError] = useState<string | null>(null)
111
- const displayedCodeFile = codeFile ?? entry
112
- const code = useMemo(
113
- () => codeProp ?? sourceText(files, displayedCodeFile),
114
- [codeProp, displayedCodeFile, files],
115
- )
116
104
 
117
105
  useEffect(() => {
118
- if (inline) return
119
106
  if (!run) {
120
107
  setState("idle")
121
108
  setUrl(null)
@@ -132,8 +119,7 @@ export function ReactDemo({
132
119
 
133
120
  const start = async () => {
134
121
  try {
135
- const projectFiles = createReactDemoFiles(files, { entry })
136
- workspace = await createAlmostNodeWorkspace(projectFiles, {
122
+ workspace = await createAlmostNodeWorkspace(files, {
137
123
  ...workspaceOptions,
138
124
  env: { ...DETACHED_SERVER_ENV, ...workspaceOptions?.env },
139
125
  onServerReady: (serverPort, serverUrl) => {
@@ -173,7 +159,52 @@ export function ReactDemo({
173
159
  cancelled = true
174
160
  workspace?.dispose()
175
161
  }
176
- }, [entry, files, inline, onError, onReady, port, run, workspaceOptions])
162
+ }, [files, onError, onReady, port, run, workspaceOptions])
163
+
164
+ return { state, url, error }
165
+ }
166
+
167
+ /** Render a multi-file React/JSX/TSX project through almost-node's Vite server. */
168
+ const NO_FILES: AlmostNodeFiles = {}
169
+
170
+ export function ReactDemo({
171
+ files = NO_FILES,
172
+ preview,
173
+ code: codeProp,
174
+ language,
175
+ status,
176
+ actions,
177
+ entry = "/src/main.jsx",
178
+ port = 4173,
179
+ height = 360,
180
+ codeFile,
181
+ collapsedCodeLines = 3,
182
+ expandedCodeLines = 30,
183
+ title = "Live React preview",
184
+ autoStart = true,
185
+ sandbox = "allow-scripts allow-same-origin allow-forms allow-modals",
186
+ workspaceOptions,
187
+ className,
188
+ onReady,
189
+ onError,
190
+ }: ReactDemoProps) {
191
+ const inline = preview !== undefined
192
+ const [run, setRun] = useState(autoStart && !inline ? 1 : 0)
193
+ const projectFiles = useMemo(() => createReactDemoFiles(files, { entry }), [files, entry])
194
+ const { state: serverState, url, error } = useAlmostNodeServer({
195
+ files: projectFiles,
196
+ port,
197
+ run: inline ? 0 : run,
198
+ workspaceOptions,
199
+ onReady,
200
+ onError,
201
+ })
202
+ const state: PreviewState = inline ? "ready" : serverState
203
+ const displayedCodeFile = codeFile ?? entry
204
+ const code = useMemo(
205
+ () => codeProp ?? sourceText(files, displayedCodeFile),
206
+ [codeProp, displayedCodeFile, files],
207
+ )
177
208
 
178
209
  const wrapperClass = className ? `docs-react-demo ${className}` : "docs-react-demo"
179
210
  const running = state === "starting"
@@ -154,6 +154,8 @@ export async function createAlmostNodeWorkspace(
154
154
 
155
155
  export interface ReactDemoFilesOptions {
156
156
  entry?: string
157
+ /** Extra npm dependencies for the generated package.json (ignored when the files bring one). */
158
+ dependencies?: Record<string, string>
157
159
  }
158
160
 
159
161
  /**
@@ -174,7 +176,7 @@ export function createReactDemoFiles(
174
176
  name: "docstream-react-demo",
175
177
  private: true,
176
178
  type: "module",
177
- dependencies: { react: "^19.0.0", "react-dom": "^19.0.0" },
179
+ dependencies: { react: "^19.0.0", "react-dom": "^19.0.0", ...options.dependencies },
178
180
  },
179
181
  null,
180
182
  2,
@@ -200,3 +202,83 @@ export function createReactDemoFiles(
200
202
 
201
203
  return map
202
204
  }
205
+
206
+ export interface InlineDemoProjectOptions {
207
+ /** Entry file among `files` (relative path). Defaults to the first file. */
208
+ entry?: string
209
+ /** Extra npm dependencies (merged over a `package.json` among the files, if any). */
210
+ dependencies?: Record<string, string>
211
+ }
212
+
213
+ export interface InlineDemoProject {
214
+ /** Complete almost-node project: the demo under `/src/demo/`, a wrapper entry, package.json, index.html. */
215
+ files: Record<string, AlmostNodeFileContent>
216
+ /** The Vite entry to hand to `createReactDemoFiles` / `ReactDemo`. */
217
+ entry: string
218
+ /** Whether the entry is wrapped (so `?variant=` reaches the component as its `variant` prop). */
219
+ wrapped: boolean
220
+ }
221
+
222
+ /** Folder the inline demo's files are written to inside the almost-node project. */
223
+ export const INLINE_DEMO_DIR = "/src/demo"
224
+ const WRAPPER_ENTRY = "/src/__docstream_demo_entry.js"
225
+
226
+ function selfMounting(source: string): boolean {
227
+ return /\bcreateRoot\s*\(|\bReactDOM\.render\s*\(|\bhydrateRoot\s*\(/.test(source)
228
+ }
229
+
230
+ /**
231
+ * Turn a `{% demo %}` block's inline files into a runnable almost-node project.
232
+ *
233
+ * The files go under `/src/demo/` (relative imports between them keep working). A tiny
234
+ * wrapper entry imports the entry file, takes its default export (or the first exported
235
+ * function), and renders it with `variant` read from the page's `?variant=` query — so
236
+ * switching variants just reloads the iframe, no new Vite server. An entry that mounts
237
+ * itself (`createRoot(…)`) is used directly instead, and receives no `variant`.
238
+ *
239
+ * A `package.json` among the files contributes its `dependencies`; `options.dependencies`
240
+ * are merged over them. Bare imports resolve through almost-node (installed packages, else
241
+ * esm.sh at the major version declared here), with `react`/`react-dom` provided.
242
+ */
243
+ export function createInlineDemoProject(
244
+ files: readonly { path: string; content: string }[],
245
+ options: InlineDemoProjectOptions = {},
246
+ ): InlineDemoProject {
247
+ const map: Record<string, AlmostNodeFileContent> = {}
248
+ let packageDeps: Record<string, string> = {}
249
+ for (const file of files) {
250
+ const relative = file.path.replace(/^\.?\/+/, "")
251
+ if (relative === "package.json") {
252
+ try {
253
+ const pkg = JSON.parse(file.content) as { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }
254
+ packageDeps = { ...pkg.devDependencies, ...pkg.dependencies }
255
+ } catch {
256
+ // Not JSON: keep it as a plain file.
257
+ }
258
+ }
259
+ map[normalizeAlmostNodePath(relative, INLINE_DEMO_DIR)] = file.content
260
+ }
261
+ const entryFile = files.find((f) => f.path === options.entry) ?? files[0]
262
+ if (!entryFile) throw new Error("An inline demo needs at least one file")
263
+ const entryPath = normalizeAlmostNodePath(entryFile.path.replace(/^\.?\/+/, ""), INLINE_DEMO_DIR)
264
+ const dependencies = { ...packageDeps, ...options.dependencies }
265
+ const wrapped = !selfMounting(entryFile.content)
266
+ if (wrapped) {
267
+ const specifier = `.${entryPath.slice("/src".length)}`
268
+ map[WRAPPER_ENTRY] = `import { createElement } from "react"
269
+ import { createRoot } from "react-dom/client"
270
+ import * as demo from ${JSON.stringify(specifier)}
271
+
272
+ const Component = demo.default ?? Object.values(demo).find((value) => typeof value === "function")
273
+ const variant = new URLSearchParams(location.search).get("variant")
274
+ const props = variant === null ? {} : { variant }
275
+ createRoot(document.getElementById("root")).render(
276
+ Component
277
+ ? createElement(Component, props)
278
+ : createElement("pre", null, ${JSON.stringify(`${entryFile.path} has no default export to render.`)}),
279
+ )
280
+ `
281
+ }
282
+ const entry = wrapped ? WRAPPER_ENTRY : entryPath
283
+ return { files: createReactDemoFiles(map, { entry, dependencies }), entry, wrapped }
284
+ }
@@ -1,8 +1,11 @@
1
- export { ReactCodePreview, ReactDemo } from "./ReactCodePreview"
2
- export type { ReactCodePreviewProps, ReactDemoProps } from "./ReactCodePreview"
1
+ export { ReactCodePreview, ReactDemo, useAlmostNodeServer } from "./ReactCodePreview"
2
+ export type { AlmostNodeServerOptions, AlmostNodeServerState, PreviewState, ReactCodePreviewProps, ReactDemoProps } from "./ReactCodePreview"
3
+ export { InlineDemoPreview, almostNodeDemoRuntime, createAlmostNodeDemoRuntime } from "./InlineDemoPreview"
4
+ export type { AlmostNodeDemoRuntimeOptions, InlineDemoPreviewProps } from "./InlineDemoPreview"
3
5
  export {
4
6
  createAlmostNodeFilesystem,
5
7
  createAlmostNodeWorkspace,
8
+ createInlineDemoProject,
6
9
  createReactDemoFiles,
7
10
  normalizeAlmostNodeFiles,
8
11
  normalizeAlmostNodePath,
@@ -15,5 +18,7 @@ export type {
15
18
  AlmostNodeFilesystemOptions,
16
19
  AlmostNodeWorkspace,
17
20
  AlmostNodeWorkspaceOptions,
21
+ InlineDemoProject,
22
+ InlineDemoProjectOptions,
18
23
  ReactDemoFilesOptions,
19
24
  } from "./filesystem"
@@ -8,8 +8,8 @@ export { CitationSources, DocstreamRefContext, InlineReference } from "./docs/re
8
8
  export { DocPageActions } from "./docs/PageActions"
9
9
  export type { DocPageAction, DocPageActionsOptions, DocPageActionsProps } from "./docs/PageActions"
10
10
  export type { DocRenderOptions } from "./docs/DocsRenderer"
11
- export { DocstreamDemoContext, DemoFullscreen, DemoViewer } from "./demo"
12
- export type { DemoFile, DemoMeta, DemoResolver, DemoVariant } from "./demo"
11
+ export { DocstreamDemoContext, DocstreamDemoRuntimeContext, DemoFullscreen, DemoViewer } from "./demo"
12
+ export type { DemoFile, DemoMeta, DemoResolver, DemoVariant, InlineDemoRuntime, InlineDemoRuntimeProps } from "./demo"
13
13
  export type { CitationPreview, CitationPreviewResolver, DocstreamRefContextValue } from "./docs/reference"
14
14
 
15
15
  export interface GitbookStreamdownProps extends DocRenderOptions {
@@ -31,6 +31,8 @@ export function GitbookStreamdown({
31
31
  liveRenderer,
32
32
  sourceRenderer,
33
33
  demoResolver,
34
+ demoRuntime,
35
+ demoDependencies,
34
36
  pageActions,
35
37
  }: GitbookStreamdownProps) {
36
38
  const content = markdown ?? children ?? ""
@@ -52,6 +54,8 @@ export function GitbookStreamdown({
52
54
  liveRenderer={liveRenderer}
53
55
  sourceRenderer={sourceRenderer}
54
56
  demoResolver={demoResolver}
57
+ demoRuntime={demoRuntime}
58
+ demoDependencies={demoDependencies}
55
59
  pageActions={isStreaming ? false : pageActions}
56
60
  />
57
61
  </div>