@monok/server 1.0.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/README.md +228 -0
- package/dist/client.d.ts +9 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +25 -0
- package/dist/client.js.map +1 -0
- package/dist/express.d.ts +4 -0
- package/dist/express.d.ts.map +1 -0
- package/dist/express.js +43 -0
- package/dist/express.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +32 -0
- package/dist/utils.js.map +1 -0
- package/dist-cjs/client.js +25 -0
- package/dist-cjs/client.js.map +1 -0
- package/dist-cjs/express.js +43 -0
- package/dist-cjs/express.js.map +1 -0
- package/dist-cjs/index.js +4 -0
- package/dist-cjs/index.js.map +1 -0
- package/dist-cjs/types.js +2 -0
- package/dist-cjs/types.js.map +1 -0
- package/dist-cjs/utils.js +32 -0
- package/dist-cjs/utils.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
Here’s the updated **README.md** with the new `action` param explained and shown in examples:
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# `@monok/server`
|
|
6
|
+
|
|
7
|
+
Monok server integration helpers for Node.js/Express.
|
|
8
|
+
Easily connect your backend to Monok to receive articles, expose site info, and send linkbacks.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## ⚡ Quick Start
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import express from "express";
|
|
16
|
+
import { createMonokRouter, type MonokArticle } from "@monok/server";
|
|
17
|
+
|
|
18
|
+
const app = express();
|
|
19
|
+
|
|
20
|
+
app.use(
|
|
21
|
+
"/monok",
|
|
22
|
+
createMonokRouter({
|
|
23
|
+
apiKey: process.env.MONOK_API_KEY!,
|
|
24
|
+
// exact format Monok expects:
|
|
25
|
+
getSiteInfo: {
|
|
26
|
+
js_scripts: "Enabled",
|
|
27
|
+
version: "1.0.0",
|
|
28
|
+
name: "My Site",
|
|
29
|
+
logo: "https://example.com/logo.png",
|
|
30
|
+
categories: ["Content", "News Articles", "Uncategorized"],
|
|
31
|
+
post_types: ["post", "page"],
|
|
32
|
+
tags: ["AI", "Health", "News"],
|
|
33
|
+
authors: [
|
|
34
|
+
{
|
|
35
|
+
name: "Jane Doe",
|
|
36
|
+
profile: "Editor-in-chief",
|
|
37
|
+
id: "1",
|
|
38
|
+
avatar: "https://example.com/avatars/jane.png",
|
|
39
|
+
roles: ["administrator"]
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
onArticle: async (article: MonokArticle, { saveThumbImage, action }) => {
|
|
44
|
+
const imagePath = await saveThumbImage();
|
|
45
|
+
|
|
46
|
+
if (action === "publish") {
|
|
47
|
+
// auto-publish in your CMS
|
|
48
|
+
} else {
|
|
49
|
+
// save as draft (default)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
app.listen(3000);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 📦 Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install @monok/server express
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 📚 Usage
|
|
69
|
+
|
|
70
|
+
### Express setup (TypeScript)
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import express from "express";
|
|
74
|
+
import {
|
|
75
|
+
createMonokRouter,
|
|
76
|
+
sendMonokLinkback,
|
|
77
|
+
type MonokArticle
|
|
78
|
+
} from "@monok/server";
|
|
79
|
+
|
|
80
|
+
const app = express();
|
|
81
|
+
|
|
82
|
+
app.use(
|
|
83
|
+
"/monok",
|
|
84
|
+
createMonokRouter({
|
|
85
|
+
apiKey: process.env.MONOK_API_KEY!,
|
|
86
|
+
imageDir: "./uploads", // optional
|
|
87
|
+
getSiteInfo: {
|
|
88
|
+
js_scripts: "Enabled",
|
|
89
|
+
version: "1.0.0",
|
|
90
|
+
name: "My Site",
|
|
91
|
+
logo: "https://example.com/logo.png",
|
|
92
|
+
categories: ["Content", "News Articles", "Uncategorized"],
|
|
93
|
+
post_types: ["post", "page"],
|
|
94
|
+
tags: ["Tech", "Health", "News"],
|
|
95
|
+
authors: [
|
|
96
|
+
{
|
|
97
|
+
name: "Jane Doe",
|
|
98
|
+
profile: "Editor-in-chief",
|
|
99
|
+
id: "1",
|
|
100
|
+
avatar: "https://example.com/avatars/jane.png",
|
|
101
|
+
roles: ["administrator"]
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
onArticle: async (article: MonokArticle, { saveThumbImage, action }) => {
|
|
106
|
+
const imagePath = await saveThumbImage();
|
|
107
|
+
console.log("Received article:", article.title);
|
|
108
|
+
|
|
109
|
+
if (action === "publish") {
|
|
110
|
+
// publish immediately
|
|
111
|
+
} else {
|
|
112
|
+
// default: save as draft
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// when you publish later, notify Monok:
|
|
116
|
+
// await sendMonokLinkback({
|
|
117
|
+
// monokId: article.id,
|
|
118
|
+
// publicUrl: "https://example.com/blog/" + (article.slug ?? article.id),
|
|
119
|
+
// title: article.title,
|
|
120
|
+
// articleTextSample: article.text?.slice(0, 500),
|
|
121
|
+
// thumbnailUrl: imagePath
|
|
122
|
+
// ? "https://example.com/uploads/" + require("path").basename(imagePath)
|
|
123
|
+
// : undefined
|
|
124
|
+
// });
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
app.listen(3000);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### CommonJS (JavaScript)
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
const express = require("express");
|
|
136
|
+
const { createMonokRouter } = require("@monok/server");
|
|
137
|
+
|
|
138
|
+
const app = express();
|
|
139
|
+
|
|
140
|
+
app.use(
|
|
141
|
+
"/monok",
|
|
142
|
+
createMonokRouter({
|
|
143
|
+
apiKey: process.env.MONOK_API_KEY,
|
|
144
|
+
getSiteInfo: {
|
|
145
|
+
js_scripts: "Enabled",
|
|
146
|
+
version: "1.0.0",
|
|
147
|
+
name: "My Site",
|
|
148
|
+
logo: "https://example.com/logo.png",
|
|
149
|
+
categories: ["Content", "News Articles", "Uncategorized"],
|
|
150
|
+
post_types: ["post", "page"],
|
|
151
|
+
tags: ["AI", "Health", "News"],
|
|
152
|
+
authors: [
|
|
153
|
+
{
|
|
154
|
+
name: "Jane Doe",
|
|
155
|
+
profile: "Editor-in-chief",
|
|
156
|
+
id: "1",
|
|
157
|
+
avatar: "https://example.com/avatars/jane.png",
|
|
158
|
+
roles: ["administrator"]
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
},
|
|
162
|
+
onArticle: async (article, { saveThumbImage, action }) => {
|
|
163
|
+
const img = await saveThumbImage();
|
|
164
|
+
console.log("Got article:", article.title, "Saved image:", img);
|
|
165
|
+
|
|
166
|
+
if (action === "publish") {
|
|
167
|
+
// publish immediately
|
|
168
|
+
} else {
|
|
169
|
+
// save as draft
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
app.listen(3000);
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## 🔑 Endpoints
|
|
181
|
+
|
|
182
|
+
* **`POST /monok/push[?action=draft|publish]`**
|
|
183
|
+
Monok pushes new article data here.
|
|
184
|
+
Header: `token: <your-api-key>`
|
|
185
|
+
Body: JSON payload of the article.
|
|
186
|
+
|
|
187
|
+
* `action` query param may be `draft` or `publish` depending on GUI dropdown selection.
|
|
188
|
+
|
|
189
|
+
* **`GET /monok/info?apikey=<your-api-key>`**
|
|
190
|
+
Monok fetches this to learn your site branding, categories, tags, and authors.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 🔗 Linkback after publishing
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { sendMonokLinkback } from "@monok/server";
|
|
198
|
+
|
|
199
|
+
await sendMonokLinkback({
|
|
200
|
+
monokId: article.id,
|
|
201
|
+
publicUrl: "https://example.com/blog/" + (article.slug ?? article.id),
|
|
202
|
+
title: article.title,
|
|
203
|
+
articleTextSample: article.text?.slice(0, 500),
|
|
204
|
+
thumbnailUrl: "https://example.com/uploads/" + imageFile
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## ⚙️ Options (`createMonokRouter`)
|
|
211
|
+
|
|
212
|
+
| Option | Type | Required | Description |
|
|
213
|
+
| ------------- | ----------------------------------------- | -------- | --------------------------------------------------------------------------------- |
|
|
214
|
+
| `apiKey` | `string` | ✅ | Token Monok sends in headers / query. |
|
|
215
|
+
| `onArticle` | `(article, ctx) => void \| Promise<void>` | ✅ | Handle new article pushes. `ctx` includes `saveThumbImage` and optional `action`. |
|
|
216
|
+
| `getSiteInfo` | `SiteInfo \| () => SiteInfo` | ✅ | Return site info in the exact format Monok requires. |
|
|
217
|
+
| `imageDir` | `string` | ❌ | Directory to save `thumbImageData`. |
|
|
218
|
+
| `basePath` | `string` | ❌ | Custom prefix for routes (default ""). |
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 🛠 Development
|
|
223
|
+
|
|
224
|
+
Build the package:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm run build
|
|
228
|
+
```
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LinkbackPayload } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Send a linkback to Monok after you publish the article.
|
|
4
|
+
*/
|
|
5
|
+
export declare function sendMonokLinkback({ monokId, publicUrl, title, articleTextSample, thumbnailUrl }: LinkbackPayload, opts?: {
|
|
6
|
+
monokBaseUrl?: string;
|
|
7
|
+
userId?: string;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAsB,EAAE,YAAY,EAAE,EAAE,eAAe,EACpF,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
/**
|
|
3
|
+
* Send a linkback to Monok after you publish the article.
|
|
4
|
+
*/
|
|
5
|
+
export async function sendMonokLinkback({ monokId, publicUrl, title, articleTextSample = "", thumbnailUrl }, opts) {
|
|
6
|
+
const endpoint = (opts?.monokBaseUrl ?? "https://monok.news") + "/api/v1/linkback";
|
|
7
|
+
const payload = {
|
|
8
|
+
puff: { title, article: articleTextSample },
|
|
9
|
+
id: monokId,
|
|
10
|
+
url: publicUrl,
|
|
11
|
+
source: new URL(publicUrl).hostname,
|
|
12
|
+
userId: opts?.userId ?? new URL(publicUrl).hostname + "-CUSTOM",
|
|
13
|
+
...(thumbnailUrl ? { thumbnail: thumbnailUrl } : {})
|
|
14
|
+
};
|
|
15
|
+
const resp = await fetch(endpoint, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify(payload)
|
|
19
|
+
});
|
|
20
|
+
if (!resp.ok) {
|
|
21
|
+
const text = await resp.text().catch(() => "");
|
|
22
|
+
throw new Error(`Monok linkback failed: ${resp.status} ${text}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,GAAG,EAAE,EAAE,YAAY,EAAmB,EACpF,IAAiD;IAEjD,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,YAAY,IAAI,oBAAoB,CAAC,GAAG,kBAAkB,CAAC;IACnF,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE;QAC3C,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,SAAS;QACd,MAAM,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ;QACnC,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,GAAG,SAAS;QAC/D,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrD,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;KAC9B,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAwD,MAAM,SAAS,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAA0B,MAAM,YAAY,CAAC;AAG9E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,mBAAmB,GAAG,MAAM,CA4CnE"}
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Router, json } from "express";
|
|
2
|
+
import { assertApiKey, makeThumbSaver } from "./utils.js";
|
|
3
|
+
export function createMonokRouter(opts) {
|
|
4
|
+
const r = Router();
|
|
5
|
+
const base = opts.basePath ?? "";
|
|
6
|
+
// JSON body parsing for pushes (allow bigger payloads for images)
|
|
7
|
+
r.use(json({ limit: "20mb" }));
|
|
8
|
+
// POST /push
|
|
9
|
+
r.post(base + "/push", wrap(async (req, res) => {
|
|
10
|
+
assertApiKey(req.header("token"), opts.apiKey);
|
|
11
|
+
const body = req.body;
|
|
12
|
+
if (!body?.id || !body?.title) {
|
|
13
|
+
return res.status(400).json({ error: "Invalid payload" });
|
|
14
|
+
}
|
|
15
|
+
// read optional ?action=draft|publish
|
|
16
|
+
const action = req.query.action;
|
|
17
|
+
const saver = makeThumbSaver(opts.imageDir);
|
|
18
|
+
await Promise.resolve(opts.onArticle(body, {
|
|
19
|
+
saveThumbImage: async (fileNameBase) => saver(fileNameBase ?? body.slug ?? body.id, body.thumbImageData),
|
|
20
|
+
action
|
|
21
|
+
}));
|
|
22
|
+
res.json({ ok: true, monokId: body.id });
|
|
23
|
+
}));
|
|
24
|
+
// GET /info
|
|
25
|
+
r.get(base + "/info", wrap(async (req, res) => {
|
|
26
|
+
const apikey = req.query.apikey;
|
|
27
|
+
assertApiKey(apikey, opts.apiKey);
|
|
28
|
+
const info = typeof opts.getSiteInfo === "function"
|
|
29
|
+
? await Promise.resolve(opts.getSiteInfo())
|
|
30
|
+
: opts.getSiteInfo;
|
|
31
|
+
res.json(info);
|
|
32
|
+
}));
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
35
|
+
function wrap(fn) {
|
|
36
|
+
return (req, res, next) => fn(req, res, next).catch((err) => {
|
|
37
|
+
const status = typeof err?.status === "number" ? err.status : 500;
|
|
38
|
+
if (status >= 500)
|
|
39
|
+
console.error(err);
|
|
40
|
+
res.status(status).json({ error: err?.message ?? "Server error" });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type VFImage = Record<string, unknown>;
|
|
2
|
+
export interface MonokArticle {
|
|
3
|
+
title: string;
|
|
4
|
+
shortTitle?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
content?: string;
|
|
7
|
+
text?: string;
|
|
8
|
+
slug?: string;
|
|
9
|
+
id: string;
|
|
10
|
+
thumbImageData?: string;
|
|
11
|
+
thumbImageDescription?: string;
|
|
12
|
+
language?: string;
|
|
13
|
+
type?: "blog" | "news";
|
|
14
|
+
keywords?: string[];
|
|
15
|
+
tags?: string[];
|
|
16
|
+
vfImage?: VFImage;
|
|
17
|
+
sections?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SiteAuthor {
|
|
20
|
+
name: string;
|
|
21
|
+
profile: string;
|
|
22
|
+
id: string;
|
|
23
|
+
avatar: string;
|
|
24
|
+
roles: string[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Exact shape Monok expects from GET /info
|
|
28
|
+
*/
|
|
29
|
+
export interface SiteInfo {
|
|
30
|
+
js_scripts: "Enabled" | "Disabled";
|
|
31
|
+
version: string;
|
|
32
|
+
name: string;
|
|
33
|
+
logo: string;
|
|
34
|
+
categories: string[];
|
|
35
|
+
post_types: string[];
|
|
36
|
+
tags: string[];
|
|
37
|
+
authors: SiteAuthor[];
|
|
38
|
+
}
|
|
39
|
+
export interface LinkbackPayload {
|
|
40
|
+
monokId: string;
|
|
41
|
+
publicUrl: string;
|
|
42
|
+
title: string;
|
|
43
|
+
articleTextSample?: string;
|
|
44
|
+
thumbnailUrl?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface CreateRouterOptions {
|
|
47
|
+
apiKey: string;
|
|
48
|
+
onArticle: (article: MonokArticle, ctx: {
|
|
49
|
+
saveThumbImage: (fileNameBase?: string) => Promise<string | undefined>;
|
|
50
|
+
action?: "draft" | "publish" | undefined;
|
|
51
|
+
}) => Promise<void> | void;
|
|
52
|
+
getSiteInfo: SiteInfo | (() => Promise<SiteInfo> | SiteInfo);
|
|
53
|
+
imageDir?: string;
|
|
54
|
+
basePath?: string;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE9C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,UAAU,EAAE,SAAS,GAAG,UAAU,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IAEf,SAAS,EAAE,CACT,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE;QACH,cAAc,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;QACvE,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;KAC1C,KACE,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAE1B,WAAW,EAAE,QAAQ,GAAG,CAAC,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAAC,CAAC;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function timingSafeEqual(a: string, b: string): boolean;
|
|
2
|
+
export declare function assertApiKey(headerValue: string | undefined, expected: string): void;
|
|
3
|
+
export declare function makeThumbSaver(imageDir?: string): (base?: string, data?: string) => Promise<string | undefined>;
|
|
4
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAK7D;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAOpF;AAED,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,IAChC,OAAO,MAAM,EAAE,OAAO,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAUzE"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
export function timingSafeEqual(a, b) {
|
|
5
|
+
const abuf = Buffer.from(a);
|
|
6
|
+
const bbuf = Buffer.from(b);
|
|
7
|
+
if (abuf.length !== bbuf.length)
|
|
8
|
+
return false;
|
|
9
|
+
return crypto.timingSafeEqual(abuf, bbuf);
|
|
10
|
+
}
|
|
11
|
+
export function assertApiKey(headerValue, expected) {
|
|
12
|
+
if (!headerValue || !timingSafeEqual(headerValue, expected)) {
|
|
13
|
+
const err = new Error("Unauthorized");
|
|
14
|
+
// @ts-expect-error custom status
|
|
15
|
+
err.status = 401;
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function makeThumbSaver(imageDir) {
|
|
20
|
+
return async (base, data) => {
|
|
21
|
+
if (!imageDir || !data)
|
|
22
|
+
return undefined;
|
|
23
|
+
const bytes = Buffer.from(data, "base64");
|
|
24
|
+
const safeName = (base || crypto.randomUUID()).replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
25
|
+
const fileName = `${safeName}.png`;
|
|
26
|
+
await fs.mkdir(imageDir, { recursive: true });
|
|
27
|
+
const full = path.join(imageDir, fileName);
|
|
28
|
+
await fs.writeFile(full, bytes);
|
|
29
|
+
return full;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,aAAa,CAAC;AAE7B,MAAM,UAAU,eAAe,CAAC,CAAS,EAAE,CAAS;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,WAA+B,EAAE,QAAgB;IAC5E,IAAI,CAAC,WAAW,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC5D,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;QACtC,iCAAiC;QACjC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAiB;IAC9C,OAAO,KAAK,EAAE,IAAa,EAAE,IAAa,EAA+B,EAAE;QACzE,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,CAAC,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QAC/E,MAAM,QAAQ,GAAG,GAAG,QAAQ,MAAM,CAAC;QACnC,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
/**
|
|
3
|
+
* Send a linkback to Monok after you publish the article.
|
|
4
|
+
*/
|
|
5
|
+
export async function sendMonokLinkback({ monokId, publicUrl, title, articleTextSample = "", thumbnailUrl }, opts) {
|
|
6
|
+
const endpoint = (opts?.monokBaseUrl ?? "https://monok.news") + "/api/v1/linkback";
|
|
7
|
+
const payload = {
|
|
8
|
+
puff: { title, article: articleTextSample },
|
|
9
|
+
id: monokId,
|
|
10
|
+
url: publicUrl,
|
|
11
|
+
source: new URL(publicUrl).hostname,
|
|
12
|
+
userId: opts?.userId ?? new URL(publicUrl).hostname + "-CUSTOM",
|
|
13
|
+
...(thumbnailUrl ? { thumbnail: thumbnailUrl } : {})
|
|
14
|
+
};
|
|
15
|
+
const resp = await fetch(endpoint, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify(payload)
|
|
19
|
+
});
|
|
20
|
+
if (!resp.ok) {
|
|
21
|
+
const text = await resp.text().catch(() => "");
|
|
22
|
+
throw new Error(`Monok linkback failed: ${resp.status} ${text}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAG/B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,iBAAiB,GAAG,EAAE,EAAE,YAAY,EAAmB,EACpF,IAAiD;IAEjD,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,YAAY,IAAI,oBAAoB,CAAC,GAAG,kBAAkB,CAAC;IACnF,MAAM,OAAO,GAAG;QACd,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE;QAC3C,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,SAAS;QACd,MAAM,EAAE,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ;QACnC,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,GAAG,SAAS;QAC/D,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrD,CAAC;IAEF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;QACjC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;KAC9B,CAAC,CAAC;IAEH,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,CAAC,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Router, json } from "express";
|
|
2
|
+
import { assertApiKey, makeThumbSaver } from "./utils.js";
|
|
3
|
+
export function createMonokRouter(opts) {
|
|
4
|
+
const r = Router();
|
|
5
|
+
const base = opts.basePath ?? "";
|
|
6
|
+
// JSON body parsing for pushes (allow bigger payloads for images)
|
|
7
|
+
r.use(json({ limit: "20mb" }));
|
|
8
|
+
// POST /push
|
|
9
|
+
r.post(base + "/push", wrap(async (req, res) => {
|
|
10
|
+
assertApiKey(req.header("token"), opts.apiKey);
|
|
11
|
+
const body = req.body;
|
|
12
|
+
if (!body?.id || !body?.title) {
|
|
13
|
+
return res.status(400).json({ error: "Invalid payload" });
|
|
14
|
+
}
|
|
15
|
+
// read optional ?action=draft|publish
|
|
16
|
+
const action = req.query.action;
|
|
17
|
+
const saver = makeThumbSaver(opts.imageDir);
|
|
18
|
+
await Promise.resolve(opts.onArticle(body, {
|
|
19
|
+
saveThumbImage: async (fileNameBase) => saver(fileNameBase ?? body.slug ?? body.id, body.thumbImageData),
|
|
20
|
+
action
|
|
21
|
+
}));
|
|
22
|
+
res.json({ ok: true, monokId: body.id });
|
|
23
|
+
}));
|
|
24
|
+
// GET /info
|
|
25
|
+
r.get(base + "/info", wrap(async (req, res) => {
|
|
26
|
+
const apikey = req.query.apikey;
|
|
27
|
+
assertApiKey(apikey, opts.apiKey);
|
|
28
|
+
const info = typeof opts.getSiteInfo === "function"
|
|
29
|
+
? await Promise.resolve(opts.getSiteInfo())
|
|
30
|
+
: opts.getSiteInfo;
|
|
31
|
+
res.json(info);
|
|
32
|
+
}));
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
35
|
+
function wrap(fn) {
|
|
36
|
+
return (req, res, next) => fn(req, res, next).catch((err) => {
|
|
37
|
+
const status = typeof err?.status === "number" ? err.status : 500;
|
|
38
|
+
if (status >= 500)
|
|
39
|
+
console.error(err);
|
|
40
|
+
res.status(status).json({ error: err?.message ?? "Server error" });
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs/promises";
|
|
4
|
+
export function timingSafeEqual(a, b) {
|
|
5
|
+
const abuf = Buffer.from(a);
|
|
6
|
+
const bbuf = Buffer.from(b);
|
|
7
|
+
if (abuf.length !== bbuf.length)
|
|
8
|
+
return false;
|
|
9
|
+
return crypto.timingSafeEqual(abuf, bbuf);
|
|
10
|
+
}
|
|
11
|
+
export function assertApiKey(headerValue, expected) {
|
|
12
|
+
if (!headerValue || !timingSafeEqual(headerValue, expected)) {
|
|
13
|
+
const err = new Error("Unauthorized");
|
|
14
|
+
// @ts-expect-error custom status
|
|
15
|
+
err.status = 401;
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function makeThumbSaver(imageDir) {
|
|
20
|
+
return async (base, data) => {
|
|
21
|
+
if (!imageDir || !data)
|
|
22
|
+
return undefined;
|
|
23
|
+
const bytes = Buffer.from(data, "base64");
|
|
24
|
+
const safeName = (base || crypto.randomUUID()).replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
25
|
+
const fileName = `${safeName}.png`;
|
|
26
|
+
await fs.mkdir(imageDir, { recursive: true });
|
|
27
|
+
const full = path.join(imageDir, fileName);
|
|
28
|
+
await fs.writeFile(full, bytes);
|
|
29
|
+
return full;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,aAAa,CAAC;AAE7B,MAAM,UAAU,eAAe,CAAC,CAAS,EAAE,CAAS;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,WAA+B,EAAE,QAAgB;IAC5E,IAAI,CAAC,WAAW,IAAI,CAAC,eAAe,CAAC,WAAW,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC5D,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;QACtC,iCAAiC;QACjC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC;QACjB,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,QAAiB;IAC9C,OAAO,KAAK,EAAE,IAAa,EAAE,IAAa,EAA+B,EAAE;QACzE,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,CAAC,IAAI,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;QAC/E,MAAM,QAAQ,GAAG,GAAG,QAAQ,MAAM,CAAC;QACnC,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@monok/server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Monok server integration helpers (Express router + linkback client)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist-cjs/index.js",
|
|
8
|
+
"module": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist-cjs/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"dist-cjs",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": { "access": "public" },
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json",
|
|
27
|
+
"dev": "tsc -w -p tsconfig.json",
|
|
28
|
+
"clean": "rimraf dist dist-cjs",
|
|
29
|
+
"prepublishOnly": "npm run build"
|
|
30
|
+
},
|
|
31
|
+
"keywords": ["monok", "cms", "express", "webhook"],
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"express": "^4.19.2",
|
|
34
|
+
"node-fetch": "^3.3.2"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"express": ">=4.18.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/express": "^4.17.21",
|
|
41
|
+
"@types/node": "^22.5.4",
|
|
42
|
+
"rimraf": "^6.0.1",
|
|
43
|
+
"typescript": "^5.5.4"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
}
|
|
48
|
+
}
|