@life-and-dev/mdsite 0.6.0 → 0.7.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.
- package/README.md +16 -17
- package/dist/commands/clean.js +28 -10
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/commands.test.js +49 -21
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/generate.js +5 -4
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/prepare.js +2 -2
- package/dist/commands/prepare.js.map +1 -1
- package/dist/commands/prepare.test.js +9 -10
- package/dist/commands/prepare.test.js.map +1 -1
- package/dist/commands/preview.js +21 -21
- package/dist/commands/preview.js.map +1 -1
- package/dist/commands/start.js +13 -11
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/stop.js +7 -4
- package/dist/commands/stop.js.map +1 -1
- package/dist/commands/workflows.test.js +25 -24
- package/dist/commands/workflows.test.js.map +1 -1
- package/dist/config/default-mdsite-config.js +7 -8
- package/dist/config/default-mdsite-config.js.map +1 -1
- package/dist/config/default-mdsite-config.test.js +7 -8
- package/dist/config/default-mdsite-config.test.js.map +1 -1
- package/dist/config/mdsite-config.d.ts +46 -10
- package/dist/config/mdsite-config.js +46 -24
- package/dist/config/mdsite-config.js.map +1 -1
- package/dist/config/mdsite-config.test.js +55 -50
- package/dist/config/mdsite-config.test.js.map +1 -1
- package/dist/process/child-process.d.ts +4 -0
- package/dist/process/child-process.js +33 -1
- package/dist/process/child-process.js.map +1 -1
- package/dist/process/child-process.test.js +39 -3
- package/dist/process/child-process.test.js.map +1 -1
- package/dist/process/runtime-state.d.ts +13 -5
- package/dist/process/runtime-state.js +21 -13
- package/dist/process/runtime-state.js.map +1 -1
- package/dist/process/runtime-state.test.js +3 -5
- package/dist/process/runtime-state.test.js.map +1 -1
- package/dist/renderer/mdsite-nuxt.d.ts +28 -3
- package/dist/renderer/mdsite-nuxt.js +29 -12
- package/dist/renderer/mdsite-nuxt.js.map +1 -1
- package/dist/renderer/mdsite-nuxt.test.js +34 -12
- package/dist/renderer/mdsite-nuxt.test.js.map +1 -1
- package/mdsite-nuxt/app/components/AppFooter.vue +84 -22
- package/mdsite-nuxt/app/composables/useFooter.test.ts +54 -0
- package/mdsite-nuxt/app/composables/useFooter.ts +48 -31
- package/mdsite-nuxt/app/composables/useSiteConfig.test.ts +13 -87
- package/mdsite-nuxt/app/composables/useSiteConfig.ts +7 -26
- package/mdsite-nuxt/app/composables/useSourceEdit.test.ts +103 -0
- package/mdsite-nuxt/app/composables/useSourceEdit.ts +39 -51
- package/mdsite-nuxt/app/layouts/default.vue +10 -3
- package/mdsite-nuxt/content.config.ts +21 -1
- package/mdsite-nuxt/nuxt.config.ts +21 -14
- package/mdsite-nuxt/scripts/generate-favicons.test.ts +3 -3
- package/mdsite-nuxt/scripts/generate-favicons.ts +4 -4
- package/mdsite-nuxt/scripts/generate-indices.test.ts +221 -11
- package/mdsite-nuxt/scripts/generate-indices.ts +187 -28
- package/mdsite-nuxt/scripts/renderer-hooks.test.ts +0 -86
- package/mdsite-nuxt/scripts/renderer-hooks.ts +1 -48
- package/mdsite-nuxt/scripts/sync-content.ts +39 -1
- package/mdsite-nuxt/utils/mdsite-config.ts +86 -41
- package/package.json +1 -1
- package/mdsite-nuxt/example.config.yml +0 -67
|
@@ -9,17 +9,17 @@ import { loadMdsiteConfigSync } from './utils/mdsite-config'
|
|
|
9
9
|
const mdsite = loadMdsiteConfigSync()
|
|
10
10
|
const siteConfig = mdsite.config
|
|
11
11
|
const appBaseURL = process.env.NUXT_APP_BASE_URL || '/'
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
|
|
13
|
+
// mdsite is a static site generator. SSR is only needed at build time so
|
|
14
|
+
// `nuxi generate` can pre-render every route to HTML. In `dev` and
|
|
15
|
+
// `preview` mode SSR uses a per-request fork worker that pulls the full
|
|
16
|
+
// Nuxt + Vuetify + @nuxt/content + mermaid + sharp pipeline and OOMs
|
|
17
|
+
// (Worker terminated … JS heap out of memory) on memory-constrained
|
|
18
|
+
// hosts. Keep SSR on for build/generate, off for dev/preview so the
|
|
19
|
+
// dev server renders client-side only and `mdsite generate` still
|
|
20
|
+
// produces per-route static HTML.
|
|
21
|
+
const isSsrNuxtCommand = process.argv.includes('build') || process.argv.includes('generate')
|
|
22
|
+
const ssrEnabled = isSsrNuxtCommand
|
|
23
23
|
|
|
24
24
|
export default defineNuxtConfig({
|
|
25
25
|
compatibilityDate: '2025-07-15',
|
|
@@ -29,7 +29,6 @@ export default defineNuxtConfig({
|
|
|
29
29
|
public: {
|
|
30
30
|
contentDomain: path.basename(mdsite.contentDir),
|
|
31
31
|
contentPath: mdsite.contentDir,
|
|
32
|
-
contentGitPath,
|
|
33
32
|
// `mdsite.config` is a valid `MdsiteConfig` at runtime, but
|
|
34
33
|
// Nuxt's runtime-config type generator collapses every complex
|
|
35
34
|
// field of `siteConfig` to a degenerate shape — `menu` becomes
|
|
@@ -58,10 +57,18 @@ export default defineNuxtConfig({
|
|
|
58
57
|
},
|
|
59
58
|
|
|
60
59
|
nitro: {
|
|
61
|
-
preset: 'static' // Pure static preset - no SPA fallbacks
|
|
60
|
+
preset: 'static', // Pure static preset - no SPA fallbacks
|
|
61
|
+
// The `mdsite` CLI sets `MDSITE_NITRO_OUTPUT_DIR` in dev mode so the
|
|
62
|
+
// build output lands in the content directory's `<paths.build>/.output/`
|
|
63
|
+
// rather than inside the renderer source (e.g. the `mdsite-nuxt/`
|
|
64
|
+
// submodule). The default `.output` is kept for direct use of the
|
|
65
|
+
// renderer (e.g. running `nuxt generate` by hand for renderer dev).
|
|
66
|
+
output: {
|
|
67
|
+
dir: process.env.MDSITE_NITRO_OUTPUT_DIR || '.output'
|
|
68
|
+
}
|
|
62
69
|
},
|
|
63
70
|
|
|
64
|
-
ssr:
|
|
71
|
+
ssr: ssrEnabled,
|
|
65
72
|
|
|
66
73
|
css: [
|
|
67
74
|
'~/assets/css/markdown.css',
|
|
@@ -66,12 +66,12 @@ describe('generate-favicons', () => {
|
|
|
66
66
|
})
|
|
67
67
|
|
|
68
68
|
describe('generateFavicons', () => {
|
|
69
|
-
it('uses the bundled default favicon and writes all expected assets when
|
|
69
|
+
it('uses the bundled default favicon and writes all expected assets when site.favicon is empty', async () => {
|
|
70
70
|
const outputDir = path.join(tmpDir, 'output')
|
|
71
71
|
|
|
72
72
|
const ok = await generateFavicons({
|
|
73
73
|
contentDir: tmpDir,
|
|
74
|
-
config: { favicon: '' },
|
|
74
|
+
config: { site: { favicon: '' } },
|
|
75
75
|
outputDir,
|
|
76
76
|
})
|
|
77
77
|
|
|
@@ -106,7 +106,7 @@ describe('generate-favicons', () => {
|
|
|
106
106
|
|
|
107
107
|
const ok = await generateFavicons({
|
|
108
108
|
contentDir: tmpDir,
|
|
109
|
-
config: { favicon: 'favicon.svg' },
|
|
109
|
+
config: { site: { favicon: 'favicon.svg' } },
|
|
110
110
|
outputDir,
|
|
111
111
|
})
|
|
112
112
|
|
|
@@ -38,7 +38,7 @@ export function resolveFaviconSource(
|
|
|
38
38
|
|
|
39
39
|
export interface GenerateFaviconsOptions {
|
|
40
40
|
contentDir?: string
|
|
41
|
-
config?: { favicon?: string;
|
|
41
|
+
config?: { site?: { favicon?: string; name?: string } }
|
|
42
42
|
outputDir?: string
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -50,9 +50,9 @@ export async function generateFavicons(options: GenerateFaviconsOptions = {}): P
|
|
|
50
50
|
? { contentDir: options.contentDir, config: options.config }
|
|
51
51
|
: loadMdsiteConfigSync()
|
|
52
52
|
const { contentDir, config } = resolved
|
|
53
|
-
const siteName =
|
|
53
|
+
const siteName = config.site?.name ?? 'site'
|
|
54
54
|
|
|
55
|
-
const resolvedSource = resolveFaviconSource(contentDir, config.favicon ?? '')
|
|
55
|
+
const resolvedSource = resolveFaviconSource(contentDir, config.site?.favicon ?? '')
|
|
56
56
|
|
|
57
57
|
if (!resolvedSource) {
|
|
58
58
|
console.error('❌ No favicon source available (configured source missing AND bundled default not found).')
|
|
@@ -64,7 +64,7 @@ export async function generateFavicons(options: GenerateFaviconsOptions = {}): P
|
|
|
64
64
|
await fs.ensureDir(publicDir)
|
|
65
65
|
|
|
66
66
|
if (resolvedSource.isDefault) {
|
|
67
|
-
console.log('ℹ️ No favicon source configured (
|
|
67
|
+
console.log('ℹ️ No favicon source configured (site.favicon empty or file not found). Using bundled default favicon.')
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
console.log(`🎨 Generating favicons for site: ${siteName}`)
|
|
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
|
|
|
2
2
|
import os from 'node:os'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
6
6
|
|
|
7
7
|
import { generateNavigationJson, generateSearchIndexJson, generateFooterJson } from './generate-indices.js'
|
|
8
8
|
|
|
@@ -244,7 +244,7 @@ describe('generated content indices', () => {
|
|
|
244
244
|
return JSON.parse(await fs.readFile(path.join(publicDir, '_navigation.json'), 'utf8'))
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
it('generates _footer.json with paths and titles from mdsite.yml footer section', async () => {
|
|
247
|
+
it('generates _footer.json with paths and titles from mdsite.yml features.footer section', async () => {
|
|
248
248
|
await fs.writeFile(path.join(contentDir, 'index.md'), '# Home\n\nWelcome home.', 'utf8')
|
|
249
249
|
await fs.writeFile(path.join(contentDir, 'about.md'), '# About\n\nAbout us.', 'utf8')
|
|
250
250
|
await fs.writeFile(path.join(contentDir, 'contacts.md'), '# Contacts\n\nContact us.', 'utf8')
|
|
@@ -253,9 +253,37 @@ describe('generated content indices', () => {
|
|
|
253
253
|
await fs.writeFile(mdsitePath, [
|
|
254
254
|
'site:',
|
|
255
255
|
' name: Test Site',
|
|
256
|
-
'
|
|
257
|
-
'
|
|
258
|
-
'
|
|
256
|
+
'features:',
|
|
257
|
+
' footer:',
|
|
258
|
+
' - about',
|
|
259
|
+
' - contacts',
|
|
260
|
+
'',
|
|
261
|
+
].join('\n'), 'utf8')
|
|
262
|
+
process.env.MDSITE_CONFIG_PATH = mdsitePath
|
|
263
|
+
|
|
264
|
+
await generateFooterJson()
|
|
265
|
+
|
|
266
|
+
const footer = await readFooter()
|
|
267
|
+
expect(footer).toEqual([
|
|
268
|
+
{ path: '/about', title: 'About', type: 'link', isExternal: false },
|
|
269
|
+
{ path: '/contacts', title: 'Contacts', type: 'link', isExternal: false },
|
|
270
|
+
])
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('generates _footer.json with custom labels, external URLs, and separators', async () => {
|
|
274
|
+
await fs.writeFile(path.join(contentDir, 'index.md'), '# Home', 'utf8')
|
|
275
|
+
await fs.writeFile(path.join(contentDir, 'about.md'), '# About', 'utf8')
|
|
276
|
+
|
|
277
|
+
const mdsitePath = path.join(tempDir, 'mdsite.yml')
|
|
278
|
+
await fs.writeFile(mdsitePath, [
|
|
279
|
+
'site:',
|
|
280
|
+
' name: Test Site',
|
|
281
|
+
'features:',
|
|
282
|
+
' footer:',
|
|
283
|
+
' - about',
|
|
284
|
+
' - "About Page": about',
|
|
285
|
+
' - "GitHub Repo": https://github.com/life-and-dev/mdsite',
|
|
286
|
+
' - null',
|
|
259
287
|
'',
|
|
260
288
|
].join('\n'), 'utf8')
|
|
261
289
|
process.env.MDSITE_CONFIG_PATH = mdsitePath
|
|
@@ -264,8 +292,10 @@ describe('generated content indices', () => {
|
|
|
264
292
|
|
|
265
293
|
const footer = await readFooter()
|
|
266
294
|
expect(footer).toEqual([
|
|
267
|
-
{ path: '/about', title: 'About' },
|
|
268
|
-
{ path: '/
|
|
295
|
+
{ path: '/about', title: 'About', type: 'link', isExternal: false },
|
|
296
|
+
{ path: '/about', title: 'About Page', type: 'link', isExternal: false },
|
|
297
|
+
{ path: 'https://github.com/life-and-dev/mdsite', title: 'GitHub Repo', type: 'link', isExternal: true },
|
|
298
|
+
{ path: '', title: '', type: 'separator', isExternal: false },
|
|
269
299
|
])
|
|
270
300
|
})
|
|
271
301
|
|
|
@@ -297,8 +327,9 @@ describe('generated content indices', () => {
|
|
|
297
327
|
'menu:',
|
|
298
328
|
' - index',
|
|
299
329
|
' - guide',
|
|
300
|
-
'
|
|
301
|
-
'
|
|
330
|
+
'features:',
|
|
331
|
+
' footer:',
|
|
332
|
+
' - contacts',
|
|
302
333
|
'',
|
|
303
334
|
].join('\n'), 'utf8')
|
|
304
335
|
process.env.MDSITE_CONFIG_PATH = mdsitePath
|
|
@@ -314,10 +345,39 @@ describe('generated content indices', () => {
|
|
|
314
345
|
|
|
315
346
|
const footer = await readFooter()
|
|
316
347
|
expect(footer).toEqual([
|
|
317
|
-
{ path: '/contacts', title: 'Contacts' },
|
|
348
|
+
{ path: '/contacts', title: 'Contacts', type: 'link', isExternal: false },
|
|
318
349
|
])
|
|
319
350
|
})
|
|
320
351
|
|
|
352
|
+
it('excludes custom-labelled internal footer entries from the navigation tree', async () => {
|
|
353
|
+
await fs.writeFile(path.join(contentDir, 'index.md'), '# Home', 'utf8')
|
|
354
|
+
await fs.writeFile(path.join(contentDir, 'guide.md'), '# Guide', 'utf8')
|
|
355
|
+
await fs.writeFile(path.join(contentDir, 'about.md'), '# About', 'utf8')
|
|
356
|
+
|
|
357
|
+
const mdsitePath = path.join(tempDir, 'mdsite.yml')
|
|
358
|
+
await fs.writeFile(mdsitePath, [
|
|
359
|
+
'site:',
|
|
360
|
+
' name: Test Site',
|
|
361
|
+
'menu:',
|
|
362
|
+
' - index',
|
|
363
|
+
' - guide',
|
|
364
|
+
'features:',
|
|
365
|
+
' footer:',
|
|
366
|
+
' - "About Page": about',
|
|
367
|
+
'',
|
|
368
|
+
].join('\n'), 'utf8')
|
|
369
|
+
process.env.MDSITE_CONFIG_PATH = mdsitePath
|
|
370
|
+
|
|
371
|
+
await generateNavigationJson()
|
|
372
|
+
await generateFooterJson()
|
|
373
|
+
|
|
374
|
+
const navigation = await readNavigation()
|
|
375
|
+
const paths = navigation.map((n: { path: string }) => n.path)
|
|
376
|
+
expect(paths).toContain('/')
|
|
377
|
+
expect(paths).toContain('/guide')
|
|
378
|
+
expect(paths).not.toContain('/about')
|
|
379
|
+
})
|
|
380
|
+
|
|
321
381
|
it('excludes footer entries from the fallback navigation tree', async () => {
|
|
322
382
|
await fs.writeFile(path.join(contentDir, 'index.md'), '# Home\n\nWelcome home.', 'utf8')
|
|
323
383
|
await fs.writeFile(path.join(contentDir, 'about.md'), '# About\n\nAbout us.', 'utf8')
|
|
@@ -327,7 +387,8 @@ describe('generated content indices', () => {
|
|
|
327
387
|
await fs.writeFile(mdsitePath, [
|
|
328
388
|
'site:',
|
|
329
389
|
' name: Test Site',
|
|
330
|
-
'
|
|
390
|
+
'features:',
|
|
391
|
+
' footer: [contacts]',
|
|
331
392
|
'',
|
|
332
393
|
].join('\n'), 'utf8')
|
|
333
394
|
delete process.env.MDSITE_CONFIG_PATH
|
|
@@ -341,4 +402,153 @@ describe('generated content indices', () => {
|
|
|
341
402
|
expect(paths).not.toContain('/contacts')
|
|
342
403
|
})
|
|
343
404
|
})
|
|
405
|
+
|
|
406
|
+
describe('build/dependency directory exclusion', () => {
|
|
407
|
+
async function readSearchIndex() {
|
|
408
|
+
return JSON.parse(await fs.readFile(path.join(publicDir, '_search-index.json'), 'utf8'))
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function readNavigation() {
|
|
412
|
+
return JSON.parse(await fs.readFile(path.join(publicDir, '_navigation.json'), 'utf8'))
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Build a fake content tree that mimics a project root used as the
|
|
417
|
+
* content directory: real user pages alongside build artifacts, hidden
|
|
418
|
+
* editor dirs, and a top-level `node_modules`/`dist`. Returns the set
|
|
419
|
+
* of "expected to be indexed" markdown paths.
|
|
420
|
+
*/
|
|
421
|
+
async function plantMixedTree() {
|
|
422
|
+
// User-authored content at the content root
|
|
423
|
+
await fs.writeFile(path.join(contentDir, 'index.md'), '# Home\n\nWelcome home.', 'utf8')
|
|
424
|
+
await fs.writeFile(path.join(contentDir, 'guide.md'), '# Guide\n\nUseful guide.', 'utf8')
|
|
425
|
+
await fs.mkdir(path.join(contentDir, 'features'), { recursive: true })
|
|
426
|
+
await fs.writeFile(path.join(contentDir, 'features', 'theme.md'), '# Theme\n\nContent.', 'utf8')
|
|
427
|
+
|
|
428
|
+
// Renderer working dir with nested node_modules containing a README
|
|
429
|
+
// — mimics the layout the CLI materializes on first run.
|
|
430
|
+
await fs.mkdir(path.join(contentDir, '.mdsite', 'node_modules', 'some-pkg'), { recursive: true })
|
|
431
|
+
await fs.writeFile(
|
|
432
|
+
path.join(contentDir, '.mdsite', 'node_modules', 'some-pkg', 'README.md'),
|
|
433
|
+
'# some-pkg\n\nThis is a dependency README and should be ignored.'
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
// A top-level node_modules dir (e.g. when the content root IS the
|
|
437
|
+
// project root) should also be skipped.
|
|
438
|
+
await fs.mkdir(path.join(contentDir, 'node_modules', 'transitive'), { recursive: true })
|
|
439
|
+
await fs.writeFile(
|
|
440
|
+
path.join(contentDir, 'node_modules', 'transitive', 'README.md'),
|
|
441
|
+
'# transitive\n\nShould also be ignored.'
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
// A `dist` directory (top-level build output) should also be skipped.
|
|
445
|
+
await fs.mkdir(path.join(contentDir, 'dist', 'legacy'), { recursive: true })
|
|
446
|
+
await fs.writeFile(
|
|
447
|
+
path.join(contentDir, 'dist', 'legacy', 'stale.md'),
|
|
448
|
+
'# Stale\n\nShould be ignored.'
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
// Any hidden directory at any depth should be skipped — editor state,
|
|
452
|
+
// tooling caches, etc. `.vscode`, `.idea`, `.history`, `.cache` are
|
|
453
|
+
// all common offenders when the content root is the project root.
|
|
454
|
+
for (const hidden of ['.vscode', '.idea', '.history', '.cache']) {
|
|
455
|
+
await fs.mkdir(path.join(contentDir, hidden, 'notes'), { recursive: true })
|
|
456
|
+
await fs.writeFile(
|
|
457
|
+
path.join(contentDir, hidden, 'notes', 'scratch.md'),
|
|
458
|
+
`# ${hidden}\n\nShould be ignored.`
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Nuxt/Nitro/CMS output dirs that may sit next to the content.
|
|
463
|
+
for (const hidden of ['.nuxt', '.output', '.nitro', '.data', '.git']) {
|
|
464
|
+
await fs.mkdir(path.join(contentDir, hidden), { recursive: true })
|
|
465
|
+
await fs.writeFile(
|
|
466
|
+
path.join(contentDir, hidden, 'scratch.md'),
|
|
467
|
+
`# ${hidden}\n\nShould be ignored.`
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
it('does not index .md files under any hidden directory, node_modules, or dist', async () => {
|
|
473
|
+
await plantMixedTree()
|
|
474
|
+
|
|
475
|
+
// console.warn would log a "No H1 found" notice for any file the
|
|
476
|
+
// walker still visited — silence it so the test output stays clean.
|
|
477
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
478
|
+
|
|
479
|
+
try {
|
|
480
|
+
await generateSearchIndexJson()
|
|
481
|
+
} finally {
|
|
482
|
+
warnSpy.mockRestore()
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const searchIndex = await readSearchIndex()
|
|
486
|
+
const paths = searchIndex.map((entry: { path: string }) => entry.path)
|
|
487
|
+
// User-authored pages are still indexed
|
|
488
|
+
expect(paths).toContain('/')
|
|
489
|
+
expect(paths).toContain('/guide')
|
|
490
|
+
expect(paths).toContain('/features/theme')
|
|
491
|
+
// No entry leaks from a build/dependency or hidden directory
|
|
492
|
+
for (const excluded of [
|
|
493
|
+
'.mdsite',
|
|
494
|
+
'node_modules',
|
|
495
|
+
'dist',
|
|
496
|
+
'.nuxt',
|
|
497
|
+
'.output',
|
|
498
|
+
'.nitro',
|
|
499
|
+
'.data',
|
|
500
|
+
'.git',
|
|
501
|
+
'.vscode',
|
|
502
|
+
'.idea',
|
|
503
|
+
'.history',
|
|
504
|
+
'.cache'
|
|
505
|
+
]) {
|
|
506
|
+
expect(paths.some((p: string) => p.split('/').includes(excluded))).toBe(false)
|
|
507
|
+
}
|
|
508
|
+
const noH1Warnings = warnSpy.mock.calls
|
|
509
|
+
.map((call) => String(call[0] ?? ''))
|
|
510
|
+
.filter((line) => line.startsWith('⚠️'))
|
|
511
|
+
expect(noH1Warnings).toEqual([])
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('excludes build/dependency and hidden directories from the fallback navigation tree', async () => {
|
|
515
|
+
await plantMixedTree()
|
|
516
|
+
|
|
517
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
518
|
+
|
|
519
|
+
try {
|
|
520
|
+
await generateNavigationJson()
|
|
521
|
+
} finally {
|
|
522
|
+
warnSpy.mockRestore()
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const navigation = await readNavigation()
|
|
526
|
+
const paths = navigation.map((n: { path: string }) => n.path)
|
|
527
|
+
expect(paths).toContain('/')
|
|
528
|
+
expect(paths).toContain('/guide')
|
|
529
|
+
expect(paths).toContain('/features/theme')
|
|
530
|
+
// No node should be derived from a build/dependency or hidden directory
|
|
531
|
+
for (const excluded of [
|
|
532
|
+
'.mdsite',
|
|
533
|
+
'node_modules',
|
|
534
|
+
'dist',
|
|
535
|
+
'.nuxt',
|
|
536
|
+
'.output',
|
|
537
|
+
'.nitro',
|
|
538
|
+
'.data',
|
|
539
|
+
'.git',
|
|
540
|
+
'.vscode',
|
|
541
|
+
'.idea',
|
|
542
|
+
'.history',
|
|
543
|
+
'.cache'
|
|
544
|
+
]) {
|
|
545
|
+
expect(paths.some((p: string) => p.split('/').includes(excluded))).toBe(false)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const noH1Warnings = warnSpy.mock.calls
|
|
549
|
+
.map((call) => String(call[0] ?? ''))
|
|
550
|
+
.filter((line) => line.startsWith('⚠️'))
|
|
551
|
+
expect(noH1Warnings).toEqual([])
|
|
552
|
+
})
|
|
553
|
+
})
|
|
344
554
|
})
|