@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
|
@@ -18,6 +18,7 @@ import PQueue from "p-queue";
|
|
|
18
18
|
import { createLogger, format, transports } from "winston";
|
|
19
19
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
20
20
|
import Semver from "semver";
|
|
21
|
+
import { isDeepStrictEqual } from "node:util";
|
|
21
22
|
|
|
22
23
|
//#region \0rolldown/runtime.js
|
|
23
24
|
var __defProp = Object.defineProperty;
|
|
@@ -39,7 +40,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
39
40
|
//#region package.json
|
|
40
41
|
var package_default = {
|
|
41
42
|
name: "@elek-io/core",
|
|
42
|
-
version: "0.
|
|
43
|
+
version: "0.17.0",
|
|
43
44
|
description: "Handles core functionality of elek.io Projects like file IO and version control.",
|
|
44
45
|
homepage: "https://elek.io",
|
|
45
46
|
repository: "https://github.com/elek-io/core",
|
|
@@ -234,11 +235,7 @@ const supportedLanguageSchema = z.enum([
|
|
|
234
235
|
"sv",
|
|
235
236
|
"zh"
|
|
236
237
|
]);
|
|
237
|
-
const supportedIconSchema = z.enum([
|
|
238
|
-
"home",
|
|
239
|
-
"plus",
|
|
240
|
-
"foobar"
|
|
241
|
-
]);
|
|
238
|
+
const supportedIconSchema = z.enum(["home", "plus"]);
|
|
242
239
|
const objectTypeSchema = z.enum([
|
|
243
240
|
"project",
|
|
244
241
|
"asset",
|
|
@@ -253,7 +250,7 @@ const logLevelSchema = z.enum([
|
|
|
253
250
|
"info",
|
|
254
251
|
"debug"
|
|
255
252
|
]);
|
|
256
|
-
const versionSchema = z.string();
|
|
253
|
+
const versionSchema = z.string().refine((version) => /^\d+\.\d+\.\d+(?:-[\w.]+)?(?:\+[\w.]+)?$/.test(version), "String must follow the Semantic Versioning format (https://semver.org/)");
|
|
257
254
|
const uuidSchema = z.uuid();
|
|
258
255
|
/**
|
|
259
256
|
* A record that can be used to translate a string value into all supported languages
|
|
@@ -270,6 +267,41 @@ const translatableBooleanSchema = z.partialRecord(supportedLanguageSchema, z.boo
|
|
|
270
267
|
function translatableArrayOf(schema) {
|
|
271
268
|
return z.partialRecord(supportedLanguageSchema, z.array(schema));
|
|
272
269
|
}
|
|
270
|
+
const reservedSlugs = new Set([
|
|
271
|
+
"index",
|
|
272
|
+
"new",
|
|
273
|
+
"create",
|
|
274
|
+
"update",
|
|
275
|
+
"delete",
|
|
276
|
+
"edit",
|
|
277
|
+
"list",
|
|
278
|
+
"count",
|
|
279
|
+
"api",
|
|
280
|
+
"admin",
|
|
281
|
+
"collection",
|
|
282
|
+
"collections",
|
|
283
|
+
"entry",
|
|
284
|
+
"entries",
|
|
285
|
+
"asset",
|
|
286
|
+
"assets",
|
|
287
|
+
"project",
|
|
288
|
+
"projects",
|
|
289
|
+
"null",
|
|
290
|
+
"undefined",
|
|
291
|
+
"true",
|
|
292
|
+
"false",
|
|
293
|
+
"constructor",
|
|
294
|
+
"__proto__",
|
|
295
|
+
"prototype",
|
|
296
|
+
"toString",
|
|
297
|
+
"valueOf",
|
|
298
|
+
"login",
|
|
299
|
+
"logout",
|
|
300
|
+
"auth",
|
|
301
|
+
"settings",
|
|
302
|
+
"config"
|
|
303
|
+
]);
|
|
304
|
+
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" });
|
|
273
305
|
|
|
274
306
|
//#endregion
|
|
275
307
|
//#region src/schema/fileSchema.ts
|
|
@@ -279,91 +311,36 @@ function translatableArrayOf(schema) {
|
|
|
279
311
|
const baseFileSchema = z.object({
|
|
280
312
|
objectType: objectTypeSchema.readonly(),
|
|
281
313
|
id: uuidSchema.readonly(),
|
|
314
|
+
coreVersion: versionSchema.readonly(),
|
|
282
315
|
created: z.string().datetime().readonly(),
|
|
283
|
-
updated: z.string().datetime().nullable()
|
|
316
|
+
updated: z.string().datetime().nullable().readonly()
|
|
284
317
|
});
|
|
285
318
|
const fileReferenceSchema = z.object({
|
|
286
319
|
id: uuidSchema,
|
|
287
320
|
extension: z.string().optional()
|
|
288
321
|
});
|
|
289
|
-
|
|
290
|
-
//#endregion
|
|
291
|
-
//#region src/schema/gitSchema.ts
|
|
292
322
|
/**
|
|
293
|
-
*
|
|
323
|
+
* Schema for the collection index file (collections/index.json).
|
|
324
|
+
* Maps collection UUIDs to their slug.plural values.
|
|
325
|
+
* This is a local performance cache, not git-tracked.
|
|
294
326
|
*/
|
|
295
|
-
const
|
|
296
|
-
name: z.string(),
|
|
297
|
-
email: z.string().email()
|
|
298
|
-
});
|
|
299
|
-
const gitMessageSchema = z.object({
|
|
300
|
-
method: z.enum([
|
|
301
|
-
"create",
|
|
302
|
-
"update",
|
|
303
|
-
"delete",
|
|
304
|
-
"upgrade"
|
|
305
|
-
]),
|
|
306
|
-
reference: z.object({
|
|
307
|
-
objectType: objectTypeSchema,
|
|
308
|
-
id: uuidSchema,
|
|
309
|
-
collectionId: uuidSchema.optional()
|
|
310
|
-
})
|
|
311
|
-
});
|
|
312
|
-
const gitTagSchema = z.object({
|
|
313
|
-
id: uuidSchema,
|
|
314
|
-
message: z.string(),
|
|
315
|
-
author: gitSignatureSchema,
|
|
316
|
-
datetime: z.string().datetime()
|
|
317
|
-
});
|
|
318
|
-
const gitCommitSchema = z.object({
|
|
319
|
-
hash: z.string(),
|
|
320
|
-
message: gitMessageSchema,
|
|
321
|
-
author: gitSignatureSchema,
|
|
322
|
-
datetime: z.string().datetime(),
|
|
323
|
-
tag: gitTagSchema.nullable()
|
|
324
|
-
});
|
|
325
|
-
const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
|
|
326
|
-
const gitCloneOptionsSchema = z.object({
|
|
327
|
-
depth: z.number(),
|
|
328
|
-
singleBranch: z.boolean(),
|
|
329
|
-
branch: z.string(),
|
|
330
|
-
bare: z.boolean()
|
|
331
|
-
});
|
|
332
|
-
const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
|
|
333
|
-
const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
|
|
334
|
-
const gitLogOptionsSchema = z.object({
|
|
335
|
-
limit: z.number().optional(),
|
|
336
|
-
between: z.object({
|
|
337
|
-
from: z.string(),
|
|
338
|
-
to: z.string().optional()
|
|
339
|
-
}),
|
|
340
|
-
filePath: z.string().optional()
|
|
341
|
-
});
|
|
342
|
-
const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
|
|
343
|
-
path: z.string(),
|
|
344
|
-
hash: z.string().optional()
|
|
345
|
-
});
|
|
346
|
-
const readGitTagSchema = z.object({
|
|
347
|
-
path: z.string(),
|
|
348
|
-
id: uuidSchema.readonly()
|
|
349
|
-
});
|
|
350
|
-
const deleteGitTagSchema = readGitTagSchema.extend({});
|
|
351
|
-
const countGitTagsSchema = z.object({ path: z.string() });
|
|
327
|
+
const collectionIndexSchema = z.record(uuidSchema, z.string());
|
|
352
328
|
|
|
353
329
|
//#endregion
|
|
354
330
|
//#region src/schema/assetSchema.ts
|
|
355
331
|
const assetFileSchema = baseFileSchema.extend({
|
|
356
332
|
objectType: z.literal(objectTypeSchema.enum.asset).readonly(),
|
|
357
|
-
name: z.string(),
|
|
358
|
-
description: z.string(),
|
|
333
|
+
name: z.string().trim().min(1),
|
|
334
|
+
description: z.string().trim().min(1),
|
|
359
335
|
extension: z.string().readonly(),
|
|
360
336
|
mimeType: z.string().readonly(),
|
|
361
337
|
size: z.number().readonly()
|
|
362
338
|
});
|
|
363
|
-
const assetSchema = assetFileSchema.extend({
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
339
|
+
const assetSchema = assetFileSchema.extend({ absolutePath: z.string().readonly() }).openapi("Asset");
|
|
340
|
+
const assetHistorySchema = z.object({
|
|
341
|
+
id: uuidSchema.readonly(),
|
|
342
|
+
projectId: uuidSchema.readonly()
|
|
343
|
+
});
|
|
367
344
|
const assetExportSchema = assetSchema.extend({});
|
|
368
345
|
const createAssetSchema = assetFileSchema.pick({
|
|
369
346
|
name: true,
|
|
@@ -393,11 +370,15 @@ const deleteAssetSchema = assetFileSchema.pick({
|
|
|
393
370
|
id: true,
|
|
394
371
|
extension: true
|
|
395
372
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
373
|
+
const migrateAssetSchema = z.looseObject(assetFileSchema.pick({
|
|
374
|
+
id: true,
|
|
375
|
+
coreVersion: true
|
|
376
|
+
}).shape);
|
|
396
377
|
const countAssetsSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
397
378
|
|
|
398
379
|
//#endregion
|
|
399
380
|
//#region src/schema/valueSchema.ts
|
|
400
|
-
const
|
|
381
|
+
const valueTypeSchema = z.enum([
|
|
401
382
|
"string",
|
|
402
383
|
"number",
|
|
403
384
|
"boolean",
|
|
@@ -412,20 +393,17 @@ const valueContentReferenceSchema = z.union([
|
|
|
412
393
|
valueContentReferenceToCollectionSchema,
|
|
413
394
|
valueContentReferenceToEntrySchema
|
|
414
395
|
]);
|
|
415
|
-
const directValueBaseSchema = z.object({
|
|
416
|
-
objectType: z.literal(objectTypeSchema.enum.value).readonly(),
|
|
417
|
-
fieldDefinitionId: uuidSchema.readonly()
|
|
418
|
-
});
|
|
396
|
+
const directValueBaseSchema = z.object({ objectType: z.literal(objectTypeSchema.enum.value).readonly() });
|
|
419
397
|
const directStringValueSchema = directValueBaseSchema.extend({
|
|
420
|
-
valueType: z.literal(
|
|
398
|
+
valueType: z.literal(valueTypeSchema.enum.string).readonly(),
|
|
421
399
|
content: translatableStringSchema
|
|
422
400
|
});
|
|
423
401
|
const directNumberValueSchema = directValueBaseSchema.extend({
|
|
424
|
-
valueType: z.literal(
|
|
402
|
+
valueType: z.literal(valueTypeSchema.enum.number).readonly(),
|
|
425
403
|
content: translatableNumberSchema
|
|
426
404
|
});
|
|
427
405
|
const directBooleanValueSchema = directValueBaseSchema.extend({
|
|
428
|
-
valueType: z.literal(
|
|
406
|
+
valueType: z.literal(valueTypeSchema.enum.boolean).readonly(),
|
|
429
407
|
content: translatableBooleanSchema
|
|
430
408
|
});
|
|
431
409
|
const directValueSchema = z.union([
|
|
@@ -435,8 +413,7 @@ const directValueSchema = z.union([
|
|
|
435
413
|
]);
|
|
436
414
|
const referencedValueSchema = z.object({
|
|
437
415
|
objectType: z.literal(objectTypeSchema.enum.value).readonly(),
|
|
438
|
-
|
|
439
|
-
valueType: z.literal(ValueTypeSchema.enum.reference).readonly(),
|
|
416
|
+
valueType: z.literal(valueTypeSchema.enum.reference).readonly(),
|
|
440
417
|
content: translatableArrayOf(valueContentReferenceSchema)
|
|
441
418
|
});
|
|
442
419
|
const valueSchema = z.union([directValueSchema, referencedValueSchema]);
|
|
@@ -451,19 +428,25 @@ const valueSchema = z.union([directValueSchema, referencedValueSchema]);
|
|
|
451
428
|
//#region src/schema/entrySchema.ts
|
|
452
429
|
const entryFileSchema = baseFileSchema.extend({
|
|
453
430
|
objectType: z.literal(objectTypeSchema.enum.entry).readonly(),
|
|
454
|
-
values: z.
|
|
431
|
+
values: z.record(slugSchema, valueSchema)
|
|
432
|
+
});
|
|
433
|
+
const entrySchema = entryFileSchema.openapi("Entry");
|
|
434
|
+
const entryHistorySchema = z.object({
|
|
435
|
+
id: uuidSchema.readonly(),
|
|
436
|
+
projectId: uuidSchema.readonly(),
|
|
437
|
+
collectionId: uuidSchema.readonly()
|
|
455
438
|
});
|
|
456
|
-
const entrySchema = entryFileSchema.extend({ history: z.array(gitCommitSchema) }).openapi("Entry");
|
|
457
439
|
const entryExportSchema = entrySchema.extend({});
|
|
458
440
|
const createEntrySchema = entryFileSchema.omit({
|
|
459
441
|
id: true,
|
|
460
442
|
objectType: true,
|
|
443
|
+
coreVersion: true,
|
|
461
444
|
created: true,
|
|
462
445
|
updated: true
|
|
463
446
|
}).extend({
|
|
464
447
|
projectId: uuidSchema.readonly(),
|
|
465
448
|
collectionId: uuidSchema.readonly(),
|
|
466
|
-
values: z.
|
|
449
|
+
values: z.record(slugSchema, valueSchema)
|
|
467
450
|
});
|
|
468
451
|
const readEntrySchema = z.object({
|
|
469
452
|
id: uuidSchema.readonly(),
|
|
@@ -473,6 +456,7 @@ const readEntrySchema = z.object({
|
|
|
473
456
|
});
|
|
474
457
|
const updateEntrySchema = entryFileSchema.omit({
|
|
475
458
|
objectType: true,
|
|
459
|
+
coreVersion: true,
|
|
476
460
|
created: true,
|
|
477
461
|
updated: true
|
|
478
462
|
}).extend({
|
|
@@ -480,6 +464,10 @@ const updateEntrySchema = entryFileSchema.omit({
|
|
|
480
464
|
collectionId: uuidSchema.readonly()
|
|
481
465
|
});
|
|
482
466
|
const deleteEntrySchema = readEntrySchema.extend({});
|
|
467
|
+
const migrateEntrySchema = z.looseObject(entryFileSchema.pick({
|
|
468
|
+
id: true,
|
|
469
|
+
coreVersion: true
|
|
470
|
+
}).shape);
|
|
483
471
|
const countEntriesSchema = z.object({
|
|
484
472
|
projectId: uuidSchema.readonly(),
|
|
485
473
|
collectionId: uuidSchema.readonly()
|
|
@@ -487,7 +475,7 @@ const countEntriesSchema = z.object({
|
|
|
487
475
|
|
|
488
476
|
//#endregion
|
|
489
477
|
//#region src/schema/fieldSchema.ts
|
|
490
|
-
const
|
|
478
|
+
const fieldTypeSchema = z.enum([
|
|
491
479
|
"text",
|
|
492
480
|
"textarea",
|
|
493
481
|
"email",
|
|
@@ -503,64 +491,65 @@ const FieldTypeSchema = z.enum([
|
|
|
503
491
|
"asset",
|
|
504
492
|
"entry"
|
|
505
493
|
]);
|
|
506
|
-
const
|
|
494
|
+
const fieldWidthSchema = z.enum([
|
|
507
495
|
"12",
|
|
508
496
|
"6",
|
|
509
497
|
"4",
|
|
510
498
|
"3"
|
|
511
499
|
]);
|
|
512
|
-
const
|
|
500
|
+
const fieldDefinitionBaseSchema = z.object({
|
|
513
501
|
id: uuidSchema.readonly(),
|
|
502
|
+
slug: slugSchema,
|
|
514
503
|
label: translatableStringSchema,
|
|
515
504
|
description: translatableStringSchema.nullable(),
|
|
516
505
|
isRequired: z.boolean(),
|
|
517
506
|
isDisabled: z.boolean(),
|
|
518
507
|
isUnique: z.boolean(),
|
|
519
|
-
inputWidth:
|
|
508
|
+
inputWidth: fieldWidthSchema
|
|
520
509
|
});
|
|
521
510
|
/**
|
|
522
511
|
* String based Field definitions
|
|
523
512
|
*/
|
|
524
|
-
const
|
|
525
|
-
valueType: z.literal(
|
|
513
|
+
const stringFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
514
|
+
valueType: z.literal(valueTypeSchema.enum.string),
|
|
526
515
|
defaultValue: z.string().nullable()
|
|
527
516
|
});
|
|
528
|
-
const textFieldDefinitionSchema =
|
|
529
|
-
fieldType: z.literal(
|
|
517
|
+
const textFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
518
|
+
fieldType: z.literal(fieldTypeSchema.enum.text),
|
|
530
519
|
min: z.number().nullable(),
|
|
531
520
|
max: z.number().nullable()
|
|
532
521
|
});
|
|
533
|
-
const textareaFieldDefinitionSchema =
|
|
534
|
-
fieldType: z.literal(
|
|
522
|
+
const textareaFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
523
|
+
fieldType: z.literal(fieldTypeSchema.enum.textarea),
|
|
535
524
|
min: z.number().nullable(),
|
|
536
525
|
max: z.number().nullable()
|
|
537
526
|
});
|
|
538
|
-
const emailFieldDefinitionSchema =
|
|
539
|
-
fieldType: z.literal(
|
|
527
|
+
const emailFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
528
|
+
fieldType: z.literal(fieldTypeSchema.enum.email),
|
|
540
529
|
defaultValue: z.email().nullable()
|
|
541
530
|
});
|
|
542
|
-
const urlFieldDefinitionSchema =
|
|
543
|
-
fieldType: z.literal(
|
|
531
|
+
const urlFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
532
|
+
fieldType: z.literal(fieldTypeSchema.enum.url),
|
|
544
533
|
defaultValue: z.url().nullable()
|
|
545
534
|
});
|
|
546
|
-
const ipv4FieldDefinitionSchema =
|
|
547
|
-
fieldType: z.literal(
|
|
535
|
+
const ipv4FieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
536
|
+
fieldType: z.literal(fieldTypeSchema.enum.ipv4),
|
|
548
537
|
defaultValue: z.ipv4().nullable()
|
|
549
538
|
});
|
|
550
|
-
const dateFieldDefinitionSchema =
|
|
551
|
-
fieldType: z.literal(
|
|
539
|
+
const dateFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
540
|
+
fieldType: z.literal(fieldTypeSchema.enum.date),
|
|
552
541
|
defaultValue: z.iso.date().nullable()
|
|
553
542
|
});
|
|
554
|
-
const timeFieldDefinitionSchema =
|
|
555
|
-
fieldType: z.literal(
|
|
543
|
+
const timeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
544
|
+
fieldType: z.literal(fieldTypeSchema.enum.time),
|
|
556
545
|
defaultValue: z.iso.time().nullable()
|
|
557
546
|
});
|
|
558
|
-
const datetimeFieldDefinitionSchema =
|
|
559
|
-
fieldType: z.literal(
|
|
547
|
+
const datetimeFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
548
|
+
fieldType: z.literal(fieldTypeSchema.enum.datetime),
|
|
560
549
|
defaultValue: z.iso.datetime().nullable()
|
|
561
550
|
});
|
|
562
|
-
const telephoneFieldDefinitionSchema =
|
|
563
|
-
fieldType: z.literal(
|
|
551
|
+
const telephoneFieldDefinitionSchema = stringFieldDefinitionBaseSchema.extend({
|
|
552
|
+
fieldType: z.literal(fieldTypeSchema.enum.telephone),
|
|
564
553
|
defaultValue: z.e164().nullable()
|
|
565
554
|
});
|
|
566
555
|
const stringFieldDefinitionSchema = z.union([
|
|
@@ -577,16 +566,16 @@ const stringFieldDefinitionSchema = z.union([
|
|
|
577
566
|
/**
|
|
578
567
|
* Number based Field definitions
|
|
579
568
|
*/
|
|
580
|
-
const
|
|
581
|
-
valueType: z.literal(
|
|
569
|
+
const numberFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
570
|
+
valueType: z.literal(valueTypeSchema.enum.number),
|
|
582
571
|
min: z.number().nullable(),
|
|
583
572
|
max: z.number().nullable(),
|
|
584
573
|
isUnique: z.literal(false),
|
|
585
574
|
defaultValue: z.number().nullable()
|
|
586
575
|
});
|
|
587
|
-
const numberFieldDefinitionSchema =
|
|
588
|
-
const rangeFieldDefinitionSchema =
|
|
589
|
-
fieldType: z.literal(
|
|
576
|
+
const numberFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.number) });
|
|
577
|
+
const rangeFieldDefinitionSchema = numberFieldDefinitionBaseSchema.extend({
|
|
578
|
+
fieldType: z.literal(fieldTypeSchema.enum.range),
|
|
590
579
|
isRequired: z.literal(true),
|
|
591
580
|
min: z.number(),
|
|
592
581
|
max: z.number(),
|
|
@@ -595,24 +584,27 @@ const rangeFieldDefinitionSchema = NumberFieldDefinitionBaseSchema.extend({
|
|
|
595
584
|
/**
|
|
596
585
|
* Boolean based Field definitions
|
|
597
586
|
*/
|
|
598
|
-
const
|
|
599
|
-
valueType: z.literal(
|
|
587
|
+
const booleanFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
588
|
+
valueType: z.literal(valueTypeSchema.enum.boolean),
|
|
600
589
|
isRequired: z.literal(true),
|
|
601
590
|
defaultValue: z.boolean(),
|
|
602
591
|
isUnique: z.literal(false)
|
|
603
592
|
});
|
|
604
|
-
const toggleFieldDefinitionSchema =
|
|
593
|
+
const toggleFieldDefinitionSchema = booleanFieldDefinitionBaseSchema.extend({ fieldType: z.literal(fieldTypeSchema.enum.toggle) });
|
|
605
594
|
/**
|
|
606
595
|
* Reference based Field definitions
|
|
607
596
|
*/
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
597
|
+
const referenceFieldDefinitionBaseSchema = fieldDefinitionBaseSchema.extend({
|
|
598
|
+
valueType: z.literal(valueTypeSchema.enum.reference),
|
|
599
|
+
isUnique: z.literal(false)
|
|
600
|
+
});
|
|
601
|
+
const assetFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
|
|
602
|
+
fieldType: z.literal(fieldTypeSchema.enum.asset),
|
|
611
603
|
min: z.number().nullable(),
|
|
612
604
|
max: z.number().nullable()
|
|
613
605
|
});
|
|
614
|
-
const entryFieldDefinitionSchema =
|
|
615
|
-
fieldType: z.literal(
|
|
606
|
+
const entryFieldDefinitionSchema = referenceFieldDefinitionBaseSchema.extend({
|
|
607
|
+
fieldType: z.literal(fieldTypeSchema.enum.entry),
|
|
616
608
|
ofCollections: z.array(uuidSchema),
|
|
617
609
|
min: z.number().nullable(),
|
|
618
610
|
max: z.number().nullable()
|
|
@@ -635,18 +627,23 @@ const collectionFileSchema = baseFileSchema.extend({
|
|
|
635
627
|
plural: translatableStringSchema
|
|
636
628
|
}),
|
|
637
629
|
slug: z.object({
|
|
638
|
-
singular:
|
|
639
|
-
plural:
|
|
630
|
+
singular: slugSchema,
|
|
631
|
+
plural: slugSchema
|
|
640
632
|
}),
|
|
641
633
|
description: translatableStringSchema,
|
|
642
634
|
icon: supportedIconSchema,
|
|
643
635
|
fieldDefinitions: z.array(fieldDefinitionSchema)
|
|
644
636
|
});
|
|
645
|
-
const collectionSchema = collectionFileSchema.
|
|
637
|
+
const collectionSchema = collectionFileSchema.openapi("Collection");
|
|
638
|
+
const collectionHistorySchema = z.object({
|
|
639
|
+
id: uuidSchema.readonly(),
|
|
640
|
+
projectId: uuidSchema.readonly()
|
|
641
|
+
});
|
|
646
642
|
const collectionExportSchema = collectionSchema.extend({ entries: z.array(entryExportSchema) });
|
|
647
643
|
const createCollectionSchema = collectionFileSchema.omit({
|
|
648
644
|
id: true,
|
|
649
645
|
objectType: true,
|
|
646
|
+
coreVersion: true,
|
|
650
647
|
created: true,
|
|
651
648
|
updated: true
|
|
652
649
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
@@ -655,6 +652,11 @@ const readCollectionSchema = z.object({
|
|
|
655
652
|
projectId: uuidSchema.readonly(),
|
|
656
653
|
commitHash: z.string().optional().readonly()
|
|
657
654
|
});
|
|
655
|
+
const readBySlugCollectionSchema = z.object({
|
|
656
|
+
slug: slugSchema,
|
|
657
|
+
projectId: uuidSchema.readonly(),
|
|
658
|
+
commitHash: z.string().optional().readonly()
|
|
659
|
+
});
|
|
658
660
|
const updateCollectionSchema = collectionFileSchema.pick({
|
|
659
661
|
id: true,
|
|
660
662
|
name: true,
|
|
@@ -665,6 +667,14 @@ const updateCollectionSchema = collectionFileSchema.pick({
|
|
|
665
667
|
}).extend({ projectId: uuidSchema.readonly() });
|
|
666
668
|
const deleteCollectionSchema = readCollectionSchema.extend({});
|
|
667
669
|
const countCollectionsSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
670
|
+
const migrateCollectionSchema = z.looseObject(collectionFileSchema.pick({
|
|
671
|
+
id: true,
|
|
672
|
+
coreVersion: true
|
|
673
|
+
}).shape);
|
|
674
|
+
const resolveCollectionIdSchema = z.object({
|
|
675
|
+
projectId: uuidSchema.readonly(),
|
|
676
|
+
idOrSlug: z.string()
|
|
677
|
+
});
|
|
668
678
|
|
|
669
679
|
//#endregion
|
|
670
680
|
//#region src/schema/coreSchema.ts
|
|
@@ -681,15 +691,88 @@ const constructorElekIoCoreSchema = elekIoCoreOptionsSchema.partial({
|
|
|
681
691
|
}).optional();
|
|
682
692
|
|
|
683
693
|
//#endregion
|
|
684
|
-
//#region src/schema/
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
694
|
+
//#region src/schema/gitSchema.ts
|
|
695
|
+
/**
|
|
696
|
+
* Signature git uses to identify users
|
|
697
|
+
*/
|
|
698
|
+
const gitSignatureSchema = z.object({
|
|
699
|
+
name: z.string().regex(/^[^|]+$/, "Name must not contain pipe characters"),
|
|
700
|
+
email: z.string().email()
|
|
701
|
+
});
|
|
702
|
+
const gitMessageSchema = z.object({
|
|
703
|
+
method: z.enum([
|
|
704
|
+
"create",
|
|
705
|
+
"update",
|
|
706
|
+
"delete",
|
|
707
|
+
"upgrade",
|
|
708
|
+
"release"
|
|
709
|
+
]),
|
|
710
|
+
reference: z.object({
|
|
711
|
+
objectType: objectTypeSchema,
|
|
712
|
+
id: uuidSchema,
|
|
713
|
+
collectionId: uuidSchema.optional()
|
|
714
|
+
})
|
|
715
|
+
});
|
|
716
|
+
const gitTagMessageSchema = z.discriminatedUnion("type", [
|
|
717
|
+
z.object({
|
|
718
|
+
type: z.literal("release"),
|
|
719
|
+
version: versionSchema
|
|
720
|
+
}),
|
|
721
|
+
z.object({
|
|
722
|
+
type: z.literal("preview"),
|
|
723
|
+
version: versionSchema
|
|
724
|
+
}),
|
|
725
|
+
z.object({
|
|
726
|
+
type: z.literal("upgrade"),
|
|
727
|
+
coreVersion: versionSchema
|
|
728
|
+
})
|
|
689
729
|
]);
|
|
730
|
+
const gitTagSchema = z.object({
|
|
731
|
+
id: uuidSchema,
|
|
732
|
+
message: gitTagMessageSchema,
|
|
733
|
+
author: gitSignatureSchema,
|
|
734
|
+
datetime: z.iso.datetime()
|
|
735
|
+
});
|
|
736
|
+
const gitCommitSchema = z.object({
|
|
737
|
+
hash: z.hash("sha1"),
|
|
738
|
+
message: gitMessageSchema,
|
|
739
|
+
author: gitSignatureSchema,
|
|
740
|
+
datetime: z.iso.datetime(),
|
|
741
|
+
tag: gitTagSchema.nullable()
|
|
742
|
+
});
|
|
743
|
+
const gitInitOptionsSchema = z.object({ initialBranch: z.string() });
|
|
744
|
+
const gitCloneOptionsSchema = z.object({
|
|
745
|
+
depth: z.number(),
|
|
746
|
+
singleBranch: z.boolean(),
|
|
747
|
+
branch: z.string(),
|
|
748
|
+
bare: z.boolean()
|
|
749
|
+
});
|
|
750
|
+
const gitMergeOptionsSchema = z.object({ squash: z.boolean() });
|
|
751
|
+
const gitSwitchOptionsSchema = z.object({ isNew: z.boolean().optional() });
|
|
752
|
+
const gitLogOptionsSchema = z.object({
|
|
753
|
+
limit: z.number().optional(),
|
|
754
|
+
between: z.object({
|
|
755
|
+
from: z.string(),
|
|
756
|
+
to: z.string().optional()
|
|
757
|
+
}),
|
|
758
|
+
filePath: z.string().optional()
|
|
759
|
+
});
|
|
760
|
+
const createGitTagSchema = gitTagSchema.pick({ message: true }).extend({
|
|
761
|
+
path: z.string(),
|
|
762
|
+
hash: z.string().optional()
|
|
763
|
+
});
|
|
764
|
+
const readGitTagSchema = z.object({
|
|
765
|
+
path: z.string(),
|
|
766
|
+
id: uuidSchema.readonly()
|
|
767
|
+
});
|
|
768
|
+
const deleteGitTagSchema = readGitTagSchema.extend({});
|
|
769
|
+
const countGitTagsSchema = z.object({ path: z.string() });
|
|
770
|
+
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region src/schema/projectSchema.ts
|
|
690
773
|
const projectSettingsSchema = z.object({ language: z.object({
|
|
691
774
|
default: supportedLanguageSchema,
|
|
692
|
-
supported: z.array(supportedLanguageSchema)
|
|
775
|
+
supported: z.array(supportedLanguageSchema).refine((langs) => new Set(langs).size === langs.length, { message: "Supported languages must not contain duplicates" })
|
|
693
776
|
}) });
|
|
694
777
|
const projectFolderSchema = z.enum([
|
|
695
778
|
"assets",
|
|
@@ -700,22 +783,21 @@ const projectFolderSchema = z.enum([
|
|
|
700
783
|
const projectBranchSchema = z.enum(["production", "work"]);
|
|
701
784
|
const projectFileSchema = baseFileSchema.extend({
|
|
702
785
|
objectType: z.literal(objectTypeSchema.enum.project).readonly(),
|
|
703
|
-
coreVersion: versionSchema,
|
|
704
786
|
name: z.string().trim().min(1),
|
|
705
787
|
description: z.string().trim().min(1),
|
|
706
788
|
version: versionSchema,
|
|
707
|
-
status: projectStatusSchema,
|
|
708
789
|
settings: projectSettingsSchema
|
|
709
790
|
});
|
|
710
|
-
const projectSchema = projectFileSchema.extend({
|
|
711
|
-
|
|
791
|
+
const projectSchema = projectFileSchema.extend({ remoteOriginUrl: z.string().nullable().openapi({ description: "URL of the remote Git repository" }) }).openapi("Project");
|
|
792
|
+
const projectHistorySchema = z.object({ id: uuidSchema.readonly() });
|
|
793
|
+
const projectHistoryResultSchema = z.object({
|
|
712
794
|
history: z.array(gitCommitSchema).openapi({ description: "Commit history of this Project" }),
|
|
713
795
|
fullHistory: z.array(gitCommitSchema).openapi({ description: "Full commit history of this Project including all Assets, Collections, Entries and other files" })
|
|
714
|
-
})
|
|
715
|
-
const migrateProjectSchema = projectFileSchema.pick({
|
|
796
|
+
});
|
|
797
|
+
const migrateProjectSchema = z.looseObject(projectFileSchema.pick({
|
|
716
798
|
id: true,
|
|
717
799
|
coreVersion: true
|
|
718
|
-
}).
|
|
800
|
+
}).shape);
|
|
719
801
|
const projectExportSchema = projectSchema.extend({
|
|
720
802
|
assets: z.array(assetExportSchema),
|
|
721
803
|
collections: z.array(collectionExportSchema)
|
|
@@ -740,13 +822,6 @@ const upgradeProjectSchema = z.object({
|
|
|
740
822
|
force: z.boolean().optional()
|
|
741
823
|
});
|
|
742
824
|
const deleteProjectSchema = readProjectSchema.extend({ force: z.boolean().optional() });
|
|
743
|
-
const projectUpgradeSchema = z.object({
|
|
744
|
-
to: versionSchema.readonly(),
|
|
745
|
-
run: z.function({
|
|
746
|
-
input: [projectFileSchema],
|
|
747
|
-
output: z.promise(z.void())
|
|
748
|
-
})
|
|
749
|
-
});
|
|
750
825
|
const cloneProjectSchema = z.object({ url: z.string() });
|
|
751
826
|
const listBranchesProjectSchema = z.object({ id: uuidSchema.readonly() });
|
|
752
827
|
const currentBranchProjectSchema = z.object({ id: uuidSchema.readonly() });
|
|
@@ -800,29 +875,29 @@ function getNumberValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
|
800
875
|
function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
801
876
|
let schema = null;
|
|
802
877
|
switch (fieldDefinition.fieldType) {
|
|
803
|
-
case
|
|
878
|
+
case fieldTypeSchema.enum.email:
|
|
804
879
|
schema = z.email();
|
|
805
880
|
break;
|
|
806
|
-
case
|
|
881
|
+
case fieldTypeSchema.enum.url:
|
|
807
882
|
schema = z.url();
|
|
808
883
|
break;
|
|
809
|
-
case
|
|
884
|
+
case fieldTypeSchema.enum.ipv4:
|
|
810
885
|
schema = z.ipv4();
|
|
811
886
|
break;
|
|
812
|
-
case
|
|
887
|
+
case fieldTypeSchema.enum.date:
|
|
813
888
|
schema = z.iso.date();
|
|
814
889
|
break;
|
|
815
|
-
case
|
|
890
|
+
case fieldTypeSchema.enum.time:
|
|
816
891
|
schema = z.iso.time();
|
|
817
892
|
break;
|
|
818
|
-
case
|
|
893
|
+
case fieldTypeSchema.enum.datetime:
|
|
819
894
|
schema = z.iso.datetime();
|
|
820
895
|
break;
|
|
821
|
-
case
|
|
896
|
+
case fieldTypeSchema.enum.telephone:
|
|
822
897
|
schema = z.e164();
|
|
823
898
|
break;
|
|
824
|
-
case
|
|
825
|
-
case
|
|
899
|
+
case fieldTypeSchema.enum.text:
|
|
900
|
+
case fieldTypeSchema.enum.textarea:
|
|
826
901
|
schema = z.string().trim();
|
|
827
902
|
break;
|
|
828
903
|
}
|
|
@@ -838,10 +913,10 @@ function getStringValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
|
838
913
|
function getReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) {
|
|
839
914
|
let schema;
|
|
840
915
|
switch (fieldDefinition.fieldType) {
|
|
841
|
-
case
|
|
916
|
+
case fieldTypeSchema.enum.asset:
|
|
842
917
|
schema = z.array(valueContentReferenceToAssetSchema);
|
|
843
918
|
break;
|
|
844
|
-
case
|
|
919
|
+
case fieldTypeSchema.enum.entry:
|
|
845
920
|
schema = z.array(valueContentReferenceToEntrySchema);
|
|
846
921
|
break;
|
|
847
922
|
}
|
|
@@ -867,35 +942,37 @@ function getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefi
|
|
|
867
942
|
*/
|
|
868
943
|
function getValueSchemaFromFieldDefinition(fieldDefinition) {
|
|
869
944
|
switch (fieldDefinition.valueType) {
|
|
870
|
-
case
|
|
871
|
-
case
|
|
872
|
-
case
|
|
873
|
-
case
|
|
945
|
+
case valueTypeSchema.enum.boolean: return directBooleanValueSchema.extend({ content: getTranslatableBooleanValueContentSchemaFromFieldDefinition() });
|
|
946
|
+
case valueTypeSchema.enum.number: return directNumberValueSchema.extend({ content: getTranslatableNumberValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
947
|
+
case valueTypeSchema.enum.string: return directStringValueSchema.extend({ content: getTranslatableStringValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
948
|
+
case valueTypeSchema.enum.reference: return referencedValueSchema.extend({ content: getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDefinition) });
|
|
874
949
|
default: throw new Error(`Error generating schema for unsupported ValueType "${fieldDefinition.valueType}"`);
|
|
875
950
|
}
|
|
876
951
|
}
|
|
877
952
|
/**
|
|
953
|
+
* Builds a z.object shape from field definitions, keyed by slug
|
|
954
|
+
*/
|
|
955
|
+
function getValuesShapeFromFieldDefinitions(fieldDefinitions) {
|
|
956
|
+
const shape = {};
|
|
957
|
+
for (const fieldDef of fieldDefinitions) shape[fieldDef.slug] = getValueSchemaFromFieldDefinition(fieldDef);
|
|
958
|
+
return shape;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
878
961
|
* Generates a schema for creating a new Entry based on the given Field definitions and Values
|
|
879
962
|
*/
|
|
880
963
|
function getCreateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
|
|
881
|
-
const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
|
|
882
|
-
return getValueSchemaFromFieldDefinition(fieldDefinition);
|
|
883
|
-
});
|
|
884
964
|
return z.object({
|
|
885
965
|
...createEntrySchema.shape,
|
|
886
|
-
values: z.
|
|
966
|
+
values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
|
|
887
967
|
});
|
|
888
968
|
}
|
|
889
969
|
/**
|
|
890
970
|
* Generates a schema for updating an existing Entry based on the given Field definitions and Values
|
|
891
971
|
*/
|
|
892
972
|
function getUpdateEntrySchemaFromFieldDefinitions(fieldDefinitions) {
|
|
893
|
-
const valueSchemas = fieldDefinitions.map((fieldDefinition) => {
|
|
894
|
-
return getValueSchemaFromFieldDefinition(fieldDefinition);
|
|
895
|
-
});
|
|
896
973
|
return z.object({
|
|
897
974
|
...updateEntrySchema.shape,
|
|
898
|
-
values: z.
|
|
975
|
+
values: z.object(getValuesShapeFromFieldDefinitions(fieldDefinitions))
|
|
899
976
|
});
|
|
900
977
|
}
|
|
901
978
|
|
|
@@ -911,7 +988,8 @@ const serviceTypeSchema = z.enum([
|
|
|
911
988
|
"Search",
|
|
912
989
|
"Collection",
|
|
913
990
|
"Entry",
|
|
914
|
-
"Value"
|
|
991
|
+
"Value",
|
|
992
|
+
"Release"
|
|
915
993
|
]);
|
|
916
994
|
function paginatedListOf(schema) {
|
|
917
995
|
return z.object({
|
|
@@ -934,18 +1012,18 @@ const listGitTagsSchema = z.object({ path: z.string() });
|
|
|
934
1012
|
|
|
935
1013
|
//#endregion
|
|
936
1014
|
//#region src/schema/userSchema.ts
|
|
937
|
-
const
|
|
1015
|
+
const userTypeSchema = z.enum(["local", "cloud"]);
|
|
938
1016
|
const baseUserSchema = gitSignatureSchema.extend({
|
|
939
|
-
userType:
|
|
1017
|
+
userType: userTypeSchema,
|
|
940
1018
|
language: supportedLanguageSchema,
|
|
941
1019
|
localApi: z.object({
|
|
942
1020
|
isEnabled: z.boolean(),
|
|
943
1021
|
port: z.number()
|
|
944
1022
|
})
|
|
945
1023
|
});
|
|
946
|
-
const localUserSchema = baseUserSchema.extend({ userType: z.literal(
|
|
1024
|
+
const localUserSchema = baseUserSchema.extend({ userType: z.literal(userTypeSchema.enum.local) });
|
|
947
1025
|
const cloudUserSchema = baseUserSchema.extend({
|
|
948
|
-
userType: z.literal(
|
|
1026
|
+
userType: z.literal(userTypeSchema.enum.cloud),
|
|
949
1027
|
id: uuidSchema
|
|
950
1028
|
});
|
|
951
1029
|
const userFileSchema = z.union([localUserSchema, cloudUserSchema]);
|
|
@@ -1019,6 +1097,97 @@ const logConsoleTransportSchema = logSchema.extend({
|
|
|
1019
1097
|
level: z.string()
|
|
1020
1098
|
});
|
|
1021
1099
|
|
|
1100
|
+
//#endregion
|
|
1101
|
+
//#region src/schema/releaseSchema.ts
|
|
1102
|
+
const semverBumpSchema = z.enum([
|
|
1103
|
+
"major",
|
|
1104
|
+
"minor",
|
|
1105
|
+
"patch"
|
|
1106
|
+
]);
|
|
1107
|
+
const fieldChangeTypeSchema = z.enum([
|
|
1108
|
+
"added",
|
|
1109
|
+
"deleted",
|
|
1110
|
+
"valueTypeChanged",
|
|
1111
|
+
"fieldTypeChanged",
|
|
1112
|
+
"slugChanged",
|
|
1113
|
+
"minMaxTightened",
|
|
1114
|
+
"isRequiredToNotRequired",
|
|
1115
|
+
"isUniqueToNotUnique",
|
|
1116
|
+
"ofCollectionsChanged",
|
|
1117
|
+
"isNotRequiredToRequired",
|
|
1118
|
+
"isNotUniqueToUnique",
|
|
1119
|
+
"labelChanged",
|
|
1120
|
+
"descriptionChanged",
|
|
1121
|
+
"defaultValueChanged",
|
|
1122
|
+
"inputWidthChanged",
|
|
1123
|
+
"isDisabledChanged",
|
|
1124
|
+
"minMaxLoosened"
|
|
1125
|
+
]);
|
|
1126
|
+
const fieldChangeSchema = z.object({
|
|
1127
|
+
collectionId: uuidSchema,
|
|
1128
|
+
fieldId: uuidSchema,
|
|
1129
|
+
fieldSlug: z.string(),
|
|
1130
|
+
changeType: fieldChangeTypeSchema,
|
|
1131
|
+
bump: semverBumpSchema
|
|
1132
|
+
});
|
|
1133
|
+
const collectionChangeTypeSchema = z.enum(["added", "deleted"]);
|
|
1134
|
+
const collectionChangeSchema = z.object({
|
|
1135
|
+
collectionId: uuidSchema,
|
|
1136
|
+
changeType: collectionChangeTypeSchema,
|
|
1137
|
+
bump: semverBumpSchema
|
|
1138
|
+
});
|
|
1139
|
+
const projectChangeTypeSchema = z.enum([
|
|
1140
|
+
"nameChanged",
|
|
1141
|
+
"descriptionChanged",
|
|
1142
|
+
"defaultLanguageChanged",
|
|
1143
|
+
"supportedLanguageAdded",
|
|
1144
|
+
"supportedLanguageRemoved"
|
|
1145
|
+
]);
|
|
1146
|
+
const projectChangeSchema = z.object({
|
|
1147
|
+
changeType: projectChangeTypeSchema,
|
|
1148
|
+
bump: semverBumpSchema
|
|
1149
|
+
});
|
|
1150
|
+
const assetChangeTypeSchema = z.enum([
|
|
1151
|
+
"added",
|
|
1152
|
+
"deleted",
|
|
1153
|
+
"metadataChanged",
|
|
1154
|
+
"binaryChanged"
|
|
1155
|
+
]);
|
|
1156
|
+
const assetChangeSchema = z.object({
|
|
1157
|
+
assetId: uuidSchema,
|
|
1158
|
+
changeType: assetChangeTypeSchema,
|
|
1159
|
+
bump: semverBumpSchema
|
|
1160
|
+
});
|
|
1161
|
+
const entryChangeTypeSchema = z.enum([
|
|
1162
|
+
"added",
|
|
1163
|
+
"deleted",
|
|
1164
|
+
"modified"
|
|
1165
|
+
]);
|
|
1166
|
+
const entryChangeSchema = z.object({
|
|
1167
|
+
collectionId: uuidSchema,
|
|
1168
|
+
entryId: uuidSchema,
|
|
1169
|
+
changeType: entryChangeTypeSchema,
|
|
1170
|
+
bump: semverBumpSchema
|
|
1171
|
+
});
|
|
1172
|
+
const releaseDiffSchema = z.object({
|
|
1173
|
+
project: projectSchema,
|
|
1174
|
+
bump: semverBumpSchema.nullable(),
|
|
1175
|
+
currentVersion: versionSchema,
|
|
1176
|
+
nextVersion: versionSchema.nullable(),
|
|
1177
|
+
projectChanges: z.array(projectChangeSchema),
|
|
1178
|
+
collectionChanges: z.array(collectionChangeSchema),
|
|
1179
|
+
fieldChanges: z.array(fieldChangeSchema),
|
|
1180
|
+
assetChanges: z.array(assetChangeSchema),
|
|
1181
|
+
entryChanges: z.array(entryChangeSchema)
|
|
1182
|
+
});
|
|
1183
|
+
const prepareReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1184
|
+
const createReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1185
|
+
const createPreviewReleaseSchema = z.object({ projectId: uuidSchema.readonly() });
|
|
1186
|
+
const releaseResultSchema = z.object({
|
|
1187
|
+
version: versionSchema,
|
|
1188
|
+
diff: releaseDiffSchema
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1022
1191
|
//#endregion
|
|
1023
1192
|
//#region src/api/routes/content/v1/projects.ts
|
|
1024
1193
|
const tags$3 = ["Content API v1"];
|
|
@@ -1140,29 +1309,36 @@ const router$5 = createRouter().openapi(createRoute({
|
|
|
1140
1309
|
return c.json(count, 200);
|
|
1141
1310
|
}).openapi(createRoute({
|
|
1142
1311
|
summary: "Get one Collection",
|
|
1143
|
-
description: "Retrieve a Collection by
|
|
1312
|
+
description: "Retrieve a Collection by UUID or slug",
|
|
1144
1313
|
method: "get",
|
|
1145
|
-
path: "/{projectId}/collections/{
|
|
1314
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}",
|
|
1146
1315
|
tags: tags$2,
|
|
1147
1316
|
request: { params: z.object({
|
|
1148
1317
|
projectId: uuidSchema.openapi({ param: {
|
|
1149
1318
|
name: "projectId",
|
|
1150
1319
|
in: "path"
|
|
1151
1320
|
} }),
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1321
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1322
|
+
param: {
|
|
1323
|
+
name: "collectionIdOrSlug",
|
|
1324
|
+
in: "path"
|
|
1325
|
+
},
|
|
1326
|
+
description: "Collection UUID or slug"
|
|
1327
|
+
})
|
|
1156
1328
|
}) },
|
|
1157
1329
|
responses: { [200]: {
|
|
1158
1330
|
content: { "application/json": { schema: collectionSchema } },
|
|
1159
1331
|
description: "The requested Collection"
|
|
1160
1332
|
} }
|
|
1161
1333
|
}), async (c) => {
|
|
1162
|
-
const { projectId,
|
|
1334
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1335
|
+
const resolvedId = await c.var.collectionService.resolveCollectionId({
|
|
1336
|
+
projectId,
|
|
1337
|
+
idOrSlug: collectionIdOrSlug
|
|
1338
|
+
});
|
|
1163
1339
|
const collection = await c.var.collectionService.read({
|
|
1164
1340
|
projectId,
|
|
1165
|
-
id:
|
|
1341
|
+
id: resolvedId
|
|
1166
1342
|
});
|
|
1167
1343
|
return c.json(collection, 200);
|
|
1168
1344
|
});
|
|
@@ -1174,7 +1350,7 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1174
1350
|
summary: "List Entries",
|
|
1175
1351
|
description: "Lists all Entries of the given Projects Collection",
|
|
1176
1352
|
method: "get",
|
|
1177
|
-
path: "/{projectId}/collections/{
|
|
1353
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries",
|
|
1178
1354
|
tags: tags$1,
|
|
1179
1355
|
request: {
|
|
1180
1356
|
params: z.object({
|
|
@@ -1182,10 +1358,13 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1182
1358
|
name: "projectId",
|
|
1183
1359
|
in: "path"
|
|
1184
1360
|
} }),
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1361
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1362
|
+
param: {
|
|
1363
|
+
name: "collectionIdOrSlug",
|
|
1364
|
+
in: "path"
|
|
1365
|
+
},
|
|
1366
|
+
description: "Collection UUID or slug"
|
|
1367
|
+
})
|
|
1189
1368
|
}),
|
|
1190
1369
|
query: z.object({
|
|
1191
1370
|
limit: z.string().pipe(z.coerce.number()).optional().openapi({
|
|
@@ -1203,8 +1382,12 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1203
1382
|
description: "A list of Entries for the given Projects Collection"
|
|
1204
1383
|
} }
|
|
1205
1384
|
}), async (c) => {
|
|
1206
|
-
const { projectId,
|
|
1385
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1207
1386
|
const { limit, offset } = c.req.valid("query");
|
|
1387
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1388
|
+
projectId,
|
|
1389
|
+
idOrSlug: collectionIdOrSlug
|
|
1390
|
+
});
|
|
1208
1391
|
const entries = await c.var.entryService.list({
|
|
1209
1392
|
projectId,
|
|
1210
1393
|
collectionId,
|
|
@@ -1216,24 +1399,31 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1216
1399
|
summary: "Count Entries",
|
|
1217
1400
|
description: "Counts all Entries of the given Projects Collection",
|
|
1218
1401
|
method: "get",
|
|
1219
|
-
path: "/{projectId}/collections/{
|
|
1402
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries/count",
|
|
1220
1403
|
tags: tags$1,
|
|
1221
1404
|
request: { params: z.object({
|
|
1222
1405
|
projectId: uuidSchema.openapi({ param: {
|
|
1223
1406
|
name: "projectId",
|
|
1224
1407
|
in: "path"
|
|
1225
1408
|
} }),
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1409
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1410
|
+
param: {
|
|
1411
|
+
name: "collectionIdOrSlug",
|
|
1412
|
+
in: "path"
|
|
1413
|
+
},
|
|
1414
|
+
description: "Collection UUID or slug"
|
|
1415
|
+
})
|
|
1230
1416
|
}) },
|
|
1231
1417
|
responses: { [200]: {
|
|
1232
1418
|
content: { "application/json": { schema: z.number() } },
|
|
1233
1419
|
description: "The number of Entries of the given Projects Collection"
|
|
1234
1420
|
} }
|
|
1235
1421
|
}), async (c) => {
|
|
1236
|
-
const { projectId,
|
|
1422
|
+
const { projectId, collectionIdOrSlug } = c.req.valid("param");
|
|
1423
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1424
|
+
projectId,
|
|
1425
|
+
idOrSlug: collectionIdOrSlug
|
|
1426
|
+
});
|
|
1237
1427
|
const count = await c.var.entryService.count({
|
|
1238
1428
|
projectId,
|
|
1239
1429
|
collectionId
|
|
@@ -1243,17 +1433,20 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1243
1433
|
summary: "Get one Entry",
|
|
1244
1434
|
description: "Retrieve an Entry by ID",
|
|
1245
1435
|
method: "get",
|
|
1246
|
-
path: "/{projectId}/collections/{
|
|
1436
|
+
path: "/{projectId}/collections/{collectionIdOrSlug}/entries/{entryId}",
|
|
1247
1437
|
tags: tags$1,
|
|
1248
1438
|
request: { params: z.object({
|
|
1249
1439
|
projectId: uuidSchema.openapi({ param: {
|
|
1250
1440
|
name: "projectId",
|
|
1251
1441
|
in: "path"
|
|
1252
1442
|
} }),
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1443
|
+
collectionIdOrSlug: z.string().openapi({
|
|
1444
|
+
param: {
|
|
1445
|
+
name: "collectionIdOrSlug",
|
|
1446
|
+
in: "path"
|
|
1447
|
+
},
|
|
1448
|
+
description: "Collection UUID or slug"
|
|
1449
|
+
}),
|
|
1257
1450
|
entryId: uuidSchema.openapi({ param: {
|
|
1258
1451
|
name: "entryId",
|
|
1259
1452
|
in: "path"
|
|
@@ -1264,7 +1457,11 @@ const router$4 = createRouter().openapi(createRoute({
|
|
|
1264
1457
|
description: "The requested Entry"
|
|
1265
1458
|
} }
|
|
1266
1459
|
}), async (c) => {
|
|
1267
|
-
const { projectId,
|
|
1460
|
+
const { projectId, collectionIdOrSlug, entryId } = c.req.valid("param");
|
|
1461
|
+
const collectionId = await c.var.collectionService.resolveCollectionId({
|
|
1462
|
+
projectId,
|
|
1463
|
+
idOrSlug: collectionIdOrSlug
|
|
1464
|
+
});
|
|
1268
1465
|
const entry = await c.var.entryService.read({
|
|
1269
1466
|
projectId,
|
|
1270
1467
|
collectionId,
|
|
@@ -1531,6 +1728,9 @@ const pathTo = {
|
|
|
1531
1728
|
collectionFile: (projectId, id) => {
|
|
1532
1729
|
return Path.join(pathTo.collection(projectId, id), "collection.json");
|
|
1533
1730
|
},
|
|
1731
|
+
collectionIndex: (projectId) => {
|
|
1732
|
+
return Path.join(pathTo.collections(projectId), "index.json");
|
|
1733
|
+
},
|
|
1534
1734
|
entries: (projectId, collectionId) => {
|
|
1535
1735
|
return Path.join(pathTo.collection(projectId, collectionId));
|
|
1536
1736
|
},
|
|
@@ -1753,6 +1953,38 @@ var AbstractCrudService = class {
|
|
|
1753
1953
|
}
|
|
1754
1954
|
};
|
|
1755
1955
|
|
|
1956
|
+
//#endregion
|
|
1957
|
+
//#region src/service/migrations/applyMigrations.ts
|
|
1958
|
+
function applyMigrations(data, migrations, targetVersion) {
|
|
1959
|
+
let current = structuredClone(data);
|
|
1960
|
+
while (current["coreVersion"] !== targetVersion) {
|
|
1961
|
+
const migration = migrations.find((m) => m.from === current["coreVersion"]);
|
|
1962
|
+
if (!migration) {
|
|
1963
|
+
current["coreVersion"] = targetVersion;
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1966
|
+
current = migration.run(current);
|
|
1967
|
+
current["coreVersion"] = migration.to;
|
|
1968
|
+
}
|
|
1969
|
+
return current;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
//#endregion
|
|
1973
|
+
//#region src/service/migrations/assetMigrations.ts
|
|
1974
|
+
const assetMigrations = [];
|
|
1975
|
+
|
|
1976
|
+
//#endregion
|
|
1977
|
+
//#region src/service/migrations/collectionMigrations.ts
|
|
1978
|
+
const collectionMigrations = [];
|
|
1979
|
+
|
|
1980
|
+
//#endregion
|
|
1981
|
+
//#region src/service/migrations/entryMigrations.ts
|
|
1982
|
+
const entryMigrations = [];
|
|
1983
|
+
|
|
1984
|
+
//#endregion
|
|
1985
|
+
//#region src/service/migrations/projectMigrations.ts
|
|
1986
|
+
const projectMigrations = [];
|
|
1987
|
+
|
|
1756
1988
|
//#endregion
|
|
1757
1989
|
//#region src/util/shared.ts
|
|
1758
1990
|
/**
|
|
@@ -1795,10 +2027,12 @@ function slug(string) {
|
|
|
1795
2027
|
* Service that manages CRUD functionality for Asset files on disk
|
|
1796
2028
|
*/
|
|
1797
2029
|
var AssetService = class extends AbstractCrudService {
|
|
2030
|
+
coreVersion;
|
|
1798
2031
|
jsonFileService;
|
|
1799
2032
|
gitService;
|
|
1800
|
-
constructor(options, logService, jsonFileService, gitService) {
|
|
2033
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService) {
|
|
1801
2034
|
super(serviceTypeSchema.enum.Asset, options, logService);
|
|
2035
|
+
this.coreVersion = coreVersion;
|
|
1802
2036
|
this.jsonFileService = jsonFileService;
|
|
1803
2037
|
this.gitService = gitService;
|
|
1804
2038
|
}
|
|
@@ -1818,6 +2052,7 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1818
2052
|
name: slug(props.name),
|
|
1819
2053
|
objectType: "asset",
|
|
1820
2054
|
id,
|
|
2055
|
+
coreVersion: this.coreVersion,
|
|
1821
2056
|
created: datetime(),
|
|
1822
2057
|
updated: null,
|
|
1823
2058
|
extension: fileType.extension,
|
|
@@ -1862,6 +2097,13 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1862
2097
|
}
|
|
1863
2098
|
}
|
|
1864
2099
|
/**
|
|
2100
|
+
* Returns the commit history of an Asset
|
|
2101
|
+
*/
|
|
2102
|
+
async history(props) {
|
|
2103
|
+
assetHistorySchema.parse(props);
|
|
2104
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.assetFile(props.projectId, props.id) });
|
|
2105
|
+
}
|
|
2106
|
+
/**
|
|
1865
2107
|
* Copies an Asset to given file path on disk
|
|
1866
2108
|
*/
|
|
1867
2109
|
async save(props) {
|
|
@@ -1970,13 +2212,11 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1970
2212
|
* @param projectId The project's ID
|
|
1971
2213
|
* @param assetFile The AssetFile to convert
|
|
1972
2214
|
*/
|
|
1973
|
-
|
|
2215
|
+
toAsset(projectId, assetFile, commitHash) {
|
|
1974
2216
|
const assetPath = commitHash ? pathTo.tmpAsset(assetFile.id, commitHash, assetFile.extension) : pathTo.asset(projectId, assetFile.id, assetFile.extension);
|
|
1975
|
-
const history = await this.gitService.log(pathTo.project(projectId), { filePath: pathTo.assetFile(projectId, assetFile.id) });
|
|
1976
2217
|
return {
|
|
1977
2218
|
...assetFile,
|
|
1978
|
-
absolutePath: assetPath
|
|
1979
|
-
history
|
|
2219
|
+
absolutePath: assetPath
|
|
1980
2220
|
};
|
|
1981
2221
|
}
|
|
1982
2222
|
/**
|
|
@@ -1999,7 +2239,8 @@ var AssetService = class extends AbstractCrudService {
|
|
|
1999
2239
|
* Migrates an potentially outdated Asset file to the current schema
|
|
2000
2240
|
*/
|
|
2001
2241
|
migrate(potentiallyOutdatedAssetFile) {
|
|
2002
|
-
|
|
2242
|
+
const migrated = applyMigrations(migrateAssetSchema.parse(potentiallyOutdatedAssetFile), assetMigrations, this.coreVersion);
|
|
2243
|
+
return assetFileSchema.parse(migrated);
|
|
2003
2244
|
}
|
|
2004
2245
|
};
|
|
2005
2246
|
|
|
@@ -2009,29 +2250,59 @@ var AssetService = class extends AbstractCrudService {
|
|
|
2009
2250
|
* Service that manages CRUD functionality for Collection files on disk
|
|
2010
2251
|
*/
|
|
2011
2252
|
var CollectionService = class extends AbstractCrudService {
|
|
2253
|
+
coreVersion;
|
|
2012
2254
|
jsonFileService;
|
|
2013
2255
|
gitService;
|
|
2014
|
-
|
|
2256
|
+
/** In-memory cache for collection indices, keyed by projectId */
|
|
2257
|
+
cachedIndex = /* @__PURE__ */ new Map();
|
|
2258
|
+
/** Promise deduplication for concurrent rebuilds, keyed by projectId */
|
|
2259
|
+
rebuildPromise = /* @__PURE__ */ new Map();
|
|
2260
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService) {
|
|
2015
2261
|
super(serviceTypeSchema.enum.Collection, options, logService);
|
|
2262
|
+
this.coreVersion = coreVersion;
|
|
2016
2263
|
this.jsonFileService = jsonFileService;
|
|
2017
2264
|
this.gitService = gitService;
|
|
2018
2265
|
}
|
|
2019
2266
|
/**
|
|
2267
|
+
* Resolves a UUID-or-slug string to a collection UUID.
|
|
2268
|
+
*
|
|
2269
|
+
* If the input matches UUID format, verifies the folder exists on disk first.
|
|
2270
|
+
* If the folder doesn't exist, falls back to slug lookup.
|
|
2271
|
+
* Otherwise, looks up via the index.
|
|
2272
|
+
*/
|
|
2273
|
+
async resolveCollectionId(props) {
|
|
2274
|
+
if (uuidSchema.safeParse(props.idOrSlug).success) {
|
|
2275
|
+
const collectionPath = pathTo.collection(props.projectId, props.idOrSlug);
|
|
2276
|
+
if (await Fs.pathExists(collectionPath)) return props.idOrSlug;
|
|
2277
|
+
}
|
|
2278
|
+
const index = await this.getIndex(props.projectId);
|
|
2279
|
+
for (const [uuid, slugValue] of Object.entries(index)) if (slugValue === props.idOrSlug) return uuid;
|
|
2280
|
+
this.cachedIndex.delete(props.projectId);
|
|
2281
|
+
const freshIndex = await this.getIndex(props.projectId);
|
|
2282
|
+
for (const [uuid, slugValue] of Object.entries(freshIndex)) if (slugValue === props.idOrSlug) return uuid;
|
|
2283
|
+
throw new Error(`Collection not found: "${props.idOrSlug}" does not match any collection UUID or slug`);
|
|
2284
|
+
}
|
|
2285
|
+
/**
|
|
2020
2286
|
* Creates a new Collection
|
|
2021
2287
|
*/
|
|
2022
2288
|
async create(props) {
|
|
2023
2289
|
createCollectionSchema.parse(props);
|
|
2290
|
+
this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
|
|
2024
2291
|
const id = uuid();
|
|
2025
2292
|
const projectPath = pathTo.project(props.projectId);
|
|
2026
2293
|
const collectionPath = pathTo.collection(props.projectId, id);
|
|
2027
2294
|
const collectionFilePath = pathTo.collectionFile(props.projectId, id);
|
|
2295
|
+
const slugPlural = slug(props.slug.plural);
|
|
2296
|
+
const index = await this.getIndex(props.projectId);
|
|
2297
|
+
if (Object.values(index).includes(slugPlural)) throw new Error(`Collection slug "${slugPlural}" is already in use by another collection`);
|
|
2028
2298
|
const collectionFile = {
|
|
2029
2299
|
...props,
|
|
2030
2300
|
objectType: "collection",
|
|
2031
2301
|
id,
|
|
2302
|
+
coreVersion: this.coreVersion,
|
|
2032
2303
|
slug: {
|
|
2033
2304
|
singular: slug(props.slug.singular),
|
|
2034
|
-
plural:
|
|
2305
|
+
plural: slugPlural
|
|
2035
2306
|
},
|
|
2036
2307
|
created: datetime(),
|
|
2037
2308
|
updated: null
|
|
@@ -2046,7 +2317,9 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2046
2317
|
id
|
|
2047
2318
|
}
|
|
2048
2319
|
});
|
|
2049
|
-
|
|
2320
|
+
index[id] = slugPlural;
|
|
2321
|
+
await this.writeIndex(props.projectId, index);
|
|
2322
|
+
return this.toCollection(collectionFile);
|
|
2050
2323
|
}
|
|
2051
2324
|
/**
|
|
2052
2325
|
* Returns a Collection by ID
|
|
@@ -2057,33 +2330,103 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2057
2330
|
readCollectionSchema.parse(props);
|
|
2058
2331
|
if (!props.commitHash) {
|
|
2059
2332
|
const collectionFile = await this.jsonFileService.read(pathTo.collectionFile(props.projectId, props.id), collectionFileSchema);
|
|
2060
|
-
return this.toCollection(
|
|
2333
|
+
return this.toCollection(collectionFile);
|
|
2061
2334
|
} else {
|
|
2062
2335
|
const collectionFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.collectionFile(props.projectId, props.id), props.commitHash)));
|
|
2063
|
-
return this.toCollection(
|
|
2336
|
+
return this.toCollection(collectionFile);
|
|
2064
2337
|
}
|
|
2065
2338
|
}
|
|
2066
2339
|
/**
|
|
2340
|
+
* Reads a Collection by its slug
|
|
2341
|
+
*/
|
|
2342
|
+
async readBySlug(props) {
|
|
2343
|
+
const id = await this.resolveCollectionId({
|
|
2344
|
+
projectId: props.projectId,
|
|
2345
|
+
idOrSlug: props.slug
|
|
2346
|
+
});
|
|
2347
|
+
return this.read({
|
|
2348
|
+
projectId: props.projectId,
|
|
2349
|
+
id,
|
|
2350
|
+
commitHash: props.commitHash
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* Returns the commit history of a Collection
|
|
2355
|
+
*/
|
|
2356
|
+
async history(props) {
|
|
2357
|
+
collectionHistorySchema.parse(props);
|
|
2358
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.collectionFile(props.projectId, props.id) });
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2067
2361
|
* Updates given Collection
|
|
2068
2362
|
*
|
|
2069
|
-
*
|
|
2070
|
-
*
|
|
2071
|
-
* @param projectId Project ID of the collection to update
|
|
2072
|
-
* @param collection Collection to write to disk
|
|
2073
|
-
* @returns An object containing information about the actions needed to be taken,
|
|
2074
|
-
* before given update can be executed or void if the update was executed successfully
|
|
2363
|
+
* Handles fieldDefinition slug rename cascade and collection slug uniqueness.
|
|
2075
2364
|
*/
|
|
2076
2365
|
async update(props) {
|
|
2077
2366
|
updateCollectionSchema.parse(props);
|
|
2367
|
+
this.validateFieldDefinitionSlugUniqueness(props.fieldDefinitions);
|
|
2078
2368
|
const projectPath = pathTo.project(props.projectId);
|
|
2079
2369
|
const collectionFilePath = pathTo.collectionFile(props.projectId, props.id);
|
|
2370
|
+
const prevCollectionFile = await this.read(props);
|
|
2080
2371
|
const collectionFile = {
|
|
2081
|
-
...
|
|
2372
|
+
...prevCollectionFile,
|
|
2082
2373
|
...props,
|
|
2083
2374
|
updated: datetime()
|
|
2084
2375
|
};
|
|
2376
|
+
const oldFieldDefs = prevCollectionFile.fieldDefinitions;
|
|
2377
|
+
const newFieldDefs = props.fieldDefinitions;
|
|
2378
|
+
const slugRenames = [];
|
|
2379
|
+
const oldByUuid = new Map(oldFieldDefs.map((fd) => [fd.id, fd]));
|
|
2380
|
+
for (const newFd of newFieldDefs) {
|
|
2381
|
+
const oldFd = oldByUuid.get(newFd.id);
|
|
2382
|
+
if (oldFd && oldFd.slug !== newFd.slug) slugRenames.push({
|
|
2383
|
+
oldSlug: oldFd.slug,
|
|
2384
|
+
newSlug: newFd.slug
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
const filesToGitAdd = [collectionFilePath];
|
|
2388
|
+
if (slugRenames.length > 0) {
|
|
2389
|
+
const entriesPath = pathTo.entries(props.projectId, props.id);
|
|
2390
|
+
if (await Fs.pathExists(entriesPath)) {
|
|
2391
|
+
const entryFiles = (await Fs.readdir(entriesPath)).filter((f) => f.endsWith(".json") && f !== "collection.json");
|
|
2392
|
+
for (const entryFileName of entryFiles) {
|
|
2393
|
+
const entryFilePath = pathTo.entryFile(props.projectId, props.id, entryFileName.replace(".json", ""));
|
|
2394
|
+
try {
|
|
2395
|
+
const entryFile = await this.jsonFileService.read(entryFilePath, entryFileSchema);
|
|
2396
|
+
let changed = false;
|
|
2397
|
+
const newValues = { ...entryFile.values };
|
|
2398
|
+
for (const { oldSlug, newSlug } of slugRenames) if (oldSlug in newValues) {
|
|
2399
|
+
newValues[newSlug] = newValues[oldSlug];
|
|
2400
|
+
delete newValues[oldSlug];
|
|
2401
|
+
changed = true;
|
|
2402
|
+
}
|
|
2403
|
+
if (changed) {
|
|
2404
|
+
const updatedEntryFile = {
|
|
2405
|
+
...entryFile,
|
|
2406
|
+
values: newValues
|
|
2407
|
+
};
|
|
2408
|
+
await this.jsonFileService.update(updatedEntryFile, entryFilePath, entryFileSchema);
|
|
2409
|
+
filesToGitAdd.push(entryFilePath);
|
|
2410
|
+
}
|
|
2411
|
+
} catch (error) {
|
|
2412
|
+
this.logService.warn({
|
|
2413
|
+
source: "core",
|
|
2414
|
+
message: `Failed to update entry "${entryFileName}" during slug rename cascade: ${error instanceof Error ? error.message : String(error)}`
|
|
2415
|
+
});
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
const newSlugPlural = slug(props.slug.plural);
|
|
2421
|
+
if (prevCollectionFile.slug.plural !== newSlugPlural) {
|
|
2422
|
+
const index = await this.getIndex(props.projectId);
|
|
2423
|
+
const existingUuid = Object.entries(index).find(([, s]) => s === newSlugPlural);
|
|
2424
|
+
if (existingUuid && existingUuid[0] !== props.id) throw new Error(`Collection slug "${newSlugPlural}" is already in use by another collection`);
|
|
2425
|
+
index[props.id] = newSlugPlural;
|
|
2426
|
+
await this.writeIndex(props.projectId, index);
|
|
2427
|
+
}
|
|
2085
2428
|
await this.jsonFileService.update(collectionFile, collectionFilePath, collectionFileSchema);
|
|
2086
|
-
await this.gitService.add(projectPath,
|
|
2429
|
+
await this.gitService.add(projectPath, filesToGitAdd);
|
|
2087
2430
|
await this.gitService.commit(projectPath, {
|
|
2088
2431
|
method: "update",
|
|
2089
2432
|
reference: {
|
|
@@ -2091,10 +2434,10 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2091
2434
|
id: collectionFile.id
|
|
2092
2435
|
}
|
|
2093
2436
|
});
|
|
2094
|
-
return this.toCollection(
|
|
2437
|
+
return this.toCollection(collectionFile);
|
|
2095
2438
|
}
|
|
2096
2439
|
/**
|
|
2097
|
-
* Deletes given Collection (folder), including it's
|
|
2440
|
+
* Deletes given Collection (folder), including it's Entries
|
|
2098
2441
|
*
|
|
2099
2442
|
* The Fields that Collection used are not deleted.
|
|
2100
2443
|
*/
|
|
@@ -2111,6 +2454,9 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2111
2454
|
id: props.id
|
|
2112
2455
|
}
|
|
2113
2456
|
});
|
|
2457
|
+
const index = await this.getIndex(props.projectId);
|
|
2458
|
+
delete index[props.id];
|
|
2459
|
+
await this.writeIndex(props.projectId, index);
|
|
2114
2460
|
}
|
|
2115
2461
|
async list(props) {
|
|
2116
2462
|
listCollectionsSchema.parse(props);
|
|
@@ -2145,7 +2491,8 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2145
2491
|
* Migrates an potentially outdated Collection file to the current schema
|
|
2146
2492
|
*/
|
|
2147
2493
|
migrate(potentiallyOutdatedCollectionFile) {
|
|
2148
|
-
|
|
2494
|
+
const migrated = applyMigrations(migrateCollectionSchema.parse(potentiallyOutdatedCollectionFile), collectionMigrations, this.coreVersion);
|
|
2495
|
+
return collectionFileSchema.parse(migrated);
|
|
2149
2496
|
}
|
|
2150
2497
|
/**
|
|
2151
2498
|
* Creates an Collection from given CollectionFile
|
|
@@ -2153,26 +2500,83 @@ var CollectionService = class extends AbstractCrudService {
|
|
|
2153
2500
|
* @param projectId The project's ID
|
|
2154
2501
|
* @param collectionFile The CollectionFile to convert
|
|
2155
2502
|
*/
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
return {
|
|
2159
|
-
...collectionFile,
|
|
2160
|
-
history
|
|
2161
|
-
};
|
|
2503
|
+
toCollection(collectionFile) {
|
|
2504
|
+
return { ...collectionFile };
|
|
2162
2505
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2506
|
+
/**
|
|
2507
|
+
* Gets the collection index, rebuilding from disk if not cached
|
|
2508
|
+
*/
|
|
2509
|
+
async getIndex(projectId) {
|
|
2510
|
+
const cached = this.cachedIndex.get(projectId);
|
|
2511
|
+
if (cached) return cached;
|
|
2512
|
+
const pending = this.rebuildPromise.get(projectId);
|
|
2513
|
+
if (pending) return pending;
|
|
2514
|
+
const promise = this.rebuildIndex(projectId);
|
|
2515
|
+
this.rebuildPromise.set(projectId, promise);
|
|
2516
|
+
const result = await promise;
|
|
2517
|
+
this.cachedIndex.set(projectId, result);
|
|
2518
|
+
this.rebuildPromise.delete(projectId);
|
|
2519
|
+
return result;
|
|
2520
|
+
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Writes the index file atomically and updates cache
|
|
2523
|
+
*/
|
|
2524
|
+
async writeIndex(projectId, index) {
|
|
2525
|
+
const indexPath = pathTo.collectionIndex(projectId);
|
|
2526
|
+
await Fs.writeFile(indexPath, JSON.stringify(index, null, 2), { encoding: "utf8" });
|
|
2527
|
+
this.cachedIndex.set(projectId, index);
|
|
2528
|
+
}
|
|
2529
|
+
/**
|
|
2530
|
+
* Rebuilds the index by scanning all collection folders
|
|
2531
|
+
*/
|
|
2532
|
+
async rebuildIndex(projectId) {
|
|
2533
|
+
this.logService.info({
|
|
2534
|
+
source: "core",
|
|
2535
|
+
message: `Rebuilding Collection index for Project "${projectId}"`
|
|
2536
|
+
});
|
|
2537
|
+
const index = {};
|
|
2538
|
+
const collectionFolders = await folders(pathTo.collections(projectId));
|
|
2539
|
+
for (const folder of collectionFolders) {
|
|
2540
|
+
if (!uuidSchema.safeParse(folder.name).success) continue;
|
|
2541
|
+
try {
|
|
2542
|
+
const collectionFilePath = pathTo.collectionFile(projectId, folder.name);
|
|
2543
|
+
const collectionFile = await this.jsonFileService.read(collectionFilePath, collectionFileSchema);
|
|
2544
|
+
index[collectionFile.id] = collectionFile.slug.plural;
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
this.logService.warn({
|
|
2547
|
+
source: "core",
|
|
2548
|
+
message: `Skipping collection folder "${folder.name}" during index rebuild: ${error instanceof Error ? error.message : String(error)}`
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
await this.writeIndex(projectId, index);
|
|
2553
|
+
return index;
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Validates that no two fieldDefinitions share the same slug
|
|
2557
|
+
*/
|
|
2558
|
+
validateFieldDefinitionSlugUniqueness(fieldDefinitions) {
|
|
2559
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2560
|
+
for (const fd of fieldDefinitions) {
|
|
2561
|
+
if (seen.has(fd.slug)) throw new Error(`Duplicate fieldDefinition slug "${fd.slug}": each fieldDefinition within a collection must have a unique slug`);
|
|
2562
|
+
seen.add(fd.slug);
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
};
|
|
2566
|
+
|
|
2165
2567
|
//#endregion
|
|
2166
2568
|
//#region src/service/EntryService.ts
|
|
2167
2569
|
/**
|
|
2168
2570
|
* Service that manages CRUD functionality for Entry files on disk
|
|
2169
2571
|
*/
|
|
2170
2572
|
var EntryService = class extends AbstractCrudService {
|
|
2573
|
+
coreVersion;
|
|
2171
2574
|
jsonFileService;
|
|
2172
2575
|
gitService;
|
|
2173
2576
|
collectionService;
|
|
2174
|
-
constructor(options, logService, jsonFileService, gitService, collectionService) {
|
|
2577
|
+
constructor(coreVersion, options, logService, jsonFileService, gitService, collectionService) {
|
|
2175
2578
|
super(serviceTypeSchema.enum.Entry, options, logService);
|
|
2579
|
+
this.coreVersion = coreVersion;
|
|
2176
2580
|
this.jsonFileService = jsonFileService;
|
|
2177
2581
|
this.gitService = gitService;
|
|
2178
2582
|
this.collectionService = collectionService;
|
|
@@ -2192,11 +2596,12 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2192
2596
|
const entryFile = {
|
|
2193
2597
|
objectType: "entry",
|
|
2194
2598
|
id,
|
|
2599
|
+
coreVersion: this.coreVersion,
|
|
2195
2600
|
values: props.values,
|
|
2196
2601
|
created: datetime(),
|
|
2197
2602
|
updated: null
|
|
2198
2603
|
};
|
|
2199
|
-
const entry =
|
|
2604
|
+
const entry = this.toEntry(entryFile);
|
|
2200
2605
|
getCreateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
|
|
2201
2606
|
await this.jsonFileService.create(entryFile, entryFilePath, entryFileSchema);
|
|
2202
2607
|
await this.gitService.add(projectPath, [entryFilePath]);
|
|
@@ -2219,13 +2624,20 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2219
2624
|
readEntrySchema.parse(props);
|
|
2220
2625
|
if (!props.commitHash) {
|
|
2221
2626
|
const entryFile = await this.jsonFileService.read(pathTo.entryFile(props.projectId, props.collectionId, props.id), entryFileSchema);
|
|
2222
|
-
return this.toEntry(
|
|
2627
|
+
return this.toEntry(entryFile);
|
|
2223
2628
|
} else {
|
|
2224
2629
|
const entryFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.projectId), pathTo.entryFile(props.projectId, props.collectionId, props.id), props.commitHash)));
|
|
2225
|
-
return this.toEntry(
|
|
2630
|
+
return this.toEntry(entryFile);
|
|
2226
2631
|
}
|
|
2227
2632
|
}
|
|
2228
2633
|
/**
|
|
2634
|
+
* Returns the commit history of an Entry
|
|
2635
|
+
*/
|
|
2636
|
+
async history(props) {
|
|
2637
|
+
entryHistorySchema.parse(props);
|
|
2638
|
+
return this.gitService.log(pathTo.project(props.projectId), { filePath: pathTo.entryFile(props.projectId, props.collectionId, props.id) });
|
|
2639
|
+
}
|
|
2640
|
+
/**
|
|
2229
2641
|
* Updates an Entry of given Collection with new Values and shared Values
|
|
2230
2642
|
*/
|
|
2231
2643
|
async update(props) {
|
|
@@ -2245,7 +2657,7 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2245
2657
|
values: props.values,
|
|
2246
2658
|
updated: datetime()
|
|
2247
2659
|
};
|
|
2248
|
-
const entry =
|
|
2660
|
+
const entry = this.toEntry(entryFile);
|
|
2249
2661
|
getUpdateEntrySchemaFromFieldDefinitions(collection.fieldDefinitions).parse(props);
|
|
2250
2662
|
await this.jsonFileService.update(entryFile, entryFilePath, entryFileSchema);
|
|
2251
2663
|
await this.gitService.add(projectPath, [entryFilePath]);
|
|
@@ -2311,17 +2723,14 @@ var EntryService = class extends AbstractCrudService {
|
|
|
2311
2723
|
* Migrates an potentially outdated Entry file to the current schema
|
|
2312
2724
|
*/
|
|
2313
2725
|
migrate(potentiallyOutdatedEntryFile) {
|
|
2314
|
-
|
|
2726
|
+
const migrated = applyMigrations(migrateEntrySchema.parse(potentiallyOutdatedEntryFile), entryMigrations, this.coreVersion);
|
|
2727
|
+
return entryFileSchema.parse(migrated);
|
|
2315
2728
|
}
|
|
2316
2729
|
/**
|
|
2317
2730
|
* Creates an Entry from given EntryFile by resolving it's Values
|
|
2318
2731
|
*/
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
return {
|
|
2322
|
-
...entryFile,
|
|
2323
|
-
history
|
|
2324
|
-
};
|
|
2732
|
+
toEntry(entryFile) {
|
|
2733
|
+
return { ...entryFile };
|
|
2325
2734
|
}
|
|
2326
2735
|
};
|
|
2327
2736
|
|
|
@@ -2350,10 +2759,11 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2350
2759
|
id
|
|
2351
2760
|
];
|
|
2352
2761
|
if (props.hash) args = [...args, props.hash];
|
|
2762
|
+
const fullMessage = `${this.serializeTagMessage(props.message)}\n\n${this.tagMessageToTrailers(props.message).join("\n")}`;
|
|
2353
2763
|
args = [
|
|
2354
2764
|
...args,
|
|
2355
2765
|
"-m",
|
|
2356
|
-
|
|
2766
|
+
fullMessage
|
|
2357
2767
|
];
|
|
2358
2768
|
await this.git(props.path, args);
|
|
2359
2769
|
return await this.read({
|
|
@@ -2412,27 +2822,33 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2412
2822
|
async list(props) {
|
|
2413
2823
|
listGitTagsSchema.parse(props);
|
|
2414
2824
|
let args = ["tag", "--list"];
|
|
2825
|
+
const format = [
|
|
2826
|
+
"%(refname:short)",
|
|
2827
|
+
"%(trailers:key=Type,valueonly)",
|
|
2828
|
+
"%(trailers:key=Version,valueonly)",
|
|
2829
|
+
"%(trailers:key=Core-Version,valueonly)",
|
|
2830
|
+
"%(*authorname)",
|
|
2831
|
+
"%(*authoremail)",
|
|
2832
|
+
"%(*authordate:iso-strict)"
|
|
2833
|
+
].join("|");
|
|
2415
2834
|
args = [
|
|
2416
2835
|
...args,
|
|
2417
2836
|
"--sort=-*authordate",
|
|
2418
|
-
|
|
2837
|
+
`--format=${format}`
|
|
2419
2838
|
];
|
|
2420
|
-
const gitTags = (await this.git(props.path, args)).stdout.split("\n").filter((line) => {
|
|
2839
|
+
const gitTags = (await this.git(props.path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
|
|
2421
2840
|
return line.trim() !== "";
|
|
2422
2841
|
}).map((line) => {
|
|
2423
2842
|
const lineArray = line.split("|");
|
|
2424
|
-
if (lineArray[
|
|
2425
|
-
lineArray[3] = lineArray[3].slice(1, -1);
|
|
2426
|
-
lineArray[3] = lineArray[3].slice(0, -1);
|
|
2427
|
-
}
|
|
2843
|
+
if (lineArray[5]?.startsWith("<") && lineArray[5]?.endsWith(">")) lineArray[5] = lineArray[5].slice(1, -1);
|
|
2428
2844
|
return {
|
|
2429
2845
|
id: lineArray[0],
|
|
2430
|
-
message: lineArray[1],
|
|
2846
|
+
message: this.parseTagTrailers(lineArray[1]?.trim(), lineArray[2]?.trim(), lineArray[3]?.trim()),
|
|
2431
2847
|
author: {
|
|
2432
|
-
name: lineArray[
|
|
2433
|
-
email: lineArray[
|
|
2848
|
+
name: lineArray[4],
|
|
2849
|
+
email: lineArray[5]
|
|
2434
2850
|
},
|
|
2435
|
-
datetime: datetime(lineArray[
|
|
2851
|
+
datetime: datetime(lineArray[6])
|
|
2436
2852
|
};
|
|
2437
2853
|
}).filter(this.isGitTag.bind(this));
|
|
2438
2854
|
return {
|
|
@@ -2455,6 +2871,43 @@ var GitTagService = class extends AbstractCrudService {
|
|
|
2455
2871
|
return (await this.list({ path: props.path })).total;
|
|
2456
2872
|
}
|
|
2457
2873
|
/**
|
|
2874
|
+
* Serializes a GitTagMessage into a human-readable subject line
|
|
2875
|
+
*/
|
|
2876
|
+
serializeTagMessage(message) {
|
|
2877
|
+
return `${message.type.charAt(0).toUpperCase() + message.type.slice(1)} ${message.type === "upgrade" ? message.coreVersion : message.version}`;
|
|
2878
|
+
}
|
|
2879
|
+
/**
|
|
2880
|
+
* Converts a GitTagMessage into git trailer lines
|
|
2881
|
+
*/
|
|
2882
|
+
tagMessageToTrailers(message) {
|
|
2883
|
+
const trailers = [`Type: ${message.type}`];
|
|
2884
|
+
if (message.type === "upgrade") trailers.push(`Core-Version: ${message.coreVersion}`);
|
|
2885
|
+
else trailers.push(`Version: ${message.version}`);
|
|
2886
|
+
return trailers;
|
|
2887
|
+
}
|
|
2888
|
+
/**
|
|
2889
|
+
* Parses git trailer values back into a GitTagMessage
|
|
2890
|
+
*/
|
|
2891
|
+
parseTagTrailers(type, version, coreVersion) {
|
|
2892
|
+
switch (type) {
|
|
2893
|
+
case "upgrade": return gitTagMessageSchema.parse({
|
|
2894
|
+
type,
|
|
2895
|
+
coreVersion
|
|
2896
|
+
});
|
|
2897
|
+
case "release":
|
|
2898
|
+
case "preview": return gitTagMessageSchema.parse({
|
|
2899
|
+
type,
|
|
2900
|
+
version
|
|
2901
|
+
});
|
|
2902
|
+
default:
|
|
2903
|
+
this.logService.warn({
|
|
2904
|
+
source: "core",
|
|
2905
|
+
message: `Tag with ID "${type}" has an invalid or missing Type trailer and will be ignored`
|
|
2906
|
+
});
|
|
2907
|
+
return null;
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
/**
|
|
2458
2911
|
* Type guard for GitTag
|
|
2459
2912
|
*
|
|
2460
2913
|
* @param obj The object to check
|
|
@@ -2744,9 +3197,16 @@ var GitService = class {
|
|
|
2744
3197
|
gitMessageSchema.parse(message);
|
|
2745
3198
|
const user = await this.userService.get();
|
|
2746
3199
|
if (!user) throw new NoCurrentUserError();
|
|
3200
|
+
const subject = `${message.method.charAt(0).toUpperCase() + message.method.slice(1)} ${message.reference.objectType} ${message.reference.id}`;
|
|
3201
|
+
const trailers = [
|
|
3202
|
+
`Method: ${message.method}`,
|
|
3203
|
+
`Object-Type: ${message.reference.objectType}`,
|
|
3204
|
+
`Object-Id: ${message.reference.id}`
|
|
3205
|
+
];
|
|
3206
|
+
if (message.reference.collectionId) trailers.push(`Collection-Id: ${message.reference.collectionId}`);
|
|
2747
3207
|
const args = [
|
|
2748
3208
|
"commit",
|
|
2749
|
-
`--message=${
|
|
3209
|
+
`--message=${`${subject}\n\n${trailers.join("\n")}`}`,
|
|
2750
3210
|
`--author=${user.name} <${user.email}>`
|
|
2751
3211
|
];
|
|
2752
3212
|
await this.git(path, args);
|
|
@@ -2766,33 +3226,52 @@ var GitService = class {
|
|
|
2766
3226
|
let args = ["log"];
|
|
2767
3227
|
if (options?.between?.from) args = [...args, `${options.between.from}..${options.between.to || "HEAD"}`];
|
|
2768
3228
|
if (options?.limit) args = [...args, `--max-count=${options.limit}`];
|
|
2769
|
-
|
|
3229
|
+
const format = [
|
|
3230
|
+
"%H",
|
|
3231
|
+
"%(trailers:key=Method,valueonly)",
|
|
3232
|
+
"%(trailers:key=Object-Type,valueonly)",
|
|
3233
|
+
"%(trailers:key=Object-Id,valueonly)",
|
|
3234
|
+
"%(trailers:key=Collection-Id,valueonly)",
|
|
3235
|
+
"%an",
|
|
3236
|
+
"%ae",
|
|
3237
|
+
"%aI",
|
|
3238
|
+
"%D"
|
|
3239
|
+
].join("|");
|
|
3240
|
+
args = [...args, `--format=${format}`];
|
|
2770
3241
|
if (options?.filePath) args = [
|
|
2771
3242
|
...args,
|
|
2772
3243
|
"--",
|
|
2773
3244
|
options.filePath
|
|
2774
3245
|
];
|
|
2775
|
-
const noEmptyLinesArr = (await this.git(path, args)).stdout.split("\n").filter((line) => {
|
|
3246
|
+
const noEmptyLinesArr = (await this.git(path, args)).stdout.replace(/\n\|/g, "|").split("\n").filter((line) => {
|
|
2776
3247
|
return line.trim() !== "";
|
|
2777
3248
|
});
|
|
2778
3249
|
return (await Promise.all(noEmptyLinesArr.map(async (line) => {
|
|
2779
3250
|
const lineArray = line.split("|");
|
|
2780
|
-
const tagId = this.refNameToTagName(lineArray[
|
|
3251
|
+
const tagId = this.refNameToTagName(lineArray[8]?.trim() || "");
|
|
2781
3252
|
const tag = tagId ? await this.tags.read({
|
|
2782
3253
|
path,
|
|
2783
3254
|
id: tagId
|
|
2784
3255
|
}) : null;
|
|
3256
|
+
const collectionId = lineArray[4]?.trim();
|
|
2785
3257
|
return {
|
|
2786
3258
|
hash: lineArray[0],
|
|
2787
|
-
message:
|
|
3259
|
+
message: {
|
|
3260
|
+
method: lineArray[1]?.trim(),
|
|
3261
|
+
reference: {
|
|
3262
|
+
objectType: lineArray[2]?.trim(),
|
|
3263
|
+
id: lineArray[3]?.trim(),
|
|
3264
|
+
...collectionId ? { collectionId } : {}
|
|
3265
|
+
}
|
|
3266
|
+
},
|
|
2788
3267
|
author: {
|
|
2789
|
-
name: lineArray[
|
|
2790
|
-
email: lineArray[
|
|
3268
|
+
name: lineArray[5],
|
|
3269
|
+
email: lineArray[6]
|
|
2791
3270
|
},
|
|
2792
|
-
datetime: datetime(lineArray[
|
|
3271
|
+
datetime: datetime(lineArray[7]),
|
|
2793
3272
|
tag
|
|
2794
3273
|
};
|
|
2795
|
-
}))).filter(this.isGitCommit
|
|
3274
|
+
}))).filter((obj) => this.isGitCommit(obj));
|
|
2796
3275
|
}
|
|
2797
3276
|
/**
|
|
2798
3277
|
* Retrieves the content of a file at a specific commit
|
|
@@ -2806,6 +3285,35 @@ var GitService = class {
|
|
|
2806
3285
|
};
|
|
2807
3286
|
return (await this.git(path, args, { processCallback: setEncoding })).stdout;
|
|
2808
3287
|
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Lists directory entries at a specific commit
|
|
3290
|
+
*
|
|
3291
|
+
* Useful for discovering what files/folders existed at a past commit,
|
|
3292
|
+
* e.g. to detect deleted collections when comparing branches.
|
|
3293
|
+
*
|
|
3294
|
+
* @see https://git-scm.com/docs/git-ls-tree
|
|
3295
|
+
*
|
|
3296
|
+
* @param path Path to the repository
|
|
3297
|
+
* @param treePath Relative path within the repository to list
|
|
3298
|
+
* @param commitRef Commit hash, branch name, or other git ref
|
|
3299
|
+
*/
|
|
3300
|
+
async listTreeAtCommit(path, treePath, commitRef) {
|
|
3301
|
+
const args = [
|
|
3302
|
+
"ls-tree",
|
|
3303
|
+
"--name-only",
|
|
3304
|
+
commitRef,
|
|
3305
|
+
`${treePath.replace(`${path}${Path.sep}`, "").split("\\").join("/")}/`
|
|
3306
|
+
];
|
|
3307
|
+
try {
|
|
3308
|
+
return (await this.git(path, args)).stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "").map((entry) => {
|
|
3309
|
+
const parts = entry.split("/");
|
|
3310
|
+
return parts[parts.length - 1] || entry;
|
|
3311
|
+
});
|
|
3312
|
+
} catch (error) {
|
|
3313
|
+
if (error instanceof GitError) return [];
|
|
3314
|
+
throw error;
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
2809
3317
|
refNameToTagName(refName) {
|
|
2810
3318
|
const tagName = refName.replace("tag: ", "").trim();
|
|
2811
3319
|
if (tagName === "" || uuidSchema.safeParse(tagName).success === false) return null;
|
|
@@ -3174,7 +3682,6 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3174
3682
|
created: datetime(),
|
|
3175
3683
|
updated: null,
|
|
3176
3684
|
coreVersion: this.coreVersion,
|
|
3177
|
-
status: "todo",
|
|
3178
3685
|
version: "0.0.1"
|
|
3179
3686
|
};
|
|
3180
3687
|
const projectPath = pathTo.project(id);
|
|
@@ -3228,11 +3735,23 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3228
3735
|
const projectFile = await this.jsonFileService.read(pathTo.projectFile(props.id), projectFileSchema);
|
|
3229
3736
|
return await this.toProject(projectFile);
|
|
3230
3737
|
} else {
|
|
3231
|
-
const projectFile = this.migrate(
|
|
3738
|
+
const projectFile = this.migrate(JSON.parse(await this.gitService.getFileContentAtCommit(pathTo.project(props.id), pathTo.projectFile(props.id), props.commitHash)));
|
|
3232
3739
|
return await this.toProject(projectFile);
|
|
3233
3740
|
}
|
|
3234
3741
|
}
|
|
3235
3742
|
/**
|
|
3743
|
+
* Returns the commit history of a Project
|
|
3744
|
+
*/
|
|
3745
|
+
async history(props) {
|
|
3746
|
+
projectHistorySchema.parse(props);
|
|
3747
|
+
const projectPath = pathTo.project(props.id);
|
|
3748
|
+
const fullHistory = await this.gitService.log(projectPath);
|
|
3749
|
+
return {
|
|
3750
|
+
history: await this.gitService.log(projectPath, { filePath: pathTo.projectFile(props.id) }),
|
|
3751
|
+
fullHistory
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
/**
|
|
3236
3755
|
* Updates given Project
|
|
3237
3756
|
*/
|
|
3238
3757
|
async update(props) {
|
|
@@ -3265,7 +3784,7 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3265
3784
|
const projectPath = pathTo.project(props.id);
|
|
3266
3785
|
const projectFilePath = pathTo.projectFile(props.id);
|
|
3267
3786
|
if (await this.gitService.branches.current(projectPath) !== projectBranchSchema.enum.work) await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
|
|
3268
|
-
const currentProjectFile =
|
|
3787
|
+
const currentProjectFile = await this.jsonFileService.unsafeRead(projectFilePath);
|
|
3269
3788
|
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}".`);
|
|
3270
3789
|
if (Semver.eq(currentProjectFile.coreVersion, this.coreVersion) && props.force !== true) throw new ProjectUpgradeError(`The Projects Core version "${currentProjectFile.coreVersion}" is already up to date.`);
|
|
3271
3790
|
const assetReferences = await this.listReferences("asset", props.id);
|
|
@@ -3302,7 +3821,10 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3302
3821
|
});
|
|
3303
3822
|
await this.gitService.tags.create({
|
|
3304
3823
|
path: projectPath,
|
|
3305
|
-
message:
|
|
3824
|
+
message: {
|
|
3825
|
+
type: "upgrade",
|
|
3826
|
+
coreVersion: migratedProjectFile.coreVersion
|
|
3827
|
+
}
|
|
3306
3828
|
});
|
|
3307
3829
|
await this.gitService.branches.delete(projectPath, upgradeBranchName, true);
|
|
3308
3830
|
this.logService.info({
|
|
@@ -3408,7 +3930,7 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3408
3930
|
return (await Promise.all(projectReferences.map(async (reference) => {
|
|
3409
3931
|
const json = await this.jsonFileService.unsafeRead(pathTo.projectFile(reference.id));
|
|
3410
3932
|
const projectFile = migrateProjectSchema.parse(json);
|
|
3411
|
-
if (projectFile.coreVersion !== this.coreVersion) return projectFile;
|
|
3933
|
+
if (projectFile.coreVersion !== this.coreVersion) return this.migrate(projectFile);
|
|
3412
3934
|
return null;
|
|
3413
3935
|
}))).filter(isNotEmpty);
|
|
3414
3936
|
}
|
|
@@ -3440,9 +3962,9 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3440
3962
|
/**
|
|
3441
3963
|
* Migrates an potentially outdated Project file to the current schema
|
|
3442
3964
|
*/
|
|
3443
|
-
migrate(
|
|
3444
|
-
|
|
3445
|
-
return projectFileSchema.parse(
|
|
3965
|
+
migrate(potentiallyOutdatedFile) {
|
|
3966
|
+
const migrated = applyMigrations(migrateProjectSchema.parse(potentiallyOutdatedFile), projectMigrations, this.coreVersion);
|
|
3967
|
+
return projectFileSchema.parse(migrated);
|
|
3446
3968
|
}
|
|
3447
3969
|
/**
|
|
3448
3970
|
* Creates a Project from given ProjectFile
|
|
@@ -3451,13 +3973,9 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3451
3973
|
const projectPath = pathTo.project(projectFile.id);
|
|
3452
3974
|
let remoteOriginUrl = null;
|
|
3453
3975
|
if (await this.gitService.remotes.hasOrigin(projectPath)) remoteOriginUrl = await this.gitService.remotes.getOriginUrl(projectPath);
|
|
3454
|
-
const fullHistory = await this.gitService.log(pathTo.project(projectFile.id));
|
|
3455
|
-
const history = await this.gitService.log(pathTo.project(projectFile.id), { filePath: pathTo.projectFile(projectFile.id) });
|
|
3456
3976
|
return {
|
|
3457
3977
|
...projectFile,
|
|
3458
|
-
remoteOriginUrl
|
|
3459
|
-
history,
|
|
3460
|
-
fullHistory
|
|
3978
|
+
remoteOriginUrl
|
|
3461
3979
|
};
|
|
3462
3980
|
}
|
|
3463
3981
|
/**
|
|
@@ -3487,7 +4005,8 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3487
4005
|
"!/.gitattributes",
|
|
3488
4006
|
"!/**/.gitkeep",
|
|
3489
4007
|
"",
|
|
3490
|
-
"# elek.io related ignores"
|
|
4008
|
+
"# elek.io related ignores",
|
|
4009
|
+
"collections/index.json"
|
|
3491
4010
|
].join(Os.EOL));
|
|
3492
4011
|
}
|
|
3493
4012
|
async upgradeObjectFile(projectId, objectType, reference, collectionId) {
|
|
@@ -3553,6 +4072,665 @@ var ProjectService = class extends AbstractCrudService {
|
|
|
3553
4072
|
}
|
|
3554
4073
|
};
|
|
3555
4074
|
|
|
4075
|
+
//#endregion
|
|
4076
|
+
//#region src/service/ReleaseService.ts
|
|
4077
|
+
/**
|
|
4078
|
+
* Service that manages Release functionality
|
|
4079
|
+
*
|
|
4080
|
+
* A release diffs the current `work` branch against the `production` branch
|
|
4081
|
+
* to determine what changed, computes a semver bump, and merges work into production.
|
|
4082
|
+
*/
|
|
4083
|
+
var ReleaseService = class extends AbstractCrudService {
|
|
4084
|
+
gitService;
|
|
4085
|
+
jsonFileService;
|
|
4086
|
+
projectService;
|
|
4087
|
+
constructor(options, logService, gitService, jsonFileService, projectService) {
|
|
4088
|
+
super(serviceTypeSchema.enum.Release, options, logService);
|
|
4089
|
+
this.gitService = gitService;
|
|
4090
|
+
this.jsonFileService = jsonFileService;
|
|
4091
|
+
this.projectService = projectService;
|
|
4092
|
+
}
|
|
4093
|
+
/**
|
|
4094
|
+
* Prepares a release by diffing the current `work` branch against `production`.
|
|
4095
|
+
*
|
|
4096
|
+
* Returns a read-only summary of all changes and the computed next version.
|
|
4097
|
+
* If there are no changes, the next version and bump will be null.
|
|
4098
|
+
*/
|
|
4099
|
+
async prepare(props) {
|
|
4100
|
+
prepareReleaseSchema.parse(props);
|
|
4101
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4102
|
+
const currentBranch = await this.gitService.branches.current(projectPath);
|
|
4103
|
+
if (currentBranch !== projectBranchSchema.enum.work) throw new Error(`Not on work branch (currently on "${currentBranch}")`);
|
|
4104
|
+
const project = await this.projectService.read({ id: props.projectId });
|
|
4105
|
+
const currentVersion = project.version;
|
|
4106
|
+
const productionRef = projectBranchSchema.enum.production;
|
|
4107
|
+
const productionProject = await this.getProjectAtRef(props.projectId, projectPath, productionRef);
|
|
4108
|
+
const projectDiff = this.diffProject(project, productionProject);
|
|
4109
|
+
const currentCollections = await this.getCollectionsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
|
|
4110
|
+
const productionCollections = await this.getCollectionsAtRef(props.projectId, projectPath, productionRef);
|
|
4111
|
+
const collectionDiff = this.diffCollections(currentCollections, productionCollections);
|
|
4112
|
+
const currentAssets = await this.getAssetsAtRef(props.projectId, projectPath, projectBranchSchema.enum.work);
|
|
4113
|
+
const productionAssets = await this.getAssetsAtRef(props.projectId, projectPath, productionRef);
|
|
4114
|
+
const assetDiff = this.diffAssets(currentAssets, productionAssets);
|
|
4115
|
+
const allCollectionIds = new Set([...currentCollections.map((c) => c.id), ...productionCollections.map((c) => c.id)]);
|
|
4116
|
+
const entryDiff = await this.diffEntries(props.projectId, projectPath, allCollectionIds, productionRef);
|
|
4117
|
+
let finalBump = null;
|
|
4118
|
+
for (const bump of [
|
|
4119
|
+
projectDiff.bump,
|
|
4120
|
+
collectionDiff.bump,
|
|
4121
|
+
assetDiff.bump,
|
|
4122
|
+
entryDiff.bump
|
|
4123
|
+
]) if (bump) finalBump = finalBump ? this.higherBump(finalBump, bump) : bump;
|
|
4124
|
+
if (!finalBump) {
|
|
4125
|
+
if (await this.hasCommitsBetween(projectPath, productionRef, projectBranchSchema.enum.work)) finalBump = "patch";
|
|
4126
|
+
}
|
|
4127
|
+
const nextVersion = finalBump ? Semver.inc(currentVersion, finalBump) : null;
|
|
4128
|
+
return {
|
|
4129
|
+
project,
|
|
4130
|
+
bump: finalBump,
|
|
4131
|
+
currentVersion,
|
|
4132
|
+
nextVersion,
|
|
4133
|
+
projectChanges: projectDiff.projectChanges,
|
|
4134
|
+
collectionChanges: collectionDiff.collectionChanges,
|
|
4135
|
+
fieldChanges: collectionDiff.fieldChanges,
|
|
4136
|
+
assetChanges: assetDiff.assetChanges,
|
|
4137
|
+
entryChanges: entryDiff.entryChanges
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
/**
|
|
4141
|
+
* Creates a release by:
|
|
4142
|
+
* 1. Recomputing the diff (stateless)
|
|
4143
|
+
* 2. Merging `work` into `production`
|
|
4144
|
+
* 3. Updating the project version on `production`
|
|
4145
|
+
* 4. Tagging on `production`
|
|
4146
|
+
* 5. Merging `production` back into `work` (fast-forward to sync the version commit)
|
|
4147
|
+
* 6. Switching back to `work`
|
|
4148
|
+
*/
|
|
4149
|
+
async create(props) {
|
|
4150
|
+
createReleaseSchema.parse(props);
|
|
4151
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4152
|
+
const projectFilePath = pathTo.projectFile(props.projectId);
|
|
4153
|
+
const diff = await this.prepare(props);
|
|
4154
|
+
if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a release: no changes detected since the last full release");
|
|
4155
|
+
const nextVersion = diff.nextVersion;
|
|
4156
|
+
try {
|
|
4157
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.production);
|
|
4158
|
+
await this.gitService.merge(projectPath, projectBranchSchema.enum.work);
|
|
4159
|
+
const updatedProjectFile = {
|
|
4160
|
+
...diff.project,
|
|
4161
|
+
version: nextVersion,
|
|
4162
|
+
updated: datetime()
|
|
4163
|
+
};
|
|
4164
|
+
await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
|
|
4165
|
+
await this.gitService.add(projectPath, [projectFilePath]);
|
|
4166
|
+
await this.gitService.commit(projectPath, {
|
|
4167
|
+
method: "release",
|
|
4168
|
+
reference: {
|
|
4169
|
+
objectType: "project",
|
|
4170
|
+
id: props.projectId
|
|
4171
|
+
}
|
|
4172
|
+
});
|
|
4173
|
+
await this.gitService.tags.create({
|
|
4174
|
+
path: projectPath,
|
|
4175
|
+
message: {
|
|
4176
|
+
type: "release",
|
|
4177
|
+
version: nextVersion
|
|
4178
|
+
}
|
|
4179
|
+
});
|
|
4180
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work);
|
|
4181
|
+
await this.gitService.merge(projectPath, projectBranchSchema.enum.production);
|
|
4182
|
+
} catch (error) {
|
|
4183
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
|
|
4184
|
+
throw error;
|
|
4185
|
+
}
|
|
4186
|
+
this.logService.info({
|
|
4187
|
+
source: "core",
|
|
4188
|
+
message: `Released version ${nextVersion} (${diff.bump} bump)`
|
|
4189
|
+
});
|
|
4190
|
+
return {
|
|
4191
|
+
version: nextVersion,
|
|
4192
|
+
diff
|
|
4193
|
+
};
|
|
4194
|
+
}
|
|
4195
|
+
/**
|
|
4196
|
+
* Creates a preview release by:
|
|
4197
|
+
* 1. Recomputing the diff (stateless)
|
|
4198
|
+
* 2. Computing the preview version (e.g. 1.1.0-preview.3)
|
|
4199
|
+
* 3. Updating the project version on `work`
|
|
4200
|
+
* 4. Tagging on `work` (no merge into production)
|
|
4201
|
+
*
|
|
4202
|
+
* Preview releases are snapshots of the current work state.
|
|
4203
|
+
* They don't promote to production — only full releases do.
|
|
4204
|
+
*/
|
|
4205
|
+
async createPreview(props) {
|
|
4206
|
+
createPreviewReleaseSchema.parse(props);
|
|
4207
|
+
const projectPath = pathTo.project(props.projectId);
|
|
4208
|
+
const projectFilePath = pathTo.projectFile(props.projectId);
|
|
4209
|
+
const diff = await this.prepare(props);
|
|
4210
|
+
if (!diff.bump || !diff.nextVersion) throw new Error("Cannot create a preview release: no changes detected since the last full release");
|
|
4211
|
+
const previewNumber = await this.countPreviewsSinceLastRelease(projectPath, diff.nextVersion);
|
|
4212
|
+
const previewVersion = `${diff.nextVersion}-preview.${previewNumber + 1}`;
|
|
4213
|
+
try {
|
|
4214
|
+
const updatedProjectFile = {
|
|
4215
|
+
...diff.project,
|
|
4216
|
+
version: previewVersion,
|
|
4217
|
+
updated: datetime()
|
|
4218
|
+
};
|
|
4219
|
+
await this.jsonFileService.update(updatedProjectFile, projectFilePath, projectFileSchema);
|
|
4220
|
+
await this.gitService.add(projectPath, [projectFilePath]);
|
|
4221
|
+
await this.gitService.commit(projectPath, {
|
|
4222
|
+
method: "release",
|
|
4223
|
+
reference: {
|
|
4224
|
+
objectType: "project",
|
|
4225
|
+
id: props.projectId
|
|
4226
|
+
}
|
|
4227
|
+
});
|
|
4228
|
+
await this.gitService.tags.create({
|
|
4229
|
+
path: projectPath,
|
|
4230
|
+
message: {
|
|
4231
|
+
type: "preview",
|
|
4232
|
+
version: previewVersion
|
|
4233
|
+
}
|
|
4234
|
+
});
|
|
4235
|
+
} catch (error) {
|
|
4236
|
+
await this.gitService.branches.switch(projectPath, projectBranchSchema.enum.work).catch(() => {});
|
|
4237
|
+
throw error;
|
|
4238
|
+
}
|
|
4239
|
+
this.logService.info({
|
|
4240
|
+
source: "core",
|
|
4241
|
+
message: `Preview released version ${previewVersion} (${diff.bump} bump)`
|
|
4242
|
+
});
|
|
4243
|
+
return {
|
|
4244
|
+
version: previewVersion,
|
|
4245
|
+
diff
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Reads the project file as it exists at a given git ref
|
|
4250
|
+
*/
|
|
4251
|
+
async getProjectAtRef(projectId, projectPath, ref) {
|
|
4252
|
+
try {
|
|
4253
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, pathTo.projectFile(projectId), ref);
|
|
4254
|
+
return projectFileSchema.parse(JSON.parse(content));
|
|
4255
|
+
} catch {
|
|
4256
|
+
return null;
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
/**
|
|
4260
|
+
* Reads asset metadata files as they exist at a given git ref
|
|
4261
|
+
*/
|
|
4262
|
+
async getAssetsAtRef(projectId, projectPath, ref) {
|
|
4263
|
+
const assetsPath = pathTo.assets(projectId);
|
|
4264
|
+
const fileNames = await this.gitService.listTreeAtCommit(projectPath, assetsPath, ref);
|
|
4265
|
+
const assets = [];
|
|
4266
|
+
for (const fileName of fileNames) {
|
|
4267
|
+
if (!fileName.endsWith(".json")) continue;
|
|
4268
|
+
const assetId = fileName.replace(".json", "");
|
|
4269
|
+
const assetFilePath = pathTo.assetFile(projectId, assetId);
|
|
4270
|
+
try {
|
|
4271
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, assetFilePath, ref);
|
|
4272
|
+
const assetFile = assetFileSchema.parse(JSON.parse(content));
|
|
4273
|
+
assets.push(assetFile);
|
|
4274
|
+
} catch {
|
|
4275
|
+
this.logService.debug({
|
|
4276
|
+
source: "core",
|
|
4277
|
+
message: `Skipping asset "${fileName}" at ref "${ref}" during release diff`
|
|
4278
|
+
});
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
return assets;
|
|
4282
|
+
}
|
|
4283
|
+
/**
|
|
4284
|
+
* Reads entry files for a single collection as they exist at a given git ref
|
|
4285
|
+
*/
|
|
4286
|
+
async getEntriesAtRef(projectId, projectPath, collectionId, ref) {
|
|
4287
|
+
const entriesPath = pathTo.entries(projectId, collectionId);
|
|
4288
|
+
const fileNames = await this.gitService.listTreeAtCommit(projectPath, entriesPath, ref);
|
|
4289
|
+
const entries = [];
|
|
4290
|
+
for (const fileName of fileNames) {
|
|
4291
|
+
if (!fileName.endsWith(".json") || fileName === "collection.json") continue;
|
|
4292
|
+
const entryId = fileName.replace(".json", "");
|
|
4293
|
+
const entryFilePath = pathTo.entryFile(projectId, collectionId, entryId);
|
|
4294
|
+
try {
|
|
4295
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, entryFilePath, ref);
|
|
4296
|
+
const entryFile = entryFileSchema.parse(JSON.parse(content));
|
|
4297
|
+
entries.push(entryFile);
|
|
4298
|
+
} catch {
|
|
4299
|
+
this.logService.debug({
|
|
4300
|
+
source: "core",
|
|
4301
|
+
message: `Skipping entry "${fileName}" in collection "${collectionId}" at ref "${ref}" during release diff`
|
|
4302
|
+
});
|
|
4303
|
+
}
|
|
4304
|
+
}
|
|
4305
|
+
return entries;
|
|
4306
|
+
}
|
|
4307
|
+
/**
|
|
4308
|
+
* Reads collections as they exist at a given git ref (branch or commit)
|
|
4309
|
+
*/
|
|
4310
|
+
async getCollectionsAtRef(projectId, projectPath, ref) {
|
|
4311
|
+
const collectionsPath = pathTo.collections(projectId);
|
|
4312
|
+
const folderNames = await this.gitService.listTreeAtCommit(projectPath, collectionsPath, ref);
|
|
4313
|
+
const collections = [];
|
|
4314
|
+
for (const folderName of folderNames) {
|
|
4315
|
+
const collectionFilePath = pathTo.collectionFile(projectId, folderName);
|
|
4316
|
+
try {
|
|
4317
|
+
const content = await this.gitService.getFileContentAtCommit(projectPath, collectionFilePath, ref);
|
|
4318
|
+
const collectionFile = collectionFileSchema.parse(JSON.parse(content));
|
|
4319
|
+
collections.push(collectionFile);
|
|
4320
|
+
} catch {
|
|
4321
|
+
this.logService.debug({
|
|
4322
|
+
source: "core",
|
|
4323
|
+
message: `Skipping folder "${folderName}" at ref "${ref}" during release diff`
|
|
4324
|
+
});
|
|
4325
|
+
}
|
|
4326
|
+
}
|
|
4327
|
+
return collections;
|
|
4328
|
+
}
|
|
4329
|
+
/**
|
|
4330
|
+
* Checks if there are any commits between two refs
|
|
4331
|
+
*/
|
|
4332
|
+
async hasCommitsBetween(projectPath, from, to) {
|
|
4333
|
+
try {
|
|
4334
|
+
return (await this.gitService.log(projectPath, { between: {
|
|
4335
|
+
from,
|
|
4336
|
+
to
|
|
4337
|
+
} })).length > 0;
|
|
4338
|
+
} catch {
|
|
4339
|
+
return true;
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
/**
|
|
4343
|
+
* Diffs two sets of collections and returns all changes with the computed bump level.
|
|
4344
|
+
*
|
|
4345
|
+
* Always collects all changes so they can be displayed to the user.
|
|
4346
|
+
*/
|
|
4347
|
+
diffCollections(currentCollections, productionCollections) {
|
|
4348
|
+
const collectionChanges = [];
|
|
4349
|
+
const fieldChanges = [];
|
|
4350
|
+
let highestBump = null;
|
|
4351
|
+
const currentById = new Map(currentCollections.map((c) => [c.id, c]));
|
|
4352
|
+
const productionById = new Map(productionCollections.map((c) => [c.id, c]));
|
|
4353
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4354
|
+
collectionChanges.push({
|
|
4355
|
+
collectionId: id,
|
|
4356
|
+
changeType: "deleted",
|
|
4357
|
+
bump: "major"
|
|
4358
|
+
});
|
|
4359
|
+
highestBump = "major";
|
|
4360
|
+
}
|
|
4361
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4362
|
+
collectionChanges.push({
|
|
4363
|
+
collectionId: id,
|
|
4364
|
+
changeType: "added",
|
|
4365
|
+
bump: "minor"
|
|
4366
|
+
});
|
|
4367
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4368
|
+
}
|
|
4369
|
+
for (const [id, currentCollection] of currentById) {
|
|
4370
|
+
const productionCollection = productionById.get(id);
|
|
4371
|
+
if (!productionCollection) continue;
|
|
4372
|
+
const changes = this.diffFieldDefinitions(id, currentCollection.fieldDefinitions, productionCollection.fieldDefinitions);
|
|
4373
|
+
fieldChanges.push(...changes);
|
|
4374
|
+
for (const change of changes) highestBump = this.higherBump(highestBump, change.bump);
|
|
4375
|
+
}
|
|
4376
|
+
return {
|
|
4377
|
+
bump: highestBump,
|
|
4378
|
+
collectionChanges,
|
|
4379
|
+
fieldChanges
|
|
4380
|
+
};
|
|
4381
|
+
}
|
|
4382
|
+
/**
|
|
4383
|
+
* Diffs the project file between current and production.
|
|
4384
|
+
*
|
|
4385
|
+
* Skips immutable/system-managed fields (id, objectType, created, updated, version, coreVersion).
|
|
4386
|
+
*/
|
|
4387
|
+
diffProject(current, production) {
|
|
4388
|
+
const projectChanges = [];
|
|
4389
|
+
if (!production) return {
|
|
4390
|
+
bump: null,
|
|
4391
|
+
projectChanges
|
|
4392
|
+
};
|
|
4393
|
+
let highestBump = null;
|
|
4394
|
+
if (current.settings.language.default !== production.settings.language.default) {
|
|
4395
|
+
projectChanges.push({
|
|
4396
|
+
changeType: "defaultLanguageChanged",
|
|
4397
|
+
bump: "major"
|
|
4398
|
+
});
|
|
4399
|
+
highestBump = "major";
|
|
4400
|
+
}
|
|
4401
|
+
const currentSupported = new Set(current.settings.language.supported);
|
|
4402
|
+
const productionSupported = new Set(production.settings.language.supported);
|
|
4403
|
+
for (const lang of productionSupported) if (!currentSupported.has(lang)) {
|
|
4404
|
+
projectChanges.push({
|
|
4405
|
+
changeType: "supportedLanguageRemoved",
|
|
4406
|
+
bump: "major"
|
|
4407
|
+
});
|
|
4408
|
+
highestBump = "major";
|
|
4409
|
+
break;
|
|
4410
|
+
}
|
|
4411
|
+
for (const lang of currentSupported) if (!productionSupported.has(lang)) {
|
|
4412
|
+
projectChanges.push({
|
|
4413
|
+
changeType: "supportedLanguageAdded",
|
|
4414
|
+
bump: "minor"
|
|
4415
|
+
});
|
|
4416
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4417
|
+
break;
|
|
4418
|
+
}
|
|
4419
|
+
if (current.name !== production.name) {
|
|
4420
|
+
projectChanges.push({
|
|
4421
|
+
changeType: "nameChanged",
|
|
4422
|
+
bump: "patch"
|
|
4423
|
+
});
|
|
4424
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4425
|
+
}
|
|
4426
|
+
if (current.description !== production.description) {
|
|
4427
|
+
projectChanges.push({
|
|
4428
|
+
changeType: "descriptionChanged",
|
|
4429
|
+
bump: "patch"
|
|
4430
|
+
});
|
|
4431
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4432
|
+
}
|
|
4433
|
+
return {
|
|
4434
|
+
bump: highestBump,
|
|
4435
|
+
projectChanges
|
|
4436
|
+
};
|
|
4437
|
+
}
|
|
4438
|
+
/**
|
|
4439
|
+
* Diffs two sets of assets and returns all changes with the computed bump level.
|
|
4440
|
+
*/
|
|
4441
|
+
diffAssets(currentAssets, productionAssets) {
|
|
4442
|
+
const assetChanges = [];
|
|
4443
|
+
let highestBump = null;
|
|
4444
|
+
const currentById = new Map(currentAssets.map((a) => [a.id, a]));
|
|
4445
|
+
const productionById = new Map(productionAssets.map((a) => [a.id, a]));
|
|
4446
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4447
|
+
assetChanges.push({
|
|
4448
|
+
assetId: id,
|
|
4449
|
+
changeType: "deleted",
|
|
4450
|
+
bump: "major"
|
|
4451
|
+
});
|
|
4452
|
+
highestBump = "major";
|
|
4453
|
+
}
|
|
4454
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4455
|
+
assetChanges.push({
|
|
4456
|
+
assetId: id,
|
|
4457
|
+
changeType: "added",
|
|
4458
|
+
bump: "minor"
|
|
4459
|
+
});
|
|
4460
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4461
|
+
}
|
|
4462
|
+
for (const [id, current] of currentById) {
|
|
4463
|
+
const production = productionById.get(id);
|
|
4464
|
+
if (!production) continue;
|
|
4465
|
+
if (current.extension !== production.extension || current.mimeType !== production.mimeType || current.size !== production.size) {
|
|
4466
|
+
assetChanges.push({
|
|
4467
|
+
assetId: id,
|
|
4468
|
+
changeType: "binaryChanged",
|
|
4469
|
+
bump: "patch"
|
|
4470
|
+
});
|
|
4471
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4472
|
+
}
|
|
4473
|
+
if (current.name !== production.name || current.description !== production.description) {
|
|
4474
|
+
assetChanges.push({
|
|
4475
|
+
assetId: id,
|
|
4476
|
+
changeType: "metadataChanged",
|
|
4477
|
+
bump: "patch"
|
|
4478
|
+
});
|
|
4479
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
return {
|
|
4483
|
+
bump: highestBump,
|
|
4484
|
+
assetChanges
|
|
4485
|
+
};
|
|
4486
|
+
}
|
|
4487
|
+
/**
|
|
4488
|
+
* Diffs entries across all collections between current and production.
|
|
4489
|
+
*/
|
|
4490
|
+
async diffEntries(projectId, projectPath, allCollectionIds, productionRef) {
|
|
4491
|
+
const entryChanges = [];
|
|
4492
|
+
let highestBump = null;
|
|
4493
|
+
for (const collectionId of allCollectionIds) {
|
|
4494
|
+
const currentEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, projectBranchSchema.enum.work);
|
|
4495
|
+
const productionEntries = await this.getEntriesAtRef(projectId, projectPath, collectionId, productionRef);
|
|
4496
|
+
const currentById = new Map(currentEntries.map((e) => [e.id, e]));
|
|
4497
|
+
const productionById = new Map(productionEntries.map((e) => [e.id, e]));
|
|
4498
|
+
for (const [id] of productionById) if (!currentById.has(id)) {
|
|
4499
|
+
entryChanges.push({
|
|
4500
|
+
collectionId,
|
|
4501
|
+
entryId: id,
|
|
4502
|
+
changeType: "deleted",
|
|
4503
|
+
bump: "major"
|
|
4504
|
+
});
|
|
4505
|
+
highestBump = "major";
|
|
4506
|
+
}
|
|
4507
|
+
for (const [id] of currentById) if (!productionById.has(id)) {
|
|
4508
|
+
entryChanges.push({
|
|
4509
|
+
collectionId,
|
|
4510
|
+
entryId: id,
|
|
4511
|
+
changeType: "added",
|
|
4512
|
+
bump: "minor"
|
|
4513
|
+
});
|
|
4514
|
+
highestBump = this.higherBump(highestBump, "minor");
|
|
4515
|
+
}
|
|
4516
|
+
for (const [id, current] of currentById) {
|
|
4517
|
+
const production = productionById.get(id);
|
|
4518
|
+
if (!production) continue;
|
|
4519
|
+
if (!isDeepStrictEqual(current.values, production.values)) {
|
|
4520
|
+
entryChanges.push({
|
|
4521
|
+
collectionId,
|
|
4522
|
+
entryId: id,
|
|
4523
|
+
changeType: "modified",
|
|
4524
|
+
bump: "patch"
|
|
4525
|
+
});
|
|
4526
|
+
highestBump = this.higherBump(highestBump, "patch");
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
return {
|
|
4531
|
+
bump: highestBump,
|
|
4532
|
+
entryChanges
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
/**
|
|
4536
|
+
* Diffs field definitions of a single collection.
|
|
4537
|
+
*
|
|
4538
|
+
* Matches fields by UUID and classifies each change.
|
|
4539
|
+
* Always collects all changes so they can be displayed to the user.
|
|
4540
|
+
*/
|
|
4541
|
+
diffFieldDefinitions(collectionId, currentFields, productionFields) {
|
|
4542
|
+
const changes = [];
|
|
4543
|
+
const currentById = new Map(currentFields.map((f) => [f.id, f]));
|
|
4544
|
+
const productionById = new Map(productionFields.map((f) => [f.id, f]));
|
|
4545
|
+
for (const [id, field] of productionById) if (!currentById.has(id)) changes.push({
|
|
4546
|
+
collectionId,
|
|
4547
|
+
fieldId: id,
|
|
4548
|
+
fieldSlug: field.slug,
|
|
4549
|
+
changeType: "deleted",
|
|
4550
|
+
bump: "major"
|
|
4551
|
+
});
|
|
4552
|
+
for (const [id, field] of currentById) if (!productionById.has(id)) changes.push({
|
|
4553
|
+
collectionId,
|
|
4554
|
+
fieldId: id,
|
|
4555
|
+
fieldSlug: field.slug,
|
|
4556
|
+
changeType: "added",
|
|
4557
|
+
bump: "minor"
|
|
4558
|
+
});
|
|
4559
|
+
for (const [id, currentField] of currentById) {
|
|
4560
|
+
const productionField = productionById.get(id);
|
|
4561
|
+
if (!productionField) continue;
|
|
4562
|
+
const fieldChanges = this.diffSingleField(collectionId, currentField, productionField);
|
|
4563
|
+
changes.push(...fieldChanges);
|
|
4564
|
+
}
|
|
4565
|
+
return changes;
|
|
4566
|
+
}
|
|
4567
|
+
/**
|
|
4568
|
+
* Compares two versions of the same field definition and returns all detected changes.
|
|
4569
|
+
*
|
|
4570
|
+
* Collects every change on the field so the full diff can be shown to the user.
|
|
4571
|
+
*/
|
|
4572
|
+
diffSingleField(collectionId, current, production) {
|
|
4573
|
+
const changes = [];
|
|
4574
|
+
const base = {
|
|
4575
|
+
collectionId,
|
|
4576
|
+
fieldId: current.id,
|
|
4577
|
+
fieldSlug: current.slug
|
|
4578
|
+
};
|
|
4579
|
+
if (current.valueType !== production.valueType) changes.push({
|
|
4580
|
+
...base,
|
|
4581
|
+
changeType: "valueTypeChanged",
|
|
4582
|
+
bump: "major"
|
|
4583
|
+
});
|
|
4584
|
+
if (current.fieldType !== production.fieldType) changes.push({
|
|
4585
|
+
...base,
|
|
4586
|
+
changeType: "fieldTypeChanged",
|
|
4587
|
+
bump: "major"
|
|
4588
|
+
});
|
|
4589
|
+
if (current.slug !== production.slug) changes.push({
|
|
4590
|
+
...base,
|
|
4591
|
+
changeType: "slugChanged",
|
|
4592
|
+
bump: "major"
|
|
4593
|
+
});
|
|
4594
|
+
if (this.isMinMaxTightened(current, production)) changes.push({
|
|
4595
|
+
...base,
|
|
4596
|
+
changeType: "minMaxTightened",
|
|
4597
|
+
bump: "major"
|
|
4598
|
+
});
|
|
4599
|
+
if (production.isRequired === true && current.isRequired === false) changes.push({
|
|
4600
|
+
...base,
|
|
4601
|
+
changeType: "isRequiredToNotRequired",
|
|
4602
|
+
bump: "major"
|
|
4603
|
+
});
|
|
4604
|
+
if (production.isUnique === true && current.isUnique === false) changes.push({
|
|
4605
|
+
...base,
|
|
4606
|
+
changeType: "isUniqueToNotUnique",
|
|
4607
|
+
bump: "major"
|
|
4608
|
+
});
|
|
4609
|
+
if (current.fieldType === "entry" && production.fieldType === "entry") {
|
|
4610
|
+
if (!isDeepStrictEqual([...current.ofCollections].sort(), [...production.ofCollections].sort())) changes.push({
|
|
4611
|
+
...base,
|
|
4612
|
+
changeType: "ofCollectionsChanged",
|
|
4613
|
+
bump: "major"
|
|
4614
|
+
});
|
|
4615
|
+
}
|
|
4616
|
+
if (production.isRequired === false && current.isRequired === true) changes.push({
|
|
4617
|
+
...base,
|
|
4618
|
+
changeType: "isNotRequiredToRequired",
|
|
4619
|
+
bump: "minor"
|
|
4620
|
+
});
|
|
4621
|
+
if (production.isUnique === false && current.isUnique === true) changes.push({
|
|
4622
|
+
...base,
|
|
4623
|
+
changeType: "isNotUniqueToUnique",
|
|
4624
|
+
bump: "minor"
|
|
4625
|
+
});
|
|
4626
|
+
if (this.isMinMaxLoosened(current, production)) changes.push({
|
|
4627
|
+
...base,
|
|
4628
|
+
changeType: "minMaxLoosened",
|
|
4629
|
+
bump: "patch"
|
|
4630
|
+
});
|
|
4631
|
+
if (!isDeepStrictEqual(current.label, production.label)) changes.push({
|
|
4632
|
+
...base,
|
|
4633
|
+
changeType: "labelChanged",
|
|
4634
|
+
bump: "patch"
|
|
4635
|
+
});
|
|
4636
|
+
if (!isDeepStrictEqual(current.description, production.description)) changes.push({
|
|
4637
|
+
...base,
|
|
4638
|
+
changeType: "descriptionChanged",
|
|
4639
|
+
bump: "patch"
|
|
4640
|
+
});
|
|
4641
|
+
if ("defaultValue" in current && "defaultValue" in production && !isDeepStrictEqual(current.defaultValue, production.defaultValue)) changes.push({
|
|
4642
|
+
...base,
|
|
4643
|
+
changeType: "defaultValueChanged",
|
|
4644
|
+
bump: "patch"
|
|
4645
|
+
});
|
|
4646
|
+
if (current.inputWidth !== production.inputWidth) changes.push({
|
|
4647
|
+
...base,
|
|
4648
|
+
changeType: "inputWidthChanged",
|
|
4649
|
+
bump: "patch"
|
|
4650
|
+
});
|
|
4651
|
+
if (current.isDisabled !== production.isDisabled) changes.push({
|
|
4652
|
+
...base,
|
|
4653
|
+
changeType: "isDisabledChanged",
|
|
4654
|
+
bump: "patch"
|
|
4655
|
+
});
|
|
4656
|
+
return changes;
|
|
4657
|
+
}
|
|
4658
|
+
/**
|
|
4659
|
+
* Checks if min/max constraints have been tightened.
|
|
4660
|
+
*
|
|
4661
|
+
* Tightening means: new min > old min, or new max < old max.
|
|
4662
|
+
* A null value means no constraint (unbounded).
|
|
4663
|
+
*/
|
|
4664
|
+
isMinMaxTightened(current, production) {
|
|
4665
|
+
const currentMin = this.getMinMax(current, "min");
|
|
4666
|
+
const productionMin = this.getMinMax(production, "min");
|
|
4667
|
+
const currentMax = this.getMinMax(current, "max");
|
|
4668
|
+
const productionMax = this.getMinMax(production, "max");
|
|
4669
|
+
if (currentMin !== null && productionMin === null) return true;
|
|
4670
|
+
if (currentMin !== null && productionMin !== null && currentMin > productionMin) return true;
|
|
4671
|
+
if (currentMax !== null && productionMax === null) return true;
|
|
4672
|
+
if (currentMax !== null && productionMax !== null && currentMax < productionMax) return true;
|
|
4673
|
+
return false;
|
|
4674
|
+
}
|
|
4675
|
+
/**
|
|
4676
|
+
* Checks if min/max constraints have been loosened.
|
|
4677
|
+
*
|
|
4678
|
+
* Loosening means: new min < old min, or new max > old max.
|
|
4679
|
+
*/
|
|
4680
|
+
isMinMaxLoosened(current, production) {
|
|
4681
|
+
const currentMin = this.getMinMax(current, "min");
|
|
4682
|
+
const productionMin = this.getMinMax(production, "min");
|
|
4683
|
+
const currentMax = this.getMinMax(current, "max");
|
|
4684
|
+
const productionMax = this.getMinMax(production, "max");
|
|
4685
|
+
if (currentMin === null && productionMin !== null) return true;
|
|
4686
|
+
if (currentMin !== null && productionMin !== null && currentMin < productionMin) return true;
|
|
4687
|
+
if (currentMax === null && productionMax !== null) return true;
|
|
4688
|
+
if (currentMax !== null && productionMax !== null && currentMax > productionMax) return true;
|
|
4689
|
+
return false;
|
|
4690
|
+
}
|
|
4691
|
+
/**
|
|
4692
|
+
* Safely extracts min or max from a field definition (not all types have it)
|
|
4693
|
+
*/
|
|
4694
|
+
getMinMax(field, prop) {
|
|
4695
|
+
switch (field.fieldType) {
|
|
4696
|
+
case "text":
|
|
4697
|
+
case "textarea":
|
|
4698
|
+
case "number":
|
|
4699
|
+
case "range":
|
|
4700
|
+
case "asset":
|
|
4701
|
+
case "entry": return field[prop];
|
|
4702
|
+
default: return null;
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
/**
|
|
4706
|
+
* Counts existing preview tags for a given base version since the last full release.
|
|
4707
|
+
*/
|
|
4708
|
+
async countPreviewsSinceLastRelease(projectPath, baseVersion) {
|
|
4709
|
+
const tags = await this.gitService.tags.list({ path: projectPath });
|
|
4710
|
+
let count = 0;
|
|
4711
|
+
for (const tag of tags.list) {
|
|
4712
|
+
if (tag.message.type === "upgrade") continue;
|
|
4713
|
+
if (tag.message.type === "release") break;
|
|
4714
|
+
if (tag.message.type === "preview") {
|
|
4715
|
+
if (tag.message.version.split("-")[0] === baseVersion) count++;
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
return count;
|
|
4719
|
+
}
|
|
4720
|
+
/**
|
|
4721
|
+
* Returns the higher of two bumps (major > minor > patch)
|
|
4722
|
+
*/
|
|
4723
|
+
higherBump(a, b) {
|
|
4724
|
+
const order = {
|
|
4725
|
+
patch: 0,
|
|
4726
|
+
minor: 1,
|
|
4727
|
+
major: 2
|
|
4728
|
+
};
|
|
4729
|
+
if (a === null) return b;
|
|
4730
|
+
return order[a] >= order[b] ? a : b;
|
|
4731
|
+
}
|
|
4732
|
+
};
|
|
4733
|
+
|
|
3556
4734
|
//#endregion
|
|
3557
4735
|
//#region src/service/UserService.ts
|
|
3558
4736
|
/**
|
|
@@ -3588,7 +4766,7 @@ var UserService = class {
|
|
|
3588
4766
|
setUserSchema.parse(props);
|
|
3589
4767
|
const userFilePath = pathTo.userFile;
|
|
3590
4768
|
const userFile = { ...props };
|
|
3591
|
-
if (userFile.userType ===
|
|
4769
|
+
if (userFile.userType === userTypeSchema.enum.cloud) {}
|
|
3592
4770
|
await this.jsonFileService.update(userFile, userFilePath, userFileSchema);
|
|
3593
4771
|
this.logService.debug({
|
|
3594
4772
|
source: "core",
|
|
@@ -3616,6 +4794,7 @@ var ElekIoCore = class {
|
|
|
3616
4794
|
projectService;
|
|
3617
4795
|
collectionService;
|
|
3618
4796
|
entryService;
|
|
4797
|
+
releaseService;
|
|
3619
4798
|
localApi;
|
|
3620
4799
|
constructor(props) {
|
|
3621
4800
|
this.coreVersion = package_default.version;
|
|
@@ -3628,10 +4807,11 @@ var ElekIoCore = class {
|
|
|
3628
4807
|
this.jsonFileService = new JsonFileService(this.options, this.logService);
|
|
3629
4808
|
this.userService = new UserService(this.logService, this.jsonFileService);
|
|
3630
4809
|
this.gitService = new GitService(this.options, this.logService, this.userService);
|
|
3631
|
-
this.assetService = new AssetService(this.options, this.logService, this.jsonFileService, this.gitService);
|
|
3632
|
-
this.collectionService = new CollectionService(this.options, this.logService, this.jsonFileService, this.gitService);
|
|
3633
|
-
this.entryService = new EntryService(this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
|
|
4810
|
+
this.assetService = new AssetService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
|
|
4811
|
+
this.collectionService = new CollectionService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService);
|
|
4812
|
+
this.entryService = new EntryService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.collectionService);
|
|
3634
4813
|
this.projectService = new ProjectService(this.coreVersion, this.options, this.logService, this.jsonFileService, this.gitService, this.assetService, this.collectionService, this.entryService);
|
|
4814
|
+
this.releaseService = new ReleaseService(this.options, this.logService, this.gitService, this.jsonFileService, this.projectService);
|
|
3635
4815
|
this.localApi = new LocalApi(this.logService, this.projectService, this.collectionService, this.entryService, this.assetService);
|
|
3636
4816
|
this.logService.info({
|
|
3637
4817
|
source: "core",
|
|
@@ -3691,6 +4871,12 @@ var ElekIoCore = class {
|
|
|
3691
4871
|
return this.entryService;
|
|
3692
4872
|
}
|
|
3693
4873
|
/**
|
|
4874
|
+
* Prepare and create releases
|
|
4875
|
+
*/
|
|
4876
|
+
get releases() {
|
|
4877
|
+
return this.releaseService;
|
|
4878
|
+
}
|
|
4879
|
+
/**
|
|
3694
4880
|
* Allows starting and stopping a REST API
|
|
3695
4881
|
* to allow developers to read local Project data
|
|
3696
4882
|
*/
|
|
@@ -3705,23 +4891,23 @@ var ElekIoCore = class {
|
|
|
3705
4891
|
* Generates a flat Zod object schema from collection field definitions
|
|
3706
4892
|
* for use with Astro's `parseData` validation.
|
|
3707
4893
|
*
|
|
3708
|
-
* Each key is the field definition
|
|
4894
|
+
* Each key is the field definition slug and each value schema
|
|
3709
4895
|
* is the translatable content schema for that field type.
|
|
3710
4896
|
*/
|
|
3711
4897
|
function buildEntryValuesSchema(fieldDefinitions) {
|
|
3712
4898
|
const shape = {};
|
|
3713
4899
|
for (const fieldDef of fieldDefinitions) switch (fieldDef.valueType) {
|
|
3714
|
-
case
|
|
3715
|
-
shape[fieldDef.
|
|
4900
|
+
case valueTypeSchema.enum.string:
|
|
4901
|
+
shape[fieldDef.slug] = getTranslatableStringValueContentSchemaFromFieldDefinition(fieldDef);
|
|
3716
4902
|
break;
|
|
3717
|
-
case
|
|
3718
|
-
shape[fieldDef.
|
|
4903
|
+
case valueTypeSchema.enum.number:
|
|
4904
|
+
shape[fieldDef.slug] = getTranslatableNumberValueContentSchemaFromFieldDefinition(fieldDef);
|
|
3719
4905
|
break;
|
|
3720
|
-
case
|
|
3721
|
-
shape[fieldDef.
|
|
4906
|
+
case valueTypeSchema.enum.boolean:
|
|
4907
|
+
shape[fieldDef.slug] = getTranslatableBooleanValueContentSchemaFromFieldDefinition();
|
|
3722
4908
|
break;
|
|
3723
|
-
case
|
|
3724
|
-
shape[fieldDef.
|
|
4909
|
+
case valueTypeSchema.enum.reference:
|
|
4910
|
+
shape[fieldDef.slug] = getTranslatableReferenceValueContentSchemaFromFieldDefinition(fieldDef);
|
|
3725
4911
|
break;
|
|
3726
4912
|
}
|
|
3727
4913
|
return z.object(shape);
|
|
@@ -3740,7 +4926,7 @@ function buildEntryValuesTypeString(fieldDefinitions) {
|
|
|
3740
4926
|
`export type Entry = {`,
|
|
3741
4927
|
fieldDefinitions.map((fieldDef) => {
|
|
3742
4928
|
const tsType = valueTypeToTsType(fieldDef.valueType);
|
|
3743
|
-
return ` "${fieldDef.
|
|
4929
|
+
return ` "${fieldDef.slug}": Partial<Record<SupportedLanguage, ${tsType}>>`;
|
|
3744
4930
|
}).join(";\n") + ";",
|
|
3745
4931
|
`};`
|
|
3746
4932
|
].join("\n");
|
|
@@ -3758,13 +4944,13 @@ function valueTypeToTsType(valueType) {
|
|
|
3758
4944
|
//#endregion
|
|
3759
4945
|
//#region src/astro/transform.ts
|
|
3760
4946
|
/**
|
|
3761
|
-
* Transforms an elek.io Entry's values
|
|
3762
|
-
* keyed by field definition
|
|
4947
|
+
* Transforms an elek.io Entry's values record into a flat object
|
|
4948
|
+
* keyed by field definition slug. Each value's translatable content
|
|
3763
4949
|
* is preserved as-is.
|
|
3764
4950
|
*/
|
|
3765
4951
|
function transformEntryValues(values) {
|
|
3766
4952
|
const result = {};
|
|
3767
|
-
for (const value of values) result[
|
|
4953
|
+
for (const [slug, value] of Object.entries(values)) result[slug] = value.content;
|
|
3768
4954
|
return result;
|
|
3769
4955
|
}
|
|
3770
4956
|
|
|
@@ -3856,9 +5042,13 @@ function elekEntries(props) {
|
|
|
3856
5042
|
return {
|
|
3857
5043
|
name: "elek-entries",
|
|
3858
5044
|
createSchema: async () => {
|
|
5045
|
+
const resolvedId = await core.collections.resolveCollectionId({
|
|
5046
|
+
projectId: props.projectId,
|
|
5047
|
+
idOrSlug: props.collectionIdOrSlug
|
|
5048
|
+
});
|
|
3859
5049
|
const collection = await core.collections.read({
|
|
3860
5050
|
projectId: props.projectId,
|
|
3861
|
-
id:
|
|
5051
|
+
id: resolvedId
|
|
3862
5052
|
});
|
|
3863
5053
|
return {
|
|
3864
5054
|
schema: buildEntryValuesSchema(collection.fieldDefinitions),
|
|
@@ -3866,11 +5056,15 @@ function elekEntries(props) {
|
|
|
3866
5056
|
};
|
|
3867
5057
|
},
|
|
3868
5058
|
load: async (context) => {
|
|
3869
|
-
|
|
5059
|
+
const resolvedCollectionId = await core.collections.resolveCollectionId({
|
|
5060
|
+
projectId: props.projectId,
|
|
5061
|
+
idOrSlug: props.collectionIdOrSlug
|
|
5062
|
+
});
|
|
5063
|
+
context.logger.info(`Loading elek.io Entries of Collection "${props.collectionIdOrSlug}" and Project "${props.projectId}"`);
|
|
3870
5064
|
context.store.clear();
|
|
3871
5065
|
const { list: entries, total } = await core.entries.list({
|
|
3872
5066
|
projectId: props.projectId,
|
|
3873
|
-
collectionId:
|
|
5067
|
+
collectionId: resolvedCollectionId,
|
|
3874
5068
|
limit: 0
|
|
3875
5069
|
});
|
|
3876
5070
|
if (total === 0) context.logger.warn("No Entries found");
|