@plume-press/cli 0.0.0-stage → 0.4.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,585 @@
1
+ // packages/core/src/schema.ts
2
+ var reserved = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3
+ var kinds = /* @__PURE__ */ new Set([
4
+ "text",
5
+ "richText",
6
+ "number",
7
+ "boolean",
8
+ "date",
9
+ "select",
10
+ "slug",
11
+ "link",
12
+ "image",
13
+ "file",
14
+ "list",
15
+ "reference"
16
+ ]);
17
+ var options = {
18
+ text: ["multiline", "maxLength"],
19
+ date: ["time"],
20
+ slug: ["from"],
21
+ select: ["options", "multiple"],
22
+ reference: ["collection", "multiple"],
23
+ image: ["multiple", "accept"],
24
+ file: ["multiple", "accept"],
25
+ list: ["fields"]
26
+ };
27
+ var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
28
+ function canonicalJson(value) {
29
+ const ancestors = /* @__PURE__ */ new Set();
30
+ const visit = (input) => {
31
+ if (input === null || typeof input === "string" || typeof input === "boolean")
32
+ return JSON.stringify(input);
33
+ if (typeof input === "number" && Number.isFinite(input))
34
+ return JSON.stringify(input);
35
+ if (typeof input !== "object" || input === null)
36
+ throw new Error("Expected a finite JSON value");
37
+ if (ancestors.has(input)) throw new Error("Cyclic JSON value");
38
+ ancestors.add(input);
39
+ try {
40
+ if (Array.isArray(input)) {
41
+ if (Reflect.ownKeys(input).length !== input.length + 1)
42
+ throw new Error("Sparse or extended array");
43
+ return `[${Array.from({ length: input.length }, (_, i) => {
44
+ const entry = Object.getOwnPropertyDescriptor(input, String(i));
45
+ if (!entry || !("value" in entry))
46
+ throw new Error("Invalid array entry");
47
+ return visit(entry.value);
48
+ }).join(",")}]`;
49
+ }
50
+ if (!object(input)) throw new Error("Expected plain JSON object");
51
+ const keys = Reflect.ownKeys(input);
52
+ if (keys.some((key) => typeof key !== "string" || reserved.has(key)))
53
+ throw new Error("Reserved JSON key");
54
+ return `{${keys.sort().map((key) => {
55
+ const entry = Object.getOwnPropertyDescriptor(input, key);
56
+ if (!entry?.enumerable || !("value" in entry))
57
+ throw new Error("Expected enumerable JSON data property");
58
+ return `${JSON.stringify(key)}:${visit(entry.value)}`;
59
+ }).join(",")}}`;
60
+ } finally {
61
+ ancestors.delete(input);
62
+ }
63
+ };
64
+ return visit(value);
65
+ }
66
+ function assert(condition, message) {
67
+ if (!condition) throw new Error(`Invalid Plume schema: ${message}`);
68
+ }
69
+ function allowedKeys(value, keys, path) {
70
+ assert(
71
+ Object.keys(value).every((key) => keys.includes(key)),
72
+ `${path}: unknown property`
73
+ );
74
+ }
75
+ function named(value, path) {
76
+ assert(
77
+ typeof value.name === "string" && /^[A-Za-z][A-Za-z0-9_]*$/.test(value.name) && !reserved.has(value.name),
78
+ `${path}: invalid name`
79
+ );
80
+ assert(
81
+ typeof value.label === "string" && value.label.trim().length > 0,
82
+ `${path}: label is required`
83
+ );
84
+ }
85
+ function defineSchema(schema) {
86
+ return checkSchema(schema);
87
+ }
88
+ function readStoredSchema(schema, ignored = () => {
89
+ }) {
90
+ return checkSchema(schema, ignored);
91
+ }
92
+ function checkSchema(schema, ignoredPreviewPath) {
93
+ canonicalJson(schema);
94
+ assert(object(schema), "expected object");
95
+ allowedKeys(schema, ["collections", "singletons"], "schema");
96
+ assert(Array.isArray(schema.collections), "collections must be an array");
97
+ assert(
98
+ schema.singletons === void 0 || Array.isArray(schema.singletons),
99
+ "singletons must be an array"
100
+ );
101
+ const collectionNames = new Set(
102
+ schema.collections.map(
103
+ (collection) => object(collection) ? collection.name : void 0
104
+ )
105
+ );
106
+ const entities = /* @__PURE__ */ new Set();
107
+ const validateFields2 = (fields, path, inList) => {
108
+ assert(
109
+ Array.isArray(fields) && fields.length > 0,
110
+ `${path}: nonempty fields array required`
111
+ );
112
+ const names = /* @__PURE__ */ new Set();
113
+ for (const field of fields) {
114
+ assert(object(field), `${path}: invalid field`);
115
+ named(field, path);
116
+ const name = field.name;
117
+ assert(!names.has(name), `${path}: duplicate field ${name}`);
118
+ names.add(name);
119
+ assert(
120
+ typeof field.kind === "string" && kinds.has(field.kind),
121
+ `${path}.${name}: invalid kind`
122
+ );
123
+ const extra = options[field.kind] ?? [];
124
+ allowedKeys(
125
+ field,
126
+ ["name", "label", "kind", "required", "locked", "default", ...extra],
127
+ `${path}.${name}`
128
+ );
129
+ for (const flag of [
130
+ "required",
131
+ "locked",
132
+ "multiple",
133
+ "multiline",
134
+ "time"
135
+ ]) {
136
+ assert(
137
+ field[flag] === void 0 || typeof field[flag] === "boolean",
138
+ `${path}.${name}: ${flag} must be boolean`
139
+ );
140
+ }
141
+ if (field.kind === "select") {
142
+ assert(
143
+ Array.isArray(field.options) && field.options.length > 0 && field.options.every(
144
+ (option) => typeof option === "string" && option.trim() !== ""
145
+ ) && new Set(field.options).size === field.options.length,
146
+ `${path}.${name}: invalid options`
147
+ );
148
+ }
149
+ if (field.kind === "reference")
150
+ assert(
151
+ typeof field.collection === "string" && collectionNames.has(field.collection),
152
+ `${path}.${name}: unknown reference collection`
153
+ );
154
+ if (field.kind === "text")
155
+ assert(
156
+ field.maxLength === void 0 || Number.isSafeInteger(field.maxLength) && field.maxLength >= 1,
157
+ `${path}.${name}: maxLength must be a positive integer`
158
+ );
159
+ if (field.kind === "slug" && field.from !== void 0)
160
+ assert(
161
+ typeof field.from === "string" && field.from !== name && fields.some(
162
+ (sibling) => object(sibling) && sibling.name === field.from && sibling.kind === "text"
163
+ ),
164
+ `${path}.${name}: from must name a text field beside it`
165
+ );
166
+ if ((field.kind === "image" || field.kind === "file") && field.accept !== void 0) {
167
+ assert(
168
+ Array.isArray(field.accept) && field.accept.length > 0 && new Set(field.accept).size === field.accept.length && field.accept.every(
169
+ (entry) => typeof entry === "string" && /^(?:[a-z]+\/(?:\*|[a-z0-9][a-z0-9.+-]*)|\.[a-z0-9]+)$/.test(
170
+ entry
171
+ )
172
+ ),
173
+ `${path}.${name}: accept must list lowercase MIME types or .extensions`
174
+ );
175
+ const supported = Object.keys(mediaExtensions).filter(
176
+ (type) => field.kind === "file" || type.startsWith("image/")
177
+ );
178
+ assert(
179
+ field.accept.every(
180
+ (entry) => supported.some(
181
+ (type) => mediaAccepted([entry], { mediaType: type, filename: "" })
182
+ )
183
+ ),
184
+ `${path}.${name}: accept supports only ${[
185
+ ...supported,
186
+ ...supported.flatMap((type) => mediaExtensions[type] ?? [])
187
+ ].join(", ")} and matching type/* wildcards`
188
+ );
189
+ }
190
+ if (field.kind === "list") {
191
+ assert(!inList, `${path}.${name}: nested lists are unsupported`);
192
+ validateFields2(field.fields, `${path}.${name}`, true);
193
+ }
194
+ if ("default" in field) {
195
+ assert(
196
+ !["slug", "reference", "image", "file"].includes(field.kind),
197
+ `${path}.${name}: default is not supported for ${field.kind} fields`
198
+ );
199
+ const problems = [];
200
+ validateValue(
201
+ field,
202
+ field.default,
203
+ "default",
204
+ false,
205
+ (_path, _code, message) => problems.push(message)
206
+ );
207
+ assert(
208
+ field.default !== null && problems.length === 0,
209
+ `${path}.${name}: default must be a valid value`
210
+ );
211
+ }
212
+ }
213
+ };
214
+ for (const [entity, isCollection] of [
215
+ ...schema.collections.map((entity2) => [entity2, true]),
216
+ ...(schema.singletons ?? []).map((entity2) => [entity2, false])
217
+ ]) {
218
+ assert(object(entity), "invalid collection or singleton");
219
+ named(entity, "entity");
220
+ const entityName = entity.name;
221
+ allowedKeys(entity, ["name", "label", "fields", "previewPath"], entityName);
222
+ assert(!entities.has(entityName), `duplicate entity ${entityName}`);
223
+ entities.add(entityName);
224
+ validateFields2(entity.fields, entityName, false);
225
+ if ("previewPath" in entity)
226
+ try {
227
+ validatePreviewPath(
228
+ entityName,
229
+ entity.previewPath,
230
+ isCollection ? entity.fields : null
231
+ );
232
+ } catch (error) {
233
+ if (!ignoredPreviewPath) throw error;
234
+ ignoredPreviewPath(error.message);
235
+ }
236
+ }
237
+ return schema;
238
+ }
239
+ function isLocalPath(path) {
240
+ if (typeof path !== "string" || path.length > 2048 || !/^\/(?!\/)/.test(path) || /[\\\s\p{Cc}]/u.test(path))
241
+ return false;
242
+ const url = new URL(path, "https://site.invalid");
243
+ return url.host === "site.invalid" && !url.pathname.startsWith("//");
244
+ }
245
+ var placeholder = /\{([^{}]*)\}/g;
246
+ function validatePreviewPath(entity, path, fields) {
247
+ assert(
248
+ isLocalPath(path),
249
+ `${entity}: previewPath must be a local path starting with /`
250
+ );
251
+ for (const [, name = ""] of path.matchAll(placeholder)) {
252
+ assert(
253
+ fields,
254
+ `${entity}: a singleton previewPath is a fixed path without {placeholders}`
255
+ );
256
+ if (name === "id") continue;
257
+ const field = fields.find((candidate) => candidate.name === name);
258
+ assert(
259
+ field,
260
+ `${entity}: previewPath placeholder {${name}} must be {id} or a field of ${entity}`
261
+ );
262
+ assert(
263
+ field.kind === "text" || field.kind === "slug",
264
+ `${entity}: previewPath placeholder {${name}} must name a text or slug field, not ${field.kind}`
265
+ );
266
+ }
267
+ assert(
268
+ !/[{}]/.test(path.replace(placeholder, "")),
269
+ `${entity}: previewPath has an unmatched { or }`
270
+ );
271
+ }
272
+ function resolvePreviewPath(entity, recordId, data) {
273
+ const template = entity.previewPath;
274
+ if (template === void 0) return null;
275
+ try {
276
+ validatePreviewPath(entity.name, template, entity.fields);
277
+ } catch {
278
+ return null;
279
+ }
280
+ let missing = false;
281
+ const path = template.replace(placeholder, (_, name) => {
282
+ const field = entity.fields.find((candidate) => candidate.name === name);
283
+ const raw = name === "id" ? recordId : field?.kind === "text" || field?.kind === "slug" ? object(data) && data[name] : void 0;
284
+ const value = typeof raw === "string" ? raw.trim() : "";
285
+ if (value === "" || value === "." || value === "..") {
286
+ missing = true;
287
+ return "";
288
+ }
289
+ try {
290
+ return encodeURIComponent(value);
291
+ } catch {
292
+ missing = true;
293
+ return "";
294
+ }
295
+ });
296
+ return !missing && isLocalPath(path) ? path : null;
297
+ }
298
+ async function schemaHash(schema) {
299
+ readStoredSchema(schema);
300
+ const bytes = new TextEncoder().encode(
301
+ canonicalJson(schema)
302
+ );
303
+ const digest = await crypto.subtle.digest("SHA-256", bytes);
304
+ return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
305
+ }
306
+ var unset = (field, record) => !Object.hasOwn(record, field.name) || record[field.name] === null && !field.required;
307
+ function validateFields(fields, value, prefix, publication, report) {
308
+ if (!object(value)) {
309
+ report(prefix, "type", "Expected record object");
310
+ return;
311
+ }
312
+ for (const key of Object.keys(value))
313
+ if (!fields.some((field) => field.name === key))
314
+ report(
315
+ prefix ? `${prefix}.${key}` : key,
316
+ "unknown_field",
317
+ "Unknown field"
318
+ );
319
+ for (const field of fields) {
320
+ const path = prefix ? `${prefix}.${field.name}` : field.name;
321
+ if (unset(field, value)) {
322
+ if (publication && field.required)
323
+ report(path, "required", "Required field is missing");
324
+ continue;
325
+ }
326
+ const v = value[field.name];
327
+ if (publication && field.required && (v === "" || Array.isArray(v) && v.length === 0)) {
328
+ report(path, "required", "Required field is empty");
329
+ continue;
330
+ }
331
+ validateValue(field, v, path, publication, report);
332
+ }
333
+ }
334
+ function validateValue(field, v, path, publication, report) {
335
+ const typeError = (expected) => report(path, "type", `Expected ${expected}`);
336
+ const distinct = (expected, entry) => {
337
+ if (!Array.isArray(v)) return typeError(expected);
338
+ v.forEach((item, index) => {
339
+ entry(item, `${path}[${index}]`);
340
+ });
341
+ if (v.every((item) => typeof item === "string") && new Set(v).size !== v.length)
342
+ report(path, "format", "Choose each value only once");
343
+ };
344
+ switch (field.kind) {
345
+ case "number":
346
+ if (typeof v !== "number") typeError("number");
347
+ break;
348
+ case "boolean":
349
+ if (typeof v !== "boolean") typeError("boolean");
350
+ break;
351
+ case "text":
352
+ if (typeof v !== "string") typeError("string");
353
+ else if (field.maxLength !== void 0 && Array.from(v).length > field.maxLength)
354
+ report(path, "format", `Use at most ${field.maxLength} characters`);
355
+ break;
356
+ case "richText":
357
+ if (typeof v !== "string") typeError("string");
358
+ break;
359
+ case "slug":
360
+ if (typeof v !== "string") typeError("slug string");
361
+ else if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(v))
362
+ report(path, "format", "Expected lowercase hyphen-separated slug");
363
+ break;
364
+ case "date":
365
+ if (typeof v !== "string") typeError("ISO date string");
366
+ else if (!validDate(v))
367
+ report(path, "format", "Expected valid ISO date or UTC timestamp");
368
+ else if (field.time === true && v.length === 10)
369
+ report(path, "format", "Expected a UTC date and time");
370
+ else if (field.time === false && v.length !== 10)
371
+ report(path, "format", "Expected a date without a time");
372
+ break;
373
+ case "select": {
374
+ const option = (item, itemPath) => {
375
+ if (typeof item !== "string")
376
+ report(itemPath, "type", "Expected option string");
377
+ else if (!field.options.includes(item))
378
+ report(itemPath, "format", "Unknown option");
379
+ };
380
+ if (field.multiple) distinct("option array", option);
381
+ else option(v, path);
382
+ break;
383
+ }
384
+ case "reference": {
385
+ const id = (item, itemPath) => {
386
+ if (typeof item !== "string")
387
+ report(itemPath, "type", "Expected record ID");
388
+ else if (item.trim() === "")
389
+ report(itemPath, "format", "Record ID cannot be empty");
390
+ };
391
+ if (field.multiple) distinct("record ID array", id);
392
+ else id(v, path);
393
+ break;
394
+ }
395
+ case "link":
396
+ if (!object(v) || typeof v.value !== "string" || typeof v.kind !== "string" || !["url", "email", "phone"].includes(v.kind) || Object.keys(v).some(
397
+ (key) => key !== "kind" && key !== "value" && key !== "newTab"
398
+ ) || v.newTab !== void 0 && typeof v.newTab !== "boolean")
399
+ typeError("link object");
400
+ else if (v.newTab === true && v.kind !== "url")
401
+ report(path, "format", "Only URL links can open in a new tab");
402
+ else {
403
+ const valid = v.kind === "email" ? /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.value) : v.kind === "phone" ? /^\+?[0-9][0-9 ()-]{2,}$/.test(v.value) : validUrl(v.value);
404
+ if (!valid) report(path, "format", "Invalid link value");
405
+ }
406
+ break;
407
+ case "image":
408
+ case "file": {
409
+ const media = (entry, mediaPath) => {
410
+ if (!object(entry) || typeof entry.id !== "string" || entry.id.trim() === "" || entry.alt !== void 0 && typeof entry.alt !== "string" || Object.keys(entry).some((key) => key !== "id" && key !== "alt"))
411
+ report(mediaPath, "type", "Expected media reference { id, alt? }");
412
+ };
413
+ if (field.multiple) {
414
+ if (!Array.isArray(v)) typeError("media reference array");
415
+ else
416
+ v.forEach((entry, index) => {
417
+ media(entry, `${path}[${index}]`);
418
+ });
419
+ } else media(v, path);
420
+ break;
421
+ }
422
+ case "list":
423
+ if (!Array.isArray(v)) typeError("list of records");
424
+ else
425
+ v.forEach((entry, index) => {
426
+ validateFields(
427
+ field.fields,
428
+ entry,
429
+ `${path}[${index}]`,
430
+ publication,
431
+ report
432
+ );
433
+ });
434
+ break;
435
+ }
436
+ }
437
+ function validateRecord(schema, name, data, options2) {
438
+ defineSchema(schema);
439
+ const entity = [...schema.collections, ...schema.singletons ?? []].find(
440
+ (candidate) => candidate.name === name
441
+ );
442
+ if (!entity)
443
+ return [
444
+ {
445
+ path: "",
446
+ code: "unknown_collection",
447
+ message: `Unknown collection or singleton: ${name}`
448
+ }
449
+ ];
450
+ try {
451
+ canonicalJson(data);
452
+ } catch {
453
+ return [
454
+ {
455
+ path: "",
456
+ code: "invalid_json",
457
+ message: "Record must contain only plain, finite JSON values"
458
+ }
459
+ ];
460
+ }
461
+ const issues = [];
462
+ validateFields(
463
+ entity.fields,
464
+ data,
465
+ "",
466
+ options2.publication,
467
+ (path, code, message) => {
468
+ issues.push({ path, code, message });
469
+ }
470
+ );
471
+ return issues;
472
+ }
473
+ function stripNullFields(fields, data) {
474
+ if (!object(data)) return data;
475
+ const result = { ...data };
476
+ for (const field of fields) {
477
+ const value = result[field.name];
478
+ if (value === null && !field.required) delete result[field.name];
479
+ else if (field.kind === "list" && Array.isArray(value))
480
+ result[field.name] = value.map(
481
+ (row) => object(row) ? stripNullFields(field.fields, row) : row
482
+ );
483
+ }
484
+ return result;
485
+ }
486
+ function withNullFields(fields, data) {
487
+ const result = { ...data };
488
+ for (const field of fields) {
489
+ const value = result[field.name];
490
+ if (value === void 0) {
491
+ if (!field.required) result[field.name] = null;
492
+ } else if (field.kind === "list" && Array.isArray(value))
493
+ result[field.name] = value.map(
494
+ (row) => object(row) ? withNullFields(field.fields, row) : row
495
+ );
496
+ }
497
+ return result;
498
+ }
499
+ function referenceIds(value) {
500
+ if (typeof value === "string") return [value];
501
+ return Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
502
+ }
503
+ function dropMissingReferences(fields, data, exists) {
504
+ let changed = false;
505
+ const result = { ...data };
506
+ for (const field of fields) {
507
+ const value = result[field.name];
508
+ if (field.kind === "reference" && !field.required && value != null) {
509
+ const ids = referenceIds(value);
510
+ const kept = ids.filter((id) => exists(field.collection, id));
511
+ if (kept.length === ids.length) continue;
512
+ changed = true;
513
+ if (Array.isArray(value) && kept.length) result[field.name] = kept;
514
+ else delete result[field.name];
515
+ } else if (field.kind === "list" && Array.isArray(value)) {
516
+ const rows = value.map((row) => {
517
+ if (!object(row)) return row;
518
+ const resolved = dropMissingReferences(
519
+ field.fields,
520
+ row,
521
+ exists
522
+ );
523
+ if (resolved) changed = true;
524
+ return resolved ?? row;
525
+ });
526
+ if (changed) result[field.name] = rows;
527
+ }
528
+ }
529
+ return changed ? result : null;
530
+ }
531
+ function slugify(value) {
532
+ return value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
533
+ }
534
+ var mediaExtensions = {
535
+ "image/jpeg": [".jpg", ".jpeg"],
536
+ "image/png": [".png"],
537
+ "image/webp": [".webp"],
538
+ "image/gif": [".gif"],
539
+ "image/avif": [".avif"],
540
+ "application/pdf": [".pdf"]
541
+ };
542
+ function mediaAccepted(accept, asset) {
543
+ if (!accept) return true;
544
+ const type = asset.mediaType.toLowerCase();
545
+ const dot = asset.filename.lastIndexOf(".");
546
+ const extensions = /* @__PURE__ */ new Set([
547
+ ...mediaExtensions[type] ?? [],
548
+ ...dot > 0 ? [asset.filename.slice(dot).toLowerCase()] : []
549
+ ]);
550
+ return accept.some(
551
+ (entry) => entry.startsWith(".") ? extensions.has(entry) : entry.endsWith("/*") ? type.startsWith(entry.slice(0, -1)) : type === entry
552
+ );
553
+ }
554
+ function validUrl(value) {
555
+ if (/^(\/(?!\/)|#)/.test(value) && !/[\s\\]/.test(value)) return true;
556
+ try {
557
+ const url = new URL(value);
558
+ return ["http:", "https:"].includes(url.protocol) && !/\s/.test(value);
559
+ } catch {
560
+ return false;
561
+ }
562
+ }
563
+ function validDate(value) {
564
+ if (!/^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z)?$/.test(value))
565
+ return false;
566
+ const time = Date.parse(value);
567
+ if (!Number.isFinite(time)) return false;
568
+ const normalized = new Date(time).toISOString();
569
+ return value.length === 10 ? normalized.slice(0, 10) === value : normalized === (value.includes(".") ? value : value.replace("Z", ".000Z"));
570
+ }
571
+ export {
572
+ canonicalJson,
573
+ defineSchema,
574
+ dropMissingReferences,
575
+ isLocalPath,
576
+ mediaAccepted,
577
+ readStoredSchema,
578
+ referenceIds,
579
+ resolvePreviewPath,
580
+ schemaHash,
581
+ slugify,
582
+ stripNullFields,
583
+ validateRecord,
584
+ withNullFields
585
+ };
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "@plume-press/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.4.0",
4
+ "description": "The plume command: set up a website's Plume CMS and check its builds.",
5
+ "homepage": "https://app.plume.press",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "engines": {
9
+ "node": ">=22.13.0"
10
+ },
11
+ "bin": {
12
+ "plume": "dist/cli.mjs"
13
+ },
14
+ "exports": {
15
+ "./local": {
16
+ "types": "./dist/local.d.ts",
17
+ "default": "./dist/local.mjs"
18
+ },
19
+ "./schema": {
20
+ "types": "./dist/schema.d.ts",
21
+ "default": "./dist/schema.mjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist/local.mjs",
26
+ "dist/local.d.ts",
27
+ "dist/cli.mjs",
28
+ "dist/schema.mjs",
29
+ "dist/schema.d.ts",
30
+ "README.md",
31
+ "LICENSE",
32
+ "THIRD_PARTY_NOTICES.md"
33
+ ],
34
+ "dependencies": {
35
+ "jiti": "2.7.0"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ }
40
+ }