jamdesk 1.1.137 → 1.1.139

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.
Files changed (52) hide show
  1. package/dist/__tests__/unit/config-suggestions.test.d.ts +2 -0
  2. package/dist/__tests__/unit/config-suggestions.test.d.ts.map +1 -0
  3. package/dist/__tests__/unit/config-suggestions.test.js +44 -0
  4. package/dist/__tests__/unit/config-suggestions.test.js.map +1 -0
  5. package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts +2 -0
  6. package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts.map +1 -0
  7. package/dist/__tests__/unit/dev-workspace-symlinks.test.js +112 -0
  8. package/dist/__tests__/unit/dev-workspace-symlinks.test.js.map +1 -0
  9. package/dist/__tests__/unit/language-filter.test.d.ts +2 -0
  10. package/dist/__tests__/unit/language-filter.test.d.ts.map +1 -0
  11. package/dist/__tests__/unit/language-filter.test.js +166 -0
  12. package/dist/__tests__/unit/language-filter.test.js.map +1 -0
  13. package/dist/__tests__/unit/loading-page.test.js +18 -0
  14. package/dist/__tests__/unit/loading-page.test.js.map +1 -1
  15. package/dist/__tests__/unit/project-favicon.test.d.ts +2 -0
  16. package/dist/__tests__/unit/project-favicon.test.d.ts.map +1 -0
  17. package/dist/__tests__/unit/project-favicon.test.js +63 -0
  18. package/dist/__tests__/unit/project-favicon.test.js.map +1 -0
  19. package/dist/commands/dev.d.ts.map +1 -1
  20. package/dist/commands/dev.js +10 -0
  21. package/dist/commands/dev.js.map +1 -1
  22. package/dist/commands/validate.d.ts.map +1 -1
  23. package/dist/commands/validate.js +13 -0
  24. package/dist/commands/validate.js.map +1 -1
  25. package/dist/lib/config-suggestions.d.ts +33 -0
  26. package/dist/lib/config-suggestions.d.ts.map +1 -0
  27. package/dist/lib/config-suggestions.js +45 -0
  28. package/dist/lib/config-suggestions.js.map +1 -0
  29. package/dist/lib/dev-loading-server.d.ts +2 -0
  30. package/dist/lib/dev-loading-server.d.ts.map +1 -1
  31. package/dist/lib/dev-loading-server.js +2 -1
  32. package/dist/lib/dev-loading-server.js.map +1 -1
  33. package/dist/lib/language-filter.d.ts +31 -0
  34. package/dist/lib/language-filter.d.ts.map +1 -0
  35. package/dist/lib/language-filter.js +14 -0
  36. package/dist/lib/language-filter.js.map +1 -0
  37. package/dist/lib/loading-page.d.ts +2 -0
  38. package/dist/lib/loading-page.d.ts.map +1 -1
  39. package/dist/lib/loading-page.js +13 -2
  40. package/dist/lib/loading-page.js.map +1 -1
  41. package/dist/lib/output.d.ts +7 -0
  42. package/dist/lib/output.d.ts.map +1 -1
  43. package/dist/lib/output.js +9 -0
  44. package/dist/lib/output.js.map +1 -1
  45. package/dist/lib/project-favicon.d.ts +9 -0
  46. package/dist/lib/project-favicon.d.ts.map +1 -0
  47. package/dist/lib/project-favicon.js +49 -0
  48. package/dist/lib/project-favicon.js.map +1 -0
  49. package/package.json +1 -1
  50. package/vendored/lib/config-suggestions.ts +71 -0
  51. package/vendored/shared/status-reporter.ts +34 -1
  52. package/vendored/workspace-package-lock.json +3 -3
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config-suggestions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-suggestions.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/config-suggestions.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { suggestConfigImprovements } from '../../lib/config-suggestions.js';
3
+ const messages = (config) => suggestConfigImprovements(config).map((s) => s.message);
4
+ describe('suggestConfigImprovements (CLI)', () => {
5
+ it('suggests favicon, description, and logo when all are unset', () => {
6
+ const suggestions = suggestConfigImprovements({});
7
+ expect(suggestions).toHaveLength(3);
8
+ expect(suggestions.map((s) => s.link)).toEqual([
9
+ 'https://jamdesk.com/docs/customization/branding#favicon',
10
+ 'https://jamdesk.com/docs/customization/branding#site-name',
11
+ 'https://jamdesk.com/docs/customization/branding#logo',
12
+ ]);
13
+ });
14
+ it('returns nothing when all fields are set', () => {
15
+ expect(suggestConfigImprovements({
16
+ favicon: '/favicon.svg',
17
+ logo: { light: '/logo.png' },
18
+ description: 'Docs',
19
+ })).toEqual([]);
20
+ });
21
+ it('object favicon with only dark counts as present', () => {
22
+ expect(messages({ favicon: { dark: '/f.svg' }, logo: '/l.png', description: 'd' })).toEqual([]);
23
+ });
24
+ it('empty object logo and whitespace description count as missing', () => {
25
+ const suggestions = suggestConfigImprovements({ favicon: '/f.svg', logo: {}, description: ' ' });
26
+ expect(suggestions.map((s) => s.link)).toEqual([
27
+ 'https://jamdesk.com/docs/customization/branding#site-name',
28
+ 'https://jamdesk.com/docs/customization/branding#logo',
29
+ ]);
30
+ });
31
+ it('logo with only href counts as missing', () => {
32
+ const suggestions = suggestConfigImprovements({ favicon: '/f.svg', logo: { href: 'https://x.com' }, description: 'd' });
33
+ expect(suggestions).toHaveLength(1);
34
+ expect(suggestions[0].link).toContain('#logo');
35
+ });
36
+ it('messages match the canonical copy (pinned — keep the build-service twin aligned)', () => {
37
+ expect(suggestConfigImprovements({}).map((s) => s.message)).toEqual([
38
+ 'Add a favicon so browser tabs and bookmarks show your brand icon instead of a generic page icon.',
39
+ 'Add a site description to improve how your docs appear in search results and social shares.',
40
+ 'Add a logo so your site header shows your brand instead of the default Jamdesk logo.',
41
+ ]);
42
+ });
43
+ });
44
+ //# sourceMappingURL=config-suggestions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-suggestions.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/config-suggestions.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,yBAAyB,EAAyB,MAAM,iCAAiC,CAAC;AAEnG,MAAM,QAAQ,GAAG,CAAC,MAAwB,EAAE,EAAE,CAC5C,yBAAyB,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAE1D,QAAQ,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC/C,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,WAAW,GAAG,yBAAyB,CAAC,EAAE,CAAC,CAAC;QAClD,MAAM,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7C,yDAAyD;YACzD,2DAA2D;YAC3D,sDAAsD;SACvD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,yBAAyB,CAAC;YAC/B,OAAO,EAAE,cAAc;YACvB,IAAI,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;YAC5B,WAAW,EAAE,MAAM;SACpB,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,WAAW,GAAG,yBAAyB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAClG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YAC7C,2DAA2D;YAC3D,sDAAsD;SACvD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,WAAW,GAAG,yBAAyB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;QACxH,MAAM,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kFAAkF,EAAE,GAAG,EAAE;QAC1F,MAAM,CAAC,yBAAyB,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;YAClE,kGAAkG;YAClG,6FAA6F;YAC7F,sFAAsF;SACvF,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dev-workspace-symlinks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-workspace-symlinks.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/dev-workspace-symlinks.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * @vitest-environment node
3
+ *
4
+ * Tests prepareProjectWorkspaceLinks — replaces the single
5
+ * <workspace>/projects/<name> -> <projectDir> symlink with per-entry
6
+ * symlinks that skip non-active language directories. This is what
7
+ * actually reduces Turbopack's filesystem scan from 403 MDX files to
8
+ * 135 on jamdesk-docs.
9
+ */
10
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
11
+ import fs from 'fs-extra';
12
+ import path from 'path';
13
+ import { tmpdir } from 'os';
14
+ import { prepareProjectWorkspaceLinks } from '../../commands/dev.js';
15
+ import { output } from '../../lib/output.js';
16
+ describe('prepareProjectWorkspaceLinks', () => {
17
+ let tmpRoot;
18
+ let projectDir;
19
+ let workspaceProjectDir;
20
+ beforeEach(() => {
21
+ tmpRoot = fs.mkdtempSync(path.join(tmpdir(), 'jam-ws-'));
22
+ projectDir = path.join(tmpRoot, 'project');
23
+ workspaceProjectDir = path.join(tmpRoot, 'ws', 'projects', 'project');
24
+ // Set up a project layout that mirrors a multi-language docs project:
25
+ // project/
26
+ // ai/intro.mdx (en at root)
27
+ // development/foo.mdx (en at root)
28
+ // es/ai/intro.mdx (spanish)
29
+ // fr/ai/intro.mdx (french)
30
+ // docs.json
31
+ // images/logo.png
32
+ fs.mkdirpSync(path.join(projectDir, 'ai'));
33
+ fs.writeFileSync(path.join(projectDir, 'ai', 'intro.mdx'), '# en');
34
+ fs.mkdirpSync(path.join(projectDir, 'development'));
35
+ fs.writeFileSync(path.join(projectDir, 'development', 'foo.mdx'), '# en');
36
+ fs.mkdirpSync(path.join(projectDir, 'es', 'ai'));
37
+ fs.writeFileSync(path.join(projectDir, 'es', 'ai', 'intro.mdx'), '# es');
38
+ fs.mkdirpSync(path.join(projectDir, 'fr', 'ai'));
39
+ fs.writeFileSync(path.join(projectDir, 'fr', 'ai', 'intro.mdx'), '# fr');
40
+ fs.writeFileSync(path.join(projectDir, 'docs.json'), '{}');
41
+ fs.mkdirpSync(path.join(projectDir, 'images'));
42
+ fs.writeFileSync(path.join(projectDir, 'images', 'logo.png'), '');
43
+ });
44
+ afterEach(() => {
45
+ fs.removeSync(tmpRoot);
46
+ });
47
+ it('symlinks every top-level entry when skip set is empty', async () => {
48
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
49
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
50
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es', 'ai', 'intro.mdx'))).toBe(true);
51
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'fr', 'ai', 'intro.mdx'))).toBe(true);
52
+ });
53
+ it('does not symlink docs.json (caller writes a filtered copy)', async () => {
54
+ // docs.json must not be symlinked — the caller writes a per-language
55
+ // filtered copy, and fs.writeFile through a symlink would clobber the
56
+ // user's source docs.json.
57
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
58
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'docs.json'))).toBe(false);
59
+ });
60
+ it('skips entries whose names are in the skip set', async () => {
61
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es', 'fr']));
62
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
63
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'development', 'foo.mdx'))).toBe(true);
64
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'images', 'logo.png'))).toBe(true);
65
+ // Skipped:
66
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
67
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'fr'))).toBe(false);
68
+ // docs.json not symlinked — caller writes a filtered copy.
69
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'docs.json'))).toBe(false);
70
+ });
71
+ it('rebuilds the workspace links from scratch on subsequent calls', async () => {
72
+ // First call: no skip
73
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set());
74
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(true);
75
+ // Second call: skip es
76
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es']));
77
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
78
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'fr'))).toBe(true);
79
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
80
+ });
81
+ it('handles a pre-existing single symlink at workspaceProjectDir (legacy layout)', async () => {
82
+ // Pre-create the legacy single-symlink layout
83
+ fs.mkdirpSync(path.dirname(workspaceProjectDir));
84
+ fs.symlinkSync(projectDir, workspaceProjectDir, 'junction');
85
+ await prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set(['es', 'fr']));
86
+ const lstat = fs.lstatSync(workspaceProjectDir);
87
+ expect(lstat.isDirectory()).toBe(true);
88
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'ai', 'intro.mdx'))).toBe(true);
89
+ expect(fs.existsSync(path.join(workspaceProjectDir, 'es'))).toBe(false);
90
+ });
91
+ it('surfaces friendly error (not raw stack trace) when fs.rm throws ENOTEMPTY', async () => {
92
+ // Regression: before safeRemoveCache, fs.remove raised an unfriendly stack
93
+ // trace when Turbopack still held open files. Now safeRemoveCache detects
94
+ // the race and calls process.exit(1) with a human-readable message.
95
+ const rmSpy = vi.spyOn(fs, 'rm');
96
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code) => {
97
+ throw new Error(`process.exit:${code}`);
98
+ }));
99
+ const errorSpy = vi.spyOn(output, 'error').mockImplementation(() => undefined);
100
+ const enotempty = Object.assign(new Error('ENOTEMPTY'), { code: 'ENOTEMPTY' });
101
+ // fs.rm will be called by safeRemoveCache; make it fail with ENOTEMPTY
102
+ // even after the internal maxRetries — simulate persistent race condition.
103
+ rmSpy.mockRejectedValue(enotempty);
104
+ await expect(prepareProjectWorkspaceLinks(projectDir, workspaceProjectDir, new Set())).rejects.toThrow('process.exit:1');
105
+ const msg = errorSpy.mock.calls[0]?.[0] ?? '';
106
+ expect(msg).toContain('Another `jamdesk dev` instance');
107
+ expect(msg).toContain('pkill -f');
108
+ expect(exitSpy).toHaveBeenCalledWith(1);
109
+ vi.restoreAllMocks();
110
+ });
111
+ });
112
+ //# sourceMappingURL=dev-workspace-symlinks.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-workspace-symlinks.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/dev-workspace-symlinks.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=language-filter.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language-filter.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @vitest-environment node
3
+ *
4
+ * Tests for getActiveLanguageFilter — pure helper that decides which
5
+ * top-level language directories to skip when symlinking project content
6
+ * into the dev workspace. Multi-language sites' non-default languages
7
+ * inflate Turbopack's filesystem scan and balloon cold compile time
8
+ * (jamdesk-docs: 67s with 3 langs vs. 12.5s with 1 lang).
9
+ */
10
+ import { describe, it, expect } from 'vitest';
11
+ import { getActiveLanguageFilter, isPageInSkippedLanguage, filterConfigByActiveLanguage, } from '../../lib/language-filter.js';
12
+ const config = (langs) => ({
13
+ navigation: { languages: langs.map(l => ({ language: l.language, default: l.default })) },
14
+ });
15
+ describe('getActiveLanguageFilter', () => {
16
+ it('returns null filter when project has no languages array', () => {
17
+ const result = getActiveLanguageFilter({ navigation: {} }, undefined, false);
18
+ expect(result).toEqual({ active: null, skip: new Set() });
19
+ });
20
+ it('returns null filter when project has only one language', () => {
21
+ const result = getActiveLanguageFilter(config([{ language: 'en', default: true }]), undefined, false);
22
+ expect(result).toEqual({ active: 'en', skip: new Set() });
23
+ });
24
+ it('skips non-default languages when active is the default', () => {
25
+ const result = getActiveLanguageFilter(config([
26
+ { language: 'en', default: true },
27
+ { language: 'es' },
28
+ { language: 'fr' },
29
+ ]), undefined, false);
30
+ expect(result.active).toBe('en');
31
+ expect(result.skip).toEqual(new Set(['es', 'fr']));
32
+ });
33
+ it('falls back to first language when none is marked default', () => {
34
+ const result = getActiveLanguageFilter(config([{ language: 'en' }, { language: 'es' }, { language: 'fr' }]), undefined, false);
35
+ expect(result.active).toBe('en');
36
+ expect(result.skip).toEqual(new Set(['es', 'fr']));
37
+ });
38
+ it('accepts --lang matching the default language (no-op equivalence)', () => {
39
+ const result = getActiveLanguageFilter(config([
40
+ { language: 'en', default: true },
41
+ { language: 'es' },
42
+ ]), 'en', false);
43
+ expect(result.active).toBe('en');
44
+ expect(result.skip).toEqual(new Set(['es']));
45
+ });
46
+ it('throws on --lang for a non-default language (initial release scope)', () => {
47
+ // Non-default-language layouts mix root-level default-lang content with
48
+ // <lang>/ subdirs for translations; correctly stripping the default
49
+ // content while keeping <lang>/ takes more work and is deferred to a
50
+ // follow-up. For now, surface the workaround clearly.
51
+ expect(() => getActiveLanguageFilter(config([
52
+ { language: 'en', default: true },
53
+ { language: 'es' },
54
+ { language: 'fr' },
55
+ ]), 'es', false)).toThrow(/--lang es: previewing non-default languages.*--all-langs/i);
56
+ });
57
+ it('returns empty skip set when --all-langs is set', () => {
58
+ const result = getActiveLanguageFilter(config([
59
+ { language: 'en', default: true },
60
+ { language: 'es' },
61
+ ]), undefined, true);
62
+ expect(result.active).toBe('en');
63
+ expect(result.skip).toEqual(new Set());
64
+ });
65
+ it('throws on --lang code that does not exist in docs.json', () => {
66
+ expect(() => getActiveLanguageFilter(config([{ language: 'en', default: true }, { language: 'es' }]), 'de', false)).toThrow(/--lang de.*not.*docs\.json.*Available: en, es/);
67
+ });
68
+ it('handles malformed languages array (entries missing language field) by ignoring them', () => {
69
+ const malformed = {
70
+ navigation: {
71
+ languages: [
72
+ { language: 'en', default: true },
73
+ { default: false }, // missing language: ignored
74
+ { language: 'es' },
75
+ { language: 42 }, // wrong type: ignored
76
+ ],
77
+ },
78
+ };
79
+ const result = getActiveLanguageFilter(malformed, undefined, false);
80
+ expect(result.active).toBe('en');
81
+ expect(result.skip).toEqual(new Set(['es']));
82
+ });
83
+ it('returns null active when no valid language entries exist', () => {
84
+ const result = getActiveLanguageFilter({ navigation: { languages: [{ default: true }] } }, undefined, false);
85
+ expect(result).toEqual({ active: null, skip: new Set() });
86
+ });
87
+ it('rejects empty-string --lang as an invalid code (commander passes "" through)', () => {
88
+ // commander.js treats `--lang ""` as a value, not as missing — so
89
+ // langOption is "" (defined, but empty). Empty string is never a valid
90
+ // language code; surface it as a clear error rather than silently
91
+ // falling back to the default.
92
+ expect(() => getActiveLanguageFilter(config([{ language: 'en', default: true }, { language: 'es' }]), '', false)).toThrow(/--lang.*not.*docs\.json/);
93
+ });
94
+ });
95
+ describe('isPageInSkippedLanguage', () => {
96
+ it('returns false when skip set is empty', () => {
97
+ expect(isPageInSkippedLanguage('fr/introduction', new Set())).toBe(false);
98
+ });
99
+ it('returns true when first path segment is a skipped language', () => {
100
+ expect(isPageInSkippedLanguage('fr/introduction', new Set(['fr']))).toBe(true);
101
+ });
102
+ it('returns true for nested paths inside a skipped language', () => {
103
+ expect(isPageInSkippedLanguage('fr/setup/connecting-github', new Set(['fr', 'de']))).toBe(true);
104
+ });
105
+ it('returns false when first segment is the active language', () => {
106
+ expect(isPageInSkippedLanguage('en/introduction', new Set(['fr']))).toBe(false);
107
+ });
108
+ it('returns false for unprefixed root pages', () => {
109
+ expect(isPageInSkippedLanguage('introduction', new Set(['fr']))).toBe(false);
110
+ });
111
+ it('does not match when the skip code is a prefix-substring of a different segment', () => {
112
+ // Guard against startsWith('fr/') matching e.g. 'fr-something/foo'
113
+ expect(isPageInSkippedLanguage('fr-something/foo', new Set(['fr']))).toBe(false);
114
+ });
115
+ it('returns true for a path with a fragment anchor in a skipped language', () => {
116
+ // Broken-anchor warnings from validate-links.cjs surface as link values
117
+ // like `fr/introduction#missing-section` — the fragment lives past the
118
+ // first segment so the language check still works.
119
+ expect(isPageInSkippedLanguage('fr/introduction#setup', new Set(['fr']))).toBe(true);
120
+ });
121
+ });
122
+ describe('filterConfigByActiveLanguage', () => {
123
+ it('returns the input unchanged (same reference) when skip is empty', () => {
124
+ const config = { name: 'foo', navigation: { languages: [{ language: 'en' }] } };
125
+ const result = filterConfigByActiveLanguage(config, { active: 'en', skip: new Set() });
126
+ expect(result).toBe(config);
127
+ });
128
+ it('drops skipped languages from navigation.languages', () => {
129
+ const config = {
130
+ name: 'jamdesk-docs',
131
+ navigation: {
132
+ languages: [
133
+ { language: 'en', default: true, tabs: [{ tab: 'Guide' }] },
134
+ { language: 'fr', tabs: [{ tab: 'Guide' }] },
135
+ { language: 'de', tabs: [{ tab: 'Guide' }] },
136
+ ],
137
+ },
138
+ };
139
+ const result = filterConfigByActiveLanguage(config, {
140
+ active: 'en',
141
+ skip: new Set(['fr', 'de']),
142
+ });
143
+ expect(result.navigation.languages).toEqual([
144
+ { language: 'en', default: true, tabs: [{ tab: 'Guide' }] },
145
+ ]);
146
+ // Top-level fields preserved.
147
+ expect(result.name).toBe('jamdesk-docs');
148
+ // Original config untouched.
149
+ expect(config.navigation.languages).toHaveLength(3);
150
+ });
151
+ it('preserves other navigation fields (tabs, global, anchors, etc.)', () => {
152
+ const config = {
153
+ navigation: {
154
+ languages: [{ language: 'en' }, { language: 'fr' }],
155
+ global: { anchors: [{ anchor: 'Support' }] },
156
+ },
157
+ };
158
+ const result = filterConfigByActiveLanguage(config, {
159
+ active: 'en',
160
+ skip: new Set(['fr']),
161
+ });
162
+ expect(result.navigation.global).toEqual({ anchors: [{ anchor: 'Support' }] });
163
+ expect(result.navigation.languages).toHaveLength(1);
164
+ });
165
+ });
166
+ //# sourceMappingURL=language-filter.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language-filter.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":"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"}
@@ -26,6 +26,24 @@ describe('loading-page', () => {
26
26
  expect(html).toContain('fill="#AD85FF"');
27
27
  expect(html).toContain('fill="#9966FF"');
28
28
  });
