sdocs 0.0.32 → 0.0.34
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/CHANGELOG.md +19 -0
- package/dist/explorer/Explorer.svelte +13 -2
- package/dist/explorer/views/PreviewFrame.svelte +7 -2
- package/dist/server/app-gen.js +5 -2
- package/dist/server/snippet-compiler.d.ts +4 -0
- package/dist/server/snippet-compiler.js +24 -8
- package/dist/vite.js +14 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.0.34] - 2026-07-03
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Preview paths are short and project-relative.** Preview URLs and emitted
|
|
15
|
+
file names used to encode the doc's absolute filesystem path, which leaked
|
|
16
|
+
the machine's directory layout into published sites and produced path
|
|
17
|
+
segments long enough for GitHub Pages to reject the deployment. Paths are
|
|
18
|
+
now encoded relative to the project root.
|
|
19
|
+
|
|
20
|
+
## [0.0.33] - 2026-07-03
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Embedded apps can deploy under a sub-path.** Preview URLs pick up the
|
|
25
|
+
host Vite `base` automatically; SvelteKit apps (whose base Vite doesn't
|
|
26
|
+
see) pass it explicitly via the new `previewBase` prop on `Explorer` —
|
|
27
|
+
e.g. `previewBase={base}` with `base` from `$app/paths`.
|
|
28
|
+
|
|
10
29
|
## [0.0.32] - 2026-07-03
|
|
11
30
|
|
|
12
31
|
### Changed
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import PageView from './views/PageView.svelte';
|
|
8
8
|
import LayoutView from './views/LayoutView.svelte';
|
|
9
9
|
import HomePage from './views/HomePage.svelte';
|
|
10
|
-
import { onMount } from 'svelte';
|
|
10
|
+
import { onMount, setContext } from 'svelte';
|
|
11
11
|
import '../ui/styles/sdocs.css';
|
|
12
12
|
|
|
13
13
|
type ThemeMode = 'light' | 'dark';
|
|
@@ -17,13 +17,24 @@
|
|
|
17
17
|
logo?: string;
|
|
18
18
|
icon?: string | false;
|
|
19
19
|
cssNames?: string[];
|
|
20
|
+
/** URL prefix for preview pages when the host app is served under a sub-path (e.g. SvelteKit's base). */
|
|
21
|
+
previewBase?: string;
|
|
20
22
|
sidebarConfig?: {
|
|
21
23
|
order?: Record<string, string[]>;
|
|
22
24
|
open?: string[];
|
|
23
25
|
};
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
let {
|
|
28
|
+
let {
|
|
29
|
+
docs,
|
|
30
|
+
logo = 'sdocs',
|
|
31
|
+
icon = 'sdocs',
|
|
32
|
+
cssNames = [],
|
|
33
|
+
previewBase = '',
|
|
34
|
+
sidebarConfig
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
setContext('sdocs-preview-base', previewBase);
|
|
27
38
|
|
|
28
39
|
let sidebarHidden = $state(false);
|
|
29
40
|
let activeStylesheet = $state<string | undefined>(undefined);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount, untrack } from 'svelte';
|
|
2
|
+
import { getContext, onMount, untrack } from 'svelte';
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
src: string;
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
let { src, props = {}, cssVars = {}, activeStylesheet, fullHeight = false }: Props = $props();
|
|
13
|
+
|
|
14
|
+
// Preview URLs are root-absolute; hosts serving under a sub-path pass the
|
|
15
|
+
// prefix via the Explorer's previewBase prop (see Explorer.svelte).
|
|
16
|
+
const previewBase = (getContext('sdocs-preview-base') as string | undefined) ?? '';
|
|
17
|
+
const resolvedSrc = $derived(previewBase.replace(/\/$/, '') + src);
|
|
13
18
|
let iframe: HTMLIFrameElement;
|
|
14
19
|
let ready = $state(false);
|
|
15
20
|
let contentHeight = $state(0);
|
|
@@ -73,7 +78,7 @@
|
|
|
73
78
|
<div class="sdocs-preview-frame" class:full-height={fullHeight}>
|
|
74
79
|
<iframe
|
|
75
80
|
bind:this={iframe}
|
|
76
|
-
{
|
|
81
|
+
src={resolvedSrc}
|
|
77
82
|
title="Component preview"
|
|
78
83
|
class="sdocs-iframe"
|
|
79
84
|
scrolling="no"
|
package/dist/server/app-gen.js
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, resolve, join } from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { discoverDocFiles, getSdocKind } from './discovery.js';
|
|
7
7
|
import { extractSnippets, hasDefaultSnippet } from './snippet-extractor.js';
|
|
8
|
-
import {
|
|
8
|
+
import { encodeDocPath, setDocPathRoot } from './snippet-compiler.js';
|
|
9
9
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
/** Source Explorer directory in the installed package (dist/explorer, next to dist/server) */
|
|
11
11
|
function getExplorerSourceDir() {
|
|
@@ -174,6 +174,9 @@ export async function generateDevFiles(config, cwd) {
|
|
|
174
174
|
/** Generate the staging directory with entry + preview HTML files for build mode */
|
|
175
175
|
export async function generateBuildFiles(config, cwd) {
|
|
176
176
|
const sdocsDir = await createStagingDir(cwd);
|
|
177
|
+
// The staging dir becomes the Vite root; encode doc paths against it now so
|
|
178
|
+
// these inputs match the URLs the plugin generates later.
|
|
179
|
+
setDocPathRoot(sdocsDir);
|
|
177
180
|
// Copy Explorer components into the staging dir
|
|
178
181
|
await copyExplorerApp(sdocsDir);
|
|
179
182
|
await writeFile(resolve(sdocsDir, 'index.html'), generateIndexHtml(config.logo));
|
|
@@ -184,7 +187,7 @@ export async function generateBuildFiles(config, cwd) {
|
|
|
184
187
|
// Discover snippets and generate preview HTML pages
|
|
185
188
|
const docSnippets = await discoverSnippets(config, cwd);
|
|
186
189
|
for (const { filePath, snippetNames } of docSnippets) {
|
|
187
|
-
const encoded =
|
|
190
|
+
const encoded = encodeDocPath(filePath);
|
|
188
191
|
for (const snippetName of snippetNames) {
|
|
189
192
|
const iframeId = `/@sdocs/iframe/${encoded}/${snippetName}.svelte`;
|
|
190
193
|
const previewDir = resolve(sdocsDir, 'previews', encoded);
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
export declare function base64urlEncode(str: string): string;
|
|
3
3
|
/** Base64url decode */
|
|
4
4
|
export declare function base64urlDecode(str: string): string;
|
|
5
|
+
/** Set the root that doc paths are encoded against (the Vite root / staging dir) */
|
|
6
|
+
export declare function setDocPathRoot(root: string): void;
|
|
7
|
+
/** Encode a doc file path for use in URLs and emitted file names */
|
|
8
|
+
export declare function encodeDocPath(filePath: string): string;
|
|
5
9
|
/** Resolve relative imports to absolute paths for use in virtual components */
|
|
6
10
|
export declare function resolveImportsToAbsolute(imports: string[], docFilePath: string): string[];
|
|
7
11
|
/** Generate a virtual Svelte iframe wrapper component for a snippet.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { dirname, resolve } from 'node:path';
|
|
1
|
+
import { dirname, relative, resolve, sep } from 'node:path';
|
|
2
2
|
/** Base64url encode a string (URL-safe, no padding) */
|
|
3
3
|
export function base64urlEncode(str) {
|
|
4
4
|
return Buffer.from(str).toString('base64url');
|
|
@@ -7,6 +7,22 @@ export function base64urlEncode(str) {
|
|
|
7
7
|
export function base64urlDecode(str) {
|
|
8
8
|
return Buffer.from(str, 'base64url').toString('utf-8');
|
|
9
9
|
}
|
|
10
|
+
// Doc paths are encoded relative to this root. Encoding absolute paths would
|
|
11
|
+
// leak the machine's filesystem layout into published URLs and produce path
|
|
12
|
+
// segments long enough to break static hosts (GitHub Pages rejects them).
|
|
13
|
+
let docPathRoot = process.cwd();
|
|
14
|
+
/** Set the root that doc paths are encoded against (the Vite root / staging dir) */
|
|
15
|
+
export function setDocPathRoot(root) {
|
|
16
|
+
docPathRoot = root;
|
|
17
|
+
}
|
|
18
|
+
/** Encode a doc file path for use in URLs and emitted file names */
|
|
19
|
+
export function encodeDocPath(filePath) {
|
|
20
|
+
return base64urlEncode(relative(docPathRoot, filePath).split(sep).join('/'));
|
|
21
|
+
}
|
|
22
|
+
/** Decode an encoded doc path back to an absolute path */
|
|
23
|
+
function decodeDocPath(encoded) {
|
|
24
|
+
return resolve(docPathRoot, base64urlDecode(encoded));
|
|
25
|
+
}
|
|
10
26
|
/** Resolve relative imports to absolute paths for use in virtual components */
|
|
11
27
|
export function resolveImportsToAbsolute(imports, docFilePath) {
|
|
12
28
|
const docDir = dirname(docFilePath);
|
|
@@ -150,19 +166,19 @@ export function generateStaticPreviewHtml(scriptSrc, cssLinks) {
|
|
|
150
166
|
}
|
|
151
167
|
/** Build the virtual module ID for an iframe wrapper component */
|
|
152
168
|
export function iframeVirtualId(docFilePath, snippetName) {
|
|
153
|
-
return `/@sdocs/iframe/${
|
|
169
|
+
return `/@sdocs/iframe/${encodeDocPath(docFilePath)}/${snippetName}.svelte`;
|
|
154
170
|
}
|
|
155
171
|
/** Build the preview URL for an iframe HTML page (dev mode) */
|
|
156
172
|
export function previewUrl(docFilePath, snippetName) {
|
|
157
|
-
return `/@sdocs/preview/${
|
|
173
|
+
return `/@sdocs/preview/${encodeDocPath(docFilePath)}/${snippetName}`;
|
|
158
174
|
}
|
|
159
175
|
/** Build the preview URL for static build output */
|
|
160
176
|
export function buildPreviewUrl(docFilePath, snippetName) {
|
|
161
|
-
return `/previews/${
|
|
177
|
+
return `/previews/${encodeDocPath(docFilePath)}/${snippetName}.html`;
|
|
162
178
|
}
|
|
163
179
|
/** Virtual module ID for a preview's mount script (embedded production builds) */
|
|
164
180
|
export function mountVirtualId(docFilePath, snippetName) {
|
|
165
|
-
return `/@sdocs/mount/${
|
|
181
|
+
return `/@sdocs/mount/${encodeDocPath(docFilePath)}/${snippetName}.js`;
|
|
166
182
|
}
|
|
167
183
|
/** Parse a mount virtual ID back into its parts */
|
|
168
184
|
export function parseMountId(id) {
|
|
@@ -170,7 +186,7 @@ export function parseMountId(id) {
|
|
|
170
186
|
if (!match)
|
|
171
187
|
return null;
|
|
172
188
|
return {
|
|
173
|
-
docFilePath:
|
|
189
|
+
docFilePath: decodeDocPath(match[1]),
|
|
174
190
|
snippetName: match[2],
|
|
175
191
|
};
|
|
176
192
|
}
|
|
@@ -180,7 +196,7 @@ export function parseIframeId(id) {
|
|
|
180
196
|
if (!match)
|
|
181
197
|
return null;
|
|
182
198
|
return {
|
|
183
|
-
docFilePath:
|
|
199
|
+
docFilePath: decodeDocPath(match[1]),
|
|
184
200
|
snippetName: match[2],
|
|
185
201
|
};
|
|
186
202
|
}
|
|
@@ -190,7 +206,7 @@ export function parsePreviewUrl(url) {
|
|
|
190
206
|
if (!match)
|
|
191
207
|
return null;
|
|
192
208
|
return {
|
|
193
|
-
docFilePath:
|
|
209
|
+
docFilePath: decodeDocPath(match[1]),
|
|
194
210
|
snippetName: match[2],
|
|
195
211
|
};
|
|
196
212
|
}
|
package/dist/vite.js
CHANGED
|
@@ -6,7 +6,7 @@ import { parseComponent } from './server/prop-parser.js';
|
|
|
6
6
|
import { extractSnippets, extractMarkupBody, hasDefaultSnippet, generateAutoDefault, } from './server/snippet-extractor.js';
|
|
7
7
|
import { highlight, disposeHighlighter } from './server/highlighter.js';
|
|
8
8
|
import { extractTocFromHtml } from './server/toc-extractor.js';
|
|
9
|
-
import { parseIframeId, parseMountId, parsePreviewUrl, resolveImportsToAbsolute, generateIframeComponent, generateMountScript, generatePreviewHtml, generateStaticPreviewHtml, iframeVirtualId, mountVirtualId, previewUrl, buildPreviewUrl,
|
|
9
|
+
import { parseIframeId, parseMountId, parsePreviewUrl, resolveImportsToAbsolute, generateIframeComponent, generateMountScript, generatePreviewHtml, generateStaticPreviewHtml, iframeVirtualId, mountVirtualId, previewUrl, buildPreviewUrl, encodeDocPath, setDocPathRoot, } from './server/snippet-compiler.js';
|
|
10
10
|
const VIRTUAL_MODULE_ID = 'virtual:sdocs';
|
|
11
11
|
const RESOLVED_VIRTUAL_ID = '\0virtual:sdocs';
|
|
12
12
|
const IFRAME_PREFIX = '/@sdocs/iframe/';
|
|
@@ -21,6 +21,9 @@ export function sdocsPlugin(userConfig) {
|
|
|
21
21
|
const buildMode = userConfig?._buildMode ?? false;
|
|
22
22
|
let isBuild = false;
|
|
23
23
|
let isSsrBuild = false;
|
|
24
|
+
// Host app's base path ('/' when served at the domain root); preview URLs
|
|
25
|
+
// are root-absolute and must carry it so embedding works under a sub-path.
|
|
26
|
+
let base = '/';
|
|
24
27
|
// Previews planned for emission into a host app's build (embedded mode)
|
|
25
28
|
let plannedPreviews = [];
|
|
26
29
|
let emittedCssLinks = [];
|
|
@@ -28,8 +31,10 @@ export function sdocsPlugin(userConfig) {
|
|
|
28
31
|
name: 'sdocs',
|
|
29
32
|
async configResolved(resolvedConfig) {
|
|
30
33
|
root = resolvedConfig.root;
|
|
34
|
+
setDocPathRoot(root);
|
|
31
35
|
isBuild = resolvedConfig.command === 'build';
|
|
32
36
|
isSsrBuild = !!resolvedConfig.build?.ssr;
|
|
37
|
+
base = resolvedConfig.base || '/';
|
|
33
38
|
const fileConfig = await loadRawConfig(root);
|
|
34
39
|
const merged = { ...fileConfig, ...userConfig };
|
|
35
40
|
config = resolveAndFinalize(merged, root);
|
|
@@ -154,7 +159,7 @@ export function sdocsPlugin(userConfig) {
|
|
|
154
159
|
}
|
|
155
160
|
}
|
|
156
161
|
for (const entry of docEntries.values()) {
|
|
157
|
-
const encoded =
|
|
162
|
+
const encoded = encodeDocPath(entry.filePath);
|
|
158
163
|
for (const snippet of entry.snippets) {
|
|
159
164
|
const jsFileName = `previews/${encoded}/${snippet.name}.js`;
|
|
160
165
|
this.emitFile({
|
|
@@ -320,9 +325,13 @@ export function sdocsPlugin(userConfig) {
|
|
|
320
325
|
name: s.name,
|
|
321
326
|
body: s.body,
|
|
322
327
|
highlightedHtml: s.highlightedHtml,
|
|
323
|
-
previewUrl:
|
|
324
|
-
|
|
325
|
-
|
|
328
|
+
previewUrl:
|
|
329
|
+
// Only absolute bases prefix here; SvelteKit builds with a relative
|
|
330
|
+
// base ('./') and passes its real path via the Explorer's previewBase.
|
|
331
|
+
(base.startsWith('/') && base !== '/' ? base.replace(/\/$/, '') : '') +
|
|
332
|
+
(buildMode || isBuild
|
|
333
|
+
? buildPreviewUrl(e.filePath, s.name)
|
|
334
|
+
: previewUrl(e.filePath, s.name)),
|
|
326
335
|
})),
|
|
327
336
|
highlightedSource: e.highlightedSource,
|
|
328
337
|
toc: e.toc,
|