@oneuptime/common 8.0.5469 → 8.0.5479

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.
@@ -2,6 +2,7 @@ import { EncryptionSecret, WorkflowHostname } from "../EnvironmentConfig";
2
2
  import PostgresAppInstance from "../Infrastructure/PostgresDatabase";
3
3
  import ClusterKeyAuthorization from "../Middleware/ClusterKeyAuthorization";
4
4
  import CountBy from "../Types/Database/CountBy";
5
+ import FindAllBy from "../Types/Database/FindAllBy";
5
6
  import CreateBy from "../Types/Database/CreateBy";
6
7
  import DeleteBy from "../Types/Database/DeleteBy";
7
8
  import DeleteById from "../Types/Database/DeleteById";
@@ -1168,6 +1169,75 @@ class DatabaseService<TBaseModel extends BaseModel> extends BaseService {
1168
1169
  }
1169
1170
  }
1170
1171
 
1172
+ @CaptureSpan()
1173
+ public async findAllBy(
1174
+ findAllBy: FindAllBy<TBaseModel>,
1175
+ ): Promise<Array<TBaseModel>> {
1176
+ const { limit, skip, ...rest } = findAllBy;
1177
+
1178
+ let remaining: number | undefined = this.normalizePositiveNumber(limit);
1179
+ let currentSkip: number = this.normalizePositiveNumber(skip) || 0;
1180
+
1181
+ const results: Array<TBaseModel> = [];
1182
+
1183
+ while (true) {
1184
+ const currentBatchSize: number =
1185
+ remaining !== undefined
1186
+ ? Math.min(LIMIT_MAX, Math.max(remaining, 0))
1187
+ : LIMIT_MAX;
1188
+
1189
+ if (currentBatchSize <= 0) {
1190
+ break;
1191
+ }
1192
+
1193
+ const page: Array<TBaseModel> = await this.findBy({
1194
+ ...rest,
1195
+ skip: currentSkip,
1196
+ limit: currentBatchSize,
1197
+ });
1198
+
1199
+ if (page.length === 0) {
1200
+ break;
1201
+ }
1202
+
1203
+ results.push(...page);
1204
+
1205
+ currentSkip += page.length;
1206
+
1207
+ if (remaining !== undefined) {
1208
+ remaining -= page.length;
1209
+
1210
+ if (remaining <= 0) {
1211
+ break;
1212
+ }
1213
+ }
1214
+
1215
+ if (page.length < currentBatchSize) {
1216
+ break;
1217
+ }
1218
+ }
1219
+
1220
+ return results;
1221
+ }
1222
+
1223
+ private normalizePositiveNumber(
1224
+ value?: PositiveNumber | number,
1225
+ ): number | undefined {
1226
+ if (value === undefined || value === null) {
1227
+ return undefined;
1228
+ }
1229
+
1230
+ if (value instanceof PositiveNumber) {
1231
+ return value.toNumber();
1232
+ }
1233
+
1234
+ if (typeof value === "number") {
1235
+ return value;
1236
+ }
1237
+
1238
+ return undefined;
1239
+ }
1240
+
1171
1241
  @CaptureSpan()