29
+ it('uses the project favicon data URI when provided', () => {
30
+ const withFavicon = getLoadingPageHtml({
31
+ projectName: 'acme-docs',
32
+ targetUrl: '/docs/introduction',
33
+ statusEndpoint: '/_jd/dev-status',
34
+ faviconDataUri: 'data:image/png;base64,AAAA',
35
+ });
36
+ expect(withFavicon).toContain('<link rel="icon" href="data:image/png;base64,AAAA">');
37
+ });
38
+ it('falls back to a transparent placeholder favicon (never Jamdesk branding)', () => {
39
+ // A link must always be present: without one the browser reuses whatever
40
+ // favicon it last cached for localhost:<port> (e.g. another docs CLI),
41
+ // since /favicon.ico requests get this HTML page back while loading.
42
+ expect(html).toContain(`<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'/%3E">`);
43
+ // The Jamdesk mark stays in the body logo but must NOT be the favicon
44
+ const faviconLine = html.split('\n').find(l => l.includes('rel="icon"'));
45
+ expect(faviconLine).not.toContain('9966FF');
46
+ });
29
47
  it('includes target URL and status endpoint in JS', () => {
30
48
  expect(html).toContain('/docs/introduction');
31
49
  expect(html).toContain('/_jd/dev-status');
@@ -1 +1 @@
1
- {"version":3,"file":"loading-page.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/loading-page.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"loading-page.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/loading-page.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=project-favicon.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-favicon.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/project-favicon.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import path from 'path';
5
+ import { resolveProjectFaviconDataUri } from '../../lib/project-favicon.js';
6
+ describe('resolveProjectFaviconDataUri', () => {
7
+ let projectDir;
8
+ beforeEach(() => {
9
+ projectDir = fs.mkdtempSync(path.join(os.tmpdir(), 'jd-favicon-'));
10
+ });
11
+ afterEach(() => {
12
+ fs.rmSync(projectDir, { recursive: true, force: true });
13
+ });
14
+ const writeFavicon = (rel, content = '<svg/>') => {
15
+ const abs = path.join(projectDir, rel);
16
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
17
+ fs.writeFileSync(abs, content);
18
+ };
19
+ it('resolves a root-relative string favicon to a base64 data URI', () => {
20
+ writeFavicon('favicon.svg');
21
+ const uri = resolveProjectFaviconDataUri('/favicon.svg', projectDir);
22
+ expect(uri).toBe(`data:image/svg+xml;base64,${Buffer.from('<svg/>').toString('base64')}`);
23
+ });
24
+ it('resolves object form via light, falling back to dark', () => {
25
+ writeFavicon('icons/light.png', Buffer.from([1, 2, 3]));
26
+ writeFavicon('icons/dark.ico', Buffer.from([4, 5]));
27
+ expect(resolveProjectFaviconDataUri({ light: '/icons/light.png' }, projectDir))
28
+ .toMatch(/^data:image\/png;base64,/);
29
+ expect(resolveProjectFaviconDataUri({ dark: '/icons/dark.ico' }, projectDir))
30
+ .toMatch(/^data:image\/x-icon;base64,/);
31
+ });
32
+ it('returns null when the field is unset, empty, or whitespace', () => {
33
+ expect(resolveProjectFaviconDataUri(undefined, projectDir)).toBeNull();
34
+ expect(resolveProjectFaviconDataUri('', projectDir)).toBeNull();
35
+ expect(resolveProjectFaviconDataUri(' ', projectDir)).toBeNull();
36
+ expect(resolveProjectFaviconDataUri({}, projectDir)).toBeNull();
37
+ });
38
+ it('returns null when the referenced file does not exist', () => {
39
+ expect(resolveProjectFaviconDataUri('/missing.svg', projectDir)).toBeNull();
40
+ });
41
+ it('returns null for unsupported extensions', () => {
42
+ writeFavicon('favicon.txt');
43
+ expect(resolveProjectFaviconDataUri('/favicon.txt', projectDir)).toBeNull();
44
+ });
45
+ it('refuses paths that escape the project directory', () => {
46
+ const outside = path.join(path.dirname(projectDir), 'outside.svg');
47
+ fs.writeFileSync(outside, '<svg/>');
48
+ try {
49
+ expect(resolveProjectFaviconDataUri('../outside.svg', projectDir)).toBeNull();
50
+ expect(resolveProjectFaviconDataUri('/../outside.svg', projectDir)).toBeNull();
51
+ }
52
+ finally {
53
+ fs.rmSync(outside, { force: true });
54
+ }
55
+ });
56
+ it('returns null for empty or oversized files', () => {
57
+ writeFavicon('empty.svg', '');
58
+ writeFavicon('huge.png', Buffer.alloc(513 * 1024));
59
+ expect(resolveProjectFaviconDataUri('/empty.svg', projectDir)).toBeNull();
60
+ expect(resolveProjectFaviconDataUri('/huge.png', projectDir)).toBeNull();
61
+ });
62
+ });
63
+ //# sourceMappingURL=project-favicon.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-favicon.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/project-favicon.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkCH;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAO/E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,OAAO,GAAG,SAAS,EAC9B,OAAO,EAAE,OAAO,GACf,OAAO,CAWT;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAKT;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,OAAO,EACzB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC5B,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAQvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAMT;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAMT;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAqCD;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9E;AA+GD;;;;GAIG;AAGH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,GACxC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA4E/D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,OAAO,CAAC,CAkElB;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAk+B5D"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoCH;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAO/E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,OAAO,GAAG,SAAS,EAC9B,OAAO,EAAE,OAAO,GACf,OAAO,CAWT;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,UAAU,EACnB,GAAG,EAAE,MAAM,CAAC,UAAU,GACrB,OAAO,CAKT;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,OAAO,EACzB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC5B,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAQvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAMT;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO,CAMT;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,0EAA0E;IAC1E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAqCD;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAO9E;AA+GD;;;;GAIG;AAGH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,GACxC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA4E/D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,oBAAoB,GACzB,OAAO,CAAC,OAAO,CAAC,CAkElB;AAED,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA2+B5D"}
@@ -20,6 +20,8 @@ import { validateOpenApiSpec, formatErrorForCli, clearSpecCache } from '../lib/o
20
20
  import { validateConfig, formatMigrationNotice } from '../lib/docs-config.js';
21
21
  import { findProjectDir } from '../lib/find-project-dir.js';
22
22
  import { validateBrandingAssets } from '../lib/validate-branding.js';
23
+ import { suggestConfigImprovements } from '../lib/config-suggestions.js';
24
+ import { resolveProjectFaviconDataUri } from '../lib/project-favicon.js';
23
25
  import { findAvailablePort } from '../lib/port.js';
24
26
  import { createLoadingServer } from '../lib/dev-loading-server.js';
25
27
  import { safeRemoveCache } from '../lib/safe-fs.js';
@@ -755,6 +757,11 @@ export async function dev(options) {
755
757
  for (const w of brandingWarnings) {
756
758
  output.warn(`${w.label} file "${w.file}" referenced in docs.json was not found in the project`);
757
759
  }
760
+ // Suggestion-tier: optional config polish (favicon, description, logo).
761
+ const configSuggestions = suggestConfigImprovements(docsConfig);
762
+ for (const s of configSuggestions) {
763
+ output.suggest(`${s.message} — ${s.link}`);
764
+ }
758
765
  // Step 4: Run vendored scripts to prepare the build
759
766
  const hostAtDocs = docsConfig.hostAtDocs === true;
760
767
  const env = {
@@ -908,6 +915,9 @@ export async function dev(options) {
908
915
  loadingServer = createLoadingServer({
909
916
  proxyPort, nextPort, basePath, firstPage,
910
917
  projectName: path.basename(projectDir),
918
+ // Tab icon belongs to the user's site: their favicon if set, else a
919
+ // transparent placeholder (never Jamdesk branding).
920
+ faviconDataUri: resolveProjectFaviconDataUri(docsConfig.favicon, projectDir),
911
921
  });
912
922
  await loadingServer.start();
913
923
  }