sdocs 0.0.26 → 0.0.28

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 CHANGED
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.0.28] - 2026-07-03
11
+
12
+ ### Changed
13
+
14
+ - **`sdocs dev`/`run`/`build` no longer write a `.sdocs/` directory into the
15
+ project.** The generated app is staged in a unique directory under
16
+ `node_modules/.cache/` (imports resolve exactly as before) and removed on
17
+ exit. This also fixes `sdocs build` breaking a concurrently running dev
18
+ server — they previously shared the same staging directory.
19
+
20
+ ## [0.0.27] - 2026-07-03
21
+
22
+ ### Added
23
+
24
+ - **`sdocs run`** — same as `dev`, built for `npx sdocs run`: works with no
25
+ local install. The dev server allows sdocs' own files from the npx cache,
26
+ and when the project has its own `svelte`, previews dedupe onto it, so
27
+ components and their dependencies always resolve from the project.
28
+
29
+ ### Changed
30
+
31
+ - `sdocs build` applies the same svelte dedupe as the dev server.
32
+
10
33
  ## [0.0.26] - 2026-07-03
11
34
 
12
35
  ### Fixed
@@ -4,11 +4,12 @@ import { svelte } from '@sveltejs/vite-plugin-svelte';
4
4
  import { loadConfig } from '../server/config.js';
5
5
  import { sdocsPlugin } from '../vite.js';
6
6
  import { generateBuildFiles, cleanBuildFiles } from '../server/app-gen.js';
