@stainless-api/docs 0.1.0-beta.52 → 0.1.0-beta.53

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/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @stainless-api/docs
2
2
 
3
+ ## 0.1.0-beta.53
4
+
5
+ ### Minor Changes
6
+
7
+ - 2b0fec9: Added prose indexing for search
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [2b0fec9]
12
+ - @stainless-api/docs-ui@0.1.0-beta.44
13
+
3
14
  ## 0.1.0-beta.52
4
15
 
5
16
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.52",
3
+ "version": "0.1.0-beta.53",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -51,8 +51,8 @@
51
51
  "unified": "^11.0.5",
52
52
  "web-worker": "^1.5.0",
53
53
  "yaml": "^2.8.2",
54
- "@stainless-api/docs-ui": "0.1.0-beta.43",
55
- "@stainless-api/ui-primitives": "0.1.0-beta.32"
54
+ "@stainless-api/ui-primitives": "0.1.0-beta.32",
55
+ "@stainless-api/docs-ui": "0.1.0-beta.44"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@astrojs/check": "^0.9.6",
@@ -66,8 +66,8 @@
66
66
  "typescript": "5.9.3",
67
67
  "vite": "^6.4.1",
68
68
  "zod": "^4.1.13",
69
- "@stainless/eslint-config": "0.1.0-beta.0",
70
- "@stainless/sdk-json": "^0.1.0-beta.0"
69
+ "@stainless/sdk-json": "^0.1.0-beta.0",
70
+ "@stainless/eslint-config": "0.1.0-beta.0"
71
71
  },
72
72
  "scripts": {
73
73
  "vendor-deps": "tsx scripts/vendor_deps.ts",
package/stl-docs/index.ts CHANGED
@@ -22,6 +22,7 @@ import type * as StlDocsVirtualModule from 'virtual:stl-docs-virtual-module';
22
22
  import { resolveSrcFile } from '../resolveSrcFile';
23
23
  import { stainlessDocsMarkdownRenderer } from './proseMarkdown/proseMarkdownIntegration';
24
24
  import { setSharedLogger } from '../shared/getSharedLogger';
25
+ import { stainlessDocsProseIndexing } from './proseSearchIndexing';
25
26
 
26
27
  export * from '../plugin';
27
28
 
@@ -242,5 +243,6 @@ export function stainlessDocs(config: StainlessDocsUserConfig) {
242
243
  stainlessDocsStarlightIntegration(normalizedConfig),
243
244
  stainlessDocsIntegration(normalizedConfig, apiReferenceBasePath),
244
245
  stainlessDocsMarkdownRenderer({ enabled: normalizedConfig.enableProseMarkdownRendering }),
246
+ stainlessDocsProseIndexing(),
245
247
  ];
246
248
  }
@@ -0,0 +1,113 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ import { readFile } from 'fs/promises';
3
+ import { getSharedLogger } from '../shared/getSharedLogger';
4
+ import { bold } from '../shared/terminalUtils';
5
+ import { buildProseIndex } from '@stainless-api/docs-ui/search/providers/algolia';
6
+ import * as cheerio from 'cheerio';
7
+
8
+ function chunkByWords(content: string, chunkSize: number = 30000, chunkOverlap: number = 10) {
9
+ if (Buffer.byteLength(content) < chunkSize) return [content];
10
+
11
+ const words = content.split(/\s+/);
12
+ const chunks: string[] = [];
13
+
14
+ let currentChunk: string[] = [];
15
+ let currentSize = 0;
16
+
17
+ for (const word of words) {
18
+ const wordSize = Buffer.byteLength(word + ' ', 'utf-8');
19
+
20
+ if (currentSize + wordSize > chunkSize && currentChunk.length > 0) {
21
+ chunks.push(currentChunk.join(' '));
22
+
23
+ const overlapStart = Math.max(0, currentChunk.length - chunkOverlap);
24
+ currentChunk = currentChunk.slice(overlapStart);
25
+ currentSize = Buffer.byteLength(currentChunk.join(' '), 'utf-8');
26
+ }
27
+
28
+ currentChunk.push(word);
29
+ currentSize += wordSize;
30
+ }
31
+
32
+ if (currentChunk.length > 0) {
33
+ chunks.push(currentChunk.join(' '));
34
+ }
35
+
36
+ return chunks;
37
+ }
38
+
39
+ export function* indexHTML(content: string, root: string, pattern: string) {
40
+ const $ = cheerio.load(content);
41
+ const matches = $(root).find(pattern);
42
+
43
+ for (const match of matches) {
44
+ const rawText = $(match).text().trim();
45
+ const chunks = chunkByWords(rawText);
46
+ const chunkId = crypto.randomUUID();
47
+
48
+ for (const [chunkN, content] of chunks.entries()) {
49
+ yield {
50
+ id: $(match).attr('id'),
51
+ tag: match.tagName.toLowerCase(),
52
+ content,
53
+ chunk: {
54
+ id: chunkId,
55
+ index: chunkN,
56
+ total: chunks.length,
57
+ },
58
+ };
59
+ }
60
+ }
61
+ }
62
+
63
+ const root = 'main';
64
+ const pattern = 'h1, h2, h3, h4, h5, h6, p, li';
65
+
66
+ export function stainlessDocsProseIndexing(): AstroIntegration {
67
+ return {
68
+ name: 'stl-docs-prose-indexing',
69
+ hooks: {
70
+ 'astro:build:done': async ({ assets, logger: localLogger, dir }) => {
71
+ const logger = getSharedLogger({ fallback: localLogger });
72
+ const outputBasePath = dir.pathname;
73
+
74
+ const {
75
+ PUBLIC_ALGOLIA_APP_ID: appId,
76
+ PUBLIC_ALGOLIA_INDEX: indexName,
77
+ PRIVATE_ALGOLIA_WRITE_KEY: algoliaWriteKey,
78
+ } = process.env;
79
+
80
+ if (!appId || !indexName || !algoliaWriteKey) {
81
+ logger.info('Skipping algolia indexing due to missing environment variables');
82
+ return;
83
+ }
84
+
85
+ const starlightPagePatterns = ['/[...slug]'];
86
+ const pagesToRender = Array.from(assets.entries())
87
+ .filter(([k]) => starlightPagePatterns.includes(k))
88
+ .map(([, v]) => v)
89
+ .flat()
90
+ .map((v) => v.pathname);
91
+
92
+ logger.info(bold(`Indexing ${pagesToRender.length} prose pages for search`));
93
+
94
+ const objects = [];
95
+ for (const absHtmlPath of pagesToRender) {
96
+ const content = await readFile(absHtmlPath, 'utf-8');
97
+ const idx = indexHTML(content, root, pattern);
98
+ for (const entry of idx)
99
+ objects.push({
100
+ ...entry,
101
+ source: absHtmlPath.slice(outputBasePath.length),
102
+ });
103
+ }
104
+
105
+ try {
106
+ await buildProseIndex(appId, `${indexName}-prose`, algoliaWriteKey, objects);
107
+ } catch (err) {
108
+ logger.error(`Failed to index prose content: ${err}`);
109
+ }
110
+ },
111
+ },
112
+ };
113
+ }