@tryghost/algolia-netlify 0.3.3 → 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/LICENSE +1 -1
- package/README.md +61 -60
- package/dist/functions/post-published.d.mts +1 -0
- package/dist/functions/post-published.mjs +80 -0
- package/dist/functions/post-unpublished.d.mts +1 -0
- package/dist/functions/post-unpublished.mjs +81 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +6 -0
- package/package.json +55 -23
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,94 +1,95 @@
|
|
|
1
1
|
# Algolia Netlify
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@tryghost/algolia-netlify` provides Netlify Functions that listen to Ghost post webhooks and update an Algolia search index.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
### Set up Algolia
|
|
5
|
+
The public package exports the native Request/Response handlers from its root and from explicit subpaths:
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
```js
|
|
8
|
+
import {postPublished, postUnpublished} from '@tryghost/algolia-netlify';
|
|
9
|
+
import publishedHandler from '@tryghost/algolia-netlify/post-published';
|
|
10
|
+
import unpublishedHandler from '@tryghost/algolia-netlify/post-unpublished';
|
|
11
|
+
```
|
|
10
12
|
|
|
11
|
-
The
|
|
13
|
+
The named and subpath exports reference the same handlers. Consumers can wrap either subpath in a basename-preserving Netlify Function entry; this repository deploys the canonical `post-published` and `post-unpublished` entries directly.
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
## Security
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- Delete records (`deleteObject`)
|
|
18
|
-
- List indexes (`listIndexes`)
|
|
19
|
-
- Delete index (`deleteIndex`)
|
|
17
|
+
> [!WARNING]
|
|
18
|
+
> The current handlers do not enforce authentication when the `key` query parameter is omitted; they reject only a supplied key that does not match `NETLIFY_KEY`. Netlify Function URLs are public endpoints, not secrets, and the user-agent check can be spoofed. Pause or restrict exposure outside the handlers until mandatory authentication is implemented before public use.
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
Use a restricted Algolia API key to limit the impact of unauthorized requests.
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
## Usage
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
[](https://app.netlify.com/start/deploy?repository=https://github.com/TryGhost/algolia)
|
|
27
|
-
2. Click 'Connect to Github' and give Netlify permission
|
|
28
|
-
3. Configure your site
|
|
29
|
-
- Choose a repository name
|
|
30
|
-
- Set 'TRUE' to trigger indexing
|
|
31
|
-
- Algolia Application ID
|
|
32
|
-
- The Algolia Admin API key or and API key with the permissions as described above
|
|
33
|
-
- The name of the index you want to use
|
|
34
|
-
- Set the `NETLIFY_KEY` to be used with the target URL
|
|
24
|
+
### Set up Algolia
|
|
35
25
|
|
|
36
|
-
|
|
26
|
+
The functions need the Algolia Application ID and an API key that can update the target index. The general Admin API key works, but a key scoped to this index is preferable. A scoped key needs these permissions:
|
|
37
27
|
|
|
38
|
-
|
|
28
|
+
- Add records (`addObject`)
|
|
29
|
+
- Delete records matching a filter (`deleteIndex`)
|
|
30
|
+
- Get index settings (`settings`)
|
|
31
|
+
- Set index settings (`editSettings`)
|
|
39
32
|
|
|
40
|
-
|
|
41
|
-
- Name: Post published
|
|
42
|
-
- Event: Post published
|
|
43
|
-
- Target URL: the endpoint of the post-published function, found on Netlify's admin panel plus the `NETLIFY_KEY` as a query parameter as defined in the configuration data above (https://YOUR-SITE-ID.netlify.com/.netlify/functions/post-published?key=NETLIFY_KEY)
|
|
33
|
+
### Deploy the Netlify Functions
|
|
44
34
|
|
|
45
|
-
|
|
46
|
-
- Name: Post updated
|
|
47
|
-
- Event: Published post updated
|
|
48
|
-
- Target URL: the endpoint of the post-published function, found on Netlify's admin panel plus the `NETLIFY_KEY` as a query parameter as defined in the configuration data above (https://YOUR-SITE-ID.netlify.com/.netlify/functions/post-published?key=NETLIFY_KEY)
|
|
35
|
+
Deploy manually, and do not expose the deployed functions publicly until mandatory authentication is implemented or access is restricted outside the handlers:
|
|
49
36
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
37
|
+
1. Create a Netlify site and connect this repository manually.
|
|
38
|
+
2. Configure the site and set the environment variables described in [`.env.example`](.env.example):
|
|
39
|
+
- Set `ALGOLIA_ACTIVE` to `TRUE` to enable indexing.
|
|
40
|
+
- Set the Algolia Application ID, API key, and index name.
|
|
41
|
+
- Choose a `NETLIFY_KEY` for the webhook query parameter. Setting it does not protect current handlers when the parameter is omitted.
|
|
54
42
|
|
|
55
|
-
|
|
56
|
-
- Name: Post deleted
|
|
57
|
-
- Event: Post deleted
|
|
58
|
-
- Target URL: the endpoint of the post-published function, found on Netlify's admin panel plus the `NETLIFY_KEY` as a query parameter as defined in the configuration data above (https://YOUR-SITE-ID.netlify.com/.netlify/functions/post-unpublished?key=NETLIFY_KEY)
|
|
43
|
+
The repository's [`netlify.toml`](../../netlify.toml) builds and deploys these functions from `packages/algolia-netlify`. The explicit `public` publish directory contains only a static landing page so package files, function sources, and function build artifacts are not exposed as site files.
|
|
59
44
|
|
|
60
|
-
|
|
45
|
+
### Set up Ghost webhooks
|
|
61
46
|
|
|
62
|
-
|
|
47
|
+
In Ghost Admin, create a **Custom Integration** under **Settings → Integrations** and add these webhooks:
|
|
63
48
|
|
|
49
|
+
| Ghost event | Function |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `post.published` | `post-published` |
|
|
52
|
+
| `post.published.edited` | `post-published` |
|
|
53
|
+
| `post.unpublished` | `post-unpublished` |
|
|
54
|
+
| `post.deleted` | `post-unpublished` |
|
|
64
55
|
|
|
65
|
-
|
|
56
|
+
Use the function URL shown by Netlify and pass the configured `NETLIFY_KEY` as the `key` query parameter. For example:
|
|
66
57
|
|
|
67
|
-
|
|
58
|
+
```text
|
|
59
|
+
https://YOUR-SITE-ID.netlify.app/.netlify/functions/post-published?key=NETLIFY_KEY
|
|
60
|
+
```
|
|
68
61
|
|
|
69
|
-
|
|
62
|
+
These webhooks keep future post changes synchronized. Use the [`@tryghost/algolia` CLI](../algolia/README.md) to create the initial index.
|
|
70
63
|
|
|
71
|
-
|
|
64
|
+
## Development
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
1. `git clone` this repo & `cd` into it as usual
|
|
75
|
-
2. Run `yarn` to install top-level dependencies.
|
|
66
|
+
Install the monorepo dependencies from the repository root with `pnpm install`. Then configure and run this package locally:
|
|
76
67
|
|
|
77
|
-
|
|
68
|
+
```sh
|
|
69
|
+
cd packages/algolia-netlify
|
|
70
|
+
cp .env.example .env
|
|
71
|
+
# Replace the example values in .env with development credentials.
|
|
72
|
+
pnpm dev
|
|
73
|
+
```
|
|
78
74
|
|
|
79
|
-
|
|
75
|
+
`pnpm dev` starts Netlify Dev, which builds the TypeScript functions as needed. Use the local URL it prints for endpoints such as `/.netlify/functions/post-published`.
|
|
80
76
|
|
|
81
|
-
|
|
77
|
+
Run this package's tests and ESLint checks from the repository root:
|
|
82
78
|
|
|
83
|
-
|
|
79
|
+
```sh
|
|
80
|
+
pnpm --filter @tryghost/algolia-netlify test
|
|
81
|
+
pnpm --filter @tryghost/algolia-netlify typecheck
|
|
82
|
+
pnpm --filter @tryghost/algolia-netlify build
|
|
83
|
+
```
|
|
84
84
|
|
|
85
|
+
Run the full monorepo suite with `pnpm test`.
|
|
85
86
|
|
|
86
|
-
|
|
87
|
+
The modern handlers use native `Request` and `Response` objects. Malformed, empty, or structurally invalid JSON now receives `400 Invalid request body`; a valid Ghost envelope with no selected post remains a `200 No valid request body detected` response. Existing valid webhook behavior, endpoint URLs, and optional `key` handling are unchanged.
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
- `yarn test` run lint and tests
|
|
89
|
+
`pnpm pack` builds an ESM-only Node 24 package with generated TypeScript declarations. The package intentionally exposes only the two handlers; webhook utilities remain internal.
|
|
90
90
|
|
|
91
|
+
---
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
## Copyright & License
|
|
93
94
|
|
|
94
|
-
Copyright (c) 2013-
|
|
95
|
+
Copyright (c) 2013-2026 Ghost Foundation - Released under the [MIT license](LICENSE). Ghost and the Ghost Logo are trademarks of Ghost Foundation Ltd. Please see our [trademark policy](https://ghost.org/trademark/) for info on acceptable usage.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function postPublished(request: Request): Promise<Response>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// functions/utils/webhook.ts
|
|
2
|
+
var TEXT_HEADERS = { "content-type": "text/plain; charset=utf-8" };
|
|
3
|
+
var GHOST_SOURCE = String.raw`https://github\.com/TryGhost/Ghost`;
|
|
4
|
+
var SEMVER = String.raw`(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?`;
|
|
5
|
+
var GHOST_USER_AGENT = new RegExp(
|
|
6
|
+
`^Ghost(?:\\(${GHOST_SOURCE}\\)|/${SEMVER} \\(${GHOST_SOURCE}\\))$`,
|
|
7
|
+
"u"
|
|
8
|
+
);
|
|
9
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
var isGhostUserAgent = (value) => value !== null && GHOST_USER_AGENT.test(value);
|
|
11
|
+
var textResponse = (body, status = 200) => new Response(body, { status, headers: TEXT_HEADERS });
|
|
12
|
+
var invalidBodyResponse = () => textResponse("Invalid request body", 400);
|
|
13
|
+
var noValidBodyResponse = () => textResponse("No valid request body detected");
|
|
14
|
+
var authorizeRequest = (request) => {
|
|
15
|
+
const keys = new URL(request.url).searchParams.getAll("key");
|
|
16
|
+
if (keys.length > 1 || keys[0] && keys[0] !== process.env.NETLIFY_KEY) {
|
|
17
|
+
return textResponse("Unauthorized", 401);
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
};
|
|
21
|
+
var algoliaSettings = () => ({
|
|
22
|
+
appId: process.env.ALGOLIA_APP_ID,
|
|
23
|
+
apiKey: process.env.ALGOLIA_API_KEY,
|
|
24
|
+
index: process.env.ALGOLIA_INDEX
|
|
25
|
+
});
|
|
26
|
+
var parseWebhookBody = async (request) => {
|
|
27
|
+
try {
|
|
28
|
+
const body = await request.json();
|
|
29
|
+
if (!isRecord(body) || !isRecord(body.post)) {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
return { post: body.post };
|
|
33
|
+
} catch {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// functions/utils/algolia.mts
|
|
39
|
+
import IndexFactory from "@tryghost/algolia-indexer";
|
|
40
|
+
import transforms from "@tryghost/algolia-fragmenter";
|
|
41
|
+
import "algolia-html-extractor";
|
|
42
|
+
import "algoliasearch";
|
|
43
|
+
|
|
44
|
+
// functions/post-published.mts
|
|
45
|
+
async function postPublished(request) {
|
|
46
|
+
const authorizationResponse = authorizeRequest(request);
|
|
47
|
+
if (authorizationResponse) {
|
|
48
|
+
return authorizationResponse;
|
|
49
|
+
}
|
|
50
|
+
if (process.env.ALGOLIA_ACTIVE !== "TRUE") {
|
|
51
|
+
return textResponse("Algolia is not activated");
|
|
52
|
+
}
|
|
53
|
+
if (!isGhostUserAgent(request.headers.get("user-agent"))) {
|
|
54
|
+
return textResponse("Unauthorized", 401);
|
|
55
|
+
}
|
|
56
|
+
const payload = await parseWebhookBody(request);
|
|
57
|
+
if (!payload) {
|
|
58
|
+
return invalidBodyResponse();
|
|
59
|
+
}
|
|
60
|
+
const post = payload.post.current;
|
|
61
|
+
if (!isRecord(post) || Object.keys(post).length === 0) {
|
|
62
|
+
return noValidBodyResponse();
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const algoliaObjects = transforms.transformToAlgoliaObject([post]);
|
|
66
|
+
const fragments = algoliaObjects.reduce(transforms.fragmentTransformer, []);
|
|
67
|
+
const index = new IndexFactory(algoliaSettings());
|
|
68
|
+
await index.setSettingsForIndex();
|
|
69
|
+
await index.save(fragments);
|
|
70
|
+
console.log("Fragments successfully saved to Algolia index");
|
|
71
|
+
return textResponse(`Post "${String(post.title)}" has been added to the index.`);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.log(error);
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
return Response.json({ msg: message }, { status: 500 });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
postPublished as default
|
|
80
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function postUnpublished(request: Request): Promise<Response>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// functions/utils/webhook.ts
|
|
2
|
+
var TEXT_HEADERS = { "content-type": "text/plain; charset=utf-8" };
|
|
3
|
+
var GHOST_SOURCE = String.raw`https://github\.com/TryGhost/Ghost`;
|
|
4
|
+
var SEMVER = String.raw`(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?`;
|
|
5
|
+
var GHOST_USER_AGENT = new RegExp(
|
|
6
|
+
`^Ghost(?:\\(${GHOST_SOURCE}\\)|/${SEMVER} \\(${GHOST_SOURCE}\\))$`,
|
|
7
|
+
"u"
|
|
8
|
+
);
|
|
9
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10
|
+
var isGhostUserAgent = (value) => value !== null && GHOST_USER_AGENT.test(value);
|
|
11
|
+
var textResponse = (body, status = 200) => new Response(body, { status, headers: TEXT_HEADERS });
|
|
12
|
+
var invalidBodyResponse = () => textResponse("Invalid request body", 400);
|
|
13
|
+
var noValidBodyResponse = () => textResponse("No valid request body detected");
|
|
14
|
+
var authorizeRequest = (request) => {
|
|
15
|
+
const keys = new URL(request.url).searchParams.getAll("key");
|
|
16
|
+
if (keys.length > 1 || keys[0] && keys[0] !== process.env.NETLIFY_KEY) {
|
|
17
|
+
return textResponse("Unauthorized", 401);
|
|
18
|
+
}
|
|
19
|
+
return void 0;
|
|
20
|
+
};
|
|
21
|
+
var algoliaSettings = () => ({
|
|
22
|
+
appId: process.env.ALGOLIA_APP_ID,
|
|
23
|
+
apiKey: process.env.ALGOLIA_API_KEY,
|
|
24
|
+
index: process.env.ALGOLIA_INDEX
|
|
25
|
+
});
|
|
26
|
+
var parseWebhookBody = async (request) => {
|
|
27
|
+
try {
|
|
28
|
+
const body = await request.json();
|
|
29
|
+
if (!isRecord(body) || !isRecord(body.post)) {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
return { post: body.post };
|
|
33
|
+
} catch {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// functions/utils/algolia.mts
|
|
39
|
+
import IndexFactory from "@tryghost/algolia-indexer";
|
|
40
|
+
import transforms from "@tryghost/algolia-fragmenter";
|
|
41
|
+
import "algolia-html-extractor";
|
|
42
|
+
import "algoliasearch";
|
|
43
|
+
|
|
44
|
+
// functions/post-unpublished.mts
|
|
45
|
+
async function postUnpublished(request) {
|
|
46
|
+
const authorizationResponse = authorizeRequest(request);
|
|
47
|
+
if (authorizationResponse) {
|
|
48
|
+
return authorizationResponse;
|
|
49
|
+
}
|
|
50
|
+
if (process.env.ALGOLIA_ACTIVE !== "TRUE") {
|
|
51
|
+
return textResponse("Algolia is not activated");
|
|
52
|
+
}
|
|
53
|
+
if (!isGhostUserAgent(request.headers.get("user-agent"))) {
|
|
54
|
+
return textResponse("Unauthorized", 401);
|
|
55
|
+
}
|
|
56
|
+
const payload = await parseWebhookBody(request);
|
|
57
|
+
if (!payload) {
|
|
58
|
+
return invalidBodyResponse();
|
|
59
|
+
}
|
|
60
|
+
const { current } = payload.post;
|
|
61
|
+
const { previous } = payload.post;
|
|
62
|
+
const post = isRecord(current) && Object.keys(current).length > 0 ? current : previous;
|
|
63
|
+
const slug = isRecord(post) && typeof post.slug === "string" ? post.slug : "";
|
|
64
|
+
if (!slug) {
|
|
65
|
+
return noValidBodyResponse();
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const index = new IndexFactory(algoliaSettings());
|
|
69
|
+
await index.initIndex();
|
|
70
|
+
await index.delete(slug);
|
|
71
|
+
console.log(`Fragments for slug "${slug}" successfully removed from Algolia index`);
|
|
72
|
+
return textResponse(`Post "${slug}" has been removed from the index.`);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
console.log(error);
|
|
75
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
+
return Response.json({ msg: message }, { status: 500 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export {
|
|
80
|
+
postUnpublished as default
|
|
81
|
+
};
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,25 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryghost/algolia-netlify",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"repository":
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/TryGhost/algolia.git",
|
|
7
|
+
"directory": "packages/algolia-netlify"
|
|
8
|
+
},
|
|
5
9
|
"author": "Ghost Foundation",
|
|
6
10
|
"license": "MIT",
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=24"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.mjs",
|
|
16
|
+
"types": "./dist/index.d.mts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.mts",
|
|
20
|
+
"import": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./post-published": {
|
|
23
|
+
"types": "./dist/functions/post-published.d.mts",
|
|
24
|
+
"import": "./dist/functions/post-published.mjs"
|
|
25
|
+
},
|
|
26
|
+
"./post-unpublished": {
|
|
27
|
+
"types": "./dist/functions/post-unpublished.d.mts",
|
|
28
|
+
"import": "./dist/functions/post-unpublished.mjs"
|
|
29
|
+
}
|
|
14
30
|
},
|
|
15
31
|
"files": [
|
|
16
|
-
"
|
|
17
|
-
"lib"
|
|
32
|
+
"dist"
|
|
18
33
|
],
|
|
19
34
|
"publishConfig": {
|
|
20
35
|
"access": "public"
|
|
21
36
|
},
|
|
22
37
|
"eslintConfig": {
|
|
38
|
+
"parser": "@typescript-eslint/parser",
|
|
39
|
+
"parserOptions": {
|
|
40
|
+
"ecmaVersion": 2023,
|
|
41
|
+
"sourceType": "module"
|
|
42
|
+
},
|
|
23
43
|
"extends": [
|
|
24
44
|
"airbnb-base",
|
|
25
45
|
"plugin:ghost/browser"
|
|
@@ -30,21 +50,33 @@
|
|
|
30
50
|
},
|
|
31
51
|
"eslintIgnore": [
|
|
32
52
|
"webpack.functions.js",
|
|
33
|
-
"build/*"
|
|
53
|
+
"build/*",
|
|
54
|
+
"dist/*"
|
|
34
55
|
],
|
|
35
56
|
"devDependencies": {
|
|
36
|
-
"
|
|
57
|
+
"@types/node": "24.13.3",
|
|
58
|
+
"@typescript-eslint/parser": "8.67.0",
|
|
59
|
+
"eslint": "8.57.1",
|
|
37
60
|
"eslint-config-airbnb-base": "15.0.0",
|
|
38
|
-
"eslint-plugin-ghost": "3.
|
|
39
|
-
"eslint-plugin-import": "2.
|
|
40
|
-
"
|
|
41
|
-
"netlify-cli": "
|
|
42
|
-
"
|
|
43
|
-
"sinon": "15.2.0"
|
|
61
|
+
"eslint-plugin-ghost": "3.5.0",
|
|
62
|
+
"eslint-plugin-import": "2.32.0",
|
|
63
|
+
"esbuild": "0.28.2",
|
|
64
|
+
"netlify-cli": "27.1.1",
|
|
65
|
+
"typescript": "6.0.3"
|
|
44
66
|
},
|
|
45
67
|
"dependencies": {
|
|
46
|
-
"
|
|
47
|
-
"
|
|
68
|
+
"algolia-html-extractor": "0.0.1",
|
|
69
|
+
"algoliasearch": "4.20.0",
|
|
70
|
+
"@tryghost/algolia-indexer": "^0.3.2",
|
|
71
|
+
"@tryghost/algolia-fragmenter": "^0.2.8"
|
|
48
72
|
},
|
|
49
|
-
"
|
|
50
|
-
|
|
73
|
+
"scripts": {
|
|
74
|
+
"dev": "pnpm --dir ../.. exec netlify dev --filter @tryghost/algolia-netlify",
|
|
75
|
+
"test": "NODE_ENV=testing vitest run --root .",
|
|
76
|
+
"typecheck": "tsc --project tsconfig.functions.json && tsc --project tsconfig.test.json",
|
|
77
|
+
"lint": "eslint . --ext .js,.cjs,.ts,.mts,.cts --cache",
|
|
78
|
+
"posttest": "pnpm typecheck && pnpm lint",
|
|
79
|
+
"build": "NODE_ENV=production pnpm --dir ../.. exec netlify build --offline --filter @tryghost/algolia-netlify",
|
|
80
|
+
"build:package": "node scripts/build-package.mjs && tsc --project tsconfig.package.json && node scripts/clean-package.mjs"
|
|
81
|
+
}
|
|
82
|
+
}
|