7
+ import { svelteDedupe } from './dev.js';
7
8
  export async function buildCommand() {
8
9
  const cwd = process.cwd();
9
10
  const config = await loadConfig(cwd);
10
11
  console.log('[sdocs] Building static site...');
11
- // Generate .sdocs/ with entry + preview HTML files
12
+ // Generate the staging directory (in the OS temp dir) with entry + preview HTML files
12
13
  const { sdocsDir, inputs } = await generateBuildFiles(config, cwd);
13
14
  const inputCount = Object.keys(inputs).length;
14
15
  console.log(`[sdocs] Generated ${inputCount} page(s) (1 main + ${inputCount - 1} previews)`);
@@ -18,6 +19,9 @@ export async function buildCommand() {
18
19
  await build({
19
20
  configFile: false,
20
21
  root: sdocsDir,
22
+ resolve: {
23
+ dedupe: svelteDedupe(cwd),
24
+ },
21
25
  plugins: [
22
26
  svelte(),
23
27
  sdocsPlugin({ ...config, include: absoluteIncludes, _buildMode: true }),
@@ -33,6 +37,6 @@ export async function buildCommand() {
33
37
  console.log(`[sdocs] Build complete → dist/`);
34
38
  }
35
39
  finally {
36
- await cleanBuildFiles(cwd);
40
+ await cleanBuildFiles(sdocsDir);
37
41
  }
38
42
  }
@@ -1 +1,9 @@
1
+ /** sdocs' own install location — under npx this is the npx cache, not the project */
2
+ export declare function sdocsPackageRoot(): string;
3
+ /**
4
+ * Prefer the project's own svelte over the copy next to sdocs when both exist
5
+ * (e.g. running via npx): previews import the project's components, and two
6
+ * svelte runtimes in one page don't mix.
7
+ */
8
+ export declare function svelteDedupe(cwd: string): string[];
1
9
  export declare function devCommand(): Promise<void>;
@@ -1,20 +1,45 @@
1
- import { resolve } from 'node:path';
1
+ import { dirname, resolve } from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { createRequire } from 'node:module';
2
4
  import { createServer } from 'vite';
3
5
  import { svelte } from '@sveltejs/vite-plugin-svelte';
4
6
  import { loadConfig } from '../server/config.js';
5
7
  import { sdocsPlugin } from '../vite.js';
6
8
  import { generateDevFiles, cleanBuildFiles } from '../server/app-gen.js';
9
+ const require = createRequire(import.meta.url);
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ /** sdocs' own install location — under npx this is the npx cache, not the project */
12
+ export function sdocsPackageRoot() {
13
+ return resolve(__dirname, '../..');
14
+ }
15
+ /**
16
+ * Prefer the project's own svelte over the copy next to sdocs when both exist
17
+ * (e.g. running via npx): previews import the project's components, and two
18
+ * svelte runtimes in one page don't mix.
19
+ */
20
+ export function svelteDedupe(cwd) {
21
+ try {
22
+ require.resolve('svelte/package.json', { paths: [cwd] });
23
+ return ['svelte'];
24
+ }
25
+ catch {
26
+ return [];
27
+ }
28
+ }
7
29
  export async function devCommand() {
8
30
  const cwd = process.cwd();
9
31
  const config = await loadConfig(cwd);
10
32
  console.log('[sdocs] Starting dev server...');
11
- // Generate .sdocs/ temp directory with entry files
33
+ // Generate the staging directory (in the OS temp dir) with entry files
12
34
  const sdocsDir = await generateDevFiles(config, cwd);
13
- // Resolve include patterns to absolute paths (relative to cwd, not .sdocs/)
35
+ // Resolve include patterns to absolute paths (relative to cwd, not the staging dir)
14
36
  const absoluteIncludes = config.include.map((p) => resolve(cwd, p));
15
37
  const server = await createServer({
16
38
  configFile: false,
17
39
  root: sdocsDir,
40
+ resolve: {
41
+ dedupe: svelteDedupe(cwd),
42
+ },
18
43
  plugins: [
19
44
  svelte(),
20
45
  sdocsPlugin({ ...config, include: absoluteIncludes }),
@@ -23,7 +48,10 @@ export async function devCommand() {
23
48
  port: config.port,
24
49
  open: config.open,
25
50
  fs: {
26
- allow: [cwd],
51
+ // The staging dir (an explicit allow list replaces Vite's implicit
52
+ // root allowance), the project, and sdocs' own dependency tree
53
+ // (the npx cache when running without a local install).
54
+ allow: [sdocsDir, cwd, resolve(sdocsPackageRoot(), '..')],
27
55
  },
28
56
  },
29
57
  });
@@ -32,7 +60,7 @@ export async function devCommand() {
32
60
  // Cleanup on exit
33
61
  const cleanup = async () => {
34
62
  await server.close();
35
- await cleanBuildFiles(cwd);
63
+ await cleanBuildFiles(sdocsDir);
36
64
  process.exit(0);
37
65
  };
38
66
  process.on('SIGINT', cleanup);
@@ -1,10 +1,10 @@
1
1
  import type { ResolvedSdocsConfig } from '../types.js';
2
- /** Generate .sdocs/ directory with entry files for dev mode */
2
+ /** Generate the staging directory with entry files for dev mode */
3
3
  export declare function generateDevFiles(config: ResolvedSdocsConfig, cwd: string): Promise<string>;
4
- /** Generate .sdocs/ directory with entry + preview HTML files for build mode */
4
+ /** Generate the staging directory with entry + preview HTML files for build mode */
5
5
  export declare function generateBuildFiles(config: ResolvedSdocsConfig, cwd: string): Promise<{
6
6
  sdocsDir: string;
7
7
  inputs: Record<string, string>;
8
8
  }>;
9
- /** Remove the .sdocs/ temp directory */
10
- export declare function cleanBuildFiles(cwd: string): Promise<void>;
9
+ /** Remove a staging directory created by generateDevFiles/generateBuildFiles */
10
+ export declare function cleanBuildFiles(stagingDir: string): Promise<void>;
@@ -1,4 +1,6 @@
1
- import { mkdir, writeFile, rm, readFile, copyFile, readdir } from 'node:fs/promises';
1
+ import { mkdir, mkdtemp, writeFile, rm, readFile, copyFile, readdir } from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
2
4
  import { dirname, resolve, join } from 'node:path';
3
5
  import { fileURLToPath } from 'node:url';
4
6
  import { discoverDocFiles, getSdocKind } from './discovery.js';
@@ -14,6 +16,36 @@ async function copyClientApp(sdocsDir) {
14
16
  await copyDir(getClientSourceDir(), resolve(sdocsDir, 'client'));
15
17
  await copyDir(resolve(__dirname, '../ui'), resolve(sdocsDir, 'ui'));
16
18
  }
19
+ /** Nearest node_modules walking up from dir (mirrors Node's resolution) */
20
+ function findNearestNodeModules(dir) {
21
+ let current = resolve(dir);
22
+ for (;;) {
23
+ const candidate = join(current, 'node_modules');
24
+ if (existsSync(candidate))
25
+ return candidate;
26
+ const parent = dirname(current);
27
+ if (parent === current)
28
+ return null;
29
+ current = parent;
30
+ }
31
+ }
32
+ /**
33
+ * Create a unique staging directory for the generated app. It lives inside
34
+ * node_modules/.cache — never a tracked file in the user's project — and being
35
+ * physically inside node_modules keeps the full walk-up resolution chain for
36
+ * bare imports (svelte, ...) intact, including hoisted monorepo setups. When
37
+ * no node_modules exists anywhere (running via npx in a bare folder), sdocs'
38
+ * own tree provides one; the OS temp dir is the last resort.
39
+ */
40
+ async function createStagingDir(cwd) {
41
+ const nodeModules = findNearestNodeModules(cwd) ?? findNearestNodeModules(resolve(__dirname, '..'));
42
+ if (nodeModules) {
43
+ const cacheDir = join(nodeModules, '.cache');
44
+ await mkdir(cacheDir, { recursive: true });
45
+ return mkdtemp(join(cacheDir, 'sdocs-'));
46
+ }
47
+ return mkdtemp(join(tmpdir(), 'sdocs-'));
48
+ }
17
49
  /** Copy a directory recursively */
18
50
  async function copyDir(src, dest) {
19
51
  await mkdir(dest, { recursive: true });
@@ -129,21 +161,19 @@ async function discoverSnippets(config, cwd) {
129
161
  }
130
162
  return results;
131
163
  }
132
- /** Generate .sdocs/ directory with entry files for dev mode */
164
+ /** Generate the staging directory with entry files for dev mode */
133
165
  export async function generateDevFiles(config, cwd) {
134
- const sdocsDir = resolve(cwd, '.sdocs');
135
- await mkdir(sdocsDir, { recursive: true });
136
- // Copy client components into .sdocs/client/ so they're compiled outside node_modules
166
+ const sdocsDir = await createStagingDir(cwd);
167
+ // Copy client components into the staging dir so they're compiled outside node_modules
137
168
  await copyClientApp(sdocsDir);
138
169
  await writeFile(resolve(sdocsDir, 'index.html'), generateIndexHtml(config.logo));
139
170
  await writeFile(resolve(sdocsDir, 'entry.js'), generateEntryJs(config));
140
171
  return sdocsDir;
141
172
  }
142
- /** Generate .sdocs/ directory with entry + preview HTML files for build mode */
173
+ /** Generate the staging directory with entry + preview HTML files for build mode */
143
174
  export async function generateBuildFiles(config, cwd) {
144
- const sdocsDir = resolve(cwd, '.sdocs');
145
- await mkdir(sdocsDir, { recursive: true });
146
- // Copy client components into .sdocs/client/
175
+ const sdocsDir = await createStagingDir(cwd);
176
+ // Copy client components into the staging dir
147
177
  await copyClientApp(sdocsDir);
148
178
  await writeFile(resolve(sdocsDir, 'index.html'), generateIndexHtml(config.logo));
149
179
  await writeFile(resolve(sdocsDir, 'entry.js'), generateEntryJs(config));
@@ -166,8 +196,7 @@ export async function generateBuildFiles(config, cwd) {
166
196
  }
167
197
  return { sdocsDir, inputs };
168
198
  }
169
- /** Remove the .sdocs/ temp directory */
170
- export async function cleanBuildFiles(cwd) {
171
- const sdocsDir = resolve(cwd, '.sdocs');
172
- await rm(sdocsDir, { recursive: true, force: true });
199
+ /** Remove a staging directory created by generateDevFiles/generateBuildFiles */
200
+ export async function cleanBuildFiles(stagingDir) {
201
+ await rm(stagingDir, { recursive: true, force: true });
173
202
  }
@@ -18,7 +18,8 @@ Usage:
18
18
  sdocs <command>
19
19
 
20
20
  Commands:
21
- dev Start development server with HMR
21
+ dev Start development server with live reload
22
+ run Same as dev — works with npx and no local install
22
23
  build Build static documentation site
23
24
  preview Serve built site locally
24
25
  init Scaffold sdocs.config.js
@@ -39,7 +40,8 @@ async function main() {
39
40
  return;
40
41
  }
41
42
  switch (command) {
42
- case 'dev': {
43
+ case 'dev':
44
+ case 'run': {
43
45
  const { devCommand } = await import('../commands/dev.js');
44
46
  await devCommand();
45
47
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdocs",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "description": "A lightweight documentation tool for Svelte 5 components",
5
5
  "type": "module",
6
6
  "license": "MIT",