jamdesk 1.1.205 → 1.1.206

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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Drift guard for the Turbopack `.js`-specifier workaround.
3
+ *
4
+ * `build-service/next.config.mjs` applies `scripts/turbopack-js-to-ts-loader.cjs`
5
+ * to every `*.ts`/`*.tsx` because Turbopack has no equivalent of webpack's
6
+ * `resolve.extensionAlias` and throws a hard "Module not found" on some
7
+ * `./foo.js` imports whose real source is `./foo.ts` — the convention this
8
+ * repo uses everywhere (tsconfig `moduleResolution: "bundler"`).
9
+ *
10
+ * The CLI vendors the SAME source tree, including the jwt-session subtree that
11
+ * provoked the original failure, and compiles it with Turbopack in
12
+ * `jamdesk dev`. So the CLI config needs the same rule, and the loader has to
13
+ * be vendored or the rule points at a file that is not in the workspace.
14
+ *
15
+ * CLAUDE.md already requires that a build-service next.config change be
16
+ * mirrored into the CLI config by hand. This test is what makes that rule
17
+ * enforceable instead of aspirational.
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=turbopack-loader-config-drift.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turbopack-loader-config-drift.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/unit/turbopack-loader-config-drift.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Drift guard for the Turbopack `.js`-specifier workaround.
3
+ *
4
+ * `build-service/next.config.mjs` applies `scripts/turbopack-js-to-ts-loader.cjs`
5
+ * to every `*.ts`/`*.tsx` because Turbopack has no equivalent of webpack's
6
+ * `resolve.extensionAlias` and throws a hard "Module not found" on some
7
+ * `./foo.js` imports whose real source is `./foo.ts` — the convention this
8
+ * repo uses everywhere (tsconfig `moduleResolution: "bundler"`).
9
+ *
10
+ * The CLI vendors the SAME source tree, including the jwt-session subtree that
11
+ * provoked the original failure, and compiles it with Turbopack in
12
+ * `jamdesk dev`. So the CLI config needs the same rule, and the loader has to
13
+ * be vendored or the rule points at a file that is not in the workspace.
14
+ *
15
+ * CLAUDE.md already requires that a build-service next.config change be
16
+ * mirrored into the CLI config by hand. This test is what makes that rule
17
+ * enforceable instead of aspirational.
18
+ */
19
+ import { describe, it, expect } from 'vitest';
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ import { fileURLToPath } from 'node:url';
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const CLI_ROOT = path.join(__dirname, '../../..');
25
+ const BUILD_SERVICE = path.join(CLI_ROOT, '../build-service');
26
+ const LOADER_BASENAME = 'turbopack-js-to-ts-loader.cjs';
27
+ const CONFIGS = {
28
+ 'build-service/next.config.mjs': path.join(BUILD_SERVICE, 'next.config.mjs'),
29
+ 'cli/config/next.config.js': path.join(CLI_ROOT, 'config/next.config.js'),
30
+ 'cli/vendored/next.config.js': path.join(CLI_ROOT, 'vendored/next.config.js'),
31
+ };
32
+ function read(p) {
33
+ return fs.readFileSync(p, 'utf8');
34
+ }
35
+ /** Comments stripped, so a prose edit never fails the guard. */
36
+ function code(src) {
37
+ return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '');
38
+ }
39
+ describe('Turbopack loader rule is mirrored across every Next config', () => {
40
+ for (const [label, file] of Object.entries(CONFIGS)) {
41
+ it(`${label} applies the loader to *.ts and *.tsx`, () => {
42
+ const src = code(read(file));
43
+ expect(src, `${label} is missing a turbopack.rules block`).toMatch(/rules\s*:\s*\{/);
44
+ for (const glob of ['*.ts', '*.tsx']) {
45
+ const re = new RegExp(`'\\${glob}'\\s*:\\s*\\{\\s*loaders\\s*:\\s*\\[\\s*'\\./scripts/${LOADER_BASENAME.replace(/\./g, '\\.')}'`);
46
+ expect(src, `${label} does not route ${glob} through ${LOADER_BASENAME}`).toMatch(re);
47
+ }
48
+ });
49
+ it(`${label} keeps the webpack extensionAlias fallback`, () => {
50
+ // `jamdesk dev --webpack` is an advertised escape hatch (src/index.ts),
51
+ // and webpack needs the alias for exactly the same imports.
52
+ const src = code(read(file));
53
+ expect(src, `${label} is missing resolve.extensionAlias`).toMatch(/extensionAlias/);
54
+ expect(src).toMatch(/'\.js'\s*:\s*\[\s*'\.ts'\s*,\s*'\.tsx'\s*,\s*'\.js'\s*\]/);
55
+ });
56
+ }
57
+ });
58
+ describe('the loader itself is present wherever a config points at it', () => {
59
+ const canonical = path.join(BUILD_SERVICE, 'scripts', LOADER_BASENAME);
60
+ const vendored = path.join(CLI_ROOT, 'vendored/scripts', LOADER_BASENAME);
61
+ it('exists in build-service/scripts', () => {
62
+ expect(fs.existsSync(canonical), `${canonical} is missing`).toBe(true);
63
+ });
64
+ it('is vendored into the CLI workspace tree', () => {
65
+ // dev.ts copies vendored/ wholesale into the workspace, so vendored/scripts
66
+ // becomes <workspace>/scripts — the path the rule resolves against.
67
+ expect(fs.existsSync(vendored), `${vendored} is missing; add it to safeScripts in cli/scripts/vendor.js`).toBe(true);
68
+ });
69
+ it('is byte-identical to the canonical copy', () => {
70
+ expect(read(vendored)).toBe(read(canonical));
71
+ });
72
+ it('is on the vendor allowlist, so re-vendoring keeps it', () => {
73
+ // The copy loop in vendor.js is existsSync-guarded, so an omission here is
74
+ // silent: vendoring succeeds and the CLI fails at compile time instead.
75
+ const vendorScript = read(path.join(CLI_ROOT, 'scripts/vendor.js'));
76
+ expect(vendorScript).toContain(LOADER_BASENAME);
77
+ });
78
+ });
79
+ //# sourceMappingURL=turbopack-loader-config-drift.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turbopack-loader-config-drift.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/turbopack-loader-config-drift.test.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.205",
3
+ "version": "1.1.206",
4
4
  "description": "CLI for Jamdesk \u2014 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",
@@ -5,10 +5,7 @@
5
5
  // non-ISR local dev). The render body lives in lib/render-doc-page.tsx.
6
6
  import { headers } from 'next/headers';
7
7
  import { after } from 'next/server';
8
- import fs from 'fs';
9
- import path from 'path';
10
8
  import type { Metadata } from 'next';
11
- import { getContentDir } from '@/lib/docs';
12
9
  import {
13
10
  isIsrMode,
14
11
  getProjectFromRequest,
@@ -18,7 +15,7 @@ import {
18
15
  import { renderDocPage, buildDocMetadata, type RenderInput } from '@/lib/render-doc-page';
19
16
  import { readNewsletterDisplay } from '@/lib/newsletter-display';
20
17
  import { withR2OpsContext, emitR2OpsSummary } from '@/lib/r2-content';
21
- import { readFileSync as readMdxFile } from '@/lib/fs-readfile';
18
+ import { getAllDocPaths } from '@/lib/mdx-import-scan';
22
19
 
23
20
  export const dynamic = 'force-dynamic';
24
21
  export const dynamicParams = true;
@@ -30,104 +27,6 @@ interface PageProps {
30
27
  }>;
31
28
  }
32
29
 
33
- // Mirror of the CLI's detector regex in cli/src/lib/relative-mdx-imports.ts.
34
- // Duplicated intentionally — build-service uses bundler module resolution
35
- // and shouldn't reach into cli/src/. Keep the regex itself in sync with
36
- // that file. Two intentional simplifications vs the CLI version:
37
- // - no `g` flag (boolean test, not iteration)
38
- // - fence stripping replaces matches with empty string instead of
39
- // blank lines of equal count — line numbers don't matter here, only
40
- // a yes/no skip decision (the CLI preserves them for warning text).
41
- const PARENT_RELATIVE_MDX_IMPORT_RE =
42
- /^[ \t]*import\s+(?:type\s+)?[\w$*{}, \n\r]+\s+from\s+["']\.{1,2}\/[^"']+\.mdx["']\s*;?/m;
43
-
44
- const FENCED_CODE_BLOCK_RE =
45
- /^( *)(```+|~~~+)[^\n]*\n([\s\S]*?)\n\1\2\s*$/gm;
46
-
47
- // Cache for pageHasRelativeMdxImport, keyed by absolute file path,
48
- // invalidated when the file's mtime changes. `generateStaticParams` runs
49
- // on every nav in `jamdesk dev`, and the regex-test reads the FULL file
50
- // (per fix 7205c17c). For dodo (~200 MDX × 70-94 KB) that's 14-19 MB of
51
- // disk I/O per nav. Cache hit reduces it to a directory walk + statSync.
52
- //
53
- // In production ISR mode generateStaticParams returns [] before any of
54
- // this runs (see isIsrMode early-return), so the cache is dev-only in
55
- // practice. The cache is unbounded — entries for deleted files persist
56
- // until process exit. For a dev-only ~200-file working set this is
57
- // trivial (a few KB). If this pattern is reused in a long-running ISR
58
- // context in the future, add LRU eviction or rebuild-on-walk.
59
- const mdxImportCache = new Map<string, { mtimeMs: number; hasImport: boolean }>();
60
-
61
- export function pageHasRelativeMdxImport(filePath: string, mtimeMs?: number): boolean {
62
- const cached = mdxImportCache.get(filePath);
63
- if (cached && mtimeMs !== undefined && cached.mtimeMs === mtimeMs) {
64
- return cached.hasImport;
65
- }
66
-
67
- let hasImport: boolean;
68
- try {
69
- // Read the full file — MDX imports can appear at any top-level position
70
- // (after a long prose intro or table of contents), and a slice was
71
- // missing real imports past byte ~8192 in 70-94 KB customer pages.
72
- const content = readMdxFile(filePath);
73
- // Strip fenced code blocks so documentation examples (e.g.
74
- // ```mdx\nimport X from "../snippets/foo.mdx";\n```) don't false-trigger.
75
- hasImport = PARENT_RELATIVE_MDX_IMPORT_RE.test(content.replace(FENCED_CODE_BLOCK_RE, ''));
76
- } catch {
77
- hasImport = false;
78
- }
79
-
80
- if (mtimeMs !== undefined) {
81
- mdxImportCache.set(filePath, { mtimeMs, hasImport });
82
- }
83
- return hasImport;
84
- }
85
-
86
- /** Test-only: clear the per-file MDX-import cache between test cases. */
87
- export function _resetMdxImportCacheForTest(): void {
88
- if (process.env.NODE_ENV === 'production') return;
89
- mdxImportCache.clear();
90
- }
91
-
92
- interface CollectedPaths {
93
- supported: string[];
94
- skipped: string[];
95
- }
96
-
97
- export function getAllDocPaths(): CollectedPaths {
98
- const contentDir = getContentDir();
99
- const supported: string[] = [];
100
- const skipped: string[] = [];
101
-
102
- function traverseDir(dir: string, basePath: string = '') {
103
- if (!fs.existsSync(dir)) return;
104
- const files = fs.readdirSync(dir);
105
- for (const file of files) {
106
- if (file.startsWith('.')) continue;
107
- const filePath = path.join(dir, file);
108
- let stat;
109
- try {
110
- stat = fs.statSync(filePath);
111
- } catch {
112
- continue;
113
- }
114
- if (stat.isDirectory()) {
115
- traverseDir(filePath, path.join(basePath, file));
116
- } else if (file.endsWith('.mdx')) {
117
- const slug = path.join(basePath, file.replace(/\.mdx$/, ''));
118
- if (pageHasRelativeMdxImport(filePath, stat.mtimeMs)) {
119
- skipped.push(slug);
120
- } else {
121
- supported.push(slug);
122
- }
123
- }
124
- }
125
- }
126
-
127
- traverseDir(contentDir);
128
- return { supported, skipped };
129
- }
130
-
131
30
  // In `jamdesk dev`, Next.js calls generateStaticParams during initial
132
31
  // compile AND for each lazily-compiled route (every page click). Without
133
32
  // a guard the same multi-line warning floods the dev console after every
@@ -10,6 +10,18 @@
10
10
  * route's redirects: never `NextResponse.redirect(new URL('/', req.url))`,
11
11
  * since `req.url`'s host is the internal rewrite host, not the customer's
12
12
  * public host behind a reverse proxy.
13
+ *
14
+ * Cross-site protection: clearing a cookie from a GET means any page on the
15
+ * web could log a reader out by pointing an <img> or <iframe> at this URL.
16
+ * We do NOT use `isSameOriginRequest` here, unlike the cookie-MINTING routes.
17
+ * That helper rejects `cross-site`, and a perfectly legitimate 'Log out of
18
+ * docs' link on the customer's own app is cross-site. The attack and the
19
+ * legitimate link differ by Sec-Fetch-Dest, not by site: a forced logout is
20
+ * always a sub-resource load, a real logout is always a document navigation.
21
+ * So we clear only for `document` (plus clients that send no Sec-Fetch at
22
+ * all, i.e. curl and pre-2020 browsers, which cannot be attacked this way).
23
+ * A suppressed logout still 303s — the route's contract is that it never
24
+ * fails loudly — it just leaves the session alone.
13
25
  */
14
26
 
15
27
  import { resolveAuth } from '@/lib/auth-resolver';
@@ -17,6 +29,23 @@ import { resolveAuth } from '@/lib/auth-resolver';
17
29
  export const runtime = 'nodejs';
18
30
  export const dynamic = 'force-dynamic';
19
31
 
32
+ /**
33
+ * True when this GET is a real top-level navigation, so acting on it is the
34
+ * reader's own doing. Sec-Fetch-Dest is browser-set and page-unforgeable.
35
+ * Absent means a non-browser client, which cannot be the victim of a
36
+ * cross-site sub-resource load, so we act.
37
+ *
38
+ * Prefetch is excluded separately: a speculative fetch of the logout link
39
+ * carries `Sec-Fetch-Dest: document` but no reader ever clicked it.
40
+ */
41
+ function isUserInitiatedNavigation(req: Request): boolean {
42
+ const purpose = req.headers.get('sec-purpose') || '';
43
+ if (purpose.includes('prefetch')) return false;
44
+ const dest = req.headers.get('sec-fetch-dest');
45
+ if (!dest) return true;
46
+ return dest === 'document';
47
+ }
48
+
20
49
  export async function GET(req: Request): Promise<Response> {
21
50
  const slug = req.headers.get('x-project-slug');
22
51
  // Relative '/' works for the same-site fallback; the customer loginUrl is
@@ -29,10 +58,13 @@ export async function GET(req: Request): Promise<Response> {
29
58
  } catch { /* logout must always succeed; fall back to '/' */ }
30
59
  }
31
60
  const headers = new Headers({ Location: location, 'Cache-Control': 'no-store' });
32
- if (slug) {
33
- headers.append('Set-Cookie', `jd_auth_${slug}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`);
61
+ if (isUserInitiatedNavigation(req)) {
62
+ if (slug) {
63
+ headers.append('Set-Cookie', `jd_auth_${slug}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`);
64
+ }
65
+ // Clear the client-readable hint the callback set (Task 8) alongside it,
66
+ // so the header never advertises a session the cookie no longer backs.
67
+ headers.append('Set-Cookie', 'jd_authed=; Path=/; Secure; SameSite=Lax; Max-Age=0');
34
68
  }
35
- // Always clear the client-readable hint the callback set (Task 8).
36
- headers.append('Set-Cookie', 'jd_authed=; Path=/; Secure; SameSite=Lax; Max-Age=0');
37
69
  return new Response(null, { status: 303, headers });
38
70
  }
@@ -24,6 +24,7 @@ import { sanitizeFrom } from '@/lib/sanitize-from';
24
24
  import { VALID_SLUG_RE } from '@/lib/middleware-helpers';
25
25
  import { signAuthCookie } from '@/shared/auth-cookie';
26
26
  import { secretsEqual } from '@/lib/crypto-helpers';
27
+ import { MASTER_AUDIT_TAG } from '@/lib/unlock-audit';
27
28
  import { isSameOriginRequest } from '@/lib/same-origin';
28
29
 
29
30
  const scryptAsync = promisify(scrypt) as (
@@ -68,10 +69,6 @@ const MASTER_COOKIE_MAX_AGE = 60 * 60;
68
69
  // an attacker rotates slugs to evade the per-slug limit.
69
70
  const MASTER_PASSWORD_MIN_LENGTH = 32;
70
71
 
71
- // Audit log tag emitted on every successful master-password unlock. Exported
72
- // so tests can assert exact format without duplicating the literal.
73
- export const MASTER_AUDIT_TAG = '[unlock] master-password used';
74
-
75
72
  // Pre-compute a dummy hash at module load time so we can always run scrypt
76
73
  // even when no hash is stored, preventing timing oracles that reveal whether
77
74
  // a project has password protection configured.
@@ -0,0 +1,84 @@
1
+ import type { BuildWarning } from '../shared/status-reporter.js'; // vendored copy — NOT '../../shared'
2
+
3
+ /**
4
+ * Warn when a gated project finished a build without its auth plane written.
5
+ *
6
+ * `setProjectAuthPublic` is the only thing that puts a project's gate config
7
+ * where the edge can read it, and in build.ts it sits inside a try whose catch
8
+ * logs `Failed to write projectAuthPublic to Redis (non-fatal)` and lets the
9
+ * build go green. That non-fatal choice is right for availability and wrong for
10
+ * visibility: `resolveAuth` returns null when the key is absent, and a null
11
+ * resolve means `applyAuthGate` returns null, which is NO GATE. So the failure
12
+ * mode of a dropped write is a fully public site reported as a successful build.
13
+ *
14
+ * Two ways in, neither of them a misconfiguration:
15
+ * - Upstash is briefly unavailable during the write.
16
+ * - The payload outgrows what `upstashCommand` can put in a URL path. It
17
+ * encodes the whole value into the path, and a few hundred group-restricted
18
+ * pages is tens of KB of URL, so this one arrives through ordinary growth on
19
+ * a project that has been fine for months.
20
+ *
21
+ * Deliberately mode-agnostic. The fail-open lives in `resolveAuth` returning
22
+ * null, which happens before authType is ever consulted, so a password tenant
23
+ * loses its gate exactly as a jwt tenant does. Silent when `enabled` is false,
24
+ * where serving publicly is the stated intent.
25
+ *
26
+ * This warns rather than fails the build. Failing would be the safer default on
27
+ * the ordering alone (the write precedes the R2 upload, so a hard fail would
28
+ * stop unprotected content from publishing at all), but it converts every
29
+ * transient Upstash blip into a broken customer build. That tradeoff is a
30
+ * product call, not a build-service one.
31
+ */
32
+ export function buildAuthPlaneWriteWarning(input: {
33
+ enabled: boolean;
34
+ writeFailed: boolean;
35
+ }): BuildWarning | null {
36
+ if (!input.enabled || !input.writeFailed) return null;
37
+ return {
38
+ // Same reuse, and the same reasoning, as buildJwtKeyWarning: this routing
39
+ // (emailed, counted, badged) is what the warning needs, and a dedicated
40
+ // BuildWarningType would mean the six-place sync chain for no difference.
41
+ type: 'invalid_openapi_spec',
42
+ file: 'docs.json',
43
+ message:
44
+ 'This project is password or JWT protected, but the build could not publish its access rules, ' +
45
+ 'so the site is currently readable by anyone. Rebuild to retry. ' +
46
+ 'If it keeps failing, the access rules may have grown too large to store and support can raise it.',
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Does docs.json itself declare a gate? Read BEFORE the auth try block, so the
52
+ * warning above survives a throw inside it.
53
+ *
54
+ * The gap this closes: `authPlaneGated` used to be assigned only from
55
+ * `effectiveEnabled`, ~19 lines into the try, after `pageInfos.map` and
56
+ * `detectAuthMode`. Anything throwing in that window skipped both the
57
+ * assignment AND `setProjectAuthPublic`, so the site went public and the
58
+ * warning was suppressed by the same throw. That window is reachable from
59
+ * user-supplied config: `collectGroupsPaths` does
60
+ * `(navigation?.languages ?? []).map(...)`, and `??` only guards null and
61
+ * undefined, so a `languages` that is a string throws.
62
+ *
63
+ * Mirrors `detectAuthMode`'s own test exactly (`?.enabled === true`), never
64
+ * truthiness — 'false' and 0 are not gates there and must not be gates here,
65
+ * or the warning fires on a site that is public by intent.
66
+ *
67
+ * MUST NOT THROW. Optional chaining is safe against any non-null value, which
68
+ * is the point: docs.json is user JSON and `auth` can be a string, an array or
69
+ * a number. Callers combine this monotonically (`a || b`) with the in-try
70
+ * value, so it can only ever turn the warning on.
71
+ *
72
+ * Residual, named rather than hidden: `specific` mode is frontmatter-derived
73
+ * (`collectPrivatePaths`), so a tenant gated only by per-page `private`
74
+ * frontmatter reads false here. A throw before `detectAuthMode` on such a
75
+ * tenant still goes unwarned. Covering it would mean replicating the
76
+ * frontmatter walk outside the try, which reintroduces the throw it guards.
77
+ */
78
+ export function configDeclaresGate(docsConfig: unknown): boolean {
79
+ const auth = (docsConfig as { auth?: unknown } | null | undefined)?.auth as {
80
+ password?: { enabled?: unknown };
81
+ jwt?: { enabled?: unknown };
82
+ } | undefined;
83
+ return auth?.password?.enabled === true || auth?.jwt?.enabled === true;
84
+ }
@@ -0,0 +1,38 @@
1
+ import type { BuildWarning } from '../shared/status-reporter.js'; // vendored copy — NOT '../../shared'
2
+
3
+ /**
4
+ * Warn when a project declares JWT authentication but has no signing key.
5
+ *
6
+ * The truth table in auth-resolver.ts is explicit: `authType === 'jwt'` with a
7
+ * missing or malformed `projectJwtSecret` means NO GATE. That is the correct
8
+ * fail-safe for a key that was cleared deliberately, but it is also reachable
9
+ * by accident: commit `auth.jwt.enabled: true`, publish, and never click
10
+ * Generate signing key. The build then succeeds, the site serves every page to
11
+ * the public, and nothing says so. The dashboard card does report it, but a
12
+ * customer who publishes from git has no reason to open that page.
13
+ *
14
+ * Silent for `enabled: false`, where a public site is the stated intent, and
15
+ * silent for password tenants, whose own secret has its own row in that table.
16
+ * A password tenant can legitimately still hold a JWT key from a half-finished
17
+ * migration; coexistence is by design and only `authType` decides.
18
+ */
19
+ export function buildJwtKeyWarning(input: {
20
+ authType: 'password' | 'jwt';
21
+ enabled: boolean;
22
+ hasSigningKey: boolean;
23
+ }): BuildWarning | null {
24
+ if (input.authType !== 'jwt' || !input.enabled || input.hasSigningKey) return null;
25
+ return {
26
+ // Reuses invalid_openapi_spec deliberately: it already routes the way this
27
+ // needs — emailed, counted, badged, not filed as a suggestion — and a
28
+ // dedicated BuildWarningType would mean editing the six-place sync chain
29
+ // plus EMAILABLE_TYPES for no behavioural difference. Same reasoning, and
30
+ // the same call, as the spec-expansion warnings in build.ts.
31
+ type: 'invalid_openapi_spec',
32
+ // docs.json is where the opt-in lives and what an author edits to change it.
33
+ file: 'docs.json',
34
+ message:
35
+ 'auth.jwt.enabled is true but this project has no signing key, so nothing is gated and every page is publicly readable. ' +
36
+ 'Generate an Ed25519 signing key under Settings, or set auth.jwt.enabled to false if the site is meant to be public.',
37
+ };
38
+ }
@@ -0,0 +1,110 @@
1
+ // Extracted from app/[[...slug]]/page.tsx: Next.js's route/page typegen
2
+ // requires that a page.tsx export ONLY the known page-convention symbols
3
+ // (default, generateStaticParams, generateMetadata, dynamic, ...) — any
4
+ // other export fails `tsc`'s check of .next/types/app/**/page.ts with
5
+ // TS2344 ("does not satisfy the constraint '{ [x: string]: never }'").
6
+ // These helpers are used internally by generateStaticParams but also need
7
+ // to be unit-testable, so they live here instead and are imported (not
8
+ // re-exported) by page.tsx.
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { getContentDir } from '@/lib/docs';
12
+ import { readFileSync as readMdxFile } from '@/lib/fs-readfile';
13
+
14
+ // Mirror of the CLI's detector regex in cli/src/lib/relative-mdx-imports.ts.
15
+ // Duplicated intentionally — build-service uses bundler module resolution
16
+ // and shouldn't reach into cli/src/. Keep the regex itself in sync with
17
+ // that file. Two intentional simplifications vs the CLI version:
18
+ // - no `g` flag (boolean test, not iteration)
19
+ // - fence stripping replaces matches with empty string instead of
20
+ // blank lines of equal count — line numbers don't matter here, only
21
+ // a yes/no skip decision (the CLI preserves them for warning text).
22
+ const PARENT_RELATIVE_MDX_IMPORT_RE =
23
+ /^[ \t]*import\s+(?:type\s+)?[\w$*{}, \n\r]+\s+from\s+["']\.{1,2}\/[^"']+\.mdx["']\s*;?/m;
24
+
25
+ const FENCED_CODE_BLOCK_RE =
26
+ /^( *)(```+|~~~+)[^\n]*\n([\s\S]*?)\n\1\2\s*$/gm;
27
+
28
+ // Cache for pageHasRelativeMdxImport, keyed by absolute file path,
29
+ // invalidated when the file's mtime changes. `generateStaticParams` runs
30
+ // on every nav in `jamdesk dev`, and the regex-test reads the FULL file
31
+ // (per fix 7205c17c). For dodo (~200 MDX × 70-94 KB) that's 14-19 MB of
32
+ // disk I/O per nav. Cache hit reduces it to a directory walk + statSync.
33
+ //
34
+ // In production ISR mode generateStaticParams returns [] before any of
35
+ // this runs (see isIsrMode early-return), so the cache is dev-only in
36
+ // practice. The cache is unbounded — entries for deleted files persist
37
+ // until process exit. For a dev-only ~200-file working set this is
38
+ // trivial (a few KB). If this pattern is reused in a long-running ISR
39
+ // context in the future, add LRU eviction or rebuild-on-walk.
40
+ const mdxImportCache = new Map<string, { mtimeMs: number; hasImport: boolean }>();
41
+
42
+ export function pageHasRelativeMdxImport(filePath: string, mtimeMs?: number): boolean {
43
+ const cached = mdxImportCache.get(filePath);
44
+ if (cached && mtimeMs !== undefined && cached.mtimeMs === mtimeMs) {
45
+ return cached.hasImport;
46
+ }
47
+
48
+ let hasImport: boolean;
49
+ try {
50
+ // Read the full file — MDX imports can appear at any top-level position
51
+ // (after a long prose intro or table of contents), and a slice was
52
+ // missing real imports past byte ~8192 in 70-94 KB customer pages.
53
+ const content = readMdxFile(filePath);
54
+ // Strip fenced code blocks so documentation examples (e.g.
55
+ // ```mdx\nimport X from "../snippets/foo.mdx";\n```) don't false-trigger.
56
+ hasImport = PARENT_RELATIVE_MDX_IMPORT_RE.test(content.replace(FENCED_CODE_BLOCK_RE, ''));
57
+ } catch {
58
+ hasImport = false;
59
+ }
60
+
61
+ if (mtimeMs !== undefined) {
62
+ mdxImportCache.set(filePath, { mtimeMs, hasImport });
63
+ }
64
+ return hasImport;
65
+ }
66
+
67
+ /** Test-only: clear the per-file MDX-import cache between test cases. */
68
+ export function _resetMdxImportCacheForTest(): void {
69
+ if (process.env.NODE_ENV === 'production') return;
70
+ mdxImportCache.clear();
71
+ }
72
+
73
+ interface CollectedPaths {
74
+ supported: string[];
75
+ skipped: string[];
76
+ }
77
+
78
+ export function getAllDocPaths(): CollectedPaths {
79
+ const contentDir = getContentDir();
80
+ const supported: string[] = [];
81
+ const skipped: string[] = [];
82
+
83
+ function traverseDir(dir: string, basePath: string = '') {
84
+ if (!fs.existsSync(dir)) return;
85
+ const files = fs.readdirSync(dir);
86
+ for (const file of files) {
87
+ if (file.startsWith('.')) continue;
88
+ const filePath = path.join(dir, file);
89
+ let stat;
90
+ try {
91
+ stat = fs.statSync(filePath);
92
+ } catch {
93
+ continue;
94
+ }
95
+ if (stat.isDirectory()) {
96
+ traverseDir(filePath, path.join(basePath, file));
97
+ } else if (file.endsWith('.mdx')) {
98
+ const slug = path.join(basePath, file.replace(/\.mdx$/, ''));
99
+ if (pageHasRelativeMdxImport(filePath, stat.mtimeMs)) {
100
+ skipped.push(slug);
101
+ } else {
102
+ supported.push(slug);
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ traverseDir(contentDir);
109
+ return { supported, skipped };
110
+ }
@@ -0,0 +1,10 @@
1
+ // Extracted from app/api/jd/unlock/route.ts: Next.js's route typegen requires
2
+ // that a route.ts export ONLY the known route-convention symbols (GET, POST,
3
+ // runtime, dynamic, ...) — any other export fails `tsc`'s check of
4
+ // .next/types/app/**/route.ts with TS2344 ("does not satisfy the constraint
5
+ // '{ [x: string]: never }'"). This constant is used by the route handler but
6
+ // also needs to be assertable-against in tests, so it lives here instead.
7
+
8
+ // Audit log tag emitted on every successful master-password unlock. Exported
9
+ // so tests can assert exact format without duplicating the literal.
10
+ export const MASTER_AUDIT_TAG = '[unlock] master-password used';
@@ -70,6 +70,25 @@ const nextConfig = {
70
70
  '../build/polyfills/polyfill-module': './lib/empty-polyfill.js',
71
71
  'next/dist/build/polyfills/polyfill-module': './lib/empty-polyfill.js',
72
72
  },
73
+ // Mirrors build-service/next.config.mjs. Turbopack has no equivalent of
74
+ // webpack's resolve.extensionAlias (https://github.com/vercel/next.js/issues/82945),
75
+ // so a relative `./foo.js` import whose real source is `./foo.ts` can fail
76
+ // to resolve. That is the convention every file in vendored/ uses, 120
77
+ // relative `.js` specifiers of it, including the lib/jwt-session.ts ->
78
+ // lib/public-paths-resolver.ts chain that made build-service fail
79
+ // deterministically. The workspace compiles the same tree, so it needs
80
+ // the same rule. Exempt from this file's keep-it-minimal rule: the
81
+ // 80s-compile warning above is about turbopack.root and resolution
82
+ // scope, and this is one regex pass over source text.
83
+ //
84
+ // vendored/ is copied wholesale into the workspace, so vendored/scripts
85
+ // lands at <workspace>/scripts and this relative path resolves. The
86
+ // loader is on the safeScripts allowlist in cli/scripts/vendor.js;
87
+ // turbopack-loader-config-drift.test.ts pins all of it together.
88
+ rules: {
89
+ '*.ts': { loaders: ['./scripts/turbopack-js-to-ts-loader.cjs'] },
90
+ '*.tsx': { loaders: ['./scripts/turbopack-js-to-ts-loader.cjs'] },
91
+ },
73
92
  },
74
93
  // Critical for fast builds
75
94
  experimental: {
@@ -87,6 +106,19 @@ const nextConfig = {
87
106
  '@radix-ui/react-tooltip',
88
107
  ],
89
108
  },
109
+ // `jamdesk dev --webpack` is an advertised fallback (src/index.ts), and
110
+ // webpack needs the same `.js`-means-`.ts` mapping the Turbopack rule above
111
+ // provides. Without it the escape hatch fails on the imports the escape
112
+ // hatch exists to work around.
113
+ webpack: (config) => {
114
+ config.resolve.extensionAlias = {
115
+ ...config.resolve.extensionAlias,
116
+ '.js': ['.ts', '.tsx', '.js'],
117
+ '.mjs': ['.mts', '.mjs'],
118
+ '.cjs': ['.cts', '.cjs'],
119
+ }
120
+ return config
121
+ },
90
122
  }
91
123
 
92
124
  export default nextConfig
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Turbopack build-time source transform: strips the `.js` suffix off
3
+ * RELATIVE import/export/dynamic-import specifiers before Turbopack's own
4
+ * resolver sees them.
5
+ *
6
+ * Why this exists: this project's tsconfig.json sets
7
+ * `moduleResolution: "bundler"`, the TypeScript convention where source
8
+ * files are `.ts` but every relative import of them is written with a
9
+ * `.js` suffix (e.g. `import { x } from './foo.js'` for a file that is
10
+ * actually `./foo.ts` on disk). webpack supports this natively via
11
+ * `resolve.extensionAlias` (wired up in next.config.mjs's `webpack()`
12
+ * function). Turbopack has NO equivalent, documented, built-in support for
13
+ * this as of Next.js 16.3.x — see https://github.com/vercel/next.js/issues/82945
14
+ * ("Turbopack: support importing .ts/.tsx via .js extension"), which is
15
+ * open/unfixed upstream. In practice Turbopack resolves the VAST MAJORITY
16
+ * of this project's `.js`-suffixed relative imports fine through some
17
+ * internal inference, but was observed to throw a hard "Module not found"
18
+ * for specific files/subtrees (e.g. the whole lib/jwt-session.ts ->
19
+ * lib/public-paths-resolver.ts -> lib/{docs-types,glob-match,
20
+ * find-first-nav-page,language-utils}.ts chain added by the jwt docs-auth
21
+ * feature) — confirmed via a standalone unrs-resolver probe, and via
22
+ * removing the `.js` suffix by hand (which fixes resolution one hop at a
23
+ * time, confirming the failure is the extension-alias gap, not a real
24
+ * missing file). Rather than rewrite this project's whole import style
25
+ * (pervasive, deliberate convention — see tsconfig.json's
26
+ * `moduleResolution: "bundler"` and CLAUDE.md), this loader rewrites the
27
+ * `.js` -> (nothing) at Turbopack-compile time only. TypeScript itself,
28
+ * `tsc --noEmit`, vitest, and webpack builds are all UNAFFECTED (this file
29
+ * is wired into next.config.mjs's `turbopack.rules` only).
30
+ *
31
+ * Scoped to RELATIVE specifiers only (`./x.js`, `../x.js`) — never bare
32
+ * package specifiers — so a legitimate deep import of an npm package's own
33
+ * `.js` file (e.g. a subpath export) is never touched.
34
+ *
35
+ * Community-sourced workaround for the same open issue, adapted to be
36
+ * relative-imports-only (the referenced version was unscoped):
37
+ * https://github.com/vercel/next.js/issues/82945#issuecomment-3269958583
38
+ */
39
+
40
+ // Matches: (import|export) ... from '<spec>'; and dynamic import('<spec>')
41
+ // where <spec> starts with ./ or ../ and ends in .js/.jsx/.mjs, capturing
42
+ // the quote character so it round-trips exactly.
43
+ const RELATIVE_JS_SPECIFIER_RE =
44
+ /((?:from\s+|import\()\s*)(["'])(\.\.?\/[^"']+?)\.(m?js|jsx)\2/g;
45
+
46
+ module.exports = function stripRelativeJsExtensions(source) {
47
+ return source.replace(
48
+ RELATIVE_JS_SPECIFIER_RE,
49
+ (_match, prefix, quote, specPath) => `${prefix}${quote}${specPath}${quote}`,
50
+ );
51
+ };
@@ -82,13 +82,13 @@
82
82
  }
83
83
  },
84
84
  "node_modules/@antfu/install-pkg": {
85
- "version": "2.0.1",
86
- "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.0.1.tgz",
87
- "integrity": "sha512-iCKVQcIC0e3oDxEfs3SHQGW+ovhBMZmS1TE+bTk50rVyMCBmCfClv7Qi3HQKlumYwvjb/iIMeWCW2i67q6kFfQ==",
85
+ "version": "2.1.0",
86
+ "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.1.0.tgz",
87
+ "integrity": "sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw==",
88
88
  "license": "MIT",
89
89
  "dependencies": {
90
- "package-manager-detector": "^1.7.0",
91
- "tinyexec": "^1.2.4"
90
+ "package-manager-detector": "^1.8.0",
91
+ "tinyexec": "^1.3.1"
92
92
  },
93
93
  "funding": {
94
94
  "url": "https://github.com/sponsors/antfu"
@@ -1974,9 +1974,9 @@
1974
1974
  "license": "MIT"
1975
1975
  },
1976
1976
  "node_modules/@types/d3-selection": {
1977
- "version": "3.0.11",
1978
- "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
1979
- "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
1977
+ "version": "3.0.12",
1978
+ "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.12.tgz",
1979
+ "integrity": "sha512-Qe/KWYhEiIIxGs7HrAAjMfShxKldx19SJtr5zu53f3afPsdZNz7HHtdTLXo/kqeiWNXVycI24kSnfzBYkTzpgw==",
1980
1980
  "license": "MIT"
1981
1981
  },
1982
1982
  "node_modules/@types/d3-shape": {
@@ -2119,9 +2119,9 @@
2119
2119
  }
2120
2120
  },
2121
2121
  "node_modules/@types/node": {
2122
- "version": "26.5.1",
2123
- "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz",
2124
- "integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==",
2122
+ "version": "26.6.1",
2123
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.1.tgz",
2124
+ "integrity": "sha512-VqGJBMCtdhqkBUCcBLvywI0NJ+KLuVzgNnlBUNFOQjqVxzo2lxLUNg1DSey8+u2u6ktswSAxg+s68QLzWHNOuA==",
2125
2125
  "license": "MIT",
2126
2126
  "dependencies": {
2127
2127
  "undici-types": "~8.9.0"
@@ -2277,9 +2277,9 @@
2277
2277
  }
2278
2278
  },
2279
2279
  "node_modules/autoprefixer": {
2280
- "version": "10.6.0",
2281
- "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.0.tgz",
2282
- "integrity": "sha512-A26d6qs9kqGgkmImIXMYvXTzqb4Qv7AVgpY1NXzr9Y659J8qHHnLOO/zE8ewIGFMprOolAoRAQYDgNryXIJKBw==",
2280
+ "version": "10.6.1",
2281
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz",
2282
+ "integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==",
2283
2283
  "funding": [
2284
2284
  {
2285
2285
  "type": "opencollective",
@@ -2332,9 +2332,9 @@
2332
2332
  }
2333
2333
  },
2334
2334
  "node_modules/baseline-browser-mapping": {
2335
- "version": "2.11.23",
2336
- "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.23.tgz",
2337
- "integrity": "sha512-le521dGVfxM7yRX0EikCoSz+rOK+hHzdDt/E7mG1jOJB/6WAAUuwVroLwaB7ApaUsz5Q0kFlDXLSA9MheUIfRQ==",
2335
+ "version": "2.11.24",
2336
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz",
2337
+ "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==",
2338
2338
  "license": "Apache-2.0",
2339
2339
  "bin": {
2340
2340
  "baseline-browser-mapping": "dist/cli.cjs"
@@ -2344,9 +2344,9 @@
2344
2344
  }
2345
2345
  },
2346
2346
  "node_modules/brace-expansion": {
2347
- "version": "5.0.9",
2348
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
2349
- "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
2347
+ "version": "5.0.12",
2348
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
2349
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
2350
2350
  "license": "MIT",
2351
2351
  "dependencies": {
2352
2352
  "balanced-match": "^4.0.2"
@@ -2356,9 +2356,9 @@
2356
2356
  }
2357
2357
  },
2358
2358
  "node_modules/browserslist": {
2359
- "version": "4.28.9",
2360
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz",
2361
- "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==",
2359
+ "version": "4.29.0",
2360
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz",
2361
+ "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==",
2362
2362
  "funding": [
2363
2363
  {
2364
2364
  "type": "opencollective",
@@ -2375,11 +2375,11 @@
2375
2375
  ],
2376
2376
  "license": "MIT",
2377
2377
  "dependencies": {
2378
- "baseline-browser-mapping": "^2.11.20",
2378
+ "baseline-browser-mapping": "^2.11.23",
2379
2379
  "caniuse-lite": "^1.0.30001810",
2380
- "electron-to-chromium": "^1.5.420",
2381
- "node-releases": "^2.0.54",
2382
- "update-browserslist-db": "^1.3.2"
2380
+ "electron-to-chromium": "^1.5.427",
2381
+ "node-releases": "^2.0.55",
2382
+ "update-browserslist-db": "^1.3.3"
2383
2383
  },
2384
2384
  "bin": {
2385
2385
  "browserslist": "cli.js"
@@ -3132,9 +3132,9 @@
3132
3132
  "license": "MIT"
3133
3133
  },
3134
3134
  "node_modules/electron-to-chromium": {
3135
- "version": "1.5.427",
3136
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.427.tgz",
3137
- "integrity": "sha512-n14zb3FdsChZ2BNobqNHAJMcP3ifFv4paox2LvCrfVAQcqGiSURgbJl+PfMpHVCNFkStnNc+RRVtPBTVW5PDgw==",
3135
+ "version": "1.5.429",
3136
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.429.tgz",
3137
+ "integrity": "sha512-/1ENIE3cx4HTIx4IfPZFaOunJmsrSVTnj6coXoRVbiJUbkeTyFkJvBeWGkdgh08OhFbxYLMT1kbkwFVSarq6Ow==",
3138
3138
  "license": "ISC"
3139
3139
  },
3140
3140
  "node_modules/empathic": {
@@ -3365,9 +3365,9 @@
3365
3365
  "license": "MIT"
3366
3366
  },
3367
3367
  "node_modules/fast-uri": {
3368
- "version": "3.1.7",
3369
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
3370
- "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==",
3368
+ "version": "3.1.8",
3369
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz",
3370
+ "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==",
3371
3371
  "funding": [
3372
3372
  {
3373
3373
  "type": "github",