@elek-io/core 0.5.4 → 0.7.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.
@@ -136,13 +136,13 @@ var baseFileSchema = z2.object({
136
136
  */
137
137
  id: uuidSchema.readonly(),
138
138
  /**
139
- * The timestamp of the file being created is set by the service of "objectType" while creating it
139
+ * The datetime of the file being created is set by the service of "objectType" while creating it
140
140
  */
141
- created: z2.number().readonly(),
141
+ created: z2.string().datetime().readonly(),
142
142
  /**
143
- * The timestamp of the file being updated is set by the service of "objectType" while updating it
143
+ * The datetime of the file being updated is set by the service of "objectType" while updating it
144
144
  */
145
- updated: z2.number().nullable()
145
+ updated: z2.string().datetime().nullable()
146
146
  });
147
147
  var baseFileWithLanguageSchema = baseFileSchema.extend({
148
148
  /**
@@ -444,7 +444,7 @@ var resolvedValueSchema = z4.union([
444
444
  function getValueContentSchemaFromDefinition(definition) {
445
445
  switch (definition.valueType) {
446
446
  case ValueTypeSchema.Enum.boolean:
447
- return getBooleanValueContentSchema(definition);
447
+ return getBooleanValueContentSchema();
448
448
  case ValueTypeSchema.Enum.number:
449
449
  return getNumberValueContentSchema(definition);
450
450
  case ValueTypeSchema.Enum.string:
@@ -458,7 +458,7 @@ function getValueContentSchemaFromDefinition(definition) {
458
458
  );
459
459
  }
460
460
  }
461
- function getBooleanValueContentSchema(definition) {
461
+ function getBooleanValueContentSchema() {
462
462
  return z4.boolean();
463
463
  }
464
464
  function getNumberValueContentSchema(definition) {
@@ -662,7 +662,7 @@ var gitCommitSchema = z8.object({
662
662
  hash: z8.string(),
663
663
  message: z8.string(),
664
664
  author: gitSignatureSchema,
665
- timestamp: z8.number(),
665
+ datetime: z8.string().datetime(),
666
666
  tag: z8.string().nullable()
667
667
  });
668
668
  var GitCommitIconNative = /* @__PURE__ */ ((GitCommitIconNative2) => {
@@ -731,7 +731,7 @@ var gitTagSchema = z9.object({
731
731
  id: uuidSchema,
732
732
  message: z9.string(),
733
733
  author: gitSignatureSchema,
734
- timestamp: z9.number()
734
+ datetime: z9.string().datetime()
735
735
  });
736
736
  var createGitTagSchema = gitTagSchema.pick({
737
737
  message: true
@@ -1082,6 +1082,12 @@ async function files(path, extension) {
1082
1082
 
1083
1083
  // src/service/AbstractCrudService.ts
1084
1084
  var AbstractCrudService = class {
1085
+ type;
1086
+ options;
1087
+ /**
1088
+ * Dynamically generated git messages for operations
1089
+ */
1090
+ gitMessage;
1085
1091
  /**
1086
1092
  * Do not instantiate directly as this is an abstract class
1087
1093
  */
@@ -1161,7 +1167,7 @@ var AbstractCrudService = class {
1161
1167
  possibleFiles.map(async (possibleFile) => {
1162
1168
  const fileNameArray = possibleFile.name.split(".");
1163
1169
  const fileReference = {
1164
- id: fileNameArray[0],
1170
+ id: fileNameArray[0] || "",
1165
1171
  language: fileNameArray.length === 3 ? fileNameArray[1] : void 0,
1166
1172
  extension: fileNameArray.length === 2 ? fileNameArray[1] : fileNameArray[2]
1167
1173
  };
@@ -1177,107 +1183,34 @@ var AbstractCrudService = class {
1177
1183
  };
1178
1184
 
1179
1185
  // src/service/AssetService.ts
1180
- import Fs3 from "fs-extra";
1181
- import IsSvg from "is-svg";
1186
+ import { fileTypeFromFile } from "file-type";
1187
+ import Fs2 from "fs-extra";
1188
+ import isSvg from "is-svg";
1182
1189
 
1183
1190
  // src/util/shared.ts
1184
- import slugify from "slugify";
1191
+ import slugify from "@sindresorhus/slugify";
1185
1192
  import { v4 as generateUuid } from "uuid";
1186
- var Slugify = slugify.default || slugify;
1187
1193
  function uuid() {
1188
1194
  return generateUuid();
1189
1195
  }
1190
- function currentTimestamp() {
1191
- return Math.floor(Date.now() / 1e3);
1196
+ function datetime(value) {
1197
+ if (!value) {
1198
+ return (/* @__PURE__ */ new Date()).toISOString();
1199
+ }
1200
+ return new Date(value).toISOString();
1192
1201
  }
1193
1202
  function slug(string) {
1194
- return Slugify(string, {
1195
- replacement: "-",
1196
- // replace spaces with replacement character, defaults to `-`
1197
- remove: void 0,
1198
- // remove characters that match regex, defaults to `undefined`
1199
- lower: true,
1200
- // convert to lower case, defaults to `false`
1201
- strict: true
1202
- // strip special characters except replacement, defaults to `false`
1203
+ return slugify(string, {
1204
+ separator: "-",
1205
+ lowercase: true,
1206
+ decamelize: true
1203
1207
  });
1204
1208
  }
1205
1209
 
1206
- // src/service/JsonFileService.ts
1207
- import Fs2 from "fs-extra";
1208
- var JsonFileService = class extends AbstractCrudService {
1209
- constructor(options) {
1210
- super(serviceTypeSchema.Enum.JsonFile, options);
1211
- this.cache = /* @__PURE__ */ new Map();
1212
- }
1213
- /**
1214
- * Creates a new file on disk. Fails if path already exists
1215
- *
1216
- * @param data Data to write into the file
1217
- * @param path Path to write the file to
1218
- * @param schema Schema of the file to validate against
1219
- * @returns Validated content of the file from disk
1220
- */
1221
- async create(data, path, schema) {
1222
- const parsedData = schema.parse(data);
1223
- const string = this.serialize(parsedData);
1224
- await Fs2.writeFile(path, string, {
1225
- flag: "wx",
1226
- encoding: "utf8"
1227
- });
1228
- this.cache.set(path, parsedData);
1229
- return parsedData;
1230
- }
1231
- /**
1232
- * Reads the content of a file on disk. Fails if path does not exist
1233
- *
1234
- * @param path Path to read the file from
1235
- * @param schema Schema of the file to validate against
1236
- * @returns Validated content of the file from disk
1237
- */
1238
- async read(path, schema) {
1239
- if (this.cache.has(path)) {
1240
- return this.cache.get(path);
1241
- }
1242
- const data = await Fs2.readFile(path, {
1243
- flag: "r",
1244
- encoding: "utf8"
1245
- });
1246
- const json = this.deserialize(data);
1247
- const parsedData = schema.parse(json);
1248
- this.cache.set(path, parsedData);
1249
- return parsedData;
1250
- }
1251
- /**
1252
- * Overwrites an existing file on disk
1253
- *
1254
- * @todo Check how to error out if the file does not exist already
1255
- *
1256
- * @param data Data to write into the file
1257
- * @param path Path to the file to overwrite
1258
- * @param schema Schema of the file to validate against
1259
- * @returns Validated content of the file from disk
1260
- */
1261
- async update(data, path, schema) {
1262
- const parsedData = schema.parse(data);
1263
- const string = this.serialize(parsedData);
1264
- await Fs2.writeFile(path, string, {
1265
- flag: "w",
1266
- encoding: "utf8"
1267
- });
1268
- this.cache.set(path, parsedData);
1269
- return parsedData;
1270
- }
1271
- serialize(data) {
1272
- return JSON.stringify(data, null, this.options.file.json.indentation);
1273
- }
1274
- deserialize(data) {
1275
- return JSON.parse(data);
1276
- }
1277
- };
1278
-
1279
1210
  // src/service/AssetService.ts
1280
1211
  var AssetService = class extends AbstractCrudService {
1212
+ jsonFileService;
1213
+ gitService;
1281
1214
  constructor(options, jsonFileService, gitService) {
1282
1215
  super(serviceTypeSchema.Enum.Asset, options);
1283
1216
  this.jsonFileService = jsonFileService;
@@ -1303,14 +1236,14 @@ var AssetService = class extends AbstractCrudService {
1303
1236
  ...props,
1304
1237
  objectType: "asset",
1305
1238
  id,
1306
- created: currentTimestamp(),
1239
+ created: datetime(),
1307
1240
  updated: null,
1308
1241
  extension: fileType.extension,
1309
1242
  mimeType: fileType.mimeType,
1310
1243
  size
1311
1244
  };
1312
1245
  try {
1313
- await Fs3.copyFile(props.filePath, assetPath);
1246
+ await Fs2.copyFile(props.filePath, assetPath);
1314
1247
  await this.jsonFileService.create(
1315
1248
  assetFile,
1316
1249
  assetFilePath,
@@ -1352,7 +1285,7 @@ var AssetService = class extends AbstractCrudService {
1352
1285
  const assetFile = {
1353
1286
  ...prevAssetFile,
1354
1287
  ...props,
1355
- updated: currentTimestamp()
1288
+ updated: datetime()
1356
1289
  };
1357
1290
  if (props.newFilePath) {
1358
1291
  const fileType = await this.getSupportedFileTypeOrThrow(
@@ -1371,8 +1304,8 @@ var AssetService = class extends AbstractCrudService {
1371
1304
  props.language,
1372
1305
  fileType.extension
1373
1306
  );
1374
- await Fs3.remove(prevAssetPath);
1375
- await Fs3.copyFile(props.newFilePath, assetPath);
1307
+ await Fs2.remove(prevAssetPath);
1308
+ await Fs2.copyFile(props.newFilePath, assetPath);
1376
1309
  assetFile.extension = fileType.extension;
1377
1310
  assetFile.mimeType = fileType.mimeType;
1378
1311
  assetFile.size = size;
@@ -1403,8 +1336,8 @@ var AssetService = class extends AbstractCrudService {
1403
1336
  props.language,
1404
1337
  props.extension
1405
1338
  );
1406
- await Fs3.remove(assetPath);
1407
- await Fs3.remove(assetFilePath);
1339
+ await Fs2.remove(assetPath);
1340
+ await Fs2.remove(assetFilePath);
1408
1341
  await this.gitService.add(projectPath, [assetFilePath, assetPath]);
1409
1342
  await this.gitService.commit(projectPath, this.gitMessage.delete);
1410
1343
  }
@@ -1453,7 +1386,7 @@ var AssetService = class extends AbstractCrudService {
1453
1386
  * @param path Path of the Asset to get the size from
1454
1387
  */
1455
1388
  async getAssetSize(path) {
1456
- return (await Fs3.stat(path)).size;
1389
+ return (await Fs2.stat(path)).size;
1457
1390
  }
1458
1391
  /**
1459
1392
  * Creates an Asset from given AssetFile
@@ -1481,17 +1414,16 @@ var AssetService = class extends AbstractCrudService {
1481
1414
  * @param filePath Path to the file to check
1482
1415
  */
1483
1416
  async getSupportedFileTypeOrThrow(filePath) {
1484
- const fileSize = (await Fs3.stat(filePath)).size;
1417
+ const fileSize = (await Fs2.stat(filePath)).size;
1485
1418
  if (fileSize / 1e3 <= 500) {
1486
- const fileBuffer = await Fs3.readFile(filePath);
1487
- if (IsSvg(fileBuffer.toString()) === true) {
1419
+ const fileBuffer = await Fs2.readFile(filePath);
1420
+ if (isSvg(fileBuffer.toString()) === true) {
1488
1421
  return {
1489
1422
  extension: supportedAssetExtensionSchema.Enum.svg,
1490
1423
  mimeType: supportedAssetMimeTypeSchema.Enum["image/svg+xml"]
1491
1424
  };
1492
1425
  }
1493
1426
  }
1494
- const { fileTypeFromFile } = await import("file-type");
1495
1427
  const fileType = await fileTypeFromFile(filePath);
1496
1428
  const result = supportedAssetTypeSchema.parse({
1497
1429
  extension: fileType?.ext,
@@ -1502,454 +1434,861 @@ var AssetService = class extends AbstractCrudService {
1502
1434
  };
1503
1435
 
1504
1436
  // src/service/CollectionService.ts
1505
- import Fs4 from "fs-extra";
1506
-
1507
- // src/service/GitService.ts
1508
- import { GitProcess } from "dugite";
1509
- import { EOL as EOL2 } from "os";
1510
- import PQueue from "p-queue";
1511
-
1512
- // src/service/GitTagService.ts
1513
- import { EOL } from "os";
1514
- var GitTagService = class extends AbstractCrudService {
1515
- constructor(options, git) {
1516
- super(serviceTypeSchema.Enum.GitTag, options);
1517
- this.git = git;
1437
+ import Fs3 from "fs-extra";
1438
+ var CollectionService = class extends AbstractCrudService {
1439
+ jsonFileService;
1440
+ gitService;
1441
+ constructor(options, jsonFileService, gitService) {
1442
+ super(serviceTypeSchema.Enum.Collection, options);
1443
+ this.jsonFileService = jsonFileService;
1444
+ this.gitService = gitService;
1518
1445
  }
1519
1446
  /**
1520
- * Creates a new tag
1521
- *
1522
- * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---annotate
1447
+ * Creates a new Collection
1523
1448
  */
1524
1449
  async create(props) {
1525
- createGitTagSchema.parse(props);
1450
+ createCollectionSchema.parse(props);
1526
1451
  const id = uuid();
1527
- let args = ["tag", "--annotate", id];
1528
- if (props.hash) {
1529
- args = [...args, props.hash];
1530
- }
1531
- args = [...args, "-m", props.message];
1532
- await this.git(props.path, args);
1533
- const tag = await this.read({ path: props.path, id });
1534
- return tag;
1452
+ const projectPath = pathTo.project(props.projectId);
1453
+ const collectionPath = pathTo.collection(props.projectId, id);
1454
+ const collectionFilePath = pathTo.collectionFile(props.projectId, id);
1455
+ const collectionFile = {
1456
+ ...props,
1457
+ objectType: "collection",
1458
+ id,
1459
+ slug: {
1460
+ singular: slug(props.slug.singular),
1461
+ plural: slug(props.slug.plural)
1462
+ },
1463
+ created: datetime(),
1464
+ updated: null
1465
+ };
1466
+ await Fs3.ensureDir(collectionPath);
1467
+ await this.jsonFileService.create(
1468
+ collectionFile,
1469
+ collectionFilePath,
1470
+ collectionFileSchema
1471
+ );
1472
+ await this.gitService.add(projectPath, [collectionFilePath]);
1473
+ await this.gitService.commit(projectPath, this.gitMessage.create);
1474
+ return collectionFile;
1535
1475
  }
1536
1476
  /**
1537
- * Returns a tag by ID
1538
- *
1539
- * Internally uses list() but only returns the tag with matching ID.
1477
+ * Returns a Collection by ID
1540
1478
  */
1541
1479
  async read(props) {
1542
- readGitTagSchema.parse(props);
1543
- const tags = await this.list({ path: props.path });
1544
- const tag = tags.list.find((tag2) => {
1545
- return tag2.id === props.id;
1546
- });
1547
- if (!tag) {
1548
- throw new GitError(
1549
- `Provided tag with UUID "${props.id}" did not match any known tags`
1550
- );
1551
- }
1552
- return tag;
1480
+ readCollectionSchema.parse(props);
1481
+ const collection = await this.jsonFileService.read(
1482
+ pathTo.collectionFile(props.projectId, props.id),
1483
+ collectionFileSchema
1484
+ );
1485
+ return collection;
1553
1486
  }
1554
1487
  /**
1555
- * Updating a git tag is not supported.
1556
- * Please delete the old and create a new one
1488
+ * Updates given Collection
1557
1489
  *
1558
- * @see https://git-scm.com/docs/git-tag#_on_re_tagging
1490
+ * @todo finish implementing checks for FieldDefinitions and extract methods
1491
+ *
1492
+ * @param projectId Project ID of the collection to update
1493
+ * @param collection Collection to write to disk
1494
+ * @returns An object containing information about the actions needed to be taken,
1495
+ * before given update can be executed or void if the update was executed successfully
1559
1496
  */
1560
- async update() {
1561
- throw new Error(
1562
- "Updating a git tag is not supported. Please delete the old and create a new one"
1497
+ async update(props) {
1498
+ updateCollectionSchema.parse(props);
1499
+ const projectPath = pathTo.project(props.projectId);
1500
+ const collectionFilePath = pathTo.collectionFile(props.projectId, props.id);
1501
+ const prevCollectionFile = await this.read(props);
1502
+ const collectionFile = {
1503
+ ...prevCollectionFile,
1504
+ ...props,
1505
+ updated: datetime()
1506
+ };
1507
+ await this.jsonFileService.update(
1508
+ collectionFile,
1509
+ collectionFilePath,
1510
+ collectionFileSchema
1563
1511
  );
1512
+ await this.gitService.add(projectPath, [collectionFilePath]);
1513
+ await this.gitService.commit(projectPath, this.gitMessage.update);
1514
+ return collectionFile;
1564
1515
  }
1565
1516
  /**
1566
- * Deletes a tag
1567
- *
1568
- * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---delete
1517
+ * Deletes given Collection (folder), including it's items
1569
1518
  *
1570
- * @param path Path to the repository
1571
- * @param id UUID of the tag to delete
1519
+ * The Fields that Collection used are not deleted.
1572
1520
  */
1573
1521
  async delete(props) {
1574
- deleteGitTagSchema.parse(props);
1575
- const args = ["tag", "--delete", props.id];
1576
- await this.git(props.path, args);
1522
+ deleteCollectionSchema.parse(props);
1523
+ const projectPath = pathTo.project(props.projectId);
1524
+ const collectionPath = pathTo.collection(props.projectId, props.id);
1525
+ await Fs3.remove(collectionPath);
1526
+ await this.gitService.add(projectPath, [collectionPath]);
1527
+ await this.gitService.commit(projectPath, this.gitMessage.delete);
1577
1528
  }
1578
- /**
1579
- * Gets all local tags or filter them by pattern
1580
- *
1581
- * They are sorted by authordate of the commit, not the timestamp the tag is created.
1582
- * This ensures tags are sorted correctly in the timeline of their commits.
1583
- *
1584
- * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---list
1585
- */
1586
1529
  async list(props) {
1587
- listGitTagsSchema.parse(props);
1588
- let args = ["tag", "--list"];
1589
- args = [
1590
- ...args,
1591
- "--sort=-*authordate",
1592
- "--format=%(refname:short)|%(subject)|%(*authorname)|%(*authoremail)|%(*authordate:unix)"
1593
- ];
1594
- const result = await this.git(props.path, args);
1595
- const noEmptyLinesArr = result.stdout.split(EOL).filter((line) => {
1596
- return line.trim() !== "";
1597
- });
1598
- const lineObjArr = noEmptyLinesArr.map((line) => {
1599
- const lineArray = line.split("|");
1600
- return {
1601
- id: lineArray[0],
1602
- message: lineArray[1],
1603
- author: {
1604
- name: lineArray[2],
1605
- email: lineArray[3]
1606
- },
1607
- timestamp: parseInt(lineArray[4])
1608
- };
1609
- });
1610
- const gitTags = lineObjArr.filter(this.isGitTag.bind(this));
1530
+ listCollectionsSchema.parse(props);
1531
+ const offset = props.offset || 0;
1532
+ const limit = props.limit || 15;
1533
+ const collectionReferences = await this.listReferences(
1534
+ objectTypeSchema.Enum.collection,
1535
+ props.projectId
1536
+ );
1537
+ const partialCollectionReferences = collectionReferences.slice(
1538
+ offset,
1539
+ limit
1540
+ );
1541
+ const collections = await returnResolved(
1542
+ partialCollectionReferences.map((reference) => {
1543
+ return this.read({
1544
+ projectId: props.projectId,
1545
+ id: reference.id
1546
+ });
1547
+ })
1548
+ );
1611
1549
  return {
1612
- total: gitTags.length,
1613
- limit: 0,
1614
- offset: 0,
1615
- list: gitTags
1550
+ total: collectionReferences.length,
1551
+ limit,
1552
+ offset,
1553
+ list: collections
1616
1554
  };
1617
1555
  }
1618
- /**
1619
- * Returns the total number of tags inside given repository
1620
- *
1621
- * Internally uses list(), so do not use count()
1622
- * in conjuncion with it to avoid multiple git calls.
1623
- *
1624
- * @param path Path to the repository
1625
- */
1626
1556
  async count(props) {
1627
- countGitTagsSchema.parse(props);
1628
- const gitTags = await this.list({ path: props.path });
1629
- return gitTags.total;
1557
+ countCollectionsSchema.parse(props);
1558
+ const count = (await this.listReferences(
1559
+ objectTypeSchema.Enum.collection,
1560
+ props.projectId
1561
+ )).length;
1562
+ return count;
1630
1563
  }
1631
1564
  /**
1632
- * Type guard for GitTag
1633
- *
1634
- * @param obj The object to check
1565
+ * Checks if given object is of type Collection
1635
1566
  */
1636
- isGitTag(obj) {
1637
- return gitTagSchema.safeParse(obj).success;
1567
+ isCollection(obj) {
1568
+ return collectionFileSchema.safeParse(obj).success;
1638
1569
  }
1639
1570
  };
1640
1571
 
1641
- // src/service/UserService.ts
1642
- var UserService = class {
1643
- constructor(jsonFileService) {
1572
+ // src/service/EntryService.ts
1573
+ import Fs4 from "fs-extra";
1574
+ var EntryService = class extends AbstractCrudService {
1575
+ jsonFileService;
1576
+ gitService;
1577
+ collectionService;
1578
+ assetService;
1579
+ // private sharedValueService: SharedValueService;
1580
+ constructor(options, jsonFileService, gitService, collectionService, assetService) {
1581
+ super(serviceTypeSchema.Enum.Entry, options);
1644
1582
  this.jsonFileService = jsonFileService;
1583
+ this.gitService = gitService;
1584
+ this.collectionService = collectionService;
1585
+ this.assetService = assetService;
1645
1586
  }
1646
1587
  /**
1647
- * Returns the User currently working with Core
1588
+ * Creates a new Entry for given Collection
1648
1589
  */
1649
- async get() {
1650
- try {
1651
- return await this.jsonFileService.read(pathTo.userFile, userFileSchema);
1652
- } catch (error) {
1653
- return void 0;
1654
- }
1590
+ async create(props) {
1591
+ createEntrySchema.parse(props);
1592
+ const id = uuid();
1593
+ const projectPath = pathTo.project(props.projectId);
1594
+ const entryFilePath = pathTo.entryFile(
1595
+ props.projectId,
1596
+ props.collectionId,
1597
+ id
1598
+ );
1599
+ const collection = await this.collectionService.read({
1600
+ projectId: props.projectId,
1601
+ id: props.collectionId
1602
+ });
1603
+ const entryFile = {
1604
+ objectType: "entry",
1605
+ id,
1606
+ values: props.values,
1607
+ created: datetime(),
1608
+ updated: null
1609
+ };
1610
+ const entry = await this.toEntry({
1611
+ projectId: props.projectId,
1612
+ collectionId: props.collectionId,
1613
+ entryFile
1614
+ });
1615
+ this.validateValues({
1616
+ collectionId: props.collectionId,
1617
+ valueDefinitions: collection.valueDefinitions,
1618
+ values: entry.values
1619
+ });
1620
+ await this.jsonFileService.create(
1621
+ entryFile,
1622
+ entryFilePath,
1623
+ entryFileSchema
1624
+ );
1625
+ await this.gitService.add(projectPath, [entryFilePath]);
1626
+ await this.gitService.commit(projectPath, this.gitMessage.create);
1627
+ return entry;
1655
1628
  }
1656
1629
  /**
1657
- * Sets the User currently working with Core
1658
- *
1659
- * By doing so all git operations are done with the signature of this User
1630
+ * Returns an Entry from given Collection by ID and language
1660
1631
  */
1661
- async set(props) {
1662
- setUserSchema.parse(props);
1663
- const userFilePath = pathTo.userFile;
1664
- const userFile = {
1665
- ...props
1666
- };
1667
- if (userFile.userType === UserTypeSchema.Enum.cloud) {
1668
- }
1669
- await this.jsonFileService.update(userFile, userFilePath, userFileSchema);
1670
- return userFile;
1632
+ async read(props) {
1633
+ readEntrySchema.parse(props);
1634
+ const entryFile = await this.jsonFileService.read(
1635
+ pathTo.entryFile(props.projectId, props.collectionId, props.id),
1636
+ entryFileSchema
1637
+ );
1638
+ return await this.toEntry({
1639
+ projectId: props.projectId,
1640
+ collectionId: props.collectionId,
1641
+ entryFile
1642
+ });
1671
1643
  }
1672
- };
1673
-
1674
- // src/service/GitService.ts
1675
- var GitService2 = class {
1676
- constructor(options, userService) {
1677
- this.branches = {
1678
- /**
1679
- * List branches
1680
- *
1681
- * @see https://www.git-scm.com/docs/git-branch
1682
- *
1683
- * @param path Path to the repository
1684
- */
1685
- list: async (path) => {
1686
- const args = ["branch", "--list", "--all"];
1687
- const result = await this.git(path, args);
1688
- const normalizedLinesArr = result.stdout.split(EOL2).filter((line) => {
1689
- return line.trim() !== "";
1690
- }).map((line) => {
1691
- return line.trim().replace("* ", "");
1692
- });
1693
- const local = [];
1694
- const remote = [];
1695
- normalizedLinesArr.forEach((line) => {
1696
- if (line.startsWith("remotes/")) {
1697
- remote.push(line.replace("remotes/", ""));
1698
- } else {
1699
- local.push(line);
1700
- }
1701
- });
1702
- return {
1703
- local,
1704
- remote
1705
- };
1706
- },
1707
- /**
1708
- * Returns the name of the current branch. In detached HEAD state, an empty string is returned.
1709
- *
1710
- * @see https://www.git-scm.com/docs/git-branch#Documentation/git-branch.txt---show-current
1711
- *
1712
- * @param path Path to the repository
1713
- */
1714
- current: async (path) => {
1715
- const args = ["branch", "--show-current"];
1716
- const result = await this.git(path, args);
1717
- return result.stdout.trim();
1718
- },
1719
- /**
1720
- * Switch branches
1721
- *
1722
- * @see https://git-scm.com/docs/git-switch/
1723
- *
1724
- * @param path Path to the repository
1725
- * @param branch Name of the branch to switch to
1726
- * @param options Options specific to the switch operation
1727
- */
1728
- switch: async (path, branch, options) => {
1729
- await this.checkBranchOrTagName(path, branch);
1730
- let args = ["switch"];
1731
- if (options?.isNew === true) {
1732
- args = [...args, "--create", branch];
1733
- } else {
1734
- args = [...args, branch];
1735
- }
1736
- await this.git(path, args);
1737
- }
1738
- };
1739
- this.remotes = {
1740
- /**
1741
- * Returns a list of currently tracked remotes
1742
- *
1743
- * @see https://git-scm.com/docs/git-remote
1744
- *
1745
- * @param path Path to the repository
1746
- */
1747
- list: async (path) => {
1748
- const args = ["remote"];
1749
- const result = await this.git(path, args);
1750
- const normalizedLinesArr = result.stdout.split(EOL2).filter((line) => {
1751
- return line.trim() !== "";
1752
- });
1753
- return normalizedLinesArr;
1754
- },
1755
- /**
1756
- * Returns true if the `origin` remote exists, otherwise false
1757
- *
1758
- * @param path Path to the repository
1759
- */
1760
- hasOrigin: async (path) => {
1761
- const remotes = await this.remotes.list(path);
1762
- if (remotes.includes("origin")) {
1763
- return true;
1764
- }
1765
- return false;
1766
- },
1767
- /**
1768
- * Adds the `origin` remote with given URL
1769
- *
1770
- * Throws if `origin` remote is added already.
1771
- *
1772
- * @see https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emaddem
1773
- *
1774
- * @param path Path to the repository
1775
- */
1776
- addOrigin: async (path, url) => {
1777
- const args = ["remote", "add", "origin", url];
1778
- await this.git(path, args);
1779
- },
1780
- /**
1781
- * Returns the current `origin` remote URL
1782
- *
1783
- * Throws if no `origin` remote is added yet.
1784
- *
1785
- * @see https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emget-urlem
1786
- *
1787
- * @param path Path to the repository
1788
- */
1789
- getOriginUrl: async (path) => {
1790
- const args = ["remote", "get-url", "origin"];
1791
- const result = (await this.git(path, args)).stdout.trim();
1792
- return result.length === 0 ? null : result;
1793
- },
1794
- /**
1795
- * Sets the current `origin` remote URL
1796
- *
1797
- * Throws if no `origin` remote is added yet.
1798
- *
1799
- * @see https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emset-urlem
1800
- *
1801
- * @param path Path to the repository
1802
- */
1803
- setOriginUrl: async (path, url) => {
1804
- const args = ["remote", "set-url", "origin", url];
1805
- await this.git(path, args);
1806
- }
1644
+ /**
1645
+ * Updates an Entry of given Collection with new Values and shared Values
1646
+ */
1647
+ async update(props) {
1648
+ updateEntrySchema.parse(props);
1649
+ const projectPath = pathTo.project(props.projectId);
1650
+ const entryFilePath = pathTo.entryFile(
1651
+ props.projectId,
1652
+ props.collectionId,
1653
+ props.id
1654
+ );
1655
+ const collection = await this.collectionService.read({
1656
+ projectId: props.projectId,
1657
+ id: props.collectionId
1658
+ });
1659
+ const prevEntryFile = await this.read({
1660
+ projectId: props.projectId,
1661
+ collectionId: props.collectionId,
1662
+ id: props.id
1663
+ });
1664
+ const entryFile = {
1665
+ ...prevEntryFile,
1666
+ values: props.values,
1667
+ updated: datetime()
1807
1668
  };
1808
- this.version = null;
1809
- this.gitPath = null;
1810
- this.queue = new PQueue({
1811
- concurrency: 1
1812
- // No concurrency because git operations are sequencial
1669
+ const entry = await this.toEntry({
1670
+ projectId: props.projectId,
1671
+ collectionId: props.collectionId,
1672
+ entryFile
1813
1673
  });
1814
- this.gitTagService = new GitTagService(options, this.git);
1815
- this.userService = userService;
1816
- this.updateVersion();
1817
- this.updateGitPath();
1674
+ this.validateValues({
1675
+ collectionId: props.collectionId,
1676
+ valueDefinitions: collection.valueDefinitions,
1677
+ values: entry.values
1678
+ });
1679
+ await this.jsonFileService.update(
1680
+ entryFile,
1681
+ entryFilePath,
1682
+ entryFileSchema
1683
+ );
1684
+ await this.gitService.add(projectPath, [entryFilePath]);
1685
+ await this.gitService.commit(projectPath, this.gitMessage.update);
1686
+ return entry;
1818
1687
  }
1819
1688
  /**
1820
- * CRUD methods to work with git tags
1689
+ * Deletes given Entry from it's Collection
1821
1690
  */
1822
- get tags() {
1823
- return this.gitTagService;
1691
+ async delete(props) {
1692
+ deleteEntrySchema.parse(props);
1693
+ const projectPath = pathTo.project(props.projectId);
1694
+ const entryFilePath = pathTo.entryFile(
1695
+ props.projectId,
1696
+ props.collectionId,
1697
+ props.id
1698
+ );
1699
+ await Fs4.remove(entryFilePath);
1700
+ await this.gitService.add(projectPath, [entryFilePath]);
1701
+ await this.gitService.commit(projectPath, this.gitMessage.delete);
1702
+ }
1703
+ async list(props) {
1704
+ listEntriesSchema.parse(props);
1705
+ const offset = props.offset || 0;
1706
+ const limit = props.limit || 15;
1707
+ const entryReferences = await this.listReferences(
1708
+ objectTypeSchema.Enum.entry,
1709
+ props.projectId,
1710
+ props.collectionId
1711
+ );
1712
+ const partialEntryReferences = entryReferences.slice(offset, limit);
1713
+ const entries = await returnResolved(
1714
+ partialEntryReferences.map((reference) => {
1715
+ return this.read({
1716
+ projectId: props.projectId,
1717
+ collectionId: props.collectionId,
1718
+ id: reference.id
1719
+ });
1720
+ })
1721
+ );
1722
+ return {
1723
+ total: entryReferences.length,
1724
+ limit,
1725
+ offset,
1726
+ list: entries
1727
+ };
1728
+ }
1729
+ async count(props) {
1730
+ countEntriesSchema.parse(props);
1731
+ return (await this.listReferences(
1732
+ objectTypeSchema.Enum.entry,
1733
+ props.projectId,
1734
+ props.collectionId
1735
+ )).length;
1824
1736
  }
1825
1737
  /**
1826
- * Create an empty Git repository or reinitialize an existing one
1827
- *
1828
- * @see https://git-scm.com/docs/git-init
1829
- *
1830
- * @param path Path to initialize in. Fails if path does not exist
1831
- * @param options Options specific to the init operation
1738
+ * Checks if given object is of type Entry
1832
1739
  */
1833
- async init(path, options) {
1834
- let args = ["init"];
1835
- if (options?.initialBranch) {
1836
- args = [...args, `--initial-branch=${options.initialBranch}`];
1837
- }
1838
- await this.git(path, args);
1839
- await this.setLocalConfig(path);
1840
- await this.installLfs(path);
1740
+ isEntry(obj) {
1741
+ return entrySchema.safeParse(obj).success;
1841
1742
  }
1842
1743
  /**
1843
- * Clone a repository into a directory
1844
- *
1845
- * @see https://git-scm.com/docs/git-clone
1846
- *
1847
- * @todo Implement progress callback / events
1848
- *
1849
- * @param url The remote repository URL to clone from
1850
- * @param path The destination path for the cloned repository.
1851
- * Which is only working if the directory is existing and empty.
1852
- * @param options Options specific to the clone operation
1744
+ * Returns a Value definition by ID
1853
1745
  */
1854
- async clone(url, path, options) {
1855
- let args = ["clone", "--progress"];
1856
- if (options?.branch) {
1857
- args = [...args, "--branch", options.branch];
1746
+ getValueDefinitionById(props) {
1747
+ const definition = props.valueDefinitions.find((def) => {
1748
+ if (def.id === props.id) {
1749
+ return true;
1750
+ }
1751
+ return false;
1752
+ });
1753
+ if (!definition) {
1754
+ throw new Error(
1755
+ `No definition with ID "${props.id}" found in Collection "${props.collectionId}" for given Value reference`
1756
+ );
1858
1757
  }
1859
- if (options?.depth) {
1860
- args = [...args, "--depth", options.depth.toString()];
1758
+ return definition;
1759
+ }
1760
+ /**
1761
+ * Validates given Values against it's Collections definitions
1762
+ */
1763
+ validateValues(props) {
1764
+ props.values.map((value) => {
1765
+ const definition = this.getValueDefinitionById({
1766
+ collectionId: props.collectionId,
1767
+ valueDefinitions: props.valueDefinitions,
1768
+ id: value.definitionId
1769
+ });
1770
+ const schema = getValueContentSchemaFromDefinition(definition);
1771
+ try {
1772
+ for (const [, content] of Object.entries(value.content)) {
1773
+ schema.parse(content);
1774
+ }
1775
+ } catch (error) {
1776
+ console.log("Definition:", definition);
1777
+ console.log("Value:", value);
1778
+ throw error;
1779
+ }
1780
+ });
1781
+ }
1782
+ /**
1783
+ * Validates given shared Value references against it's Collections definitions
1784
+ */
1785
+ // private validateResolvedSharedValues(props: {
1786
+ // collectionId: string;
1787
+ // valueDefinitions: ValueDefinition[];
1788
+ // resolvedSharedValues: ResolvedSharedValueReference[];
1789
+ // }) {
1790
+ // props.resolvedSharedValues.map((value) => {
1791
+ // const definition = this.getValueDefinitionById({
1792
+ // collectionId: props.collectionId,
1793
+ // valueDefinitions: props.valueDefinitions,
1794
+ // id: value.definitionId,
1795
+ // });
1796
+ // const schema = getValueSchemaFromDefinition(definition);
1797
+ // schema.parse(value.resolved.content);
1798
+ // });
1799
+ // }
1800
+ async resolveValueContentReference(props) {
1801
+ switch (props.valueContentReference.objectType) {
1802
+ case objectTypeSchema.Enum.asset:
1803
+ return await this.assetService.read({
1804
+ projectId: props.projectId,
1805
+ id: props.valueContentReference.id,
1806
+ language: props.valueContentReference.language
1807
+ });
1808
+ case objectTypeSchema.Enum.entry:
1809
+ return await this.read({
1810
+ projectId: props.projectId,
1811
+ collectionId: props.collectionId,
1812
+ id: props.valueContentReference.id
1813
+ });
1814
+ default:
1815
+ throw new Error(
1816
+ // @ts-ignore
1817
+ `Tried to resolve unsupported Value reference "${props.valueContentReference.referenceObjectType}"`
1818
+ );
1861
1819
  }
1862
- if (options?.singleBranch === true) {
1863
- args = [...args, "--single-branch"];
1820
+ }
1821
+ async resolveValueContentReferences(props) {
1822
+ let resolvedContent = {};
1823
+ for (const language in props.valueReference.content) {
1824
+ const referencesOfLanguage = props.valueReference.content[language];
1825
+ if (!referencesOfLanguage) {
1826
+ throw new Error(
1827
+ `Trying to access content references by language "${language}" failed`
1828
+ );
1829
+ }
1830
+ const resolvedReferencesOfLanguage = await Promise.all(
1831
+ referencesOfLanguage.map(async (reference) => {
1832
+ return await this.resolveValueContentReference({
1833
+ projectId: props.projectId,
1834
+ collectionId: props.collectionId,
1835
+ valueContentReference: reference
1836
+ });
1837
+ })
1838
+ );
1839
+ resolvedContent = {
1840
+ ...resolvedContent,
1841
+ [language]: resolvedReferencesOfLanguage
1842
+ };
1864
1843
  }
1865
- await this.git("", [...args, url, path]);
1866
- await this.setLocalConfig(path);
1844
+ return resolvedContent;
1867
1845
  }
1868
1846
  /**
1869
- * Add file contents to the index
1870
- *
1871
- * @see https://git-scm.com/docs/git-add
1872
- *
1873
- * @param path Path to the repository
1874
- * @param files Files to add
1847
+ * Creates an Entry from given EntryFile by resolving it's Values
1875
1848
  */
1876
- async add(path, files2) {
1877
- const args = ["add", "--", ...files2];
1878
- await this.git(path, args);
1849
+ async toEntry(props) {
1850
+ return {
1851
+ ...props.entryFile,
1852
+ // @ts-ignore @todo fixme - I have no idea why this happens. The types seem to be compatible to me and they work
1853
+ values: await Promise.all(
1854
+ props.entryFile.values.map(async (value) => {
1855
+ if (value.valueType === ValueTypeSchema.Enum.reference) {
1856
+ const resolvedContentReferences = await this.resolveValueContentReferences({
1857
+ projectId: props.projectId,
1858
+ collectionId: props.collectionId,
1859
+ valueReference: value
1860
+ });
1861
+ return {
1862
+ ...value,
1863
+ content: resolvedContentReferences
1864
+ };
1865
+ }
1866
+ return value;
1867
+ })
1868
+ )
1869
+ };
1870
+ }
1871
+ };
1872
+
1873
+ // src/service/GitService.ts
1874
+ import { GitProcess } from "dugite";
1875
+ import { EOL as EOL2 } from "os";
1876
+ import PQueue from "p-queue";
1877
+
1878
+ // src/service/GitTagService.ts
1879
+ import { EOL } from "os";
1880
+ var GitTagService = class extends AbstractCrudService {
1881
+ git;
1882
+ constructor(options, git) {
1883
+ super(serviceTypeSchema.Enum.GitTag, options);
1884
+ this.git = git;
1879
1885
  }
1880
1886
  /**
1881
- * Reset current HEAD to the specified state
1882
- *
1883
- * @todo maybe add more options
1884
- * @see https://git-scm.com/docs/git-reset
1887
+ * Creates a new tag
1885
1888
  *
1886
- * @param path Path to the repository
1887
- * @param mode Modifies the working tree depending on given mode
1888
- * @param commit Resets the current branch head to this commit / tag
1889
+ * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---annotate
1889
1890
  */
1890
- async reset(path, mode, commit) {
1891
- const args = ["reset", `--${mode}`, commit];
1892
- await this.git(path, args);
1891
+ async create(props) {
1892
+ createGitTagSchema.parse(props);
1893
+ const id = uuid();
1894
+ let args = ["tag", "--annotate", id];
1895
+ if (props.hash) {
1896
+ args = [...args, props.hash];
1897
+ }
1898
+ args = [...args, "-m", props.message];
1899
+ await this.git(props.path, args);
1900
+ const tag = await this.read({ path: props.path, id });
1901
+ return tag;
1893
1902
  }
1894
1903
  /**
1895
- * Restore working tree files
1896
- *
1897
- * @see https://git-scm.com/docs/git-restore/
1898
- *
1899
- * @todo It's probably a good idea to not use restore
1900
- * for a use case where someone just wants to have a look
1901
- * and maybe copy something from a deleted file.
1902
- * We should use `checkout` without `add .` and `commit` for that
1904
+ * Returns a tag by ID
1903
1905
  *
1904
- * @param path Path to the repository
1905
- * @param source Git commit SHA or tag name to restore to
1906
- * @param files Files to restore
1906
+ * Internally uses list() but only returns the tag with matching ID.
1907
1907
  */
1908
- // public async restore(
1909
- // path: string,
1910
- // source: string,
1911
- // files: string[]
1912
- // ): Promise<void> {
1913
- // const args = ['restore', `--source=${source}`, ...files];
1914
- // await this.git(path, args);
1915
- // }
1908
+ async read(props) {
1909
+ readGitTagSchema.parse(props);
1910
+ const tags = await this.list({ path: props.path });
1911
+ const tag = tags.list.find((tag2) => {
1912
+ return tag2.id === props.id;
1913
+ });
1914
+ if (!tag) {
1915
+ throw new GitError(
1916
+ `Provided tag with UUID "${props.id}" did not match any known tags`
1917
+ );
1918
+ }
1919
+ return tag;
1920
+ }
1916
1921
  /**
1917
- * Download objects and refs from remote `origin`
1918
- *
1919
- * @see https://www.git-scm.com/docs/git-fetch
1922
+ * Updating a git tag is not supported.
1923
+ * Please delete the old and create a new one
1920
1924
  *
1921
- * @param path Path to the repository
1925
+ * @see https://git-scm.com/docs/git-tag#_on_re_tagging
1922
1926
  */
1923
- async fetch(path) {
1924
- const args = ["fetch"];
1925
- await this.git(path, args);
1927
+ async update() {
1928
+ throw new Error(
1929
+ "Updating a git tag is not supported. Please delete the old and create a new one"
1930
+ );
1926
1931
  }
1927
1932
  /**
1928
- * Fetch from and integrate (rebase or merge) with a local branch
1933
+ * Deletes a tag
1929
1934
  *
1930
- * @see https://git-scm.com/docs/git-pull
1935
+ * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---delete
1931
1936
  *
1932
- * @param path Path to the repository
1937
+ * @param path Path to the repository
1938
+ * @param id UUID of the tag to delete
1933
1939
  */
1934
- async pull(path) {
1935
- const args = ["pull"];
1936
- await this.git(path, args);
1940
+ async delete(props) {
1941
+ deleteGitTagSchema.parse(props);
1942
+ const args = ["tag", "--delete", props.id];
1943
+ await this.git(props.path, args);
1937
1944
  }
1938
1945
  /**
1939
- * Update remote refs along with associated objects to remote `origin`
1946
+ * Gets all local tags or filter them by pattern
1940
1947
  *
1941
- * @see https://git-scm.com/docs/git-push
1948
+ * They are sorted by authordate of the commit, not when the tag is created.
1949
+ * This ensures tags are sorted correctly in the timeline of their commits.
1942
1950
  *
1943
- * @param path Path to the repository
1951
+ * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---list
1944
1952
  */
1945
- async push(path, options) {
1946
- let args = ["push", "origin"];
1947
- if (options?.all === true) {
1948
- args = [...args, "--all"];
1949
- }
1950
- if (options?.force === true) {
1951
- args = [...args, "--force"];
1952
- }
1953
+ async list(props) {
1954
+ listGitTagsSchema.parse(props);
1955
+ let args = ["tag", "--list"];
1956
+ args = [
1957
+ ...args,
1958
+ "--sort=-*authordate",
1959
+ "--format=%(refname:short)|%(subject)|%(*authorname)|%(*authoremail)|%(*authordate:iso-strict)"
1960
+ ];
1961
+ const result = await this.git(props.path, args);
1962
+ const noEmptyLinesArr = result.stdout.split(EOL).filter((line) => {
1963
+ return line.trim() !== "";
1964
+ });
1965
+ const lineObjArr = noEmptyLinesArr.map((line) => {
1966
+ const lineArray = line.split("|");
1967
+ return {
1968
+ id: lineArray[0],
1969
+ message: lineArray[1],
1970
+ author: {
1971
+ name: lineArray[2],
1972
+ email: lineArray[3]
1973
+ },
1974
+ datetime: datetime(lineArray[4])
1975
+ };
1976
+ });
1977
+ const gitTags = lineObjArr.filter(this.isGitTag.bind(this));
1978
+ return {
1979
+ total: gitTags.length,
1980
+ limit: 0,
1981
+ offset: 0,
1982
+ list: gitTags
1983
+ };
1984
+ }
1985
+ /**
1986
+ * Returns the total number of tags inside given repository
1987
+ *
1988
+ * Internally uses list(), so do not use count()
1989
+ * in conjuncion with it to avoid multiple git calls.
1990
+ *
1991
+ * @param path Path to the repository
1992
+ */
1993
+ async count(props) {
1994
+ countGitTagsSchema.parse(props);
1995
+ const gitTags = await this.list({ path: props.path });
1996
+ return gitTags.total;
1997
+ }
1998
+ /**
1999
+ * Type guard for GitTag
2000
+ *
2001
+ * @param obj The object to check
2002
+ */
2003
+ isGitTag(obj) {
2004
+ return gitTagSchema.safeParse(obj).success;
2005
+ }
2006
+ };
2007
+
2008
+ // src/service/GitService.ts
2009
+ var GitService = class {
2010
+ version;
2011
+ gitPath;
2012
+ queue;
2013
+ gitTagService;
2014
+ userService;
2015
+ constructor(options, userService) {
2016
+ this.version = null;
2017
+ this.gitPath = null;
2018
+ this.queue = new PQueue({
2019
+ concurrency: 1
2020
+ // No concurrency because git operations are sequencial
2021
+ });
2022
+ this.gitTagService = new GitTagService(options, this.git);
2023
+ this.userService = userService;
2024
+ this.updateVersion();
2025
+ this.updateGitPath();
2026
+ }
2027
+ /**
2028
+ * CRUD methods to work with git tags
2029
+ */
2030
+ get tags() {
2031
+ return this.gitTagService;
2032
+ }
2033
+ /**
2034
+ * Create an empty Git repository or reinitialize an existing one
2035
+ *
2036
+ * @see https://git-scm.com/docs/git-init
2037
+ *
2038
+ * @param path Path to initialize in. Fails if path does not exist
2039
+ * @param options Options specific to the init operation
2040
+ */
2041
+ async init(path, options) {
2042
+ let args = ["init"];
2043
+ if (options?.initialBranch) {
2044
+ args = [...args, `--initial-branch=${options.initialBranch}`];
2045
+ }
2046
+ await this.git(path, args);
2047
+ await this.setLocalConfig(path);
2048
+ await this.installLfs(path);
2049
+ }
2050
+ /**
2051
+ * Clone a repository into a directory
2052
+ *
2053
+ * @see https://git-scm.com/docs/git-clone
2054
+ *
2055
+ * @todo Implement progress callback / events
2056
+ *
2057
+ * @param url The remote repository URL to clone from
2058
+ * @param path The destination path for the cloned repository.
2059
+ * Which is only working if the directory is existing and empty.
2060
+ * @param options Options specific to the clone operation
2061
+ */
2062
+ async clone(url, path, options) {
2063
+ let args = ["clone", "--progress"];
2064
+ if (options?.branch) {
2065
+ args = [...args, "--branch", options.branch];
2066
+ }
2067
+ if (options?.depth) {
2068
+ args = [...args, "--depth", options.depth.toString()];
2069
+ }
2070
+ if (options?.singleBranch === true) {
2071
+ args = [...args, "--single-branch"];
2072
+ }
2073
+ await this.git("", [...args, url, path]);
2074
+ await this.setLocalConfig(path);
2075
+ }
2076
+ /**
2077
+ * Add file contents to the index
2078
+ *
2079
+ * @see https://git-scm.com/docs/git-add
2080
+ *
2081
+ * @param path Path to the repository
2082
+ * @param files Files to add
2083
+ */
2084
+ async add(path, files2) {
2085
+ const args = ["add", "--", ...files2];
2086
+ await this.git(path, args);
2087
+ }
2088
+ branches = {
2089
+ /**
2090
+ * List branches
2091
+ *
2092
+ * @see https://www.git-scm.com/docs/git-branch
2093
+ *
2094
+ * @param path Path to the repository
2095
+ */
2096
+ list: async (path) => {
2097
+ const args = ["branch", "--list", "--all"];
2098
+ const result = await this.git(path, args);
2099
+ const normalizedLinesArr = result.stdout.split(EOL2).filter((line) => {
2100
+ return line.trim() !== "";
2101
+ }).map((line) => {
2102
+ return line.trim().replace("* ", "");
2103
+ });
2104
+ const local = [];
2105
+ const remote = [];
2106
+ normalizedLinesArr.forEach((line) => {
2107
+ if (line.startsWith("remotes/")) {
2108
+ remote.push(line.replace("remotes/", ""));
2109
+ } else {
2110
+ local.push(line);
2111
+ }
2112
+ });
2113
+ return {
2114
+ local,
2115
+ remote
2116
+ };
2117
+ },
2118
+ /**
2119
+ * Returns the name of the current branch. In detached HEAD state, an empty string is returned.
2120
+ *
2121
+ * @see https://www.git-scm.com/docs/git-branch#Documentation/git-branch.txt---show-current
2122
+ *
2123
+ * @param path Path to the repository
2124
+ */
2125
+ current: async (path) => {
2126
+ const args = ["branch", "--show-current"];
2127
+ const result = await this.git(path, args);
2128
+ return result.stdout.trim();
2129
+ },
2130
+ /**
2131
+ * Switch branches
2132
+ *
2133
+ * @see https://git-scm.com/docs/git-switch/
2134
+ *
2135
+ * @param path Path to the repository
2136
+ * @param branch Name of the branch to switch to
2137
+ * @param options Options specific to the switch operation
2138
+ */
2139
+ switch: async (path, branch, options) => {
2140
+ await this.checkBranchOrTagName(path, branch);
2141
+ let args = ["switch"];
2142
+ if (options?.isNew === true) {
2143
+ args = [...args, "--create", branch];
2144
+ } else {
2145
+ args = [...args, branch];
2146
+ }
2147
+ await this.git(path, args);
2148
+ }
2149
+ };
2150
+ remotes = {
2151
+ /**
2152
+ * Returns a list of currently tracked remotes
2153
+ *
2154
+ * @see https://git-scm.com/docs/git-remote
2155
+ *
2156
+ * @param path Path to the repository
2157
+ */
2158
+ list: async (path) => {
2159
+ const args = ["remote"];
2160
+ const result = await this.git(path, args);
2161
+ const normalizedLinesArr = result.stdout.split(EOL2).filter((line) => {
2162
+ return line.trim() !== "";
2163
+ });
2164
+ return normalizedLinesArr;
2165
+ },
2166
+ /**
2167
+ * Returns true if the `origin` remote exists, otherwise false
2168
+ *
2169
+ * @param path Path to the repository
2170
+ */
2171
+ hasOrigin: async (path) => {
2172
+ const remotes = await this.remotes.list(path);
2173
+ if (remotes.includes("origin")) {
2174
+ return true;
2175
+ }
2176
+ return false;
2177
+ },
2178
+ /**
2179
+ * Adds the `origin` remote with given URL
2180
+ *
2181
+ * Throws if `origin` remote is added already.
2182
+ *
2183
+ * @see https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emaddem
2184
+ *
2185
+ * @param path Path to the repository
2186
+ */
2187
+ addOrigin: async (path, url) => {
2188
+ const args = ["remote", "add", "origin", url];
2189
+ await this.git(path, args);
2190
+ },
2191
+ /**
2192
+ * Returns the current `origin` remote URL
2193
+ *
2194
+ * Throws if no `origin` remote is added yet.
2195
+ *
2196
+ * @see https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emget-urlem
2197
+ *
2198
+ * @param path Path to the repository
2199
+ */
2200
+ getOriginUrl: async (path) => {
2201
+ const args = ["remote", "get-url", "origin"];
2202
+ const result = (await this.git(path, args)).stdout.trim();
2203
+ return result.length === 0 ? null : result;
2204
+ },
2205
+ /**
2206
+ * Sets the current `origin` remote URL
2207
+ *
2208
+ * Throws if no `origin` remote is added yet.
2209
+ *
2210
+ * @see https://git-scm.com/docs/git-remote#Documentation/git-remote.txt-emset-urlem
2211
+ *
2212
+ * @param path Path to the repository
2213
+ */
2214
+ setOriginUrl: async (path, url) => {
2215
+ const args = ["remote", "set-url", "origin", url];
2216
+ await this.git(path, args);
2217
+ }
2218
+ };
2219
+ /**
2220
+ * Reset current HEAD to the specified state
2221
+ *
2222
+ * @todo maybe add more options
2223
+ * @see https://git-scm.com/docs/git-reset
2224
+ *
2225
+ * @param path Path to the repository
2226
+ * @param mode Modifies the working tree depending on given mode
2227
+ * @param commit Resets the current branch head to this commit / tag
2228
+ */
2229
+ async reset(path, mode, commit) {
2230
+ const args = ["reset", `--${mode}`, commit];
2231
+ await this.git(path, args);
2232
+ }
2233
+ /**
2234
+ * Restore working tree files
2235
+ *
2236
+ * @see https://git-scm.com/docs/git-restore/
2237
+ *
2238
+ * @todo It's probably a good idea to not use restore
2239
+ * for a use case where someone just wants to have a look
2240
+ * and maybe copy something from a deleted file.
2241
+ * We should use `checkout` without `add .` and `commit` for that
2242
+ *
2243
+ * @param path Path to the repository
2244
+ * @param source Git commit SHA or tag name to restore to
2245
+ * @param files Files to restore
2246
+ */
2247
+ // public async restore(
2248
+ // path: string,
2249
+ // source: string,
2250
+ // files: string[]
2251
+ // ): Promise<void> {
2252
+ // const args = ['restore', `--source=${source}`, ...files];
2253
+ // await this.git(path, args);
2254
+ // }
2255
+ /**
2256
+ * Download objects and refs from remote `origin`
2257
+ *
2258
+ * @see https://www.git-scm.com/docs/git-fetch
2259
+ *
2260
+ * @param path Path to the repository
2261
+ */
2262
+ async fetch(path) {
2263
+ const args = ["fetch"];
2264
+ await this.git(path, args);
2265
+ }
2266
+ /**
2267
+ * Fetch from and integrate (rebase or merge) with a local branch
2268
+ *
2269
+ * @see https://git-scm.com/docs/git-pull
2270
+ *
2271
+ * @param path Path to the repository
2272
+ */
2273
+ async pull(path) {
2274
+ const args = ["pull"];
2275
+ await this.git(path, args);
2276
+ }
2277
+ /**
2278
+ * Update remote refs along with associated objects to remote `origin`
2279
+ *
2280
+ * @see https://git-scm.com/docs/git-push
2281
+ *
2282
+ * @param path Path to the repository
2283
+ */
2284
+ async push(path, options) {
2285
+ let args = ["push", "origin"];
2286
+ if (options?.all === true) {
2287
+ args = [...args, "--all"];
2288
+ }
2289
+ if (options?.force === true) {
2290
+ args = [...args, "--force"];
2291
+ }
1953
2292
  await this.git(path, args);
1954
2293
  }
1955
2294
  /**
@@ -1996,7 +2335,7 @@ var GitService2 = class {
1996
2335
  }
1997
2336
  const result = await this.git(path, [
1998
2337
  ...args,
1999
- "--format=%H|%s|%an|%ae|%at|%D"
2338
+ "--format=%H|%s|%an|%ae|%aI|%D"
2000
2339
  ]);
2001
2340
  const noEmptyLinesArr = result.stdout.split(EOL2).filter((line) => {
2002
2341
  return line.trim() !== "";
@@ -2010,8 +2349,8 @@ var GitService2 = class {
2010
2349
  name: lineArray[2],
2011
2350
  email: lineArray[3]
2012
2351
  },
2013
- timestamp: parseInt(lineArray[4]),
2014
- tag: this.refNameToTagName(lineArray[5])
2352
+ datetime: datetime(lineArray[4]),
2353
+ tag: this.refNameToTagName(lineArray[5] || "")
2015
2354
  };
2016
2355
  });
2017
2356
  return lineObjArr.filter(this.isGitCommit.bind(this));
@@ -2024,69 +2363,13 @@ var GitService2 = class {
2024
2363
  return tagName;
2025
2364
  }
2026
2365
  /**
2027
- * Returns a timestamp of given files creation
2028
- *
2029
- * Git only returns the timestamp the file was added,
2030
- * which could be different from the file being created.
2031
- * But since file operations will always be committed
2032
- * immediately, this is practically the same.
2366
+ * Reads the currently used version of Git
2033
2367
  *
2034
- * @param path Path to the repository
2035
- * @param file File to get timestamp from
2368
+ * This can help debugging
2036
2369
  */
2037
- async getFileCreatedTimestamp(path, file) {
2038
- const result = await this.git(path, [
2039
- "log",
2040
- "--diff-filter=A",
2041
- "--follow",
2042
- "--format=%at",
2043
- "--max-count=1",
2044
- "--",
2045
- file
2046
- ]);
2047
- return parseInt(result.stdout);
2048
- }
2049
- /**
2050
- * Returns a timestamp of the files last modification
2051
- *
2052
- * @param path Path to the repository
2053
- * @param file File to get timestamp from
2054
- */
2055
- async getFileLastUpdatedTimestamp(path, file) {
2056
- const result = await this.git(path, [
2057
- "log",
2058
- "--follow",
2059
- "--format=%at",
2060
- "--max-count=1",
2061
- "--",
2062
- file
2063
- ]);
2064
- return parseInt(result.stdout);
2065
- }
2066
- /**
2067
- * Returns created and updated timestamps from given file
2068
- *
2069
- * @param path Path to the project
2070
- * @param file Path to the file
2071
- */
2072
- async getFileCreatedUpdatedMeta(path, file) {
2073
- const meta = await Promise.all([
2074
- this.getFileCreatedTimestamp(path, file),
2075
- this.getFileLastUpdatedTimestamp(path, file)
2076
- ]);
2077
- return {
2078
- created: meta[0],
2079
- updated: meta[1]
2080
- };
2081
- }
2082
- /**
2083
- * Reads the currently used version of Git
2084
- *
2085
- * This can help debugging
2086
- */
2087
- async updateVersion() {
2088
- const result = await this.git("", ["--version"]);
2089
- this.version = result.stdout.replace("git version", "").trim();
2370
+ async updateVersion() {
2371
+ const result = await this.git("", ["--version"]);
2372
+ this.version = result.stdout.replace("git version", "").trim();
2090
2373
  }
2091
2374
  /**
2092
2375
  * Reads the path to the executable of Git that is used
@@ -2144,475 +2427,118 @@ var GitService2 = class {
2144
2427
  await this.git(path, autoSetupRemoteArgs);
2145
2428
  }
2146
2429
  /**
2147
- * Type guard for GitCommit
2148
- *
2149
- * @param obj The object to check
2150
- */
2151
- isGitCommit(obj) {
2152
- return gitCommitSchema.safeParse(obj).success;
2153
- }
2154
- /**
2155
- * Wraps the execution of any git command
2156
- * to use a FIFO queue for sequential processing
2157
- *
2158
- * @param path Path to the repository
2159
- * @param args Arguments to append after the `git` command
2160
- */
2161
- async git(path, args) {
2162
- const result = await this.queue.add(
2163
- () => GitProcess.exec(args, path, {
2164
- env: {
2165
- // @todo Nasty stuff - remove after update to dugite with git > v2.45.2 once available
2166
- // @see https://github.com/git-lfs/git-lfs/issues/5749
2167
- GIT_CLONE_PROTECTION_ACTIVE: "false"
2168
- }
2169
- })
2170
- );
2171
- if (!result) {
2172
- throw new GitError(
2173
- `Git ${this.version} (${this.gitPath}) command "git ${args.join(
2174
- " "
2175
- )}" failed to return a result`
2176
- );
2177
- }
2178
- if (result.exitCode !== 0) {
2179
- throw new GitError(
2180
- `Git ${this.version} (${this.gitPath}) command "git ${args.join(
2181
- " "
2182
- )}" failed with exit code "${result.exitCode}" and message "${result.stderr}"`
2183
- );
2184
- }
2185
- return result;
2186
- }
2187
- };
2188
-
2189
- // src/service/CollectionService.ts
2190
- var CollectionService = class extends AbstractCrudService {
2191
- constructor(options, jsonFileService, gitService) {
2192
- super(serviceTypeSchema.Enum.Collection, options);
2193
- this.jsonFileService = jsonFileService;
2194
- this.gitService = gitService;
2195
- }
2196
- /**
2197
- * Creates a new Collection
2198
- */
2199
- async create(props) {
2200
- createCollectionSchema.parse(props);
2201
- const id = uuid();
2202
- const projectPath = pathTo.project(props.projectId);
2203
- const collectionPath = pathTo.collection(props.projectId, id);
2204
- const collectionFilePath = pathTo.collectionFile(props.projectId, id);
2205
- const collectionFile = {
2206
- ...props,
2207
- objectType: "collection",
2208
- id,
2209
- slug: {
2210
- singular: slug(props.slug.singular),
2211
- plural: slug(props.slug.plural)
2212
- },
2213
- created: currentTimestamp(),
2214
- updated: null
2215
- };
2216
- await Fs4.ensureDir(collectionPath);
2217
- await this.jsonFileService.create(
2218
- collectionFile,
2219
- collectionFilePath,
2220
- collectionFileSchema
2221
- );
2222
- await this.gitService.add(projectPath, [collectionFilePath]);
2223
- await this.gitService.commit(projectPath, this.gitMessage.create);
2224
- return collectionFile;
2225
- }
2226
- /**
2227
- * Returns a Collection by ID
2228
- */
2229
- async read(props) {
2230
- readCollectionSchema.parse(props);
2231
- const collection = await this.jsonFileService.read(
2232
- pathTo.collectionFile(props.projectId, props.id),
2233
- collectionFileSchema
2234
- );
2235
- return collection;
2236
- }
2237
- /**
2238
- * Updates given Collection
2239
- *
2240
- * @todo finish implementing checks for FieldDefinitions and extract methods
2241
- *
2242
- * @param projectId Project ID of the collection to update
2243
- * @param collection Collection to write to disk
2244
- * @returns An object containing information about the actions needed to be taken,
2245
- * before given update can be executed or void if the update was executed successfully
2246
- */
2247
- async update(props) {
2248
- updateCollectionSchema.parse(props);
2249
- const projectPath = pathTo.project(props.projectId);
2250
- const collectionFilePath = pathTo.collectionFile(props.projectId, props.id);
2251
- const prevCollectionFile = await this.read(props);
2252
- const collectionFile = {
2253
- ...prevCollectionFile,
2254
- ...props,
2255
- updated: currentTimestamp()
2256
- };
2257
- await this.jsonFileService.update(
2258
- collectionFile,
2259
- collectionFilePath,
2260
- collectionFileSchema
2261
- );
2262
- await this.gitService.add(projectPath, [collectionFilePath]);
2263
- await this.gitService.commit(projectPath, this.gitMessage.update);
2264
- return collectionFile;
2265
- }
2266
- /**
2267
- * Deletes given Collection (folder), including it's items
2268
- *
2269
- * The Fields that Collection used are not deleted.
2270
- */
2271
- async delete(props) {
2272
- deleteCollectionSchema.parse(props);
2273
- const projectPath = pathTo.project(props.projectId);
2274
- const collectionPath = pathTo.collection(props.projectId, props.id);
2275
- await Fs4.remove(collectionPath);
2276
- await this.gitService.add(projectPath, [collectionPath]);
2277
- await this.gitService.commit(projectPath, this.gitMessage.delete);
2278
- }
2279
- async list(props) {
2280
- listCollectionsSchema.parse(props);
2281
- const offset = props.offset || 0;
2282
- const limit = props.limit || 15;
2283
- const collectionReferences = await this.listReferences(
2284
- objectTypeSchema.Enum.collection,
2285
- props.projectId
2286
- );
2287
- const partialCollectionReferences = collectionReferences.slice(
2288
- offset,
2289
- limit
2290
- );
2291
- const collections = await returnResolved(
2292
- partialCollectionReferences.map((reference) => {
2293
- return this.read({
2294
- projectId: props.projectId,
2295
- id: reference.id
2296
- });
2297
- })
2298
- );
2299
- return {
2300
- total: collectionReferences.length,
2301
- limit,
2302
- offset,
2303
- list: collections
2304
- };
2305
- }
2306
- async count(props) {
2307
- countCollectionsSchema.parse(props);
2308
- const count = (await this.listReferences(
2309
- objectTypeSchema.Enum.collection,
2310
- props.projectId
2311
- )).length;
2312
- return count;
2313
- }
2314
- /**
2315
- * Checks if given object is of type Collection
2316
- */
2317
- isCollection(obj) {
2318
- return collectionFileSchema.safeParse(obj).success;
2319
- }
2320
- };
2321
-
2322
- // src/service/EntryService.ts
2323
- import Fs5 from "fs-extra";
2324
- var EntryService = class extends AbstractCrudService {
2325
- // private sharedValueService: SharedValueService;
2326
- constructor(options, jsonFileService, gitService, collectionService, assetService) {
2327
- super(serviceTypeSchema.Enum.Entry, options);
2328
- this.jsonFileService = jsonFileService;
2329
- this.gitService = gitService;
2330
- this.collectionService = collectionService;
2331
- this.assetService = assetService;
2332
- }
2333
- /**
2334
- * Creates a new Entry for given Collection
2335
- */
2336
- async create(props) {
2337
- createEntrySchema.parse(props);
2338
- const id = uuid();
2339
- const projectPath = pathTo.project(props.projectId);
2340
- const entryFilePath = pathTo.entryFile(
2341
- props.projectId,
2342
- props.collectionId,
2343
- id
2344
- );
2345
- const collection = await this.collectionService.read({
2346
- projectId: props.projectId,
2347
- id: props.collectionId
2348
- });
2349
- const entryFile = {
2350
- objectType: "entry",
2351
- id,
2352
- values: props.values,
2353
- created: currentTimestamp(),
2354
- updated: null
2355
- };
2356
- const entry = await this.toEntry({
2357
- projectId: props.projectId,
2358
- collectionId: props.collectionId,
2359
- entryFile
2360
- });
2361
- this.validateValues({
2362
- collectionId: props.collectionId,
2363
- valueDefinitions: collection.valueDefinitions,
2364
- values: entry.values
2365
- });
2366
- await this.jsonFileService.create(
2367
- entryFile,
2368
- entryFilePath,
2369
- entryFileSchema
2370
- );
2371
- await this.gitService.add(projectPath, [entryFilePath]);
2372
- await this.gitService.commit(projectPath, this.gitMessage.create);
2373
- return entry;
2374
- }
2375
- /**
2376
- * Returns an Entry from given Collection by ID and language
2377
- */
2378
- async read(props) {
2379
- readEntrySchema.parse(props);
2380
- const entryFile = await this.jsonFileService.read(
2381
- pathTo.entryFile(props.projectId, props.collectionId, props.id),
2382
- entryFileSchema
2383
- );
2384
- return await this.toEntry({
2385
- projectId: props.projectId,
2386
- collectionId: props.collectionId,
2387
- entryFile
2388
- });
2389
- }
2390
- /**
2391
- * Updates an Entry of given Collection with new Values and shared Values
2392
- */
2393
- async update(props) {
2394
- updateEntrySchema.parse(props);
2395
- const projectPath = pathTo.project(props.projectId);
2396
- const entryFilePath = pathTo.entryFile(
2397
- props.projectId,
2398
- props.collectionId,
2399
- props.id
2400
- );
2401
- const collection = await this.collectionService.read({
2402
- projectId: props.projectId,
2403
- id: props.collectionId
2404
- });
2405
- const prevEntryFile = await this.read({
2406
- projectId: props.projectId,
2407
- collectionId: props.collectionId,
2408
- id: props.id
2409
- });
2410
- const entryFile = {
2411
- ...prevEntryFile,
2412
- values: props.values,
2413
- updated: currentTimestamp()
2414
- };
2415
- const entry = await this.toEntry({
2416
- projectId: props.projectId,
2417
- collectionId: props.collectionId,
2418
- entryFile
2419
- });
2420
- this.validateValues({
2421
- collectionId: props.collectionId,
2422
- valueDefinitions: collection.valueDefinitions,
2423
- values: entry.values
2424
- });
2425
- await this.jsonFileService.update(
2426
- entryFile,
2427
- entryFilePath,
2428
- entryFileSchema
2429
- );
2430
- await this.gitService.add(projectPath, [entryFilePath]);
2431
- await this.gitService.commit(projectPath, this.gitMessage.update);
2432
- return entry;
2433
- }
2434
- /**
2435
- * Deletes given Entry from it's Collection
2436
- */
2437
- async delete(props) {
2438
- deleteEntrySchema.parse(props);
2439
- const projectPath = pathTo.project(props.projectId);
2440
- const entryFilePath = pathTo.entryFile(
2441
- props.projectId,
2442
- props.collectionId,
2443
- props.id
2444
- );
2445
- await Fs5.remove(entryFilePath);
2446
- await this.gitService.add(projectPath, [entryFilePath]);
2447
- await this.gitService.commit(projectPath, this.gitMessage.delete);
2448
- }
2449
- async list(props) {
2450
- listEntriesSchema.parse(props);
2451
- const offset = props.offset || 0;
2452
- const limit = props.limit || 15;
2453
- const entryReferences = await this.listReferences(
2454
- objectTypeSchema.Enum.entry,
2455
- props.projectId,
2456
- props.collectionId
2457
- );
2458
- const partialEntryReferences = entryReferences.slice(offset, limit);
2459
- const entries = await returnResolved(
2460
- partialEntryReferences.map((reference) => {
2461
- return this.read({
2462
- projectId: props.projectId,
2463
- collectionId: props.collectionId,
2464
- id: reference.id
2465
- });
2466
- })
2467
- );
2468
- return {
2469
- total: entryReferences.length,
2470
- limit,
2471
- offset,
2472
- list: entries
2473
- };
2474
- }
2475
- async count(props) {
2476
- countEntriesSchema.parse(props);
2477
- return (await this.listReferences(
2478
- objectTypeSchema.Enum.entry,
2479
- props.projectId,
2480
- props.collectionId
2481
- )).length;
2482
- }
2483
- /**
2484
- * Checks if given object is of type Entry
2430
+ * Type guard for GitCommit
2431
+ *
2432
+ * @param obj The object to check
2485
2433
  */
2486
- isEntry(obj) {
2487
- return entrySchema.safeParse(obj).success;
2434
+ isGitCommit(obj) {
2435
+ return gitCommitSchema.safeParse(obj).success;
2488
2436
  }
2489
2437
  /**
2490
- * Returns a Value definition by ID
2438
+ * Wraps the execution of any git command
2439
+ * to use a FIFO queue for sequential processing
2440
+ *
2441
+ * @param path Path to the repository
2442
+ * @param args Arguments to append after the `git` command
2491
2443
  */
2492
- getValueDefinitionById(props) {
2493
- const definition = props.valueDefinitions.find((def) => {
2494
- if (def.id === props.id) {
2495
- return true;
2496
- }
2497
- return false;
2498
- });
2499
- if (!definition) {
2500
- throw new Error(
2501
- `No definition with ID "${props.id}" found in Collection "${props.collectionId}" for given Value reference`
2444
+ async git(path, args) {
2445
+ const result = await this.queue.add(
2446
+ () => GitProcess.exec(args, path, {
2447
+ env: {
2448
+ // @todo Nasty stuff - remove after update to dugite with git > v2.45.2 once available
2449
+ // @see https://github.com/git-lfs/git-lfs/issues/5749
2450
+ GIT_CLONE_PROTECTION_ACTIVE: "false"
2451
+ }
2452
+ })
2453
+ );
2454
+ if (!result) {
2455
+ throw new GitError(
2456
+ `Git ${this.version} (${this.gitPath}) command "git ${args.join(
2457
+ " "
2458
+ )}" failed to return a result`
2502
2459
  );
2503
2460
  }
2504
- return definition;
2461
+ if (result.exitCode !== 0) {
2462
+ throw new GitError(
2463
+ `Git ${this.version} (${this.gitPath}) command "git ${args.join(
2464
+ " "
2465
+ )}" failed with exit code "${result.exitCode}" and message "${result.stderr}"`
2466
+ );
2467
+ }
2468
+ return result;
2469
+ }
2470
+ };
2471
+
2472
+ // src/service/JsonFileService.ts
2473
+ import Fs5 from "fs-extra";
2474
+ var JsonFileService = class extends AbstractCrudService {
2475
+ cache = /* @__PURE__ */ new Map();
2476
+ constructor(options) {
2477
+ super(serviceTypeSchema.Enum.JsonFile, options);
2505
2478
  }
2506
2479
  /**
2507
- * Validates given Values against it's Collections definitions
2480
+ * Creates a new file on disk. Fails if path already exists
2481
+ *
2482
+ * @param data Data to write into the file
2483
+ * @param path Path to write the file to
2484
+ * @param schema Schema of the file to validate against
2485
+ * @returns Validated content of the file from disk
2508
2486
  */
2509
- validateValues(props) {
2510
- props.values.map((value) => {
2511
- const definition = this.getValueDefinitionById({
2512
- collectionId: props.collectionId,
2513
- valueDefinitions: props.valueDefinitions,
2514
- id: value.definitionId
2515
- });
2516
- const schema = getValueContentSchemaFromDefinition(definition);
2517
- try {
2518
- for (const [language, content] of Object.entries(value.content)) {
2519
- schema.parse(content);
2520
- }
2521
- } catch (error) {
2522
- console.log("Definition:", definition);
2523
- console.log("Value:", value);
2524
- throw error;
2525
- }
2487
+ async create(data, path, schema) {
2488
+ const parsedData = schema.parse(data);
2489
+ const string = this.serialize(parsedData);
2490
+ await Fs5.writeFile(path, string, {
2491
+ flag: "wx",
2492
+ encoding: "utf8"
2526
2493
  });
2494
+ this.cache.set(path, parsedData);
2495
+ return parsedData;
2527
2496
  }
2528
2497
  /**
2529
- * Validates given shared Value references against it's Collections definitions
2498
+ * Reads the content of a file on disk. Fails if path does not exist
2499
+ *
2500
+ * @param path Path to read the file from
2501
+ * @param schema Schema of the file to validate against
2502
+ * @returns Validated content of the file from disk
2530
2503
  */
2531
- // private validateResolvedSharedValues(props: {
2532
- // collectionId: string;
2533
- // valueDefinitions: ValueDefinition[];
2534
- // resolvedSharedValues: ResolvedSharedValueReference[];
2535
- // }) {
2536
- // props.resolvedSharedValues.map((value) => {
2537
- // const definition = this.getValueDefinitionById({
2538
- // collectionId: props.collectionId,
2539
- // valueDefinitions: props.valueDefinitions,
2540
- // id: value.definitionId,
2541
- // });
2542
- // const schema = getValueSchemaFromDefinition(definition);
2543
- // schema.parse(value.resolved.content);
2544
- // });
2545
- // }
2546
- async resolveValueContentReference(props) {
2547
- switch (props.valueContentReference.objectType) {
2548
- case objectTypeSchema.Enum.asset:
2549
- return await this.assetService.read({
2550
- projectId: props.projectId,
2551
- id: props.valueContentReference.id,
2552
- language: props.valueContentReference.language
2553
- });
2554
- case objectTypeSchema.Enum.entry:
2555
- return await this.read({
2556
- projectId: props.projectId,
2557
- collectionId: props.collectionId,
2558
- id: props.valueContentReference.id
2559
- });
2560
- default:
2561
- throw new Error(
2562
- // @ts-ignore
2563
- `Tried to resolve unsupported Value reference "${props.valueContentReference.referenceObjectType}"`
2564
- );
2565
- }
2566
- }
2567
- async resolveValueContentReferences(props) {
2568
- let resolvedContent = {};
2569
- for (const language in props.valueReference.content) {
2570
- const referencesOfLanguage = props.valueReference.content[language];
2571
- if (!referencesOfLanguage) {
2572
- throw new Error(
2573
- `Trying to access content references by language "${language}" failed`
2574
- );
2575
- }
2576
- const resolvedReferencesOfLanguage = await Promise.all(
2577
- referencesOfLanguage.map(async (reference) => {
2578
- return await this.resolveValueContentReference({
2579
- projectId: props.projectId,
2580
- collectionId: props.collectionId,
2581
- valueContentReference: reference
2582
- });
2583
- })
2584
- );
2585
- resolvedContent = {
2586
- ...resolvedContent,
2587
- [language]: resolvedReferencesOfLanguage
2588
- };
2504
+ async read(path, schema) {
2505
+ if (this.cache.has(path)) {
2506
+ return this.cache.get(path);
2589
2507
  }
2590
- return resolvedContent;
2508
+ const data = await Fs5.readFile(path, {
2509
+ flag: "r",
2510
+ encoding: "utf8"
2511
+ });
2512
+ const json = this.deserialize(data);
2513
+ const parsedData = schema.parse(json);
2514
+ this.cache.set(path, parsedData);
2515
+ return parsedData;
2591
2516
  }
2592
2517
  /**
2593
- * Creates an Entry from given EntryFile by resolving it's Values
2518
+ * Overwrites an existing file on disk
2519
+ *
2520
+ * @todo Check how to error out if the file does not exist already
2521
+ *
2522
+ * @param data Data to write into the file
2523
+ * @param path Path to the file to overwrite
2524
+ * @param schema Schema of the file to validate against
2525
+ * @returns Validated content of the file from disk
2594
2526
  */
2595
- async toEntry(props) {
2596
- return {
2597
- ...props.entryFile,
2598
- // @ts-ignore @todo fixme - I have no idea why this happens. The types seem to be compatible to me and they work
2599
- values: await Promise.all(
2600
- props.entryFile.values.map(async (value) => {
2601
- if (value.valueType === ValueTypeSchema.Enum.reference) {
2602
- const resolvedContentReferences = await this.resolveValueContentReferences({
2603
- projectId: props.projectId,
2604
- collectionId: props.collectionId,
2605
- valueReference: value
2606
- });
2607
- return {
2608
- ...value,
2609
- content: resolvedContentReferences
2610
- };
2611
- }
2612
- return value;
2613
- })
2614
- )
2615
- };
2527
+ async update(data, path, schema) {
2528
+ const parsedData = schema.parse(data);
2529
+ const string = this.serialize(parsedData);
2530
+ await Fs5.writeFile(path, string, {
2531
+ flag: "w",
2532
+ encoding: "utf8"
2533
+ });
2534
+ this.cache.set(path, parsedData);
2535
+ return parsedData;
2536
+ }
2537
+ serialize(data) {
2538
+ return JSON.stringify(data, null, this.options.file.json.indentation);
2539
+ }
2540
+ deserialize(data) {
2541
+ return JSON.parse(data);
2616
2542
  }
2617
2543
  };
2618
2544
 
@@ -2622,47 +2548,14 @@ import Os2 from "os";
2622
2548
  import Path2 from "path";
2623
2549
  import Semver from "semver";
2624
2550
  var ProjectService = class extends AbstractCrudService {
2551
+ jsonFileService;
2552
+ userService;
2553
+ gitService;
2554
+ assetService;
2555
+ collectionService;
2556
+ entryService;
2625
2557
  constructor(options, jsonFileService, userService, gitService, assetService, collectionService, entryService) {
2626
2558
  super(serviceTypeSchema.Enum.Project, options);
2627
- this.branches = {
2628
- list: async (props) => {
2629
- listBranchesProjectSchema.parse(props);
2630
- const projectPath = pathTo.project(props.id);
2631
- await this.gitService.fetch(projectPath);
2632
- return await this.gitService.branches.list(projectPath);
2633
- },
2634
- current: async (props) => {
2635
- currentBranchProjectSchema.parse(props);
2636
- const projectPath = pathTo.project(props.id);
2637
- return await this.gitService.branches.current(projectPath);
2638
- },
2639
- switch: async (props) => {
2640
- switchBranchProjectSchema.parse(props);
2641
- const projectPath = pathTo.project(props.id);
2642
- return await this.gitService.branches.switch(
2643
- projectPath,
2644
- props.branch,
2645
- props.options
2646
- );
2647
- }
2648
- };
2649
- this.remotes = {
2650
- getOriginUrl: async (props) => {
2651
- getRemoteOriginUrlProjectSchema.parse(props);
2652
- const projectPath = pathTo.project(props.id);
2653
- return await this.gitService.remotes.getOriginUrl(projectPath);
2654
- },
2655
- setOriginUrl: async (props) => {
2656
- setRemoteOriginUrlProjectSchema.parse(props);
2657
- const projectPath = pathTo.project(props.id);
2658
- const hasOrigin = await this.gitService.remotes.hasOrigin(projectPath);
2659
- if (!hasOrigin) {
2660
- await this.gitService.remotes.addOrigin(projectPath, props.url);
2661
- } else {
2662
- await this.gitService.remotes.setOriginUrl(projectPath, props.url);
2663
- }
2664
- }
2665
- };
2666
2559
  this.jsonFileService = jsonFileService;
2667
2560
  this.userService = userService;
2668
2561
  this.gitService = gitService;
@@ -2692,7 +2585,7 @@ var ProjectService = class extends AbstractCrudService {
2692
2585
  id,
2693
2586
  description: props.description || "",
2694
2587
  settings: Object.assign({}, defaultSettings, props.settings),
2695
- created: currentTimestamp(),
2588
+ created: datetime(),
2696
2589
  updated: null,
2697
2590
  coreVersion: this.options.version,
2698
2591
  // @todo should be read from package.json to avoid duplicates
@@ -2776,8 +2669,15 @@ var ProjectService = class extends AbstractCrudService {
2776
2669
  const prevProjectFile = await this.read(props);
2777
2670
  const projectFile = {
2778
2671
  ...prevProjectFile,
2779
- ...props,
2780
- updated: currentTimestamp()
2672
+ name: props.name || prevProjectFile.name,
2673
+ description: props.description || prevProjectFile.description,
2674
+ settings: {
2675
+ language: {
2676
+ supported: props.settings?.language.supported || prevProjectFile.settings.language.supported,
2677
+ default: props.settings?.language.default || prevProjectFile.settings.language.default
2678
+ }
2679
+ },
2680
+ updated: datetime()
2781
2681
  };
2782
2682
  await this.jsonFileService.update(projectFile, filePath, projectFileSchema);
2783
2683
  await this.gitService.add(projectPath, [filePath]);
@@ -2822,6 +2722,7 @@ var ProjectService = class extends AbstractCrudService {
2822
2722
  if (upgrade.to !== "0.0.0") {
2823
2723
  return upgrade;
2824
2724
  }
2725
+ return;
2825
2726
  });
2826
2727
  for (let index = 0; index < sortedUpgrades.length; index++) {
2827
2728
  const upgrade = sortedUpgrades[index];
@@ -2852,6 +2753,45 @@ var ProjectService = class extends AbstractCrudService {
2852
2753
  }
2853
2754
  }
2854
2755
  }
2756
+ branches = {
2757
+ list: async (props) => {
2758
+ listBranchesProjectSchema.parse(props);
2759
+ const projectPath = pathTo.project(props.id);
2760
+ await this.gitService.fetch(projectPath);
2761
+ return await this.gitService.branches.list(projectPath);
2762
+ },
2763
+ current: async (props) => {
2764
+ currentBranchProjectSchema.parse(props);
2765
+ const projectPath = pathTo.project(props.id);
2766
+ return await this.gitService.branches.current(projectPath);
2767
+ },
2768
+ switch: async (props) => {
2769
+ switchBranchProjectSchema.parse(props);
2770
+ const projectPath = pathTo.project(props.id);
2771
+ return await this.gitService.branches.switch(
2772
+ projectPath,
2773
+ props.branch,
2774
+ props.options
2775
+ );
2776
+ }
2777
+ };
2778
+ remotes = {
2779
+ getOriginUrl: async (props) => {
2780
+ getRemoteOriginUrlProjectSchema.parse(props);
2781
+ const projectPath = pathTo.project(props.id);
2782
+ return await this.gitService.remotes.getOriginUrl(projectPath);
2783
+ },
2784
+ setOriginUrl: async (props) => {
2785
+ setRemoteOriginUrlProjectSchema.parse(props);
2786
+ const projectPath = pathTo.project(props.id);
2787
+ const hasOrigin = await this.gitService.remotes.hasOrigin(projectPath);
2788
+ if (!hasOrigin) {
2789
+ await this.gitService.remotes.addOrigin(projectPath, props.url);
2790
+ } else {
2791
+ await this.gitService.remotes.setOriginUrl(projectPath, props.url);
2792
+ }
2793
+ }
2794
+ };
2855
2795
  /**
2856
2796
  * Returns the differences of the given Projects current branch
2857
2797
  * between the local and remote `origin` (commits ahead & behind)
@@ -3003,8 +2943,50 @@ var ProjectService = class extends AbstractCrudService {
3003
2943
  }
3004
2944
  };
3005
2945
 
2946
+ // src/service/UserService.ts
2947
+ var UserService = class {
2948
+ jsonFileService;
2949
+ constructor(jsonFileService) {
2950
+ this.jsonFileService = jsonFileService;
2951
+ }
2952
+ /**
2953
+ * Returns the User currently working with Core
2954
+ */
2955
+ async get() {
2956
+ try {
2957
+ return await this.jsonFileService.read(pathTo.userFile, userFileSchema);
2958
+ } catch (error) {
2959
+ return void 0;
2960
+ }
2961
+ }
2962
+ /**
2963
+ * Sets the User currently working with Core
2964
+ *
2965
+ * By doing so all git operations are done with the signature of this User
2966
+ */
2967
+ async set(props) {
2968
+ setUserSchema.parse(props);
2969
+ const userFilePath = pathTo.userFile;
2970
+ const userFile = {
2971
+ ...props
2972
+ };
2973
+ if (userFile.userType === UserTypeSchema.Enum.cloud) {
2974
+ }
2975
+ await this.jsonFileService.update(userFile, userFilePath, userFileSchema);
2976
+ return userFile;
2977
+ }
2978
+ };
2979
+
3006
2980
  // src/index.node.ts
3007
2981
  var ElekIoCore = class {
2982
+ options;
2983
+ userService;
2984
+ gitService;
2985
+ jsonFileService;
2986
+ assetService;
2987
+ projectService;
2988
+ collectionService;
2989
+ entryService;
3008
2990
  // private readonly sharedValueService: SharedValueService;
3009
2991
  constructor(props) {
3010
2992
  const parsedProps = constructorElekIoCoreSchema.parse(props);
@@ -3020,7 +3002,7 @@ var ElekIoCore = class {
3020
3002
  this.options = Object.assign({}, defaults, parsedProps);
3021
3003
  this.jsonFileService = new JsonFileService(this.options);
3022
3004
  this.userService = new UserService(this.jsonFileService);
3023
- this.gitService = new GitService2(this.options, this.userService);
3005
+ this.gitService = new GitService(this.options, this.userService);
3024
3006
  this.assetService = new AssetService(
3025
3007
  this.options,
3026
3008
  this.jsonFileService,
@@ -3142,8 +3124,8 @@ export {
3142
3124
  createGitTagSchema,
3143
3125
  createProjectSchema,
3144
3126
  currentBranchProjectSchema,
3145
- currentTimestamp,
3146
3127
  dateValueDefinitionSchema,
3128
+ datetime,
3147
3129
  datetimeValueDefinitionSchema,
3148
3130
  ElekIoCore as default,
3149
3131
  deleteAssetSchema,