medusa-plugin-cloudflare-images 0.1.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.
@@ -0,0 +1,2 @@
1
+ declare const _default: import("@medusajs/types").ModuleProviderExports;
2
+ export default _default;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const utils_1 = require("@medusajs/framework/utils");
7
+ const service_1 = __importDefault(require("./service"));
8
+ exports.default = (0, utils_1.ModuleProvider)(utils_1.Modules.FILE, {
9
+ services: [service_1.default],
10
+ });
11
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvcHJvdmlkZXJzL2Nsb3VkZmxhcmUtaW1hZ2VzL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBQUEscURBQW1FO0FBRW5FLHdEQUEyRDtBQUUzRCxrQkFBZSxJQUFBLHNCQUFjLEVBQUMsZUFBTyxDQUFDLElBQUksRUFBRTtJQUMxQyxRQUFRLEVBQUUsQ0FBQyxpQkFBbUMsQ0FBQztDQUNoRCxDQUFDLENBQUEifQ==
@@ -0,0 +1,79 @@
1
+ import type { FileTypes, Logger } from "@medusajs/framework/types";
2
+ import { AbstractFileProviderService } from "@medusajs/framework/utils";
3
+ import { PassThrough, Readable } from "node:stream";
4
+ type InjectedDependencies = {
5
+ logger: Logger;
6
+ };
7
+ type CloudflareImagesBaseOptions = {
8
+ account_id: string;
9
+ api_token: string;
10
+ variant?: string;
11
+ prefix?: string;
12
+ };
13
+ type CloudflareImagesDeliveryOptions = {
14
+ account_hash: string;
15
+ delivery_url?: string;
16
+ } | {
17
+ account_hash?: string;
18
+ delivery_url: string;
19
+ };
20
+ type CloudflareImagesFileProviderOptions = CloudflareImagesBaseOptions & CloudflareImagesDeliveryOptions;
21
+ type PartialCloudflareImagesFileProviderOptions = Partial<CloudflareImagesBaseOptions & {
22
+ account_hash: string;
23
+ delivery_url: string;
24
+ }>;
25
+ type NormalizedCloudflareImagesFileProviderOptions = {
26
+ account_id: string;
27
+ api_token: string;
28
+ account_hash?: string;
29
+ delivery_url?: string;
30
+ variant: string;
31
+ prefix: string;
32
+ };
33
+ type CloudflareImagesError = {
34
+ code?: number;
35
+ message?: string;
36
+ };
37
+ type CloudflareImageResult = {
38
+ id: string;
39
+ filename?: string;
40
+ variants?: string[];
41
+ };
42
+ type UploadBufferInput = {
43
+ content: Buffer;
44
+ filename: string;
45
+ mimeType: string;
46
+ fileKey?: string;
47
+ };
48
+ declare class CloudflareImagesFileProviderService extends AbstractFileProviderService {
49
+ static identifier: string;
50
+ protected readonly logger_: Logger;
51
+ protected readonly options_: NormalizedCloudflareImagesFileProviderOptions;
52
+ constructor({ logger }: InjectedDependencies, options: CloudflareImagesFileProviderOptions);
53
+ static validateOptions(options: PartialCloudflareImagesFileProviderOptions): void;
54
+ upload(file: FileTypes.ProviderUploadFileDTO): Promise<FileTypes.ProviderFileResultDTO>;
55
+ getUploadStream(fileData: FileTypes.ProviderUploadStreamDTO): Promise<{
56
+ writeStream: PassThrough;
57
+ promise: Promise<FileTypes.ProviderFileResultDTO>;
58
+ url: string;
59
+ fileKey: string;
60
+ }>;
61
+ delete(files: FileTypes.ProviderDeleteFileDTO | FileTypes.ProviderDeleteFileDTO[]): Promise<void>;
62
+ getPresignedDownloadUrl(fileData: FileTypes.ProviderGetFileDTO): Promise<string>;
63
+ getPresignedUploadUrl(fileData: FileTypes.ProviderGetPresignedUploadUrlDTO): Promise<FileTypes.ProviderFileResultDTO>;
64
+ getDownloadStream(fileData: FileTypes.ProviderGetFileDTO): Promise<Readable>;
65
+ getAsBuffer(fileData: FileTypes.ProviderGetFileDTO): Promise<Buffer>;
66
+ protected uploadBuffer(input: UploadBufferInput): Promise<CloudflareImageResult>;
67
+ protected parseCloudflareResponse<T>(response: Response, action: string): Promise<T>;
68
+ protected streamToBuffer(stream: Readable): Promise<Buffer>;
69
+ protected createCustomFileKey(): string | undefined;
70
+ protected getDeliveryUrl(fileKey: string): string;
71
+ protected getExpiry(expiresIn: number): string;
72
+ protected normalizePrefix(prefix?: string): string;
73
+ protected formatCloudflareErrors(errors?: CloudflareImagesError[]): string;
74
+ protected imagesEndpoint(): string;
75
+ protected directUploadEndpoint(): string;
76
+ protected imageEndpoint(fileKey: string): string;
77
+ protected authHeaders(): HeadersInit;
78
+ }
79
+ export default CloudflareImagesFileProviderService;
@@ -0,0 +1,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("@medusajs/framework/utils");
4
+ const node_crypto_1 = require("node:crypto");
5
+ const node_stream_1 = require("node:stream");
6
+ class CloudflareImagesFileProviderService extends utils_1.AbstractFileProviderService {
7
+ constructor({ logger }, options) {
8
+ super();
9
+ this.logger_ = logger;
10
+ this.options_ = {
11
+ account_id: options.account_id,
12
+ api_token: options.api_token,
13
+ account_hash: options.account_hash,
14
+ delivery_url: options.delivery_url?.replace(/\/$/, ""),
15
+ variant: options.variant || "public",
16
+ prefix: this.normalizePrefix(options.prefix),
17
+ };
18
+ }
19
+ static validateOptions(options) {
20
+ if (!options.account_id) {
21
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Cloudflare Images provider requires account_id");
22
+ }
23
+ if (!options.api_token) {
24
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Cloudflare Images provider requires api_token");
25
+ }
26
+ if (!options.account_hash && !options.delivery_url) {
27
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Cloudflare Images provider requires account_hash or delivery_url");
28
+ }
29
+ }
30
+ async upload(file) {
31
+ if (!file?.filename) {
32
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No filename provided");
33
+ }
34
+ if (!file.content) {
35
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No file content provided");
36
+ }
37
+ const content = Buffer.from(file.content, "base64");
38
+ const result = await this.uploadBuffer({
39
+ content,
40
+ filename: file.filename,
41
+ mimeType: file.mimeType,
42
+ fileKey: this.createCustomFileKey(),
43
+ });
44
+ return {
45
+ key: result.id,
46
+ url: this.getDeliveryUrl(result.id),
47
+ };
48
+ }
49
+ async getUploadStream(fileData) {
50
+ if (!fileData?.filename) {
51
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No filename provided");
52
+ }
53
+ const fileKey = this.createCustomFileKey();
54
+ if (!fileKey) {
55
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_ALLOWED, "Cloudflare Images upload streams require the prefix option so the file key can be known before upload");
56
+ }
57
+ const writeStream = new node_stream_1.PassThrough();
58
+ const url = this.getDeliveryUrl(fileKey);
59
+ const promise = this.streamToBuffer(writeStream)
60
+ .then((content) => this.uploadBuffer({
61
+ content,
62
+ filename: fileData.filename,
63
+ mimeType: fileData.mimeType,
64
+ fileKey,
65
+ }))
66
+ .then((result) => ({
67
+ key: result.id,
68
+ url: this.getDeliveryUrl(result.id),
69
+ }));
70
+ return {
71
+ writeStream,
72
+ promise,
73
+ url,
74
+ fileKey,
75
+ };
76
+ }
77
+ async delete(files) {
78
+ const fileArray = Array.isArray(files) ? files : [files];
79
+ await Promise.all(fileArray.map(async (file) => {
80
+ if (!file?.fileKey) {
81
+ return;
82
+ }
83
+ const response = await fetch(this.imageEndpoint(file.fileKey), {
84
+ method: "DELETE",
85
+ headers: this.authHeaders(),
86
+ });
87
+ if (response.status === 404) {
88
+ this.logger_.warn(`Cloudflare image ${file.fileKey} was already deleted`);
89
+ return;
90
+ }
91
+ await this.parseCloudflareResponse(response, `delete Cloudflare image ${file.fileKey}`);
92
+ }));
93
+ }
94
+ async getPresignedDownloadUrl(fileData) {
95
+ if (!fileData?.fileKey) {
96
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No fileKey provided");
97
+ }
98
+ return this.getDeliveryUrl(fileData.fileKey);
99
+ }
100
+ async getPresignedUploadUrl(fileData) {
101
+ if (!fileData?.filename) {
102
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No filename provided");
103
+ }
104
+ const fileKey = this.createCustomFileKey();
105
+ const formData = new FormData();
106
+ formData.append("requireSignedURLs", "false");
107
+ if (fileKey) {
108
+ formData.append("id", fileKey);
109
+ }
110
+ if (fileData.expiresIn) {
111
+ formData.append("expiry", this.getExpiry(fileData.expiresIn));
112
+ }
113
+ const response = await fetch(this.directUploadEndpoint(), {
114
+ method: "POST",
115
+ headers: this.authHeaders(),
116
+ body: formData,
117
+ });
118
+ const result = await this.parseCloudflareResponse(response, "create Cloudflare direct upload URL");
119
+ return {
120
+ key: result.id,
121
+ url: result.uploadURL,
122
+ };
123
+ }
124
+ async getDownloadStream(fileData) {
125
+ const buffer = await this.getAsBuffer(fileData);
126
+ return node_stream_1.Readable.from(buffer);
127
+ }
128
+ async getAsBuffer(fileData) {
129
+ if (!fileData?.fileKey) {
130
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "No fileKey provided");
131
+ }
132
+ const response = await fetch(this.getDeliveryUrl(fileData.fileKey));
133
+ if (!response.ok) {
134
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Failed to fetch Cloudflare image ${fileData.fileKey}: ${response.status} ${response.statusText}`);
135
+ }
136
+ return Buffer.from(await response.arrayBuffer());
137
+ }
138
+ async uploadBuffer(input) {
139
+ const formData = new FormData();
140
+ const blob = new Blob([new Uint8Array(input.content)], { type: input.mimeType });
141
+ formData.append("file", blob, input.filename);
142
+ formData.append("requireSignedURLs", "false");
143
+ if (input.fileKey) {
144
+ formData.append("id", input.fileKey);
145
+ }
146
+ formData.append("metadata", JSON.stringify({
147
+ original_filename: input.filename,
148
+ source: "medusa",
149
+ }));
150
+ const response = await fetch(this.imagesEndpoint(), {
151
+ method: "POST",
152
+ headers: this.authHeaders(),
153
+ body: formData,
154
+ });
155
+ return await this.parseCloudflareResponse(response, "upload Cloudflare image");
156
+ }
157
+ async parseCloudflareResponse(response, action) {
158
+ const body = await response.text();
159
+ let payload;
160
+ try {
161
+ payload = body ? JSON.parse(body) : undefined;
162
+ }
163
+ catch {
164
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Cloudflare Images failed to ${action}: invalid JSON response (${response.status})`);
165
+ }
166
+ if (!response.ok || !payload?.success) {
167
+ const details = this.formatCloudflareErrors(payload?.errors) || body;
168
+ throw new utils_1.MedusaError(utils_1.MedusaError.Types.UNEXPECTED_STATE, `Cloudflare Images failed to ${action}: ${response.status} ${details}`);
169
+ }
170
+ return payload.result;
171
+ }
172
+ async streamToBuffer(stream) {
173
+ const chunks = [];
174
+ for await (const chunk of stream) {
175
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
176
+ }
177
+ return Buffer.concat(chunks);
178
+ }
179
+ createCustomFileKey() {
180
+ if (!this.options_.prefix) {
181
+ return undefined;
182
+ }
183
+ return `${this.options_.prefix}${(0, node_crypto_1.randomUUID)()}`;
184
+ }
185
+ getDeliveryUrl(fileKey) {
186
+ const encodedKey = fileKey.split("/").map(encodeURIComponent).join("/");
187
+ const baseUrl = this.options_.delivery_url ||
188
+ `https://imagedelivery.net/${this.options_.account_hash}`;
189
+ return `${baseUrl}/${encodedKey}/${this.options_.variant}`;
190
+ }
191
+ getExpiry(expiresIn) {
192
+ const minSeconds = 2 * 60;
193
+ const maxSeconds = 6 * 60 * 60;
194
+ const seconds = Math.min(Math.max(expiresIn, minSeconds), maxSeconds);
195
+ return new Date(Date.now() + seconds * 1000).toISOString();
196
+ }
197
+ normalizePrefix(prefix) {
198
+ if (!prefix) {
199
+ return "";
200
+ }
201
+ return prefix.endsWith("/") ? prefix : `${prefix}/`;
202
+ }
203
+ formatCloudflareErrors(errors) {
204
+ if (!errors?.length) {
205
+ return "";
206
+ }
207
+ return errors
208
+ .map((error) => [error.code, error.message].filter((part) => part !== undefined).join(": "))
209
+ .join("; ");
210
+ }
211
+ imagesEndpoint() {
212
+ return `https://api.cloudflare.com/client/v4/accounts/${this.options_.account_id}/images/v1`;
213
+ }
214
+ directUploadEndpoint() {
215
+ return `https://api.cloudflare.com/client/v4/accounts/${this.options_.account_id}/images/v2/direct_upload`;
216
+ }
217
+ imageEndpoint(fileKey) {
218
+ return `${this.imagesEndpoint()}/${encodeURIComponent(fileKey)}`;
219
+ }
220
+ authHeaders() {
221
+ return {
222
+ Authorization: `Bearer ${this.options_.api_token}`,
223
+ };
224
+ }
225
+ }
226
+ CloudflareImagesFileProviderService.identifier = "cloudflare-images";
227
+ exports.default = CloudflareImagesFileProviderService;
228
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9wcm92aWRlcnMvY2xvdWRmbGFyZS1pbWFnZXMvc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUNBLHFEQUFvRjtBQUNwRiw2Q0FBd0M7QUFDeEMsNkNBQW1EO0FBd0VuRCxNQUFNLG1DQUFvQyxTQUFRLG1DQUEyQjtJQU0zRSxZQUFZLEVBQUUsTUFBTSxFQUF3QixFQUFFLE9BQTRDO1FBQ3hGLEtBQUssRUFBRSxDQUFBO1FBRVAsSUFBSSxDQUFDLE9BQU8sR0FBRyxNQUFNLENBQUE7UUFDckIsSUFBSSxDQUFDLFFBQVEsR0FBRztZQUNkLFVBQVUsRUFBRSxPQUFPLENBQUMsVUFBVTtZQUM5QixTQUFTLEVBQUUsT0FBTyxDQUFDLFNBQVM7WUFDNUIsWUFBWSxFQUFFLE9BQU8sQ0FBQyxZQUFZO1lBQ2xDLFlBQVksRUFBRSxPQUFPLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDO1lBQ3RELE9BQU8sRUFBRSxPQUFPLENBQUMsT0FBTyxJQUFJLFFBQVE7WUFDcEMsTUFBTSxFQUFFLElBQUksQ0FBQyxlQUFlLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQztTQUM3QyxDQUFBO0lBQ0gsQ0FBQztJQUVELE1BQU0sQ0FBQyxlQUFlLENBQUMsT0FBbUQ7UUFDeEUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsQ0FBQztZQUN4QixNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUM5QixnREFBZ0QsQ0FDakQsQ0FBQTtRQUNILENBQUM7UUFFRCxJQUFJLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxDQUFDO1lBQ3ZCLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLCtDQUErQyxDQUNoRCxDQUFBO1FBQ0gsQ0FBQztR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
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # Medusa Cloudflare Images File Provider
2
+
3
+ Cloudflare Images File Module Provider for Medusa v2.
4
+
5
+ This provider stores Medusa uploads in Cloudflare Images and returns delivery
6
+ URLs that can be used by product media and custom file workflows.
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pnpm add medusa-plugin-cloudflare-images
12
+ ```
13
+
14
+ Use the equivalent install command for npm, yarn, or bun if needed.
15
+
16
+ ## Configuration
17
+
18
+ Register the provider in `medusa-config.ts`:
19
+
20
+ ```ts
21
+ import { defineConfig, loadEnv } from "@medusajs/framework/utils"
22
+
23
+ loadEnv(process.env.NODE_ENV || "development", process.cwd())
24
+
25
+ module.exports = defineConfig({
26
+ modules: [
27
+ {
28
+ resolve: "@medusajs/medusa/file",
29
+ options: {
30
+ providers: [
31
+ {
32
+ resolve: "medusa-plugin-cloudflare-images/providers/cloudflare-images",
33
+ id: "cloudflare-images",
34
+ options: {
35
+ account_id: process.env.CLOUDFLARE_IMAGES_ACCOUNT_ID,
36
+ api_token: process.env.CLOUDFLARE_IMAGES_API_TOKEN,
37
+ account_hash: process.env.CLOUDFLARE_IMAGES_ACCOUNT_HASH,
38
+ delivery_url: process.env.CLOUDFLARE_IMAGES_DELIVERY_URL,
39
+ variant: process.env.CLOUDFLARE_IMAGES_VARIANT || "public",
40
+ prefix: process.env.CLOUDFLARE_IMAGES_PREFIX,
41
+ },
42
+ },
43
+ ],
44
+ },
45
+ },
46
+ ],
47
+ })
48
+ ```
49
+
50
+ The Medusa File Module accepts one provider. Register this provider instead of
51
+ the local or S3 file provider.
52
+
53
+ ## Options
54
+
55
+ Required:
56
+
57
+ ```env
58
+ CLOUDFLARE_IMAGES_ACCOUNT_ID=
59
+ CLOUDFLARE_IMAGES_API_TOKEN=
60
+ ```
61
+
62
+ Delivery configuration, at least one is required:
63
+
64
+ ```env
65
+ CLOUDFLARE_IMAGES_ACCOUNT_HASH=
66
+ CLOUDFLARE_IMAGES_DELIVERY_URL=
67
+ ```
68
+
69
+ Optional:
70
+
71
+ ```env
72
+ CLOUDFLARE_IMAGES_VARIANT=public
73
+ CLOUDFLARE_IMAGES_PREFIX=products
74
+ ```
75
+
76
+ Set `CLOUDFLARE_IMAGES_DELIVERY_URL` when serving through a custom delivery base
77
+ URL. Otherwise the provider builds URLs as:
78
+
79
+ ```txt
80
+ https://imagedelivery.net/<account_hash>/<image_id>/<variant>
81
+ ```
82
+
83
+ `prefix` is optional for regular uploads and direct uploads. It is required for
84
+ stream uploads because Medusa expects the provider to return the final file key
85
+ and URL before the upload stream has completed.
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "medusa-plugin-cloudflare-images",
3
+ "version": "0.1.0",
4
+ "description": "Cloudflare Images File Module Provider for Medusa v2.",
5
+ "license": "MIT",
6
+ "keywords": [
7
+ "medusa-plugin-integration",
8
+ "medusa-v2",
9
+ "medusa-plugin-other",
10
+ "medusa",
11
+ "medusa-plugin",
12
+ "cloudflare",
13
+ "cloudflare-images",
14
+ "file-provider",
15
+ "image-storage"
16
+ ],
17
+ "homepage": "https://github.com/AlekseyMonakhov/medusa-plugin-cloudflare-images#readme",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/AlekseyMonakhov/medusa-plugin-cloudflare-images.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/AlekseyMonakhov/medusa-plugin-cloudflare-images/issues"
24
+ },
25
+ "files": [
26
+ ".medusa/server/src/providers",
27
+ "README.md"
28
+ ],
29
+ "main": "./.medusa/server/src/providers/cloudflare-images/index.js",
30
+ "types": "./.medusa/server/src/providers/cloudflare-images/index.d.ts",
31
+ "exports": {
32
+ "./package.json": "./package.json",
33
+ "./providers/*": {
34
+ "types": "./.medusa/server/src/providers/*/index.d.ts",
35
+ "default": "./.medusa/server/src/providers/*/index.js"
36
+ },
37
+ ".": {
38
+ "types": "./.medusa/server/src/providers/cloudflare-images/index.d.ts",
39
+ "default": "./.medusa/server/src/providers/cloudflare-images/index.js"
40
+ }
41
+ },
42
+ "scripts": {
43
+ "build": "medusa plugin:build",
44
+ "dev": "medusa plugin:develop",
45
+ "prepublishOnly": "medusa plugin:build"
46
+ },
47
+ "peerDependencies": {
48
+ "@medusajs/framework": ">=2.8.0 <3.0.0",
49
+ "@medusajs/medusa": ">=2.8.0 <3.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@medusajs/admin-shared": "2.15.5",
53
+ "@medusajs/cli": "2.15.5",
54
+ "@medusajs/framework": "2.15.5",
55
+ "@medusajs/medusa": "2.15.5",
56
+ "@swc/core": "^1.7.28",
57
+ "@types/node": "^20.12.11",
58
+ "ts-node": "^10.9.2",
59
+ "typescript": "^5.6.2"
60
+ },
61
+ "engines": {
62
+ "node": ">=20"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ },
67
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319"
68
+ }