1172
1242
  public async findBy(findBy: FindBy<TBaseModel>): Promise<Array<TBaseModel>> {
1173
1243
  return await this._findBy(findBy);
@@ -71,6 +71,8 @@ import URL from "../../Types/API/URL";
71
71
  import Exception from "../../Types/Exception/Exception";
72
72
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
73
73
  import DatabaseConfig from "../DatabaseConfig";
74
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
75
+ import PositiveNumber from "../../Types/PositiveNumber";
74
76
 
75
77
  export interface CurrentPlan {
76
78
  plan: PlanType | null;
@@ -1435,6 +1437,28 @@ export class ProjectService extends DatabaseService<Model> {
1435
1437
  };
1436
1438
  }
1437
1439
 
1440
+ @CaptureSpan()
1441
+ public async getAllActiveProjects(params?: {
1442
+ select?: Select<Model>;
1443
+ props?: DatabaseCommonInteractionProps;
1444
+ skip?: PositiveNumber | number;
1445
+ limit?: PositiveNumber | number;
1446
+ }): Promise<Array<Model>> {
1447
+ const select: Select<Model> | undefined =
1448
+ params?.select || ({ _id: true } as Select<Model>);
1449
+ const props: DatabaseCommonInteractionProps = params?.props || {
1450
+ isRoot: true,
1451
+ };
1452
+
1453
+ return await this.findAllBy({
1454
+ query: this.getActiveProjectStatusQuery(),
1455
+ select,
1456
+ props,
1457
+ skip: params?.skip,
1458
+ limit: params?.limit,
1459
+ });
1460
+ }
1461
+
1438
1462
  @CaptureSpan()
1439
1463
  public async getProjectLinkInDashboard(projectId: ObjectID): Promise<URL> {
1440
1464
  const dashboardUrl: URL = await DatabaseConfig.getDashboardUrl();
@@ -0,0 +1,25 @@
1
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
2
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
3
+ import GroupBy from "./GroupBy";
4
+ import Query from "./Query";
5
+ import Select from "./Select";
6
+ import Sort from "./Sort";
7
+ import PositiveNumber from "../../../Types/PositiveNumber";
8
+
9
+ export default interface FindAllBy<TBaseModel extends BaseModel> {
10
+ query: Query<TBaseModel>;
11
+ select?: Select<TBaseModel> | undefined;
12
+ sort?: Sort<TBaseModel> | undefined;
13
+ groupBy?: GroupBy<TBaseModel> | undefined;
14
+ props: DatabaseCommonInteractionProps;
15
+ /**
16
+ * Optional number of documents to skip before fetching results.
17
+ * Acts the same way as `skip` in `findBy` but defaults to 0 when omitted.
18
+ */
19
+ skip?: PositiveNumber | number | undefined;
20
+ /**
21
+ * Optional total number of documents to return across all batches.
22
+ * When omitted, the method keeps fetching until no more data is returned.
23
+ */
24
+ limit?: PositiveNumber | number | undefined;
25
+ }
@@ -13,24 +13,14 @@ export default class CodeRepositoryUtil {
13
13
  public static getCurrentCommitHash(data: {
14
14
  repoPath: string;
15
15
  }): Promise<string> {
16
- const command: string = `cd ${data.repoPath} && git rev-parse HEAD`;
17
-
18
- logger.debug("Executing command: " + command);
19
-
20
- return Execute.executeCommand(command);
16
+ return this.runGitCommand(data.repoPath, ["rev-parse", "HEAD"]);
21
17
  }
22
18
 
23
19
  @CaptureSpan()
24
20
  public static async addAllChangedFilesToGit(data: {
25
21
  repoPath: string;
26
22
  }): Promise<void> {
27
- const command: string = `cd ${data.repoPath} && git add -A`;
28
-
29
- logger.debug("Executing command: " + command);
30
-
31
- const stdout: string = await Execute.executeCommand(command);
32
-
33
- logger.debug(stdout);
23
+ await this.runGitCommand(data.repoPath, ["add", "-A"]);
34
24
  }
35
25
 
36
26
  @CaptureSpan()
@@ -39,26 +29,26 @@ export default class CodeRepositoryUtil {
39
29
  authorName: string;
40
30
  authorEmail: string;
41
31
  }): Promise<void> {
42
- const command: string = `cd ${data.repoPath} && git config --global user.name "${data.authorName}" && git config --global user.email "${data.authorEmail}"`;
43
-
44
- logger.debug("Executing command: " + command);
45
-
46
- const stdout: string = await Execute.executeCommand(command);
47
-
48
- logger.debug(stdout);
32
+ await this.runGitCommand(data.repoPath, [
33
+ "config",
34
+ "--global",
35
+ "user.name",
36
+ data.authorName,
37
+ ]);
38
+
39
+ await this.runGitCommand(data.repoPath, [
40
+ "config",
41
+ "--global",
42
+ "user.email",
43
+ data.authorEmail,
44
+ ]);
49
45
  }
50
46
 
51
47
  @CaptureSpan()
52
48
  public static async discardAllChangesOnCurrentBranch(data: {
53
49
  repoPath: string;
54
50
  }): Promise<void> {
55
- const command: string = `cd ${data.repoPath} && git checkout .`;
56
-
57
- logger.debug("Executing command: " + command);
58
-
59
- const stdout: string = await Execute.executeCommand(command);
60
-
61
- logger.debug(stdout);
51
+ await this.runGitCommand(data.repoPath, ["checkout", "."]);
62
52
  }
63
53
 
64
54
  // returns the folder name of the cloned repository
@@ -67,33 +57,25 @@ export default class CodeRepositoryUtil {
67
57
  repoPath: string;
68
58
  repoUrl: string;
69
59
  }): Promise<string> {
70
- const command: string = `cd ${data.repoPath} && git clone ${data.repoUrl}`;
71
-
72
- logger.debug("Executing command: " + command);
73
-
74
- const stdout: string = await Execute.executeCommand(command);
60
+ await this.runGitCommand(data.repoPath, ["clone", data.repoUrl]);
75
61
 
76
- logger.debug(stdout);
77
-
78
- // get the folder name of the repository from the disk.
62
+ const normalizedUrl: string = data.repoUrl.trim().replace(/\/+$/g, "");
63
+ const lastSegment: string =
64
+ normalizedUrl.split("/").pop() || normalizedUrl.split(":").pop() || "";
65
+ const folderName: string = lastSegment.replace(/\.git$/i, "");
79
66
 
80
- const getFolderNameCommand: string = `cd ${data.repoPath} && ls`;
81
-
82
- const folderName: string =
83
- await Execute.executeCommand(getFolderNameCommand);
67
+ if (!folderName) {
68
+ throw new BadDataException(
69
+ "Unable to determine repository folder name after cloning.",
70
+ );
71
+ }
84
72
 
85
73
  return folderName.trim();
86
74
  }
87
75
 
88
76
  @CaptureSpan()
89
77
  public static async pullChanges(data: { repoPath: string }): Promise<void> {
90
- const command: string = `cd ${data.repoPath} && git pull`;
91
-
92
- logger.debug("Executing command: " + command);
93
-
94
- const stdout: string = await Execute.executeCommand(command);
95
-
96
- logger.debug(stdout);
78
+ await this.runGitCommand(data.repoPath, ["pull"]);
97
79
  }
98
80
 
99
81
  @CaptureSpan()
@@ -101,13 +83,26 @@ export default class CodeRepositoryUtil {
101
83
  repoPath: string;
102
84
  branchName: string;
103
85
  }): Promise<void> {
104
- const command: string = `cd ${data.repoPath} && git checkout ${data.branchName} || git checkout -b ${data.branchName}`;
105
-
106
- logger.debug("Executing command: " + command);
86
+ try {
87
+ await this.runGitCommand(data.repoPath, [
88
+ "rev-parse",
89
+ "--verify",
90
+ data.branchName,
91
+ ]);
92
+ await this.runGitCommand(data.repoPath, ["checkout", data.branchName]);
93
+ } catch (error) {
94
+ logger.debug(
95
+ `Branch ${data.branchName} not found. Creating a new branch instead.`,
96
+ );
107
97
 
108
- const stdout: string = await Execute.executeCommand(command);
98
+ logger.debug(error);
109
99
 
110
- logger.debug(stdout);
100
+ await this.runGitCommand(data.repoPath, [
101
+ "checkout",
102
+ "-b",
103
+ data.branchName,
104
+ ]);
105
+ }
111
106
  }
