@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/README.md +64 -0
- package/dist/index.cjs +2177 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +816 -0
- package/dist/index.d.ts +816 -0
- package/dist/index.js +2250 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
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
|