@elek-io/core 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,2177 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ default: () => ElekIoCore
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+ var import_shared13 = require("@elek-io/shared");
37
+ var import_fs_extra8 = __toESM(require("fs-extra"), 1);
38
+
39
+ // src/service/AssetService.ts
40
+ var import_shared7 = require("@elek-io/shared");
41
+ var import_fs_extra3 = __toESM(require("fs-extra"), 1);
42
+ var import_is_svg = __toESM(require("is-svg"), 1);
43
+
44
+ // src/error/RequiredParameterMissingError.ts
45
+ var RequiredParameterMissingError = class extends Error {
46
+ constructor(parameter) {
47
+ super(`Missing required parameter "${parameter}"`);
48
+ this.name = "RequiredParameterMissingError";
49
+ }
50
+ };
51
+
52
+ // src/util/index.ts
53
+ var util_exports = {};
54
+ __export(util_exports, {
55
+ assignDefaultIfMissing: () => assignDefaultIfMissing,
56
+ files: () => files,
57
+ folders: () => folders,
58
+ fromPath: () => fromPath,
59
+ getDuplicates: () => getDuplicates,
60
+ getRelativePath: () => getRelativePath,
61
+ isNoError: () => isNoError,
62
+ notEmpty: () => notEmpty,
63
+ pathTo: () => pathTo,
64
+ returnResolved: () => returnResolved,
65
+ spawnChildProcess: () => spawnChildProcess,
66
+ workingDirectory: () => workingDirectory
67
+ });
68
+ var import_shared = require("@elek-io/shared");
69
+ var import_child_process = require("child_process");
70
+ var import_fs_extra = __toESM(require("fs-extra"), 1);
71
+ var import_lodash_es = require("lodash-es");
72
+ var import_os = __toESM(require("os"), 1);
73
+ var import_path = __toESM(require("path"), 1);
74
+ var workingDirectory = import_path.default.join(import_os.default.homedir(), "elek.io");
75
+ var pathTo = {
76
+ tmp: import_path.default.join(workingDirectory, "tmp"),
77
+ userFile: import_path.default.join(workingDirectory, "user.json"),
78
+ // logs: Path.join(workingDirectory, 'logs'),
79
+ projects: import_path.default.join(workingDirectory, "projects"),
80
+ project: (projectId) => {
81
+ return import_path.default.join(pathTo.projects, projectId);
82
+ },
83
+ projectFile: (projectId) => {
84
+ return import_path.default.join(pathTo.project(projectId), "project.json");
85
+ },
86
+ // projectLogs: (projectId: string): string => {
87
+ // return Path.join(pathTo.project(projectId), projectFolderSchema.Enum.logs);
88
+ // },
89
+ // public: (projectId: string): string => {
90
+ // return Path.join(pathTo.project(projectId), 'public');
91
+ // },
92
+ lfs: (projectId) => {
93
+ return import_path.default.join(pathTo.project(projectId), import_shared.projectFolderSchema.Enum.lfs);
94
+ },
95
+ collections: (projectId) => {
96
+ return import_path.default.join(
97
+ pathTo.project(projectId),
98
+ import_shared.projectFolderSchema.Enum.collections
99
+ );
100
+ },
101
+ collection: (projectId, id) => {
102
+ return import_path.default.join(pathTo.collections(projectId), id);
103
+ },
104
+ collectionFile: (projectId, id) => {
105
+ return import_path.default.join(pathTo.collection(projectId, id), "collection.json");
106
+ },
107
+ entries: (projectId, collectionId) => {
108
+ return import_path.default.join(pathTo.collection(projectId, collectionId));
109
+ },
110
+ entryFile: (projectId, collectionId, id, language) => {
111
+ return import_path.default.join(
112
+ pathTo.entries(projectId, collectionId),
113
+ `${id}.${language}.json`
114
+ );
115
+ },
116
+ values: (projectId) => {
117
+ return import_path.default.join(pathTo.project(projectId), "values");
118
+ },
119
+ valueFile: (projectId, id, language) => {
120
+ return import_path.default.join(pathTo.values(projectId), `${id}.${language}.json`);
121
+ },
122
+ assets: (projectId) => {
123
+ return import_path.default.join(
124
+ pathTo.project(projectId),
125
+ import_shared.projectFolderSchema.Enum.assets
126
+ );
127
+ },
128
+ assetFile: (projectId, id, language) => {
129
+ return import_path.default.join(pathTo.assets(projectId), `${id}.${language}.json`);
130
+ },
131
+ asset: (projectId, id, language, extension) => {
132
+ return import_path.default.join(pathTo.lfs(projectId), `${id}.${language}.${extension}`);
133
+ }
134
+ };
135
+ var fromPath = {
136
+ projectId: (path) => {
137
+ const startsWith = "projects/";
138
+ const endsWith = "/";
139
+ const start = path.indexOf(startsWith) + startsWith.length;
140
+ if (start === -1) {
141
+ return void 0;
142
+ }
143
+ const end = path.indexOf(endsWith, start);
144
+ const result = path.substring(start, end === -1 ? path.length : end);
145
+ if (result && import_shared.uuidSchema.safeParse(result).success) {
146
+ return result;
147
+ }
148
+ return void 0;
149
+ }
150
+ };
151
+ function assignDefaultIfMissing(value, defaultsTo) {
152
+ return Object.assign(defaultsTo, value);
153
+ }
154
+ function notEmpty(value) {
155
+ if (value === null || value === void 0) {
156
+ return false;
157
+ }
158
+ if (typeof value === "string") {
159
+ if (value.trim() === "") {
160
+ return false;
161
+ }
162
+ }
163
+ return true;
164
+ }
165
+ function isNoError(item) {
166
+ return item instanceof Error !== true;
167
+ }
168
+ async function returnResolved(promises) {
169
+ const toCheck = [];
170
+ for (let index = 0; index < promises.length; index++) {
171
+ const promise = promises[index];
172
+ if (!promise) {
173
+ throw new Error(`No promise found at index "${index}"`);
174
+ }
175
+ toCheck.push(
176
+ promise.then((result) => {
177
+ return result;
178
+ }).catch((error) => {
179
+ return new Error(error);
180
+ })
181
+ );
182
+ }
183
+ const checked = await Promise.all(toCheck);
184
+ return checked.filter(isNoError);
185
+ }
186
+ function spawnChildProcess(command, args, options) {
187
+ return new Promise((resolve, reject) => {
188
+ const childProcess = (0, import_child_process.spawn)(command, args, options);
189
+ let log = "";
190
+ childProcess.stdout.on("data", (data) => {
191
+ log += data;
192
+ });
193
+ childProcess.stderr.on("data", (data) => {
194
+ log += data;
195
+ });
196
+ childProcess.on("error", (error) => {
197
+ throw error;
198
+ });
199
+ childProcess.on("exit", (code) => {
200
+ if (code === 0) {
201
+ return resolve(log);
202
+ }
203
+ return reject(log);
204
+ });
205
+ });
206
+ }
207
+ async function folders(path) {
208
+ const dirent = await import_fs_extra.default.readdir(path, { withFileTypes: true });
209
+ return dirent.filter((dirent2) => {
210
+ return dirent2.isDirectory();
211
+ });
212
+ }
213
+ async function files(path, extension) {
214
+ const dirent = await import_fs_extra.default.readdir(path, { withFileTypes: true });
215
+ return dirent.filter((dirent2) => {
216
+ if (extension && dirent2.isFile() === true) {
217
+ if (dirent2.name.endsWith(extension)) {
218
+ return true;
219
+ }
220
+ return false;
221
+ }
222
+ return dirent2.isFile();
223
+ });
224
+ }
225
+ function getRelativePath(path) {
226
+ let relativePath = path.replace(workingDirectory, "");
227
+ if (relativePath.startsWith("/")) {
228
+ relativePath = relativePath.substr(1);
229
+ }
230
+ return relativePath;
231
+ }
232
+ function getDuplicates(arr, key) {
233
+ const grouped = (0, import_lodash_es.groupBy)(arr, (item) => {
234
+ return item[key];
235
+ });
236
+ return (0, import_lodash_es.uniq)(
237
+ (0, import_lodash_es.flatten)(
238
+ (0, import_lodash_es.filter)(grouped, (item) => {
239
+ return item.length > 1;
240
+ })
241
+ )
242
+ );
243
+ }
244
+
245
+ // src/service/AbstractCrudService.ts
246
+ var import_shared2 = require("@elek-io/shared");
247
+ var import_lodash_es2 = require("lodash-es");
248
+ var AbstractCrudService = class {
249
+ /**
250
+ * Do not instantiate directly as this is an abstract class
251
+ */
252
+ constructor(type, options) {
253
+ this.type = type;
254
+ this.options = options;
255
+ this.gitMessage = {
256
+ create: `${import_shared2.gitCommitIconSchema.enum.CREATE} Created ${this.type}`,
257
+ update: `${import_shared2.gitCommitIconSchema.enum.UPDATE} Updated ${this.type}`,
258
+ delete: `${import_shared2.gitCommitIconSchema.enum.DELETE} Deleted ${this.type}`
259
+ };
260
+ }
261
+ /**
262
+ * Returns the filtered, sorted and paginated version of given list
263
+ *
264
+ * @todo Sorting and filtering requires all models to be loaded
265
+ * from disk. This results in a huge memory spike before the
266
+ * filtering and pagination takes effect - removing most of it again.
267
+ * This approach is still better than returning everything and
268
+ * letting the frontend handle it, since the memory usage would then be constant.
269
+ * But this still could fill the memory limit of node.js (default 1,4 GB).
270
+ *
271
+ * @param list Array to filter, sort and paginate
272
+ * @param sort Array of sort objects containing information about what to sort and how
273
+ * @param filter Filter all object values of `list` by this string
274
+ * @param limit Limit the result to this amount. If 0 is given, no limit is applied
275
+ * @param offset Start at this index instead of 0
276
+ */
277
+ async paginate(list, sort = [], filter2 = "", limit = 15, offset = 0) {
278
+ let result = list;
279
+ const total = list.length;
280
+ const normalizedFilter = filter2.trim().toLowerCase();
281
+ if (normalizedFilter !== "") {
282
+ (0, import_lodash_es2.remove)(result, (model) => {
283
+ let key;
284
+ for (key in model) {
285
+ const value = model[key];
286
+ if (String(value).toLowerCase().includes(normalizedFilter)) {
287
+ return false;
288
+ }
289
+ }
290
+ return true;
291
+ });
292
+ }
293
+ if (sort.length !== 0) {
294
+ const keys = sort.map((value) => value.by);
295
+ const orders = sort.map((value) => value.order);
296
+ result = (0, import_lodash_es2.orderBy)(result, keys, orders);
297
+ }
298
+ if (limit !== 0) {
299
+ result = result.slice(offset, offset + limit);
300
+ }
301
+ return {
302
+ total,
303
+ limit,
304
+ offset,
305
+ list: result
306
+ };
307
+ }
308
+ /**
309
+ * Returns a list of all file references of given project and type
310
+ *
311
+ * @param type File type of the references wanted
312
+ * @param projectId Project to get all asset references from
313
+ * @param collectionId Only needed when requesting files of type "Entry"
314
+ */
315
+ async listReferences(type, projectId, collectionId) {
316
+ switch (type) {
317
+ case import_shared2.fileTypeSchema.Enum.asset:
318
+ if (!projectId) {
319
+ throw new RequiredParameterMissingError("projectId");
320
+ }
321
+ return this.getFileReferences(pathTo.lfs(projectId));
322
+ case import_shared2.fileTypeSchema.Enum.project:
323
+ return this.getFolderReferences(pathTo.projects);
324
+ case import_shared2.fileTypeSchema.Enum.collection:
325
+ if (!projectId) {
326
+ throw new RequiredParameterMissingError("projectId");
327
+ }
328
+ return this.getFolderReferences(pathTo.collections(projectId));
329
+ case import_shared2.fileTypeSchema.Enum.entry:
330
+ if (!projectId) {
331
+ throw new RequiredParameterMissingError("projectId");
332
+ }
333
+ if (!collectionId) {
334
+ throw new RequiredParameterMissingError("collectionId");
335
+ }
336
+ return this.getFileReferences(
337
+ pathTo.collection(projectId, collectionId)
338
+ );
339
+ case import_shared2.fileTypeSchema.Enum.value:
340
+ if (!projectId) {
341
+ throw new RequiredParameterMissingError("projectId");
342
+ }
343
+ return this.getFileReferences(pathTo.values(projectId));
344
+ default:
345
+ throw new Error(`Trying to list files of unsupported type "${type}"`);
346
+ }
347
+ }
348
+ async getFolderReferences(path) {
349
+ const possibleFolders = await folders(path);
350
+ const results = possibleFolders.map((possibleFolder) => {
351
+ const folderReference = {
352
+ id: possibleFolder.name
353
+ };
354
+ try {
355
+ return import_shared2.fileReferenceSchema.parse(folderReference);
356
+ } catch (error) {
357
+ return null;
358
+ }
359
+ });
360
+ return results.filter(notEmpty);
361
+ }
362
+ /**
363
+ * Searches for all files inside given folder,
364
+ * parses their names and returns them as FileReference
365
+ *
366
+ * Ignores files not matching the [id].[language].[extension]
367
+ * or [id].[extension] format for their names
368
+ */
369
+ async getFileReferences(path) {
370
+ const possibleFiles = await files(path);
371
+ const results = await Promise.all(
372
+ possibleFiles.map(async (possibleFile) => {
373
+ const fileNameArray = possibleFile.name.split(".");
374
+ const fileReference = {
375
+ id: fileNameArray[0],
376
+ language: fileNameArray.length === 3 ? fileNameArray[1] : void 0,
377
+ extension: fileNameArray.length === 2 ? fileNameArray[1] : fileNameArray[2]
378
+ };
379
+ try {
380
+ return import_shared2.fileReferenceSchema.parse(fileReference);
381
+ } catch (error) {
382
+ return null;
383
+ }
384
+ })
385
+ );
386
+ return results.filter(notEmpty);
387
+ }
388
+ };
389
+
390
+ // src/service/GitService.ts
391
+ var import_shared6 = require("@elek-io/shared");
392
+ var import_dugite = require("dugite");
393
+ var import_p_queue = __toESM(require("p-queue"), 1);
394
+
395
+ // src/error/GitError.ts
396
+ var GitError = class extends Error {
397
+ constructor(message) {
398
+ super(message);
399
+ this.name = "GitError";
400
+ }
401
+ };
402
+
403
+ // src/error/NoCurrentUserError.ts
404
+ var NoCurrentUserError = class extends Error {
405
+ constructor() {
406
+ super("Make sure to set a User via Core before using other methods");
407
+ this.name = "NoCurrentUserError";
408
+ }
409
+ };
410
+
411
+ // src/service/GitTagService.ts
412
+ var import_shared3 = require("@elek-io/shared");
413
+ var GitTagService = class extends AbstractCrudService {
414
+ constructor(options, git) {
415
+ super(import_shared3.serviceTypeSchema.Enum.GitTag, options);
416
+ this.git = git;
417
+ }
418
+ /**
419
+ * Creates a new tag
420
+ *
421
+ * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---annotate
422
+ */
423
+ async create(props) {
424
+ import_shared3.createGitTagSchema.parse(props);
425
+ const id = (0, import_shared3.uuid)();
426
+ let args = ["tag", "--annotate", id];
427
+ if (props.hash) {
428
+ args = [...args, props.hash];
429
+ }
430
+ args = [...args, "-m", props.message];
431
+ await this.git(props.path, args);
432
+ const tag = await this.read({ path: props.path, id });
433
+ return tag;
434
+ }
435
+ /**
436
+ * Returns a tag by ID
437
+ *
438
+ * Internally uses list() with id as pattern.
439
+ */
440
+ async read(props) {
441
+ import_shared3.readGitTagSchema.parse(props);
442
+ const tags = await this.list({ path: props.path, filter: props.id });
443
+ if (tags.total === 0) {
444
+ throw new GitError(
445
+ `Provided tag with UUID "${props.id}" did not match any known tags`
446
+ );
447
+ }
448
+ if (tags.total > 1) {
449
+ throw new GitError(
450
+ `Provided tag with UUID "${props.id}" matched multiple known tags`
451
+ );
452
+ }
453
+ return tags.list[0];
454
+ }
455
+ /**
456
+ * Updating a git tag is not supported.
457
+ * Please delete the old and create a new one
458
+ *
459
+ * @see https://git-scm.com/docs/git-tag#_on_re_tagging
460
+ */
461
+ async update() {
462
+ throw new Error(
463
+ "Updating a git tag is not supported. Please delete the old and create a new one"
464
+ );
465
+ }
466
+ /**
467
+ * Deletes a tag
468
+ *
469
+ * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---delete
470
+ *
471
+ * @param path Path to the repository
472
+ * @param id UUID of the tag to delete
473
+ */
474
+ async delete(props) {
475
+ import_shared3.deleteGitTagSchema.parse(props);
476
+ const args = ["tag", "--delete", props.id];
477
+ await this.git(props.path, args);
478
+ }
479
+ /**
480
+ * Gets all local tags or filter them by pattern
481
+ *
482
+ * They are sorted by authordate of the commit, not the timestamp the tag is created.
483
+ * This ensures tags are sorted correctly in the timeline of their commits.
484
+ *
485
+ * @see https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---list
486
+ */
487
+ async list(props) {
488
+ import_shared3.listGitTagsSchema.parse(props);
489
+ let args = ["tag", "--list"];
490
+ args = [
491
+ ...args,
492
+ "--sort=-*authordate",
493
+ "--format=%(refname:short)|%(subject)|%(*authorname)|%(*authoremail)|%(*authordate:unix)"
494
+ ];
495
+ const result = await this.git(props.path, args);
496
+ const noEmptyLinesArr = result.stdout.split("\n").filter((line) => {
497
+ return line !== "";
498
+ });
499
+ const lineObjArr = noEmptyLinesArr.map((line) => {
500
+ const lineArray = line.split("|");
501
+ return {
502
+ id: lineArray[0],
503
+ message: lineArray[1],
504
+ author: {
505
+ name: lineArray[2],
506
+ email: lineArray[3]
507
+ },
508
+ timestamp: typeof lineArray[4] === "string" ? parseInt(lineArray[4]) : void 0
509
+ };
510
+ });
511
+ const gitTags = lineObjArr.filter(this.isGitTag.bind(this));
512
+ return this.paginate(
513
+ gitTags,
514
+ props?.sort,
515
+ props?.filter,
516
+ props?.limit,
517
+ props?.offset
518
+ );
519
+ }
520
+ /**
521
+ * Returns the total number of tags inside given repository
522
+ *
523
+ * Internally uses list(), so do not use count()
524
+ * in conjuncion with it to avoid multiple git calls.
525
+ *
526
+ * @param path Path to the repository
527
+ */
528
+ async count(props) {
529
+ import_shared3.countGitTagsSchema.parse(props);
530
+ const gitTags = await this.list({ path: props.path });
531
+ return gitTags.total;
532
+ }
533
+ /**
534
+ * Type guard for GitTag
535
+ *
536
+ * @param obj The object to check
537
+ */
538
+ isGitTag(obj) {
539
+ return import_shared3.gitTagSchema.safeParse(obj).success;
540
+ }
541
+ };
542
+
543
+ // src/service/UserService.ts
544
+ var import_shared5 = require("@elek-io/shared");
545
+
546
+ // src/service/JsonFileService.ts
547
+ var import_shared4 = require("@elek-io/shared");
548
+ var import_fs_extra2 = __toESM(require("fs-extra"), 1);
549
+ var JsonFileService = class extends AbstractCrudService {
550
+ constructor(options) {
551
+ super(import_shared4.serviceTypeSchema.Enum.JsonFile, options);
552
+ this.cache = /* @__PURE__ */ new Map();
553
+ }
554
+ /**
555
+ * Creates a new file on disk. Fails if path already exists
556
+ *
557
+ * @param data Data to write into the file
558
+ * @param path Path to write the file to
559
+ * @param schema Schema of the file to validate against
560
+ * @returns Validated content of the file from disk
561
+ */
562
+ async create(data, path, schema) {
563
+ const parsedData = schema.parse(data);
564
+ const string = this.serialize(parsedData);
565
+ await import_fs_extra2.default.writeFile(path, string, {
566
+ flag: "wx",
567
+ encoding: "utf8"
568
+ });
569
+ this.cache.set(path, parsedData);
570
+ return parsedData;
571
+ }
572
+ /**
573
+ * Reads the content of a file on disk. Fails if path does not exist
574
+ *
575
+ * @param path Path to read the file from
576
+ * @param schema Schema of the file to validate against
577
+ * @returns Validated content of the file from disk
578
+ */
579
+ async read(path, schema) {
580
+ if (this.cache.has(path)) {
581
+ return this.cache.get(path);
582
+ }
583
+ const data = await import_fs_extra2.default.readFile(path, {
584
+ flag: "r",
585
+ encoding: "utf8"
586
+ });
587
+ const json = this.deserialize(data);
588
+ const parsedData = schema.parse(json);
589
+ this.cache.set(path, parsedData);
590
+ return parsedData;
591
+ }
592
+ /**
593
+ * Overwrites an existing file on disk
594
+ *
595
+ * @todo Check how to error out if the file does not exist already
596
+ *
597
+ * @param data Data to write into the file
598
+ * @param path Path to the file to overwrite
599
+ * @param schema Schema of the file to validate against
600
+ * @returns Validated content of the file from disk
601
+ */
602
+ async update(data, path, schema) {
603
+ const parsedData = schema.parse(data);
604
+ const string = this.serialize(parsedData);
605
+ await import_fs_extra2.default.writeFile(path, string, {
606
+ flag: "w",
607
+ encoding: "utf8"
608
+ });
609
+ this.cache.set(path, parsedData);
610
+ return parsedData;
611
+ }
612
+ serialize(data) {
613
+ return JSON.stringify(data, null, this.options.file.json.indentation);
614
+ }
615
+ deserialize(data) {
616
+ return JSON.parse(data);
617
+ }
618
+ };
619
+
620
+ // src/service/UserService.ts
621
+ var UserService = class {
622
+ constructor(jsonFileService) {
623
+ this.jsonFileService = jsonFileService;
624
+ }
625
+ /**
626
+ * Returns the User currently working with Core
627
+ */
628
+ async get() {
629
+ try {
630
+ return await this.jsonFileService.read(
631
+ pathTo.userFile,
632
+ import_shared5.userFileSchema
633
+ );
634
+ } catch (error) {
635
+ return void 0;
636
+ }
637
+ }
638
+ /**
639
+ * Sets the User currently working with Core
640
+ *
641
+ * By doing so all git operations are done with the signature of this User
642
+ */
643
+ async set(props) {
644
+ import_shared5.setUserSchema.parse(props);
645
+ const userFilePath = pathTo.userFile;
646
+ const userFile = {
647
+ ...props
648
+ };
649
+ if (userFile.userType === import_shared5.UserTypeSchema.Enum.cloud) {
650
+ }
651
+ await this.jsonFileService.update(userFile, userFilePath, import_shared5.userFileSchema);
652
+ return userFile;
653
+ }
654
+ };
655
+
656
+ // src/service/GitService.ts
657
+ var GitService2 = class {
658
+ constructor(options, userService) {
659
+ this.version = void 0;
660
+ this.queue = new import_p_queue.default({
661
+ concurrency: 1
662
+ // No concurrency because git operations are sequencial
663
+ });
664
+ this.gitTagService = new GitTagService(options, this.git);
665
+ this.userService = userService;
666
+ }
667
+ /**
668
+ * CRUD methods to work with git tags
669
+ */
670
+ get tags() {
671
+ return this.gitTagService;
672
+ }
673
+ /**
674
+ * Reads the currently used version of Git
675
+ */
676
+ async getVersion() {
677
+ const result = await this.git("", ["--version"]);
678
+ this.version = result.stdout.replace("git version", "").trim();
679
+ }
680
+ /**
681
+ * Create an empty Git repository or reinitialize an existing one
682
+ *
683
+ * @see https://git-scm.com/docs/git-init
684
+ *
685
+ * @param path Path to initialize in. Fails if path does not exist
686
+ * @param options Options specific to the init operation
687
+ */
688
+ async init(path, options) {
689
+ let args = ["init"];
690
+ if (options?.initialBranch) {
691
+ args = [...args, `--initial-branch=${options.initialBranch}`];
692
+ }
693
+ await this.git(path, args);
694
+ await this.setLocalConfig(path);
695
+ await this.installLfs(path);
696
+ }
697
+ /**
698
+ * Clone a repository into a directory
699
+ *
700
+ * @see https://git-scm.com/docs/git-clone
701
+ *
702
+ * @todo Implement progress callback / events
703
+ *
704
+ * @param url The remote repository URL to clone from
705
+ * @param path The destination path for the cloned repository.
706
+ * Which is only working if the directory is existing and empty.
707
+ * @param options Options specific to the clone operation
708
+ */
709
+ async clone(url, path, options) {
710
+ let args = ["clone", "--progress"];
711
+ if (options?.branch) {
712
+ args = [...args, "--branch", options.branch];
713
+ }
714
+ if (options?.depth) {
715
+ args = [...args, "--depth", options.depth.toString()];
716
+ }
717
+ if (options?.singleBranch === true) {
718
+ args = [...args, "--single-branch"];
719
+ }
720
+ await this.git(path, [...args, url, "."]);
721
+ await this.setLocalConfig(path);
722
+ }
723
+ /**
724
+ * Add file contents to the index
725
+ *
726
+ * @see https://git-scm.com/docs/git-add
727
+ *
728
+ * @param path Path to the repository
729
+ * @param files Files to add
730
+ */
731
+ async add(path, files2) {
732
+ const args = ["add", "--", ...files2];
733
+ await this.git(path, args);
734
+ }
735
+ /**
736
+ * Switch branches
737
+ *
738
+ * @see https://git-scm.com/docs/git-switch/
739
+ *
740
+ * @param path Path to the repository
741
+ * @param name Name of the branch to switch to
742
+ * @param options Options specific to the switch operation
743
+ */
744
+ async switch(path, name, options) {
745
+ await this.checkBranchOrTagName(path, name);
746
+ let args = ["switch"];
747
+ if (options?.isNew === true) {
748
+ args = [...args, "--create", name];
749
+ } else {
750
+ args = [...args, name];
751
+ }
752
+ await this.git(path, args);
753
+ }
754
+ /**
755
+ * Reset current HEAD to the specified state
756
+ *
757
+ * @todo maybe add more options
758
+ * @see https://git-scm.com/docs/git-reset
759
+ *
760
+ * @param path Path to the repository
761
+ * @param mode Modifies the working tree depending on given mode
762
+ * @param commit Resets the current branch head to this commit / tag
763
+ */
764
+ async reset(path, mode, commit) {
765
+ const args = ["reset", `--${mode}`, commit];
766
+ await this.git(path, args);
767
+ }
768
+ /**
769
+ * Restore working tree files
770
+ *
771
+ * @see https://git-scm.com/docs/git-restore/
772
+ *
773
+ * @todo It's probably a good idea to not use restore
774
+ * for a use case where someone just wants to have a look
775
+ * and maybe copy something from a deleted file.
776
+ * We should use `checkout` without `add .` and `commit` for that
777
+ *
778
+ * @param path Path to the repository
779
+ * @param source Git commit SHA or tag name to restore to
780
+ * @param files Files to restore
781
+ */
782
+ // public async restore(
783
+ // path: string,
784
+ // source: string,
785
+ // files: string[]
786
+ // ): Promise<void> {
787
+ // const args = ['restore', `--source=${source}`, ...files];
788
+ // await this.git(path, args);
789
+ // }
790
+ /**
791
+ * Fetch from and integrate with another repository or a local branch
792
+ *
793
+ * @see https://git-scm.com/docs/git-pull
794
+ *
795
+ * @param path Path to the repository
796
+ */
797
+ async pull(path) {
798
+ const args = ["pull"];
799
+ await this.git(path, args);
800
+ }
801
+ /**
802
+ * Record changes to the repository
803
+ *
804
+ * @see https://git-scm.com/docs/git-commit
805
+ *
806
+ * @param path Path to the repository
807
+ * @param message A message that describes the changes
808
+ */
809
+ async commit(path, message) {
810
+ const user = await this.userService.get();
811
+ if (!user) {
812
+ throw new NoCurrentUserError();
813
+ }
814
+ const args = [
815
+ "commit",
816
+ `--message=${message}`,
817
+ `--author=${user.name} <${user.email}>`
818
+ ];
819
+ await this.git(path, args);
820
+ }
821
+ /**
822
+ * Gets local commit history
823
+ *
824
+ * @see https://git-scm.com/docs/git-log
825
+ *
826
+ * @todo Check if there is a need to trim the git commit message of chars
827
+ * @todo Use this method in a service. Decide if we need a HistoryService for example
828
+ *
829
+ * @param path Path to the repository
830
+ * @param options Options specific to the log operation
831
+ */
832
+ async log(path, options) {
833
+ let args = ["log"];
834
+ if (options?.between?.from) {
835
+ args = [
836
+ ...args,
837
+ `${options.between.from}...${options.between.to || "HEAD"}`
838
+ ];
839
+ }
840
+ if (options?.limit) {
841
+ args = [...args, `--max-count=${options.limit}`];
842
+ }
843
+ const result = await this.git(path, [
844
+ ...args,
845
+ "--format=%H|%s|%an|%ae|%at|%D"
846
+ ]);
847
+ const noEmptyLinesArr = result.stdout.split("\n").filter((line) => {
848
+ return line !== "";
849
+ });
850
+ const lineObjArr = noEmptyLinesArr.map((line) => {
851
+ const lineArray = line.split("|");
852
+ return {
853
+ hash: lineArray[0],
854
+ message: lineArray[1],
855
+ author: {
856
+ name: lineArray[2],
857
+ email: lineArray[3]
858
+ },
859
+ timestamp: parseInt(lineArray[4]),
860
+ tag: this.refNameToTagName(lineArray[5])
861
+ };
862
+ });
863
+ return lineObjArr.filter(this.isGitCommit.bind(this));
864
+ }
865
+ refNameToTagName(refName) {
866
+ let tagName = "";
867
+ tagName = refName.replace("tag: ", "");
868
+ if (tagName.trim() === "" || import_shared6.uuidSchema.safeParse(tagName).success === false) {
869
+ tagName = void 0;
870
+ }
871
+ return tagName;
872
+ }
873
+ /**
874
+ * Returns a timestamp of given files creation
875
+ *
876
+ * Git only returns the timestamp the file was added,
877
+ * which could be different from the file being created.
878
+ * But since file operations will always be committed
879
+ * immediately, this is practically the same.
880
+ *
881
+ * @param path Path to the repository
882
+ * @param file File to get timestamp from
883
+ */
884
+ async getFileCreatedTimestamp(path, file) {
885
+ const result = await this.git(path, [
886
+ "log",
887
+ "--diff-filter=A",
888
+ "--follow",
889
+ "--format=%at",
890
+ "--max-count=1",
891
+ "--",
892
+ file
893
+ ]);
894
+ return parseInt(result.stdout);
895
+ }
896
+ /**
897
+ * Returns a timestamp of the files last modification
898
+ *
899
+ * @param path Path to the repository
900
+ * @param file File to get timestamp from
901
+ */
902
+ async getFileLastUpdatedTimestamp(path, file) {
903
+ const result = await this.git(path, [
904
+ "log",
905
+ "--follow",
906
+ "--format=%at",
907
+ "--max-count=1",
908
+ "--",
909
+ file
910
+ ]);
911
+ return parseInt(result.stdout);
912
+ }
913
+ /**
914
+ * Returns created and updated timestamps from given file
915
+ *
916
+ * @param path Path to the project
917
+ * @param file Path to the file
918
+ */
919
+ async getFileCreatedUpdatedMeta(path, file) {
920
+ const meta = await Promise.all([
921
+ this.getFileCreatedTimestamp(path, file),
922
+ this.getFileLastUpdatedTimestamp(path, file)
923
+ ]);
924
+ return {
925
+ created: meta[0],
926
+ updated: meta[1]
927
+ };
928
+ }
929
+ /**
930
+ * A reference is used in Git to specify branches and tags.
931
+ * This method checks if given name matches the required format
932
+ *
933
+ * @see https://git-scm.com/docs/git-check-ref-format
934
+ *
935
+ * @param path Path to the repository
936
+ * @param name Name to check
937
+ */
938
+ async checkBranchOrTagName(path, name) {
939
+ await this.git(path, ["check-ref-format", "--allow-onelevel", name]);
940
+ }
941
+ /**
942
+ * Installs LFS support and starts tracking
943
+ * all files inside the lfs folder
944
+ *
945
+ * @param path Path to the repository
946
+ */
947
+ async installLfs(path) {
948
+ await this.git(path, ["lfs", "install"]);
949
+ await this.git(path, ["lfs", "track", "lfs/*"]);
950
+ }
951
+ /**
952
+ * Sets the git config of given local repository from ElekIoCoreOptions
953
+ *
954
+ * @param path Path to the repository
955
+ */
956
+ async setLocalConfig(path) {
957
+ const user = await this.userService.get();
958
+ if (!user) {
959
+ throw new NoCurrentUserError();
960
+ }
961
+ const userNameArgs = ["config", "--local", "user.name", user.name];
962
+ const userEmailArgs = ["config", "--local", "user.email", user.email];
963
+ await this.git(path, userNameArgs);
964
+ await this.git(path, userEmailArgs);
965
+ }
966
+ /**
967
+ * Type guard for GitCommit
968
+ *
969
+ * @param obj The object to check
970
+ */
971
+ isGitCommit(obj) {
972
+ return import_shared6.gitCommitSchema.safeParse(obj).success;
973
+ }
974
+ /**
975
+ * Wraps the execution of any git command
976
+ * to use a FIFO queue for sequential processing
977
+ *
978
+ * @param path Path to the repository
979
+ * @param args Arguments to append after the `git` command
980
+ */
981
+ async git(path, args) {
982
+ const result = await this.queue.add(() => import_dugite.GitProcess.exec(args, path));
983
+ if (!result) {
984
+ throw new GitError(
985
+ `Git (${this.version}) command "git ${args.join(
986
+ " "
987
+ )}" failed to return a result`
988
+ );
989
+ }
990
+ return result;
991
+ }
992
+ };
993
+
994
+ // src/service/AssetService.ts
995
+ var AssetService = class extends AbstractCrudService {
996
+ constructor(options, jsonFileService, gitService) {
997
+ super(import_shared7.serviceTypeSchema.Enum.Asset, options);
998
+ this.jsonFileService = jsonFileService;
999
+ this.gitService = gitService;
1000
+ }
1001
+ /**
1002
+ * Creates a new Asset
1003
+ */
1004
+ async create(props) {
1005
+ import_shared7.createAssetSchema.parse(props);
1006
+ const id = (0, import_shared7.uuid)();
1007
+ const projectPath = pathTo.project(props.projectId);
1008
+ const fileType = await this.getSupportedFileTypeOrThrow(props.filePath);
1009
+ const size = await this.getAssetSize(props.filePath);
1010
+ const assetPath = pathTo.asset(
1011
+ props.projectId,
1012
+ id,
1013
+ props.language,
1014
+ fileType.extension
1015
+ );
1016
+ const assetFilePath = pathTo.assetFile(
1017
+ props.projectId,
1018
+ id,
1019
+ props.language
1020
+ );
1021
+ const assetFile = {
1022
+ ...props,
1023
+ fileType: "asset",
1024
+ id,
1025
+ created: (0, import_shared7.currentTimestamp)(),
1026
+ extension: fileType.extension,
1027
+ mimeType: fileType.mimeType,
1028
+ size
1029
+ };
1030
+ try {
1031
+ await import_fs_extra3.default.copyFile(props.filePath, assetPath);
1032
+ await this.jsonFileService.create(
1033
+ assetFile,
1034
+ assetFilePath,
1035
+ import_shared7.assetFileSchema
1036
+ );
1037
+ } catch (error) {
1038
+ await this.delete({ ...assetFile, projectId: props.projectId });
1039
+ throw error;
1040
+ }
1041
+ await this.gitService.add(projectPath, [assetFilePath, assetPath]);
1042
+ await this.gitService.commit(projectPath, this.gitMessage.create);
1043
+ return this.toAsset(props.projectId, assetFile);
1044
+ }
1045
+ /**
1046
+ * Returns an Asset by ID and language
1047
+ */
1048
+ async read(props) {
1049
+ import_shared7.readAssetSchema.parse(props);
1050
+ const assetFile = await this.jsonFileService.read(
1051
+ pathTo.assetFile(props.projectId, props.id, props.language),
1052
+ import_shared7.assetFileSchema
1053
+ );
1054
+ return this.toAsset(props.projectId, assetFile);
1055
+ }
1056
+ /**
1057
+ * Updates given Asset
1058
+ *
1059
+ * Use the optional "newFilePath" prop to update the Asset itself
1060
+ */
1061
+ async update(props) {
1062
+ import_shared7.updateAssetSchema.parse(props);
1063
+ const projectPath = pathTo.project(props.projectId);
1064
+ const assetFilePath = pathTo.assetFile(
1065
+ props.projectId,
1066
+ props.id,
1067
+ props.language
1068
+ );
1069
+ const prevAssetFile = await this.read(props);
1070
+ const assetFile = {
1071
+ ...prevAssetFile,
1072
+ ...props,
1073
+ updated: (0, import_shared7.currentTimestamp)()
1074
+ };
1075
+ if (props.newFilePath) {
1076
+ const fileType = await this.getSupportedFileTypeOrThrow(
1077
+ props.newFilePath
1078
+ );
1079
+ const size = await this.getAssetSize(props.newFilePath);
1080
+ const prevAssetPath = pathTo.asset(
1081
+ props.projectId,
1082
+ props.id,
1083
+ props.language,
1084
+ prevAssetFile.extension
1085
+ );
1086
+ const assetPath = pathTo.asset(
1087
+ props.projectId,
1088
+ props.id,
1089
+ props.language,
1090
+ fileType.extension
1091
+ );
1092
+ await import_fs_extra3.default.remove(prevAssetPath);
1093
+ await import_fs_extra3.default.copyFile(props.newFilePath, assetPath);
1094
+ assetFile.extension = fileType.extension;
1095
+ assetFile.mimeType = fileType.mimeType;
1096
+ assetFile.size = size;
1097
+ }
1098
+ await this.jsonFileService.update(
1099
+ assetFile,
1100
+ assetFilePath,
1101
+ import_shared7.assetFileSchema
1102
+ );
1103
+ await this.gitService.add(projectPath, [assetFilePath]);
1104
+ await this.gitService.commit(projectPath, this.gitMessage.update);
1105
+ return this.toAsset(props.projectId, assetFile);
1106
+ }
1107
+ /**
1108
+ * Deletes given Asset
1109
+ */
1110
+ async delete(props) {
1111
+ import_shared7.deleteAssetSchema.parse(props);
1112
+ const projectPath = pathTo.project(props.projectId);
1113
+ const assetFilePath = pathTo.assetFile(
1114
+ props.projectId,
1115
+ props.id,
1116
+ props.language
1117
+ );
1118
+ const assetPath = pathTo.asset(
1119
+ props.projectId,
1120
+ props.id,
1121
+ props.language,
1122
+ props.extension
1123
+ );
1124
+ await import_fs_extra3.default.remove(assetPath);
1125
+ await import_fs_extra3.default.remove(assetFilePath);
1126
+ await this.gitService.add(projectPath, [assetFilePath, assetPath]);
1127
+ await this.gitService.commit(projectPath, this.gitMessage.delete);
1128
+ }
1129
+ async list(props) {
1130
+ import_shared7.listAssetsSchema.parse(props);
1131
+ const assetReferences = await this.listReferences(
1132
+ import_shared7.fileTypeSchema.Enum.asset,
1133
+ props.projectId
1134
+ );
1135
+ const list = await returnResolved(
1136
+ assetReferences.map((assetReference) => {
1137
+ if (!assetReference.language) {
1138
+ throw new RequiredParameterMissingError("language");
1139
+ }
1140
+ return this.read({
1141
+ projectId: props.projectId,
1142
+ id: assetReference.id,
1143
+ language: assetReference.language
1144
+ });
1145
+ })
1146
+ );
1147
+ const paginatedResult = this.paginate(
1148
+ list,
1149
+ props.sort,
1150
+ props.filter,
1151
+ props.limit,
1152
+ props.offset
1153
+ );
1154
+ return paginatedResult;
1155
+ }
1156
+ async count(props) {
1157
+ import_shared7.countAssetsSchema.parse(props);
1158
+ const count = (await this.listReferences(import_shared7.fileTypeSchema.Enum.asset, props.projectId)).length;
1159
+ return count;
1160
+ }
1161
+ /**
1162
+ * Checks if given object is of type Asset
1163
+ */
1164
+ isAsset(obj) {
1165
+ return import_shared7.assetSchema.safeParse(obj).success;
1166
+ }
1167
+ /**
1168
+ * Returns the size of an Asset in bytes
1169
+ *
1170
+ * @param path Path of the Asset to get the size from
1171
+ */
1172
+ async getAssetSize(path) {
1173
+ return (await import_fs_extra3.default.stat(path)).size;
1174
+ }
1175
+ /**
1176
+ * Creates an Asset from given AssetFile
1177
+ *
1178
+ * @param projectId The project's ID
1179
+ * @param assetFile The AssetFile to convert
1180
+ */
1181
+ async toAsset(projectId, assetFile) {
1182
+ const assetPath = pathTo.asset(
1183
+ projectId,
1184
+ assetFile.id,
1185
+ assetFile.language,
1186
+ assetFile.extension
1187
+ );
1188
+ const asset = {
1189
+ ...assetFile,
1190
+ absolutePath: assetPath
1191
+ };
1192
+ return asset;
1193
+ }
1194
+ /**
1195
+ * Returns the found and supported extension as well as mime type,
1196
+ * otherwise throws an error
1197
+ *
1198
+ * @param filePath Path to the file to check
1199
+ */
1200
+ async getSupportedFileTypeOrThrow(filePath) {
1201
+ const fileSize = (await import_fs_extra3.default.stat(filePath)).size;
1202
+ if (fileSize / 1e3 <= 500) {
1203
+ const fileBuffer = await import_fs_extra3.default.readFile(filePath);
1204
+ if ((0, import_is_svg.default)(fileBuffer.toString()) === true) {
1205
+ return {
1206
+ extension: import_shared7.supportedExtensionSchema.Enum.svg,
1207
+ mimeType: import_shared7.supportedMimeTypeSchema.Enum["image/svg+xml"]
1208
+ };
1209
+ }
1210
+ }
1211
+ const { fileTypeFromFile } = await import("file-type");
1212
+ const fileType = await fileTypeFromFile(filePath);
1213
+ const result = import_shared7.supportedFileTypeSchema.parse({
1214
+ extension: fileType?.ext,
1215
+ mimeType: fileType?.mime
1216
+ });
1217
+ return result;
1218
+ }
1219
+ };
1220
+
1221
+ // src/service/CollectionService.ts
1222
+ var import_shared8 = require("@elek-io/shared");
1223
+ var import_fs_extra4 = __toESM(require("fs-extra"), 1);
1224
+ var CollectionService = class extends AbstractCrudService {
1225
+ constructor(options, jsonFileService, gitService) {
1226
+ super(import_shared8.serviceTypeSchema.Enum.Collection, options);
1227
+ this.jsonFileService = jsonFileService;
1228
+ this.gitService = gitService;
1229
+ }
1230
+ /**
1231
+ * Creates a new Collection
1232
+ */
1233
+ async create(props) {
1234
+ import_shared8.createCollectionSchema.parse(props);
1235
+ const id = (0, import_shared8.uuid)();
1236
+ const projectPath = pathTo.project(props.projectId);
1237
+ const collectionPath = pathTo.collection(props.projectId, id);
1238
+ const collectionFilePath = pathTo.collectionFile(
1239
+ props.projectId,
1240
+ id
1241
+ );
1242
+ const collectionFile = {
1243
+ ...props,
1244
+ fileType: "collection",
1245
+ id,
1246
+ slug: {
1247
+ singular: (0, import_shared8.slug)(props.slug.singular),
1248
+ plural: (0, import_shared8.slug)(props.slug.plural)
1249
+ },
1250
+ created: (0, import_shared8.currentTimestamp)()
1251
+ };
1252
+ await import_fs_extra4.default.ensureDir(collectionPath);
1253
+ await this.jsonFileService.create(
1254
+ collectionFile,
1255
+ collectionFilePath,
1256
+ import_shared8.collectionFileSchema
1257
+ );
1258
+ await this.gitService.add(projectPath, [collectionFilePath]);
1259
+ await this.gitService.commit(projectPath, this.gitMessage.create);
1260
+ return collectionFile;
1261
+ }
1262
+ /**
1263
+ * Returns a Collection by ID
1264
+ */
1265
+ async read(props) {
1266
+ import_shared8.readCollectionSchema.parse(props);
1267
+ const collection = await this.jsonFileService.read(
1268
+ pathTo.collectionFile(props.projectId, props.id),
1269
+ import_shared8.collectionFileSchema
1270
+ );
1271
+ return collection;
1272
+ }
1273
+ /**
1274
+ * Updates given Collection
1275
+ *
1276
+ * @todo finish implementing checks for FieldDefinitions and extract methods
1277
+ *
1278
+ * @param projectId Project ID of the collection to update
1279
+ * @param collection Collection to write to disk
1280
+ * @returns An object containing information about the actions needed to be taken,
1281
+ * before given update can be executed or void if the update was executed successfully
1282
+ */
1283
+ async update(props) {
1284
+ import_shared8.updateCollectionSchema.parse(props);
1285
+ const projectPath = pathTo.project(props.projectId);
1286
+ const collectionFilePath = pathTo.collectionFile(
1287
+ props.projectId,
1288
+ props.id
1289
+ );
1290
+ const prevCollectionFile = await this.read(props);
1291
+ const collectionFile = {
1292
+ ...prevCollectionFile,
1293
+ ...props,
1294
+ updated: (0, import_shared8.currentTimestamp)()
1295
+ };
1296
+ await this.jsonFileService.update(
1297
+ collectionFile,
1298
+ collectionFilePath,
1299
+ import_shared8.collectionFileSchema
1300
+ );
1301
+ await this.gitService.add(projectPath, [collectionFilePath]);
1302
+ await this.gitService.commit(projectPath, this.gitMessage.update);
1303
+ return collectionFile;
1304
+ }
1305
+ /**
1306
+ * Deletes given Collection (folder), including it's items
1307
+ *
1308
+ * The Fields that Collection used are not deleted.
1309
+ */
1310
+ async delete(props) {
1311
+ import_shared8.deleteCollectionSchema.parse(props);
1312
+ const projectPath = pathTo.project(props.projectId);
1313
+ const collectionPath = pathTo.collection(
1314
+ props.projectId,
1315
+ props.id
1316
+ );
1317
+ await import_fs_extra4.default.remove(collectionPath);
1318
+ await this.gitService.add(projectPath, [collectionPath]);
1319
+ await this.gitService.commit(projectPath, this.gitMessage.delete);
1320
+ }
1321
+ async list(props) {
1322
+ import_shared8.listCollectionsSchema.parse(props);
1323
+ const references = await this.listReferences(
1324
+ import_shared8.fileTypeSchema.Enum.collection,
1325
+ props.projectId
1326
+ );
1327
+ const list = await returnResolved(
1328
+ references.map((reference) => {
1329
+ return this.read({
1330
+ projectId: props.projectId,
1331
+ id: reference.id
1332
+ });
1333
+ })
1334
+ );
1335
+ return this.paginate(
1336
+ list,
1337
+ props.sort,
1338
+ props.filter,
1339
+ props.limit,
1340
+ props.offset
1341
+ );
1342
+ }
1343
+ async count(props) {
1344
+ import_shared8.countCollectionsSchema.parse(props);
1345
+ const count = (await this.listReferences(import_shared8.fileTypeSchema.Enum.collection, props.projectId)).length;
1346
+ return count;
1347
+ }
1348
+ /**
1349
+ * Checks if given object is of type Collection
1350
+ */
1351
+ isCollection(obj) {
1352
+ return import_shared8.collectionFileSchema.safeParse(obj).success;
1353
+ }
1354
+ };
1355
+
1356
+ // src/service/EntryService.ts
1357
+ var import_shared10 = require("@elek-io/shared");
1358
+ var import_fs_extra6 = __toESM(require("fs-extra"), 1);
1359
+
1360
+ // src/service/ValueService.ts
1361
+ var import_shared9 = require("@elek-io/shared");
1362
+ var import_fs_extra5 = __toESM(require("fs-extra"), 1);
1363
+ var ValueService = class extends AbstractCrudService {
1364
+ constructor(options, jsonFileService, gitService, assetService) {
1365
+ super(import_shared9.serviceTypeSchema.Enum.Value, options);
1366
+ this.jsonFileService = jsonFileService;
1367
+ this.gitService = gitService;
1368
+ }
1369
+ /**
1370
+ * Creates a new Value
1371
+ */
1372
+ async create(props) {
1373
+ import_shared9.createValueSchema.parse(props);
1374
+ const id = (0, import_shared9.uuid)();
1375
+ const projectPath = pathTo.project(props.projectId);
1376
+ const valueFilePath = pathTo.valueFile(
1377
+ props.projectId,
1378
+ id,
1379
+ props.language
1380
+ );
1381
+ const valueFile = {
1382
+ ...props,
1383
+ fileType: "value",
1384
+ id,
1385
+ created: (0, import_shared9.currentTimestamp)()
1386
+ };
1387
+ await this.jsonFileService.create(
1388
+ valueFile,
1389
+ valueFilePath,
1390
+ import_shared9.valueFileSchema
1391
+ );
1392
+ await this.gitService.add(projectPath, [valueFilePath]);
1393
+ await this.gitService.commit(projectPath, this.gitMessage.create);
1394
+ return valueFile;
1395
+ }
1396
+ /**
1397
+ * Returns a Value by ID and language
1398
+ */
1399
+ async read(props) {
1400
+ import_shared9.readValueSchema.parse(props);
1401
+ const valueFile = await this.jsonFileService.read(
1402
+ pathTo.valueFile(props.projectId, props.id, props.language),
1403
+ import_shared9.valueFileSchema
1404
+ );
1405
+ return valueFile;
1406
+ }
1407
+ /**
1408
+ * Updates given Value
1409
+ */
1410
+ async update(props) {
1411
+ import_shared9.updateValueSchema.parse(props);
1412
+ const projectPath = pathTo.project(props.projectId);
1413
+ const valueFilePath = pathTo.valueFile(
1414
+ props.projectId,
1415
+ props.id,
1416
+ props.language
1417
+ );
1418
+ const prevValueFile = await this.read(props);
1419
+ const valueFile = {
1420
+ ...prevValueFile,
1421
+ ...props,
1422
+ updated: (0, import_shared9.currentTimestamp)()
1423
+ };
1424
+ await this.jsonFileService.update(
1425
+ valueFile,
1426
+ valueFilePath,
1427
+ import_shared9.valueFileSchema
1428
+ );
1429
+ await this.gitService.add(projectPath, [valueFilePath]);
1430
+ await this.gitService.commit(projectPath, this.gitMessage.update);
1431
+ return valueFile;
1432
+ }
1433
+ /**
1434
+ * Deletes given Value
1435
+ */
1436
+ async delete(props) {
1437
+ import_shared9.deleteValueSchema.parse(props);
1438
+ const projectPath = pathTo.project(props.projectId);
1439
+ const valueFilePath = pathTo.valueFile(
1440
+ props.projectId,
1441
+ props.id,
1442
+ props.language
1443
+ );
1444
+ await import_fs_extra5.default.remove(valueFilePath);
1445
+ await this.gitService.add(projectPath, [valueFilePath]);
1446
+ await this.gitService.commit(projectPath, this.gitMessage.delete);
1447
+ }
1448
+ async list(props) {
1449
+ import_shared9.listValuesSchema.parse(props);
1450
+ const references = await this.listReferences(
1451
+ import_shared9.fileTypeSchema.Enum.value,
1452
+ props.projectId
1453
+ );
1454
+ const list = await returnResolved(
1455
+ references.map((reference) => {
1456
+ if (!reference.language) {
1457
+ throw new RequiredParameterMissingError("language");
1458
+ }
1459
+ return this.read({
1460
+ projectId: props.projectId,
1461
+ id: reference.id,
1462
+ language: reference.language
1463
+ });
1464
+ })
1465
+ );
1466
+ return this.paginate(
1467
+ list,
1468
+ props.sort,
1469
+ props.filter,
1470
+ props.limit,
1471
+ props.offset
1472
+ );
1473
+ }
1474
+ async count(props) {
1475
+ import_shared9.countValuesSchema.parse(props);
1476
+ const count = (await this.listReferences(import_shared9.fileTypeSchema.Enum.value, props.projectId)).length;
1477
+ return count;
1478
+ }
1479
+ /**
1480
+ * Checks if given object is of type Value
1481
+ */
1482
+ isValue(obj) {
1483
+ return import_shared9.valueFileSchema.safeParse(obj).success;
1484
+ }
1485
+ /**
1486
+ * Reads the given Value from disk and validates it against the ValueDefinition
1487
+ */
1488
+ async validate(props) {
1489
+ import_shared9.validateValueSchema.parse(props);
1490
+ const value = await this.read(props);
1491
+ const valueSchema = (0, import_shared9.getValueSchemaFromDefinition)(props.definition);
1492
+ return valueSchema.safeParse(value.content);
1493
+ }
1494
+ };
1495
+
1496
+ // src/service/EntryService.ts
1497
+ var EntryService = class extends AbstractCrudService {
1498
+ constructor(options, jsonFileService, gitService, collectionService, valueService) {
1499
+ super(import_shared10.serviceTypeSchema.Enum.Entry, options);
1500
+ this.jsonFileService = jsonFileService;
1501
+ this.gitService = gitService;
1502
+ this.collectionService = collectionService;
1503
+ this.valueService = valueService;
1504
+ }
1505
+ /**
1506
+ * Creates a new Entry
1507
+ */
1508
+ async create(props) {
1509
+ import_shared10.createEntrySchema.parse(props);
1510
+ const id = (0, import_shared10.uuid)();
1511
+ const projectPath = pathTo.project(props.projectId);
1512
+ const entryFilePath = pathTo.entryFile(
1513
+ props.projectId,
1514
+ props.collectionId,
1515
+ id,
1516
+ props.language
1517
+ );
1518
+ const collection = await this.collectionService.read({
1519
+ projectId: props.projectId,
1520
+ id: props.collectionId
1521
+ });
1522
+ await this.validateValueReferences(
1523
+ props.projectId,
1524
+ props.collectionId,
1525
+ props.valueReferences,
1526
+ collection.valueDefinitions
1527
+ );
1528
+ const entryFile = {
1529
+ fileType: "entry",
1530
+ id,
1531
+ language: props.language,
1532
+ valueReferences: props.valueReferences,
1533
+ created: (0, import_shared10.currentTimestamp)()
1534
+ };
1535
+ await this.jsonFileService.create(
1536
+ entryFile,
1537
+ entryFilePath,
1538
+ import_shared10.entryFileSchema
1539
+ );
1540
+ await this.gitService.add(projectPath, [entryFilePath]);
1541
+ await this.gitService.commit(projectPath, this.gitMessage.create);
1542
+ return entryFile;
1543
+ }
1544
+ /**
1545
+ * Returns an Entry by ID and language
1546
+ */
1547
+ async read(props) {
1548
+ import_shared10.readEntrySchema.parse(props);
1549
+ const entryFile = await this.jsonFileService.read(
1550
+ pathTo.entryFile(
1551
+ props.projectId,
1552
+ props.collectionId,
1553
+ props.id,
1554
+ props.language
1555
+ ),
1556
+ import_shared10.entryFileSchema
1557
+ );
1558
+ return entryFile;
1559
+ }
1560
+ /**
1561
+ * Updates Entry with given ValueReferences
1562
+ */
1563
+ async update(props) {
1564
+ import_shared10.updateEntrySchema.parse(props);
1565
+ const projectPath = pathTo.project(props.projectId);
1566
+ const entryFilePath = pathTo.entryFile(
1567
+ props.projectId,
1568
+ props.collectionId,
1569
+ props.id,
1570
+ props.language
1571
+ );
1572
+ const collection = await this.collectionService.read({
1573
+ projectId: props.projectId,
1574
+ id: props.collectionId
1575
+ });
1576
+ await this.validateValueReferences(
1577
+ props.projectId,
1578
+ props.collectionId,
1579
+ props.valueReferences,
1580
+ collection.valueDefinitions
1581
+ );
1582
+ const prevEntryFile = await this.read({
1583
+ projectId: props.projectId,
1584
+ collectionId: props.collectionId,
1585
+ id: props.id,
1586
+ language: props.language
1587
+ });
1588
+ const entryFile = {
1589
+ ...prevEntryFile,
1590
+ valueReferences: props.valueReferences,
1591
+ updated: (0, import_shared10.currentTimestamp)()
1592
+ };
1593
+ await this.jsonFileService.update(
1594
+ entryFile,
1595
+ entryFilePath,
1596
+ import_shared10.entryFileSchema
1597
+ );
1598
+ await this.gitService.add(projectPath, [entryFilePath]);
1599
+ await this.gitService.commit(projectPath, this.gitMessage.update);
1600
+ return entryFile;
1601
+ }
1602
+ /**
1603
+ * Deletes given Entry
1604
+ */
1605
+ async delete(props) {
1606
+ import_shared10.deleteEntrySchema.parse(props);
1607
+ const projectPath = pathTo.project(props.projectId);
1608
+ const entryFilePath = pathTo.entryFile(
1609
+ props.projectId,
1610
+ props.collectionId,
1611
+ props.id,
1612
+ props.language
1613
+ );
1614
+ await import_fs_extra6.default.remove(entryFilePath);
1615
+ await this.gitService.add(projectPath, [entryFilePath]);
1616
+ await this.gitService.commit(projectPath, this.gitMessage.delete);
1617
+ }
1618
+ async list(props) {
1619
+ import_shared10.listEntriesSchema.parse(props);
1620
+ const references = await this.listReferences(
1621
+ import_shared10.fileTypeSchema.Enum.entry,
1622
+ props.projectId,
1623
+ props.collectionId
1624
+ );
1625
+ const list = await returnResolved(
1626
+ references.map((reference) => {
1627
+ if (!reference.language) {
1628
+ throw new RequiredParameterMissingError("language");
1629
+ }
1630
+ return this.read({
1631
+ projectId: props.projectId,
1632
+ collectionId: props.collectionId,
1633
+ id: reference.id,
1634
+ language: reference.language
1635
+ });
1636
+ })
1637
+ );
1638
+ return this.paginate(
1639
+ list,
1640
+ props.sort,
1641
+ props.filter,
1642
+ props.limit,
1643
+ props.offset
1644
+ );
1645
+ }
1646
+ async count(props) {
1647
+ import_shared10.countEntriesSchema.parse(props);
1648
+ return (await this.listReferences(
1649
+ import_shared10.fileTypeSchema.Enum.entry,
1650
+ props.projectId,
1651
+ props.collectionId
1652
+ )).length;
1653
+ }
1654
+ /**
1655
+ * Checks if given object of Collection, CollectionItem,
1656
+ * Field, Project or Asset is of type CollectionItem
1657
+ */
1658
+ isEntry(obj) {
1659
+ return import_shared10.entrySchema.safeParse(obj).success;
1660
+ }
1661
+ /**
1662
+ * Validates referenced Values against the Collections definition
1663
+ *
1664
+ * @todo should probably return all errors occurring during parsing instead of throwing
1665
+ */
1666
+ async validateValueReferences(projectId, collectionId, valueReferences, valueDefinitions) {
1667
+ await Promise.all(
1668
+ valueReferences.map(async (reference) => {
1669
+ const definition = valueDefinitions.find((def) => {
1670
+ if (def.id === reference.definitionId) {
1671
+ return true;
1672
+ }
1673
+ return false;
1674
+ });
1675
+ if (!definition) {
1676
+ throw new Error(
1677
+ `No definition with ID "${reference.definitionId}" found in Collection "${collectionId}" for given Value reference`
1678
+ );
1679
+ }
1680
+ const schema = (0, import_shared10.getValueSchemaFromDefinition)(definition);
1681
+ const value = await this.valueService.read({
1682
+ ...reference.references,
1683
+ projectId
1684
+ });
1685
+ schema.parse(value.content);
1686
+ })
1687
+ );
1688
+ }
1689
+ };
1690
+
1691
+ // src/service/ProjectService.ts
1692
+ var import_shared12 = require("@elek-io/shared");
1693
+ var import_fs_extra7 = __toESM(require("fs-extra"), 1);
1694
+ var import_os2 = __toESM(require("os"), 1);
1695
+ var import_path2 = __toESM(require("path"), 1);
1696
+ var import_semver = __toESM(require("semver"), 1);
1697
+
1698
+ // src/error/ProjectUpgradeError.ts
1699
+ var ProjectUpgradeError = class extends Error {
1700
+ constructor(message) {
1701
+ super(message);
1702
+ this.name = "ProjectUpgradeError";
1703
+ }
1704
+ };
1705
+
1706
+ // src/service/SearchService.ts
1707
+ var import_shared11 = require("@elek-io/shared");
1708
+ var SearchService = class extends AbstractCrudService {
1709
+ constructor(options, assetService, collectionService) {
1710
+ super(import_shared11.serviceTypeSchema.enum.Search, options);
1711
+ this.assetService = assetService;
1712
+ this.collectionService = collectionService;
1713
+ }
1714
+ /**
1715
+ * Search all models inside the project for given query
1716
+ *
1717
+ * @todo Implement SearchOptions parameter
1718
+ *
1719
+ * @param project Project to search in
1720
+ * @param query Query to search for
1721
+ */
1722
+ async search(projectId, query, fileType) {
1723
+ const results = [];
1724
+ const normalizedQuery = query.trim();
1725
+ if (normalizedQuery === "") {
1726
+ return results;
1727
+ }
1728
+ const paginatedLists = (await Promise.all([this.assetService.list({ projectId, filter: query })])).flat();
1729
+ paginatedLists.forEach((paginatedList) => {
1730
+ paginatedList.list.flat().forEach((file) => {
1731
+ const result = {
1732
+ id: file.id,
1733
+ language: file.language,
1734
+ name: file.name,
1735
+ type: file.fileType,
1736
+ matches: []
1737
+ };
1738
+ for (const [key, value] of Object.entries(file)) {
1739
+ const valueString = String(value);
1740
+ if (valueString.toLowerCase().includes(normalizedQuery.toLowerCase())) {
1741
+ const matchStart = valueString.toLowerCase().indexOf(normalizedQuery.toLowerCase());
1742
+ const matchEnd = matchStart + normalizedQuery.length;
1743
+ result.matches.push({
1744
+ key,
1745
+ prefix: this.truncate(
1746
+ valueString.substring(0, matchStart),
1747
+ "start"
1748
+ ),
1749
+ match: valueString.substring(matchStart, matchEnd),
1750
+ suffix: this.truncate(
1751
+ valueString.substring(matchEnd, valueString.length),
1752
+ "end"
1753
+ )
1754
+ });
1755
+ }
1756
+ }
1757
+ if (result.matches.length > 0) {
1758
+ results.push(result);
1759
+ }
1760
+ });
1761
+ });
1762
+ return results;
1763
+ }
1764
+ truncate(value, at, limit = 15) {
1765
+ if (at === "start") {
1766
+ return `${value.substring(value.length - limit, value.length)}`;
1767
+ } else {
1768
+ return `${value.substring(0, limit)}`;
1769
+ }
1770
+ }
1771
+ };
1772
+
1773
+ // src/service/ProjectService.ts
1774
+ var ProjectService = class extends AbstractCrudService {
1775
+ constructor(options, jsonFileService, userService, gitService, searchService, assetService, collectionService, entryService, valueService) {
1776
+ super(import_shared12.serviceTypeSchema.Enum.Project, options);
1777
+ this.jsonFileService = jsonFileService;
1778
+ this.userService = userService;
1779
+ this.gitService = gitService;
1780
+ this.searchService = searchService;
1781
+ this.assetService = assetService;
1782
+ this.collectionService = collectionService;
1783
+ this.entryService = entryService;
1784
+ this.valueService = valueService;
1785
+ }
1786
+ /**
1787
+ * Creates a new Project
1788
+ */
1789
+ async create(props) {
1790
+ import_shared12.createProjectSchema.parse(props);
1791
+ const user = await this.userService.get();
1792
+ if (!user) {
1793
+ throw new NoCurrentUserError();
1794
+ }
1795
+ const id = (0, import_shared12.uuid)();
1796
+ const defaultSettings = {
1797
+ locale: {
1798
+ default: user.locale,
1799
+ supported: [user.locale]
1800
+ }
1801
+ };
1802
+ const projectFile = {
1803
+ ...props,
1804
+ fileType: "project",
1805
+ id,
1806
+ description: props.description || "",
1807
+ settings: Object.assign({}, defaultSettings, props.settings),
1808
+ created: (0, import_shared12.currentTimestamp)(),
1809
+ coreVersion: this.options.version,
1810
+ // @todo should be read from package.json to avoid duplicates
1811
+ status: "todo",
1812
+ version: "0.0.1"
1813
+ };
1814
+ const projectPath = pathTo.project(id);
1815
+ await import_fs_extra7.default.ensureDir(projectPath);
1816
+ try {
1817
+ await this.createFolderStructure(projectPath);
1818
+ await this.createGitignore(projectPath);
1819
+ await this.gitService.init(projectPath, { initialBranch: "main" });
1820
+ await this.jsonFileService.create(
1821
+ projectFile,
1822
+ pathTo.projectFile(id),
1823
+ import_shared12.projectFileSchema
1824
+ );
1825
+ await this.gitService.add(projectPath, ["."]);
1826
+ await this.gitService.commit(
1827
+ projectPath,
1828
+ `${import_shared12.gitCommitIconSchema.enum.INIT} Created this new elek.io project`
1829
+ );
1830
+ await this.gitService.switch(projectPath, "stage", { isNew: true });
1831
+ } catch (error) {
1832
+ await this.delete({
1833
+ id
1834
+ });
1835
+ throw error;
1836
+ }
1837
+ return projectFile;
1838
+ }
1839
+ /**
1840
+ * Returns a Project by ID
1841
+ */
1842
+ async read(props) {
1843
+ import_shared12.readProjectSchema.parse(props);
1844
+ const projectFile = await this.jsonFileService.read(
1845
+ pathTo.projectFile(props.id),
1846
+ import_shared12.projectFileSchema
1847
+ );
1848
+ return projectFile;
1849
+ }
1850
+ /**
1851
+ * Updates given Project
1852
+ */
1853
+ async update(props) {
1854
+ import_shared12.updateProjectSchema.parse(props);
1855
+ const projectPath = pathTo.project(props.id);
1856
+ const filePath = pathTo.projectFile(props.id);
1857
+ const prevProjectFile = await this.read(props);
1858
+ const projectFile = {
1859
+ ...prevProjectFile,
1860
+ ...props,
1861
+ updated: (0, import_shared12.currentTimestamp)()
1862
+ };
1863
+ await this.jsonFileService.update(projectFile, filePath, import_shared12.projectFileSchema);
1864
+ await this.gitService.add(projectPath, [filePath]);
1865
+ await this.gitService.commit(projectPath, this.gitMessage.update);
1866
+ return projectFile;
1867
+ }
1868
+ /**
1869
+ * Upgrades given Project to the latest version of this client
1870
+ *
1871
+ * Needed when a new core version is requiring changes to existing files or structure.
1872
+ *
1873
+ * @todo Find out why using this.snapshotService is throwing isObjWithKeyAndValueOfString of undefined error in gitService (maybe binding issue)
1874
+ */
1875
+ async upgrade(props) {
1876
+ import_shared12.upgradeProjectSchema.parse(props);
1877
+ const project = await this.read(props);
1878
+ const projectPath = pathTo.project(project.id);
1879
+ if (import_semver.default.gt(project.coreVersion, this.options.version)) {
1880
+ throw new Error(
1881
+ `Failed upgrading project. The projects core version "${project.coreVersion}" is higher than the current core version "${this.options.version}" of this client. A client upgrade is needed beforehand.`
1882
+ );
1883
+ }
1884
+ if (import_semver.default.eq(project.coreVersion, this.options.version)) {
1885
+ return;
1886
+ }
1887
+ const upgradeFiles = await files(
1888
+ import_path2.default.resolve(__dirname, "../upgrade"),
1889
+ "ts"
1890
+ );
1891
+ const upgrades = (await Promise.all(
1892
+ upgradeFiles.map((file) => {
1893
+ return import(import_path2.default.join("../upgrade", file.name));
1894
+ })
1895
+ )).map((upgradeImport) => {
1896
+ return upgradeImport.default;
1897
+ });
1898
+ const sortedUpgrades = upgrades.sort((a, b) => {
1899
+ return import_semver.default.compare(a.to, b.to);
1900
+ }).filter((upgrade) => {
1901
+ if (upgrade.to !== "0.0.0") {
1902
+ return upgrade;
1903
+ }
1904
+ });
1905
+ for (let index = 0; index < sortedUpgrades.length; index++) {
1906
+ const upgrade = sortedUpgrades[index];
1907
+ if (!upgrade) {
1908
+ throw new Error("Expected ProjectUpgrade but got undefined");
1909
+ }
1910
+ const failsafeTag = await this.gitService.tags.create({
1911
+ path: projectPath,
1912
+ message: `Attempting to upgrade Project to Core version "${upgrade.to}"`
1913
+ });
1914
+ try {
1915
+ await upgrade.run(project);
1916
+ project.coreVersion = upgrade.to;
1917
+ await this.update(project);
1918
+ await this.gitService.tags.create({
1919
+ path: projectPath,
1920
+ message: `Upgraded Project to Core version "${upgrade.to}"`
1921
+ });
1922
+ await this.gitService.tags.delete({
1923
+ path: projectPath,
1924
+ id: failsafeTag.id
1925
+ });
1926
+ } catch (error) {
1927
+ await this.gitService.reset(projectPath, "hard", failsafeTag.id);
1928
+ throw new ProjectUpgradeError(
1929
+ `Failed to upgrade Project to Core version "${upgrade.to}"`
1930
+ );
1931
+ }
1932
+ }
1933
+ }
1934
+ /**
1935
+ * Deletes given Project
1936
+ *
1937
+ * Deletes the whole Project folder including the history, not only the config file.
1938
+ * Use with caution, since a Project that is only available locally could be lost forever.
1939
+ * Or changes that are not pushed to a remote yet, will be lost too.
1940
+ */
1941
+ async delete(props) {
1942
+ import_shared12.deleteProjectSchema.parse(props);
1943
+ await import_fs_extra7.default.remove(pathTo.project(props.id));
1944
+ }
1945
+ async list(props) {
1946
+ if (props) {
1947
+ import_shared12.listProjectsSchema.parse(props);
1948
+ }
1949
+ const references = await this.listReferences(import_shared12.fileTypeSchema.Enum.project);
1950
+ const list = await returnResolved(
1951
+ references.map((reference) => {
1952
+ return this.read({ id: reference.id });
1953
+ })
1954
+ );
1955
+ return this.paginate(
1956
+ list,
1957
+ props?.sort,
1958
+ props?.filter,
1959
+ props?.limit,
1960
+ props?.offset
1961
+ );
1962
+ }
1963
+ async count() {
1964
+ return (await this.listReferences(import_shared12.fileTypeSchema.Enum.project)).length;
1965
+ }
1966
+ /**
1967
+ * Search all models inside the project for given query
1968
+ *
1969
+ * @param projectId Project ID to search in
1970
+ * @param query Query to search for
1971
+ * @param type (Optional) specify the type to search for
1972
+ */
1973
+ async search(projectId, query, type) {
1974
+ return this.searchService.search(projectId, query, type);
1975
+ }
1976
+ /**
1977
+ * Checks if given object is of type Project
1978
+ */
1979
+ isProject(obj) {
1980
+ return import_shared12.projectFileSchema.safeParse(obj).success;
1981
+ }
1982
+ /**
1983
+ * Exports given Project to JSON
1984
+ *
1985
+ * @todo performance tests
1986
+ * @todo add progress callback
1987
+ */
1988
+ async exportToJson(projectId) {
1989
+ const project = await this.read({ id: projectId });
1990
+ const assets = (await this.assetService.list({ projectId, limit: 0 })).list;
1991
+ const collections = (await this.collectionService.list({ projectId, limit: 0 })).list;
1992
+ const collectionExport = await Promise.all(
1993
+ collections.map(async (collection) => {
1994
+ const entries = (await this.entryService.list({
1995
+ projectId,
1996
+ collectionId: collection.id,
1997
+ limit: 0
1998
+ })).list;
1999
+ const entryExport = await Promise.all(
2000
+ entries.map(async (entry) => {
2001
+ const valueExport = await Promise.all(
2002
+ entry.valueReferences.map(async (valueReference) => {
2003
+ return this.valueService.read({
2004
+ projectId,
2005
+ id: valueReference.references.id,
2006
+ language: valueReference.references.language
2007
+ });
2008
+ })
2009
+ );
2010
+ return {
2011
+ ...entry,
2012
+ values: valueExport
2013
+ };
2014
+ })
2015
+ );
2016
+ return {
2017
+ ...collection,
2018
+ entries: entryExport
2019
+ };
2020
+ })
2021
+ );
2022
+ return {
2023
+ ...project,
2024
+ assets,
2025
+ collections: collectionExport
2026
+ };
2027
+ }
2028
+ /**
2029
+ * Creates the projects folder structure and makes sure to
2030
+ * write empty .gitkeep files inside them to ensure they are
2031
+ * committed
2032
+ */
2033
+ async createFolderStructure(path) {
2034
+ const folders2 = Object.values(import_shared12.projectFolderSchema.Values);
2035
+ await Promise.all(
2036
+ folders2.map(async (folder) => {
2037
+ await import_fs_extra7.default.mkdirp(import_path2.default.join(path, folder));
2038
+ await import_fs_extra7.default.writeFile(import_path2.default.join(path, folder, ".gitkeep"), "");
2039
+ })
2040
+ );
2041
+ }
2042
+ /**
2043
+ * Writes the Projects main .gitignore file to disk
2044
+ *
2045
+ * @todo Add general things to ignore
2046
+ * @see https://github.com/github/gitignore/tree/master/Global
2047
+ */
2048
+ async createGitignore(path) {
2049
+ const lines = [
2050
+ "# Ignore all hidden files and folders...",
2051
+ ".*",
2052
+ "# ...but these",
2053
+ "!/.gitignore",
2054
+ "!/.gitattributes",
2055
+ "!/**/.gitkeep",
2056
+ "",
2057
+ "# elek.io related ignores"
2058
+ // projectFolderSchema.Enum.theme + '/',
2059
+ // projectFolderSchema.Enum.public + '/',
2060
+ // projectFolderSchema.Enum.logs + '/',
2061
+ ];
2062
+ await import_fs_extra7.default.writeFile(import_path2.default.join(path, ".gitignore"), lines.join(import_os2.default.EOL));
2063
+ }
2064
+ };
2065
+
2066
+ // src/index.ts
2067
+ var ElekIoCore = class {
2068
+ constructor(props) {
2069
+ if (props) {
2070
+ import_shared13.constructorElekIoCoreSchema.parse(props);
2071
+ }
2072
+ const environment = import_shared13.environmentSchema.parse(process.env.NODE_ENV);
2073
+ const defaults = {
2074
+ environment,
2075
+ version: "0.0.0",
2076
+ file: {
2077
+ json: {
2078
+ indentation: 2
2079
+ }
2080
+ }
2081
+ };
2082
+ this.options = Object.assign({}, defaults, props);
2083
+ this.jsonFileService = new JsonFileService(this.options);
2084
+ this.userService = new UserService(this.jsonFileService);
2085
+ this.gitService = new GitService2(this.options, this.userService);
2086
+ this.assetService = new AssetService(
2087
+ this.options,
2088
+ this.jsonFileService,
2089
+ this.gitService
2090
+ );
2091
+ this.valueService = new ValueService(
2092
+ this.options,
2093
+ this.jsonFileService,
2094
+ this.gitService,
2095
+ this.assetService
2096
+ );
2097
+ this.collectionService = new CollectionService(
2098
+ this.options,
2099
+ this.jsonFileService,
2100
+ this.gitService
2101
+ );
2102
+ this.entryService = new EntryService(
2103
+ this.options,
2104
+ this.jsonFileService,
2105
+ this.gitService,
2106
+ this.collectionService,
2107
+ this.valueService
2108
+ );
2109
+ this.searchService = new SearchService(
2110
+ this.options,
2111
+ this.assetService,
2112
+ this.collectionService
2113
+ );
2114
+ this.projectService = new ProjectService(
2115
+ this.options,
2116
+ this.jsonFileService,
2117
+ this.userService,
2118
+ this.gitService,
2119
+ this.searchService,
2120
+ this.assetService,
2121
+ this.collectionService,
2122
+ this.entryService,
2123
+ this.valueService
2124
+ );
2125
+ if (environment !== "production") {
2126
+ console.info(`Initializing inside an "${environment}" environment`, {
2127
+ options: this.options
2128
+ });
2129
+ }
2130
+ import_fs_extra8.default.mkdirpSync(pathTo.projects);
2131
+ import_fs_extra8.default.mkdirpSync(pathTo.tmp);
2132
+ import_fs_extra8.default.emptyDirSync(pathTo.tmp);
2133
+ }
2134
+ /**
2135
+ * Utility / helper functions
2136
+ */
2137
+ get util() {
2138
+ return util_exports;
2139
+ }
2140
+ /**
2141
+ *
2142
+ */
2143
+ get user() {
2144
+ return this.userService;
2145
+ }
2146
+ /**
2147
+ * CRUD methods to work with Projects
2148
+ */
2149
+ get projects() {
2150
+ return this.projectService;
2151
+ }
2152
+ /**
2153
+ * CRUD methods to work with Assets
2154
+ */
2155
+ get assets() {
2156
+ return this.assetService;
2157
+ }
2158
+ /**
2159
+ * CRUD methods to work with Collections
2160
+ */
2161
+ get collections() {
2162
+ return this.collectionService;
2163
+ }
2164
+ /**
2165
+ * CRUD methods to work with Entries
2166
+ */
2167
+ get entries() {
2168
+ return this.entryService;
2169
+ }
2170
+ /**
2171
+ * CRUD methods to work with Values
2172
+ */
2173
+ get values() {
2174
+ return this.valueService;
2175
+ }
2176
+ };
2177
+ //# sourceMappingURL=index.cjs.map