112
107
 
113
108
  @CaptureSpan()
@@ -128,13 +123,7 @@ export default class CodeRepositoryUtil {
128
123
  public static async discardChanges(data: {
129
124
  repoPath: string;
130
125
  }): Promise<void> {
131
- const command: string = `cd ${data.repoPath} && git checkout .`;
132
-
133
- logger.debug("Executing command: " + command);
134
-
135
- const stdout: string = await Execute.executeCommand(command);
136
-
137
- logger.debug(stdout);
126
+ await this.runGitCommand(data.repoPath, ["checkout", "."]);
138
127
  }
139
128
 
140
129
  @CaptureSpan()
@@ -193,11 +182,15 @@ export default class CodeRepositoryUtil {
193
182
  repoPath: string;
194
183
  branchName: string;
195
184
  }): Promise<void> {
196
- const command: string = `cd ${data.repoPath} && git checkout -b ${data.branchName}`;
197
-
198
- logger.debug("Executing command: " + command);
185
+ logger.debug(
186
+ `Creating git branch '${data.branchName}' in ${path.resolve(data.repoPath)}`,
187
+ );
199
188
 
200
- const stdout: string = await Execute.executeCommand(command);
189
+ const stdout: string = await this.runGitCommand(data.repoPath, [
190
+ "checkout",
191
+ "-b",
192
+ data.branchName,
193
+ ]);
201
194
 
202
195
  logger.debug(stdout);
203
196
  }
@@ -207,11 +200,14 @@ export default class CodeRepositoryUtil {
207
200
  repoPath: string;
208
201
  branchName: string;
209
202
  }): Promise<void> {
210
- const command: string = `cd ${data.repoPath} && git checkout ${data.branchName}`;
211
-
212
- logger.debug("Executing command: " + command);
203
+ logger.debug(
204
+ `Checking out git branch '${data.branchName}' in ${path.resolve(data.repoPath)}`,
205
+ );
213
206
 
214
- const stdout: string = await Execute.executeCommand(command);
207
+ const stdout: string = await this.runGitCommand(data.repoPath, [
208
+ "checkout",
209
+ data.branchName,
210
+ ]);
215
211
 
216
212
  logger.debug(stdout);
217
213
  }
