@yrest/cli 0.11.1 → 0.11.2
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/README.md +96 -24
- package/dist/cli/index.js +85 -16
- package/dist/cli/index.mjs +85 -16
- package/dist/index.d.mts +69 -3
- package/dist/index.d.ts +69 -3
- package/dist/index.js +87 -11
- package/dist/index.mjs +87 -11
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
[](https://github.com/aggiovato/yRest/actions)
|
|
11
11
|
[](https://www.npmjs.com/package/@yrest/cli)
|
|
12
12
|
[](https://www.typescriptlang.org/)
|
|
13
|
-
[](https://socket.dev/npm/package/@yrest/cli)
|
|
14
14
|
|
|
15
15
|
YAML-powered json-server alternative. Zero-config REST API mock server with full CRUD, relations, filters and snapshots from a `db.yml` file.
|
|
16
16
|
|
|
@@ -241,26 +241,59 @@ posts:
|
|
|
241
241
|
|
|
242
242
|
Each top-level key becomes a resource with full CRUD endpoints.
|
|
243
243
|
|
|
244
|
+
Alternatively, collections can be grouped under a `_data` block:
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
_data:
|
|
248
|
+
users:
|
|
249
|
+
- id: 1
|
|
250
|
+
name: Ana
|
|
251
|
+
posts:
|
|
252
|
+
- id: 1
|
|
253
|
+
title: First post
|
|
254
|
+
userId: 1
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Both formats are equivalent. You can also mix them — if the same collection name appears both inside `_data` and at the root level, the root-level entry wins. The file is persisted in whichever format it was originally written.
|
|
258
|
+
|
|
244
259
|
---
|
|
245
260
|
|
|
246
261
|
## Field annotations (`_schema`)
|
|
247
262
|
|
|
248
263
|
Add a `_schema` block to `db.yml` to declare field-level metadata per collection. Used by the OpenAPI generator to produce accurate schemas — has no effect on runtime CRUD behavior.
|
|
249
264
|
|
|
265
|
+
All keys inside `_schema` field objects use the `_` prefix convention. Two shorthand strings are also accepted at the field level:
|
|
266
|
+
|
|
250
267
|
```yaml
|
|
251
268
|
_schema:
|
|
252
269
|
users:
|
|
253
|
-
name: required # shorthand
|
|
270
|
+
name: required # shorthand → { _required: true }
|
|
254
271
|
email:
|
|
255
|
-
|
|
256
|
-
|
|
272
|
+
_required: true
|
|
273
|
+
_type: string
|
|
274
|
+
_format: email
|
|
275
|
+
_minLength: 5
|
|
276
|
+
_maxLength: 100
|
|
257
277
|
age:
|
|
258
|
-
|
|
278
|
+
_type: integer
|
|
279
|
+
_minimum: 0
|
|
280
|
+
_maximum: 150
|
|
281
|
+
_nullable: true
|
|
259
282
|
role:
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
283
|
+
_type: string
|
|
284
|
+
_enum: [admin, editor, viewer]
|
|
285
|
+
_default: viewer
|
|
286
|
+
_description: User role
|
|
287
|
+
tags:
|
|
288
|
+
_type: array
|
|
289
|
+
_items:
|
|
290
|
+
_type: string
|
|
291
|
+
_minItems: 1
|
|
292
|
+
_uniqueItems: true
|
|
293
|
+
createdAt:
|
|
294
|
+
_type: string
|
|
295
|
+
_format: date-time
|
|
296
|
+
_readOnly: true
|
|
264
297
|
|
|
265
298
|
users:
|
|
266
299
|
- id: 1
|
|
@@ -270,14 +303,53 @@ users:
|
|
|
270
303
|
role: admin
|
|
271
304
|
```
|
|
272
305
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
|
276
|
-
|
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
306
|
+
**Core**
|
|
307
|
+
|
|
308
|
+
| Key | Type | Description |
|
|
309
|
+
| -------------- | --------- | ----------------------------------------------------------------------------------------- |
|
|
310
|
+
| `_required` | `boolean` | Marks the field as required in the OpenAPI schema |
|
|
311
|
+
| `_type` | `string` | Overrides the inferred type (`string`, `integer`, `number`, `boolean`, `array`, `object`) |
|
|
312
|
+
| `_format` | `string` | OpenAPI format hint (`email`, `date`, `date-time`, `uuid`, `uri`, …) |
|
|
313
|
+
| `_enum` | `array` | Restricts the field to a fixed set of values |
|
|
314
|
+
| `_description` | `string` | Field description included in the OpenAPI schema |
|
|
315
|
+
| `_default` | `any` | Default value shown in the OpenAPI schema |
|
|
316
|
+
| `_example` | `any` | Example value shown in the OpenAPI schema |
|
|
317
|
+
| `_nullable` | `boolean` | Allows the field to be `null` |
|
|
318
|
+
|
|
319
|
+
**String constraints**
|
|
320
|
+
|
|
321
|
+
| Key | Type | Description |
|
|
322
|
+
| ------------ | -------- | ------------------------------------ |
|
|
323
|
+
| `_minLength` | `number` | Minimum string length |
|
|
324
|
+
| `_maxLength` | `number` | Maximum string length |
|
|
325
|
+
| `_pattern` | `string` | Regex pattern the value must satisfy |
|
|
326
|
+
|
|
327
|
+
**Number / integer constraints**
|
|
328
|
+
|
|
329
|
+
| Key | Type | Description |
|
|
330
|
+
| ------------------- | -------- | ----------------------------- |
|
|
331
|
+
| `_minimum` | `number` | Inclusive lower bound |
|
|
332
|
+
| `_maximum` | `number` | Inclusive upper bound |
|
|
333
|
+
| `_exclusiveMinimum` | `number` | Exclusive lower bound |
|
|
334
|
+
| `_exclusiveMaximum` | `number` | Exclusive upper bound |
|
|
335
|
+
| `_multipleOf` | `number` | Value must be a multiple of N |
|
|
336
|
+
|
|
337
|
+
**Array constraints**
|
|
338
|
+
|
|
339
|
+
| Key | Type | Description |
|
|
340
|
+
| -------------- | --------- | ------------------------------------------------------------ |
|
|
341
|
+
| `_minItems` | `number` | Minimum number of array items |
|
|
342
|
+
| `_maxItems` | `number` | Maximum number of array items |
|
|
343
|
+
| `_uniqueItems` | `boolean` | All items must be unique |
|
|
344
|
+
| `_items` | `object` | Schema for array items — accepts `_type`, `_format`, `_enum` |
|
|
345
|
+
|
|
346
|
+
**OpenAPI meta**
|
|
347
|
+
|
|
348
|
+
| Key | Type | Description |
|
|
349
|
+
| ------------- | --------- | --------------------------------------- |
|
|
350
|
+
| `_deprecated` | `boolean` | Marks the field as deprecated |
|
|
351
|
+
| `_readOnly` | `boolean` | Field is read-only (excluded from POST) |
|
|
352
|
+
| `_writeOnly` | `boolean` | Field is write-only (excluded from GET) |
|
|
281
353
|
|
|
282
354
|
Fields not listed in `_schema` are inferred from the first items in the collection and treated as optional. `_schema` itself is excluded from the generated REST resources.
|
|
283
355
|
|
|
@@ -722,14 +794,14 @@ when:
|
|
|
722
794
|
- body.role: superadmin
|
|
723
795
|
```
|
|
724
796
|
|
|
725
|
-
Condition keys use dot-notation to address request data:
|
|
797
|
+
Condition keys use dot-notation to address request data. Both bare and `_`-prefixed forms are accepted:
|
|
726
798
|
|
|
727
|
-
| Prefix
|
|
728
|
-
|
|
|
729
|
-
| `body.X`
|
|
730
|
-
| `params.X`
|
|
731
|
-
| `query.X`
|
|
732
|
-
| `headers.X` | `headers.x-api-key` | `req.headers["x-api-key"]` |
|
|
799
|
+
| Prefix | Example | Resolves to |
|
|
800
|
+
| -------------------------- | ------------------- | -------------------------- |
|
|
801
|
+
| `body.X` / `_body.X` | `body.email` | `req.body.email` |
|
|
802
|
+
| `params.X` / `_params.X` | `params.id` | `req.params.id` |
|
|
803
|
+
| `query.X` / `_query.X` | `query.page` | `req.query.page` |
|
|
804
|
+
| `headers.X` / `_headers.X` | `headers.x-api-key` | `req.headers["x-api-key"]` |
|
|
733
805
|
|
|
734
806
|
Field operator suffixes (`_ne`, `_like`, `_start`, `_regex`, `_gte`, `_lte`) work on condition keys exactly as they do on query params:
|
|
735
807
|
|
package/dist/cli/index.js
CHANGED
|
@@ -717,6 +717,7 @@ function parseSchema(raw) {
|
|
|
717
717
|
}
|
|
718
718
|
return result;
|
|
719
719
|
}
|
|
720
|
+
var VALID_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean", "object", "array"]);
|
|
720
721
|
function normaliseFieldDef(value) {
|
|
721
722
|
if (value === "required") return { required: true };
|
|
722
723
|
if (value === "optional") return { required: false };
|
|
@@ -724,15 +725,60 @@ function normaliseFieldDef(value) {
|
|
|
724
725
|
const v = value;
|
|
725
726
|
const def = {};
|
|
726
727
|
if (v["_required"] === true || v["_required"] === false) def.required = v["_required"];
|
|
727
|
-
if (typeof v["_type"] === "string" &&
|
|
728
|
+
if (typeof v["_type"] === "string" && VALID_TYPES.has(v["_type"]))
|
|
728
729
|
def.type = v["_type"];
|
|
729
730
|
if (typeof v["_format"] === "string") def.format = v["_format"];
|
|
730
731
|
if (Array.isArray(v["_enum"])) def.enum = v["_enum"];
|
|
731
732
|
if (typeof v["_description"] === "string") def.description = v["_description"];
|
|
732
733
|
if (v["_default"] !== void 0) def.default = v["_default"];
|
|
734
|
+
if (v["_example"] !== void 0) def.example = v["_example"];
|
|
735
|
+
if (v["_nullable"] === true || v["_nullable"] === false) def.nullable = v["_nullable"];
|
|
736
|
+
if (typeof v["_minLength"] === "number") def.minLength = v["_minLength"];
|
|
737
|
+
if (typeof v["_maxLength"] === "number") def.maxLength = v["_maxLength"];
|
|
738
|
+
if (typeof v["_pattern"] === "string") def.pattern = v["_pattern"];
|
|
739
|
+
if (typeof v["_minimum"] === "number") def.minimum = v["_minimum"];
|
|
740
|
+
if (typeof v["_maximum"] === "number") def.maximum = v["_maximum"];
|
|
741
|
+
if (typeof v["_exclusiveMinimum"] === "number") def.exclusiveMinimum = v["_exclusiveMinimum"];
|
|
742
|
+
if (typeof v["_exclusiveMaximum"] === "number") def.exclusiveMaximum = v["_exclusiveMaximum"];
|
|
743
|
+
if (typeof v["_multipleOf"] === "number") def.multipleOf = v["_multipleOf"];
|
|
744
|
+
if (typeof v["_minItems"] === "number") def.minItems = v["_minItems"];
|
|
745
|
+
if (typeof v["_maxItems"] === "number") def.maxItems = v["_maxItems"];
|
|
746
|
+
if (v["_uniqueItems"] === true || v["_uniqueItems"] === false)
|
|
747
|
+
def.uniqueItems = v["_uniqueItems"];
|
|
748
|
+
if (v["_items"] && typeof v["_items"] === "object" && !Array.isArray(v["_items"])) {
|
|
749
|
+
const items = v["_items"];
|
|
750
|
+
const itemsDef = {};
|
|
751
|
+
if (typeof items["_type"] === "string" && VALID_TYPES.has(items["_type"]))
|
|
752
|
+
itemsDef.type = items["_type"];
|
|
753
|
+
if (typeof items["_format"] === "string") itemsDef.format = items["_format"];
|
|
754
|
+
if (Array.isArray(items["_enum"])) itemsDef.enum = items["_enum"];
|
|
755
|
+
def.items = itemsDef;
|
|
756
|
+
}
|
|
757
|
+
if (v["_deprecated"] === true || v["_deprecated"] === false)
|
|
758
|
+
def.deprecated = v["_deprecated"];
|
|
759
|
+
if (v["_readOnly"] === true || v["_readOnly"] === false) def.readOnly = v["_readOnly"];
|
|
760
|
+
if (v["_writeOnly"] === true || v["_writeOnly"] === false)
|
|
761
|
+
def.writeOnly = v["_writeOnly"];
|
|
733
762
|
return def;
|
|
734
763
|
}
|
|
735
764
|
|
|
765
|
+
// src/storage/parseData.ts
|
|
766
|
+
var RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema", "_data"]);
|
|
767
|
+
function parseData(raw) {
|
|
768
|
+
const block = raw["_data"];
|
|
769
|
+
const fromBlock = block && typeof block === "object" && !Array.isArray(block) ? Object.fromEntries(
|
|
770
|
+
Object.entries(block).filter(([, v]) => Array.isArray(v))
|
|
771
|
+
) : {};
|
|
772
|
+
const fromRoot = Object.fromEntries(
|
|
773
|
+
Object.entries(raw).filter(([key, val]) => !RESERVED.has(key) && Array.isArray(val))
|
|
774
|
+
);
|
|
775
|
+
return { ...fromBlock, ...fromRoot };
|
|
776
|
+
}
|
|
777
|
+
function hasDataBlock(raw) {
|
|
778
|
+
const d = raw["_data"];
|
|
779
|
+
return d !== null && d !== void 0 && typeof d === "object" && !Array.isArray(d);
|
|
780
|
+
}
|
|
781
|
+
|
|
736
782
|
// src/utils/deepCopy.ts
|
|
737
783
|
function deepCopyData(source) {
|
|
738
784
|
return Object.fromEntries(
|
|
@@ -798,13 +844,11 @@ function serializeScenario(s) {
|
|
|
798
844
|
function createYrestStorage(filePath) {
|
|
799
845
|
const absPath = (0, import_node_path2.resolve)(filePath);
|
|
800
846
|
const raw = (0, import_yaml.parse)((0, import_node_fs2.readFileSync)(absPath, "utf8")) ?? {};
|
|
801
|
-
const RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
802
847
|
const relations = parseRelations(raw["_rel"]);
|
|
803
848
|
const routes = parseRoutes(raw["_routes"]);
|
|
804
849
|
const schema = parseSchema(raw["_schema"]);
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
);
|
|
850
|
+
const dataBlock = hasDataBlock(raw);
|
|
851
|
+
const data = parseData(raw);
|
|
808
852
|
let snapshot = {
|
|
809
853
|
data: deepCopyData(data),
|
|
810
854
|
relations: { ...relations },
|
|
@@ -833,7 +877,11 @@ function createYrestStorage(filePath) {
|
|
|
833
877
|
const payload = {};
|
|
834
878
|
if (Object.keys(relations).length > 0) payload._rel = serializeRelations(relations);
|
|
835
879
|
if (routes.length > 0) payload._routes = serializeRoutes(routes);
|
|
836
|
-
|
|
880
|
+
if (dataBlock) {
|
|
881
|
+
payload._data = data;
|
|
882
|
+
} else {
|
|
883
|
+
Object.assign(payload, data);
|
|
884
|
+
}
|
|
837
885
|
const tmp = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(absPath), `.yrest-${(0, import_node_crypto.randomUUID)()}.tmp`);
|
|
838
886
|
(0, import_node_fs2.writeFileSync)(tmp, (0, import_yaml.stringify)(payload), "utf8");
|
|
839
887
|
(0, import_node_fs2.renameSync)(tmp, absPath);
|
|
@@ -841,9 +889,7 @@ function createYrestStorage(filePath) {
|
|
|
841
889
|
reload() {
|
|
842
890
|
const fresh = (0, import_yaml.parse)((0, import_node_fs2.readFileSync)(absPath, "utf8")) ?? {};
|
|
843
891
|
const freshRelations = parseRelations(fresh["_rel"]);
|
|
844
|
-
const freshData =
|
|
845
|
-
Object.entries(fresh).filter(([key]) => !RESERVED.has(key))
|
|
846
|
-
);
|
|
892
|
+
const freshData = parseData(fresh);
|
|
847
893
|
for (const key of Object.keys(data)) delete data[key];
|
|
848
894
|
Object.assign(data, freshData);
|
|
849
895
|
for (const key of Object.keys(relations)) delete relations[key];
|
|
@@ -2403,15 +2449,19 @@ function resolveRequestPath(dotPath, req) {
|
|
|
2403
2449
|
const [root, ...rest] = dotPath.split(".");
|
|
2404
2450
|
let value;
|
|
2405
2451
|
switch (root) {
|
|
2452
|
+
case "_body":
|
|
2406
2453
|
case "body":
|
|
2407
2454
|
value = req.body;
|
|
2408
2455
|
break;
|
|
2456
|
+
case "_params":
|
|
2409
2457
|
case "params":
|
|
2410
2458
|
value = req.params;
|
|
2411
2459
|
break;
|
|
2460
|
+
case "_query":
|
|
2412
2461
|
case "query":
|
|
2413
2462
|
value = req.query;
|
|
2414
2463
|
break;
|
|
2464
|
+
case "_headers":
|
|
2415
2465
|
case "headers":
|
|
2416
2466
|
value = req.headers;
|
|
2417
2467
|
break;
|
|
@@ -2703,6 +2753,23 @@ function buildCollectionSchema(items, fieldDefs = {}) {
|
|
|
2703
2753
|
if (def?.description) prop.description = def.description;
|
|
2704
2754
|
if (def?.enum) prop.enum = def.enum;
|
|
2705
2755
|
if (def?.default !== void 0) prop.default = def.default;
|
|
2756
|
+
if (def?.example !== void 0) prop.example = def.example;
|
|
2757
|
+
if (def?.nullable) prop.nullable = def.nullable;
|
|
2758
|
+
if (def?.minLength !== void 0) prop.minLength = def.minLength;
|
|
2759
|
+
if (def?.maxLength !== void 0) prop.maxLength = def.maxLength;
|
|
2760
|
+
if (def?.pattern) prop.pattern = def.pattern;
|
|
2761
|
+
if (def?.minimum !== void 0) prop.minimum = def.minimum;
|
|
2762
|
+
if (def?.maximum !== void 0) prop.maximum = def.maximum;
|
|
2763
|
+
if (def?.exclusiveMinimum !== void 0) prop.exclusiveMinimum = def.exclusiveMinimum;
|
|
2764
|
+
if (def?.exclusiveMaximum !== void 0) prop.exclusiveMaximum = def.exclusiveMaximum;
|
|
2765
|
+
if (def?.multipleOf !== void 0) prop.multipleOf = def.multipleOf;
|
|
2766
|
+
if (def?.items) prop.items = def.items;
|
|
2767
|
+
if (def?.minItems !== void 0) prop.minItems = def.minItems;
|
|
2768
|
+
if (def?.maxItems !== void 0) prop.maxItems = def.maxItems;
|
|
2769
|
+
if (def?.uniqueItems !== void 0) prop.uniqueItems = def.uniqueItems;
|
|
2770
|
+
if (def?.deprecated) prop.deprecated = def.deprecated;
|
|
2771
|
+
if (def?.readOnly) prop.readOnly = def.readOnly;
|
|
2772
|
+
if (def?.writeOnly) prop.writeOnly = def.writeOnly;
|
|
2706
2773
|
properties[field] = prop;
|
|
2707
2774
|
if (def?.required === true) required.push(field);
|
|
2708
2775
|
}
|
|
@@ -3245,6 +3312,14 @@ async function loadHandlers(filePath) {
|
|
|
3245
3312
|
}
|
|
3246
3313
|
|
|
3247
3314
|
// src/cli/commands/serve.ts
|
|
3315
|
+
function buildServeOptions(file, fileConfig, cliOverrides, fileArgIsExplicit = false) {
|
|
3316
|
+
return yrestOptionsSchema.parse({
|
|
3317
|
+
file,
|
|
3318
|
+
...fileConfig,
|
|
3319
|
+
...fileArgIsExplicit ? { file } : {},
|
|
3320
|
+
...cliOverrides
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3248
3323
|
function registerServe(program2) {
|
|
3249
3324
|
program2.command("serve").description("Start the mock server using a YAML file as database").argument("[file]", "Path to the YAML database file", "db.yml").option("-p, --port <number>", "Port to listen on", "3070").option("-H, --host <host>", "Host to bind", "localhost").option("-b, --base <path>", "Base path prefix for all routes", "").option("-w, --watch", "Reload db.yml automatically when it changes on disk").option("-r, --readonly", "Reject all write operations (POST, PUT, PATCH, DELETE) with 405").option(
|
|
3250
3325
|
"-d, --delay <ms>",
|
|
@@ -3268,13 +3343,7 @@ function registerServe(program2) {
|
|
|
3268
3343
|
const cliOverrides = Object.fromEntries(
|
|
3269
3344
|
Object.entries(flags).filter(([key]) => cmd.getOptionValueSource(key) === "cli")
|
|
3270
3345
|
);
|
|
3271
|
-
const
|
|
3272
|
-
file,
|
|
3273
|
-
...fileConfig,
|
|
3274
|
-
...cmd.args.length > 0 ? { file } : {},
|
|
3275
|
-
...cliOverrides
|
|
3276
|
-
};
|
|
3277
|
-
const options = yrestOptionsSchema.parse(merged);
|
|
3346
|
+
const options = buildServeOptions(file, fileConfig, cliOverrides, cmd.args.length > 0);
|
|
3278
3347
|
let storage;
|
|
3279
3348
|
try {
|
|
3280
3349
|
storage = createYrestStorage(options.file);
|
package/dist/cli/index.mjs
CHANGED
|
@@ -690,6 +690,7 @@ function parseSchema(raw) {
|
|
|
690
690
|
}
|
|
691
691
|
return result;
|
|
692
692
|
}
|
|
693
|
+
var VALID_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean", "object", "array"]);
|
|
693
694
|
function normaliseFieldDef(value) {
|
|
694
695
|
if (value === "required") return { required: true };
|
|
695
696
|
if (value === "optional") return { required: false };
|
|
@@ -697,15 +698,60 @@ function normaliseFieldDef(value) {
|
|
|
697
698
|
const v = value;
|
|
698
699
|
const def = {};
|
|
699
700
|
if (v["_required"] === true || v["_required"] === false) def.required = v["_required"];
|
|
700
|
-
if (typeof v["_type"] === "string" &&
|
|
701
|
+
if (typeof v["_type"] === "string" && VALID_TYPES.has(v["_type"]))
|
|
701
702
|
def.type = v["_type"];
|
|
702
703
|
if (typeof v["_format"] === "string") def.format = v["_format"];
|
|
703
704
|
if (Array.isArray(v["_enum"])) def.enum = v["_enum"];
|
|
704
705
|
if (typeof v["_description"] === "string") def.description = v["_description"];
|
|
705
706
|
if (v["_default"] !== void 0) def.default = v["_default"];
|
|
707
|
+
if (v["_example"] !== void 0) def.example = v["_example"];
|
|
708
|
+
if (v["_nullable"] === true || v["_nullable"] === false) def.nullable = v["_nullable"];
|
|
709
|
+
if (typeof v["_minLength"] === "number") def.minLength = v["_minLength"];
|
|
710
|
+
if (typeof v["_maxLength"] === "number") def.maxLength = v["_maxLength"];
|
|
711
|
+
if (typeof v["_pattern"] === "string") def.pattern = v["_pattern"];
|
|
712
|
+
if (typeof v["_minimum"] === "number") def.minimum = v["_minimum"];
|
|
713
|
+
if (typeof v["_maximum"] === "number") def.maximum = v["_maximum"];
|
|
714
|
+
if (typeof v["_exclusiveMinimum"] === "number") def.exclusiveMinimum = v["_exclusiveMinimum"];
|
|
715
|
+
if (typeof v["_exclusiveMaximum"] === "number") def.exclusiveMaximum = v["_exclusiveMaximum"];
|
|
716
|
+
if (typeof v["_multipleOf"] === "number") def.multipleOf = v["_multipleOf"];
|
|
717
|
+
if (typeof v["_minItems"] === "number") def.minItems = v["_minItems"];
|
|
718
|
+
if (typeof v["_maxItems"] === "number") def.maxItems = v["_maxItems"];
|
|
719
|
+
if (v["_uniqueItems"] === true || v["_uniqueItems"] === false)
|
|
720
|
+
def.uniqueItems = v["_uniqueItems"];
|
|
721
|
+
if (v["_items"] && typeof v["_items"] === "object" && !Array.isArray(v["_items"])) {
|
|
722
|
+
const items = v["_items"];
|
|
723
|
+
const itemsDef = {};
|
|
724
|
+
if (typeof items["_type"] === "string" && VALID_TYPES.has(items["_type"]))
|
|
725
|
+
itemsDef.type = items["_type"];
|
|
726
|
+
if (typeof items["_format"] === "string") itemsDef.format = items["_format"];
|
|
727
|
+
if (Array.isArray(items["_enum"])) itemsDef.enum = items["_enum"];
|
|
728
|
+
def.items = itemsDef;
|
|
729
|
+
}
|
|
730
|
+
if (v["_deprecated"] === true || v["_deprecated"] === false)
|
|
731
|
+
def.deprecated = v["_deprecated"];
|
|
732
|
+
if (v["_readOnly"] === true || v["_readOnly"] === false) def.readOnly = v["_readOnly"];
|
|
733
|
+
if (v["_writeOnly"] === true || v["_writeOnly"] === false)
|
|
734
|
+
def.writeOnly = v["_writeOnly"];
|
|
706
735
|
return def;
|
|
707
736
|
}
|
|
708
737
|
|
|
738
|
+
// src/storage/parseData.ts
|
|
739
|
+
var RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema", "_data"]);
|
|
740
|
+
function parseData(raw) {
|
|
741
|
+
const block = raw["_data"];
|
|
742
|
+
const fromBlock = block && typeof block === "object" && !Array.isArray(block) ? Object.fromEntries(
|
|
743
|
+
Object.entries(block).filter(([, v]) => Array.isArray(v))
|
|
744
|
+
) : {};
|
|
745
|
+
const fromRoot = Object.fromEntries(
|
|
746
|
+
Object.entries(raw).filter(([key, val]) => !RESERVED.has(key) && Array.isArray(val))
|
|
747
|
+
);
|
|
748
|
+
return { ...fromBlock, ...fromRoot };
|
|
749
|
+
}
|
|
750
|
+
function hasDataBlock(raw) {
|
|
751
|
+
const d = raw["_data"];
|
|
752
|
+
return d !== null && d !== void 0 && typeof d === "object" && !Array.isArray(d);
|
|
753
|
+
}
|
|
754
|
+
|
|
709
755
|
// src/utils/deepCopy.ts
|
|
710
756
|
function deepCopyData(source) {
|
|
711
757
|
return Object.fromEntries(
|
|
@@ -771,13 +817,11 @@ function serializeScenario(s) {
|
|
|
771
817
|
function createYrestStorage(filePath) {
|
|
772
818
|
const absPath = resolve2(filePath);
|
|
773
819
|
const raw = parse(readFileSync(absPath, "utf8")) ?? {};
|
|
774
|
-
const RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
775
820
|
const relations = parseRelations(raw["_rel"]);
|
|
776
821
|
const routes = parseRoutes(raw["_routes"]);
|
|
777
822
|
const schema = parseSchema(raw["_schema"]);
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
);
|
|
823
|
+
const dataBlock = hasDataBlock(raw);
|
|
824
|
+
const data = parseData(raw);
|
|
781
825
|
let snapshot = {
|
|
782
826
|
data: deepCopyData(data),
|
|
783
827
|
relations: { ...relations },
|
|
@@ -806,7 +850,11 @@ function createYrestStorage(filePath) {
|
|
|
806
850
|
const payload = {};
|
|
807
851
|
if (Object.keys(relations).length > 0) payload._rel = serializeRelations(relations);
|
|
808
852
|
if (routes.length > 0) payload._routes = serializeRoutes(routes);
|
|
809
|
-
|
|
853
|
+
if (dataBlock) {
|
|
854
|
+
payload._data = data;
|
|
855
|
+
} else {
|
|
856
|
+
Object.assign(payload, data);
|
|
857
|
+
}
|
|
810
858
|
const tmp = resolve2(dirname(absPath), `.yrest-${randomUUID()}.tmp`);
|
|
811
859
|
writeFileSync2(tmp, stringify(payload), "utf8");
|
|
812
860
|
renameSync(tmp, absPath);
|
|
@@ -814,9 +862,7 @@ function createYrestStorage(filePath) {
|
|
|
814
862
|
reload() {
|
|
815
863
|
const fresh = parse(readFileSync(absPath, "utf8")) ?? {};
|
|
816
864
|
const freshRelations = parseRelations(fresh["_rel"]);
|
|
817
|
-
const freshData =
|
|
818
|
-
Object.entries(fresh).filter(([key]) => !RESERVED.has(key))
|
|
819
|
-
);
|
|
865
|
+
const freshData = parseData(fresh);
|
|
820
866
|
for (const key of Object.keys(data)) delete data[key];
|
|
821
867
|
Object.assign(data, freshData);
|
|
822
868
|
for (const key of Object.keys(relations)) delete relations[key];
|
|
@@ -2376,15 +2422,19 @@ function resolveRequestPath(dotPath, req) {
|
|
|
2376
2422
|
const [root, ...rest] = dotPath.split(".");
|
|
2377
2423
|
let value;
|
|
2378
2424
|
switch (root) {
|
|
2425
|
+
case "_body":
|
|
2379
2426
|
case "body":
|
|
2380
2427
|
value = req.body;
|
|
2381
2428
|
break;
|
|
2429
|
+
case "_params":
|
|
2382
2430
|
case "params":
|
|
2383
2431
|
value = req.params;
|
|
2384
2432
|
break;
|
|
2433
|
+
case "_query":
|
|
2385
2434
|
case "query":
|
|
2386
2435
|
value = req.query;
|
|
2387
2436
|
break;
|
|
2437
|
+
case "_headers":
|
|
2388
2438
|
case "headers":
|
|
2389
2439
|
value = req.headers;
|
|
2390
2440
|
break;
|
|
@@ -2676,6 +2726,23 @@ function buildCollectionSchema(items, fieldDefs = {}) {
|
|
|
2676
2726
|
if (def?.description) prop.description = def.description;
|
|
2677
2727
|
if (def?.enum) prop.enum = def.enum;
|
|
2678
2728
|
if (def?.default !== void 0) prop.default = def.default;
|
|
2729
|
+
if (def?.example !== void 0) prop.example = def.example;
|
|
2730
|
+
if (def?.nullable) prop.nullable = def.nullable;
|
|
2731
|
+
if (def?.minLength !== void 0) prop.minLength = def.minLength;
|
|
2732
|
+
if (def?.maxLength !== void 0) prop.maxLength = def.maxLength;
|
|
2733
|
+
if (def?.pattern) prop.pattern = def.pattern;
|
|
2734
|
+
if (def?.minimum !== void 0) prop.minimum = def.minimum;
|
|
2735
|
+
if (def?.maximum !== void 0) prop.maximum = def.maximum;
|
|
2736
|
+
if (def?.exclusiveMinimum !== void 0) prop.exclusiveMinimum = def.exclusiveMinimum;
|
|
2737
|
+
if (def?.exclusiveMaximum !== void 0) prop.exclusiveMaximum = def.exclusiveMaximum;
|
|
2738
|
+
if (def?.multipleOf !== void 0) prop.multipleOf = def.multipleOf;
|
|
2739
|
+
if (def?.items) prop.items = def.items;
|
|
2740
|
+
if (def?.minItems !== void 0) prop.minItems = def.minItems;
|
|
2741
|
+
if (def?.maxItems !== void 0) prop.maxItems = def.maxItems;
|
|
2742
|
+
if (def?.uniqueItems !== void 0) prop.uniqueItems = def.uniqueItems;
|
|
2743
|
+
if (def?.deprecated) prop.deprecated = def.deprecated;
|
|
2744
|
+
if (def?.readOnly) prop.readOnly = def.readOnly;
|
|
2745
|
+
if (def?.writeOnly) prop.writeOnly = def.writeOnly;
|
|
2679
2746
|
properties[field] = prop;
|
|
2680
2747
|
if (def?.required === true) required.push(field);
|
|
2681
2748
|
}
|
|
@@ -3218,6 +3285,14 @@ async function loadHandlers(filePath) {
|
|
|
3218
3285
|
}
|
|
3219
3286
|
|
|
3220
3287
|
// src/cli/commands/serve.ts
|
|
3288
|
+
function buildServeOptions(file, fileConfig, cliOverrides, fileArgIsExplicit = false) {
|
|
3289
|
+
return yrestOptionsSchema.parse({
|
|
3290
|
+
file,
|
|
3291
|
+
...fileConfig,
|
|
3292
|
+
...fileArgIsExplicit ? { file } : {},
|
|
3293
|
+
...cliOverrides
|
|
3294
|
+
});
|
|
3295
|
+
}
|
|
3221
3296
|
function registerServe(program2) {
|
|
3222
3297
|
program2.command("serve").description("Start the mock server using a YAML file as database").argument("[file]", "Path to the YAML database file", "db.yml").option("-p, --port <number>", "Port to listen on", "3070").option("-H, --host <host>", "Host to bind", "localhost").option("-b, --base <path>", "Base path prefix for all routes", "").option("-w, --watch", "Reload db.yml automatically when it changes on disk").option("-r, --readonly", "Reject all write operations (POST, PUT, PATCH, DELETE) with 405").option(
|
|
3223
3298
|
"-d, --delay <ms>",
|
|
@@ -3241,13 +3316,7 @@ function registerServe(program2) {
|
|
|
3241
3316
|
const cliOverrides = Object.fromEntries(
|
|
3242
3317
|
Object.entries(flags).filter(([key]) => cmd.getOptionValueSource(key) === "cli")
|
|
3243
3318
|
);
|
|
3244
|
-
const
|
|
3245
|
-
file,
|
|
3246
|
-
...fileConfig,
|
|
3247
|
-
...cmd.args.length > 0 ? { file } : {},
|
|
3248
|
-
...cliOverrides
|
|
3249
|
-
};
|
|
3250
|
-
const options = yrestOptionsSchema.parse(merged);
|
|
3319
|
+
const options = buildServeOptions(file, fileConfig, cliOverrides, cmd.args.length > 0);
|
|
3251
3320
|
let storage;
|
|
3252
3321
|
try {
|
|
3253
3322
|
storage = createYrestStorage(options.file);
|
package/dist/index.d.mts
CHANGED
|
@@ -10,21 +10,87 @@ type Resource = Record<string, unknown>;
|
|
|
10
10
|
* All properties are optional — omit what you don't need. Fields not mentioned
|
|
11
11
|
* in `_schema` are inferred from the collection data and treated as optional.
|
|
12
12
|
*
|
|
13
|
+
* In the YAML file every property uses the `_` prefix convention:
|
|
14
|
+
* `_type`, `_required`, `_format`, `_enum`, `_description`, `_default`, etc.
|
|
15
|
+
*
|
|
13
16
|
* Shorthand: `fieldName: required` normalises to `{ required: true }`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```yaml
|
|
20
|
+
* _schema:
|
|
21
|
+
* users:
|
|
22
|
+
* name:
|
|
23
|
+
* _type: string
|
|
24
|
+
* _required: true
|
|
25
|
+
* _minLength: 2
|
|
26
|
+
* _maxLength: 100
|
|
27
|
+
* email:
|
|
28
|
+
* _type: string
|
|
29
|
+
* _required: true
|
|
30
|
+
* _format: email
|
|
31
|
+
* age:
|
|
32
|
+
* _type: integer
|
|
33
|
+
* _minimum: 0
|
|
34
|
+
* _maximum: 120
|
|
35
|
+
* _nullable: true
|
|
36
|
+
* role:
|
|
37
|
+
* _type: string
|
|
38
|
+
* _enum: [admin, editor, viewer]
|
|
39
|
+
* _default: viewer
|
|
40
|
+
* tags:
|
|
41
|
+
* _type: array
|
|
42
|
+
* _items:
|
|
43
|
+
* _type: string
|
|
44
|
+
* _uniqueItems: true
|
|
45
|
+
* ```
|
|
14
46
|
*/
|
|
15
47
|
type FieldDef = {
|
|
16
|
-
/** If `true`, the field is listed in the OpenAPI `required` array
|
|
48
|
+
/** If `true`, the field is listed in the OpenAPI `required` array. */
|
|
17
49
|
required?: boolean;
|
|
18
50
|
/** Explicit type override. If absent, inferred from the data. */
|
|
19
51
|
type?: "string" | "integer" | "number" | "boolean" | "object" | "array";
|
|
20
|
-
/** OpenAPI `format` hint (e.g. `email`, `date`, `uuid`, `uri`, `date-time`). */
|
|
52
|
+
/** OpenAPI `format` hint (e.g. `email`, `date`, `uuid`, `uri`, `date-time`, `binary`). */
|
|
21
53
|
format?: string;
|
|
22
|
-
/** Restricts the field to a fixed set of values. */
|
|
54
|
+
/** Restricts the field to a fixed set of allowed values. */
|
|
23
55
|
enum?: unknown[];
|
|
24
56
|
/** Human-readable description included in the OpenAPI spec. */
|
|
25
57
|
description?: string;
|
|
26
58
|
/** Default value included in the OpenAPI spec. */
|
|
27
59
|
default?: unknown;
|
|
60
|
+
/** Example value included in the OpenAPI spec. */
|
|
61
|
+
example?: unknown;
|
|
62
|
+
/** If `true`, the field may be `null` in addition to its declared type. */
|
|
63
|
+
nullable?: boolean;
|
|
64
|
+
/** Minimum string length (inclusive). Applies when `type` is `string`. */
|
|
65
|
+
minLength?: number;
|
|
66
|
+
/** Maximum string length (inclusive). Applies when `type` is `string`. */
|
|
67
|
+
maxLength?: number;
|
|
68
|
+
/** Regex pattern the string value must match. Applies when `type` is `string`. */
|
|
69
|
+
pattern?: string;
|
|
70
|
+
/** Minimum numeric value (inclusive). Applies when `type` is `number` or `integer`. */
|
|
71
|
+
minimum?: number;
|
|
72
|
+
/** Maximum numeric value (inclusive). Applies when `type` is `number` or `integer`. */
|
|
73
|
+
maximum?: number;
|
|
74
|
+
/** Exclusive lower bound. Applies when `type` is `number` or `integer`. */
|
|
75
|
+
exclusiveMinimum?: number;
|
|
76
|
+
/** Exclusive upper bound. Applies when `type` is `number` or `integer`. */
|
|
77
|
+
exclusiveMaximum?: number;
|
|
78
|
+
/** Value must be a multiple of this number. */
|
|
79
|
+
multipleOf?: number;
|
|
80
|
+
/** Schema descriptor for the items of an array field. */
|
|
81
|
+
items?: Pick<FieldDef, "type" | "format" | "enum">;
|
|
82
|
+
/** Minimum number of items in the array. */
|
|
83
|
+
minItems?: number;
|
|
84
|
+
/** Maximum number of items in the array. */
|
|
85
|
+
maxItems?: number;
|
|
86
|
+
/** If `true`, all array items must be unique. */
|
|
87
|
+
uniqueItems?: boolean;
|
|
88
|
+
/** If `true`, marks the field as deprecated in the OpenAPI spec. */
|
|
89
|
+
deprecated?: boolean;
|
|
90
|
+
/** If `true`, the field is only returned in responses (excluded from request bodies). */
|
|
91
|
+
readOnly?: boolean;
|
|
92
|
+
/** If `true`, the field is only accepted in request bodies (excluded from responses). */
|
|
93
|
+
writeOnly?: boolean;
|
|
28
94
|
};
|
|
29
95
|
/**
|
|
30
96
|
* Field-level schema declarations for one or more collections, parsed from `_schema` in the YAML.
|
package/dist/index.d.ts
CHANGED
|
@@ -10,21 +10,87 @@ type Resource = Record<string, unknown>;
|
|
|
10
10
|
* All properties are optional — omit what you don't need. Fields not mentioned
|
|
11
11
|
* in `_schema` are inferred from the collection data and treated as optional.
|
|
12
12
|
*
|
|
13
|
+
* In the YAML file every property uses the `_` prefix convention:
|
|
14
|
+
* `_type`, `_required`, `_format`, `_enum`, `_description`, `_default`, etc.
|
|
15
|
+
*
|
|
13
16
|
* Shorthand: `fieldName: required` normalises to `{ required: true }`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```yaml
|
|
20
|
+
* _schema:
|
|
21
|
+
* users:
|
|
22
|
+
* name:
|
|
23
|
+
* _type: string
|
|
24
|
+
* _required: true
|
|
25
|
+
* _minLength: 2
|
|
26
|
+
* _maxLength: 100
|
|
27
|
+
* email:
|
|
28
|
+
* _type: string
|
|
29
|
+
* _required: true
|
|
30
|
+
* _format: email
|
|
31
|
+
* age:
|
|
32
|
+
* _type: integer
|
|
33
|
+
* _minimum: 0
|
|
34
|
+
* _maximum: 120
|
|
35
|
+
* _nullable: true
|
|
36
|
+
* role:
|
|
37
|
+
* _type: string
|
|
38
|
+
* _enum: [admin, editor, viewer]
|
|
39
|
+
* _default: viewer
|
|
40
|
+
* tags:
|
|
41
|
+
* _type: array
|
|
42
|
+
* _items:
|
|
43
|
+
* _type: string
|
|
44
|
+
* _uniqueItems: true
|
|
45
|
+
* ```
|
|
14
46
|
*/
|
|
15
47
|
type FieldDef = {
|
|
16
|
-
/** If `true`, the field is listed in the OpenAPI `required` array
|
|
48
|
+
/** If `true`, the field is listed in the OpenAPI `required` array. */
|
|
17
49
|
required?: boolean;
|
|
18
50
|
/** Explicit type override. If absent, inferred from the data. */
|
|
19
51
|
type?: "string" | "integer" | "number" | "boolean" | "object" | "array";
|
|
20
|
-
/** OpenAPI `format` hint (e.g. `email`, `date`, `uuid`, `uri`, `date-time`). */
|
|
52
|
+
/** OpenAPI `format` hint (e.g. `email`, `date`, `uuid`, `uri`, `date-time`, `binary`). */
|
|
21
53
|
format?: string;
|
|
22
|
-
/** Restricts the field to a fixed set of values. */
|
|
54
|
+
/** Restricts the field to a fixed set of allowed values. */
|
|
23
55
|
enum?: unknown[];
|
|
24
56
|
/** Human-readable description included in the OpenAPI spec. */
|
|
25
57
|
description?: string;
|
|
26
58
|
/** Default value included in the OpenAPI spec. */
|
|
27
59
|
default?: unknown;
|
|
60
|
+
/** Example value included in the OpenAPI spec. */
|
|
61
|
+
example?: unknown;
|
|
62
|
+
/** If `true`, the field may be `null` in addition to its declared type. */
|
|
63
|
+
nullable?: boolean;
|
|
64
|
+
/** Minimum string length (inclusive). Applies when `type` is `string`. */
|
|
65
|
+
minLength?: number;
|
|
66
|
+
/** Maximum string length (inclusive). Applies when `type` is `string`. */
|
|
67
|
+
maxLength?: number;
|
|
68
|
+
/** Regex pattern the string value must match. Applies when `type` is `string`. */
|
|
69
|
+
pattern?: string;
|
|
70
|
+
/** Minimum numeric value (inclusive). Applies when `type` is `number` or `integer`. */
|
|
71
|
+
minimum?: number;
|
|
72
|
+
/** Maximum numeric value (inclusive). Applies when `type` is `number` or `integer`. */
|
|
73
|
+
maximum?: number;
|
|
74
|
+
/** Exclusive lower bound. Applies when `type` is `number` or `integer`. */
|
|
75
|
+
exclusiveMinimum?: number;
|
|
76
|
+
/** Exclusive upper bound. Applies when `type` is `number` or `integer`. */
|
|
77
|
+
exclusiveMaximum?: number;
|
|
78
|
+
/** Value must be a multiple of this number. */
|
|
79
|
+
multipleOf?: number;
|
|
80
|
+
/** Schema descriptor for the items of an array field. */
|
|
81
|
+
items?: Pick<FieldDef, "type" | "format" | "enum">;
|
|
82
|
+
/** Minimum number of items in the array. */
|
|
83
|
+
minItems?: number;
|
|
84
|
+
/** Maximum number of items in the array. */
|
|
85
|
+
maxItems?: number;
|
|
86
|
+
/** If `true`, all array items must be unique. */
|
|
87
|
+
uniqueItems?: boolean;
|
|
88
|
+
/** If `true`, marks the field as deprecated in the OpenAPI spec. */
|
|
89
|
+
deprecated?: boolean;
|
|
90
|
+
/** If `true`, the field is only returned in responses (excluded from request bodies). */
|
|
91
|
+
readOnly?: boolean;
|
|
92
|
+
/** If `true`, the field is only accepted in request bodies (excluded from responses). */
|
|
93
|
+
writeOnly?: boolean;
|
|
28
94
|
};
|
|
29
95
|
/**
|
|
30
96
|
* Field-level schema declarations for one or more collections, parsed from `_schema` in the YAML.
|
package/dist/index.js
CHANGED
|
@@ -174,18 +174,48 @@ function normaliseFieldDef(value) {
|
|
|
174
174
|
const v = value;
|
|
175
175
|
const def = {};
|
|
176
176
|
if (v["_required"] === true || v["_required"] === false) def.required = v["_required"];
|
|
177
|
-
if (typeof v["_type"] === "string" &&
|
|
177
|
+
if (typeof v["_type"] === "string" && VALID_TYPES.has(v["_type"]))
|
|
178
178
|
def.type = v["_type"];
|
|
179
179
|
if (typeof v["_format"] === "string") def.format = v["_format"];
|
|
180
180
|
if (Array.isArray(v["_enum"])) def.enum = v["_enum"];
|
|
181
181
|
if (typeof v["_description"] === "string") def.description = v["_description"];
|
|
182
182
|
if (v["_default"] !== void 0) def.default = v["_default"];
|
|
183
|
+
if (v["_example"] !== void 0) def.example = v["_example"];
|
|
184
|
+
if (v["_nullable"] === true || v["_nullable"] === false) def.nullable = v["_nullable"];
|
|
185
|
+
if (typeof v["_minLength"] === "number") def.minLength = v["_minLength"];
|
|
186
|
+
if (typeof v["_maxLength"] === "number") def.maxLength = v["_maxLength"];
|
|
187
|
+
if (typeof v["_pattern"] === "string") def.pattern = v["_pattern"];
|
|
188
|
+
if (typeof v["_minimum"] === "number") def.minimum = v["_minimum"];
|
|
189
|
+
if (typeof v["_maximum"] === "number") def.maximum = v["_maximum"];
|
|
190
|
+
if (typeof v["_exclusiveMinimum"] === "number") def.exclusiveMinimum = v["_exclusiveMinimum"];
|
|
191
|
+
if (typeof v["_exclusiveMaximum"] === "number") def.exclusiveMaximum = v["_exclusiveMaximum"];
|
|
192
|
+
if (typeof v["_multipleOf"] === "number") def.multipleOf = v["_multipleOf"];
|
|
193
|
+
if (typeof v["_minItems"] === "number") def.minItems = v["_minItems"];
|
|
194
|
+
if (typeof v["_maxItems"] === "number") def.maxItems = v["_maxItems"];
|
|
195
|
+
if (v["_uniqueItems"] === true || v["_uniqueItems"] === false)
|
|
196
|
+
def.uniqueItems = v["_uniqueItems"];
|
|
197
|
+
if (v["_items"] && typeof v["_items"] === "object" && !Array.isArray(v["_items"])) {
|
|
198
|
+
const items = v["_items"];
|
|
199
|
+
const itemsDef = {};
|
|
200
|
+
if (typeof items["_type"] === "string" && VALID_TYPES.has(items["_type"]))
|
|
201
|
+
itemsDef.type = items["_type"];
|
|
202
|
+
if (typeof items["_format"] === "string") itemsDef.format = items["_format"];
|
|
203
|
+
if (Array.isArray(items["_enum"])) itemsDef.enum = items["_enum"];
|
|
204
|
+
def.items = itemsDef;
|
|
205
|
+
}
|
|
206
|
+
if (v["_deprecated"] === true || v["_deprecated"] === false)
|
|
207
|
+
def.deprecated = v["_deprecated"];
|
|
208
|
+
if (v["_readOnly"] === true || v["_readOnly"] === false) def.readOnly = v["_readOnly"];
|
|
209
|
+
if (v["_writeOnly"] === true || v["_writeOnly"] === false)
|
|
210
|
+
def.writeOnly = v["_writeOnly"];
|
|
183
211
|
return def;
|
|
184
212
|
}
|
|
213
|
+
var VALID_TYPES;
|
|
185
214
|
var init_parseSchema = __esm({
|
|
186
215
|
"src/storage/parseSchema.ts"() {
|
|
187
216
|
"use strict";
|
|
188
217
|
init_cjs_shims();
|
|
218
|
+
VALID_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean", "object", "array"]);
|
|
189
219
|
}
|
|
190
220
|
});
|
|
191
221
|
|
|
@@ -256,6 +286,30 @@ var init_parseRoutes = __esm({
|
|
|
256
286
|
}
|
|
257
287
|
});
|
|
258
288
|
|
|
289
|
+
// src/storage/parseData.ts
|
|
290
|
+
function parseData(raw) {
|
|
291
|
+
const block = raw["_data"];
|
|
292
|
+
const fromBlock = block && typeof block === "object" && !Array.isArray(block) ? Object.fromEntries(
|
|
293
|
+
Object.entries(block).filter(([, v]) => Array.isArray(v))
|
|
294
|
+
) : {};
|
|
295
|
+
const fromRoot = Object.fromEntries(
|
|
296
|
+
Object.entries(raw).filter(([key, val]) => !RESERVED.has(key) && Array.isArray(val))
|
|
297
|
+
);
|
|
298
|
+
return { ...fromBlock, ...fromRoot };
|
|
299
|
+
}
|
|
300
|
+
function hasDataBlock(raw) {
|
|
301
|
+
const d = raw["_data"];
|
|
302
|
+
return d !== null && d !== void 0 && typeof d === "object" && !Array.isArray(d);
|
|
303
|
+
}
|
|
304
|
+
var RESERVED;
|
|
305
|
+
var init_parseData = __esm({
|
|
306
|
+
"src/storage/parseData.ts"() {
|
|
307
|
+
"use strict";
|
|
308
|
+
init_cjs_shims();
|
|
309
|
+
RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema", "_data"]);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
259
313
|
// src/storage/yrestStorage.ts
|
|
260
314
|
var yrestStorage_exports = {};
|
|
261
315
|
__export(yrestStorage_exports, {
|
|
@@ -318,13 +372,11 @@ function serializeScenario(s) {
|
|
|
318
372
|
function createYrestStorage(filePath) {
|
|
319
373
|
const absPath = (0, import_node_path2.resolve)(filePath);
|
|
320
374
|
const raw = (0, import_yaml3.parse)((0, import_node_fs3.readFileSync)(absPath, "utf8")) ?? {};
|
|
321
|
-
const RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
322
375
|
const relations = parseRelations(raw["_rel"]);
|
|
323
376
|
const routes = parseRoutes(raw["_routes"]);
|
|
324
377
|
const schema = parseSchema(raw["_schema"]);
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
);
|
|
378
|
+
const dataBlock = hasDataBlock(raw);
|
|
379
|
+
const data = parseData(raw);
|
|
328
380
|
let snapshot = {
|
|
329
381
|
data: deepCopyData(data),
|
|
330
382
|
relations: { ...relations },
|
|
@@ -353,7 +405,11 @@ function createYrestStorage(filePath) {
|
|
|
353
405
|
const payload = {};
|
|
354
406
|
if (Object.keys(relations).length > 0) payload._rel = serializeRelations(relations);
|
|
355
407
|
if (routes.length > 0) payload._routes = serializeRoutes(routes);
|
|
356
|
-
|
|
408
|
+
if (dataBlock) {
|
|
409
|
+
payload._data = data;
|
|
410
|
+
} else {
|
|
411
|
+
Object.assign(payload, data);
|
|
412
|
+
}
|
|
357
413
|
const tmp = (0, import_node_path2.resolve)((0, import_node_path2.dirname)(absPath), `.yrest-${(0, import_node_crypto2.randomUUID)()}.tmp`);
|
|
358
414
|
(0, import_node_fs3.writeFileSync)(tmp, (0, import_yaml3.stringify)(payload), "utf8");
|
|
359
415
|
(0, import_node_fs3.renameSync)(tmp, absPath);
|
|
@@ -361,9 +417,7 @@ function createYrestStorage(filePath) {
|
|
|
361
417
|
reload() {
|
|
362
418
|
const fresh = (0, import_yaml3.parse)((0, import_node_fs3.readFileSync)(absPath, "utf8")) ?? {};
|
|
363
419
|
const freshRelations = parseRelations(fresh["_rel"]);
|
|
364
|
-
const freshData =
|
|
365
|
-
Object.entries(fresh).filter(([key]) => !RESERVED.has(key))
|
|
366
|
-
);
|
|
420
|
+
const freshData = parseData(fresh);
|
|
367
421
|
for (const key of Object.keys(data)) delete data[key];
|
|
368
422
|
Object.assign(data, freshData);
|
|
369
423
|
for (const key of Object.keys(relations)) delete relations[key];
|
|
@@ -401,6 +455,7 @@ var init_yrestStorage = __esm({
|
|
|
401
455
|
init_parseRelations();
|
|
402
456
|
init_parseRoutes();
|
|
403
457
|
init_parseSchema();
|
|
458
|
+
init_parseData();
|
|
404
459
|
init_deepCopy();
|
|
405
460
|
}
|
|
406
461
|
});
|
|
@@ -2059,15 +2114,19 @@ function resolveRequestPath(dotPath, req) {
|
|
|
2059
2114
|
const [root, ...rest] = dotPath.split(".");
|
|
2060
2115
|
let value;
|
|
2061
2116
|
switch (root) {
|
|
2117
|
+
case "_body":
|
|
2062
2118
|
case "body":
|
|
2063
2119
|
value = req.body;
|
|
2064
2120
|
break;
|
|
2121
|
+
case "_params":
|
|
2065
2122
|
case "params":
|
|
2066
2123
|
value = req.params;
|
|
2067
2124
|
break;
|
|
2125
|
+
case "_query":
|
|
2068
2126
|
case "query":
|
|
2069
2127
|
value = req.query;
|
|
2070
2128
|
break;
|
|
2129
|
+
case "_headers":
|
|
2071
2130
|
case "headers":
|
|
2072
2131
|
value = req.headers;
|
|
2073
2132
|
break;
|
|
@@ -2368,6 +2427,23 @@ function buildCollectionSchema(items, fieldDefs = {}) {
|
|
|
2368
2427
|
if (def?.description) prop.description = def.description;
|
|
2369
2428
|
if (def?.enum) prop.enum = def.enum;
|
|
2370
2429
|
if (def?.default !== void 0) prop.default = def.default;
|
|
2430
|
+
if (def?.example !== void 0) prop.example = def.example;
|
|
2431
|
+
if (def?.nullable) prop.nullable = def.nullable;
|
|
2432
|
+
if (def?.minLength !== void 0) prop.minLength = def.minLength;
|
|
2433
|
+
if (def?.maxLength !== void 0) prop.maxLength = def.maxLength;
|
|
2434
|
+
if (def?.pattern) prop.pattern = def.pattern;
|
|
2435
|
+
if (def?.minimum !== void 0) prop.minimum = def.minimum;
|
|
2436
|
+
if (def?.maximum !== void 0) prop.maximum = def.maximum;
|
|
2437
|
+
if (def?.exclusiveMinimum !== void 0) prop.exclusiveMinimum = def.exclusiveMinimum;
|
|
2438
|
+
if (def?.exclusiveMaximum !== void 0) prop.exclusiveMaximum = def.exclusiveMaximum;
|
|
2439
|
+
if (def?.multipleOf !== void 0) prop.multipleOf = def.multipleOf;
|
|
2440
|
+
if (def?.items) prop.items = def.items;
|
|
2441
|
+
if (def?.minItems !== void 0) prop.minItems = def.minItems;
|
|
2442
|
+
if (def?.maxItems !== void 0) prop.maxItems = def.maxItems;
|
|
2443
|
+
if (def?.uniqueItems !== void 0) prop.uniqueItems = def.uniqueItems;
|
|
2444
|
+
if (def?.deprecated) prop.deprecated = def.deprecated;
|
|
2445
|
+
if (def?.readOnly) prop.readOnly = def.readOnly;
|
|
2446
|
+
if (def?.writeOnly) prop.writeOnly = def.writeOnly;
|
|
2371
2447
|
properties[field] = prop;
|
|
2372
2448
|
if (def?.required === true) required.push(field);
|
|
2373
2449
|
}
|
|
@@ -2871,12 +2947,12 @@ function buildOptions(opts) {
|
|
|
2871
2947
|
}
|
|
2872
2948
|
function createInMemoryStorage(data) {
|
|
2873
2949
|
const raw = data;
|
|
2874
|
-
const
|
|
2950
|
+
const RESERVED2 = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
2875
2951
|
const relations = parseRelations(raw["_rel"]);
|
|
2876
2952
|
const routes = Array.isArray(raw["_routes"]) ? raw["_routes"] : [];
|
|
2877
2953
|
const schema = parseSchema(raw["_schema"]);
|
|
2878
2954
|
const collections = Object.fromEntries(
|
|
2879
|
-
Object.entries(raw).filter(([k]) => !
|
|
2955
|
+
Object.entries(raw).filter(([k]) => !RESERVED2.has(k))
|
|
2880
2956
|
);
|
|
2881
2957
|
let snapshot = {
|
|
2882
2958
|
data: deepCopyData(collections),
|
package/dist/index.mjs
CHANGED
|
@@ -151,18 +151,48 @@ function normaliseFieldDef(value) {
|
|
|
151
151
|
const v = value;
|
|
152
152
|
const def = {};
|
|
153
153
|
if (v["_required"] === true || v["_required"] === false) def.required = v["_required"];
|
|
154
|
-
if (typeof v["_type"] === "string" &&
|
|
154
|
+
if (typeof v["_type"] === "string" && VALID_TYPES.has(v["_type"]))
|
|
155
155
|
def.type = v["_type"];
|
|
156
156
|
if (typeof v["_format"] === "string") def.format = v["_format"];
|
|
157
157
|
if (Array.isArray(v["_enum"])) def.enum = v["_enum"];
|
|
158
158
|
if (typeof v["_description"] === "string") def.description = v["_description"];
|
|
159
159
|
if (v["_default"] !== void 0) def.default = v["_default"];
|
|
160
|
+
if (v["_example"] !== void 0) def.example = v["_example"];
|
|
161
|
+
if (v["_nullable"] === true || v["_nullable"] === false) def.nullable = v["_nullable"];
|
|
162
|
+
if (typeof v["_minLength"] === "number") def.minLength = v["_minLength"];
|
|
163
|
+
if (typeof v["_maxLength"] === "number") def.maxLength = v["_maxLength"];
|
|
164
|
+
if (typeof v["_pattern"] === "string") def.pattern = v["_pattern"];
|
|
165
|
+
if (typeof v["_minimum"] === "number") def.minimum = v["_minimum"];
|
|
166
|
+
if (typeof v["_maximum"] === "number") def.maximum = v["_maximum"];
|
|
167
|
+
if (typeof v["_exclusiveMinimum"] === "number") def.exclusiveMinimum = v["_exclusiveMinimum"];
|
|
168
|
+
if (typeof v["_exclusiveMaximum"] === "number") def.exclusiveMaximum = v["_exclusiveMaximum"];
|
|
169
|
+
if (typeof v["_multipleOf"] === "number") def.multipleOf = v["_multipleOf"];
|
|
170
|
+
if (typeof v["_minItems"] === "number") def.minItems = v["_minItems"];
|
|
171
|
+
if (typeof v["_maxItems"] === "number") def.maxItems = v["_maxItems"];
|
|
172
|
+
if (v["_uniqueItems"] === true || v["_uniqueItems"] === false)
|
|
173
|
+
def.uniqueItems = v["_uniqueItems"];
|
|
174
|
+
if (v["_items"] && typeof v["_items"] === "object" && !Array.isArray(v["_items"])) {
|
|
175
|
+
const items = v["_items"];
|
|
176
|
+
const itemsDef = {};
|
|
177
|
+
if (typeof items["_type"] === "string" && VALID_TYPES.has(items["_type"]))
|
|
178
|
+
itemsDef.type = items["_type"];
|
|
179
|
+
if (typeof items["_format"] === "string") itemsDef.format = items["_format"];
|
|
180
|
+
if (Array.isArray(items["_enum"])) itemsDef.enum = items["_enum"];
|
|
181
|
+
def.items = itemsDef;
|
|
182
|
+
}
|
|
183
|
+
if (v["_deprecated"] === true || v["_deprecated"] === false)
|
|
184
|
+
def.deprecated = v["_deprecated"];
|
|
185
|
+
if (v["_readOnly"] === true || v["_readOnly"] === false) def.readOnly = v["_readOnly"];
|
|
186
|
+
if (v["_writeOnly"] === true || v["_writeOnly"] === false)
|
|
187
|
+
def.writeOnly = v["_writeOnly"];
|
|
160
188
|
return def;
|
|
161
189
|
}
|
|
190
|
+
var VALID_TYPES;
|
|
162
191
|
var init_parseSchema = __esm({
|
|
163
192
|
"src/storage/parseSchema.ts"() {
|
|
164
193
|
"use strict";
|
|
165
194
|
init_esm_shims();
|
|
195
|
+
VALID_TYPES = /* @__PURE__ */ new Set(["string", "integer", "number", "boolean", "object", "array"]);
|
|
166
196
|
}
|
|
167
197
|
});
|
|
168
198
|
|
|
@@ -233,6 +263,30 @@ var init_parseRoutes = __esm({
|
|
|
233
263
|
}
|
|
234
264
|
});
|
|
235
265
|
|
|
266
|
+
// src/storage/parseData.ts
|
|
267
|
+
function parseData(raw) {
|
|
268
|
+
const block = raw["_data"];
|
|
269
|
+
const fromBlock = block && typeof block === "object" && !Array.isArray(block) ? Object.fromEntries(
|
|
270
|
+
Object.entries(block).filter(([, v]) => Array.isArray(v))
|
|
271
|
+
) : {};
|
|
272
|
+
const fromRoot = Object.fromEntries(
|
|
273
|
+
Object.entries(raw).filter(([key, val]) => !RESERVED.has(key) && Array.isArray(val))
|
|
274
|
+
);
|
|
275
|
+
return { ...fromBlock, ...fromRoot };
|
|
276
|
+
}
|
|
277
|
+
function hasDataBlock(raw) {
|
|
278
|
+
const d = raw["_data"];
|
|
279
|
+
return d !== null && d !== void 0 && typeof d === "object" && !Array.isArray(d);
|
|
280
|
+
}
|
|
281
|
+
var RESERVED;
|
|
282
|
+
var init_parseData = __esm({
|
|
283
|
+
"src/storage/parseData.ts"() {
|
|
284
|
+
"use strict";
|
|
285
|
+
init_esm_shims();
|
|
286
|
+
RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema", "_data"]);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
236
290
|
// src/storage/yrestStorage.ts
|
|
237
291
|
var yrestStorage_exports = {};
|
|
238
292
|
__export(yrestStorage_exports, {
|
|
@@ -299,13 +353,11 @@ function serializeScenario(s) {
|
|
|
299
353
|
function createYrestStorage(filePath) {
|
|
300
354
|
const absPath = resolve(filePath);
|
|
301
355
|
const raw = parse2(readFileSync2(absPath, "utf8")) ?? {};
|
|
302
|
-
const RESERVED = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
303
356
|
const relations = parseRelations(raw["_rel"]);
|
|
304
357
|
const routes = parseRoutes(raw["_routes"]);
|
|
305
358
|
const schema = parseSchema(raw["_schema"]);
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
);
|
|
359
|
+
const dataBlock = hasDataBlock(raw);
|
|
360
|
+
const data = parseData(raw);
|
|
309
361
|
let snapshot = {
|
|
310
362
|
data: deepCopyData(data),
|
|
311
363
|
relations: { ...relations },
|
|
@@ -334,7 +386,11 @@ function createYrestStorage(filePath) {
|
|
|
334
386
|
const payload = {};
|
|
335
387
|
if (Object.keys(relations).length > 0) payload._rel = serializeRelations(relations);
|
|
336
388
|
if (routes.length > 0) payload._routes = serializeRoutes(routes);
|
|
337
|
-
|
|
389
|
+
if (dataBlock) {
|
|
390
|
+
payload._data = data;
|
|
391
|
+
} else {
|
|
392
|
+
Object.assign(payload, data);
|
|
393
|
+
}
|
|
338
394
|
const tmp = resolve(dirname2(absPath), `.yrest-${randomUUID2()}.tmp`);
|
|
339
395
|
writeFileSync(tmp, stringify2(payload), "utf8");
|
|
340
396
|
renameSync(tmp, absPath);
|
|
@@ -342,9 +398,7 @@ function createYrestStorage(filePath) {
|
|
|
342
398
|
reload() {
|
|
343
399
|
const fresh = parse2(readFileSync2(absPath, "utf8")) ?? {};
|
|
344
400
|
const freshRelations = parseRelations(fresh["_rel"]);
|
|
345
|
-
const freshData =
|
|
346
|
-
Object.entries(fresh).filter(([key]) => !RESERVED.has(key))
|
|
347
|
-
);
|
|
401
|
+
const freshData = parseData(fresh);
|
|
348
402
|
for (const key of Object.keys(data)) delete data[key];
|
|
349
403
|
Object.assign(data, freshData);
|
|
350
404
|
for (const key of Object.keys(relations)) delete relations[key];
|
|
@@ -377,6 +431,7 @@ var init_yrestStorage = __esm({
|
|
|
377
431
|
init_parseRelations();
|
|
378
432
|
init_parseRoutes();
|
|
379
433
|
init_parseSchema();
|
|
434
|
+
init_parseData();
|
|
380
435
|
init_deepCopy();
|
|
381
436
|
}
|
|
382
437
|
});
|
|
@@ -2025,15 +2080,19 @@ function resolveRequestPath(dotPath, req) {
|
|
|
2025
2080
|
const [root, ...rest] = dotPath.split(".");
|
|
2026
2081
|
let value;
|
|
2027
2082
|
switch (root) {
|
|
2083
|
+
case "_body":
|
|
2028
2084
|
case "body":
|
|
2029
2085
|
value = req.body;
|
|
2030
2086
|
break;
|
|
2087
|
+
case "_params":
|
|
2031
2088
|
case "params":
|
|
2032
2089
|
value = req.params;
|
|
2033
2090
|
break;
|
|
2091
|
+
case "_query":
|
|
2034
2092
|
case "query":
|
|
2035
2093
|
value = req.query;
|
|
2036
2094
|
break;
|
|
2095
|
+
case "_headers":
|
|
2037
2096
|
case "headers":
|
|
2038
2097
|
value = req.headers;
|
|
2039
2098
|
break;
|
|
@@ -2334,6 +2393,23 @@ function buildCollectionSchema(items, fieldDefs = {}) {
|
|
|
2334
2393
|
if (def?.description) prop.description = def.description;
|
|
2335
2394
|
if (def?.enum) prop.enum = def.enum;
|
|
2336
2395
|
if (def?.default !== void 0) prop.default = def.default;
|
|
2396
|
+
if (def?.example !== void 0) prop.example = def.example;
|
|
2397
|
+
if (def?.nullable) prop.nullable = def.nullable;
|
|
2398
|
+
if (def?.minLength !== void 0) prop.minLength = def.minLength;
|
|
2399
|
+
if (def?.maxLength !== void 0) prop.maxLength = def.maxLength;
|
|
2400
|
+
if (def?.pattern) prop.pattern = def.pattern;
|
|
2401
|
+
if (def?.minimum !== void 0) prop.minimum = def.minimum;
|
|
2402
|
+
if (def?.maximum !== void 0) prop.maximum = def.maximum;
|
|
2403
|
+
if (def?.exclusiveMinimum !== void 0) prop.exclusiveMinimum = def.exclusiveMinimum;
|
|
2404
|
+
if (def?.exclusiveMaximum !== void 0) prop.exclusiveMaximum = def.exclusiveMaximum;
|
|
2405
|
+
if (def?.multipleOf !== void 0) prop.multipleOf = def.multipleOf;
|
|
2406
|
+
if (def?.items) prop.items = def.items;
|
|
2407
|
+
if (def?.minItems !== void 0) prop.minItems = def.minItems;
|
|
2408
|
+
if (def?.maxItems !== void 0) prop.maxItems = def.maxItems;
|
|
2409
|
+
if (def?.uniqueItems !== void 0) prop.uniqueItems = def.uniqueItems;
|
|
2410
|
+
if (def?.deprecated) prop.deprecated = def.deprecated;
|
|
2411
|
+
if (def?.readOnly) prop.readOnly = def.readOnly;
|
|
2412
|
+
if (def?.writeOnly) prop.writeOnly = def.writeOnly;
|
|
2337
2413
|
properties[field] = prop;
|
|
2338
2414
|
if (def?.required === true) required.push(field);
|
|
2339
2415
|
}
|
|
@@ -2837,12 +2913,12 @@ function buildOptions(opts) {
|
|
|
2837
2913
|
}
|
|
2838
2914
|
function createInMemoryStorage(data) {
|
|
2839
2915
|
const raw = data;
|
|
2840
|
-
const
|
|
2916
|
+
const RESERVED2 = /* @__PURE__ */ new Set(["_rel", "_routes", "_schema"]);
|
|
2841
2917
|
const relations = parseRelations(raw["_rel"]);
|
|
2842
2918
|
const routes = Array.isArray(raw["_routes"]) ? raw["_routes"] : [];
|
|
2843
2919
|
const schema = parseSchema(raw["_schema"]);
|
|
2844
2920
|
const collections = Object.fromEntries(
|
|
2845
|
-
Object.entries(raw).filter(([k]) => !
|
|
2921
|
+
Object.entries(raw).filter(([k]) => !RESERVED2.has(k))
|
|
2846
2922
|
);
|
|
2847
2923
|
let snapshot = {
|
|
2848
2924
|
data: deepCopyData(collections),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yrest/cli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "YAML-powered json-server alternative. Zero-config REST API mock server with full CRUD, relations, filters and snapshots from a db.yml file.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"yrest",
|
|
@@ -60,12 +60,16 @@
|
|
|
60
60
|
"dev": "tsup --watch",
|
|
61
61
|
"test": "vitest",
|
|
62
62
|
"test:run": "vitest run",
|
|
63
|
+
"test:unit": "vitest run tests/config tests/cli tests/storage tests/utils",
|
|
64
|
+
"test:integration": "vitest run tests/routes tests/openapi tests/security tests/api",
|
|
65
|
+
"test:e2e": "vitest run tests/e2e",
|
|
66
|
+
"test:preflight": "vitest run",
|
|
63
67
|
"typecheck": "tsc --noEmit",
|
|
64
68
|
"lint": "eslint src tests",
|
|
65
69
|
"lint:fix": "eslint src tests --fix",
|
|
66
70
|
"format": "prettier --write .",
|
|
67
71
|
"format:check": "prettier --check .",
|
|
68
|
-
"prepublishOnly": "npm run
|
|
72
|
+
"prepublishOnly": "npm run build && npm run test:preflight",
|
|
69
73
|
"version": "node scripts/update-version-badge.mjs && git add README.md"
|
|
70
74
|
},
|
|
71
75
|
"dependencies": {
|