@upyo/plunk 0.3.0-dev.35
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 +20 -0
- package/README.md +93 -0
- package/dist/index.cjs +434 -0
- package/dist/index.d.cts +269 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +433 -0
- package/package.json +70 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
//#region src/config.ts
|
|
2
|
+
/**
|
|
3
|
+
* Creates a resolved Plunk configuration by applying default values to optional fields.
|
|
4
|
+
*
|
|
5
|
+
* This function takes a partial Plunk configuration and returns a complete
|
|
6
|
+
* configuration with all optional fields filled with sensible defaults.
|
|
7
|
+
* It is used internally by the Plunk transport.
|
|
8
|
+
*
|
|
9
|
+
* @param config - The Plunk configuration with optional fields
|
|
10
|
+
* @returns A resolved configuration with all defaults applied
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
function createPlunkConfig(config) {
|
|
14
|
+
return {
|
|
15
|
+
apiKey: config.apiKey,
|
|
16
|
+
baseUrl: config.baseUrl ?? "https://api.useplunk.com",
|
|
17
|
+
timeout: config.timeout ?? 3e4,
|
|
18
|
+
retries: config.retries ?? 3,
|
|
19
|
+
validateSsl: config.validateSsl ?? true,
|
|
20
|
+
headers: config.headers ?? {}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/http-client.ts
|
|
26
|
+
/**
|
|
27
|
+
* HTTP client wrapper for Plunk API requests.
|
|
28
|
+
*
|
|
29
|
+
* This class handles authentication, request formatting, error handling,
|
|
30
|
+
* and retry logic for the Plunk HTTP API.
|
|
31
|
+
*/
|
|
32
|
+
var PlunkHttpClient = class {
|
|
33
|
+
config;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a new Plunk HTTP client instance.
|
|
36
|
+
*
|
|
37
|
+
* @param config - Resolved Plunk configuration
|
|
38
|
+
*/
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.config = config;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sends a message via the Plunk API.
|
|
44
|
+
*
|
|
45
|
+
* This method makes a POST request to the `/v1/send` endpoint with proper
|
|
46
|
+
* authentication, retry logic, and error handling.
|
|
47
|
+
*
|
|
48
|
+
* @param emailData - The email data in Plunk API format
|
|
49
|
+
* @param signal - Optional AbortSignal for request cancellation
|
|
50
|
+
* @returns Promise that resolves to Plunk API response
|
|
51
|
+
* @throws PlunkError if the request fails after all retries
|
|
52
|
+
*/
|
|
53
|
+
async sendMessage(emailData, signal) {
|
|
54
|
+
const url = `${this.config.baseUrl}/v1/send`;
|
|
55
|
+
let lastError = null;
|
|
56
|
+
for (let attempt = 0; attempt <= this.config.retries; attempt++) {
|
|
57
|
+
signal?.throwIfAborted();
|
|
58
|
+
try {
|
|
59
|
+
const response = await this.makeRequest(url, emailData, signal);
|
|
60
|
+
return await this.parseResponse(response);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
63
|
+
if (error instanceof Error) {
|
|
64
|
+
if (error.name === "AbortError") throw error;
|
|
65
|
+
if (error.message.includes("status: 4")) throw this.createPlunkError(error.message, 400);
|
|
66
|
+
}
|
|
67
|
+
if (attempt === this.config.retries) break;
|
|
68
|
+
const delay = Math.min(1e3 * Math.pow(2, attempt), 1e4);
|
|
69
|
+
await this.sleep(delay);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const errorMessage = lastError?.message ?? "Unknown error occurred";
|
|
73
|
+
throw this.createPlunkError(errorMessage);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Makes an HTTP request to the Plunk API.
|
|
77
|
+
*
|
|
78
|
+
* @param url - The request URL
|
|
79
|
+
* @param emailData - The email data to send
|
|
80
|
+
* @param signal - Optional AbortSignal for cancellation
|
|
81
|
+
* @returns Promise that resolves to the Response object
|
|
82
|
+
*/
|
|
83
|
+
async makeRequest(url, emailData, signal) {
|
|
84
|
+
const headers = {
|
|
85
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
86
|
+
"Content-Type": "application/json",
|
|
87
|
+
...this.config.headers
|
|
88
|
+
};
|
|
89
|
+
const response = await fetch(url, {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers,
|
|
92
|
+
body: JSON.stringify(emailData),
|
|
93
|
+
signal,
|
|
94
|
+
...this.config.timeout > 0 && typeof globalThis.AbortSignal?.timeout === "function" ? { signal: AbortSignal.any([signal, AbortSignal.timeout(this.config.timeout)].filter(Boolean)) } : {}
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
let errorBody;
|
|
98
|
+
try {
|
|
99
|
+
errorBody = await response.text();
|
|
100
|
+
} catch {
|
|
101
|
+
errorBody = "Failed to read error response";
|
|
102
|
+
}
|
|
103
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}. ${errorBody}`);
|
|
104
|
+
}
|
|
105
|
+
return response;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Parses the response from the Plunk API.
|
|
109
|
+
*
|
|
110
|
+
* @param response - The Response object from fetch
|
|
111
|
+
* @returns Promise that resolves to parsed PlunkResponse
|
|
112
|
+
*/
|
|
113
|
+
async parseResponse(response) {
|
|
114
|
+
try {
|
|
115
|
+
const data = await response.json();
|
|
116
|
+
if (typeof data !== "object" || data === null) throw new Error("Invalid response format: expected object");
|
|
117
|
+
if (typeof data.success !== "boolean") throw new Error("Invalid response format: missing success field");
|
|
118
|
+
if (!data.success) throw new Error(data.message ?? "Send operation failed without error details");
|
|
119
|
+
return data;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (error instanceof SyntaxError) throw new Error("Invalid JSON response from Plunk API");
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Creates a PlunkError from an error message and optional status code.
|
|
127
|
+
*
|
|
128
|
+
* @param message - The error message
|
|
129
|
+
* @param statusCode - Optional HTTP status code
|
|
130
|
+
* @returns PlunkError instance
|
|
131
|
+
*/
|
|
132
|
+
createPlunkError(message, statusCode) {
|
|
133
|
+
return {
|
|
134
|
+
message,
|
|
135
|
+
statusCode
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Sleeps for the specified number of milliseconds.
|
|
140
|
+
*
|
|
141
|
+
* @param ms - Milliseconds to sleep
|
|
142
|
+
* @returns Promise that resolves after the delay
|
|
143
|
+
*/
|
|
144
|
+
sleep(ms) {
|
|
145
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/message-converter.ts
|
|
151
|
+
/**
|
|
152
|
+
* Converts a Upyo message to Plunk API format.
|
|
153
|
+
*
|
|
154
|
+
* This function transforms the standardized Upyo message format into the
|
|
155
|
+
* format expected by the Plunk HTTP API, handling email addresses,
|
|
156
|
+
* content conversion, attachments, and headers.
|
|
157
|
+
*
|
|
158
|
+
* @param message - The Upyo message to convert
|
|
159
|
+
* @param config - Resolved Plunk configuration
|
|
160
|
+
* @returns Promise that resolves to Plunk-formatted email data
|
|
161
|
+
*/
|
|
162
|
+
async function convertMessage(message, _config) {
|
|
163
|
+
const recipients = message.recipients.map((addr) => addr.address);
|
|
164
|
+
const to = recipients.length === 1 ? recipients[0] : recipients;
|
|
165
|
+
const senderName = message.sender.name;
|
|
166
|
+
const senderEmail = message.sender.address;
|
|
167
|
+
const replyTo = message.replyRecipients.length > 0 ? message.replyRecipients[0].address : void 0;
|
|
168
|
+
let body;
|
|
169
|
+
if ("html" in message.content && message.content.html) body = message.content.html;
|
|
170
|
+
else if ("text" in message.content && message.content.text) body = message.content.text;
|
|
171
|
+
else body = "";
|
|
172
|
+
const customHeaders = {};
|
|
173
|
+
if (message.headers) for (const [key, value] of message.headers.entries()) {
|
|
174
|
+
const lowerKey = key.toLowerCase();
|
|
175
|
+
if (![
|
|
176
|
+
"to",
|
|
177
|
+
"from",
|
|
178
|
+
"reply-to",
|
|
179
|
+
"subject",
|
|
180
|
+
"content-type"
|
|
181
|
+
].includes(lowerKey)) customHeaders[lowerKey] = value;
|
|
182
|
+
}
|
|
183
|
+
const attachments = [];
|
|
184
|
+
const maxAttachments = Math.min(message.attachments.length, 5);
|
|
185
|
+
for (let i = 0; i < maxAttachments; i++) {
|
|
186
|
+
const attachment = message.attachments[i];
|
|
187
|
+
const convertedAttachment = await convertAttachment(attachment);
|
|
188
|
+
if (convertedAttachment) attachments.push(convertedAttachment);
|
|
189
|
+
}
|
|
190
|
+
const plunkEmail = {
|
|
191
|
+
to,
|
|
192
|
+
subject: message.subject,
|
|
193
|
+
body,
|
|
194
|
+
subscribed: false
|
|
195
|
+
};
|
|
196
|
+
if (senderName) plunkEmail.name = senderName;
|
|
197
|
+
if (senderEmail) plunkEmail.from = senderEmail;
|
|
198
|
+
if (replyTo) plunkEmail.reply = replyTo;
|
|
199
|
+
if (Object.keys(customHeaders).length > 0) plunkEmail.headers = customHeaders;
|
|
200
|
+
if (attachments.length > 0) plunkEmail.attachments = attachments;
|
|
201
|
+
return plunkEmail;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Converts a Upyo attachment to Plunk format.
|
|
205
|
+
*
|
|
206
|
+
* @param attachment - The Upyo attachment to convert
|
|
207
|
+
* @returns Promise that resolves to Plunk attachment or null if conversion fails
|
|
208
|
+
*/
|
|
209
|
+
async function convertAttachment(attachment) {
|
|
210
|
+
try {
|
|
211
|
+
const content = await attachment.content;
|
|
212
|
+
const base64Content = arrayBufferToBase64(content);
|
|
213
|
+
return {
|
|
214
|
+
filename: attachment.filename,
|
|
215
|
+
content: base64Content,
|
|
216
|
+
type: attachment.contentType
|
|
217
|
+
};
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.warn(`Failed to convert attachment ${attachment.filename}:`, error);
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Converts an ArrayBuffer or Uint8Array to base64 string.
|
|
225
|
+
*
|
|
226
|
+
* @param buffer - The buffer to convert
|
|
227
|
+
* @returns Base64 encoded string
|
|
228
|
+
*/
|
|
229
|
+
function arrayBufferToBase64(buffer) {
|
|
230
|
+
if (typeof btoa !== "undefined") {
|
|
231
|
+
const binaryString = Array.from(buffer, (byte) => String.fromCharCode(byte)).join("");
|
|
232
|
+
return btoa(binaryString);
|
|
233
|
+
}
|
|
234
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
235
|
+
let result = "";
|
|
236
|
+
let i = 0;
|
|
237
|
+
while (i < buffer.length) {
|
|
238
|
+
const a = buffer[i++];
|
|
239
|
+
const b = i < buffer.length ? buffer[i++] : 0;
|
|
240
|
+
const c = i < buffer.length ? buffer[i++] : 0;
|
|
241
|
+
const bitmap = a << 16 | b << 8 | c;
|
|
242
|
+
result += chars.charAt(bitmap >> 18 & 63);
|
|
243
|
+
result += chars.charAt(bitmap >> 12 & 63);
|
|
244
|
+
result += i - 2 < buffer.length ? chars.charAt(bitmap >> 6 & 63) : "=";
|
|
245
|
+
result += i - 1 < buffer.length ? chars.charAt(bitmap & 63) : "=";
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/plunk-transport.ts
|
|
252
|
+
/**
|
|
253
|
+
* Plunk transport implementation for sending emails via Plunk API.
|
|
254
|
+
*
|
|
255
|
+
* This transport provides efficient email delivery using Plunk's HTTP API,
|
|
256
|
+
* with support for both cloud-hosted and self-hosted instances, authentication,
|
|
257
|
+
* retry logic, and batch sending capabilities.
|
|
258
|
+
*
|
|
259
|
+
* @example
|
|
260
|
+
* ```typescript
|
|
261
|
+
* import { PlunkTransport } from '@upyo/plunk';
|
|
262
|
+
*
|
|
263
|
+
* const transport = new PlunkTransport({
|
|
264
|
+
* apiKey: 'your-plunk-api-key',
|
|
265
|
+
* baseUrl: 'https://api.useplunk.com', // or self-hosted URL
|
|
266
|
+
* timeout: 30000,
|
|
267
|
+
* retries: 3
|
|
268
|
+
* });
|
|
269
|
+
*
|
|
270
|
+
* const receipt = await transport.send(message);
|
|
271
|
+
* if (receipt.successful) {
|
|
272
|
+
* console.log('Message sent with ID:', receipt.messageId);
|
|
273
|
+
* } else {
|
|
274
|
+
* console.error('Send failed:', receipt.errorMessages.join(', '));
|
|
275
|
+
* }
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
var PlunkTransport = class {
|
|
279
|
+
/**
|
|
280
|
+
* The resolved Plunk configuration used by this transport.
|
|
281
|
+
*/
|
|
282
|
+
config;
|
|
283
|
+
httpClient;
|
|
284
|
+
/**
|
|
285
|
+
* Creates a new Plunk transport instance.
|
|
286
|
+
*
|
|
287
|
+
* @param config Plunk configuration including API key and options.
|
|
288
|
+
*/
|
|
289
|
+
constructor(config) {
|
|
290
|
+
this.config = createPlunkConfig(config);
|
|
291
|
+
this.httpClient = new PlunkHttpClient(this.config);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Sends a single email message via Plunk API.
|
|
295
|
+
*
|
|
296
|
+
* This method converts the message to Plunk format, makes an HTTP request
|
|
297
|
+
* to the Plunk API, and returns a receipt with the result.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* ```typescript
|
|
301
|
+
* const receipt = await transport.send({
|
|
302
|
+
* sender: { address: 'from@example.com' },
|
|
303
|
+
* recipients: [{ address: 'to@example.com' }],
|
|
304
|
+
* ccRecipients: [],
|
|
305
|
+
* bccRecipients: [],
|
|
306
|
+
* replyRecipients: [],
|
|
307
|
+
* subject: 'Hello',
|
|
308
|
+
* content: { text: 'Hello World!' },
|
|
309
|
+
* attachments: [],
|
|
310
|
+
* priority: 'normal',
|
|
311
|
+
* tags: [],
|
|
312
|
+
* headers: new Headers()
|
|
313
|
+
* });
|
|
314
|
+
*
|
|
315
|
+
* if (receipt.successful) {
|
|
316
|
+
* console.log('Message sent successfully');
|
|
317
|
+
* }
|
|
318
|
+
* ```
|
|
319
|
+
*
|
|
320
|
+
* @param message The email message to send.
|
|
321
|
+
* @param options Optional transport options including `AbortSignal` for
|
|
322
|
+
* cancellation.
|
|
323
|
+
* @returns A promise that resolves to a receipt indicating success or
|
|
324
|
+
* failure.
|
|
325
|
+
*/
|
|
326
|
+
async send(message, options) {
|
|
327
|
+
try {
|
|
328
|
+
options?.signal?.throwIfAborted();
|
|
329
|
+
const emailData = await convertMessage(message, this.config);
|
|
330
|
+
options?.signal?.throwIfAborted();
|
|
331
|
+
const response = await this.httpClient.sendMessage(emailData, options?.signal);
|
|
332
|
+
const messageId = this.extractMessageId(response, message);
|
|
333
|
+
return {
|
|
334
|
+
successful: true,
|
|
335
|
+
messageId
|
|
336
|
+
};
|
|
337
|
+
} catch (error) {
|
|
338
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
339
|
+
return {
|
|
340
|
+
successful: false,
|
|
341
|
+
errorMessages: [errorMessage]
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Sends multiple email messages efficiently via Plunk API.
|
|
347
|
+
*
|
|
348
|
+
* This method sends each message individually but provides a streamlined
|
|
349
|
+
* interface for processing multiple messages. Each message is sent as a
|
|
350
|
+
* separate API request to Plunk.
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* const messages = [
|
|
355
|
+
* {
|
|
356
|
+
* sender: { address: 'from@example.com' },
|
|
357
|
+
* recipients: [{ address: 'user1@example.com' }],
|
|
358
|
+
* ccRecipients: [],
|
|
359
|
+
* bccRecipients: [],
|
|
360
|
+
* replyRecipients: [],
|
|
361
|
+
* subject: 'Message 1',
|
|
362
|
+
* content: { text: 'Hello User 1!' },
|
|
363
|
+
* attachments: [],
|
|
364
|
+
* priority: 'normal',
|
|
365
|
+
* tags: [],
|
|
366
|
+
* headers: new Headers()
|
|
367
|
+
* },
|
|
368
|
+
* {
|
|
369
|
+
* sender: { address: 'from@example.com' },
|
|
370
|
+
* recipients: [{ address: 'user2@example.com' }],
|
|
371
|
+
* ccRecipients: [],
|
|
372
|
+
* bccRecipients: [],
|
|
373
|
+
* replyRecipients: [],
|
|
374
|
+
* subject: 'Message 2',
|
|
375
|
+
* content: { text: 'Hello User 2!' },
|
|
376
|
+
* attachments: [],
|
|
377
|
+
* priority: 'normal',
|
|
378
|
+
* tags: [],
|
|
379
|
+
* headers: new Headers()
|
|
380
|
+
* }
|
|
381
|
+
* ];
|
|
382
|
+
*
|
|
383
|
+
* for await (const receipt of transport.sendMany(messages)) {
|
|
384
|
+
* if (receipt.successful) {
|
|
385
|
+
* console.log('Sent:', receipt.messageId);
|
|
386
|
+
* } else {
|
|
387
|
+
* console.error('Failed:', receipt.errorMessages);
|
|
388
|
+
* }
|
|
389
|
+
* }
|
|
390
|
+
* ```
|
|
391
|
+
*
|
|
392
|
+
* @param messages An iterable or async iterable of messages to send.
|
|
393
|
+
* @param options Optional transport options including `AbortSignal` for
|
|
394
|
+
* cancellation.
|
|
395
|
+
* @returns An async iterable of receipts, one for each message.
|
|
396
|
+
*/
|
|
397
|
+
async *sendMany(messages, options) {
|
|
398
|
+
options?.signal?.throwIfAborted();
|
|
399
|
+
const isAsyncIterable = Symbol.asyncIterator in messages;
|
|
400
|
+
if (isAsyncIterable) for await (const message of messages) {
|
|
401
|
+
options?.signal?.throwIfAborted();
|
|
402
|
+
yield await this.send(message, options);
|
|
403
|
+
}
|
|
404
|
+
else for (const message of messages) {
|
|
405
|
+
options?.signal?.throwIfAborted();
|
|
406
|
+
yield await this.send(message, options);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Extracts or generates a message ID from the Plunk response.
|
|
411
|
+
*
|
|
412
|
+
* Plunk returns email details in the response, so we can use the contact ID
|
|
413
|
+
* and timestamp to create a meaningful message ID.
|
|
414
|
+
*
|
|
415
|
+
* @param response The Plunk API response.
|
|
416
|
+
* @param message The original message for fallback ID generation.
|
|
417
|
+
* @returns A message ID string.
|
|
418
|
+
*/
|
|
419
|
+
extractMessageId(response, message) {
|
|
420
|
+
if (response.emails && response.emails.length > 0) {
|
|
421
|
+
const contactId = response.emails[0].contact?.id;
|
|
422
|
+
const timestamp$1 = response.timestamp;
|
|
423
|
+
if (contactId && timestamp$1) return `plunk-${contactId}-${new Date(timestamp$1).getTime()}`;
|
|
424
|
+
}
|
|
425
|
+
const timestamp = Date.now();
|
|
426
|
+
const recipientHash = message.recipients[0]?.address.split("@")[0].substring(0, 8) ?? "unknown";
|
|
427
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
428
|
+
return `plunk-${timestamp}-${recipientHash}-${random}`;
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
//#endregion
|
|
433
|
+
export { PlunkTransport };
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@upyo/plunk",
|
|
3
|
+
"version": "0.3.0-dev.35+502fb5e8",
|
|
4
|
+
"description": "Plunk transport for Upyo email library",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"email",
|
|
7
|
+
"mail",
|
|
8
|
+
"sendmail",
|
|
9
|
+
"plunk"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": {
|
|
13
|
+
"name": "Hong Minhee",
|
|
14
|
+
"email": "hong@minhee.org",
|
|
15
|
+
"url": "https://hongminhee.org/"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://upyo.org/transports/plunk",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/dahlia/upyo.git",
|
|
21
|
+
"directory": "packages/plunk/"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/dahlia/upyo/issues"
|
|
25
|
+
},
|
|
26
|
+
"funding": [
|
|
27
|
+
"https://github.com/sponsors/dahlia"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20.0.0",
|
|
31
|
+
"bun": ">=1.2.0",
|
|
32
|
+
"deno": ">=2.3.0"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist/",
|
|
36
|
+
"package.json",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"type": "module",
|
|
40
|
+
"module": "./dist/index.js",
|
|
41
|
+
"main": "./dist/index.cjs",
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"exports": {
|
|
44
|
+
".": {
|
|
45
|
+
"types": {
|
|
46
|
+
"import": "./dist/index.d.ts",
|
|
47
|
+
"require": "./dist/index.d.cts"
|
|
48
|
+
},
|
|
49
|
+
"import": "./dist/index.js",
|
|
50
|
+
"require": "./dist/index.cjs"
|
|
51
|
+
},
|
|
52
|
+
"./package.json": "./package.json"
|
|
53
|
+
},
|
|
54
|
+
"sideEffects": false,
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@upyo/core": "0.3.0-dev.35+502fb5e8"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@dotenvx/dotenvx": "^1.47.3",
|
|
60
|
+
"tsdown": "^0.12.7",
|
|
61
|
+
"typescript": "5.8.3"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"build": "tsdown",
|
|
65
|
+
"prepublish": "tsdown",
|
|
66
|
+
"test": "tsdown && dotenvx run --ignore=MISSING_ENV_FILE -- node --experimental-transform-types --test",
|
|
67
|
+
"test:bun": "tsdown && bun test --timeout=30000 --env-file=.env",
|
|
68
|
+
"test:deno": "deno test --allow-env --allow-net --env-file=.env"
|
|
69
|
+
}
|
|
70
|
+
}
|