@@ -221,22 +217,51 @@ export default class CodeRepositoryUtil {
221
217
  repoPath: string;
222
218
  filePaths: Array<string>;
223
219
  }): Promise<void> {
224
- const filePaths: Array<string> = data.filePaths.map((filePath: string) => {
225
- if (filePath.startsWith("/")) {
226
- // remove the leading slash and return
227
- return filePath.substring(1);
220
+ const repoRoot: string = path.resolve(data.repoPath);
221
+
222
+ const sanitizedRelativeFilePaths: Array<string> = [];
223
+
224
+ for (const inputFilePath of data.filePaths) {
225
+ const normalizedPath: string = inputFilePath.startsWith("/")
226
+ ? inputFilePath.substring(1)
227
+ : inputFilePath;
228
+
229
+ if (normalizedPath.trim() === "") {
230
+ continue;
228
231
  }
229
232
 
230
- return filePath;
231
- });
233
+ const absoluteFilePath: string = this.resolvePathWithinRepo(
234
+ data.repoPath,
235
+ normalizedPath,
236
+ );
232
237
 
233
- const command: string = `cd ${
234
- data.repoPath
235
- } && git add ${filePaths.join(" ")}`;
238
+ const relativeFilePath: string = path.relative(
239
+ repoRoot,
240
+ absoluteFilePath,
241
+ );
236
242
 
237
- logger.debug("Executing command: " + command);
243
+ if (relativeFilePath.trim() === "") {
244
+ continue;
245
+ }
238
246
 
239
- const stdout: string = await Execute.executeCommand(command);
247
+ sanitizedRelativeFilePaths.push(
248
+ LocalFile.sanitizeFilePath(relativeFilePath),
249
+ );
250
+ }
251
+
252
+ if (sanitizedRelativeFilePaths.length === 0) {
253
+ logger.debug("git add skipped because no file paths were provided");
254
+ return;
255
+ }
256
+
257
+ logger.debug(
258
+ `Adding ${sanitizedRelativeFilePaths.length} file(s) to git in ${path.resolve(data.repoPath)}`,
259
+ );
260
+
261
+ const stdout: string = await this.runGitCommand(data.repoPath, [
262
+ "add",
263
+ ...sanitizedRelativeFilePaths,
264
+ ]);
240
265
 
241
266
  logger.debug(stdout);
242
267
  }
@@ -246,11 +271,13 @@ export default class CodeRepositoryUtil {
246
271
  repoPath: string;
247
272
  username: string;
248
273
  }): Promise<void> {
249
- const command: string = `cd ${data.repoPath} && git config user.name "${data.username}"`;
250
-
251
- logger.debug("Executing command: " + command);
274
+ logger.debug(`Setting git user.name in ${path.resolve(data.repoPath)}`);
252
275
 
253
- const stdout: string = await Execute.executeCommand(command);
276
+ const stdout: string = await this.runGitCommand(data.repoPath, [
277
+ "config",
278
+ "user.name",
279
+ data.username,
280
+ ]);
254
281
 
255
282
  logger.debug(stdout);
256
283
  }
@@ -286,15 +313,28 @@ export default class CodeRepositoryUtil {
286
313
 
287
314
  const { repoPath, filePath } = data;
288
315
 
289
- const command: string = `cd ${repoPath} && git log -1 --pretty=format:"%H" ".${filePath}"`;
316
+ const repoRoot: string = path.resolve(repoPath);
317
+ const absoluteTarget: string = this.resolvePathWithinRepo(
318
+ repoPath,
319
+ filePath,
320
+ );
321
+ const relativeTarget: string = path.relative(repoRoot, absoluteTarget);
322
+ const gitArgument: string = LocalFile.sanitizeFilePath(
323
+ `./${relativeTarget}`,
324
+ );
290
325
 
291
- logger.debug("Executing command: " + command);
326
+ logger.debug(`Getting last commit hash for ${gitArgument} in ${repoRoot}`);
292
327
 
293
- const hash: string = await Execute.executeCommand(command);
328
+ const hash: string = await this.runGitCommand(repoRoot, [
329
+ "log",
330
+ "-1",
331
+ "--pretty=format:%H",
332
+ gitArgument,
333
+ ]);
294
334
 
295
335
  logger.debug(hash);
296
336
 
297
- return hash;
337
+ return hash.trim();
298
338
  }
299
339
 
300
340
  @CaptureSpan()
