jamdesk 1.1.122 → 1.1.124
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/dist/__tests__/unit/custom-assets.test.d.ts +2 -0
- package/dist/__tests__/unit/custom-assets.test.d.ts.map +1 -0
- package/dist/__tests__/unit/custom-assets.test.js +162 -0
- package/dist/__tests__/unit/custom-assets.test.js.map +1 -0
- package/dist/__tests__/unit/deps-sync.test.js +7 -4
- package/dist/__tests__/unit/deps-sync.test.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +54 -0
- package/dist/commands/dev.js.map +1 -1
- package/dist/lib/custom-assets.d.ts +38 -0
- package/dist/lib/custom-assets.d.ts.map +1 -0
- package/dist/lib/custom-assets.js +103 -0
- package/dist/lib/custom-assets.js.map +1 -0
- package/package.json +1 -1
- package/vendored/lib/isr-build-executor.ts +35 -0
- package/vendored/lib/seo.ts +32 -2
- package/vendored/scripts/copy-files.cjs +42 -3
- package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts +0 -2
- package/dist/__tests__/unit/dev-workspace-symlinks.test.d.ts.map +0 -1
- package/dist/__tests__/unit/dev-workspace-symlinks.test.js +0 -112
- package/dist/__tests__/unit/dev-workspace-symlinks.test.js.map +0 -1
- package/dist/__tests__/unit/language-filter.test.d.ts +0 -2
- package/dist/__tests__/unit/language-filter.test.d.ts.map +0 -1
- package/dist/__tests__/unit/language-filter.test.js +0 -166
- package/dist/__tests__/unit/language-filter.test.js.map +0 -1
- package/dist/lib/language-filter.d.ts +0 -31
- package/dist/lib/language-filter.d.ts.map +0 -1
- package/dist/lib/language-filter.js +0 -14
- package/dist/lib/language-filter.js.map +0 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* Whether an fs.watch event on the project root warrants a custom-CSS re-sync.
|
|
5
|
+
* Matches any .css file (covers style.css plus atomic-save temp .css names) and
|
|
6
|
+
* a missing filename (some platforms/events pass null — re-sync to be safe;
|
|
7
|
+
* syncCustomAssets content-compares so a spurious re-sync is a cheap no-op).
|
|
8
|
+
* Non-.css edits (.mdx, docs.json) are ignored so routine authoring does not
|
|
9
|
+
* trigger sync work.
|
|
10
|
+
*/
|
|
11
|
+
export function shouldResyncCss(filename) {
|
|
12
|
+
if (!filename)
|
|
13
|
+
return true;
|
|
14
|
+
return filename.endsWith('.css');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Collect every root-level .css file (sorted alphabetically) and concatenate
|
|
18
|
+
* their contents with a newline. Returns null when the project has no root
|
|
19
|
+
* .css at all. This mirrors how the production build assembles custom CSS
|
|
20
|
+
* (collectCustomCss in build.ts / copy-files.cjs) and how Mintlify
|
|
21
|
+
* auto-includes any .css in the docs root — so a project migrated from
|
|
22
|
+
* Mintlify, or one using a non-`style.css` name, behaves identically in
|
|
23
|
+
* `jamdesk dev` and in production.
|
|
24
|
+
*
|
|
25
|
+
* Only regular files are read (a directory named `*.css` is skipped), and a
|
|
26
|
+
* per-file ENOENT is tolerated so a file vanishing mid-read (atomic editor
|
|
27
|
+
* saves — exactly when the watcher fires) is treated as "that file is absent"
|
|
28
|
+
* rather than crashing the sync.
|
|
29
|
+
*/
|
|
30
|
+
async function collectRootCss(projectDir) {
|
|
31
|
+
let entries;
|
|
32
|
+
try {
|
|
33
|
+
entries = await fs.readdir(projectDir, { withFileTypes: true });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null; // project dir unreadable — treat as no custom CSS
|
|
37
|
+
}
|
|
38
|
+
const cssFiles = entries
|
|
39
|
+
.filter((d) => d.isFile() && d.name.endsWith('.css'))
|
|
40
|
+
.map((d) => d.name)
|
|
41
|
+
.sort();
|
|
42
|
+
if (cssFiles.length === 0)
|
|
43
|
+
return null;
|
|
44
|
+
const parts = [];
|
|
45
|
+
for (const file of cssFiles) {
|
|
46
|
+
try {
|
|
47
|
+
parts.push(await fs.readFile(path.join(projectDir, file), 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
if (err.code !== 'ENOENT')
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return parts.length > 0 ? parts.join('\n') : null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Sync a docs project's custom CSS into the dev workspace so `jamdesk dev`
|
|
58
|
+
* applies it like the production build does:
|
|
59
|
+
* all root <projectDir>/*.css (sorted, concatenated) -> <workspaceDir>/public/custom.css
|
|
60
|
+
*
|
|
61
|
+
* The dev (non-ISR) layout reads this from <cwd>/public/custom.css via
|
|
62
|
+
* getLocalFileContent, and Next runs with cwd = workspaceDir, so no docs.json
|
|
63
|
+
* flag is needed in dev.
|
|
64
|
+
*
|
|
65
|
+
* Removes the workspace copy when there is no root .css — syncVendoredFiles
|
|
66
|
+
* skips public/ from its sweep, so a stale custom.css would otherwise persist
|
|
67
|
+
* across runs. But a custom.css the project ships in its own public/ dir is
|
|
68
|
+
* copied in by syncDirectory (which does not prune), so removal is skipped
|
|
69
|
+
* when that source exists — otherwise we would delete the author's content.
|
|
70
|
+
* Content-compares before writing so the file watcher does not churn
|
|
71
|
+
* Turbopack on no-op editor saves, and only touches the filesystem when there
|
|
72
|
+
* is real work (filesystem-inert otherwise). Image url(/images/..) refs need no
|
|
73
|
+
* rewrite: the dev next.config.js rewrites /images/* -> /_jd/images/* at the
|
|
74
|
+
* HTTP layer.
|
|
75
|
+
*/
|
|
76
|
+
export async function syncCustomAssets(opts) {
|
|
77
|
+
const { projectDir, workspaceDir } = opts;
|
|
78
|
+
const publicDir = path.join(workspaceDir, 'public');
|
|
79
|
+
const cssDest = path.join(publicDir, 'custom.css');
|
|
80
|
+
const result = { cssWritten: false, cssRemoved: false };
|
|
81
|
+
const srcContent = await collectRootCss(projectDir);
|
|
82
|
+
if (srcContent !== null) {
|
|
83
|
+
const destContent = (await fs.pathExists(cssDest)) ? await fs.readFile(cssDest, 'utf-8') : null;
|
|
84
|
+
if (srcContent !== destContent) {
|
|
85
|
+
await fs.ensureDir(publicDir); // only create public/ when we actually write
|
|
86
|
+
await fs.writeFile(cssDest, srcContent);
|
|
87
|
+
result.cssWritten = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// No root .css source. Only remove a STALE workspace custom.css that a
|
|
92
|
+
// previous sync wrote. If the project ships its own public/custom.css,
|
|
93
|
+
// syncDirectory legitimately copied it into the workspace — leave it so we
|
|
94
|
+
// never delete the author's content.
|
|
95
|
+
const projectShipsPublicCss = await fs.pathExists(path.join(projectDir, 'public', 'custom.css'));
|
|
96
|
+
if (!projectShipsPublicCss && (await fs.pathExists(cssDest))) {
|
|
97
|
+
await fs.remove(cssDest);
|
|
98
|
+
result.cssRemoved = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=custom-assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-assets.js","sourceRoot":"","sources":["../../src/lib/custom-assets.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jamdesk",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.124",
|
|
4
4
|
"description": "CLI for Jamdesk — build, preview, and deploy documentation sites from MDX. Dev server with hot reload, 50+ components, OpenAPI support, AI search, and Mintlify migration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jamdesk",
|
|
@@ -338,3 +338,38 @@ export function collectCustomJs(
|
|
|
338
338
|
|
|
339
339
|
return contents.length > 0 ? contents.join('\n') : null;
|
|
340
340
|
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Collect custom CSS content from the project root by auto-detecting every
|
|
344
|
+
* root-level .css file (sorted alphabetically) and concatenating them with a
|
|
345
|
+
* newline. Returns null when none exist.
|
|
346
|
+
*
|
|
347
|
+
* Unlike collectCustomJs there is no docs.json config field: Jamdesk follows
|
|
348
|
+
* Mintlify's convention of auto-including any .css in the docs root, which is
|
|
349
|
+
* what makes a Mintlify-migrated project (or one using a non-`style.css` name)
|
|
350
|
+
* work without edits. Alphabetical order keeps the cascade reproducible across
|
|
351
|
+
* the ISR build, the static build (copy-files.cjs), and `jamdesk dev`.
|
|
352
|
+
*/
|
|
353
|
+
export function collectCustomCss(projectDir: string): string | null {
|
|
354
|
+
let cssFiles: string[];
|
|
355
|
+
try {
|
|
356
|
+
cssFiles = (fs.readdirSync(projectDir, { withFileTypes: true }) as fs.Dirent[])
|
|
357
|
+
.filter(d => d.isFile() && d.name.endsWith('.css'))
|
|
358
|
+
.map(d => d.name)
|
|
359
|
+
.sort();
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (cssFiles.length === 0) return null;
|
|
365
|
+
|
|
366
|
+
const contents: string[] = [];
|
|
367
|
+
for (const file of cssFiles) {
|
|
368
|
+
const fullPath = path.join(projectDir, file);
|
|
369
|
+
if (fs.existsSync(fullPath)) {
|
|
370
|
+
contents.push(fs.readFileSync(fullPath, 'utf-8'));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return contents.length > 0 ? contents.join('\n') : null;
|
|
375
|
+
}
|
package/vendored/lib/seo.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import type { Metadata } from 'next';
|
|
12
12
|
import type { DocsConfig, Logo, LogoConfig, Favicon, LanguageConfig, LanguageCode } from './docs-types';
|
|
13
13
|
import { normalizeLogo } from './docs-types';
|
|
14
|
-
import { findHreflangAliasCollisions, transformLanguagePath, toHreflang } from './language-utils';
|
|
14
|
+
import { findHreflangAliasCollisions, transformLanguagePath, toHreflang, resolveLocaleFromPath } from './language-utils';
|
|
15
15
|
import { logger } from '../shared/logger';
|
|
16
16
|
|
|
17
17
|
// Dedupe per-process: same docs.json shape produces the same collision
|
|
@@ -228,6 +228,7 @@ function numericMeta(metatags: Record<string, string>, key: string): number | un
|
|
|
228
228
|
|
|
229
229
|
type FallbackOg = {
|
|
230
230
|
title?: string; description?: string; url: string; siteName: string; ogImageUrl: string;
|
|
231
|
+
locale?: string;
|
|
231
232
|
};
|
|
232
233
|
|
|
233
234
|
/**
|
|
@@ -261,7 +262,10 @@ export function buildOpenGraphMetadata(
|
|
|
261
262
|
og.description = metatags['og:description'] || fb.description;
|
|
262
263
|
og.url = metatags['og:url'] || fb.url;
|
|
263
264
|
og.siteName = metatags['og:site_name'] || fb.siteName;
|
|
264
|
-
|
|
265
|
+
// Explicit og:locale wins; otherwise fall back to the locale derived from the
|
|
266
|
+
// page's resolved language (multi-language sites only — see derivePageLocale).
|
|
267
|
+
const ogLocale = metatags['og:locale'] || fb.locale;
|
|
268
|
+
if (ogLocale) og.locale = ogLocale;
|
|
265
269
|
// og:determiner is a narrow Next union ('a'|'an'|'the'|'auto'|''); ignore invalid.
|
|
266
270
|
if (['a', 'an', 'the', 'auto', ''].includes(metatags['og:determiner'])) {
|
|
267
271
|
og.determiner = metatags['og:determiner'];
|
|
@@ -563,6 +567,31 @@ export function buildHreflangAlternates(
|
|
|
563
567
|
return alternates;
|
|
564
568
|
}
|
|
565
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Derive an `og:locale` value from the page's resolved language.
|
|
572
|
+
*
|
|
573
|
+
* Open Graph wants `language_TERRITORY` (underscore). We reuse the hreflang
|
|
574
|
+
* BCP-47 mapping and swap the hyphen for an underscore, so region-bearing codes
|
|
575
|
+
* map exactly (`pt-BR` → `pt_BR`, `fr-CA` → `fr_CA`, `cn` → `zh_Hans`) and bare
|
|
576
|
+
* codes emit the language alone (`fr` → `fr`). We deliberately do NOT invent a
|
|
577
|
+
* territory for bare codes — guessing `es_ES` for a Latin-American site would be
|
|
578
|
+
* worse than emitting `es`.
|
|
579
|
+
*
|
|
580
|
+
* Gated to multi-language sites: with one (or zero) declared languages we can't
|
|
581
|
+
* know the site's locale, so we emit nothing rather than guess `en`. The default
|
|
582
|
+
* (unprefixed) page resolves to the configured default language.
|
|
583
|
+
*/
|
|
584
|
+
function derivePageLocale(pagePath: string, languages?: LanguageConfig[]): string | undefined {
|
|
585
|
+
if (!languages || languages.length <= 1) return undefined;
|
|
586
|
+
const declared = languages.map((l) => l.language);
|
|
587
|
+
const fromPath = resolveLocaleFromPath(pagePath, declared);
|
|
588
|
+
const code = (fromPath
|
|
589
|
+
|| languages.find((l) => l.default)?.language
|
|
590
|
+
|| languages[0]?.language) as LanguageCode | undefined;
|
|
591
|
+
if (!code) return undefined;
|
|
592
|
+
return toHreflang(code).replace(/-/g, '_');
|
|
593
|
+
}
|
|
594
|
+
|
|
566
595
|
/**
|
|
567
596
|
* Build SEO metadata for a documentation page.
|
|
568
597
|
*
|
|
@@ -683,6 +712,7 @@ export function buildSeoMetadata(
|
|
|
683
712
|
url: pageUrl,
|
|
684
713
|
siteName: config.name,
|
|
685
714
|
ogImageUrl,
|
|
715
|
+
locale: derivePageLocale(pagePath, languages),
|
|
686
716
|
});
|
|
687
717
|
}
|
|
688
718
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Copy Static Assets Script
|
|
4
4
|
*
|
|
5
5
|
* Copies static assets from project's images directory to public/images.
|
|
6
|
-
* Also
|
|
6
|
+
* Also assembles custom CSS and JS files if they exist in project root.
|
|
7
7
|
* Supports project-specific builds via PROJECT_NAME environment variable.
|
|
8
8
|
*
|
|
9
9
|
* File type and size restrictions:
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* PROJECT_NAME=acme node scripts/copy-files.js # Same as above
|
|
17
17
|
*
|
|
18
18
|
* Custom Files (optional):
|
|
19
|
-
* projects/<name
|
|
19
|
+
* projects/<name>/*.css -> public/custom.css (theme overrides, concatenated)
|
|
20
|
+
* - Any .css in the project root is auto-detected (Mintlify convention)
|
|
20
21
|
* projects/<name>/*.js -> public/custom.js (custom JavaScript, concatenated)
|
|
21
22
|
* - Configured via styling.js in docs.json, or auto-detected from project root
|
|
22
23
|
*
|
|
@@ -103,7 +104,6 @@ async function copyCustomFiles() {
|
|
|
103
104
|
if (!projectDir) return;
|
|
104
105
|
|
|
105
106
|
const customFiles = [
|
|
106
|
-
{ src: 'style.css', dest: 'custom.css', configKey: '_hasCustomCss' },
|
|
107
107
|
{ src: 'sitemap.xml', dest: 'sitemap.xml', configKey: '_hasCustomSitemap' },
|
|
108
108
|
{ src: 'robots.txt', dest: 'robots.txt', configKey: '_hasCustomRobots' },
|
|
109
109
|
];
|
|
@@ -135,6 +135,45 @@ async function copyCustomFiles() {
|
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
+
// Handle custom CSS — auto-detect every root .css (Mintlify convention),
|
|
139
|
+
// sorted alphabetically and concatenated, so a non-`style.css` name or a
|
|
140
|
+
// Mintlify-migrated project works without edits. Mirrors collectCustomCss in
|
|
141
|
+
// build.ts and syncCustomAssets in the CLI.
|
|
142
|
+
const customCssDest = path.join(publicDir, 'custom.css');
|
|
143
|
+
let cssFiles = [];
|
|
144
|
+
try {
|
|
145
|
+
// withFileTypes avoids a per-entry statSync (and the TOCTOU window where a
|
|
146
|
+
// single failing stat would drop the whole readdir result) — mirrors
|
|
147
|
+
// collectCustomCss in isr-build-executor.ts.
|
|
148
|
+
cssFiles = fs.readdirSync(projectDir, { withFileTypes: true })
|
|
149
|
+
.filter(d => d.isFile() && d.name.endsWith('.css'))
|
|
150
|
+
.map(d => d.name)
|
|
151
|
+
.sort();
|
|
152
|
+
} catch { /* ignore */ }
|
|
153
|
+
|
|
154
|
+
if (cssFiles.length > 0) {
|
|
155
|
+
const cssContents = [];
|
|
156
|
+
for (const file of cssFiles) {
|
|
157
|
+
const srcPath = path.join(projectDir, file);
|
|
158
|
+
if (fs.existsSync(srcPath)) {
|
|
159
|
+
cssContents.push(fs.readFileSync(srcPath, 'utf8'));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (cssContents.length > 0) {
|
|
163
|
+
fs.writeFileSync(customCssDest, cssContents.join('\n'));
|
|
164
|
+
docsConfig._hasCustomCss = true;
|
|
165
|
+
console.log(` ✓ Custom CSS (${cssFiles.join(', ')}) -> custom.css`);
|
|
166
|
+
} else {
|
|
167
|
+
if (fs.existsSync(customCssDest)) fs.removeSync(customCssDest);
|
|
168
|
+
docsConfig._hasCustomCss = false;
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
if (fs.existsSync(customCssDest)) {
|
|
172
|
+
fs.removeSync(customCssDest);
|
|
173
|
+
}
|
|
174
|
+
docsConfig._hasCustomCss = false;
|
|
175
|
+
}
|
|
176
|
+
|
|
138
177
|
// Handle custom JavaScript (supports styling.js config + auto-detection)
|
|
139
178
|
const customJsDest = path.join(publicDir, 'custom.js');
|
|
140
179
|
let jsFiles = [];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dev-workspace-symlinks.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/dev-workspace-symlinks.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,112 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"language-filter.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,166 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"language-filter.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/language-filter.test.ts"],"names":[],"mappings":"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"}
|