@nocloud/sdk 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NoneM
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,146 @@
1
+ <div align="center">
2
+ <img src="https://assets.nonefivem.com/logo/dark-bg.png" alt="NoneM Logo" width="200" />
3
+
4
+ # @nocloud/sdk
5
+
6
+ **Official SDK for NoCloud services**
7
+
8
+ [![npm version](https://img.shields.io/npm/v/@nocloud/sdk?style=for-the-badge)](https://www.npmjs.com/package/@nocloud/sdk)
9
+ [![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
10
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge)](https://opensource.org/licenses/MIT)
11
+
12
+ </div>
13
+
14
+ ---
15
+
16
+ ## 🚀 Getting Started
17
+
18
+ ### Installation
19
+
20
+ ```bash
21
+ npm install @nocloud/sdk
22
+ # or
23
+ bun add @nocloud/sdk
24
+ # or
25
+ pnpm add @nocloud/sdk
26
+ ```
27
+
28
+ ### Quick Start
29
+
30
+ ```typescript
31
+ import { NoCloud } from "@nocloud/sdk";
32
+
33
+ const cloud = new NoCloud("your-api-key");
34
+
35
+ // Upload a file
36
+ const file = new File(["hello"], "hello.txt", { type: "text/plain" });
37
+ const { id, url } = await cloud.storage.upload(file);
38
+
39
+ console.log(`Uploaded: ${url}`);
40
+
41
+ // Delete a file
42
+ await cloud.storage.delete(id);
43
+ ```
44
+
45
+ ---
46
+
47
+ ## 📖 Usage
48
+
49
+ ### Initialize
50
+
51
+ ```typescript
52
+ import { NoCloud } from "@nocloud/sdk";
53
+
54
+ // Simple
55
+ const cloud = new NoCloud("your-api-key");
56
+
57
+ // With options
58
+ const cloud = new NoCloud({
59
+ apiKey: "your-api-key",
60
+ baseUrl: "https://api.nonefivem.com", // optional
61
+ retries: 3, // optional
62
+ retryDelayMs: 1000, // optional
63
+ });
64
+ ```
65
+
66
+ ### 📦 Storage
67
+
68
+ #### Upload a File
69
+
70
+ ```typescript
71
+ // From File/Blob
72
+ const file = new File(["content"], "file.txt", { type: "text/plain" });
73
+ const { id, url } = await cloud.storage.upload(file);
74
+
75
+ // From ArrayBuffer
76
+ const buffer = new ArrayBuffer(8);
77
+ const { id, url } = await cloud.storage.upload(buffer);
78
+
79
+ // From base64 string (auto-detects mime type)
80
+ const base64 = "iVBORw0KGgo..."; // PNG base64
81
+ const { id, url } = await cloud.storage.upload(base64);
82
+
83
+ // With metadata
84
+ const { id, url } = await cloud.storage.upload(file, {
85
+ userId: "123",
86
+ category: "avatars",
87
+ });
88
+ ```
89
+
90
+ #### Upload a Stream
91
+
92
+ ```typescript
93
+ const stream = getReadableStream();
94
+ const { id, url } = await cloud.storage.uploadStream(
95
+ stream,
96
+ "video/mp4",
97
+ fileSize,
98
+ );
99
+ ```
100
+
101
+ #### Delete a File
102
+
103
+ ```typescript
104
+ await cloud.storage.delete(mediaId);
105
+ ```
106
+
107
+ ---
108
+
109
+ ## 📋 Supported Body Types
110
+
111
+ | Type | Description |
112
+ | ------------- | -------------------- |
113
+ | `File` | Browser File object |
114
+ | `Blob` | Binary data |
115
+ | `ArrayBuffer` | Raw binary buffer |
116
+ | `string` | Base64 or plain text |
117
+
118
+ Base64 strings with data URLs (`data:image/png;base64,...`) or raw base64 are automatically detected and the mime type is inferred.
119
+
120
+ ---
121
+
122
+ ## ⚠️ Error Handling
123
+
124
+ ```typescript
125
+ import { NoCloud, NoCloudAPIError } from "@nocloud/sdk";
126
+
127
+ try {
128
+ await cloud.storage.upload(file);
129
+ } catch (error) {
130
+ if (error instanceof NoCloudAPIError) {
131
+ console.error(`API Error: ${error.message} (${error.status})`);
132
+ }
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 🔧 Compatibility
139
+
140
+ Works in both Node.js (>=18) and browser environments. No Node-specific APIs are used.
141
+
142
+ ---
143
+
144
+ ## 📄 License
145
+
146
+ [MIT](LICENSE) © [NoneM](https://nonefivem.com)
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ var{defineProperty:u,getOwnPropertyNames:j,getOwnPropertyDescriptor:v}=Object,G=Object.prototype.hasOwnProperty;var U=new WeakMap,M=(r)=>{var x=U.get(r),F;if(x)return x;if(x=u({},"__esModule",{value:!0}),r&&typeof r==="object"||typeof r==="function")j(r).map((f)=>!G.call(x,f)&&u(x,f,{get:()=>r[f],enumerable:!(F=v(r,f))||F.enumerable}));return U.set(r,x),x};var P=(r,x)=>{for(var F in x)u(r,F,{get:x[F],enumerable:!0,configurable:!0,set:(f)=>x[F]=()=>f})};var C={};P(C,{default:()=>w,NoCloudAPIError:()=>h,NoCloud:()=>_});module.exports=M(C);var V="https://api.nonefivem.com";class h extends Error{status;constructor(r,x){super(r);this.status=x;this.name="NoCloudAPIError"}}function k(r){return new Promise((x)=>setTimeout(x,r))}async function W(r,x=3,F=1000){let f;for(let K=0;K<=x;K++)try{return await r()}catch(c){if(f=c,K<x)await k(F)}throw f}var A={iVBORw0KGgo:"image/png","/9j/":"image/jpeg",R0lGOD:"image/gif",UklGR:"image/webp",AAAA:"video/mp4",JVBERi0:"application/pdf",UEsDB:"application/zip",PD94bWw:"application/xml",PHN2Zw:"image/svg+xml"};function X(r){let x=r.match(/^data:([^;,]+)/);if(x?.[1])return x[1];for(let[F,f]of Object.entries(A))if(r.startsWith(F))return f;return null}function Y(r){let x=r.match(/^data:[^;,]+;base64,(.+)$/);if(x?.[1])return x[1];return r}function Z(r){let x=(r.match(/=+$/)||[""])[0].length;return Math.floor(r.length*3/4)-x}class D{baseUrl;apiKey;retryCount;retryDelayMs;constructor(r){if(this.apiKey=r.apiKey,this.baseUrl=r.baseUrl||V,this.retryCount=r.retries??3,this.retryDelayMs=r.retryDelayMs??1000,!this.apiKey)throw Error("API key is required")}buildUrl(r){return`${this.baseUrl}/cloud/${r}`}fetch(r,x={}){let F=this.buildUrl(r);return W(()=>fetch(F,{...x,headers:{Authorization:`Bearer ${this.apiKey}`,...x.headers||{}}}),x.retries??this.retryCount,x.retryDelayMs??this.retryDelayMs)}}async function B(r){if(!r.ok)try{let x=await r.json();throw new h(x.message||"API Error",r.status)}catch(x){throw new h("Unknown error",r.status)}return await r.json()}class H{fetcher;constructor(r){this.fetcher=r}fetch(r,x){return this.fetcher.fetch(r,x)}}class Q extends H{async generateSignedUrl(r,x,F){let f=await this.fetch("storage/signed-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentType:r,size:x,metadata:F})});return B(f)}getBodyInfo(r){if(typeof Blob<"u"&&r instanceof Blob)return{contentType:r.type||"application/octet-stream",size:r.size};if(typeof ArrayBuffer<"u"&&r instanceof ArrayBuffer)return{contentType:"application/octet-stream",size:r.byteLength};if(typeof r==="string"){let x=X(r);if(x){let F=Y(r),f=Z(F);return{contentType:x,size:f}}return{contentType:"text/plain",size:new TextEncoder().encode(r).length}}throw new h("Unsupported body type",400)}async upload(r,x){let{contentType:F,size:f}=this.getBodyInfo(r),{url:K,mediaUrl:c,mediaId:$}=await this.generateSignedUrl(F,f,x),O=await fetch(K,{method:"PUT",headers:{"Content-Type":F,"Content-Length":f.toString()},body:r});if(!O.ok){let q=await O.text().catch(()=>O.statusText);throw new h(`Failed to upload file to R2: ${q}`,O.status)}return{id:$,url:c}}async uploadStream(r,x,F,f){let{url:K,mediaUrl:c,mediaId:$}=await this.generateSignedUrl(x,F,f),O=await fetch(K,{method:"PUT",headers:{"Content-Type":x,"Content-Length":F.toString()},body:r,duplex:"half"});if(!O.ok){let q=await O.text().catch(()=>O.statusText);throw new h(`Failed to upload stream to R2: ${q}`,O.status)}return{id:$,url:c}}async delete(r){let x=await this.fetch(`storage/${r}`,{method:"DELETE"});return B(x)}}class _{fetcher;constructor(r){if(typeof r==="string")r={apiKey:r};this.fetcher=new D({apiKey:r.apiKey,baseUrl:r.baseUrl,retries:r.retries,retryDelayMs:r.retryDelayMs})}_storage;get storage(){return this._storage??=new Q(this.fetcher)}}var w=_;
@@ -0,0 +1,185 @@
1
+ // Generated by dts-bundle-generator v9.5.1
2
+
3
+ export interface FetchOptionsBase {
4
+ retries?: number;
5
+ retryDelayMs?: number;
6
+ }
7
+ export interface FetcherOptions extends FetchOptionsBase {
8
+ apiKey: string;
9
+ baseUrl?: string;
10
+ }
11
+ export type FetchOptions = RequestInit & FetchOptionsBase;
12
+ declare class Fetcher {
13
+ private readonly baseUrl;
14
+ private readonly apiKey;
15
+ private readonly retryCount;
16
+ private readonly retryDelayMs;
17
+ constructor(options: FetcherOptions);
18
+ private buildUrl;
19
+ fetch(endpoint: string, options?: FetchOptions): Promise<Response>;
20
+ }
21
+ declare class SDKModule {
22
+ private readonly fetcher;
23
+ constructor(fetcher: Fetcher);
24
+ protected fetch(endpoint: string, options: FetchOptions): Promise<Response>;
25
+ }
26
+ /**
27
+ * Upload body types.
28
+ *
29
+ * `File` and `Blob` are streamed.
30
+ * `ArrayBuffer` and `string` are buffered in memory.
31
+ *
32
+ * `string` values (e.g. Base64) are uploaded as-is.
33
+ */
34
+ export type FileBody = File | Blob | ArrayBuffer | string;
35
+ /**
36
+ * Metadata associated with a file.
37
+ */
38
+ export type FileMetadata = Record<string, string | number | boolean>;
39
+ /**
40
+ * Response returned after a successful file upload.
41
+ */
42
+ export interface UploadResponse {
43
+ /**
44
+ * The unique identifier for the uploaded file.
45
+ */
46
+ id: string;
47
+ /**
48
+ * The public URL where the uploaded file can be accessed.
49
+ */
50
+ url: string;
51
+ }
52
+ /**
53
+ * Response returned when requesting a signed URL for uploading media.
54
+ */
55
+ export interface SignedUrlResponse {
56
+ /**
57
+ * The signed URL for uploading the file.
58
+ */
59
+ url: string;
60
+ /**
61
+ * The expiration time of the signed URL in ISO 8601 format.
62
+ */
63
+ expiresAt: string;
64
+ /**
65
+ * The unique identifier for the media after upload.
66
+ */
67
+ mediaId: string;
68
+ /**
69
+ * The public URL to access the media after upload.
70
+ */
71
+ mediaUrl: string;
72
+ }
73
+ declare class Storage$1 extends SDKModule {
74
+ /**
75
+ * Generates a signed URL for uploading a file.
76
+ * @param contentType - The MIME type of the file.
77
+ * @param size - The size of the file in bytes.
78
+ * @param metadata - Optional metadata associated with the file.
79
+ * @returns {Promise<SignedUrlResponse>} An object containing the signed URL and its expiration time.
80
+ * @throws {NoCloudAPIError} If the API request fails.
81
+ */
82
+ generateSignedUrl(contentType: string, size: number, metadata?: FileMetadata): Promise<SignedUrlResponse>;
83
+ /**
84
+ * Extracts content type and size from the body.
85
+ */
86
+ private getBodyInfo;
87
+ /**
88
+ * Uploads a file to R2 storage using S3-compatible API.
89
+ * @param body The file body to upload. Supports File, Blob, ArrayBuffer, or string.
90
+ * @param metadata Optional metadata associated with the file.
91
+ * @returns {Promise<UploadResponse>} An object containing the upload ID and URL.
92
+ * @throws {NoCloudAPIError} If the upload fails.
93
+ */
94
+ upload(body: FileBody, metadata?: FileMetadata): Promise<UploadResponse>;
95
+ /**
96
+ * Uploads a ReadableStream to R2 storage using S3-compatible API.
97
+ * @param stream The ReadableStream to upload.
98
+ * @param contentType The MIME type of the content.
99
+ * @param contentLength The size of the content in bytes.
100
+ * @param metadata Optional metadata associated with the file.
101
+ * @returns {Promise<UploadResponse>} An object containing the upload ID and URL.
102
+ * @throws {NoCloudAPIError} If the upload fails.
103
+ */
104
+ uploadStream(stream: ReadableStream, contentType: string, contentLength: number, metadata?: FileMetadata): Promise<UploadResponse>;
105
+ /**
106
+ * Deletes a media file from the storage.
107
+ * @param mediaId The ID of the media file to be deleted.
108
+ * @returns {Promise<void>} A promise that resolves when the deletion is complete.
109
+ * @throws {NoCloudAPIError} If the deletion fails.
110
+ */
111
+ delete(mediaId: string): Promise<void>;
112
+ }
113
+ export interface NoCloudOptions {
114
+ /**
115
+ * Your API key for authenticating requests.
116
+ */
117
+ apiKey: string;
118
+ /**
119
+ * Optional base URL for the API.
120
+ * @default "https://api.nonefivem.com"
121
+ */
122
+ baseUrl?: string;
123
+ /**
124
+ * Number of retry attempts for failed requests.
125
+ * @default 3
126
+ */
127
+ retries?: number;
128
+ /**
129
+ * Delay in milliseconds between retry attempts.
130
+ * @default 1000
131
+ */
132
+ retryDelayMs?: number;
133
+ }
134
+ /**
135
+ * Main SDK class for interacting with NoCloud services.
136
+ * @example
137
+ * ```ts
138
+ * // Using a string API key
139
+ * const cloud = new NoCloud("your-api-key");
140
+ *
141
+ * // Using an options object
142
+ * const cloud = new NoCloud({
143
+ * apiKey: "your-api-key",
144
+ * baseUrl: "https://api.nonefivem.com",
145
+ * retries: 5,
146
+ * retryDelayMs: 2000,
147
+ * });
148
+ * ```
149
+ */
150
+ export declare class NoCloud {
151
+ private readonly fetcher;
152
+ /**
153
+ * Creates an instance of the NoCloud SDK.
154
+ * @param options - Your API key or an options object.
155
+ * @example
156
+ * ```ts
157
+ * // Using a string API key
158
+ * const cloud = new NoCloud("your-api-key");
159
+ *
160
+ * // Using an options object
161
+ * const cloud = new NoCloud({
162
+ * apiKey: "your-api-key",
163
+ * baseUrl: "https://api.nonefivem.com",
164
+ * retries: 5,
165
+ * retryDelayMs: 1000,
166
+ * });
167
+ * ```
168
+ */
169
+ constructor(options: string | NoCloudOptions);
170
+ private _storage?;
171
+ /**
172
+ * Storage module for handling file storage operations.
173
+ */
174
+ get storage(): Storage$1;
175
+ }
176
+ export declare class NoCloudAPIError extends Error {
177
+ readonly status: number;
178
+ constructor(message: string, status: number);
179
+ }
180
+
181
+ export {
182
+ NoCloud as default,
183
+ };
184
+
185
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var Q="https://api.nonefivem.com";class h extends Error{status;constructor(r,x){super(r);this.status=x;this.name="NoCloudAPIError"}}function Y(r){return new Promise((x)=>setTimeout(x,r))}async function U(r,x=3,F=1000){let f;for(let K=0;K<=x;K++)try{return await r()}catch(c){if(f=c,K<x)await Y(F)}throw f}var Z={iVBORw0KGgo:"image/png","/9j/":"image/jpeg",R0lGOD:"image/gif",UklGR:"image/webp",AAAA:"video/mp4",JVBERi0:"application/pdf",UEsDB:"application/zip",PD94bWw:"application/xml",PHN2Zw:"image/svg+xml"};function V(r){let x=r.match(/^data:([^;,]+)/);if(x?.[1])return x[1];for(let[F,f]of Object.entries(Z))if(r.startsWith(F))return f;return null}function W(r){let x=r.match(/^data:[^;,]+;base64,(.+)$/);if(x?.[1])return x[1];return r}function X(r){let x=(r.match(/=+$/)||[""])[0].length;return Math.floor(r.length*3/4)-x}class q{baseUrl;apiKey;retryCount;retryDelayMs;constructor(r){if(this.apiKey=r.apiKey,this.baseUrl=r.baseUrl||Q,this.retryCount=r.retries??3,this.retryDelayMs=r.retryDelayMs??1000,!this.apiKey)throw Error("API key is required")}buildUrl(r){return`${this.baseUrl}/cloud/${r}`}fetch(r,x={}){let F=this.buildUrl(r);return U(()=>fetch(F,{...x,headers:{Authorization:`Bearer ${this.apiKey}`,...x.headers||{}}}),x.retries??this.retryCount,x.retryDelayMs??this.retryDelayMs)}}async function u(r){if(!r.ok)try{let x=await r.json();throw new h(x.message||"API Error",r.status)}catch(x){throw new h("Unknown error",r.status)}return await r.json()}class D{fetcher;constructor(r){this.fetcher=r}fetch(r,x){return this.fetcher.fetch(r,x)}}class B extends D{async generateSignedUrl(r,x,F){let f=await this.fetch("storage/signed-url",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentType:r,size:x,metadata:F})});return u(f)}getBodyInfo(r){if(typeof Blob<"u"&&r instanceof Blob)return{contentType:r.type||"application/octet-stream",size:r.size};if(typeof ArrayBuffer<"u"&&r instanceof ArrayBuffer)return{contentType:"application/octet-stream",size:r.byteLength};if(typeof r==="string"){let x=V(r);if(x){let F=W(r),f=X(F);return{contentType:x,size:f}}return{contentType:"text/plain",size:new TextEncoder().encode(r).length}}throw new h("Unsupported body type",400)}async upload(r,x){let{contentType:F,size:f}=this.getBodyInfo(r),{url:K,mediaUrl:c,mediaId:_}=await this.generateSignedUrl(F,f,x),O=await fetch(K,{method:"PUT",headers:{"Content-Type":F,"Content-Length":f.toString()},body:r});if(!O.ok){let $=await O.text().catch(()=>O.statusText);throw new h(`Failed to upload file to R2: ${$}`,O.status)}return{id:_,url:c}}async uploadStream(r,x,F,f){let{url:K,mediaUrl:c,mediaId:_}=await this.generateSignedUrl(x,F,f),O=await fetch(K,{method:"PUT",headers:{"Content-Type":x,"Content-Length":F.toString()},body:r,duplex:"half"});if(!O.ok){let $=await O.text().catch(()=>O.statusText);throw new h(`Failed to upload stream to R2: ${$}`,O.status)}return{id:_,url:c}}async delete(r){let x=await this.fetch(`storage/${r}`,{method:"DELETE"});return u(x)}}class H{fetcher;constructor(r){if(typeof r==="string")r={apiKey:r};this.fetcher=new q({apiKey:r.apiKey,baseUrl:r.baseUrl,retries:r.retries,retryDelayMs:r.retryDelayMs})}_storage;get storage(){return this._storage??=new B(this.fetcher)}}var s=H;export{s as default,h as NoCloudAPIError,H as NoCloud};
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@nocloud/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Official SDK for NoCloud services - file storage and more",
5
+ "author": {
6
+ "name": "sinanovicanes",
7
+ "email": "anes@nonefivem.com",
8
+ "url": "https://nonefivem.com"
9
+ },
10
+ "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/nonefivem/no-cloud-sdk.git"
14
+ },
15
+ "homepage": "https://github.com/nonefivem/no-cloud-sdk#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/nonefivem/no-cloud-sdk/issues"
18
+ },
19
+ "keywords": [
20
+ "nocloud",
21
+ "no-cloud",
22
+ "nonem",
23
+ "nonefivem",
24
+ "sdk",
25
+ "storage",
26
+ "r2",
27
+ "s3",
28
+ "file-upload",
29
+ "api"
30
+ ],
31
+ "type": "module",
32
+ "main": "./dist/index.js",
33
+ "module": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "import": {
38
+ "types": "./dist/index.d.ts",
39
+ "default": "./dist/index.js"
40
+ },
41
+ "require": {
42
+ "types": "./dist/index.d.ts",
43
+ "default": "./dist/index.cjs"
44
+ }
45
+ }
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "README.md",
50
+ "LICENSE"
51
+ ],
52
+ "scripts": {
53
+ "build": "bun run clean && bun run build:js && bun run build:types",
54
+ "build:js": "bun build ./src/index.ts --outfile ./dist/index.js --target browser --format esm --minify && bun build ./src/index.ts --outfile ./dist/index.cjs --target browser --format cjs --minify",
55
+ "build:types": "bun x dts-bundle-generator -o ./dist/index.d.ts ./src/index.ts --project ./tsconfig.build.json --no-check",
56
+ "clean": "rm -rf dist",
57
+ "prepublishOnly": "bun run build",
58
+ "typecheck": "tsc --noEmit"
59
+ },
60
+ "private": false,
61
+ "devDependencies": {
62
+ "@types/node": "^22.0.0",
63
+ "typescript": "^5.7.0"
64
+ },
65
+ "engines": {
66
+ "node": ">=18.0.0"
67
+ }
68
+ }