@@ -438,6 +478,27 @@ export default class CodeRepositoryUtil {
438
478
  return files;
439
479
  }
440
480
 
481
+ private static runGitCommand(
482
+ repoPath: string,
483
+ args: Array<string>,
484
+ ): Promise<string> {
485
+ const cwd: string = path.resolve(repoPath);
486
+
487
+ logger.debug(
488
+ `Executing git command in ${cwd}: git ${args
489
+ .map((arg: string) => {
490
+ return arg.includes(" ") ? `"${arg}"` : arg;
491
+ })
492
+ .join(" ")}`,
493
+ );
494
+
495
+ return Execute.executeCommandFile({
496
+ command: "git",
497
+ args,
498
+ cwd,
499
+ });
500
+ }
501
+
441
502
  private static resolvePathWithinRepo(
442
503
  repoPath: string,
443
504
  targetPath: string,
@@ -170,13 +170,15 @@ export default class GitHubUtil extends HostedCodeRepository {
170
170
  `https://github.com/${data.organizationName}/${data.repositoryName}.git`,
171
171
  );
172
172
 
173
- const command: string = `git remote add ${
174
- data.remoteName
175
- } ${url.toString()}`;
176
-
177
- logger.debug("Executing command: " + command);
173
+ logger.debug(
174
+ `Adding remote '${data.remoteName}' for ${data.organizationName}/${data.repositoryName}`,
175
+ );
178
176
 
179
- const result: string = await Execute.executeCommand(command);
177
+ const result: string = await Execute.executeCommandFile({
178
+ command: "git",
179
+ args: ["remote", "add", data.remoteName, url.toString()],
180
+ cwd: process.cwd(),
181
+ });
180
182
 
181
183
  logger.debug(result);
182
184
  }
@@ -197,10 +199,19 @@ export default class GitHubUtil extends HostedCodeRepository {
197
199
  "Pushing changes to remote repository with username: " + username,
198
200
  );
199
201
 
200
- const command: string = `cd ${data.repoPath} && git push -u https://${username}:${password}@github.com/${data.organizationName}/${data.repositoryName}.git ${branchName}`;
201
- logger.debug("Executing command: " + command);
202
+ const encodedUsername: string = encodeURIComponent(username);
203
+ const encodedPassword: string = encodeURIComponent(password);
204
+ const remoteUrl: string = `https://${encodedUsername}:${encodedPassword}@github.com/${data.organizationName}/${data.repositoryName}.git`;
202
205
 
203
- const result: string = await Execute.executeCommand(command);
206
+ logger.debug(
207
+ `Pushing branch '${branchName}' to ${data.organizationName}/${data.repositoryName}`,
208
+ );
209
+
210
+ const result: string = await Execute.executeCommandFile({
211
+ command: "git",
212
+ args: ["push", "-u", remoteUrl, branchName],
213
+ cwd: data.repoPath,
214
+ });
204
215
 
205
216
  logger.debug(result);
206
217
  }
@@ -1,26 +1,47 @@
1
1
  import { PromiseRejectErrorFunction } from "../../Types/FunctionTypes";
2
- import { ExecException, exec, execFile } from "node:child_process";
2
+ import {
3
+ ExecException,
4
+ ExecOptions,
5
+ exec,
6
+ execFile,
7
+ } from "node:child_process";
3
8
  import logger from "./Logger";
4
9
  import CaptureSpan from "./Telemetry/CaptureSpan";
5
10
 
6
11
  export default class Execute {
7
12
  @CaptureSpan()
8
- public static executeCommand(command: string): Promise<string> {
13
+ public static executeCommand(
14
+ command: string,
15
+ options?: ExecOptions,
16
+ ): Promise<string> {
9
17
  return new Promise(
10
18
  (
11
19
  resolve: (output: string) => void,
12
20
  reject: PromiseRejectErrorFunction,
13
21
  ) => {
14
- exec(`${command}`, (err: ExecException | null, stdout: string) => {
22
+ exec(
23
+ `${command}`,
24
+ {
25
+ ...options,
26
+ },
27
+ (err: ExecException | null, stdout: string, stderr: string) => {
15
28
  if (err) {
16
29
  logger.error(`Error executing command: ${command}`);
17
30
  logger.error(err);
18
31
  logger.error(stdout);
32
+ if (stderr) {
33
+ logger.error(stderr);
34
+ }
19
35
  return reject(err);
20
36
  }
21
37
 
38
+ if (stderr) {
39
+ logger.debug(stderr);
40
+ }
41
+
22
42
  return resolve(stdout);
23
- });
43
+ },
44
+ );
24
45
  },
25
46
  );
26
47
  }