medusa-dynamic-metadata 0.0.1

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,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeMetadataDescriptors = normalizeMetadataDescriptors;
4
+ exports.getDescriptorMap = getDescriptorMap;
5
+ exports.buildInitialFormState = buildInitialFormState;
6
+ exports.buildMetadataPayload = buildMetadataPayload;
7
+ exports.hasMetadataChanges = hasMetadataChanges;
8
+ exports.validateValueForDescriptor = validateValueForDescriptor;
9
+ const types_1 = require("./types");
10
+ const VALID_FIELD_TYPES = new Set(types_1.METADATA_FIELD_TYPES);
11
+ const BOOLEAN_TRUES = new Set(["true", "1", "yes", "y", "on"]);
12
+ const BOOLEAN_FALSES = new Set(["false", "0", "no", "n", "off"]);
13
+ function normalizeMetadataDescriptors(input) {
14
+ if (!Array.isArray(input))
15
+ return [];
16
+ const seenKeys = new Set();
17
+ return input
18
+ .filter((item) => item && typeof item === "object")
19
+ .map(item => {
20
+ const key = normalizeKey(item.key);
21
+ const type = normalizeType(item.type);
22
+ const label = normalizeLabel(item.label);
23
+ const filterable = !!(item.filterable);
24
+ return { key, type, label, filterable };
25
+ })
26
+ .filter(({ key, type }) => key && type && !seenKeys.has(key))
27
+ .map(({ key, type, label, filterable }) => {
28
+ seenKeys.add(key);
29
+ return {
30
+ key: key,
31
+ type: type,
32
+ ...(label && { label }),
33
+ ...(filterable && { filterable: true })
34
+ };
35
+ });
36
+ }
37
+ function getDescriptorMap(descriptors) {
38
+ return descriptors.reduce((acc, descriptor) => {
39
+ acc[descriptor.key] = descriptor;
40
+ return acc;
41
+ }, {});
42
+ }
43
+ function buildInitialFormState(descriptors, metadata) {
44
+ const base = metadata && typeof metadata === "object" ? metadata : {};
45
+ return descriptors.reduce((acc, descriptor) => {
46
+ acc[descriptor.key] = normalizeFormValue(descriptor, base[descriptor.key]);
47
+ return acc;
48
+ }, {});
49
+ }
50
+ function buildMetadataPayload({ descriptors, values, originalMetadata, }) {
51
+ const base = originalMetadata && typeof originalMetadata === "object"
52
+ ? { ...originalMetadata }
53
+ : {};
54
+ descriptors.forEach((descriptor) => {
55
+ const coerced = coerceMetadataValue(descriptor, values[descriptor.key]);
56
+ if (coerced === undefined) {
57
+ delete base[descriptor.key];
58
+ }
59
+ else {
60
+ base[descriptor.key] = coerced;
61
+ }
62
+ });
63
+ return base;
64
+ }
65
+ function hasMetadataChanges({ descriptors, values, originalMetadata, }) {
66
+ const next = buildMetadataPayload({ descriptors, values, originalMetadata });
67
+ const prev = originalMetadata && typeof originalMetadata === "object"
68
+ ? originalMetadata
69
+ : {};
70
+ return descriptors.some(({ key }) => !isDeepEqual(prev[key], next[key]));
71
+ }
72
+ function validateValueForDescriptor(descriptor, value) {
73
+ if (descriptor.type === "number") {
74
+ if (value == null || value === "")
75
+ return undefined;
76
+ const num = typeof value === "number" ? value : Number(String(value).trim());
77
+ return isNaN(num) ? "Enter a valid number" : undefined;
78
+ }
79
+ if (descriptor.type === "file") {
80
+ if (!value)
81
+ return undefined;
82
+ try {
83
+ new URL(String(value).trim());
84
+ return undefined;
85
+ }
86
+ catch {
87
+ return "Enter a valid URL";
88
+ }
89
+ }
90
+ return undefined;
91
+ }
92
+ function normalizeFormValue(descriptor, currentValue) {
93
+ if (descriptor.type === "bool")
94
+ return Boolean(currentValue);
95
+ if ((descriptor.type === "number" || descriptor.type === "text") && typeof currentValue === "number") {
96
+ return currentValue.toString();
97
+ }
98
+ if (typeof currentValue === "string" || typeof currentValue === "number") {
99
+ return String(currentValue);
100
+ }
101
+ return "";
102
+ }
103
+ function coerceMetadataValue(descriptor, value) {
104
+ if (value == null || value === "")
105
+ return undefined;
106
+ if (descriptor.type === "bool") {
107
+ if (typeof value === "boolean")
108
+ return value;
109
+ if (typeof value === "number")
110
+ return value !== 0;
111
+ const normalized = String(value).trim().toLowerCase();
112
+ if (!normalized)
113
+ return undefined;
114
+ if (BOOLEAN_TRUES.has(normalized))
115
+ return true;
116
+ if (BOOLEAN_FALSES.has(normalized))
117
+ return false;
118
+ return Boolean(value);
119
+ }
120
+ if (descriptor.type === "number") {
121
+ const num = typeof value === "number" ? value : Number(String(value).trim());
122
+ return isNaN(num) ? undefined : num;
123
+ }
124
+ // For text and file types, trim and check if empty
125
+ const trimmed = String(value).trim();
126
+ if (!trimmed)
127
+ return undefined;
128
+ return trimmed;
129
+ }
130
+ function normalizeKey(value) {
131
+ return typeof value === "string" ? value.trim() || undefined : undefined;
132
+ }
133
+ function normalizeType(value) {
134
+ if (typeof value !== "string")
135
+ return undefined;
136
+ const type = value.trim().toLowerCase();
137
+ return VALID_FIELD_TYPES.has(type) ? type : undefined;
138
+ }
139
+ function normalizeLabel(value) {
140
+ return typeof value === "string" ? value.trim() || undefined : undefined;
141
+ }
142
+ function isDeepEqual(a, b) {
143
+ if (a === b)
144
+ return true;
145
+ if (!a || !b || typeof a !== "object" || typeof b !== "object")
146
+ return false;
147
+ const aKeys = Object.keys(a);
148
+ const bKeys = Object.keys(b);
149
+ if (aKeys.length !== bKeys.length)
150
+ return false;
151
+ return aKeys.every(key => isDeepEqual(a[key], b[key]));
152
+ }
153
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvc2hhcmVkL21ldGFkYXRhL3V0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBY0Esb0VBeUJDO0FBRUQsNENBS0M7QUFFRCxzREFVQztBQUVELG9EQXdCQztBQUVELGdEQWVDO0FBRUQsZ0VBdUJDO0FBOUhELG1DQUtnQjtBQUtoQixNQUFNLGlCQUFpQixHQUFHLElBQUksR0FBRyxDQUFvQiw0QkFBb0IsQ0FBQyxDQUFBO0FBQzFFLE1BQU0sYUFBYSxHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUE7QUFDOUQsTUFBTSxjQUFjLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQTtBQUVoRSxTQUFnQiw0QkFBNEIsQ0FBQyxLQUFjO0lBQ3pELElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQztRQUFFLE9BQU8sRUFBRSxDQUFBO0lBRXBDLE1BQU0sUUFBUSxHQUFHLElBQUksR0FBRyxFQUFVLENBQUE7SUFDbEMsT0FBTyxLQUFLO1NBQ1QsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFtQyxFQUFFLENBQ2hELElBQUksSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLENBQ2pDO1NBQ0EsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFO1FBQ1YsTUFBTSxHQUFHLEdBQUcsWUFBWSxDQUFFLElBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQTtRQUMzQyxNQUFNLElBQUksR0FBRyxhQUFhLENBQUUsSUFBWSxDQUFDLElBQUksQ0FBQyxDQUFBO1FBQzlDLE1BQU0sS0FBSyxHQUFHLGNBQWMsQ0FBRSxJQUFZLENBQUMsS0FBSyxDQUFDLENBQUE7UUFD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
package/README.md ADDED
@@ -0,0 +1,292 @@
1
+ <p align="center">
2
+ <a href="https://www.medusajs.com">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/59018053/229103275-b5e482bb-4601-46e6-8142-244f531cebdb.svg">
5
+ <source media="(prefers-color-scheme: light)" srcset="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
6
+ <img alt="Medusa logo" src="https://user-images.githubusercontent.com/59018053/229103726-e5b529a3-9b3f-4970-8a1f-c6af37f087bf.svg">
7
+ </picture>
8
+ </a>
9
+ </p>
10
+ <h1 align="center">
11
+ Medusa Dynamic Metadata
12
+ </h1>
13
+
14
+ <h4 align="center">
15
+ <a href="https://docs.medusajs.com">Documentation</a> |
16
+ <a href="https://www.medusajs.com">Website</a>
17
+ </h4>
18
+
19
+ <p align="center">
20
+ Building blocks for digital commerce
21
+ </p>
22
+ <p align="center">
23
+ <a href="https://github.com/medusajs/medusa/blob/master/CONTRIBUTING.md">
24
+ <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat" alt="PRs welcome!" />
25
+ </a>
26
+ <a href="https://www.producthunt.com/posts/medusa"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Day-%23DA552E" alt="Product Hunt"></a>
27
+ <a href="https://discord.gg/xpCwq3Kfn8">
28
+ <img src="https://img.shields.io/badge/chat-on%20discord-7289DA.svg" alt="Discord Chat" />
29
+ </a>
30
+ <a href="https://twitter.com/intent/follow?screen_name=medusajs">
31
+ <img src="https://img.shields.io/twitter/follow/medusajs.svg?label=Follow%20@medusajs" alt="Follow @medusajs" />
32
+ </a>
33
+ </p>
34
+
35
+ ## Compatibility
36
+
37
+ This plugin is compatible with versions >= 2.4.0 of `@medusajs/medusa`.
38
+
39
+ ## Overview
40
+
41
+ `medusa-dynamic-metadata` is a flexible, configuration-driven plugin that enables metadata management for any Medusa entity type. Simply configure entities in your `medusa-config.ts` file, and the plugin provides a universal widget component that automatically detects entity types and renders the appropriate metadata fields.
42
+
43
+ ### Key Features
44
+
45
+ - **Configuration-Driven**: Enable metadata for any entity type through simple configuration
46
+ - **Universal Widget**: Single widget component works for all entities with runtime entity detection
47
+ - **Type-Safe**: Full TypeScript support with proper type definitions
48
+ - **Flexible Schema**: Define metadata fields with types (text, number, bool, file) and filterability
49
+ - **Zero Code Changes**: Add metadata to new entities by updating configuration only
50
+
51
+ ## Supported Entities
52
+
53
+ This plugin supports metadata configuration for the following entities that have valid admin detail pages:
54
+
55
+ 1. **products** - Widget zone: `product.details.after`
56
+ 2. **orders** - Widget zone: `order.details.after`
57
+ 3. **categories** - Widget zone: `product_category.details.after`
58
+ 4. **collections** - Widget zone: `product_collection.details.after`
59
+ 5. **customers** - Widget zone: `customer.details.after`
60
+ 6. **regions** - Widget zone: `region.details.after`
61
+ 7. **sales_channels** - Widget zone: `sales_channel.details.after`
62
+ 8. **stores** - Widget zone: `store.details.after`
63
+ 9. **promotions** - Widget zone: `promotion.details.after`
64
+ 10. **campaigns** - Widget zone: `campaign.details.after`
65
+ 11. **price_lists** - Widget zone: `price_list.details.after`
66
+ 12. **shipping_profiles** - Widget zone: `shipping_profile.details.after`
67
+ 13. **inventory_items** - Widget zone: `inventory_item.details.after`
68
+ 14. **product_variants** - Widget zone: `product_variant.details.after`
69
+
70
+ > **Note**: Only entities with valid admin detail pages can be configured. The plugin includes pre-built widget files for all 14 supported entities above.
71
+
72
+ ## Getting Started
73
+
74
+ ### Installation
75
+
76
+ ```bash
77
+ npm install medusa-dynamic-metadata
78
+ ```
79
+
80
+ ### Configuration
81
+
82
+ Add the plugin to your `medusa-config.ts`:
83
+
84
+ ```typescript
85
+ import { defineConfig } from "@medusajs/framework"
86
+
87
+ export default defineConfig({
88
+ // ... other config
89
+ plugins: [
90
+ {
91
+ resolve: "medusa-dynamic-metadata",
92
+ options: {
93
+ entities: {
94
+ products: {
95
+ descriptors: [
96
+ { key: "brand", label: "Brand", type: "text", filterable: true },
97
+ { key: "warranty_years", label: "Warranty (Years)", type: "number" },
98
+ ],
99
+ expose_client_helpers: true,
100
+ filterable: true,
101
+ },
102
+ orders: {
103
+ descriptors: [
104
+ { key: "source", label: "Source", type: "text" },
105
+ ],
106
+ },
107
+ },
108
+ },
109
+ },
110
+ ],
111
+ })
112
+ ```
113
+
114
+ ### Widget Files
115
+
116
+ The plugin includes minimal wrapper widget files for all 14 supported entities listed above. Each wrapper:
117
+
118
+ 1. Specifies the widget zone (required by Medusa's widget system)
119
+ 2. Delegates to the universal `UniversalMetadataWidget` component
120
+ 3. Automatically detects the entity type at runtime
121
+
122
+ **For entities not in the supported list**: Only entities with valid admin detail pages can have metadata widgets. If you need to add support for a new entity that has a detail page in Medusa's admin, create a minimal wrapper widget file using the template in `src/admin/widgets/generic-metadata-widget.template.tsx`.
123
+
124
+ ### Building the Plugin
125
+
126
+ ```bash
127
+ npm run build
128
+ ```
129
+
130
+ This builds the plugin with all extensions.
131
+
132
+ ### Development
133
+
134
+ ```bash
135
+ npm run dev
136
+ ```
137
+
138
+ This runs the plugin in watch mode for development.
139
+
140
+ ## How It Works
141
+
142
+ ### Architecture Flow
143
+
144
+ ```
145
+ Configuration (medusa-config.ts)
146
+
147
+ Plugin Initialization
148
+
149
+ Widget Files (minimal wrappers with zones)
150
+
151
+ UniversalMetadataWidget (runtime entity detection)
152
+
153
+ MetadataTableWidget (UI component)
154
+
155
+ API Endpoints (save/load metadata)
156
+ ```
157
+
158
+ ### Universal Widget Architecture
159
+
160
+ The plugin uses a single universal widget component (`UniversalMetadataWidget`) that works for all entity types:
161
+
162
+ 1. **Runtime Entity Detection**: The widget automatically detects the entity type using:
163
+ - **Zone-based detection** (primary): Resolves entity type from the widget zone (e.g., `"product.details.after"` → `"products"`)
164
+ - **Data structure detection** (fallback): Pattern matching on data properties (e.g., `handle`, `title` → products; `display_id`, `status` → orders)
165
+
166
+ 2. **Configuration Loading**: Fetches metadata descriptors from `/admin/metadata-config?entity={entityType}` based on detected entity type
167
+
168
+ 3. **Dynamic Rendering**: Renders appropriate form fields (text, number, bool, file) based on the descriptors
169
+
170
+ 4. **Metadata Management**: Saves metadata through standard Medusa entity API endpoints
171
+
172
+ ### Widget Structure
173
+
174
+ Widget files are minimal wrappers that:
175
+ - Specify the widget zone (required by Medusa)
176
+ - Pass the zone to `UniversalMetadataWidget`
177
+ - Let the universal component handle all entity-specific logic
178
+
179
+ Example widget file:
180
+ ```typescript
181
+ const Widget = ({ data }) => {
182
+ return <UniversalMetadataWidget data={data} zone="product.details.after" />
183
+ }
184
+
185
+ export const config = defineWidgetConfig({
186
+ zone: "product.details.after"
187
+ })
188
+ ```
189
+
190
+ ### Creating Widgets for New Entities
191
+
192
+ To add metadata support for a new entity:
193
+
194
+ 1. **Add configuration** in `medusa-config.ts`:
195
+ ```typescript
196
+ entities: {
197
+ customers: {
198
+ descriptors: [
199
+ { key: "preferred_language", type: "text" }
200
+ ]
201
+ }
202
+ }
203
+ ```
204
+
205
+ 2. **Create a minimal widget file** (if zone doesn't match existing patterns):
206
+ - Copy `src/admin/widgets/generic-metadata-widget.template.tsx`
207
+ - Update the zone to match your entity's admin zone
208
+ - The universal widget will automatically detect the entity type
209
+
210
+ ## Configuration Options
211
+
212
+ ### Entity Configuration
213
+
214
+ Each entity in the configuration supports:
215
+
216
+ - `descriptors`: Array of metadata field definitions
217
+ - `key`: Unique identifier for the field
218
+ - `label`: Display label (optional, defaults to key)
219
+ - `type`: Field type (`"text"`, `"number"`, `"bool"`, `"file"`)
220
+ - `filterable`: Whether the field can be used for filtering (default: `false`)
221
+ - `expose_client_helpers`: Expose client-side helper functions (default: `false`)
222
+ - `filterable`: Enable filtering for this entity (default: `false`)
223
+ - `widget_zone`: Custom widget zone override (optional)
224
+ - `api_endpoint`: Custom API endpoint override (optional)
225
+
226
+ ## API
227
+
228
+ The plugin provides admin API endpoints for metadata management:
229
+
230
+ - `GET /admin/metadata-config?entity={entityType}` - Get metadata descriptors for an entity
231
+ - Metadata is managed through the standard Medusa entity APIs with metadata fields
232
+
233
+ ## Examples
234
+
235
+ ### Basic Configuration
236
+
237
+ ```typescript
238
+ {
239
+ entities: {
240
+ products: {
241
+ descriptors: [
242
+ { key: "brand", type: "text", filterable: true },
243
+ { key: "warranty_years", type: "number" },
244
+ ],
245
+ },
246
+ },
247
+ }
248
+ ```
249
+
250
+ ### Advanced Configuration
251
+
252
+ ```typescript
253
+ {
254
+ entities: {
255
+ products: {
256
+ descriptors: [
257
+ { key: "brand", label: "Brand Name", type: "text", filterable: true },
258
+ { key: "warranty_years", label: "Warranty Period", type: "number" },
259
+ { key: "has_warranty", label: "Has Warranty", type: "bool" },
260
+ { key: "manual_pdf", label: "Manual PDF", type: "file" },
261
+ ],
262
+ expose_client_helpers: true,
263
+ filterable: true,
264
+ widget_zone: "product.details.after", // Optional override
265
+ },
266
+ },
267
+ }
268
+ ```
269
+
270
+ ## Scripts
271
+
272
+ - `npm run build` - Build the plugin
273
+ - `npm run dev` - Run in development/watch mode
274
+
275
+ ## What is Medusa
276
+
277
+ Medusa is a set of commerce modules and tools that allow you to build rich, reliable, and performant commerce applications without reinventing core commerce logic. The modules can be customized and used to build advanced ecommerce stores, marketplaces, or any product that needs foundational commerce primitives. All modules are open-source and freely available on npm.
278
+
279
+ Learn more about [Medusa’s architecture](https://docs.medusajs.com/learn/introduction/architecture) and [commerce modules](https://docs.medusajs.com/learn/fundamentals/modules/commerce-modules) in the Docs.
280
+
281
+ ## Community & Contributions
282
+
283
+ The community and core team are available in [GitHub Discussions](https://github.com/medusajs/medusa/discussions), where you can ask for support, discuss roadmap, and share ideas.
284
+
285
+ Join our [Discord server](https://discord.com/invite/medusajs) to meet other community members.
286
+
287
+ ## Other channels
288
+
289
+ - [GitHub Issues](https://github.com/medusajs/medusa/issues)
290
+ - [Twitter](https://twitter.com/medusajs)
291
+ - [LinkedIn](https://www.linkedin.com/company/medusajs)
292
+ - [Medusa Blog](https://medusajs.com/blog/)
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "medusa-dynamic-metadata",
3
+ "version": "0.0.1",
4
+ "description": "A starter for Medusa plugins.",
5
+ "author": "Medusa (https://medusajs.com)",
6
+ "license": "MIT",
7
+ "files": [
8
+ ".medusa/server"
9
+ ],
10
+ "exports": {
11
+ "./package.json": "./package.json",
12
+ "./workflows": "./.medusa/server/src/workflows/index.js",
13
+ "./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
14
+ "./modules/*": "./.medusa/server/src/modules/*/index.js",
15
+ "./providers/*": "./.medusa/server/src/providers/*/index.js",
16
+ "./*": "./.medusa/server/src/*.js",
17
+ "./admin": {
18
+ "import": "./.medusa/server/src/admin/index.mjs",
19
+ "require": "./.medusa/server/src/admin/index.js",
20
+ "default": "./.medusa/server/src/admin/index.js"
21
+ }
22
+ },
23
+ "keywords": [
24
+ "medusa",
25
+ "plugin",
26
+ "medusa-plugin-other",
27
+ "medusa-plugin",
28
+ "medusa-v2"
29
+ ],
30
+ "scripts": {
31
+ "build": "medusa plugin:build",
32
+ "dev": "medusa plugin:develop",
33
+ "prepublishOnly": "medusa plugin:build"
34
+ },
35
+ "devDependencies": {
36
+ "@medusajs/admin-sdk": "2.12.3",
37
+ "@medusajs/cli": "2.12.3",
38
+ "@medusajs/framework": "2.12.3",
39
+ "@medusajs/medusa": "2.12.3",
40
+ "@medusajs/test-utils": "2.12.3",
41
+ "@medusajs/ui": "4.0.25",
42
+ "@medusajs/icons": "2.12.3",
43
+ "@swc/core": "1.5.7",
44
+ "@types/node": "^20.0.0",
45
+ "@types/react": "^18.3.2",
46
+ "@types/react-dom": "^18.2.25",
47
+ "prop-types": "^15.8.1",
48
+ "react": "^18.2.0",
49
+ "react-dom": "^18.2.0",
50
+ "ts-node": "^10.9.2",
51
+ "typescript": "^5.6.2",
52
+ "vite": "^5.2.11",
53
+ "yalc": "^1.0.0-pre.53",
54
+ "zod": "^3.23.8"
55
+ },
56
+ "dependencies": {
57
+ "zod": "^3.23.8"
58
+ },
59
+ "peerDependencies": {
60
+ "@medusajs/admin-sdk": "2.12.3",
61
+ "@medusajs/cli": "2.12.3",
62
+ "@medusajs/framework": "2.12.3",
63
+ "@medusajs/test-utils": "2.12.3",
64
+ "@medusajs/medusa": "2.12.3",
65
+ "@medusajs/ui": "4.0.25",
66
+ "@medusajs/icons": "2.12.3"
67
+ },
68
+ "engines": {
69
+ "node": ">=20"
70
+ }
71
+ }