@docslurp/contrib-astro 0.3.0 → 0.4.0
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/DocSlurpSearch.astro +7 -4
- package/README.md +7 -10
- package/build.mjs +6 -2
- package/index-docs.mjs +12 -15
- package/package.json +3 -3
package/DocSlurpSearch.astro
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { assertBrowserSafeApiKey, assertSecureApiOrigin } from "@docslurp/sdk";
|
|
2
3
|
/**
|
|
3
4
|
* Browser key only: restrict it to this site's origin and the search scope.
|
|
4
|
-
*
|
|
5
|
+
* workspace takes an ID, slug or name, or "default" (the key organization's default
|
|
5
6
|
* workspace) and defaults to "default"; apiUrl defaults to https://docslurp.io.
|
|
6
7
|
*/
|
|
7
|
-
interface Props { clientKey: string;
|
|
8
|
-
const { clientKey,
|
|
8
|
+
interface Props { clientKey: string; workspace?: string; apiUrl?: string; placeholder?: string; }
|
|
9
|
+
const { clientKey, workspace = 'default', apiUrl = 'https://docslurp.io', placeholder = 'Search documents…' } = Astro.props;
|
|
10
|
+
assertBrowserSafeApiKey(clientKey, true);
|
|
11
|
+
assertSecureApiOrigin(apiUrl);
|
|
9
12
|
---
|
|
10
|
-
<docslurp-search-box api-url={apiUrl} workspace
|
|
13
|
+
<docslurp-search-box api-url={apiUrl} workspace={workspace} client-key={clientKey} placeholder={placeholder}></docslurp-search-box>
|
|
11
14
|
<noscript><p>Enable JavaScript to search the document collection.</p></noscript>
|
|
12
15
|
<script>
|
|
13
16
|
import { defineDocSlurpSearch } from '@docslurp/sdk/widget';
|
package/README.md
CHANGED
|
@@ -10,13 +10,13 @@ import DocSlurpSearch from "@docslurp/contrib-astro";
|
|
|
10
10
|
---
|
|
11
11
|
<!-- PUBLIC_DOCSLURP_CLIENT_KEY holds a browser key, safe to ship in page HTML. -->
|
|
12
12
|
<DocSlurpSearch clientKey={import.meta.env.PUBLIC_DOCSLURP_CLIENT_KEY} />
|
|
13
|
-
<!-- To search a named workspace: <DocSlurpSearch clientKey={...}
|
|
13
|
+
<!-- To search a named workspace: <DocSlurpSearch clientKey={...} workspace="product-manuals" /> -->
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
| Prop | Default | Notes |
|
|
17
17
|
| --- | --- | --- |
|
|
18
18
|
| `clientKey` | Required | A browser key: search-only and restricted to your allowed websites. Server keys are refused in the browser. |
|
|
19
|
-
| `
|
|
19
|
+
| `workspace` | `default` | A workspace ID, slug (such as `product-manuals`) or name, or `default`, the key organization's default workspace. See [Workspaces](https://docslurp.io/docs/http#workspaces). |
|
|
20
20
|
| `apiUrl` | `https://docslurp.io` | The service origin. |
|
|
21
21
|
| `placeholder` | `Search documents…` | Search box placeholder. |
|
|
22
22
|
|
|
@@ -38,28 +38,25 @@ Your server key is used only during the build. It never ends up in the generated
|
|
|
38
38
|
import docslurpDocsIndex from '@docslurp/contrib-astro/build';
|
|
39
39
|
|
|
40
40
|
integrations: [docslurpDocsIndex({
|
|
41
|
-
routes: ['/docs', '/docs/getting-started'], //
|
|
41
|
+
routes: ['/docs', '/docs/getting-started'], // Every public page to index: lowercase paths under /docs.
|
|
42
42
|
site: 'https://docs.example.com', // Origin used for canonical page URLs.
|
|
43
43
|
apiUrl: 'https://docslurp.io',
|
|
44
44
|
apiKey: process.env.DOCSLURP_API_KEY, // Server key with the ingest scope.
|
|
45
|
-
|
|
46
|
-
// To add to an existing workspace instead: workspaceId: 'existing-workspace-slug',
|
|
45
|
+
workspace: 'Product documentation', // ID, slug or name; created on the first build if none matches.
|
|
47
46
|
})]
|
|
48
47
|
```
|
|
49
48
|
|
|
50
|
-
Without `apiKey`, the build still writes `.docslurp/docs-index.json` and skips the upload. With a key, any indexing failure fails the build. A successful upload writes `docslurp-docs-search.json`
|
|
49
|
+
Without `apiKey`, the build still writes `.docslurp/docs-index.json` and skips the upload. With a key, any indexing failure fails the build. A successful upload writes `docslurp-docs-search.json` (`{ "workspace": "<workspace ID>" }`) to the client output, so your search component can find it. Use `renderEnv` to pass environment variables that only the page renderer needs. `apiUrl` and `site` default to `https://docslurp.io` and `workspace` to `DocSlurp documentation`, so set `site` and `workspace` for your own docs.
|
|
51
50
|
|
|
52
51
|
Pages are indexed as text: the upload asks for `{ visionFallback: false, preserveImages: false, summarize: true }`, so rendered HTML skips page images and vision and gets per-page summaries. Pass `pipeline` with other pipeline overrides to change that.
|
|
53
52
|
|
|
54
53
|
To index a site after it deploys (for example when the build has no API key), crawl the live pages instead:
|
|
55
54
|
|
|
56
55
|
```bash
|
|
57
|
-
DOCSLURP_API_KEY=... DOCSLURP_DOCS_WORKSPACE_ID=<workspace
|
|
56
|
+
DOCSLURP_API_KEY=... DOCSLURP_DOCS_WORKSPACE_ID=<workspace ID, slug or name> \
|
|
58
57
|
node node_modules/@docslurp/contrib-astro/index-docs.mjs --site https://docs.example.com --routes routes.json --output .docslurp/docs-index.json
|
|
59
58
|
```
|
|
60
59
|
|
|
61
|
-
`--routes` is a file or URL with a JSON array of paths. Only those paths on the site's own origin are fetched, and redirects are refused. Elements marked `data-search-exclude` (repeated boilerplate) are left out of the index.
|
|
60
|
+
Set `DOCSLURP_URL` for a self-hosted service (default `https://docslurp.io`). `--routes` is a file or URL with a JSON array of `/docs` paths. Only those paths on the site's own origin are fetched, and redirects are refused. Elements marked `data-search-exclude` (repeated boilerplate) are left out of the index.
|
|
62
61
|
|
|
63
62
|
This integration does not render protected pages, discover dynamic routes, delete documents for removed pages, or support other Astro adapters, so list your public documentation routes explicitly. Keep public documentation in its own organization, and use keys from that organization for both indexing and search.
|
|
64
|
-
|
|
65
|
-
For how DocSlurp indexes its own website, see [Dogfood docs search](https://github.com/DocSlurp/docslurp-oss/blob/main/docs/docs-search.md).
|
package/build.mjs
CHANGED
|
@@ -2,7 +2,11 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
3
|
|
|
4
4
|
/** Index rendered Astro Node standalone pages through DocSlurp's public API. */
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* `workspace`: the workspace ID, slug or name to index into. A name that matches
|
|
7
|
+
* no workspace is created on the first build; later builds find it by name.
|
|
8
|
+
*/
|
|
9
|
+
export default function docslurpDocsIndex({ routes, apiKey, apiUrl = 'https://docslurp.io', workspace = 'DocSlurp documentation', site = 'https://docslurp.io', renderEnv = {}, pipeline }) {
|
|
6
10
|
let root;
|
|
7
11
|
return {
|
|
8
12
|
name: '@docslurp/contrib-astro/docs-index',
|
|
@@ -15,7 +19,7 @@ export default function docslurpDocsIndex({ routes, apiKey, apiUrl = 'https://do
|
|
|
15
19
|
await new Promise((resolve, reject) => {
|
|
16
20
|
const child = spawn(process.execPath, [fileURLToPath(new URL('./index-docs.mjs', import.meta.url)), '--entry', fileURLToPath(entry), '--output', fileURLToPath(output), '--public-config', fileURLToPath(new URL('docslurp-docs-search.json', dir))], {
|
|
17
21
|
cwd: fileURLToPath(root), stdio: 'inherit',
|
|
18
|
-
env: { ...process.env, ...renderEnv, ASTRO_NODE_AUTOSTART: 'disabled', DOCSLURP_API_KEY: apiKey ?? '', DOCSLURP_DOCS_INDEX_OPTIONS: JSON.stringify({ routes, apiUrl,
|
|
22
|
+
env: { ...process.env, ...renderEnv, ASTRO_NODE_AUTOSTART: 'disabled', DOCSLURP_API_KEY: apiKey ?? '', DOCSLURP_DOCS_INDEX_OPTIONS: JSON.stringify({ routes, apiUrl, workspace, site, ...(pipeline ? { pipeline } : {}) }) },
|
|
19
23
|
});
|
|
20
24
|
child.on('error', reject);
|
|
21
25
|
child.on('exit', code => code === 0 ? resolve() : reject(new Error(`DocSlurp docs indexing exited with ${code}`)));
|
package/index-docs.mjs
CHANGED
|
@@ -59,20 +59,18 @@ export async function crawlDocs({ site, routes }, fetchImpl = fetch) {
|
|
|
59
59
|
*/
|
|
60
60
|
export const DEFAULT_DOCS_PIPELINE = Object.freeze({ visionFallback: false, preserveImages: false, summarize: true });
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Uploads the rendered corpus into `workspace` (ID, slug or name): the server
|
|
64
|
+
* finds it, or creates a workspace with that name on the first build, so
|
|
65
|
+
* repeated builds keep landing in the same one.
|
|
66
|
+
*/
|
|
67
|
+
export async function indexDocs(corpus, { apiUrl, workspace = 'DocSlurp documentation', pipeline = DEFAULT_DOCS_PIPELINE }, apiKey, fetchImpl = fetch) {
|
|
63
68
|
const base = apiUrl.replace(/\/$/, '');
|
|
64
69
|
const request = async (path, init = {}) => {
|
|
65
70
|
const response = await fetchImpl(`${base}${path}`, { ...init, headers: { ...init.headers, Authorization: `Bearer ${apiKey}` }, signal: AbortSignal.timeout(60_000) });
|
|
66
71
|
if (!response.ok) throw new Error(`DocSlurp ${path}: HTTP ${response.status}`);
|
|
67
72
|
return response.json();
|
|
68
73
|
};
|
|
69
|
-
let target = workspaceId;
|
|
70
|
-
if (!target) {
|
|
71
|
-
const { workspaces } = await request('/v1/workspaces');
|
|
72
|
-
const matches = workspaces.filter(w => w.name === workspaceName);
|
|
73
|
-
if (matches.length > 1) throw new Error('Multiple docs workspaces: set DOCSLURP_DOCS_WORKSPACE_ID');
|
|
74
|
-
target = matches[0]?.id;
|
|
75
|
-
}
|
|
76
74
|
const form = new FormData();
|
|
77
75
|
const canonicalIds = [];
|
|
78
76
|
for (const doc of corpus) {
|
|
@@ -81,10 +79,9 @@ export async function indexDocs(corpus, { apiUrl, workspaceId, workspaceName, pi
|
|
|
81
79
|
}
|
|
82
80
|
form.set('canonicalIds', JSON.stringify(canonicalIds));
|
|
83
81
|
if (pipeline) form.set('pipeline', JSON.stringify(pipeline));
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const result = await request('/v1/ingest/bulk', { method: 'POST', headers: { 'Idempotency-Key': `docs-${hash}` }, body: form });
|
|
82
|
+
form.set('workspace', workspace);
|
|
83
|
+
const hash = createHash('sha256').update(JSON.stringify({ target: workspace, corpus, pipeline: pipeline ?? null })).digest('hex');
|
|
84
|
+
const result = await request('/v1/uploads/bulk', { method: 'POST', headers: { 'Idempotency-Key': `docs-${hash}` }, body: form });
|
|
88
85
|
const deadline = Date.now() + 10 * 60_000;
|
|
89
86
|
while (Date.now() < deadline) {
|
|
90
87
|
const { run } = await request(`/v1/runs/${encodeURIComponent(result.run.id)}/progress`);
|
|
@@ -98,7 +95,7 @@ export async function indexDocs(corpus, { apiUrl, workspaceId, workspaceName, pi
|
|
|
98
95
|
async function main() {
|
|
99
96
|
const args = process.argv.slice(2);
|
|
100
97
|
const value = name => args[args.indexOf(name) + 1];
|
|
101
|
-
const options = { apiUrl: process.env.DOCSLURP_URL ?? 'https://docslurp.io',
|
|
98
|
+
const options = { apiUrl: process.env.DOCSLURP_URL ?? 'https://docslurp.io', workspace: process.env.DOCSLURP_DOCS_WORKSPACE_ID || process.env.PUBLIC_DOCSLURP_DOCS_WORKSPACE_ID || 'DocSlurp documentation', ...(process.env.DOCSLURP_DOCS_PIPELINE ? { pipeline: JSON.parse(process.env.DOCSLURP_DOCS_PIPELINE) } : {}), ...JSON.parse(process.env.DOCSLURP_DOCS_INDEX_OPTIONS ?? '{}') };
|
|
102
99
|
const output = value('--output');
|
|
103
100
|
let corpus;
|
|
104
101
|
if (args.includes('--entry')) {
|
|
@@ -136,11 +133,11 @@ async function main() {
|
|
|
136
133
|
if (key) {
|
|
137
134
|
const indexed = await indexDocs(corpus, options, key);
|
|
138
135
|
console.log('[docs-index]', indexed);
|
|
139
|
-
if (args.includes('--public-config')) await writeFile(value('--public-config'), JSON.stringify({
|
|
136
|
+
if (args.includes('--public-config')) await writeFile(value('--public-config'), JSON.stringify({ workspace: indexed.workspaceId }));
|
|
140
137
|
}
|
|
141
138
|
else {
|
|
142
139
|
console.log('[docs-index] No API key; remote indexing skipped');
|
|
143
|
-
if (args.includes('--public-config')) await writeFile(value('--public-config'), JSON.stringify({
|
|
140
|
+
if (args.includes('--public-config')) await writeFile(value('--public-config'), JSON.stringify({ workspace: options.workspace ?? null }));
|
|
144
141
|
}
|
|
145
142
|
}
|
|
146
143
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docslurp/contrib-astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Astro search component for DocSlurp cited search, with a framework-free browser widget.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://docslurp.io/docs/packages/contrib-astro",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"index-docs.mjs"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@docslurp/sdk": "^0.
|
|
30
|
+
"@docslurp/sdk": "^0.4.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "pnpm --filter @docslurp/sdk build",
|
|
37
37
|
"typecheck": "pnpm --filter @docslurp/sdk typecheck",
|
|
38
|
-
"test": "node
|
|
38
|
+
"test": "node ../../scripts/node-test.mjs index-docs.test.mjs"
|
|
39
39
|
}
|
|
40
40
|
}
|