jamdesk 1.1.41 → 1.1.42
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/vendored-sync.test.js +4 -0
- package/dist/__tests__/unit/vendored-sync.test.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/vendored/app/[[...slug]]/page.tsx +41 -28
- package/vendored/app/api/isr-health/route.ts +6 -4
- package/vendored/components/mdx/StepSlugContext.tsx +57 -0
- package/vendored/components/mdx/Steps.tsx +2 -2
- package/vendored/components/navigation/TableOfContents.tsx +77 -5
- package/vendored/lib/cache-tags.ts +25 -0
- package/vendored/lib/cache-utils.ts +19 -0
- package/vendored/lib/heading-extractor.ts +25 -6
- package/vendored/lib/indexnow.ts +1 -1
- package/vendored/lib/navigation-resolver.ts +1 -1
- package/vendored/lib/openapi-isr.ts +13 -8
- package/vendored/lib/r2-cleanup.ts +70 -0
- package/vendored/lib/r2-content.ts +0 -24
- package/vendored/lib/r2-manifest.ts +13 -3
- package/vendored/lib/revalidation-helpers.ts +41 -11
- package/vendored/lib/revalidation-trigger.ts +104 -28
- package/vendored/lib/scanner-blocklist.ts +256 -0
- package/vendored/lib/snippet-compiler-isr.ts +5 -2
- package/vendored/scripts/validate-links.cjs +17 -6
- package/vendored/workspace-package-lock.json +9 -9
- package/vendored/lib/cache-keys.ts +0 -117
|
@@ -30,6 +30,10 @@ const SYNCED_FILES = [
|
|
|
30
30
|
// This file MUST be identical - it controls which languages are highlighted
|
|
31
31
|
// and which are passed through to special components like Mermaid
|
|
32
32
|
['build-service/lib/shiki-config.ts', 'cli/vendored/lib/shiki-config.ts'],
|
|
33
|
+
['build-service/lib/cache-tags.ts', 'cli/vendored/lib/cache-tags.ts'],
|
|
34
|
+
['build-service/lib/revalidation-helpers.ts', 'cli/vendored/lib/revalidation-helpers.ts'],
|
|
35
|
+
['build-service/lib/revalidation-trigger.ts', 'cli/vendored/lib/revalidation-trigger.ts'],
|
|
36
|
+
['build-service/lib/r2-manifest.ts', 'cli/vendored/lib/r2-manifest.ts'],
|
|
33
37
|
];
|
|
34
38
|
describe('CLI vendored files sync', () => {
|
|
35
39
|
it.each(SYNCED_FILES)('should have matching content: %s', async (buildServicePath, vendoredPath) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vendored-sync.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/vendored-sync.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE/D;;;;;;;;;GASG;AACH,MAAM,YAAY,GAAuB;IACvC,2EAA2E;IAC3E,4EAA4E;IAC5E,kEAAkE;IAClE,CAAC,mCAAmC,EAAE,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"vendored-sync.test.js","sourceRoot":"","sources":["../../../src/__tests__/unit/vendored-sync.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import { fileURLToPath } from 'url';
|
|
|
19
19
|
import { dirname, resolve } from 'path';
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
21
|
const isLocal = existsSync(resolve(dirname(__filename), '..', 'src'));
|
|
22
|
-
const VERSION = isLocal ? `${pkg.version} (
|
|
22
|
+
const VERSION = isLocal ? `${pkg.version} (development)` : pkg.version;
|
|
23
23
|
const program = new Command();
|
|
24
24
|
program
|
|
25
25
|
.name('jamdesk')
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.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.42",
|
|
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",
|
|
@@ -44,6 +44,8 @@ import { getTypographyRemarkPlugins } from '@/lib/typography-config';
|
|
|
44
44
|
import { remarkVisibility } from '@/lib/remark-visibility';
|
|
45
45
|
import { recmaCompoundComponents } from '@/lib/recma-compound-components';
|
|
46
46
|
import { extractInlineComponents } from '@/lib/process-mdx-with-exports';
|
|
47
|
+
import { extractHeadings } from '@/lib/heading-extractor';
|
|
48
|
+
import { StepSlugProvider, type StepSlugEntry } from '@/components/mdx/StepSlugContext';
|
|
47
49
|
import { buildEndpointFromMdx } from '@/lib/build-endpoint-from-mdx';
|
|
48
50
|
import { mdxSecurityOptions } from '@/lib/mdx-security-options';
|
|
49
51
|
import fs from 'fs';
|
|
@@ -435,6 +437,13 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
435
437
|
// (snippets are injected globally via AllComponents)
|
|
436
438
|
const content = preprocessMdx(rawContent, { assetVersion: config.assetVersion });
|
|
437
439
|
|
|
440
|
+
// Pre-resolve unique slugs for every <Step> on the page so duplicate titles
|
|
441
|
+
// across separate <Steps> blocks get -2, -3, ... suffixes that match the
|
|
442
|
+
// build-time TOC. Provider supplies the list to <Step> via context.
|
|
443
|
+
const stepEntries: StepSlugEntry[] = extractHeadings(content)
|
|
444
|
+
.filter(h => typeof h.stepNumber === 'number')
|
|
445
|
+
.map(h => ({ title: h.text, slug: h.id }));
|
|
446
|
+
|
|
438
447
|
// Extract and compile inline component exports from MDX
|
|
439
448
|
// Only pass MDXComponents (server-compatible) to inline extraction
|
|
440
449
|
const { inlineComponents, paramFields } = await extractInlineComponents(content, MDXComponents);
|
|
@@ -708,21 +717,23 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
708
717
|
{/* Additional MDX content — strip <ResponseExample> on OpenAPI pages
|
|
709
718
|
(auto-generated ResponseExamplePanel already handles responses) */}
|
|
710
719
|
<ImagePriorityProvider>
|
|
711
|
-
<
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
720
|
+
<StepSlugProvider entries={stepEntries}>
|
|
721
|
+
<MDXRemote
|
|
722
|
+
source={openApiEndpointData
|
|
723
|
+
? content.replace(/<ResponseExample>[\s\S]*?<\/ResponseExample>/g, '')
|
|
724
|
+
: content}
|
|
725
|
+
components={AllComponentsWithInline}
|
|
726
|
+
options={{
|
|
727
|
+
// Keep expression props (e.g. cols={2}) compatible under next-mdx-remote v6.
|
|
728
|
+
...mdxSecurityOptions,
|
|
729
|
+
mdxOptions: {
|
|
730
|
+
remarkPlugins: [remarkGfm, [remarkVisibility, { audience: 'humans' }], ...getTypographyRemarkPlugins(config), ...getLatexRemarkPlugins(config)],
|
|
731
|
+
rehypePlugins: [rehypeNoZoomToData, rehypeClassToClassName, rehypeCodeMeta, createShikiRehypePlugin(highlighter, config), rehypeRestoreDataTitle, ...getLatexRehypePlugins(config), rehypeSlug],
|
|
732
|
+
recmaPlugins: [recmaCompoundComponents],
|
|
733
|
+
},
|
|
734
|
+
}}
|
|
735
|
+
/>
|
|
736
|
+
</StepSlugProvider>
|
|
726
737
|
</ImagePriorityProvider>
|
|
727
738
|
</div>
|
|
728
739
|
|
|
@@ -736,19 +747,21 @@ export default async function DocPage({ params }: PageProps) {
|
|
|
736
747
|
|
|
737
748
|
// MDX content for non-API pages (extracted for conditional ViewWrapper wrapping)
|
|
738
749
|
const mdxContent = (
|
|
739
|
-
<
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
750
|
+
<StepSlugProvider entries={stepEntries}>
|
|
751
|
+
<MDXRemote
|
|
752
|
+
source={content}
|
|
753
|
+
components={AllComponentsWithInline}
|
|
754
|
+
options={{
|
|
755
|
+
// Keep expression props (e.g. cols={2}) compatible under next-mdx-remote v6.
|
|
756
|
+
...mdxSecurityOptions,
|
|
757
|
+
mdxOptions: {
|
|
758
|
+
remarkPlugins: [remarkGfm, [remarkVisibility, { audience: 'humans' }], ...getTypographyRemarkPlugins(config), ...getLatexRemarkPlugins(config)],
|
|
759
|
+
rehypePlugins: [rehypeNoZoomToData, rehypeClassToClassName, rehypeCodeMeta, createShikiRehypePlugin(highlighter, config), rehypeRestoreDataTitle, ...getLatexRehypePlugins(config), rehypeSlug],
|
|
760
|
+
recmaPlugins: [recmaCompoundComponents],
|
|
761
|
+
},
|
|
762
|
+
}}
|
|
763
|
+
/>
|
|
764
|
+
</StepSlugProvider>
|
|
752
765
|
);
|
|
753
766
|
|
|
754
767
|
// Shared article content for non-API pages
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { NextResponse } from 'next/server';
|
|
9
|
-
import { getConfigCacheSize } from '@/lib/r2-content';
|
|
10
9
|
import { getSnippetCacheSize } from '@/lib/snippet-compiler-isr';
|
|
11
10
|
import { getOpenApiCacheSize } from '@/lib/openapi-isr';
|
|
12
11
|
import { isIsrMode } from '@/lib/page-isr-helpers';
|
|
@@ -14,7 +13,9 @@ import { isIsrMode } from '@/lib/page-isr-helpers';
|
|
|
14
13
|
interface HealthCheck {
|
|
15
14
|
status: 'ok' | 'degraded' | 'unhealthy';
|
|
16
15
|
cache: {
|
|
17
|
-
configEntries
|
|
16
|
+
// configEntries removed — config is now in Next.js Data Cache (unstable_cache),
|
|
17
|
+
// which is opaque from inside the lambda. Use cacheLayer field instead.
|
|
18
|
+
cacheLayer: string;
|
|
18
19
|
snippetEntries: number;
|
|
19
20
|
openApiEntries: number;
|
|
20
21
|
};
|
|
@@ -40,9 +41,10 @@ export async function GET() {
|
|
|
40
41
|
heapTotalMB: Math.round(memUsage.heapTotal / 1024 / 1024),
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
// Cache stats
|
|
44
|
+
// Cache stats — config/mdx/snippet/openapi raw content now in unstable_cache (opaque).
|
|
45
|
+
// snippetEntries and openApiEntries reflect the in-memory parsed-object layers only.
|
|
44
46
|
const cache = {
|
|
45
|
-
|
|
47
|
+
cacheLayer: 'unstable_cache',
|
|
46
48
|
snippetEntries: getSnippetCacheSize(),
|
|
47
49
|
openApiEntries: getOpenApiCacheSize(),
|
|
48
50
|
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useRef, ReactNode } from 'react';
|
|
4
|
+
import { generateSlug } from '@/lib/heading-extractor';
|
|
5
|
+
|
|
6
|
+
export interface StepSlugEntry {
|
|
7
|
+
title: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface StepSlugContextValue {
|
|
12
|
+
// Per-title counter — advances each time useStepSlug is called for that title.
|
|
13
|
+
counts: Map<string, number>;
|
|
14
|
+
entries: StepSlugEntry[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const StepSlugCtx = createContext<StepSlugContextValue | null>(null);
|
|
18
|
+
|
|
19
|
+
export function StepSlugProvider({
|
|
20
|
+
entries,
|
|
21
|
+
children,
|
|
22
|
+
}: {
|
|
23
|
+
entries: StepSlugEntry[];
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}) {
|
|
26
|
+
// useRef so the same Map persists across renders. We rely on the same
|
|
27
|
+
// contract React's own useId rests on: source-order rendering during SSR
|
|
28
|
+
// and during client hydration produces matching ids.
|
|
29
|
+
const ref = useRef<StepSlugContextValue | null>(null);
|
|
30
|
+
if (ref.current === null || ref.current.entries !== entries) {
|
|
31
|
+
ref.current = { counts: new Map(), entries };
|
|
32
|
+
}
|
|
33
|
+
return <StepSlugCtx.Provider value={ref.current}>{children}</StepSlugCtx.Provider>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the unique slug for a Step with this title. Each call advances the
|
|
38
|
+
* per-title counter, so two Steps with the same title get sequential slugs
|
|
39
|
+
* from the provider's `entries` list. Falls back to `generateSlug(title)` if
|
|
40
|
+
* no provider is mounted (preview/storybook/etc).
|
|
41
|
+
*/
|
|
42
|
+
export function useStepSlug(title: string | undefined): string | undefined {
|
|
43
|
+
const ctx = useContext(StepSlugCtx);
|
|
44
|
+
if (!title) return undefined;
|
|
45
|
+
if (!ctx) return generateSlug(title) || undefined;
|
|
46
|
+
|
|
47
|
+
const idx = ctx.counts.get(title) ?? 0;
|
|
48
|
+
ctx.counts.set(title, idx + 1);
|
|
49
|
+
|
|
50
|
+
let seen = 0;
|
|
51
|
+
for (const entry of ctx.entries) {
|
|
52
|
+
if (entry.title !== title) continue;
|
|
53
|
+
if (seen === idx) return entry.slug;
|
|
54
|
+
seen += 1;
|
|
55
|
+
}
|
|
56
|
+
return generateSlug(title) || undefined;
|
|
57
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { ReactNode, Children, cloneElement, isValidElement, memo } from 'react';
|
|
4
4
|
import { getIconClass } from '@/lib/icon-utils';
|
|
5
|
-
import {
|
|
5
|
+
import { useStepSlug } from './StepSlugContext';
|
|
6
6
|
|
|
7
7
|
type TitleSize = 'p' | 'h2' | 'h3';
|
|
8
8
|
type IconType = 'regular' | 'solid' | 'light' | 'thin' | 'sharp-solid' | 'duotone' | 'brands';
|
|
@@ -130,7 +130,7 @@ function StepTitle({ title, titleSize }: { title: string; titleSize: TitleSize }
|
|
|
130
130
|
|
|
131
131
|
export const Step = memo(function Step({ title, children, stepNumber, isLast, icon, iconType, titleSize = 'p' }: StepProps) {
|
|
132
132
|
const containerClassName = isLast ? 'relative pb-0' : 'relative pb-8';
|
|
133
|
-
const slug =
|
|
133
|
+
const slug = useStepSlug(title);
|
|
134
134
|
|
|
135
135
|
// Emit both attrs together or neither — the DOM scanner keys off
|
|
136
136
|
// `data-step-number`, so a label without a number would be a dangling
|
|
@@ -53,6 +53,31 @@ function getOffsetRelativeTo(element: HTMLElement, container: HTMLElement): numb
|
|
|
53
53
|
return offset;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// Breathing-room buffer at top/bottom edges of the TOC scroll container so
|
|
57
|
+
// the active item doesn't sit flush against the edge after auto-scrolling.
|
|
58
|
+
// 80px lines up with the TOC's `py-6 sm:py-10` outer padding (PageColumns.tsx
|
|
59
|
+
// wrapper); update both together if that padding changes.
|
|
60
|
+
const TOC_SCROLL_PAD = 80;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Walk up from `start` looking for the nearest ancestor whose computed
|
|
64
|
+
* overflow-y is `auto` or `scroll`. Returns null if none is found before
|
|
65
|
+
* reaching <body>. The TOC lives inside `<aside class="toc-scroll">` which
|
|
66
|
+
* has `xl:overflow-y-auto`; this helper finds it without hard-coding a
|
|
67
|
+
* selector so the component remains reusable in other layouts.
|
|
68
|
+
*/
|
|
69
|
+
function findScrollableAncestor(start: HTMLElement): HTMLElement | null {
|
|
70
|
+
let el: HTMLElement | null = start.parentElement;
|
|
71
|
+
while (el && el !== document.body) {
|
|
72
|
+
const cs = getComputedStyle(el);
|
|
73
|
+
if (cs.overflowY === 'auto' || cs.overflowY === 'scroll') {
|
|
74
|
+
return el;
|
|
75
|
+
}
|
|
76
|
+
el = el.parentElement;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
56
81
|
/**
|
|
57
82
|
* Calculate the thumb position (top + height) spanning all active anchors.
|
|
58
83
|
*/
|
|
@@ -105,6 +130,53 @@ export function TableOfContents({ content, className = '' }: TableOfContentsProp
|
|
|
105
130
|
updateThumb();
|
|
106
131
|
}, [updateThumb]);
|
|
107
132
|
|
|
133
|
+
// Keep the active TOC item visible inside the TOC's own scroll container
|
|
134
|
+
// (the right-side `<aside class="toc-scroll">` is independent of the center
|
|
135
|
+
// content scroll, so it doesn't follow the user's reading position on its
|
|
136
|
+
// own). When `activeAnchors` changes, find the first active link, locate
|
|
137
|
+
// the nearest scrollable ancestor, and nudge ONLY that ancestor's scrollTop
|
|
138
|
+
// — never `scrollIntoView`, which would also scroll <main>/window and yank
|
|
139
|
+
// the user's reading position in the center column.
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (activeAnchors.length === 0) return;
|
|
142
|
+
const container = containerRef.current;
|
|
143
|
+
if (!container) return;
|
|
144
|
+
|
|
145
|
+
const firstActiveId = activeAnchors[0];
|
|
146
|
+
const link = container.querySelector<HTMLElement>(
|
|
147
|
+
`a[href="#${firstActiveId}"]`,
|
|
148
|
+
);
|
|
149
|
+
if (!link) return;
|
|
150
|
+
|
|
151
|
+
const scroller = findScrollableAncestor(container);
|
|
152
|
+
if (!scroller) return;
|
|
153
|
+
|
|
154
|
+
const linkRect = link.getBoundingClientRect();
|
|
155
|
+
// Bail out if the link hasn't laid out yet. Two real cases this catches:
|
|
156
|
+
// 1. First commit before the browser has measured boxes (or jsdom's
|
|
157
|
+
// default 0×0 rect in unit tests).
|
|
158
|
+
// 2. Sub-xl breakpoints where the TOC's parent <aside class="hidden
|
|
159
|
+
// xl:block"> is `display: none` — TableOfContents is still mounted
|
|
160
|
+
// but every descendant returns 0×0 rects. Without this guard, the
|
|
161
|
+
// ancestor walker below would pick `<main>` (which has
|
|
162
|
+
// overflow-y:auto in our layout) and scroll the page itself.
|
|
163
|
+
if (linkRect.width === 0 && linkRect.height === 0) return;
|
|
164
|
+
|
|
165
|
+
const scrollerRect = scroller.getBoundingClientRect();
|
|
166
|
+
// If the scroller is shorter than 2× the padding, the "already visible"
|
|
167
|
+
// window `[top + pad, bottom - pad]` collapses to nothing and both branches
|
|
168
|
+
// below would fire on alternating ticks, oscillating scrollTop. Bail out
|
|
169
|
+
// — the scroller is too short to position usefully anyway. Reachable on
|
|
170
|
+
// 200%-zoomed iframe-embedded docs viewers (~220px aside).
|
|
171
|
+
if (scrollerRect.height < TOC_SCROLL_PAD * 2) return;
|
|
172
|
+
|
|
173
|
+
if (linkRect.top < scrollerRect.top + TOC_SCROLL_PAD) {
|
|
174
|
+
scroller.scrollTop += linkRect.top - scrollerRect.top - TOC_SCROLL_PAD;
|
|
175
|
+
} else if (linkRect.bottom > scrollerRect.bottom - TOC_SCROLL_PAD) {
|
|
176
|
+
scroller.scrollTop += linkRect.bottom - scrollerRect.bottom + TOC_SCROLL_PAD;
|
|
177
|
+
}
|
|
178
|
+
}, [activeAnchors]);
|
|
179
|
+
|
|
108
180
|
// Regenerate SVG path on headings change + resize
|
|
109
181
|
useEffect(() => {
|
|
110
182
|
const container = containerRef.current;
|
|
@@ -495,7 +567,7 @@ export function TableOfContents({ content, className = '' }: TableOfContentsProp
|
|
|
495
567
|
}
|
|
496
568
|
};
|
|
497
569
|
|
|
498
|
-
// 30px leaves room for the absolute-positioned
|
|
570
|
+
// 30px leaves room for the absolute-positioned 14px step circle
|
|
499
571
|
// (insetInlineStart: 1) + a gap. Non-step H3s use the same value
|
|
500
572
|
// so mixed step/non-step groups align visually.
|
|
501
573
|
const startOffset = heading.level === 3
|
|
@@ -518,13 +590,13 @@ export function TableOfContents({ content, className = '' }: TableOfContentsProp
|
|
|
518
590
|
{hasStepNumber && (
|
|
519
591
|
<span
|
|
520
592
|
aria-hidden="true"
|
|
521
|
-
className="absolute flex items-center justify-center rounded-full text-[
|
|
593
|
+
className="absolute flex items-center justify-center rounded-full text-[9px] font-medium"
|
|
522
594
|
style={{
|
|
523
|
-
insetInlineStart:
|
|
595
|
+
insetInlineStart: 3,
|
|
524
596
|
top: '50%',
|
|
525
597
|
transform: 'translateY(-50%)',
|
|
526
|
-
width:
|
|
527
|
-
height:
|
|
598
|
+
width: 14,
|
|
599
|
+
height: 14,
|
|
528
600
|
backgroundColor: isActive
|
|
529
601
|
? 'var(--color-primary)'
|
|
530
602
|
: 'var(--color-bg-primary)',
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache tag helpers for unstable_cache + revalidateTag (Next.js Data Cache).
|
|
3
|
+
*
|
|
4
|
+
* Tag shape (keep stable — referenced by /api/revalidate callers in
|
|
5
|
+
* build-service Cloud Run and by revalidation-helpers.ts invalidators):
|
|
6
|
+
*
|
|
7
|
+
* project:<slug> invalidates ALL data for a project
|
|
8
|
+
* config:<slug> invalidates only the docs.json config
|
|
9
|
+
* mdx:<slug>:<path> invalidates one page
|
|
10
|
+
* snippet:<slug>:<path> invalidates one snippet
|
|
11
|
+
* openapi:<slug>:<path> invalidates one OpenAPI spec
|
|
12
|
+
* manifest:<slug> invalidates the project path manifest
|
|
13
|
+
* static:<slug>:<filename> invalidates one static file (custom.css, custom.js)
|
|
14
|
+
*
|
|
15
|
+
* Not related to the `project-<slug>` hyphen-tagged CDN header set by
|
|
16
|
+
* app/api/r2/[project]/[...path]/route.ts — that's a separate edge-cache
|
|
17
|
+
* namespace on the R2 binary route and does not intersect with these.
|
|
18
|
+
*/
|
|
19
|
+
export const projectCacheTag = (slug: string): string => `project:${slug}`;
|
|
20
|
+
export const configCacheTag = (slug: string): string => `config:${slug}`;
|
|
21
|
+
export const mdxCacheTag = (slug: string, path: string): string => `mdx:${slug}:${path}`;
|
|
22
|
+
export const snippetCacheTag = (slug: string, path: string): string => `snippet:${slug}:${path}`;
|
|
23
|
+
export const openapiCacheTag = (slug: string, path: string): string => `openapi:${slug}:${path}`;
|
|
24
|
+
export const manifestCacheTag = (slug: string): string => `manifest:${slug}`;
|
|
25
|
+
export const staticCacheTag = (slug: string, filename: string): string => `static:${slug}:${filename}`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure in-memory cache helpers. Kept in a standalone module so client
|
|
3
|
+
* bundles can import it without pulling in server-only `next/cache` APIs
|
|
4
|
+
* via r2-content.ts (Turbopack rejects transitive imports of
|
|
5
|
+
* `revalidateTag` into client components).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export function evictOldest<K, V extends { timestamp: number }>(
|
|
9
|
+
cache: Map<K, V>,
|
|
10
|
+
maxSize: number
|
|
11
|
+
): void {
|
|
12
|
+
if (cache.size <= maxSize) return;
|
|
13
|
+
|
|
14
|
+
const entries = [...cache.entries()].sort((a, b) => a[1].timestamp - b[1].timestamp);
|
|
15
|
+
const toDelete = entries.slice(0, cache.size - maxSize);
|
|
16
|
+
for (const [key] of toDelete) {
|
|
17
|
+
cache.delete(key);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -26,6 +26,19 @@ export function generateSlug(text: string): string {
|
|
|
26
26
|
.replace(/^-+|-+$/g, '');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Suffix `base` with -2, -3, ... if it has been seen before. First occurrence
|
|
31
|
+
* keeps the bare slug for backwards compatibility with existing inbound links.
|
|
32
|
+
* Mutates `seen` in place — caller owns the lifetime (one Map per page).
|
|
33
|
+
*
|
|
34
|
+
* Mirrored in scripts/validate-links.cjs — keep both in sync.
|
|
35
|
+
*/
|
|
36
|
+
export function uniquifySlug(seen: Map<string, number>, base: string): string {
|
|
37
|
+
const count = seen.get(base) ?? 0;
|
|
38
|
+
seen.set(base, count + 1);
|
|
39
|
+
return count === 0 ? base : `${base}-${count + 1}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
const HEADING_REGEX = /^(#{1,6})\s+(.+)$/;
|
|
30
43
|
const FENCE_REGEX = /^(`{3,}|~{3,})/;
|
|
31
44
|
const UPDATE_LABEL_REGEX = /<Update\s+label=["']([^"']+)["']/;
|
|
@@ -54,6 +67,9 @@ export function extractHeadings(content: string): HeadingInfo[] {
|
|
|
54
67
|
// block can't stick the counter across subsequent blocks.
|
|
55
68
|
let inStepsBlock = false;
|
|
56
69
|
let stepCounter = 0;
|
|
70
|
+
// Page-scoped slug counter so duplicate titles (across H2s, Update labels,
|
|
71
|
+
// and Steps) get -2, -3, ... suffixes in source order.
|
|
72
|
+
const seenSlugs = new Map<string, number>();
|
|
57
73
|
|
|
58
74
|
for (let i = 0; i < lines.length; i++) {
|
|
59
75
|
const line = lines[i];
|
|
@@ -85,8 +101,9 @@ export function extractHeadings(content: string): HeadingInfo[] {
|
|
|
85
101
|
if (headingMatch) {
|
|
86
102
|
const level = headingMatch[1].length;
|
|
87
103
|
const text = headingMatch[2].trim();
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
104
|
+
const base = generateSlug(text);
|
|
105
|
+
if (base) {
|
|
106
|
+
const id = uniquifySlug(seenSlugs, base);
|
|
90
107
|
headings.push({ id, text, level, line: i + 1 });
|
|
91
108
|
}
|
|
92
109
|
}
|
|
@@ -94,8 +111,9 @@ export function extractHeadings(content: string): HeadingInfo[] {
|
|
|
94
111
|
const updateMatch = line.match(UPDATE_LABEL_REGEX);
|
|
95
112
|
if (updateMatch) {
|
|
96
113
|
const text = updateMatch[1];
|
|
97
|
-
const
|
|
98
|
-
if (
|
|
114
|
+
const base = generateSlug(text);
|
|
115
|
+
if (base) {
|
|
116
|
+
const id = uniquifySlug(seenSlugs, base);
|
|
99
117
|
headings.push({ id, text, level: 2, line: i + 1 });
|
|
100
118
|
}
|
|
101
119
|
}
|
|
@@ -103,9 +121,10 @@ export function extractHeadings(content: string): HeadingInfo[] {
|
|
|
103
121
|
if (inStepsBlock) {
|
|
104
122
|
for (const match of line.matchAll(STEP_TITLE_REGEX)) {
|
|
105
123
|
const text = match[1] ?? match[2];
|
|
106
|
-
const
|
|
107
|
-
if (!
|
|
124
|
+
const base = generateSlug(text);
|
|
125
|
+
if (!base) continue;
|
|
108
126
|
stepCounter += 1;
|
|
127
|
+
const id = uniquifySlug(seenSlugs, base);
|
|
109
128
|
headings.push({
|
|
110
129
|
id,
|
|
111
130
|
text,
|
package/vendored/lib/indexnow.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function buildChangedUrls(
|
|
|
35
35
|
if (changedPaths.length === 0) return [];
|
|
36
36
|
const prefix = hostAtDocs ? '/docs' : '';
|
|
37
37
|
return changedPaths.map((p) => {
|
|
38
|
-
// Defensive:
|
|
38
|
+
// Defensive: callers strip the .mdx extension, but guard against raw file paths
|
|
39
39
|
const clean = p.replace(/\.mdx?$/, '');
|
|
40
40
|
return `${baseUrl}${prefix}/${clean}`;
|
|
41
41
|
});
|
|
@@ -22,7 +22,7 @@ import type {
|
|
|
22
22
|
|
|
23
23
|
import { normalizeNavPage, getIconName } from './docs-types';
|
|
24
24
|
import { getLanguageDisplayInfo, extractLanguageFromPath } from './language-utils';
|
|
25
|
-
import { evictOldest } from './
|
|
25
|
+
import { evictOldest } from './cache-utils';
|
|
26
26
|
|
|
27
27
|
// =============================================================================
|
|
28
28
|
// TYPE GUARDS
|
|
@@ -94,16 +94,21 @@ export async function resolveOpenApiSpec(
|
|
|
94
94
|
return fetchOpenApiSpecFromR2(projectSlug, specRef);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Clear the in-memory parsed-spec Map (covers external-URL specs not wrapped
|
|
99
|
+
* in unstable_cache). Does NOT emit a project: revalidateTag — that would
|
|
100
|
+
* discard config/mdx/snippet/static entries unrelated to OpenAPI; the caller
|
|
101
|
+
* (executeRevalidation) dispatches the project tag explicitly when needed.
|
|
102
|
+
*/
|
|
98
103
|
export function clearOpenApiCache(projectSlug?: string): void {
|
|
99
|
-
if (projectSlug) {
|
|
100
|
-
for (const key of specCache.keys()) {
|
|
101
|
-
if (key.startsWith(`r2:${projectSlug}:`)) {
|
|
102
|
-
specCache.delete(key);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
104
|
+
if (!projectSlug) {
|
|
106
105
|
specCache.clear();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const key of specCache.keys()) {
|
|
109
|
+
if (key.startsWith(`r2:${projectSlug}:`)) {
|
|
110
|
+
specCache.delete(key);
|
|
111
|
+
}
|
|
107
112
|
}
|
|
108
113
|
}
|
|
109
114
|
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delete R2 keys that existed in the previous manifest but are absent from
|
|
3
|
+
* the new one. Without this, removed `custom.css`, `custom.js`, or asset
|
|
4
|
+
* bytes would linger and continue to be served after the customer dropped
|
|
5
|
+
* them — `revalidateTag('project:<slug>')` only flushes the parsed cache,
|
|
6
|
+
* it doesn't touch the underlying R2 objects.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { DeleteObjectCommand } from '@aws-sdk/client-s3';
|
|
10
|
+
import { getR2S3Client, getR2Config } from './r2.js';
|
|
11
|
+
import { logger } from '../shared/logger.js';
|
|
12
|
+
|
|
13
|
+
interface HashEntry {
|
|
14
|
+
hash: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CleanupResult {
|
|
18
|
+
deleted: string[];
|
|
19
|
+
failed: string[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Diff old vs new keys, delete removed entries from R2 under the given prefix.
|
|
24
|
+
* Failures are non-fatal — the next build will re-attempt the deletion.
|
|
25
|
+
*
|
|
26
|
+
* @param projectSlug Project namespace under the bucket
|
|
27
|
+
* @param oldEntries Hash map from the previous manifest (may be undefined)
|
|
28
|
+
* @param newEntries Hash map from the new manifest (may be undefined)
|
|
29
|
+
* @param keyPrefix Path under {slug}/ — empty for root files (custom.css),
|
|
30
|
+
* "assets/" for asset uploads
|
|
31
|
+
*/
|
|
32
|
+
export async function deleteRemovedR2Objects(
|
|
33
|
+
projectSlug: string,
|
|
34
|
+
oldEntries: Record<string, HashEntry> | undefined,
|
|
35
|
+
newEntries: Record<string, HashEntry> | undefined,
|
|
36
|
+
keyPrefix: string,
|
|
37
|
+
): Promise<CleanupResult> {
|
|
38
|
+
const oldKeys = Object.keys(oldEntries || {});
|
|
39
|
+
if (oldKeys.length === 0) return { deleted: [], failed: [] };
|
|
40
|
+
|
|
41
|
+
const newSet = new Set(Object.keys(newEntries || {}));
|
|
42
|
+
const removed = oldKeys.filter((k) => !newSet.has(k));
|
|
43
|
+
if (removed.length === 0) return { deleted: [], failed: [] };
|
|
44
|
+
|
|
45
|
+
const client = getR2S3Client();
|
|
46
|
+
const { bucketName } = getR2Config();
|
|
47
|
+
const deleted: string[] = [];
|
|
48
|
+
const failed: string[] = [];
|
|
49
|
+
|
|
50
|
+
await Promise.all(
|
|
51
|
+
removed.map(async (relKey) => {
|
|
52
|
+
const fullKey = `${projectSlug}/${keyPrefix}${relKey}`;
|
|
53
|
+
try {
|
|
54
|
+
await client.send(
|
|
55
|
+
new DeleteObjectCommand({ Bucket: bucketName, Key: fullKey }),
|
|
56
|
+
);
|
|
57
|
+
deleted.push(fullKey);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
failed.push(fullKey);
|
|
60
|
+
logger.warn('R2 delete failed (non-fatal)', {
|
|
61
|
+
projectSlug,
|
|
62
|
+
key: fullKey,
|
|
63
|
+
error: (err as Error).message,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return { deleted, failed };
|
|
70
|
+
}
|