@elek-io/core 0.16.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -6
- package/dist/astro/index.astro.d.mts +2 -1
- package/dist/astro/index.astro.mjs +1492 -298
- package/dist/astro/index.astro.mjs.map +1 -1
- package/dist/browser/index.browser.d.ts +1488 -1566
- package/dist/browser/index.browser.js +1 -1
- package/dist/browser/index.browser.js.map +1 -1
- package/dist/cli/index.cli.mjs +1470 -284
- package/dist/node/index.node.d.mts +1520 -1598
- package/dist/node/index.node.mjs +1470 -287
- package/dist/node/index.node.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node/index.node.mjs
CHANGED
|
@@ -19,11 +19,12 @@ import PQueue from "p-queue";
|
|
|
19
19
|
import { createLogger, format, transports } from "winston";
|
|
20
20
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
21
21
|
import Semver from "semver";
|
|
22
|
+
import { isDeepStrictEqual } from "node:util";
|
|
22
23
|
|
|
23
24
|
//#region package.json
|
|
24
25
|
var package_default = {
|
|
25
26
|
name: "@elek-io/core",
|
|
26
|
-
version: "0.
|
|
27
|
+
version: "0.17.0",
|
|
27
28
|
description: "Handles core functionality of elek.io Projects like file IO and version control.",
|
|
28
29
|
homepage: "https://elek.io",
|
|
29
30
|
repository: "https://github.com/elek-io/core",
|
|
@@ -218,11 +219,7 @@ const supportedLanguageSchema = z.enum([
|
|
|
218
219
|
"sv",
|
|
219
220
|
"zh"
|
|
220
221
|
]);
|
|
221
|
-
const supportedIconSchema = z.enum([
|
|
222
|
-
"home",
|
|
223
|
-
"plus",
|
|
224
|
-
"foobar"
|
|
225
|
-
]);
|
|
222
|
+
const supportedIconSchema = z.enum(["home", "plus"]);
|
|
226
223
|
const objectTypeSchema = z.enum([
|
|
227
224
|
"project",
|
|
228
225
|
"asset",
|
|
@@ -237,7 +234,7 @@ const logLevelSchema = z.enum([
|
|
|
237
234
|
"info",
|
|
238
235
|
"debug"
|
|
239
236
|
]);
|
|
240
|
-
const versionSchema = z.string();
|
|
237
|
+
const versionSchema = z.string().refine((version) => /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/.test(version), "String must follow the Semantic Versioning format (https://semver.org/)");
|
|
241
238
|
const uuidSchema = z.uuid();
|
|
242
239
|
/**
|
|
243
240
|
* A record that can be used to translate a string value into all supported languages
|
|
@@ -254,6 +251,41 @@ const translatableBooleanSchema = z.partialRecord(supportedLanguageSchema, z.boo
|
|
|
254
251
|
function translatableArrayOf(schema) {
|
|
255
252
|
return z.partialRecord(supportedLanguageSchema, z.array(schema));
|
|
256
253
|
}
|
|
254
|
+
const reservedSlugs = new Set([
|
|
255
|
+
"index",
|
|
256
|
+
"new",
|
|
257
|
+
"create",
|
|
258
|
+
"update",
|
|
259
|
+
"delete",
|
|
260
|
+
"edit",
|
|
261
|
+
"list",
|
|
262
|
+
"count",
|
|
263
|
+
"api",
|
|
264
|
+
"admin",
|
|
265
|
+
"collection",
|
|
266
|
+
"collections",
|
|
267
|
+
"entry",
|
|
268
|
+
"entries",
|
|
269
|
+
"asset",
|
|
270
|
+
"assets",
|
|
271
|
+
"project",
|
|
272
|
+
"projects",
|
|
273
|
+
"null",
|
|
274
|
+
"undefined",
|
|
275
|
+
"true",
|
|
276
|
+
"false",
|
|
277
|
+
"constructor",
|
|
278
|
+
"__proto__",
|
|
279
|
+
"prototype",
|
|
280
|
+
"toString",
|
|
281
|
+
"valueOf",
|
|
282
|
+
"login",
|
|
283
|
+
"logout",
|
|
284
|
+
"auth",
|
|
285
|
+
"settings",
|
|
286
|
+
"config"
|
|
287
|
+
]);
|
|
288
|
+
const slugSchema = z.string().min(1).max(128).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).refine((slug) => !reservedSlugs.has(slug), { message: "This slug is reserved and cannot be used" });
|
|
257
289
|
|
|
258
290
|
//#endregion
|
|
259
291
|
//#region src/schema/fileSchema.ts
|
|
@@ -263,91 +295,36 @@ function translatableArrayOf(schema) {
|
|
|
263
295
|
const baseFileSchema = z.object({
|
|
264
296
|
objectType: objectTypeSchema.readonly(),
|
|
265
297
|
id: uuidSchema.readonly(),
|
|
298
|
+
coreVersion: versionSchema.readonly(),
|
|
266
299
|
created: z.string().datetime().readonly(),
|
|
267
|
-
updated: z.string().datetime().nullable()
|
|
300
|
+
updated: z.string().datetime().nullable().readonly()
|
|
268
301
|
});
|
|
269
302
|
const fileReferenceSchema = z.object({
|
|
270
303
|
id: uuidSchema,
|
|
271
304
|
extension: z.string().optional()
|
|
272
305
|
});
|
|
273
|
-
|
|
274
|
-
//#endregion
|
|
275
|
-
//#region src/schema/gitSchema.ts
|
|
276
306
|
/**
|
|
277
|
-
*
|
|
307
|
+
* Schema for the collection index file (collections/index.json).
|
|
308
|
+
* Maps collection UUIDs to their slug.plural values.
|
|
309
|
+
* This is a local performance cache, not git-tracked.
|
|
278
310
|
*/
|
|
279
|
-
const
|
|
280
|
-
name: z.string(),
|
|
281
|
-
email: z.string().email()
|
|
282
|
-
});
|
|
283
|
-
const gitMessageSchema = z.object({
|
|
284
|
-
method: z.enum([
|
|
285
|
-
"create",
|
|
286
|
-
"update",
|
|
287
|
-
"delete",
|
|
288
|
-
"upgrade"
|
|
289
|
-
]),
|
|
290
|
-
reference: z.object({
|
|
291
|
-
objectType: objectTypeSchema,
|
|
292
|
-
id: uuidSchema,
|
|
293
|
-
collectionId: uuidSchema.optional()
|
|
294
|
-
})
|
|
295
|
-
});
|
|
296
|
-
const gitTagSchema = z.object({
|
|
297
|
-
id: uuidSchema,
|
|
298
|
-
message: z.string(),
|
|
299
|
-
author: gitSignatureSchema,
|
|
300
|
-
datetime: z.string().datetime()
|
|
301
|
-
});
|
|
302
|
-
const gitCommitSchema = z.object({
|
|
303
|
-
hash: z.string(),
|
|
304
|
-
message: gitMessageSchema,
|
|
305
|
-
author: gitSignatureSchema,
|
|
306
|
-
datetime: z.string().datetime(),
|
|
307
|
-
tag: gitTagSchema.nullable()
|
|
308
|
-
});
|
|
309
|
-
const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
|
|
310
|
-
const gitCloneOptionsSchema = z.object({
|
|
311
|
-
depth: z.number(),
|
|
312
|
-
singleBranch: z.boolean(),
|
|
313
|
-
branch: z.string(),
|
|
314
|
-
bare: z.boolean()
|
|
315
|
-
});
|
|
316
|
-
const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
|
|
317
|
-
const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
|
|
318
|
-
const gitLogOptionsSchema = z.object({
|
|
319
|
-
limit: z.number().optional(),
|
|
320
|
-
between: z.object({
|
|
321
|
-
from: z.string(),
|
|
322
|
-
to: z.string().optional()
|
|
323
|
-
}),
|
|
324
|
-
filePath: z.string().optional()
|
|
325
|
-
});
|
|
326
|
-
const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
|
|
327
|
-
path: z.string(),
|
|
328
|
-
hash: z.string().optional()
|
|
329
|
-
});
|
|
330
|
-
const readGitTagSchema = z.object({
|
|
331
|
-
path: z.string(),
|
|
332
|
-
id: uuidSchema.readonly()
|
|
333
|
-
});
|
|
334
|
-
const deleteGitTagSchema = readGitTagSchema.extend({});
|
|
335
|
-
const countGitTagsSchema = z.object({ path: z.string() });
|
|
311
|
+
const collectionIndexSchema = z.record(uuidSchema, z.string());
|
|
336
312
|
|
|
337
313
|
//#endregion
|
|
338
314
|
//#region src/schema/assetSchema.ts
|
|
339
315
|
const assetFileSchema = baseFileSchema.extend({
|
|
340
316
|
objectType: z.literal(objectTypeSchema.enum.asset).readonly(),
|
|
341
|
-
name: z.string(),
|
|
342
|
-
description: z.string(),
|
|
317
|
+
name: z.string().trim().min(1),
|
|
318
|
+
description: z.string().trim().min(1),
|
|
343
319
|
extension: z.string().readonly(),
|
|
344
320
|
mimeType: z.string().readonly(),
|
|
345
321
|
size: z.number().readonly()
|
|
346
322
|
});
|
|
347
|
-
const assetSchema = assetFileSchema.extend({
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
323
|
+
const assetSchema = assetFileSchema.extend({ absolutePath: z.string().readonly() }).openapi("Asset");
|
|
324
|
+
const assetHistorySchema = z.object({
|
|
325
|
+
id: uuidSchema.readonly(),
|
|
326
|
+
projectId: uuidSchema.readonly()
|
|
327
|
+
});
|
|
351
328
|
const assetExportSchema = assetSchema.extend({});
|
|
352
329
|
const createAssetSchema = assetFileSchema.pick({
|
|
353
330
|
name: true,
|
|
@@ -377,11 +354,15 @@ const deleteAssetSchema = assetFileSchema.pick({
|
|
|
377
354
|
id: true,
|
|
378
355
|
extension: true
|
|
379
356
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
357
|
+
const migrateAssetSchema = z.looseObject(assetFileSchema.pick({
|
|
358
|
+
id: true,
|
|
359
|
+
coreVersion: true
|
|
360
|
+
}).shape);
|
|
380
361
|
const countAssetsSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
381
362
|
|
|
382
363
|
//#endregion
|
|
383
364
|
//#region src/schema/valueSchema.ts
|
|
384
|
-
const
|
|
365
|
+
const valueTypeSchema = z.enum([
|
|
385
366
|
"string",
|
|
386
367
|
"number",
|
|
387
368
|
"boolean",
|
|
@@ -396,20 +377,17 @@ const valueContentReferenceSchema = z.union([
|
|
|
396
377
|
valueContentReferenceToCollectionSchema,
|
|
397
378
|
valueContentReferenceToEntrySchema
|
|
398
379
|
]);
|
|
399
|
-
const directValueBaseSchema = z.object({
|
|
400
|
-
objectType: z.literal(objectTypeSchema.enum.value).readonly(),
|
|
401
|
-
fieldDefinitionId: uuidSchema.readonly()
|
|
402
|
-
});
|
|
380
|
+
const directValueBaseSchema = z.object({ objectType: z.literal(objectTypeSchema.enum.value).readonly() });
|
|
403
381
|
const directStringValueSchema = directValueBaseSchema.extend({
|
|
404
|
-
valueType: z.literal(
|
|
382
|
+
valueType: z.literal(valueTypeSchema.enum.string).readonly(),
|
|
405
383
|
content: translatableStringSchema
|
|
406
384
|
});
|
|
407
385
|
const directNumberValueSchema = directValueBaseSchema.extend({
|
|
408
|
-
valueType: z.literal(
|
|
386
|
+
valueType: z.literal(valueTypeSchema.enum.number).readonly(),
|
|
409
387
|
content: translatableNumberSchema
|
|
410
388
|
});
|
|
411
389
|
const directBooleanValueSchema = directValueBaseSchema.extend({
|
|
412
|
-
valueType: z.literal(
|
|
390
|
+
valueType: z.literal(valueTypeSchema.enum.boolean).readonly(),
|
|
413
391
|
content: translatableBooleanSchema
|
|
414
392
|
});
|
|
415
393
|
const directValueSchema = z.union([
|
|
@@ -419,8 +397,7 @@ const directValueSchema = z.union([
|
|
|
419
397
|
]);
|
|
420
398
|
const referencedValueSchema = z.object({
|
|
421
399
|
objectType: z.literal(objectTypeSchema.enum.value).readonly(),
|
|
422
|
-
|
|
423
|
-
valueType: z.literal(ValueTypeSchema.enum.reference).readonly(),
|
|
400
|
+
valueType: z.literal(valueTypeSchema.enum.reference).readonly(),
|
|
424
401
|
content: translatableArrayOf(valueContentReferenceSchema)
|
|
425
402
|
});
|
|
426
403
|
const valueSchema = z.union([directValueSchema, referencedValueSchema]);
|
|
@@ -435,19 +412,25 @@ const valueSchema = z.union([directValueSchema, referencedValueSchema]);
|
|
|
435
412
|
//#region src/schema/entrySchema.ts
|
|
436
413
|
const entryFileSchema = baseFileSchema.extend({
|
|
437
414
|
objectType: z.literal(objectTypeSchema.enum.entry).readonly(),
|
|
438
|
-
values: z.
|
|
415
|
+
values: z.record(slugSchema, valueSchema)
|
|
416
|
+
});
|
|
417
|
+
const entrySchema = entryFileSchema.openapi("Entry");
|
|
418
|
+
const entryHistorySchema = z.object({
|
|
419
|
+
id: uuidSchema.readonly(),
|
|
420
|
+
projectId: uuidSchema.readonly(),
|
|
421
|
+
collectionId: uuidSchema.readonly()
|
|
439
422
|
});
|
|
440
|
-
const entrySchema = entryFileSchema.extend({ history: z.array(gitCommitSchema) }).openapi("Entry");
|
|
441
423
|
const entryExportSchema = entrySchema.extend({});
|
|
442
424
|
const createEntrySchema = entryFileSchema.omit({
|
|
443
425
|
id: true,
|
|
444
426
|
objectType: true,
|
|
427
|
+
coreVersion: true,
|
|
445
428
|
created: true,
|
|
446
429
|
updated: true
|
|
447
430
|
}).extend({
|
|
448
431
|
projectId: uuidSchema.readonly(),
|
|
449
432
|
collectionId: uuidSchema.readonly(),
|
|
450
|
-
values: z.
|
|
433
|
+
values: z.record(slugSchema, valueSchema)
|
|
451
434
|
});
|
|
452
435
|
const readEntrySchema = z.object({
|
|
453
436
|
id: uuidSchema.readonly(),
|
|
@@ -457,6 +440,7 @@ const readEntrySchema = z.object({
|
|
|
457
440
|
});
|
|
458
441
|
const updateEntrySchema = entryFileSchema.omit({
|
|
459
442
|
objectType: true,
|
|
443
|
+
coreVersion: true,
|
|
460
444
|
created: true,
|
|
461
445
|
updated: true
|
|
462
446
|
}).extend({
|
|
@@ -464,6 +448,10 @@ const updateEntrySchema = entryFileSchema.omit({
|
|
|
464
448
|
collectionId: uuidSchema.readonly()
|
|
465
449
|
});
|
|
466
450
|
const deleteEntrySchema = readEntrySchema.extend({});
|
|
451
|
+
const migrateEntrySchema = z.looseObject(entryFileSchema.pick({
|
|
452
|
+
id: true,
|
|
453
|
+
coreVersion: true
|
|
454
|
+
}).shape);
|
|
467
455
|
const countEntriesSchema = z.object({
|
|
468
456
|
projectId: uuidSchema.readonly(),
|
|
469
457
|
collectionId: uuidSchema.readonly()
|
|
@@ -471,7 +459,7 @@ const countEntriesSchema = z.object({
|
|
|
471
459
|
|
|
472
460
|
//#endregion
|
|
473
461
|
//#region src/schema/fieldSchema.ts
|
|
474
|
-
const
|
|
462
|
+
const fieldTypeSchema = z.enum([
|
|
475
463
|
"text",
|
|
476
464
|
"textarea",
|
|
477
465
|
"email",
|
|
@@ -487,64 +475,65 @@ const FieldTypeSchema = z.enum([
|
|
|
487
475
|
"asset",
|
|
488
476
|
"entry"
|
|
489
477
|
]);
|
|
490
|
-
const
|
|
478
|
+
const fieldWidthSchema = z.enum([
|
|
491
479
|
"12",
|
|
492
480
|
"6",
|
|
493
481
|
"4",
|
|
494
482
|
"3"
|
|
495
483
|
]);
|
|
496
|
-
const
|
|
484
|
+
const fieldDefinitionBaseSchema = z.object({
|
|
497
485
|
id: uuidSchema.readonly(),
|
|
486
|
+
slug: slugSchema,
|
|
498
487
|
label: translatableStringSchema,
|
|
499
488
|
description: translatableStringSchema.nullable(),
|
|
500
489
|
isRequired: z.boolean(),
|
|
501
490
|
isDisabled: z.boolean(),
|
|
502
491
|
isUnique: z.boolean(),
|
|
503
|
-
inputWidth:
|
|
492
|
+
inputWidth: fieldWidthSchema
|
|
504
493
|
});
|
|
505
494
|
/**
|
|
506
495
|
* String based Field definitions
|
|
507
496
|
*/
|
|
508
|
-
const
|
|
509
|
-
valueType: z.literal(
|
|
497
|
+
const stringFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
498
|
+
valueType: z.literal(valueTypeSchema.enum.string),
|
|
510
499
|
defaultValue: z.string().nullable()
|
|
511
500
|
});
|
|
512
|
-
const textFieldDefinitionSchema =
|
|
513
|
-
fieldType: z.literal(
|
|
501
|
+
const textFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
502
|
+
fieldType: z.literal(fieldTypeSchema.enum.text),
|
|
514
503
|
min: z.number().nullable(),
|
|
515
504
|
max: z.number().nullable()
|
|
516
505
|
});
|
|
517
|
-
const textareaFieldDefinitionSchema =
|
|
518
|
-
fieldType: z.literal(
|
|
506
|
+
const textareaFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
507
|
+
fieldType: z.literal(fieldTypeSchema.enum.textarea),
|
|
519
508
|
min: z.number().nullable(),
|
|
520
509
|
max: z.number().nullable()
|
|
521
510
|
});
|
|
522
|
-
const emailFieldDefinitionSchema =
|
|
523
|
-
fieldType: z.literal(
|
|
511
|
+
const emailFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
512
|
+
fieldType: z.literal(fieldTypeSchema.enum.email),
|
|
524
513
|
defaultValue: z.email().nullable()
|
|
525
514
|
});
|
|
526
|
-
const urlFieldDefinitionSchema =
|
|
527
|
-
fieldType: z.literal(
|
|
515
|
+
const urlFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
516
|
+
fieldType: z.literal(fieldTypeSchema.enum.url),
|
|
528
517
|
defaultValue: z.url().nullable()
|
|
529
518
|
});
|
|
530
|
-
const ipv4FieldDefinitionSchema =
|
|
531
|
-
fieldType: z.literal(
|
|
519
|
+
const ipv4FieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
520
|
+
fieldType: z.literal(fieldTypeSchema.enum.ipv4),
|
|
532
521
|
defaultValue: z.ipv4().nullable()
|
|
533
522
|
});
|
|
534
|
-
const dateFieldDefinitionSchema =
|
|
535
|
-
fieldType: z.literal(
|
|
523
|
+
const dateFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
524
|
+
fieldType: z.literal(fieldTypeSchema.enum.date),
|
|
536
525
|
defaultValue: z.iso.date().nullable()
|
|
537
526
|
});
|
|
538
|
-
const timeFieldDefinitionSchema =
|
|
539
|
-
fieldType: z.literal(
|
|
527
|
+
const timeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
528
|
+
fieldType: z.literal(fieldTypeSchema.enum.time),
|
|
540
529
|
defaultValue: z.iso.time().nullable()
|
|
541
530
|
});
|
|
542
|
-
const datetimeFieldDefinitionSchema =
|
|
543
|
-
fieldType: z.literal(
|
|
531
|
+
const datetimeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
532
|
+
fieldType: z.literal(fieldTypeSchema.enum.datetime),
|
|
544
533
|
defaultValue: z.iso.datetime().nullable()
|
|
545
534
|
});
|
|
546
|
-
const telephoneFieldDefinitionSchema =
|
|
547
|
-
fieldType: z.literal(
|
|
535
|
+
const telephoneFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
536
|
+
fieldType: z.literal(fieldTypeSchema.enum.telephone),
|
|
548
537
|
defaultValue: z.e164().nullable()
|
|
549
538
|
});
|
|
550
539
|
const stringFieldDefinitionSchema = z.union([
|
|
@@ -561,16 +550,16 @@ const stringFieldDefinitionSchema = z.union([
|
|
|
561
550
|
/**
|
|
562
551
|
* Number based Field definitions
|
|
563
552
|
*/
|
|
564
|
-
const
|
|
565
|
-
valueType: z.literal(
|
|
553
|
+
const numberFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
554
|
+
valueType: z.literal(valueTypeSchema.enum.number),
|
|
566
555
|
min: z.number().nullable(),
|
|
567
556
|
max: z.number().nullable(),
|
|
568
557
|
isUnique: z.literal(false),
|
|
569
558
|
defaultValue: z.number().nullable()
|
|
570
559
|
});
|
|
571
|
-
const numberFieldDefinitionSchema =
|
|
572
|
-
const rangeFieldDefinitionSchema =
|
|
573
|
-
fieldType: z.literal(
|
|
560
|
+
const numberFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.number) });
|
|
561
|
+
const rangeFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({
|
|
562
|
+
fieldType: z.literal(fieldTypeSchema.enum.range),
|
|
574
563
|
isRequired: z.literal(true),
|
|
575
564
|
min: z.number(),
|
|
576
565
|
max: z.number(),
|
|
@@ -579,24 +568,27 @@ const rangeFieldDefinitionSchema = NumberFieldDefinitionBaseSchema.extend({
|
|
|
579
568
|
/**
|
|
580
569
|
* Boolean based Field definitions
|
|
581
570
|
*/
|
|
582
|
-
const
|
|
583
|
-
valueType: z.literal(
|
|
571
|
+
const booleanFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
572
|
+
valueType: z.literal(valueTypeSchema.enum.boolean),
|
|
584
573
|
isRequired: z.literal(true),
|
|
585
574
|
defaultValue: z.boolean(),
|
|
586
575
|
isUnique: z.literal(false)
|
|
587
576
|
});
|
|
588
|
-
const toggleFieldDefinitionSchema =
|
|
577
|
+
const toggleFieldDefinitionSchema = booleanFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.toggle) });
|
|
589
578
|
/**
|
|
590
579
|
* Reference based Field definitions
|
|
591
580
|
*/
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
581
|
+
const referenceFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
582
|
+
valueType: z.literal(valueTypeSchema.enum.reference),
|
|
583
|
+
isUnique: z.literal(false)
|
|
584
|
+
});
|
|
585
|
+
const assetFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
|
|
586
|
+
fieldType: z.literal(fieldTypeSchema.enum.asset),
|
|
595
587
|
min: z.number().nullable(),
|
|
596
588
|
max: z.number().nullable()
|
|
597
589
|
});
|
|
598
|
-
const entryFieldDefinitionSchema =
|
|
599
|
-
fieldType: z.literal(
|
|
590
|
+
const entryFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
|
|
591
|
+
fieldType: z.literal(fieldTypeSchema.enum.entry),
|
|
600
592
|
ofCollections: z.array(uuidSchema),
|
|
601
593
|
min: z.number().nullable(),
|
|
602
594
|
max: z.number().nullable()
|
|
@@ -619,18 +611,23 @@ const collectionFileSchema = baseFileSchema.extend({
|
|
|
619
611
|
plural: translatableStringSchema
|
|
620
612
|
}),
|
|
621
613
|
slug: z.object({
|
|
622
|
-
singular:
|
|
623
|
-
plural:
|
|
614
|
+
singular: slugSchema,
|
|
615
|
+
plural: slugSchema
|
|
624
616
|
}),
|
|
625
617
|
description: translatableStringSchema,
|
|
626
618
|
icon: supportedIconSchema,
|
|
627
619
|
fieldDefinitions: z.array(fieldDefinitionSchema)
|
|
628
620
|
});
|
|
629
|
-
const collectionSchema = collectionFileSchema.
|
|
621
|
+
const collectionSchema = collectionFileSchema.openapi("Collection");
|
|
622
|
+
const collectionHistorySchema = z.object({
|
|
623
|
+
id: uuidSchema.readonly(),
|
|
624
|
+
projectId: uuidSchema.readonly()
|
|
625
|
+
});
|
|
630
626
|
const collectionExportSchema = collectionSchema.extend({ entries: z.array(entryExportSchema) });
|
|
631
627
|
const createCollectionSchema = collectionFileSchema.omit({
|
|
632
628
|
id: true,
|
|
633
629
|
objectType: true,
|
|
630
|
+
coreVersion: true,
|
|
634
631
|
created: true,
|
|
635
632
|
updated: true
|
|
636
633
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
@@ -639,6 +636,11 @@ const readCollectionSchema = z.object({
|
|
|
639
636
|
projectId: uuidSchema.readonly(),
|
|
640
637
|
commitHash: z.string().optional().readonly()
|
|
641
638
|
});
|
|
639
|
+
const readBySlugCollectionSchema = z.object({
|
|
640
|
+
slug: slugSchema,
|
|
641
|
+
projectId: uuidSchema.readonly(),
|
|
642
|
+
commitHash: z.string().optional().readonly()
|
|
643
|
+
});
|
|
642
644
|
const updateCollectionSchema = collectionFileSchema.pick({
|
|
643
645
|
id: true,
|
|
644
646
|
name: true,
|
|
@@ -649,6 +651,14 @@ const updateCollectionSchema = collectionFileSchema.pick({
|
|
|
649
651
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
650
652
|
const deleteCollectionSchema = readCollectionSchema.extend({});
|
|
651
653
|
const countCollectionsSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
654
|
+
const migrateCollectionSchema = z.looseObject(collectionFileSchema.pick({
|
|
655
|
+
id: true,
|
|
656
|
+
coreVersion: true
|
|
657
|
+
}).shape);
|
|
658
|
+
const resolveCollectionIdSchema = z.object({
|
|
659
|
+
projectId: uuidSchema.readonly(),
|
|
660
|
+
idOrSlug: z.string()
|
|
661
|
+
});
|
|
652
662
|
|
|
653
663
|
//#endregion
|
|
654
664
|
//#region src/schema/coreSchema.ts
|
|
@@ -665,15 +675,88 @@ const constructorElekIoCoreSchema = elekIoCoreOptionsSchema.partial({
|
|
|
665
675
|
}).optional();
|
|
666
676
|
|
|
667
677
|
//#endregion
|
|
668
|
-
//#region src/schema/
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
678
|
+
//#region src/schema/gitSchema.ts
|
|
679
|
+
/**
|
|
680
|
+
* Signature git uses to identify users
|
|
681
|
+
*/
|
|
682
|
+
const gitSignatureSchema = z.object({
|
|
683
|
+
name: z.string().regex(/^[^|]+$/, "Name must not contain pipe characters"),
|
|
684
|
+
email: z.string().email()
|
|
685
|
+
});
|
|
686
|
+
const gitMessageSchema = z.object({
|
|
687
|
+
method: z.enum([
|
|
688
|
+
"create",
|
|
689
|
+
"update",
|
|
690
|
+
"delete",
|
|
691
|
+
"upgrade",
|
|
692
|
+
"release"
|
|
693
|
+
]),
|
|
694
|
+
reference: z.object({
|
|
695
|
+
objectType: objectTypeSchema,
|
|
696
|
+
id: uuidSchema,
|
|
697
|
+
collectionId: uuidSchema.optional()
|
|
698
|
+
})
|
|
699
|
+
});
|
|
700
|
+
const gitTagMessageSchema = z.discriminatedUnion("type", [
|
|
701
|
+
z.object({
|
|
702
|
+
type: z.literal("release"),
|
|
703
|
+
version: versionSchema
|
|
704
|
+
}),
|
|
705
|
+
z.object({
|
|
706
|
+
type: z.literal("preview"),
|
|
707
|
+
version: versionSchema
|
|
708
|
+
}),
|
|
709
|
+
z.object({
|
|
710
|
+
type: z.literal("upgrade"),
|
|
711
|
+
coreVersion: versionSchema
|
|
712
|
+
})
|
|
673
713
|
]);
|
|
714
|
+
const gitTagSchema = z.object({
|
|
715
|
+
id: uuidSchema,
|
|
716
|
+
message: gitTagMessageSchema,
|
|
717
|
+
author: gitSignatureSchema,
|
|
718
|
+
datetime: z.iso.datetime()
|
|
719
|
+
});
|
|
720
|
+
const gitCommitSchema = z.object({
|
|
721
|
+
hash: z.hash("sha1"),
|
|
722
|
+
message: gitMessageSchema,
|
|
723
|
+
author: gitSignatureSchema,
|
|
724
|
+
datetime: z.iso.datetime(),
|
|
725
|
+
tag: gitTagSchema.nullable()
|
|
726
|
+
});
|
|
727
|
+
const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
|
|
728
|
+
const gitCloneOptionsSchema = z.object({
|
|
729
|
+
depth: z.number(),
|
|
730
|
+
singleBranch: z.boolean(),
|
|
731
|
+
branch: z.string(),
|
|
732
|
+
bare: z.boolean()
|
|
733
|
+
});
|
|
734
|
+
const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
|
|
735
|
+
const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
|
|
736
|
+
const gitLogOptionsSchema = z.object({
|
|
737
|
+
limit: z.number().optional(),
|
|
738
|
+
between: z.object({
|
|
739
|
+
from: z.string(),
|
|
740
|
+
to: z.string().optional()
|
|
741
|
+
}),
|
|
742
|
+
filePath: z.string().optional()
|
|
743
|
+
});
|
|
744
|
+
const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
|
|
745
|
+
path: z.string(),
|
|
746
|
+
hash: z.string().optional()
|
|
747
|
+
});
|
|
748
|
+
const readGitTagSchema = z.object({
|
|
749
|
+
path: z.string(),
|
|
750
|
+
id: uuidSchema.readonly()
|
|
751
|
+
});
|
|
752
|
+
const deleteGitTagSchema = readGitTagSchema.extend({});
|
|
753
|
+
const countGitTagsSchema = z.object({ path: z.string() });
|
|
754
|
+
|
|
755
|
+
//#endregion
|
|
756
|
+
//#region src/schema/projectSchema.ts
|
|
674
757
|
const projectSettingsSchema = z.object({ language: z.object({
|
|
675
758
|
default: supportedLanguageSchema,
|
|
676
|
-
supported: z.array(supportedLanguageSchema)
|
|
759
|
+
supported: z.array(supportedLanguageSchema).refine((langs) => new Set(langs).size === langs.length, { message: "Supported languages must not contain duplicates" })
|
|
677
760
|
}) });
|
|
678
761
|
const projectFolderSchema = z.enum([
|
|
679
762
|
"assets",
|
|
@@ -684,22 +767,21 @@ const projectFolderSchema = z.enum([
|
|
|
684
767
|
const projectBranchSchema = z.enum(["production", "work"]);
|
|
685
768
|
const projectFileSchema = baseFileSchema.extend({
|
|
686
769
|
objectType: z.literal(objectTypeSchema.enum.project).readonly(),
|
|
687
|
-
coreVersion: versionSchema,
|
|
688
770
|
name: z.string().trim().min(1),
|
|
689
771
|
description: z.string().trim().min(1),
|
|
690
772
|
version: versionSchema,
|
|
691
|
-
status: projectStatusSchema,
|
|
692
773
|
settings: projectSettingsSchema
|
|
693
774
|
});
|
|
694
|
-
const projectSchema = projectFileSchema.extend({
|
|
695
|
-
|
|
775
|
+
const projectSchema = projectFileSchema.extend({ remoteOriginUrl: z.string().nullable().openapi({ description: "URL of the remote Git repository" }) }).openapi("Project");
|
|
776
|
+
const projectHistorySchema = z.object({ id: uuidSchema.readonly() });
|
|
777
|
+
const projectHistoryResultSchema = z.object({
|
|
696
778
|
history: z.array(gitCommitSchema).openapi({ description: "Commit history of this Project" }),
|
|
697
779
|
fullHistory: z.array(gitCommitSchema).openapi({ description: "Full commit history of this Project including all Assets, Collections, Entries and other files" })
|
|
698
|
-
})
|
|
699
|
-
const migrateProjectSchema = projectFileSchema.pick({
|
|
780
|
+
});
|
|
781
|
+
const migrateProjectSchema = z.looseObject(projectFileSchema.pick({
|
|
700
782
|
id: true,
|
|
701
783
|
coreVersion: true
|
|
702
|
-
}).
|
|
784
|
+
}).shape);
|
|
703
785
|
const projectExportSchema = projectSchema.extend({
|
|
704
786
|
assets: z.array(assetExportSchema),
|
|
705
787
|
collections: z.array(collectionExportSchema)
|
|
@@ -724,13 +806,6 @@ const upgradeProjectSchema = z.object({
|
|
|
724
806
|
force: z.boolean().optional()
|
|
725
807
|
});
|
|
726
808
|
const deleteProjectSchema = readProjectSchema.extend({ force: z.boolean().optional() });
|
|
727
|
-
const projectUpgradeSchema = z.object({
|
|
728
|
-
to: versionSchema.readonly(),
|
|
729
|
-
run: z.function({
|
|
730
|
-
input: [projectFileSchema],
|
|
731
|
-
output: z.promise(z.void())
|
|
732
|
-
})
|
|
733
|
-
});
|
|
734
809
|
const cloneProjectSchema = z.object({ url: z.string() });
|
|
735
810
|
const listBranchesProjectSchema = z.object({ id: uuidSchema.readonly() });
|
|
736
811
|
const currentBranchProjectSchema = z.object({ id: uuidSchema.readonly() });
|
|
@@ -784,29 +859,29 @@ function getNumberValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
|
784
859
|
function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
785
860
|
let schema = null;
|
|
786
861
|
switch (fieldDefinition.fieldType) {
|
|
787
|
-
case
|
|
862
|
+
case fieldTypeSchema.enum.email:
|
|
788
863
|
schema = z.email();
|
|
789
864
|
break;
|
|
790
|
-
case
|
|
865
|
+
case fieldTypeSchema.enum.url:
|
|
791
866
|
schema = z.url();
|
|
792
867
|
break;
|
|
793
|
-
case
|
|
868
|
+
case fieldTypeSchema.enum.ipv4:
|
|
794
869
|
schema = z.ipv4();
|
|
795
870
|
break;
|
|
796
|
-
case
|
|
871
|
+
case fieldTypeSchema.enum.date:
|
|
797
872
|
schema = z.iso.date();
|
|
798
873
|
break;
|
|
799
|
-
case
|
|
874
|
+
case fieldTypeSchema.enum.time:
|
|
800
875
|
schema = z.iso.time();
|
|
801
876
|
break;
|
|
802
|
-
case
|
|
877
|
+
case fieldTypeSchema.enum.datetime:
|
|
803
878
|
schema = z.iso.datetime();
|
|
804
879
|
break;
|
|
805
|
-
case
|
|
880
|
+
case fieldTypeSchema.enum.telephone:
|
|
806
881
|
schema = z.e164();
|
|
807
882
|
break;
|
|
808
|
-
case
|
|
809
|
-
case
|
|
883
|
+
case fieldTypeSchema.enum.text:
|
|
884
|
+
case fieldTypeSchema.enum.textarea:
|
|
810
885
|
schema = z.string().trim();
|
|
811
886
|
break;
|
|
812
887
|
}
|
|
@@ -822,10 +897,10 @@ function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
|
822
897
|
function getReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
823
898
|
let schema;
|
|
824
899
|
switch (fieldDefinition.fieldType) {
|
|
825
|
-
case
|
|
900
|
+
case fieldTypeSchema.enum.asset:
|
|
826
901
|
schema = z.array(valueContentReferenceToAssetSchema);
|
|
827
902
|
break;
|
|
828
|
-
case
|
|
903
|
+
case fieldTypeSchema.enum.entry:
|
|
829
904
|
schema = z.array(valueContentReferenceToEntrySchema);
|
|
830
905
|
break;
|
|
831
906
|
}
|
|
@@ -851,47 +926,46 @@ function getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefi
|
|
|
851
926
|
*/
|
|
852
927
|
function getValueSchemaFromFieldDefinition(fieldDefinition) {
|
|
853
928
|
switch (fieldDefinition.valueType) {
|
|
854
|
-
case
|
|
855
|
-
case
|
|
856
|
-
case
|
|
857
|
-
case
|
|
929
|
+
case valueTypeSchema.enum.boolean: return directBooleanValueSchema.extend({ content: getTranslatableBooleanValueContentSchemaFromFieldDefinition() });
|
|
930
|
+
case valueTypeSchema.enum.number: return directNumberValueSchema.extend({ content: getTranslatableNumberValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
931
|
+
case valueTypeSchema.enum.string: return directStringValueSchema.extend({ content: getTranslatableStringValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
932
|
+
case valueTypeSchema.enum.reference: return referencedValueSchema.extend({ content: getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
858
933
|
default: throw new Error(`Error generating schema for unsupported ValueType "${fieldDefinition.valueType}"`);
|
|
859
934
|
}
|
|
860
935
|
}
|
|
861
936
|
/**
|
|
937
|
+
* Builds a z.object shape from field definitions, keyed by slug
|
|
938
|
+
*/
|
|
939
|
+
function getValuesShapeFromFieldDefinitions(fieldDefinitions) {
|
|
940
|
+
const shape = {};
|
|
941
|
+
for (const fieldDef of fieldDefinitions) shape[fieldDef.slug] = getValueSchemaFromFieldDefinition(fieldDef);
|
|
942
|
+
return shape;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
862
945
|
* Generates a schema for an Entry based on the given Field definitions and Values
|
|
863
946
|
*/
|
|
864
947
|
function getEntrySchemaFromFieldDefinitions(fieldDefinitions) {
|
|
865
|
-
const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
|
|
866
|
-
return getValueSchemaFromFieldDefinition(fieldDefinition);
|
|
867
|
-
});
|
|
868
948
|
return z.object({
|
|
869
949
|
...entrySchema.shape,
|
|
870
|
-
values: z.
|
|
950
|
+
values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
|
|
871
951
|
});
|
|
872
952
|
}
|
|
873
953
|
/**
|
|
874
954
|
* Generates a schema for creating a new Entry based on the given Field definitions and Values
|
|
875
955
|
*/
|
|
876
956
|
function getCreateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
|
|
877
|
-
const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
|
|
878
|
-
return getValueSchemaFromFieldDefinition(fieldDefinition);
|
|
879
|
-
});
|
|
880
957
|
return z.object({
|
|
881
958
|
...createEntrySchema.shape,
|
|
882
|
-
values: z.
|
|
959
|
+
values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
|
|
883
960
|
});
|
|
884
961
|
}
|
|
885
962
|
/**
|
|
886
963
|
* Generates a schema for updating an existing Entry based on the given Field definitions and Values
|
|
887
964
|
*/
|
|
888
965
|
function getUpdateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
|
|
889
|
-
const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
|
|
890
|
-
return getValueSchemaFromFieldDefinition(fieldDefinition);
|
|
891
|
-
});
|
|
892
966
|
return z.object({
|
|
893
967
|
...updateEntrySchema.shape,
|
|
894
|
-
values: z.
|
|
968
|
+
values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
|
|
895
969
|
});
|
|
896
970
|
}
|
|
897
971
|
|
|
@@ -907,7 +981,8 @@ const serviceTypeSchema = z.enum([
|
|
|
907
981
|
"Search",
|
|
908
982
|
"Collection",
|
|
909
983
|
"Entry",
|
|
910
|
-
"Value"
|
|
984
|
+
"Value",
|
|
985
|
+
"Release"
|
|
911
986
|
]);
|
|
912
987
|
function paginatedListOf(schema) {
|
|
913
988
|
return z.object({
|
|
@@ -930,18 +1005,18 @@ const listGitTagsSchema = z.object({ path: z.string() });
|
|
|
930
1005
|
|
|
931
1006
|
//#endregion
|
|
932
1007
|
//#region src/schema/userSchema.ts
|
|
933
|
-
const
|
|
1008
|
+
const userTypeSchema = z.enum(["local", "cloud"]);
|
|
934
1009
|
const baseUserSchema = gitSignatureSchema.extend({
|
|
935
|
-
userType:
|
|
1010
|
+
userType: userTypeSchema,
|
|
936
1011
|
language: supportedLanguageSchema,
|
|
937
1012
|
localApi: z.object({
|
|
938
1013
|
isEnabled: z.boolean(),
|
|
939
1014
|
port: z.number()
|
|
940
1015
|
})
|
|
941
1016
|
});
|
|
942
|
-
const localUserSchema = baseUserSchema.extend({ userType: z.literal(
|
|
1017
|
+
const localUserSchema = baseUserSchema.extend({ userType: z.literal(userTypeSchema.enum.local) });
|
|
943
1018
|
const cloudUserSchema = baseUserSchema.extend({
|
|
944
|
-
userType: z.literal(
|
|
1019
|
+
userType: z.literal(userTypeSchema.enum.cloud),
|
|
945
1020
|
id: uuidSchema
|
|
946
1021
|
});
|
|
947
1022
|
const userFileSchema = z.union([localUserSchema, cloudUserSchema]);
|
|
@@ -1015,6 +1090,97 @@ const logConsoleTransportSchema = logSchema.extend({
|
|
|
1015
1090
|
level: z.string()
|
|
1016
1091
|
});
|
|
1017
1092
|
|
|
1093
|
+
//#endregion
|
|
1094
|
+
//#region src/schema/releaseSchema.ts
|
|
1095
|
+
const semverBumpSchema = z.enum([
|
|
1096
|
+
"major",
|
|
1097
|
+
"minor",
|
|
1098
|
+
"patch"
|
|
1099
|
+
]);
|
|
1100
|
+
const fieldChangeTypeSchema = z.enum([
|
|
1101
|
+
"added",
|
|
1102
|
+
"deleted",
|
|
1103
|
+
"valueTypeChanged",
|
|
1104
|
+
"fieldTypeChanged",
|
|
1105
|
+
"slugChanged",
|
|
1106
|
+
"minMaxTightened",
|
|
1107
|
+
"isRequiredToNotRequired",
|
|
1108
|
+
"isUniqueToNotUnique",
|
|
1109
|
+
"ofCollectionsChanged",
|
|
1110
|
+
"isNotRequiredToRequired",
|
|
1111
|
+
"isNotUniqueToUnique",
|
|
1112
|
+
"labelChanged",
|
|
1113
|
+
"descriptionChanged",
|
|
1114
|
+
"defaultValueChanged",
|
|
1115
|
+
"inputWidthChanged",
|
|
1116
|
+
"isDisabledChanged",
|
|
1117
|
+
"minMaxLoosened"
|
|
1118
|
+
]);
|
|
1119
|
+
const fieldChangeSchema = z.object({
|
|
1120
|
+
collectionId: uuidSchema,
|
|
1121
|
+
fieldId: uuidSchema,
|
|
1122
|
+
fieldSlug: z.string(),
|
|
1123
|
+
changeType: fieldChangeTypeSchema,
|
|
1124
|
+
bump: semverBumpSchema
|
|
1125
|
+
});
|
|
1126
|
+
const collectionChangeTypeSchema = z.enum(["added", "deleted"]);
|
|
1127
|
+
const collectionChangeSchema = z.object({
|
|
1128
|
+
collectionId: uuidSchema,
|
|
1129
|
+
changeType: collectionChangeTypeSchema,
|
|
1130
|
+
bump: semverBumpSchema
|
|
1131
|
+
});
|
|
1132
|
+
const projectChangeTypeSchema = z.enum([
|
|
1133
|
+
"nameChanged",
|
|
1134
|
+
"descriptionChanged",
|
|
1135
|
+
"defaultLanguageChanged",
|
|
1136
|
+
"supportedLanguageAdded",
|
|
1137
|
+
"supportedLanguageRemoved"
|
|
1138
|
+
]);
|
|
1139
|
+
const projectChangeSchema = z.object({
|
|
1140
|
+
changeType: projectChangeTypeSchema,
|
|
1141
|
+
bump: semverBumpSchema
|
|
1142
|
+
});
|
|
1143
|
+
const assetChangeTypeSchema = z.enum([
|
|
1144
|
+
"added",
|
|
1145
|
+
"deleted",
|
|
1146
|
+
"metadataChanged",
|
|
1147
|
+
"binaryChanged"
|
|
1148
|
+
]);
|
|
1149
|
+
const assetChangeSchema = z.object({
|
|
1150
|
+
assetId: uuidSchema,
|
|
1151
|
+
changeType: assetChangeTypeSchema,
|
|
1152
|
+
bump: semverBumpSchema
|
|
1153
|
+
});
|
|
1154
|
+
const entryChangeTypeSchema = z.enum([
|
|
1155
|
+
"added",
|
|
1156
|
+
"deleted",
|
|
1157
|
+
"modified"
|
|
1158
|
+
]);
|
|
1159
|
+
const entryChangeSchema = z.object({
|
|
1160
|
+
collectionId: uuidSchema,
|
|
1161
|
+
entryId: uuidSchema,
|
|
1162
|
+
changeType: entryChangeTypeSchema,
|
|
1163
|
+
bump: semverBumpSchema
|
|
1164
|
+
});
|
|
1165
|
+
const releaseDiffSchema = z.object({
|
|
1166
|
+
project: projectSchema,
|
|
1167
|
+
bump: semverBumpSchema.nullable(),
|
|
1168
|
+
currentVersion: versionSchema,
|
|
1169
|
+
nextVersion: versionSchema.nullable(),
|
|
1170
|
+
projectChanges: z.array(projectChangeSchema),
|
|
1171
|
+
collectionChanges: z.array(collectionChangeSchema),
|
|
1172
|
+
fieldChanges: z.array(fieldChangeSchema),
|
|
1173
|
+
assetChanges: z.array(assetChangeSchema),
|
|
1174
|
+
entryChanges: z.array(entryChangeSchema)
|
|
1175
|
+
});
|
|
1176
|
+
const prepareReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1177
|
+
const createReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1178
|
+
const createPreviewReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1179
|
+
const releaseResultSchema = z.object({
|
|
1180
|
+
version: versionSchema,
|
|
1181
|
+
diff: releaseDiffSchema
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1018
1184
|
//#endregion
|
|
1019
1185
|
//#region src/api/routes/content/v1/projects.ts
|
|
1020
1186
|
const tags$3 = ["Content API v1"];
|
|
@@ -1136,29 +1302,36 @@ const router$5 = createRouter().openapi(createRoute({
|
|
|
1136
1302
|
return c.json(count, 200);
|
|
1137
1303
|
}).openapi(createRoute({
|
|
1138
1304
|
summary: "Get one Collection",
|
|
1139
|
-
description: "Retrieve a Collection by
|
|
1305
|
+
description: "Retrieve a Collection by UUID or slug",
|
|
1140
1306
|
method: "get",
|
|
1141
|
-
path: "/{projectId}/collections/{
|
|
1307
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}",
|
|
1142
1308
|
tags: tags$2,
|
|
1143
1309
|
request: { params: z.object({
|
|
1144
1310
|
projectId: uuidSchema.openapi({ param: {
|
|
1145
1311
|
name: "projectId",
|
|
1146
1312
|
in: "path"
|
|
1147
1313
|
} }),
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1314
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1315
|
+
param: {
|
|
1316
|
+
name: "collectionIdOrSlug",
|
|
1317
|
+
in: "path"
|
|
1318
|
+
},
|
|
1319
|
+
description: "Collection UUID or slug"
|
|
1320
|
+
})
|
|
1152
1321
|
}) },
|
|
1153
1322
|
responses: { [200]: {
|
|
1154
1323
|
content: { "application/json": { schema: collectionSchema } },
|
|
1155
1324
|
description: "The requested Collection"
|
|
1156
1325
|
} }
|
|
1157
1326
|
}), async (c) => {
|
|
1158
|
-
const { projectId,
|
|
1327
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1328
|
+
const resolvedId = await c.var.collectionService.resolveCollectionId({
|
|
1329
|
+
projectId,
|
|
1330
|
+
idOrSlug: collectionIdOrSlug
|
|
1331
|
+
});
|
|
1159
1332
|
const collection = await c.var.collectionService.read({
|
|
1160
1333
|
projectId,
|
|
1161
|
-
id:
|
|
1334
|
+
id: resolvedId
|
|
1162
1335
|
});
|
|
1163
1336
|
return c.json(collection, 200);
|
|
1164
1337
|
});
|
|
@@ -1170,7 +1343,7 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1170
1343
|
summary: "List Entries",
|
|
1171
1344
|
description: "Lists all Entries of the given Projects Collection",
|
|
1172
1345
|
method: "get",
|
|
1173
|
-
path: "/{projectId}/collections/{
|
|
1346
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries",
|
|
1174
1347
|
tags: tags$1,
|
|
1175
1348
|
request: {
|
|
1176
1349
|
params: z.object({
|
|
@@ -1178,10 +1351,13 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1178
1351
|
name: "projectId",
|
|
1179
1352
|
in: "path"
|
|
1180
1353
|
} }),
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1354
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1355
|
+
param: {
|
|
1356
|
+
name: "collectionIdOrSlug",
|
|
1357
|
+
in: "path"
|
|
1358
|
+
},
|
|
1359
|
+
description: "Collection UUID or slug"
|
|
1360
|
+
})
|
|
1185
1361
|
}),
|
|
1186
1362
|
query: z.object({
|
|
1187
1363
|
limit: z.string().pipe(z.coerce.number()).optional().openapi({
|
|
@@ -1199,8 +1375,12 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1199
1375
|
description: "A list of Entries for the given Projects Collection"
|
|
1200
1376
|
} }
|
|
1201
1377
|
}), async (c) => {
|
|
1202
|
-
const { projectId,
|
|
1378
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1203
1379
|
const { limit, offset } = c.req.valid("query");
|
|
1380
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1381
|
+
projectId,
|
|
1382
|
+
idOrSlug: collectionIdOrSlug
|
|
1383
|
+
});
|
|
1204
1384
|
const entries = await c.var.entryService.list({
|
|
1205
1385
|
projectId,
|
|
1206
1386
|
collectionId,
|
|
@@ -1212,24 +1392,31 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1212
1392
|
summary: "Count Entries",
|
|
1213
1393
|
description: "Counts all Entries of the given Projects Collection",
|
|
1214
1394
|
method: "get",
|
|
1215
|
-
path: "/{projectId}/collections/{
|
|
1395
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries/count",
|
|
1216
1396
|
tags: tags$1,
|
|
1217
1397
|
request: { params: z.object({
|
|
1218
1398
|
projectId: uuidSchema.openapi({ param: {
|
|
1219
1399
|
name: "projectId",
|
|
1220
1400
|
in: "path"
|
|
1221
1401
|
} }),
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1402
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1403
|
+
param: {
|
|
1404
|
+
name: "collectionIdOrSlug",
|
|
1405
|
+
in: "path"
|
|
1406
|
+
},
|
|
1407
|
+
description: "Collection UUID or slug"
|
|
1408
|
+
})
|
|
1226
1409
|
}) },
|
|
1227
1410
|
responses: { [200]: {
|
|
1228
1411
|
content: { "application/json": { schema: z.number() } },
|
|
1229
1412
|
description: "The number of Entries of the given Projects Collection"
|
|
1230
1413
|
} }
|
|
1231
1414
|
}), async (c) => {
|
|
1232
|
-
const { projectId,
|
|
1415
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1416
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1417
|
+
projectId,
|
|
1418
|
+
idOrSlug: collectionIdOrSlug
|
|
1419
|
+
});
|
|
1233
1420
|
const count = await c.var.entryService.count({
|
|
1234
1421
|
projectId,
|
|
1235
1422
|
collectionId
|
|
@@ -1239,17 +1426,20 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1239
1426
|
summary: "Get one Entry",
|
|
1240
1427
|
description: "Retrieve an Entry by ID",
|
|
1241
1428
|
method: "get",
|
|
1242
|
-
path: "/{projectId}/collections/{
|
|
1429
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries/{entryId}",
|
|
1243
1430
|
tags: tags$1,
|
|
1244
1431
|
request: { params: z.object({
|
|
1245
1432
|
projectId: uuidSchema.openapi({ param: {
|
|
1246
1433
|
name: "projectId",
|
|
1247
1434
|
in: "path"
|
|
1248
1435
|
} }),
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1436
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1437
|
+
param: {
|
|
1438
|
+
name: "collectionIdOrSlug",
|
|
1439
|
+
in: "path"
|
|
1440
|
+
},
|
|
1441
|
+
description: "Collection UUID or slug"
|
|
1442
|
+
}),
|
|
1253
1443
|
entryId: uuidSchema.openapi({ param: {
|
|
1254
1444
|
name: "entryId",
|
|
1255
1445
|
in: "path"
|
|
@@ -1260,7 +1450,11 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1260
1450
|
description: "The requested Entry"
|
|
1261
1451
|
} }
|
|
1262
1452
|
}), async (c) => {
|
|
1263
|
-
const { projectId,
|
|
1453
|
+
const { projectId, collectionIdOrSlug, entryId } = c.req.valid("param");
|
|
1454
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1455
|
+
projectId,
|
|
1456
|
+
idOrSlug: collectionIdOrSlug
|
|
1457
|
+
});
|
|
1264
1458
|
const entry = await c.var.entryService.read({
|
|
1265
1459
|
projectId,
|
|
1266
1460
|
collectionId,
|
|
@@ -1527,6 +1721,9 @@ const pathTo = {
|
|
|
1527
1721
|
collectionFile: (projectId, id) => {
|
|
1528
1722
|
return Path.join(pathTo.collection(projectId, id), "collection.json");
|
|
1529
1723
|
},
|
|
1724
|
+
collectionIndex: (projectId) => {
|
|
1725
|
+
return Path.join(pathTo.collections(projectId), "index.json");
|
|
1726
|
+
},
|
|
1530
1727
|
entries: (projectId, collectionId) => {
|
|
1531
1728
|
return Path.join(pathTo.collection(projectId, collectionId));
|
|
1532
1729
|
},
|
|
@@ -1749,6 +1946,38 @@ var AbstractCrudService = class {
|
|
|
1749
1946
|
}
|
|
1750
1947
|
};
|
|
1751
1948
|
|
|
1949
|
+
//#endregion
|
|
1950
|
+
//#region src/service/migrations/applyMigrations.ts
|
|
1951
|
+
function applyMigrations(data, migrations, targetVersion) {
|
|
1952
|
+
let current = structuredClone(data);
|
|
1953
|
+
while (current["coreVersion"] !== targetVersion) {
|
|
1954
|
+
const migration = migrations.find((m) => m.from === current["coreVersion"]);
|
|
1955
|
+
if (!migration) {
|
|
1956
|
+
current["coreVersion"] = targetVersion;
|
|
1957
|
+
break;
|
|
1958
|
+
}
|
|
1959
|
+
current = migration.run(current);
|
|
1960
|
+
current["coreVersion"] = migration.to;
|
|
1961
|
+
}
|
|
1962
|
+
return current;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
//#endregion
|
|
1966
|
+
//#region src/service/migrations/assetMigrations.ts
|
|
1967
|
+
const assetMigrations = [];
|
|
1968
|
+
|
|
1969
|
+
//#endregion
|
|
1970
|
+
//#region src/service/migrations/collectionMigrations.ts
|
|
1971
|
+
const collectionMigrations = [];
|
|
1972
|
+
|
|
1973
|
+
//#endregion
|
|
1974
|
+
//#region src/service/migrations/entryMigrations.ts
|
|
1975
|
+
const entryMigrations = [];
|
|
1976
|
+
|
|
1977
|
+
//#endregion
|
|
1978
|
+
//#region src/service/migrations/projectMigrations.ts
|
|
1979
|
+
const projectMigrations = [];
|
|
1980
|
+
|
|
1752
1981
|
//#endregion
|
|
1753
1982
|
//#region src/util/shared.ts
|
|
1754
1983
|
/**
|
|
@@ -1791,10 +2020,12 @@ function slug(string) {
|
|
|
1791
2020
|
* Service that manages CRUD functionality for Asset files on disk
|
|
1792
2021
|
*/
|
|
1793
2022
|
var AssetService = class extends AbstractCrudService {
|
|
2023
|
+
coreVersion;
|
|
1794
2024
|
jsonFileService;
|
|
1795
2025
|
gitService;
|
|
1796
|
-
constructor(options, logService, jsonFileService, gitService) {
|
|
2026
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService) {
|
|
1797
2027
|
super(serviceTypeSchema.enum.Asset, options, logService);
|
|
2028
|
+
this.coreVersion = coreVersion;
|
|
1798
2029
|
this.jsonFileService = jsonFileService;
|
|
1799
2030
|
this.gitService = gitService;
|
|
1800
2031
|
}
|
|
@@ -1814,6 +2045,7 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1814
2045
|
name: slug(props.name),
|
|
1815
2046
|
objectType: "asset",
|
|
1816
2047
|
id,
|
|
2048
|
+
coreVersion: this.coreVersion,
|
|
1817
2049
|
created: datetime(),
|
|
1818
2050
|
updated: null,
|
|
1819
2051
|
extension: fileType.extension,
|
|
@@ -1858,6 +2090,13 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1858
2090
|
}
|
|
1859
2091
|
}
|
|
1860
2092
|
/**
|
|
2093
|
+
* Returns the commit history of an Asset
|
|
2094
|
+
*/
|
|
2095
|
+
async history(props) {
|
|
2096
|
+
assetHistorySchema.parse(props);
|
|
2097
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.assetFile(props.projectId, props.id) });
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
1861
2100
|
* Copies an Asset to given file path on disk
|
|
1862
2101
|
*/
|
|
1863
2102
|
async save(props) {
|
|
@@ -1966,13 +2205,11 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1966
2205
|
* @param projectId The project's ID
|
|
1967
2206
|
* @param assetFile The AssetFile to convert
|
|
1968
2207
|
*/
|
|
1969
|
-
|
|
2208
|
+
toAsset(projectId, assetFile, commitHash) {
|
|
1970
2209
|
const assetPath = commitHash ? pathTo.tmpAsset(assetFile.id, commitHash, assetFile.extension) : pathTo.asset(projectId, assetFile.id, assetFile.extension);
|
|
1971
|
-
const history = await this.gitService.log(pathTo.project(projectId), { filePath: pathTo.assetFile(projectId, assetFile.id) });
|
|
1972
2210
|
return {
|
|
1973
2211
|
...assetFile,
|
|
1974
|
-
absolutePath: assetPath
|
|
1975
|
-
history
|
|
2212
|
+
absolutePath: assetPath
|
|
1976
2213
|
};
|
|
1977
2214
|
}
|
|
1978
2215
|
/**
|
|
@@ -1995,7 +2232,8 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1995
2232
|
* Migrates an potentially outdated Asset file to the current schema
|
|
1996
2233
|
*/
|
|
1997
2234
|
migrate(potentiallyOutdatedAssetFile) {
|
|
1998
|
-
|
|
2235
|
+
const migrated = applyMigrations(migrateAssetSchema.parse(potentiallyOutdatedAssetFile), assetMigrations, this.coreVersion);
|
|
2236
|
+
return assetFileSchema.parse(migrated);
|
|
1999
2237
|
}
|
|
2000
2238
|
};
|
|
2001
2239
|
|
|
@@ -2005,29 +2243,59 @@ var AssetService = class extends AbstractCrudService {
|
|
|
2005
2243
|
* Service that manages CRUD functionality for Collection files on disk
|
|
2006
2244
|
*/
|
|
2007
2245
|
var CollectionService = class extends AbstractCrudService {
|
|
2246
|
+
coreVersion;
|
|
2008
2247
|
jsonFileService;
|
|
2009
2248
|
gitService;
|
|
2010
|
-
|
|
2249
|
+
/** In-memory cache for collection indices, keyed by projectId */
|
|
2250
|
+
cachedIndex = /* @__PURE__ */ new Map();
|
|
2251
|
+
/** Promise deduplication for concurrent rebuilds, keyed by projectId */
|
|
2252
|
+
rebuildPromise = /* @__PURE__ */ new Map();
|
|
2253
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService) {
|
|
2011
2254
|
super(serviceTypeSchema.enum.Collection, options, logService);
|
|
2255
|
+
this.coreVersion = coreVersion;
|
|
2012
2256
|
this.jsonFileService = jsonFileService;
|
|
2013
2257
|
this.gitService = gitService;
|
|
2014
2258
|
}
|
|
2015
2259
|
/**
|
|
2260
|
+
* Resolves a UUID-or-slug string to a collection UUID.
|
|
2261
|
+
*
|
|
2262
|
+
* If the input matches UUID format, verifies the folder exists on disk first.
|
|
2263
|
+
* If the folder doesn't exist, falls back to slug lookup.
|
|
2264
|
+
* Otherwise, looks up via the index.
|
|
2265
|
+
*/
|
|
2266
|
+
async resolveCollectionId(props) {
|
|
2267
|
+
if (uuidSchema.safeParse(props.idOrSlug).success) {
|
|
2268
|
+
const collectionPath = pathTo.collection(props.projectId, props.idOrSlug);
|
|
2269
|
+
if (await Fs.pathExists(collectionPath)) return props.idOrSlug;
|
|
2270
|
+
}
|
|
2271
|
+
const index = await this.getIndex(props.projectId);
|
|
2272
|
+
for (const [uuid, slugValue] of Object.entries(index)) if (slugValue === props.idOrSlug) return uuid;
|
|
2273
|
+
this.cachedIndex.delete(props.projectId);
|
|
2274
|
+
const freshIndex = await this.getIndex(props.projectId);
|
|
2275
|
+
for (const [uuid, slugValue] of Object.entries(freshIndex)) if (slugValue === props.idOrSlug) return uuid;
|
|
2276
|
+
throw new Error(`Collection not found: "${props.idOrSlug}" does not match any collection UUID or slug`);
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2016
2279
|
* Creates a new Collection
|
|
2017
2280
|
*/
|
|
2018
2281
|
async create(props) {
|
|
2019
2282
|
createCollectionSchema.parse(props);
|
|
2283
|
+
this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
|
|
2020
2284
|
const id = uuid();
|
|
2021
2285
|
const projectPath = pathTo.project(props.projectId);
|
|
2022
2286
|
const collectionPath = pathTo.collection(props.projectId, id);
|
|
2023
2287
|
const collectionFilePath = pathTo.collectionFile(props.projectId, id);
|
|
2288
|
+
const slugPlural = slug(props.slug.plural);
|
|
2289
|
+
const index = await this.getIndex(props.projectId);
|
|
2290
|
+
if (Object.values(index).includes(slugPlural)) throw new Error(`Collection slug "${slugPlural}" is already in use by another collection`);
|
|
2024
2291
|
const collectionFile = {
|
|
2025
2292
|
...props,
|
|
2026
2293
|
objectType: "collection",
|
|
2027
2294
|
id,
|
|
2295
|
+
coreVersion: this.coreVersion,
|
|
2028
2296
|
slug: {
|
|
2029
2297
|
singular: slug(props.slug.singular),
|
|
2030
|
-
plural:
|
|
2298
|
+
plural: slugPlural
|
|
2031
2299
|
},
|
|
2032
2300
|
created: datetime(),
|
|
2033
2301
|
updated: null
|
|
@@ -2042,7 +2310,9 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2042
2310
|
id
|
|
2043
2311
|
}
|
|
2044
2312
|
});
|
|
2045
|
-
|
|
2313
|
+
index[id] = slugPlural;
|
|
2314
|
+
await this.writeIndex(props.projectId, index);
|
|
2315
|
+
return this.toCollection(collectionFile);
|
|
2046
2316
|
}
|
|
2047
2317
|
/**
|
|
2048
2318
|
* Returns a Collection by ID
|
|
@@ -2053,33 +2323,103 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2053
2323
|
readCollectionSchema.parse(props);
|
|
2054
2324
|
if (!props.commitHash) {
|
|
2055
2325
|
const collectionFile = await this.jsonFileService.read(pathTo.collectionFile(props.projectId, props.id), collectionFileSchema);
|
|
2056
|
-
return this.toCollection(
|
|
2326
|
+
return this.toCollection(collectionFile);
|
|
2057
2327
|
} else {
|
|
2058
2328
|
const collectionFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.collectionFile(props.projectId, props.id), props.commitHash)));
|
|
2059
|
-
return this.toCollection(
|
|
2329
|
+
return this.toCollection(collectionFile);
|
|
2060
2330
|
}
|
|
2061
2331
|
}
|
|
2062
2332
|
/**
|
|
2333
|
+
* Reads a Collection by its slug
|
|
2334
|
+
*/
|
|
2335
|
+
async readBySlug(props) {
|
|
2336
|
+
const id = await this.resolveCollectionId({
|
|
2337
|
+
projectId: props.projectId,
|
|
2338
|
+
idOrSlug: props.slug
|
|
2339
|
+
});
|
|
2340
|
+
return this.read({
|
|
2341
|
+
projectId: props.projectId,
|
|
2342
|
+
id,
|
|
2343
|
+
commitHash: props.commitHash
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
/**
|
|
2347
|
+
* Returns the commit history of a Collection
|
|
2348
|
+
*/
|
|
2349
|
+
async history(props) {
|
|
2350
|
+
collectionHistorySchema.parse(props);
|
|
2351
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.collectionFile(props.projectId, props.id) });
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2063
2354
|
* Updates given Collection
|
|
2064
2355
|
*
|
|
2065
|
-
*
|
|
2066
|
-
*
|
|
2067
|
-
* @param projectId Project ID of the collection to update
|
|
2068
|
-
* @param collection Collection to write to disk
|
|
2069
|
-
* @returns An object containing information about the actions needed to be taken,
|
|
2070
|
-
* before given update can be executed or void if the update was executed successfully
|
|
2356
|
+
* Handles fieldDefinition slug rename cascade and collection slug uniqueness.
|
|
2071
2357
|
*/
|
|
2072
2358
|
async update(props) {
|
|
2073
2359
|
updateCollectionSchema.parse(props);
|
|
2360
|
+
this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
|
|
2074
2361
|
const projectPath = pathTo.project(props.projectId);
|
|
2075
2362
|
const collectionFilePath = pathTo.collectionFile(props.projectId, props.id);
|
|
2363
|
+
const prevCollectionFile = await this.read(props);
|
|
2076
2364
|
const collectionFile = {
|
|
2077
|
-
...
|
|
2365
|
+
...prevCollectionFile,
|
|
2078
2366
|
...props,
|
|
2079
2367
|
updated: datetime()
|
|
2080
2368
|
};
|
|
2369
|
+
const oldFieldDefs = prevCollectionFile.fieldDefinitions;
|
|
2370
|
+
const newFieldDefs = props.fieldDefinitions;
|
|
2371
|
+
const slugRenames = [];
|
|
2372
|
+
const oldByUuid = new Map(oldFieldDefs.map((fd) => [fd.id, fd]));
|
|
2373
|
+
for (const newFd of newFieldDefs) {
|
|
2374
|
+
const oldFd = oldByUuid.get(newFd.id);
|
|
2375
|
+
if (oldFd && oldFd.slug !== newFd.slug) slugRenames.push({
|
|
2376
|
+
oldSlug: oldFd.slug,
|
|
2377
|
+
newSlug: newFd.slug
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
const filesToGitAdd = [collectionFilePath];
|
|
2381
|
+
if (slugRenames.length > 0) {
|
|
2382
|
+
const entriesPath = pathTo.entries(props.projectId, props.id);
|
|
2383
|
+
if (await Fs.pathExists(entriesPath)) {
|
|
2384
|
+
const entryFiles = (await Fs.readdir(entriesPath)).filter((f) => f.endsWith(".json") && f !== "collection.json");
|
|
2385
|
+
for (const entryFileName of entryFiles) {
|
|
2386
|
+
const entryFilePath = pathTo.entryFile(props.projectId, props.id, entryFileName.replace(".json", ""));
|
|
2387
|
+
try {
|
|
2388
|
+
const entryFile = await this.jsonFileService.read(entryFilePath, entryFileSchema);
|
|
2389
|
+
let changed = false;
|
|
2390
|
+
const newValues = { ...entryFile.values };
|
|
2391
|
+
for (const { oldSlug, newSlug } of slugRenames) if (oldSlug in newValues) {
|
|
2392
|
+
newValues[newSlug] = newValues[oldSlug];
|
|
2393
|
+
delete newValues[oldSlug];
|
|
2394
|
+
changed = true;
|
|
2395
|
+
}
|
|
2396
|
+
if (changed) {
|
|
2397
|
+
const updatedEntryFile = {
|
|
2398
|
+
...entryFile,
|
|
2399
|
+
values: newValues
|
|
2400
|
+
};
|
|
2401
|
+
await this.jsonFileService.update(updatedEntryFile, entryFilePath, entryFileSchema);
|
|
2402
|
+
filesToGitAdd.push(entryFilePath);
|
|
2403
|
+
}
|
|
2404
|
+
} catch (error) {
|
|
2405
|
+
this.logService.warn({
|
|
2406
|
+
source: "core",
|
|
2407
|
+
message: `Failed to update entry "${entryFileName}" during slug rename cascade: ${error instanceof Error ? error.message : String(error)}`
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
const newSlugPlural = slug(props.slug.plural);
|
|
2414
|
+
if (prevCollectionFile.slug.plural !== newSlugPlural) {
|
|
2415
|
+
const index = await this.getIndex(props.projectId);
|
|
2416
|
+
const existingUuid = Object.entries(index).find(([, s]) => s === newSlugPlural);
|
|
2417
|
+
if (existingUuid && existingUuid[0] !== props.id) throw new Error(`Collection slug "${newSlugPlural}" is already in use by another collection`);
|
|
2418
|
+
index[props.id] = newSlugPlural;
|
|
2419
|
+
await this.writeIndex(props.projectId, index);
|
|
2420
|
+
}
|
|
2081
2421
|
await this.jsonFileService.update(collectionFile, collectionFilePath, collectionFileSchema);
|
|
2082
|
-
await this.gitService.add(projectPath,
|
|
2422
|
+
await this.gitService.add(projectPath, filesToGitAdd);
|
|
2083
2423
|
await this.gitService.commit(projectPath, {
|
|
2084
2424
|
method: "update",
|
|
2085
2425
|
reference: {
|
|
@@ -2087,10 +2427,10 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2087
2427
|
id: collectionFile.id
|
|
2088
2428
|
}
|
|
2089
2429
|
});
|
|
2090
|
-
return this.toCollection(
|
|
2430
|
+
return this.toCollection(collectionFile);
|
|
2091
2431
|
}
|
|
2092
2432
|
/**
|
|
2093
|
-
* Deletes given Collection (folder), including it's
|
|
2433
|
+
* Deletes given Collection (folder), including it's Entries
|
|
2094
2434
|
*
|
|
2095
2435
|
* The Fields that Collection used are not deleted.
|
|
2096
2436
|
*/
|
|
@@ -2107,6 +2447,9 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2107
2447
|
id: props.id
|
|
2108
2448
|
}
|
|
2109
2449
|
});
|
|
2450
|
+
const index = await this.getIndex(props.projectId);
|
|
2451
|
+
delete index[props.id];
|
|
2452
|
+
await this.writeIndex(props.projectId, index);
|
|
2110
2453
|
}
|
|
2111
2454
|
async list(props) {
|
|
2112
2455
|
listCollectionsSchema.parse(props);
|
|
@@ -2141,7 +2484,8 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2141
2484
|
* Migrates an potentially outdated Collection file to the current schema
|
|
2142
2485
|
*/
|
|
2143
2486
|
migrate(potentiallyOutdatedCollectionFile) {
|
|
2144
|
-
|
|
2487
|
+
const migrated = applyMigrations(migrateCollectionSchema.parse(potentiallyOutdatedCollectionFile), collectionMigrations, this.coreVersion);
|
|
2488
|
+
return collectionFileSchema.parse(migrated);
|
|
2145
2489
|
}
|
|
2146
2490
|
/**
|
|
2147
2491
|
* Creates an Collection from given CollectionFile
|
|
@@ -2149,26 +2493,83 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2149
2493
|
* @param projectId The project's ID
|
|
2150
2494
|
* @param collectionFile The CollectionFile to convert
|
|
2151
2495
|
*/
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
return {
|
|
2155
|
-
...collectionFile,
|
|
2156
|
-
history
|
|
2157
|
-
};
|
|
2496
|
+
toCollection(collectionFile) {
|
|
2497
|
+
return { ...collectionFile };
|
|
2158
2498
|
}
|
|
2159
|
-
|
|
2160
|
-
|
|
2499
|
+
/**
|
|
2500
|
+
* Gets the collection index, rebuilding from disk if not cached
|
|
2501
|
+
*/
|
|
2502
|
+
async getIndex(projectId) {
|
|
2503
|
+
const cached = this.cachedIndex.get(projectId);
|
|
2504
|
+
if (cached) return cached;
|
|
2505
|
+
const pending = this.rebuildPromise.get(projectId);
|
|
2506
|
+
if (pending) return pending;
|
|
2507
|
+
const promise = this.rebuildIndex(projectId);
|
|
2508
|
+
this.rebuildPromise.set(projectId, promise);
|
|
2509
|
+
const result = await promise;
|
|
2510
|
+
this.cachedIndex.set(projectId, result);
|
|
2511
|
+
this.rebuildPromise.delete(projectId);
|
|
2512
|
+
return result;
|
|
2513
|
+
}
|
|
2514
|
+
/**
|
|
2515
|
+
* Writes the index file atomically and updates cache
|
|
2516
|
+
*/
|
|
2517
|
+
async writeIndex(projectId, index) {
|
|
2518
|
+
const indexPath = pathTo.collectionIndex(projectId);
|
|
2519
|
+
await Fs.writeFile(indexPath, JSON.stringify(index, null, 2), { encoding: "utf8" });
|
|
2520
|
+
this.cachedIndex.set(projectId, index);
|
|
2521
|
+
}
|
|
2522
|
+
/**
|
|
2523
|
+
* Rebuilds the index by scanning all collection folders
|
|
2524
|
+
*/
|
|
2525
|
+
async rebuildIndex(projectId) {
|
|
2526
|
+
this.logService.info({
|
|
2527
|
+
source: "core",
|
|
2528
|
+
message: `Rebuilding Collection index for Project "${projectId}"`
|
|
2529
|
+
});
|
|
2530
|
+
const index = {};
|
|
2531
|
+
const collectionFolders = await folders(pathTo.collections(projectId));
|
|
2532
|
+
for (const folder of collectionFolders) {
|
|
2533
|
+
if (!uuidSchema.safeParse(folder.name).success) continue;
|
|
2534
|
+
try {
|
|
2535
|
+
const collectionFilePath = pathTo.collectionFile(projectId, folder.name);
|
|
2536
|
+
const collectionFile = await this.jsonFileService.read(collectionFilePath, collectionFileSchema);
|
|
2537
|
+
index[collectionFile.id] = collectionFile.slug.plural;
|
|
2538
|
+
} catch (error) {
|
|
2539
|
+
this.logService.warn({
|
|
2540
|
+
source: "core",
|
|
2541
|
+
message: `Skipping collection folder "${folder.name}" during index rebuild: ${error instanceof Error ? error.message : String(error)}`
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
await this.writeIndex(projectId, index);
|
|
2546
|
+
return index;
|
|
2547
|
+
}
|
|
2548
|
+
/**
|
|
2549
|
+
* Validates that no two fieldDefinitions share the same slug
|
|
2550
|
+
*/
|
|
2551
|
+
validateFieldDefinitionSlugUniqueness(fieldDefinitions) {
|
|
2552
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2553
|
+
for (const fd of fieldDefinitions) {
|
|
2554
|
+
if (seen.has(fd.slug)) throw new Error(`Duplicate fieldDefinition slug "${fd.slug}": each fieldDefinition within a collection must have a unique slug`);
|
|
2555
|
+
seen.add(fd.slug);
|
|
2556
|
+
}
|
|
2557
|
+
}
|
|
2558
|
+
};
|
|
2559
|
+
|
|
2161
2560
|
//#endregion
|
|
2162
2561
|
//#region src/service/EntryService.ts
|
|
2163
2562
|
/**
|
|
2164
2563
|
* Service that manages CRUD functionality for Entry files on disk
|
|
2165
2564
|
*/
|
|
2166
2565
|
var EntryService = class extends AbstractCrudService {
|
|
2566
|
+
coreVersion;
|
|
2167
2567
|
jsonFileService;
|
|
2168
2568
|
gitService;
|
|
2169
2569
|
collectionService;
|
|
2170
|
-
constructor(options, logService, jsonFileService, gitService, collectionService) {
|
|
2570
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService, collectionService) {
|
|
2171
2571
|
super(serviceTypeSchema.enum.Entry, options, logService);
|
|
2572
|
+
this.coreVersion = coreVersion;
|
|
2172
2573
|
this.jsonFileService = jsonFileService;
|
|
2173
2574
|
this.gitService = gitService;
|
|
2174
2575
|
this.collectionService = collectionService;
|
|
@@ -2188,11 +2589,12 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2188
2589
|
const entryFile = {
|
|
2189
2590
|
objectType: "entry",
|
|
2190
2591
|
id,
|
|
2592
|
+
coreVersion: this.coreVersion,
|
|
2191
2593
|
values: props.values,
|
|
2192
2594
|
created: datetime(),
|
|
2193
2595
|
updated: null
|
|
2194
2596
|
};
|
|
2195
|
-
const entry =
|
|
2597
|
+
const entry = this.toEntry(entryFile);
|
|
2196
2598
|
getCreateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
|
|
2197
2599
|
await this.jsonFileService.create(entryFile, entryFilePath, entryFileSchema);
|
|
2198
2600
|
await this.gitService.add(projectPath, [entryFilePath]);
|
|
@@ -2215,13 +2617,20 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2215
2617
|
readEntrySchema.parse(props);
|
|
2216
2618
|
if (!props.commitHash) {
|
|
2217
2619
|
const entryFile = await this.jsonFileService.read(pathTo.entryFile(props.projectId, props.collectionId, props.id), entryFileSchema);
|
|
2218
|
-
return this.toEntry(
|
|
2620
|
+
return this.toEntry(entryFile);
|
|
2219
2621
|
} else {
|
|
2220
2622
|
const entryFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.entryFile(props.projectId, props.collectionId, props.id), props.commitHash)));
|
|
2221
|
-
return this.toEntry(
|
|
2623
|
+
return this.toEntry(entryFile);
|
|
2222
2624
|
}
|
|
2223
2625
|
}
|
|
2224
2626
|
/**
|
|
2627
|
+
* Returns the commit history of an Entry
|
|
2628
|
+
*/
|
|
2629
|
+
async history(props) {
|
|
2630
|
+
entryHistorySchema.parse(props);
|
|
2631
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.entryFile(props.projectId, props.collectionId, props.id) });
|
|
2632
|
+
}
|
|
2633
|
+
/**
|
|
2225
2634
|
* Updates an Entry of given Collection with new Values and shared Values
|
|
2226
2635
|
*/
|
|
2227
2636
|
async update(props) {
|
|
@@ -2241,7 +2650,7 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2241
2650
|
values: props.values,
|
|
2242
2651
|
updated: datetime()
|
|
2243
2652
|
};
|
|
2244
|
-
const entry =
|
|
2653
|
+
const entry = this.toEntry(entryFile);
|
|
2245
2654
|
getUpdateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
|
|
2246
2655
|
await this.jsonFileService.update(entryFile, entryFilePath, entryFileSchema);
|
|
2247
2656
|
await this.gitService.add(projectPath, [entryFilePath]);
|
|
@@ -2307,17 +2716,14 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2307
2716
|
* Migrates an potentially outdated Entry file to the current schema
|
|
2308
2717
|
*/
|
|
2309
2718
|
migrate(potentiallyOutdatedEntryFile) {
|
|
2310
|
-
|
|
2719
|
+
const migrated = applyMigrations(migrateEntrySchema.parse(potentiallyOutdatedEntryFile), entryMigrations, this.coreVersion);
|
|
2720
|
+
return entryFileSchema.parse(migrated);
|
|
2311
2721
|
}
|
|
2312
2722
|
/**
|
|
2313
2723
|
* Creates an Entry from given EntryFile by resolving it's Values
|
|
2314
2724
|
*/
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
return {
|
|
2318
|
-
...entryFile,
|
|
2319
|
-
history
|
|
2320
|
-
};
|
|
2725
|
+
toEntry(entryFile) {
|
|
2726
|
+
return { ...entryFile };
|
|
2321
2727
|
}
|
|
2322
2728
|
};
|
|
2323
2729
|
|
|
@@ -2346,10 +2752,11 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2346
2752
|
id
|
|
2347
2753
|
];
|
|
2348
2754
|
if (props.hash) args = [...args, props.hash];
|
|
2755
|
+
const fullMessage = `${this.serializeTagMessage(props.message)}\n\n${this.tagMessageToTrailers(props.message).join("\n")}`;
|
|
2349
2756
|
args = [
|
|
2350
2757
|
...args,
|
|
2351
2758
|
"-m",
|
|
2352
|
-
|
|
2759
|
+
fullMessage
|
|
2353
2760
|
];
|
|
2354
2761
|
await this.git(props.path, args);
|
|
2355
2762
|
return await this.read({
|
|
@@ -2408,27 +2815,33 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2408
2815
|
async list(props) {
|
|
2409
2816
|
listGitTagsSchema.parse(props);
|
|
2410
2817
|
let args = ["tag", "--list"];
|
|
2818
|
+
const format = [
|
|
2819
|
+
"%(refname:short)",
|
|
2820
|
+
"%(trailers:key=Type,valueonly)",
|
|
2821
|
+
"%(trailers:key=Version,valueonly)",
|
|
2822
|
+
"%(trailers:key=Core-Version,valueonly)",
|
|
2823
|
+
"%(*authorname)",
|
|
2824
|
+
"%(*authoremail)",
|
|
2825
|
+
"%(*authordate:iso-strict)"
|
|
2826
|
+
].join("|");
|
|
2411
2827
|
args = [
|
|
2412
2828
|
...args,
|
|
2413
2829
|
"--sort=-*authordate",
|
|
2414
|
-
|
|
2830
|
+
`--format=${format}`
|
|
2415
2831
|
];
|
|
2416
|
-
const gitTags = (await this.git(props.path, args)).stdout.split("\n").filter((line) => {
|
|
2832
|
+
const gitTags = (await this.git(props.path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
|
|
2417
2833
|
return line.trim() !== "";
|
|
2418
2834
|
}).map((line) => {
|
|
2419
2835
|
const lineArray = line.split("|");
|
|
2420
|
-
if (lineArray[
|
|
2421
|
-
lineArray[3] = lineArray[3].slice(1, -1);
|
|
2422
|
-
lineArray[3] = lineArray[3].slice(0, -1);
|
|
2423
|
-
}
|
|
2836
|
+
if (lineArray[5]?.startsWith("<") && lineArray[5]?.endsWith(">")) lineArray[5] = lineArray[5].slice(1, -1);
|
|
2424
2837
|
return {
|
|
2425
2838
|
id: lineArray[0],
|
|
2426
|
-
message: lineArray[1],
|
|
2839
|
+
message: this.parseTagTrailers(lineArray[1]?.trim(), lineArray[2]?.trim(), lineArray[3]?.trim()),
|
|
2427
2840
|
author: {
|
|
2428
|
-
name: lineArray[
|
|
2429
|
-
email: lineArray[
|
|
2841
|
+
name: lineArray[4],
|
|
2842
|
+
email: lineArray[5]
|
|
2430
2843
|
},
|
|
2431
|
-
datetime: datetime(lineArray[
|
|
2844
|
+
datetime: datetime(lineArray[6])
|
|
2432
2845
|
};
|
|
2433
2846
|
}).filter(this.isGitTag.bind(this));
|
|
2434
2847
|
return {
|
|
@@ -2451,6 +2864,43 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2451
2864
|
return (await this.list({ path: props.path })).total;
|
|
2452
2865
|
}
|
|
2453
2866
|
/**
|
|
2867
|
+
* Serializes a GitTagMessage into a human-readable subject line
|
|
2868
|
+
*/
|
|
2869
|
+
serializeTagMessage(message) {
|
|
2870
|
+
return `${message.type.charAt(0).toUpperCase() + message.type.slice(1)} ${message.type === "upgrade" ? message.coreVersion : message.version}`;
|
|
2871
|
+
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Converts a GitTagMessage into git trailer lines
|
|
2874
|
+
*/
|
|
2875
|
+
tagMessageToTrailers(message) {
|
|
2876
|
+
const trailers = [`Type: ${message.type}`];
|
|
2877
|
+
if (message.type === "upgrade") trailers.push(`Core-Version: ${message.coreVersion}`);
|
|
2878
|
+
else trailers.push(`Version: ${message.version}`);
|
|
2879
|
+
return trailers;
|
|
2880
|
+
}
|
|
2881
|
+
/**
|
|
2882
|
+
* Parses git trailer values back into a GitTagMessage
|
|
2883
|
+
*/
|
|
2884
|
+
parseTagTrailers(type, version, coreVersion) {
|
|
2885
|
+
switch (type) {
|
|
2886
|
+
case "upgrade": return gitTagMessageSchema.parse({
|
|
2887
|
+
type,
|
|
2888
|
+
coreVersion
|
|
2889
|
+
});
|
|
2890
|
+
case "release":
|
|
2891
|
+
case "preview": return gitTagMessageSchema.parse({
|
|
2892
|
+
type,
|
|
2893
|
+
version
|
|
2894
|
+
});
|
|
2895
|
+
default:
|
|
2896
|
+
this.logService.warn({
|
|
2897
|
+
source: "core",
|
|
2898
|
+
message: `Tag with ID "${type}" has an invalid or missing Type trailer and will be ignored`
|
|
2899
|
+
});
|
|
2900
|
+
return null;
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
/**
|
|
2454
2904
|
* Type guard for GitTag
|
|
2455
2905
|
*
|
|
2456
2906
|
* @param obj The object to check
|
|
@@ -2740,9 +3190,16 @@ var GitService = class {
|
|
|
2740
3190
|
gitMessageSchema.parse(message);
|
|
2741
3191
|
const user = await this.userService.get();
|
|
2742
3192
|
if (!user) throw new NoCurrentUserError();
|
|
3193
|
+
const subject = `${message.method.charAt(0).toUpperCase() + message.method.slice(1)} ${message.reference.objectType} ${message.reference.id}`;
|
|
3194
|
+
const trailers = [
|
|
3195
|
+
`Method: ${message.method}`,
|
|
3196
|
+
`Object-Type: ${message.reference.objectType}`,
|
|
3197
|
+
`Object-Id: ${message.reference.id}`
|
|
3198
|
+
];
|
|
3199
|
+
if (message.reference.collectionId) trailers.push(`Collection-Id: ${message.reference.collectionId}`);
|
|
2743
3200
|
const args = [
|
|
2744
3201
|
"commit",
|
|
2745
|
-
`--message=${
|
|
3202
|
+
`--message=${`${subject}\n\n${trailers.join("\n")}`}`,
|
|
2746
3203
|
`--author=${user.name} <${user.email}>`
|
|
2747
3204
|
];
|
|
2748
3205
|
await this.git(path, args);
|
|
@@ -2762,33 +3219,52 @@ var GitService = class {
|
|
|
2762
3219
|
let args = ["log"];
|
|
2763
3220
|
if (options?.between?.from) args = [...args, `${options.between.from}..${options.between.to || "HEAD"}`];
|
|
2764
3221
|
if (options?.limit) args = [...args, `--max-count=${options.limit}`];
|
|
2765
|
-
|
|
3222
|
+
const format = [
|
|
3223
|
+
"%H",
|
|
3224
|
+
"%(trailers:key=Method,valueonly)",
|
|
3225
|
+
"%(trailers:key=Object-Type,valueonly)",
|
|
3226
|
+
"%(trailers:key=Object-Id,valueonly)",
|
|
3227
|
+
"%(trailers:key=Collection-Id,valueonly)",
|
|
3228
|
+
"%an",
|
|
3229
|
+
"%ae",
|
|
3230
|
+
"%aI",
|
|
3231
|
+
"%D"
|
|
3232
|
+
].join("|");
|
|
3233
|
+
args = [...args, `--format=${format}`];
|
|
2766
3234
|
if (options?.filePath) args = [
|
|
2767
3235
|
...args,
|
|
2768
3236
|
"--",
|
|
2769
3237
|
options.filePath
|
|
2770
3238
|
];
|
|
2771
|
-
const noEmptyLinesArr = (await this.git(path, args)).stdout.split("\n").filter((line) => {
|
|
3239
|
+
const noEmptyLinesArr = (await this.git(path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
|
|
2772
3240
|
return line.trim() !== "";
|
|
2773
3241
|
});
|
|
2774
3242
|
return (await Promise.all(noEmptyLinesArr.map(async (line) => {
|
|
2775
3243
|
const lineArray = line.split("|");
|
|
2776
|
-
const tagId = this.refNameToTagName(lineArray[
|
|
3244
|
+
const tagId = this.refNameToTagName(lineArray[8]?.trim() || "");
|
|
2777
3245
|
const tag = tagId ? await this.tags.read({
|
|
2778
3246
|
path,
|
|
2779
3247
|
id: tagId
|
|
2780
3248
|
}) : null;
|
|
3249
|
+
const collectionId = lineArray[4]?.trim();
|
|
2781
3250
|
return {
|
|
2782
3251
|
hash: lineArray[0],
|
|
2783
|
-
message:
|
|
3252
|
+
message: {
|
|
3253
|
+
method: lineArray[1]?.trim(),
|
|
3254
|
+
reference: {
|
|
3255
|
+
objectType: lineArray[2]?.trim(),
|
|
3256
|
+
id: lineArray[3]?.trim(),
|
|
3257
|
+
...collectionId ? { collectionId } : {}
|
|
3258
|
+
}
|
|
3259
|
+
},
|
|
2784
3260
|
author: {
|
|
2785
|
-
name: lineArray[
|
|
2786
|
-
email: lineArray[
|
|
3261
|
+
name: lineArray[5],
|
|
3262
|
+
email: lineArray[6]
|
|
2787
3263
|
},
|
|
2788
|
-
datetime: datetime(lineArray[
|
|
3264
|
+
datetime: datetime(lineArray[7]),
|
|
2789
3265
|
tag
|
|
2790
3266
|
};
|
|
2791
|
-
}))).filter(this.isGitCommit
|
|
3267
|
+
}))).filter((obj) => this.isGitCommit(obj));
|
|
2792
3268
|
}
|
|
2793
3269
|
/**
|
|
2794
3270
|
* Retrieves the content of a file at a specific commit
|
|
@@ -2802,6 +3278,35 @@ var GitService = class {
|
|
|
2802
3278
|
};
|
|
2803
3279
|
return (await this.git(path, args, { processCallback: setEncoding })).stdout;
|
|
2804
3280
|
}
|
|
3281
|
+
/**
|
|
3282
|
+
* Lists directory entries at a specific commit
|
|
3283
|
+
*
|
|
3284
|
+
* Useful for discovering what files/folders existed at a past commit,
|
|
3285
|
+
* e.g. to detect deleted collections when comparing branches.
|
|
3286
|
+
*
|
|
3287
|
+
* @see https://git-scm.com/docs/git-ls-tree
|
|
3288
|
+
*
|
|
3289
|
+
* @param path Path to the repository
|
|
3290
|
+
* @param treePath Relative path within the repository to list
|
|
3291
|
+
* @param commitRef Commit hash, branch name, or other git ref
|
|
3292
|
+
*/
|
|
3293
|
+
async listTreeAtCommit(path, treePath, commitRef) {
|
|
3294
|
+
const args = [
|
|
3295
|
+
"ls-tree",
|
|
3296
|
+
"--name-only",
|
|
3297
|
+
commitRef,
|
|
3298
|
+
`${treePath.replace(`${path}${Path.sep}`, "").split("\\").join("/")}/`
|
|
3299
|
+
];
|
|
3300
|
+
try {
|
|
3301
|
+
return (await this.git(path, args)).stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "").map((entry) => {
|
|
3302
|
+
const parts = entry.split("/");
|
|
3303
|
+
return parts[parts.length - 1] || entry;
|
|
3304
|
+
});
|
|
3305
|
+
} catch (error) {
|
|
3306
|
+
if (error instanceof GitError) return [];
|
|
3307
|
+
throw error;
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
2805
3310
|
refNameToTagName(refName) {
|
|
2806
3311
|
const tagName = refName.replace("tag: ", "").trim();
|
|
2807
3312
|
if (tagName === "" || uuidSchema.safeParse(tagName).success === false) return null;
|
|
@@ -3170,7 +3675,6 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3170
3675
|
created: datetime(),
|
|
3171
3676
|
updated: null,
|
|
3172
3677
|
coreVersion: this.coreVersion,
|
|
3173
|
-
status: "todo",
|
|
3174
3678
|
version: "0.0.1"
|
|
3175
3679
|
};
|
|
3176
3680
|
const projectPath = pathTo.project(id);
|
|
@@ -3224,11 +3728,23 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3224
3728
|
const projectFile = await this.jsonFileService.read(pathTo.projectFile(props.id), projectFileSchema);
|
|
3225
3729
|
return await this.toProject(projectFile);
|
|
3226
3730
|
} else {
|
|
3227
|
-
const projectFile = this.migrate(
|
|
3731
|
+
const projectFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.id), pathTo.projectFile(props.id), props.commitHash)));
|
|
3228
3732
|
return await this.toProject(projectFile);
|
|
3229
3733
|
}
|
|
3230
3734
|
}
|
|
3231
3735
|
/**
|
|
3736
|
+
* Returns the commit history of a Project
|
|
3737
|
+
*/
|
|
3738
|
+
async history(props) {
|
|
3739
|
+
projectHistorySchema.parse(props);
|
|
3740
|
+
const projectPath = pathTo.project(props.id);
|
|
3741
|
+
const fullHistory = await this.gitService.log(projectPath);
|
|
3742
|
+
return {
|
|
3743
|
+
history: await this.gitService.log(projectPath, { filePath: pathTo.projectFile(props.id) }),
|
|
3744
|
+
fullHistory
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
/**
|
|
3232
3748
|
* Updates given Project
|
|
3233
3749
|
*/
|
|
3234
3750
|
async update(props) {
|
|
@@ -3261,7 +3777,7 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3261
3777
|
const projectPath = pathTo.project(props.id);
|
|
3262
3778
|
const projectFilePath = pathTo.projectFile(props.id);
|
|
3263
3779
|
if (await this.gitService.branches.current(projectPath) !== projectBranchSchema.enum.work) await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
|
|
3264
|
-
const currentProjectFile =
|
|
3780
|
+
const currentProjectFile = await this.jsonFileService.unsafeRead(projectFilePath);
|
|
3265
3781
|
if (Semver.gt(currentProjectFile.coreVersion, this.coreVersion)) throw new ProjectUpgradeError(`The Projects Core version "${currentProjectFile.coreVersion}" is higher than the current Core version "${this.coreVersion}".`);
|
|
3266
3782
|
if (Semver.eq(currentProjectFile.coreVersion, this.coreVersion) && props.force !== true) throw new ProjectUpgradeError(`The Projects Core version "${currentProjectFile.coreVersion}" is already up to date.`);
|
|
3267
3783
|
const assetReferences = await this.listReferences("asset", props.id);
|
|
@@ -3298,7 +3814,10 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3298
3814
|
});
|
|
3299
3815
|
await this.gitService.tags.create({
|
|
3300
3816
|
path: projectPath,
|
|
3301
|
-
message:
|
|
3817
|
+
message: {
|
|
3818
|
+
type: "upgrade",
|
|
3819
|
+
coreVersion: migratedProjectFile.coreVersion
|
|
3820
|
+
}
|
|
3302
3821
|
});
|
|
3303
3822
|
await this.gitService.branches.delete(projectPath, upgradeBranchName, true);
|
|
3304
3823
|
this.logService.info({
|
|
@@ -3404,7 +3923,7 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3404
3923
|
return (await Promise.all(projectReferences.map(async (reference) => {
|
|
3405
3924
|
const json = await this.jsonFileService.unsafeRead(pathTo.projectFile(reference.id));
|
|
3406
3925
|
const projectFile = migrateProjectSchema.parse(json);
|
|
3407
|
-
if (projectFile.coreVersion !== this.coreVersion) return projectFile;
|
|
3926
|
+
if (projectFile.coreVersion !== this.coreVersion) return this.migrate(projectFile);
|
|
3408
3927
|
return null;
|
|
3409
3928
|
}))).filter(isNotEmpty);
|
|
3410
3929
|
}
|
|
@@ -3436,9 +3955,9 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3436
3955
|
/**
|
|
3437
3956
|
* Migrates an potentially outdated Project file to the current schema
|
|
3438
3957
|
*/
|
|
3439
|
-
migrate(
|
|
3440
|
-
|
|
3441
|
-
return projectFileSchema.parse(
|
|
3958
|
+
migrate(potentiallyOutdatedFile) {
|
|
3959
|
+
const migrated = applyMigrations(migrateProjectSchema.parse(potentiallyOutdatedFile), projectMigrations, this.coreVersion);
|
|
3960
|
+
return projectFileSchema.parse(migrated);
|
|
3442
3961
|
}
|
|
3443
3962
|
/**
|
|
3444
3963
|
* Creates a Project from given ProjectFile
|
|
@@ -3447,13 +3966,9 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3447
3966
|
const projectPath = pathTo.project(projectFile.id);
|
|
3448
3967
|
let remoteOriginUrl = null;
|
|
3449
3968
|
if (await this.gitService.remotes.hasOrigin(projectPath)) remoteOriginUrl = await this.gitService.remotes.getOriginUrl(projectPath);
|
|
3450
|
-
const fullHistory = await this.gitService.log(pathTo.project(projectFile.id));
|
|
3451
|
-
const history = await this.gitService.log(pathTo.project(projectFile.id), { filePath: pathTo.projectFile(projectFile.id) });
|
|
3452
3969
|
return {
|
|
3453
3970
|
...projectFile,
|
|
3454
|
-
remoteOriginUrl
|
|
3455
|
-
history,
|
|
3456
|
-
fullHistory
|
|
3971
|
+
remoteOriginUrl
|
|
3457
3972
|
};
|
|
3458
3973
|
}
|
|
3459
3974
|
/**
|
|
@@ -3483,7 +3998,8 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3483
3998
|
"!/.gitattributes",
|
|
3484
3999
|
"!/**/.gitkeep",
|
|
3485
4000
|
"",
|
|
3486
|
-
"# elek.io related ignores"
|
|
4001
|
+
"# elek.io related ignores",
|
|
4002
|
+
"collections/index.json"
|
|
3487
4003
|
].join(Os.EOL));
|
|
3488
4004
|
}
|
|
3489
4005
|
async upgradeObjectFile(projectId, objectType, reference, collectionId) {
|
|
@@ -3549,6 +4065,665 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3549
4065
|
}
|
|
3550
4066
|
};
|
|
3551
4067
|
|
|
4068
|
+
//#endregion
|
|
4069
|
+
//#region src/service/ReleaseService.ts
|
|
4070
|
+
/**
|
|
4071
|
+
* Service that manages Release functionality
|
|
4072
|
+
*
|
|
4073
|
+
* A release diffs the current `work` branch against the `production` branch
|
|
4074
|
+
* to determine what changed, computes a semver bump, and merges work into production.
|
|
4075
|
+
*/
|
|
4076
|
+
var ReleaseService = class extends AbstractCrudService {
|
|
4077
|
+
gitService;
|
|
4078
|
+
jsonFileService;
|
|
4079
|
+
projectService;
|
|
4080
|
+
constructor(options, logService, gitService, jsonFileService, projectService) {
|
|
4081
|
+
super(serviceTypeSchema.enum.Release, options, logService);
|
|
4082
|
+
this.gitService = gitService;
|
|
4083
|
+
this.jsonFileService = jsonFileService;
|
|
4084
|
+
this.projectService = projectService;
|
|
4085
|
+
}
|
|
4086
|
+
/**
|
|
4087
|
+
* Prepares a release by diffing the current `work` branch against `production`.
|
|
4088
|
+
*
|
|
4089
|
+
* Returns a read-only summary of all changes and the computed next version.
|
|
4090
|
+
* If there are no changes, the next version and bump will be null.
|
|
4091
|
+
*/
|
|
4092
|
+
async prepare(props) {
|
|
4093
|
+
prepareReleaseSchema.parse(props);
|
|
4094
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4095
|
+
const currentBranch = await this.gitService.branches.current(projectPath);
|
|
4096
|
+
if (currentBranch !== projectBranchSchema.enum.work) throw new Error(`Not on work branch (currently on "${currentBranch}")`);
|
|
4097
|
+
const project = await this.projectService.read({ id: props.projectId });
|
|
4098
|
+
const currentVersion = project.version;
|
|
4099
|
+
const productionRef = projectBranchSchema.enum.production;
|
|
4100
|
+
const productionProject = await this.getProjectAtRef(props.projectId, projectPath, productionRef);
|
|
4101
|
+
const projectDiff = this.diffProject(project, productionProject);
|
|
4102
|
+
const currentCollections = await this.getCollectionsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
|
|
4103
|
+
const productionCollections = await this.getCollectionsAtRef(props.projectId, projectPath, productionRef);
|
|
4104
|
+
const collectionDiff = this.diffCollections(currentCollections, productionCollections);
|
|
4105
|
+
const currentAssets = await this.getAssetsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
|
|
4106
|
+
const productionAssets = await this.getAssetsAtRef(props.projectId, projectPath, productionRef);
|
|
4107
|
+
const assetDiff = this.diffAssets(currentAssets, productionAssets);
|
|
4108
|
+
const allCollectionIds = new Set([...currentCollections.map((c) => c.id), ...productionCollections.map((c) => c.id)]);
|
|
4109
|
+
const entryDiff = await this.diffEntries(props.projectId, projectPath, allCollectionIds, productionRef);
|
|
4110
|
+
let finalBump = null;
|
|
4111
|
+
for (const bump of [
|
|
4112
|
+
projectDiff.bump,
|
|
4113
|
+
collectionDiff.bump,
|
|
4114
|
+
assetDiff.bump,
|
|
4115
|
+
entryDiff.bump
|
|
4116
|
+
]) if (bump) finalBump = finalBump ? this.higherBump(finalBump, bump) : bump;
|
|
4117
|
+
if (!finalBump) {
|
|
4118
|
+
if (await this.hasCommitsBetween(projectPath, productionRef, projectBranchSchema.enum.work)) finalBump = "patch";
|
|
4119
|
+
}
|
|
4120
|
+
const nextVersion = finalBump ? Semver.inc(currentVersion, finalBump) : null;
|
|
4121
|
+
return {
|
|
4122
|
+
project,
|
|
4123
|
+
bump: finalBump,
|
|
4124
|
+
currentVersion,
|
|
4125
|
+
nextVersion,
|
|
4126
|
+
projectChanges: projectDiff.projectChanges,
|
|
4127
|
+
collectionChanges: collectionDiff.collectionChanges,
|
|
4128
|
+
fieldChanges: collectionDiff.fieldChanges,
|
|
4129
|
+
assetChanges: assetDiff.assetChanges,
|
|
4130
|
+
entryChanges: entryDiff.entryChanges
|
|
4131
|
+
};
|
|
4132
|
+
}
|
|
4133
|
+
/**
|
|
4134
|
+
* Creates a release by:
|
|
4135
|
+
* 1. Recomputing the diff (stateless)
|
|
4136
|
+
* 2. Merging `work` into `production`
|
|
4137
|
+
* 3. Updating the project version on `production`
|
|
4138
|
+
* 4. Tagging on `production`
|
|
4139
|
+
* 5. Merging `production` back into `work` (fast-forward to sync the version commit)
|
|
4140
|
+
* 6. Switching back to `work`
|
|
4141
|
+
*/
|
|
4142
|
+
async create(props) {
|
|
4143
|
+
createReleaseSchema.parse(props);
|
|
4144
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4145
|
+
const projectFilePath = pathTo.projectFile(props.projectId);
|
|
4146
|
+
const diff = await this.prepare(props);
|
|
4147
|
+
if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a release: no changes detected since the last full release");
|
|
4148
|
+
const nextVersion = diff.nextVersion;
|
|
4149
|
+
try {
|
|
4150
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.production);
|
|
4151
|
+
await this.gitService.merge(projectPath, projectBranchSchema.enum.work);
|
|
4152
|
+
const updatedProjectFile = {
|
|
4153
|
+
...diff.project,
|
|
4154
|
+
version: nextVersion,
|
|
4155
|
+
updated: datetime()
|
|
4156
|
+
};
|
|
4157
|
+
await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
|
|
4158
|
+
await this.gitService.add(projectPath, [projectFilePath]);
|
|
4159
|
+
await this.gitService.commit(projectPath, {
|
|
4160
|
+
method: "release",
|
|
4161
|
+
reference: {
|
|
4162
|
+
objectType: "project",
|
|
4163
|
+
id: props.projectId
|
|
4164
|
+
}
|
|
4165
|
+
});
|
|
4166
|
+
await this.gitService.tags.create({
|
|
4167
|
+
path: projectPath,
|
|
4168
|
+
message: {
|
|
4169
|
+
type: "release",
|
|
4170
|
+
version: nextVersion
|
|
4171
|
+
}
|
|
4172
|
+
});
|
|
4173
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
|
|
4174
|
+
await this.gitService.merge(projectPath, projectBranchSchema.enum.production);
|
|
4175
|
+
} catch (error) {
|
|
4176
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
|
|
4177
|
+
throw error;
|
|
4178
|
+
}
|
|
4179
|
+
this.logService.info({
|
|
4180
|
+
source: "core",
|
|
4181
|
+
message: `Released version ${nextVersion} (${diff.bump} bump)`
|
|
4182
|
+
});
|
|
4183
|
+
return {
|
|
4184
|
+
version: nextVersion,
|
|
4185
|
+
diff
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
4188
|
+
/**
|
|
4189
|
+
* Creates a preview release by:
|
|
4190
|
+
* 1. Recomputing the diff (stateless)
|
|
4191
|
+
* 2. Computing the preview version (e.g. 1.1.0-preview.3)
|
|
4192
|
+
* 3. Updating the project version on `work`
|
|
4193
|
+
* 4. Tagging on `work` (no merge into production)
|
|
4194
|
+
*
|
|
4195
|
+
* Preview releases are snapshots of the current work state.
|
|
4196
|
+
* They don't promote to production — only full releases do.
|
|
4197
|
+
*/
|
|
4198
|
+
async createPreview(props) {
|
|
4199
|
+
createPreviewReleaseSchema.parse(props);
|
|
4200
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4201
|
+
const projectFilePath = pathTo.projectFile(props.projectId);
|
|
4202
|
+
const diff = await this.prepare(props);
|
|
4203
|
+
if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a preview release: no changes detected since the last full release");
|
|
4204
|
+
const previewNumber = await this.countPreviewsSinceLastRelease(projectPath, diff.nextVersion);
|
|
4205
|
+
const previewVersion = `${diff.nextVersion}-preview.${previewNumber + 1}`;
|
|
4206
|
+
try {
|
|
4207
|
+
const updatedProjectFile = {
|
|
4208
|
+
...diff.project,
|
|
4209
|
+
version: previewVersion,
|
|
4210
|
+
updated: datetime()
|
|
4211
|
+
};
|
|
4212
|
+
await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
|
|
4213
|
+
await this.gitService.add(projectPath, [projectFilePath]);
|
|
4214
|
+
await this.gitService.commit(projectPath, {
|
|
4215
|
+
method: "release",
|
|
4216
|
+
reference: {
|
|
4217
|
+
objectType: "project",
|
|
4218
|
+
id: props.projectId
|
|
4219
|
+
}
|
|
4220
|
+
});
|
|
4221
|
+
await this.gitService.tags.create({
|
|
4222
|
+
path: projectPath,
|
|
4223
|
+
message: {
|
|
4224
|
+
type: "preview",
|
|
4225
|
+
version: previewVersion
|
|
4226
|
+
}
|
|
4227
|
+
});
|
|
4228
|
+
} catch (error) {
|
|
4229
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
|
|
4230
|
+
throw error;
|
|
4231
|
+
}
|
|
4232
|
+
this.logService.info({
|
|
4233
|
+
source: "core",
|
|
4234
|
+
message: `Preview released version ${previewVersion} (${diff.bump} bump)`
|
|
4235
|
+
});
|
|
4236
|
+
return {
|
|
4237
|
+
version: previewVersion,
|
|
4238
|
+
diff
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
/**
|
|
4242
|
+
* Reads the project file as it exists at a given git ref
|
|
4243
|
+
*/
|
|
4244
|
+
async getProjectAtRef(projectId, projectPath, ref) {
|
|
4245
|
+
try {
|
|
4246
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, pathTo.projectFile(projectId), ref);
|
|
4247
|
+
return projectFileSchema.parse(JSON.parse(content));
|
|
4248
|
+
} catch {
|
|
4249
|
+
return null;
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
/**
|
|
4253
|
+
* Reads asset metadata files as they exist at a given git ref
|
|
4254
|
+
*/
|
|
4255
|
+
async getAssetsAtRef(projectId, projectPath, ref) {
|
|
4256
|
+
const assetsPath = pathTo.assets(projectId);
|
|
4257
|
+
const fileNames = await this.gitService.listTreeAtCommit(projectPath, assetsPath, ref);
|
|
4258
|
+
const assets = [];
|
|
4259
|
+
for (const fileName of fileNames) {
|
|
4260
|
+
if (!fileName.endsWith(".json")) continue;
|
|
4261
|
+
const assetId = fileName.replace(".json", "");
|
|
4262
|
+
const assetFilePath = pathTo.assetFile(projectId, assetId);
|
|
4263
|
+
try {
|
|
4264
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, assetFilePath, ref);
|
|
4265
|
+
const assetFile = assetFileSchema.parse(JSON.parse(content));
|
|
4266
|
+
assets.push(assetFile);
|
|
4267
|
+
} catch {
|
|
4268
|
+
this.logService.debug({
|
|
4269
|
+
source: "core",
|
|
4270
|
+
message: `Skipping asset "${fileName}" at ref "${ref}" during release diff`
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
return assets;
|
|
4275
|
+
}
|
|
4276
|
+
/**
|
|
4277
|
+
* Reads entry files for a single collection as they exist at a given git ref
|
|
4278
|
+
*/
|
|
4279
|
+
async getEntriesAtRef(projectId, projectPath, collectionId, ref) {
|
|
4280
|
+
const entriesPath = pathTo.entries(projectId, collectionId);
|
|
4281
|
+
const fileNames = await this.gitService.listTreeAtCommit(projectPath, entriesPath, ref);
|
|
4282
|
+
const entries = [];
|
|
4283
|
+
for (const fileName of fileNames) {
|
|
4284
|
+
if (!fileName.endsWith(".json") || fileName === "collection.json") continue;
|
|
4285
|
+
const entryId = fileName.replace(".json", "");
|
|
4286
|
+
const entryFilePath = pathTo.entryFile(projectId, collectionId, entryId);
|
|
4287
|
+
try {
|
|
4288
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, entryFilePath, ref);
|
|
4289
|
+
const entryFile = entryFileSchema.parse(JSON.parse(content));
|
|
4290
|
+
entries.push(entryFile);
|
|
4291
|
+
} catch {
|
|
4292
|
+
this.logService.debug({
|
|
4293
|
+
source: "core",
|
|
4294
|
+
message: `Skipping entry "${fileName}" in collection "${collectionId}" at ref "${ref}" during release diff`
|
|
4295
|
+
});
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4298
|
+
return entries;
|
|
4299
|
+
}
|
|
4300
|
+
/**
|
|
4301
|
+
* Reads collections as they exist at a given git ref (branch or commit)
|
|
4302
|
+
*/
|
|
4303
|
+
async getCollectionsAtRef(projectId, projectPath, ref) {
|
|
4304
|
+
const collectionsPath = pathTo.collections(projectId);
|
|
4305
|
+
const folderNames = await this.gitService.listTreeAtCommit(projectPath, collectionsPath, ref);
|
|
4306
|
+
const collections = [];
|
|
4307
|
+
for (const folderName of folderNames) {
|
|
4308
|
+
const collectionFilePath = pathTo.collectionFile(projectId, folderName);
|
|
4309
|
+
try {
|
|
4310
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, collectionFilePath, ref);
|
|
4311
|
+
const collectionFile = collectionFileSchema.parse(JSON.parse(content));
|
|
4312
|
+
collections.push(collectionFile);
|
|
4313
|
+
} catch {
|
|
4314
|
+
this.logService.debug({
|
|
4315
|
+
source: "core",
|
|
4316
|
+
message: `Skipping folder "${folderName}" at ref "${ref}" during release diff`
|
|
4317
|
+
});
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
return collections;
|
|
4321
|
+
}
|
|
4322
|
+
/**
|
|
4323
|
+
* Checks if there are any commits between two refs
|
|
4324
|
+
*/
|
|
4325
|
+
async hasCommitsBetween(projectPath, from, to) {
|
|
4326
|
+
try {
|
|
4327
|
+
return (await this.gitService.log(projectPath, { between: {
|
|
4328
|
+
from,
|
|
4329
|
+
to
|
|
4330
|
+
} })).length > 0;
|
|
4331
|
+
} catch {
|
|
4332
|
+
return true;
|
|
4333
|
+
}
|
|
4334
|
+
}
|
|
4335
|
+
/**
|
|
4336
|
+
* Diffs two sets of collections and returns all changes with the computed bump level.
|
|
4337
|
+
*
|
|
4338
|
+
* Always collects all changes so they can be displayed to the user.
|
|
4339
|
+
*/
|
|
4340
|
+
diffCollections(currentCollections, productionCollections) {
|
|
4341
|
+
const collectionChanges = [];
|
|
4342
|
+
const fieldChanges = [];
|
|
4343
|
+
let highestBump = null;
|
|
4344
|
+
const currentById = new Map(currentCollections.map((c) => [c.id, c]));
|
|
4345
|
+
const productionById = new Map(productionCollections.map((c) => [c.id, c]));
|
|
4346
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4347
|
+
collectionChanges.push({
|
|
4348
|
+
collectionId: id,
|
|
4349
|
+
changeType: "deleted",
|
|
4350
|
+
bump: "major"
|
|
4351
|
+
});
|
|
4352
|
+
highestBump = "major";
|
|
4353
|
+
}
|
|
4354
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4355
|
+
collectionChanges.push({
|
|
4356
|
+
collectionId: id,
|
|
4357
|
+
changeType: "added",
|
|
4358
|
+
bump: "minor"
|
|
4359
|
+
});
|
|
4360
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4361
|
+
}
|
|
4362
|
+
for (const [id, currentCollection] of currentById) {
|
|
4363
|
+
const productionCollection = productionById.get(id);
|
|
4364
|
+
if (!productionCollection) continue;
|
|
4365
|
+
const changes = this.diffFieldDefinitions(id, currentCollection.fieldDefinitions, productionCollection.fieldDefinitions);
|
|
4366
|
+
fieldChanges.push(...changes);
|
|
4367
|
+
for (const change of changes) highestBump = this.higherBump(highestBump, change.bump);
|
|
4368
|
+
}
|
|
4369
|
+
return {
|
|
4370
|
+
bump: highestBump,
|
|
4371
|
+
collectionChanges,
|
|
4372
|
+
fieldChanges
|
|
4373
|
+
};
|
|
4374
|
+
}
|
|
4375
|
+
/**
|
|
4376
|
+
* Diffs the project file between current and production.
|
|
4377
|
+
*
|
|
4378
|
+
* Skips immutable/system-managed fields (id, objectType, created, updated, version, coreVersion).
|
|
4379
|
+
*/
|
|
4380
|
+
diffProject(current, production) {
|
|
4381
|
+
const projectChanges = [];
|
|
4382
|
+
if (!production) return {
|
|
4383
|
+
bump: null,
|
|
4384
|
+
projectChanges
|
|
4385
|
+
};
|
|
4386
|
+
let highestBump = null;
|
|
4387
|
+
if (current.settings.language.default !== production.settings.language.default) {
|
|
4388
|
+
projectChanges.push({
|
|
4389
|
+
changeType: "defaultLanguageChanged",
|
|
4390
|
+
bump: "major"
|
|
4391
|
+
});
|
|
4392
|
+
highestBump = "major";
|
|
4393
|
+
}
|
|
4394
|
+
const currentSupported = new Set(current.settings.language.supported);
|
|
4395
|
+
const productionSupported = new Set(production.settings.language.supported);
|
|
4396
|
+
for (const lang of productionSupported) if (!currentSupported.has(lang)) {
|
|
4397
|
+
projectChanges.push({
|
|
4398
|
+
changeType: "supportedLanguageRemoved",
|
|
4399
|
+
bump: "major"
|
|
4400
|
+
});
|
|
4401
|
+
highestBump = "major";
|
|
4402
|
+
break;
|
|
4403
|
+
}
|
|
4404
|
+
for (const lang of currentSupported) if (!productionSupported.has(lang)) {
|
|
4405
|
+
projectChanges.push({
|
|
4406
|
+
changeType: "supportedLanguageAdded",
|
|
4407
|
+
bump: "minor"
|
|
4408
|
+
});
|
|
4409
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4410
|
+
break;
|
|
4411
|
+
}
|
|
4412
|
+
if (current.name !== production.name) {
|
|
4413
|
+
projectChanges.push({
|
|
4414
|
+
changeType: "nameChanged",
|
|
4415
|
+
bump: "patch"
|
|
4416
|
+
});
|
|
4417
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4418
|
+
}
|
|
4419
|
+
if (current.description !== production.description) {
|
|
4420
|
+
projectChanges.push({
|
|
4421
|
+
changeType: "descriptionChanged",
|
|
4422
|
+
bump: "patch"
|
|
4423
|
+
});
|
|
4424
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4425
|
+
}
|
|
4426
|
+
return {
|
|
4427
|
+
bump: highestBump,
|
|
4428
|
+
projectChanges
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4431
|
+
/**
|
|
4432
|
+
* Diffs two sets of assets and returns all changes with the computed bump level.
|
|
4433
|
+
*/
|
|
4434
|
+
diffAssets(currentAssets, productionAssets) {
|
|
4435
|
+
const assetChanges = [];
|
|
4436
|
+
let highestBump = null;
|
|
4437
|
+
const currentById = new Map(currentAssets.map((a) => [a.id, a]));
|
|
4438
|
+
const productionById = new Map(productionAssets.map((a) => [a.id, a]));
|
|
4439
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4440
|
+
assetChanges.push({
|
|
4441
|
+
assetId: id,
|
|
4442
|
+
changeType: "deleted",
|
|
4443
|
+
bump: "major"
|
|
4444
|
+
});
|
|
4445
|
+
highestBump = "major";
|
|
4446
|
+
}
|
|
4447
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4448
|
+
assetChanges.push({
|
|
4449
|
+
assetId: id,
|
|
4450
|
+
changeType: "added",
|
|
4451
|
+
bump: "minor"
|
|
4452
|
+
});
|
|
4453
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4454
|
+
}
|
|
4455
|
+
for (const [id, current] of currentById) {
|
|
4456
|
+
const production = productionById.get(id);
|
|
4457
|
+
if (!production) continue;
|
|
4458
|
+
if (current.extension !== production.extension || current.mimeType !== production.mimeType || current.size !== production.size) {
|
|
4459
|
+
assetChanges.push({
|
|
4460
|
+
assetId: id,
|
|
4461
|
+
changeType: "binaryChanged",
|
|
4462
|
+
bump: "patch"
|
|
4463
|
+
});
|
|
4464
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4465
|
+
}
|
|
4466
|
+
if (current.name !== production.name || current.description !== production.description) {
|
|
4467
|
+
assetChanges.push({
|
|
4468
|
+
assetId: id,
|
|
4469
|
+
changeType: "metadataChanged",
|
|
4470
|
+
bump: "patch"
|
|
4471
|
+
});
|
|
4472
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4473
|
+
}
|
|
4474
|
+
}
|
|
4475
|
+
return {
|
|
4476
|
+
bump: highestBump,
|
|
4477
|
+
assetChanges
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4480
|
+
/**
|
|
4481
|
+
* Diffs entries across all collections between current and production.
|
|
4482
|
+
*/
|
|
4483
|
+
async diffEntries(projectId, projectPath, allCollectionIds, productionRef) {
|
|
4484
|
+
const entryChanges = [];
|
|
4485
|
+
let highestBump = null;
|
|
4486
|
+
for (const collectionId of allCollectionIds) {
|
|
4487
|
+
const currentEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, projectBranchSchema.enum.work);
|
|
4488
|
+
const productionEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, productionRef);
|
|
4489
|
+
const currentById = new Map(currentEntries.map((e) => [e.id, e]));
|
|
4490
|
+
const productionById = new Map(productionEntries.map((e) => [e.id, e]));
|
|
4491
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4492
|
+
entryChanges.push({
|
|
4493
|
+
collectionId,
|
|
4494
|
+
entryId: id,
|
|
4495
|
+
changeType: "deleted",
|
|
4496
|
+
bump: "major"
|
|
4497
|
+
});
|
|
4498
|
+
highestBump = "major";
|
|
4499
|
+
}
|
|
4500
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4501
|
+
entryChanges.push({
|
|
4502
|
+
collectionId,
|
|
4503
|
+
entryId: id,
|
|
4504
|
+
changeType: "added",
|
|
4505
|
+
bump: "minor"
|
|
4506
|
+
});
|
|
4507
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4508
|
+
}
|
|
4509
|
+
for (const [id, current] of currentById) {
|
|
4510
|
+
const production = productionById.get(id);
|
|
4511
|
+
if (!production) continue;
|
|
4512
|
+
if (!isDeepStrictEqual(current.values, production.values)) {
|
|
4513
|
+
entryChanges.push({
|
|
4514
|
+
collectionId,
|
|
4515
|
+
entryId: id,
|
|
4516
|
+
changeType: "modified",
|
|
4517
|
+
bump: "patch"
|
|
4518
|
+
});
|
|
4519
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
}
|
|
4523
|
+
return {
|
|
4524
|
+
bump: highestBump,
|
|
4525
|
+
entryChanges
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
/**
|
|
4529
|
+
* Diffs field definitions of a single collection.
|
|
4530
|
+
*
|
|
4531
|
+
* Matches fields by UUID and classifies each change.
|
|
4532
|
+
* Always collects all changes so they can be displayed to the user.
|
|
4533
|
+
*/
|
|
4534
|
+
diffFieldDefinitions(collectionId, currentFields, productionFields) {
|
|
4535
|
+
const changes = [];
|
|
4536
|
+
const currentById = new Map(currentFields.map((f) => [f.id, f]));
|
|
4537
|
+
const productionById = new Map(productionFields.map((f) => [f.id, f]));
|
|
4538
|
+
for (const [id, field] of productionById) if (!currentById.has(id)) changes.push({
|
|
4539
|
+
collectionId,
|
|
4540
|
+
fieldId: id,
|
|
4541
|
+
fieldSlug: field.slug,
|
|
4542
|
+
changeType: "deleted",
|
|
4543
|
+
bump: "major"
|
|
4544
|
+
});
|
|
4545
|
+
for (const [id, field] of currentById) if (!productionById.has(id)) changes.push({
|
|
4546
|
+
collectionId,
|
|
4547
|
+
fieldId: id,
|
|
4548
|
+
fieldSlug: field.slug,
|
|
4549
|
+
changeType: "added",
|
|
4550
|
+
bump: "minor"
|
|
4551
|
+
});
|
|
4552
|
+
for (const [id, currentField] of currentById) {
|
|
4553
|
+
const productionField = productionById.get(id);
|
|
4554
|
+
if (!productionField) continue;
|
|
4555
|
+
const fieldChanges = this.diffSingleField(collectionId, currentField, productionField);
|
|
4556
|
+
changes.push(...fieldChanges);
|
|
4557
|
+
}
|
|
4558
|
+
return changes;
|
|
4559
|
+
}
|
|
4560
|
+
/**
|
|
4561
|
+
* Compares two versions of the same field definition and returns all detected changes.
|
|
4562
|
+
*
|
|
4563
|
+
* Collects every change on the field so the full diff can be shown to the user.
|
|
4564
|
+
*/
|
|
4565
|
+
diffSingleField(collectionId, current, production) {
|
|
4566
|
+
const changes = [];
|
|
4567
|
+
const base = {
|
|
4568
|
+
collectionId,
|
|
4569
|
+
fieldId: current.id,
|
|
4570
|
+
fieldSlug: current.slug
|
|
4571
|
+
};
|
|
4572
|
+
if (current.valueType !== production.valueType) changes.push({
|
|
4573
|
+
...base,
|
|
4574
|
+
changeType: "valueTypeChanged",
|
|
4575
|
+
bump: "major"
|
|
4576
|
+
});
|
|
4577
|
+
if (current.fieldType !== production.fieldType) changes.push({
|
|
4578
|
+
...base,
|
|
4579
|
+
changeType: "fieldTypeChanged",
|
|
4580
|
+
bump: "major"
|
|
4581
|
+
});
|
|
4582
|
+
if (current.slug !== production.slug) changes.push({
|
|
4583
|
+
...base,
|
|
4584
|
+
changeType: "slugChanged",
|
|
4585
|
+
bump: "major"
|
|
4586
|
+
});
|
|
4587
|
+
if (this.isMinMaxTightened(current, production)) changes.push({
|
|
4588
|
+
...base,
|
|
4589
|
+
changeType: "minMaxTightened",
|
|
4590
|
+
bump: "major"
|
|
4591
|
+
});
|
|
4592
|
+
if (production.isRequired === true && current.isRequired === false) changes.push({
|
|
4593
|
+
...base,
|
|
4594
|
+
changeType: "isRequiredToNotRequired",
|
|
4595
|
+
bump: "major"
|
|
4596
|
+
});
|
|
4597
|
+
if (production.isUnique === true && current.isUnique === false) changes.push({
|
|
4598
|
+
...base,
|
|
4599
|
+
changeType: "isUniqueToNotUnique",
|
|
4600
|
+
bump: "major"
|
|
4601
|
+
});
|
|
4602
|
+
if (current.fieldType === "entry" && production.fieldType === "entry") {
|
|
4603
|
+
if (!isDeepStrictEqual([...current.ofCollections].sort(), [...production.ofCollections].sort())) changes.push({
|
|
4604
|
+
...base,
|
|
4605
|
+
changeType: "ofCollectionsChanged",
|
|
4606
|
+
bump: "major"
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
if (production.isRequired === false && current.isRequired === true) changes.push({
|
|
4610
|
+
...base,
|
|
4611
|
+
changeType: "isNotRequiredToRequired",
|
|
4612
|
+
bump: "minor"
|
|
4613
|
+
});
|
|
4614
|
+
if (production.isUnique === false && current.isUnique === true) changes.push({
|
|
4615
|
+
...base,
|
|
4616
|
+
changeType: "isNotUniqueToUnique",
|
|
4617
|
+
bump: "minor"
|
|
4618
|
+
});
|
|
4619
|
+
if (this.isMinMaxLoosened(current, production)) changes.push({
|
|
4620
|
+
...base,
|
|
4621
|
+
changeType: "minMaxLoosened",
|
|
4622
|
+
bump: "patch"
|
|
4623
|
+
});
|
|
4624
|
+
if (!isDeepStrictEqual(current.label, production.label)) changes.push({
|
|
4625
|
+
...base,
|
|
4626
|
+
changeType: "labelChanged",
|
|
4627
|
+
bump: "patch"
|
|
4628
|
+
});
|
|
4629
|
+
if (!isDeepStrictEqual(current.description, production.description)) changes.push({
|
|
4630
|
+
...base,
|
|
4631
|
+
changeType: "descriptionChanged",
|
|
4632
|
+
bump: "patch"
|
|
4633
|
+
});
|
|
4634
|
+
if ("defaultValue" in current && "defaultValue" in production && !isDeepStrictEqual(current.defaultValue, production.defaultValue)) changes.push({
|
|
4635
|
+
...base,
|
|
4636
|
+
changeType: "defaultValueChanged",
|
|
4637
|
+
bump: "patch"
|
|
4638
|
+
});
|
|
4639
|
+
if (current.inputWidth !== production.inputWidth) changes.push({
|
|
4640
|
+
...base,
|
|
4641
|
+
changeType: "inputWidthChanged",
|
|
4642
|
+
bump: "patch"
|
|
4643
|
+
});
|
|
4644
|
+
if (current.isDisabled !== production.isDisabled) changes.push({
|
|
4645
|
+
...base,
|
|
4646
|
+
changeType: "isDisabledChanged",
|
|
4647
|
+
bump: "patch"
|
|
4648
|
+
});
|
|
4649
|
+
return changes;
|
|
4650
|
+
}
|
|
4651
|
+
/**
|
|
4652
|
+
* Checks if min/max constraints have been tightened.
|
|
4653
|
+
*
|
|
4654
|
+
* Tightening means: new min > old min, or new max < old max.
|
|
4655
|
+
* A null value means no constraint (unbounded).
|
|
4656
|
+
*/
|
|
4657
|
+
isMinMaxTightened(current, production) {
|
|
4658
|
+
const currentMin = this.getMinMax(current, "min");
|
|
4659
|
+
const productionMin = this.getMinMax(production, "min");
|
|
4660
|
+
const currentMax = this.getMinMax(current, "max");
|
|
4661
|
+
const productionMax = this.getMinMax(production, "max");
|
|
4662
|
+
if (currentMin !== null && productionMin === null) return true;
|
|
4663
|
+
if (currentMin !== null && productionMin !== null && currentMin > productionMin) return true;
|
|
4664
|
+
if (currentMax !== null && productionMax === null) return true;
|
|
4665
|
+
if (currentMax !== null && productionMax !== null && currentMax < productionMax) return true;
|
|
4666
|
+
return false;
|
|
4667
|
+
}
|
|
4668
|
+
/**
|
|
4669
|
+
* Checks if min/max constraints have been loosened.
|
|
4670
|
+
*
|
|
4671
|
+
* Loosening means: new min < old min, or new max > old max.
|
|
4672
|
+
*/
|
|
4673
|
+
isMinMaxLoosened(current, production) {
|
|
4674
|
+
const currentMin = this.getMinMax(current, "min");
|
|
4675
|
+
const productionMin = this.getMinMax(production, "min");
|
|
4676
|
+
const currentMax = this.getMinMax(current, "max");
|
|
4677
|
+
const productionMax = this.getMinMax(production, "max");
|
|
4678
|
+
if (currentMin === null && productionMin !== null) return true;
|
|
4679
|
+
if (currentMin !== null && productionMin !== null && currentMin < productionMin) return true;
|
|
4680
|
+
if (currentMax === null && productionMax !== null) return true;
|
|
4681
|
+
if (currentMax !== null && productionMax !== null && currentMax > productionMax) return true;
|
|
4682
|
+
return false;
|
|
4683
|
+
}
|
|
4684
|
+
/**
|
|
4685
|
+
* Safely extracts min or max from a field definition (not all types have it)
|
|
4686
|
+
*/
|
|
4687
|
+
getMinMax(field, prop) {
|
|
4688
|
+
switch (field.fieldType) {
|
|
4689
|
+
case "text":
|
|
4690
|
+
case "textarea":
|
|
4691
|
+
case "number":
|
|
4692
|
+
case "range":
|
|
4693
|
+
case "asset":
|
|
4694
|
+
case "entry": return field[prop];
|
|
4695
|
+
default: return null;
|
|
4696
|
+
}
|
|
4697
|
+
}
|
|
4698
|
+
/**
|
|
4699
|
+
* Counts existing preview tags for a given base version since the last full release.
|
|
4700
|
+
*/
|
|
4701
|
+
async countPreviewsSinceLastRelease(projectPath, baseVersion) {
|
|
4702
|
+
const tags = await this.gitService.tags.list({ path: projectPath });
|
|
4703
|
+
let count = 0;
|
|
4704
|
+
for (const tag of tags.list) {
|
|
4705
|
+
if (tag.message.type === "upgrade") continue;
|
|
4706
|
+
if (tag.message.type === "release") break;
|
|
4707
|
+
if (tag.message.type === "preview") {
|
|
4708
|
+
if (tag.message.version.split("-")[0] === baseVersion) count++;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
return count;
|
|
4712
|
+
}
|
|
4713
|
+
/**
|
|
4714
|
+
* Returns the higher of two bumps (major > minor > patch)
|
|
4715
|
+
*/
|
|
4716
|
+
higherBump(a, b) {
|
|
4717
|
+
const order = {
|
|
4718
|
+
patch: 0,
|
|
4719
|
+
minor: 1,
|
|
4720
|
+
major: 2
|
|
4721
|
+
};
|
|
4722
|
+
if (a === null) return b;
|
|
4723
|
+
return order[a] >= order[b] ? a : b;
|
|
4724
|
+
}
|
|
4725
|
+
};
|
|
4726
|
+
|
|
3552
4727
|
//#endregion
|
|
3553
4728
|
//#region src/service/UserService.ts
|
|
3554
4729
|
/**
|
|
@@ -3584,7 +4759,7 @@ var UserService = class {
|
|
|
3584
4759
|
setUserSchema.parse(props);
|
|
3585
4760
|
const userFilePath = pathTo.userFile;
|
|
3586
4761
|
const userFile = { ...props };
|
|
3587
|
-
if (userFile.userType ===
|
|
4762
|
+
if (userFile.userType === userTypeSchema.enum.cloud) {}
|
|
3588
4763
|
await this.jsonFileService.update(userFile, userFilePath, userFileSchema);
|
|
3589
4764
|
this.logService.debug({
|
|
3590
4765
|
source: "core",
|
|
@@ -3612,6 +4787,7 @@ var ElekIoCore = class {
|
|
|
3612
4787
|
projectService;
|
|
3613
4788
|
collectionService;
|
|
3614
4789
|
entryService;
|
|
4790
|
+
releaseService;
|
|
3615
4791
|
localApi;
|
|
3616
4792
|
constructor(props) {
|
|
3617
4793
|
this.coreVersion = package_default.version;
|
|
@@ -3624,10 +4800,11 @@ var ElekIoCore = class {
|
|
|
3624
4800
|
this.jsonFileService = new JsonFileService(this.options, this.logService);
|
|
3625
4801
|
this.userService = new UserService(this.logService, this.jsonFileService);
|
|
3626
4802
|
this.gitService = new GitService(this.options, this.logService, this.userService);
|
|
3627
|
-
this.assetService = new AssetService(this.options, this.logService, this.jsonFileService, this.gitService);
|
|
3628
|
-
this.collectionService = new CollectionService(this.options, this.logService, this.jsonFileService, this.gitService);
|
|
3629
|
-
this.entryService = new EntryService(this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
|
|
4803
|
+
this.assetService = new AssetService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
|
|
4804
|
+
this.collectionService = new CollectionService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
|
|
4805
|
+
this.entryService = new EntryService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
|
|
3630
4806
|
this.projectService = new ProjectService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.assetService, this.collectionService, this.entryService);
|
|
4807
|
+
this.releaseService = new ReleaseService(this.options, this.logService, this.gitService, this.jsonFileService, this.projectService);
|
|
3631
4808
|
this.localApi = new LocalApi(this.logService, this.projectService, this.collectionService, this.entryService, this.assetService);
|
|
3632
4809
|
this.logService.info({
|
|
3633
4810
|
source: "core",
|
|
@@ -3687,6 +4864,12 @@ var ElekIoCore = class {
|
|
|
3687
4864
|
return this.entryService;
|
|
3688
4865
|
}
|
|
3689
4866
|
/**
|
|
4867
|
+
* Prepare and create releases
|
|
4868
|
+
*/
|
|
4869
|
+
get releases() {
|
|
4870
|
+
return this.releaseService;
|
|
4871
|
+
}
|
|
4872
|
+
/**
|
|
3690
4873
|
* Allows starting and stopping a REST API
|
|
3691
4874
|
* to allow developers to read local Project data
|
|
3692
4875
|
*/
|
|
@@ -3696,5 +4879,5 @@ var ElekIoCore = class {
|
|
|
3696
4879
|
};
|
|
3697
4880
|
|
|
3698
4881
|
//#endregion
|
|
3699
|
-
export {
|
|
4882
|
+
export { apiStartSchema, assetChangeSchema, assetChangeTypeSchema, assetExportSchema, assetFieldDefinitionSchema, assetFileSchema, assetHistorySchema, assetSchema, baseFileSchema, baseUserSchema, booleanFieldDefinitionBaseSchema, cloneProjectSchema, cloudUserSchema, collectionChangeSchema, collectionChangeTypeSchema, collectionExportSchema, collectionFileSchema, collectionHistorySchema, collectionIndexSchema, collectionSchema, constructorElekIoCoreSchema, countAssetsSchema, countCollectionsSchema, countEntriesSchema, countGitTagsSchema, createAssetSchema, createCollectionSchema, createEntrySchema, createGitTagSchema, createPreviewReleaseSchema, createProjectSchema, createReleaseSchema, currentBranchProjectSchema, dateFieldDefinitionSchema, datetime, datetimeFieldDefinitionSchema, ElekIoCore as default, deleteAssetSchema, deleteCollectionSchema, deleteEntrySchema, deleteGitTagSchema, deleteProjectSchema, directBooleanValueSchema, directNumberValueSchema, directStringValueSchema, directValueBaseSchema, directValueSchema, elekIoCoreOptionsSchema, emailFieldDefinitionSchema, entryChangeSchema, entryChangeTypeSchema, entryExportSchema, entryFieldDefinitionSchema, entryFileSchema, entryHistorySchema, entrySchema, exportSchema, fieldChangeSchema, fieldChangeTypeSchema, fieldDefinitionBaseSchema, fieldDefinitionSchema, fieldTypeSchema, fieldWidthSchema, fileReferenceSchema, generateApiClientSchema, getChangesProjectSchema, getCreateEntrySchemaFromFieldDefinitions, getEntrySchemaFromFieldDefinitions, getRemoteOriginUrlProjectSchema, getTranslatableBooleanValueContentSchemaFromFieldDefinition, getTranslatableNumberValueContentSchemaFromFieldDefinition, getTranslatableReferenceValueContentSchemaFromFieldDefinition, getTranslatableStringValueContentSchemaFromFieldDefinition, getUpdateEntrySchemaFromFieldDefinitions, getValueSchemaFromFieldDefinition, gitCloneOptionsSchema, gitCommitSchema, gitInitOptionsSchema, gitLogOptionsSchema, gitMergeOptionsSchema, gitMessageSchema, gitSignatureSchema, gitSwitchOptionsSchema, gitTagMessageSchema, gitTagSchema, ipv4FieldDefinitionSchema, listAssetsSchema, listBranchesProjectSchema, listCollectionsSchema, listEntriesSchema, listGitTagsSchema, listProjectsSchema, localUserSchema, logConsoleTransportSchema, logLevelSchema, logSchema, logSourceSchema, migrateAssetSchema, migrateCollectionSchema, migrateEntrySchema, migrateProjectSchema, numberFieldDefinitionBaseSchema, numberFieldDefinitionSchema, objectTypeSchema, paginatedListOf, prepareReleaseSchema, projectBranchSchema, projectChangeSchema, projectChangeTypeSchema, projectExportSchema, projectFileSchema, projectFolderSchema, projectHistoryResultSchema, projectHistorySchema, projectSchema, projectSettingsSchema, rangeFieldDefinitionSchema, readAssetSchema, readBySlugCollectionSchema, readCollectionSchema, readEntrySchema, readGitTagSchema, readProjectSchema, referenceFieldDefinitionBaseSchema, referencedValueSchema, releaseDiffSchema, releaseResultSchema, reservedSlugs, resolveCollectionIdSchema, saveAssetSchema, searchProjectSchema, semverBumpSchema, serviceTypeSchema, setRemoteOriginUrlProjectSchema, setUserSchema, slug, slugSchema, stringFieldDefinitionBaseSchema, stringFieldDefinitionSchema, supportedIconSchema, supportedLanguageSchema, switchBranchProjectSchema, synchronizeProjectSchema, telephoneFieldDefinitionSchema, textFieldDefinitionSchema, textareaFieldDefinitionSchema, timeFieldDefinitionSchema, toggleFieldDefinitionSchema, translatableArrayOf, translatableBooleanSchema, translatableNumberSchema, translatableStringSchema, updateAssetSchema, updateCollectionSchema, updateEntrySchema, updateProjectSchema, upgradeProjectSchema, urlFieldDefinitionSchema, userFileSchema, userSchema, userTypeSchema, uuid, uuidSchema, valueContentReferenceBase, valueContentReferenceSchema, valueContentReferenceToAssetSchema, valueContentReferenceToCollectionSchema, valueContentReferenceToEntrySchema, valueSchema, valueTypeSchema, versionSchema };
|
|
3700
4883
|
//# sourceMappingURL=index.node.mjs.map
|