tsondb 0.17.7 → 0.18.1

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.
Files changed (115) hide show
  1. package/dist/src/bin/tsondb.js +46 -33
  2. package/dist/src/node/config.d.ts +2 -1
  3. package/dist/src/node/config.js +5 -0
  4. package/dist/src/node/git.d.ts +130 -0
  5. package/dist/src/node/git.js +233 -0
  6. package/dist/src/node/index.d.ts +217 -17
  7. package/dist/src/node/index.js +441 -105
  8. package/dist/src/node/renderers/jsonschema/index.js +4 -4
  9. package/dist/src/node/renderers/jsonschema/render.js +1 -1
  10. package/dist/src/node/renderers/ts/index.js +1 -1
  11. package/dist/src/node/renderers/ts/render.js +3 -3
  12. package/dist/src/node/schema/Node.js +1 -1
  13. package/dist/src/node/schema/Schema.d.ts +13 -5
  14. package/dist/src/node/schema/Schema.js +78 -36
  15. package/dist/src/node/schema/declarations/Declaration.js +1 -1
  16. package/dist/src/node/schema/declarations/EntityDecl.d.ts +1 -1
  17. package/dist/src/node/schema/declarations/EntityDecl.js +1 -1
  18. package/dist/src/node/schema/declarations/EnumDecl.d.ts +46 -9
  19. package/dist/src/node/schema/declarations/EnumDecl.js +9 -17
  20. package/dist/src/node/schema/declarations/TypeAliasDecl.d.ts +45 -10
  21. package/dist/src/node/schema/declarations/TypeAliasDecl.js +10 -17
  22. package/dist/src/node/schema/helpers.d.ts +50 -9
  23. package/dist/src/node/schema/helpers.js +9 -0
  24. package/dist/src/node/schema/types/Type.js +2 -2
  25. package/dist/src/node/schema/types/generic/ObjectType.js +2 -2
  26. package/dist/src/node/schema/types/generic/TranslationObjectType.js +2 -2
  27. package/dist/src/node/schema/types/references/NestedEntityMapType.d.ts +1 -1
  28. package/dist/src/node/schema/types/references/NestedEntityMapType.js +3 -3
  29. package/dist/src/node/server/api/declarations.js +41 -64
  30. package/dist/src/node/server/api/git.js +44 -75
  31. package/dist/src/node/server/api/index.js +1 -1
  32. package/dist/src/node/server/api/instances.js +1 -2
  33. package/dist/src/node/server/api/search.js +1 -38
  34. package/dist/src/node/server/index.d.ts +3 -20
  35. package/dist/src/node/server/index.js +25 -7
  36. package/dist/src/node/server/utils/instanceOperations.d.ts +4 -5
  37. package/dist/src/node/server/utils/instanceOperations.js +27 -48
  38. package/dist/src/node/transaction.d.ts +44 -0
  39. package/dist/src/node/transaction.js +125 -0
  40. package/dist/src/node/utils/childInstances.d.ts +4 -6
  41. package/dist/src/node/utils/childInstances.js +58 -46
  42. package/dist/src/node/utils/customConstraints.d.ts +20 -21
  43. package/dist/src/node/utils/customConstraints.js +13 -37
  44. package/dist/src/node/utils/databaseInMemory.d.ts +17 -20
  45. package/dist/src/node/utils/databaseInMemory.js +28 -44
  46. package/dist/src/node/utils/databaseOnDisk.d.ts +2 -2
  47. package/dist/src/node/utils/databaseOnDisk.js +8 -10
  48. package/dist/src/node/utils/displayName.d.ts +15 -12
  49. package/dist/src/node/utils/displayName.js +49 -31
  50. package/dist/src/node/utils/error.d.ts +2 -0
  51. package/dist/src/node/utils/error.js +12 -0
  52. package/dist/src/node/utils/git.d.ts +2 -1
  53. package/dist/src/node/utils/git.js +1 -2
  54. package/dist/src/node/utils/references.d.ts +3 -2
  55. package/dist/src/node/utils/references.js +21 -13
  56. package/dist/src/node/utils/render.js +1 -1
  57. package/dist/src/node/utils/unique.d.ts +2 -2
  58. package/dist/src/node/utils/unique.js +5 -4
  59. package/dist/src/node/utils/workers.d.ts +1 -1
  60. package/dist/src/node/utils/workers.js +1 -1
  61. package/dist/src/shared/output.d.ts +2 -1
  62. package/dist/src/shared/schema/Node.js +1 -1
  63. package/dist/src/shared/schema/types/Type.js +1 -1
  64. package/dist/src/shared/schema/utils/keyPath.d.ts +1 -1
  65. package/dist/src/shared/schema/utils/keyPath.js +3 -3
  66. package/dist/src/shared/utils/instances.d.ts +5 -6
  67. package/dist/src/shared/utils/instances.js +3 -3
  68. package/dist/src/shared/utils/markdown.js +3 -3
  69. package/dist/src/shared/utils/object.d.ts +0 -7
  70. package/dist/src/shared/utils/object.js +1 -14
  71. package/dist/src/shared/utils/translation.js +1 -1
  72. package/dist/src/shared/utils/validation.js +1 -1
  73. package/dist/src/shared/validation/number.js +1 -1
  74. package/dist/src/web/components/InstanceRouteSkeleton.js +2 -2
  75. package/dist/src/web/components/Settings.js +1 -1
  76. package/dist/src/web/components/typeInputs/ArrayTypeInput.js +1 -1
  77. package/dist/src/web/components/typeInputs/ChildEntitiesTypeInput.js +1 -1
  78. package/dist/src/web/components/typeInputs/EnumTypeInput.js +2 -2
  79. package/dist/src/web/components/typeInputs/NestedEntityMapTypeInput.js +4 -4
  80. package/dist/src/web/components/typeInputs/ObjectTypeInput.js +4 -4
  81. package/dist/src/web/components/typeInputs/TranslationObjectTypeInput.js +3 -3
  82. package/dist/src/web/components/typeInputs/TypeInput.js +1 -1
  83. package/dist/src/web/routes/CreateInstance.js +1 -1
  84. package/dist/src/web/routes/Entity.js +1 -1
  85. package/dist/src/web/routes/Home.js +1 -1
  86. package/dist/src/web/routes/Instance.js +1 -1
  87. package/dist/src/web/routes/Search.js +1 -1
  88. package/dist/src/web/utils/BlockMarkdown.js +1 -1
  89. package/dist/src/web/utils/InlineMarkdown.js +1 -1
  90. package/dist/src/web/utils/typeSkeleton.js +1 -1
  91. package/package.json +8 -7
  92. package/dist/src/node/server/init.d.ts +0 -7
  93. package/dist/src/node/server/init.js +0 -65
  94. package/dist/src/node/server/utils/childInstances.d.ts +0 -4
  95. package/dist/src/node/server/utils/childInstances.js +0 -12
  96. package/dist/src/node/utils/databaseTransactions.d.ts +0 -46
  97. package/dist/src/node/utils/databaseTransactions.js +0 -117
  98. package/dist/src/node/utils/instanceTransactionSteps.d.ts +0 -10
  99. package/dist/src/node/utils/instanceTransactionSteps.js +0 -40
  100. package/dist/src/shared/utils/array.d.ts +0 -61
  101. package/dist/src/shared/utils/array.js +0 -117
  102. package/dist/src/shared/utils/async.d.ts +0 -8
  103. package/dist/src/shared/utils/async.js +0 -35
  104. package/dist/src/shared/utils/compare.d.ts +0 -13
  105. package/dist/src/shared/utils/compare.js +0 -29
  106. package/dist/src/shared/utils/dictionary.d.ts +0 -18
  107. package/dist/src/shared/utils/dictionary.js +0 -77
  108. package/dist/src/shared/utils/lazy.d.ts +0 -16
  109. package/dist/src/shared/utils/lazy.js +0 -32
  110. package/dist/src/shared/utils/result.d.ts +0 -73
  111. package/dist/src/shared/utils/result.js +0 -52
  112. package/dist/src/shared/utils/string.d.ts +0 -7
  113. package/dist/src/shared/utils/string.js +0 -64
  114. package/dist/src/shared/utils/typeSafety.d.ts +0 -5
  115. package/dist/src/shared/utils/typeSafety.js +0 -11
@@ -7,15 +7,16 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
7
7
  }
8
8
  return path;
9
9
  };
10
+ import { omitUndefinedKeys } from "@elyukai/utils/object";
10
11
  import Debug from "debug";
11
12
  import { access, constants } from "node:fs/promises";
12
13
  import { join } from "node:path";
13
14
  import { cwd } from "node:process";
14
15
  import { pathToFileURL } from "node:url";
15
16
  import { parseArguments } from "simple-cli-args";
16
- import { validateConfigForFormatting, validateConfigForGeneration, validateConfigForServer, validateConfigForTesting, } from "../node/config.js";
17
- import { format, generateOutputs, serve, validate } from "../node/index.js";
18
- import { omitUndefinedKeys } from "../shared/utils/object.js";
17
+ import { validateConfigForData, validateConfigForGeneration, validateConfigForServer, validateConfigForTesting, } from "../node/config.js";
18
+ import { TSONDB } from "../node/index.js";
19
+ import { createServer } from "../node/server/index.js";
19
20
  const debug = Debug("tsondb:cli");
20
21
  const passedArguments = parseArguments({
21
22
  commands: {
@@ -83,35 +84,47 @@ if (config === undefined) {
83
84
  if (passedArguments.command === undefined) {
84
85
  throw new Error("No command has been specified. Possible commands are: generate, serve, validate.");
85
86
  }
86
- switch (passedArguments.command.name) {
87
- case "generate":
88
- debug(`running command: generate`);
89
- validateConfigForGeneration(config);
90
- await generateOutputs(config.schema, config.outputs);
91
- break;
92
- case "serve":
93
- debug(`running command: serve`);
94
- validateConfigForServer(config);
95
- await serve(config.schema, config.dataRootPath, config.locales, config.homeLayoutSections, config.serverOptions, config.validationOptions, config.customStylesheetPath);
96
- break;
97
- case "validate":
98
- debug(`running command: validate`);
99
- validateConfigForTesting(config);
100
- if (passedArguments.command.options?.checkReferentialIntegrity !== undefined) {
101
- debug(`check referential integrity: ${passedArguments.command.options.checkReferentialIntegrity ? "yes" : "no"}`);
102
- }
103
- if (passedArguments.command.options?.checkOnlyEntities !== undefined) {
104
- const entities = passedArguments.command.options.checkOnlyEntities;
105
- debug(`only check the following entities: ${entities.join(", ")}`);
87
+ if (passedArguments.command.name === "generate") {
88
+ debug(`running command: generate`);
89
+ validateConfigForGeneration(config);
90
+ await TSONDB.generateOutputs(config);
91
+ }
92
+ else {
93
+ validateConfigForData(config);
94
+ const db = await TSONDB.create({
95
+ ...config,
96
+ validationOptions: passedArguments.command.name === "validate"
97
+ ? {
98
+ ...config.validationOptions,
99
+ ...omitUndefinedKeys(passedArguments.command.options ?? {}),
100
+ }
101
+ : config.validationOptions,
102
+ });
103
+ switch (passedArguments.command.name) {
104
+ case "serve":
105
+ debug(`running command: serve`);
106
+ validateConfigForServer(config);
107
+ await createServer(db, config.homeLayoutSections, config.serverOptions, config.validationOptions, config.customStylesheetPath);
108
+ break;
109
+ case "validate": {
110
+ debug(`running command: validate`);
111
+ validateConfigForTesting(config);
112
+ if (passedArguments.command.options?.checkReferentialIntegrity !== undefined) {
113
+ debug(`check referential integrity: ${passedArguments.command.options.checkReferentialIntegrity ? "yes" : "no"}`);
114
+ }
115
+ if (passedArguments.command.options?.checkOnlyEntities !== undefined) {
116
+ const entities = passedArguments.command.options.checkOnlyEntities;
117
+ debug(`only check the following entities: ${entities.join(", ")}`);
118
+ }
119
+ const result = db.validate();
120
+ if (!result) {
121
+ process.exitCode = 1;
122
+ }
123
+ break;
106
124
  }
107
- await validate(config.schema, config.dataRootPath, config.locales, {
108
- ...config.validationOptions,
109
- ...omitUndefinedKeys(passedArguments.command.options ?? {}),
110
- });
111
- break;
112
- case "format":
113
- debug(`running command: format`);
114
- validateConfigForFormatting(config);
115
- await format(config.schema, config.dataRootPath);
116
- break;
125
+ case "format":
126
+ debug(`running command: format`);
127
+ await db.format();
128
+ break;
129
+ }
117
130
  }
@@ -37,10 +37,11 @@ export declare const validateConfigForGeneration: (config: Config) => asserts co
37
37
  /**
38
38
  * The configuration type required for any commands that need to read data stored in the database.
39
39
  */
40
- export type DataConfig = {
40
+ export type DataConfig = Config & {
41
41
  schema: Schema;
42
42
  dataRootPath: string;
43
43
  };
44
+ export declare const validateConfigForData: (config: Config) => asserts config is DataConfig;
44
45
  /**
45
46
  * The configuration type required for running the server for the editor.
46
47
  */
@@ -3,6 +3,11 @@ export const validateConfigForGeneration = config => {
3
3
  throw new Error("At least one output must be specified in the config.");
4
4
  }
5
5
  };
6
+ export const validateConfigForData = config => {
7
+ if (config.dataRootPath === undefined) {
8
+ throw new Error("A data root path must be specified in the config.");
9
+ }
10
+ };
6
11
  export const validateConfigForServer = config => {
7
12
  if ((config.locales?.length ?? 0) === 0) {
8
13
  throw new Error("At least one locale must be specified in the config.");
@@ -0,0 +1,130 @@
1
+ import type { SimpleGit } from "simple-git";
2
+ import type { InstanceContainerOverview } from "../shared/utils/instances.ts";
3
+ import type { DefaultTSONDBTypes, EntityName, TSONDB } from "./index.ts";
4
+ import type { DatabaseInMemory } from "./utils/databaseInMemory.ts";
5
+ export declare class Git<T extends DefaultTSONDBTypes = DefaultTSONDBTypes> {
6
+ #private;
7
+ constructor(db: TSONDB<T>, git: SimpleGit, gitRoot: string, dataRoot: string, getData: () => DatabaseInMemory<T["entityMap"]>, setData: (data: DatabaseInMemory<T["entityMap"]>) => void);
8
+ /**
9
+ * Runs a `git status` operation and updates the in-memory database with the latest statuses.
10
+ *
11
+ * @throws {HTTPError} when no git repository is found
12
+ */
13
+ status(): Promise<{
14
+ currentBranch: string | null;
15
+ trackingBranch: string | null;
16
+ commitsAhead: number;
17
+ commitsBehind: number;
18
+ latestCommit: string;
19
+ instances: Record<string, InstanceContainerOverview[]>;
20
+ }>;
21
+ /**
22
+ * Fetches updates from remote git repositories.
23
+ *
24
+ * @throws {HTTPError} when no git repository is found
25
+ */
26
+ fetch(options: Partial<{
27
+ all: boolean;
28
+ prune: boolean;
29
+ }>): Promise<void>;
30
+ /**
31
+ * Adds files to the git staging area.
32
+ *
33
+ * - If the function is called without parameters, all changes to all instances are added.
34
+ * - If the function is called with only the `entity` parameter, all changes to instances of that entity are added.
35
+ * - If both `entity` and `id` parameters are provided, only the changes to that specific instance are added.
36
+ *
37
+ * @throws {HTTPError} when no git repository is found
38
+ */
39
+ stage(entity?: EntityName<T>, id?: string): Promise<void>;
40
+ /**
41
+ * Removes files from the git staging area.
42
+ *
43
+ * - If the function is called without parameters, all changes to all instances are removed.
44
+ * - If the function is called with only the `entity` parameter, all changes to instances of that entity are removed.
45
+ * - If both `entity` and `id` parameters are provided, only the changes to that specific instance are removed.
46
+ *
47
+ * @throws {HTTPError} when no git repository is found
48
+ */
49
+ unstage(entity?: EntityName<T>, id?: string): Promise<void>;
50
+ /**
51
+ * Restores files in the git repository to their last committed state.
52
+ *
53
+ * - If the function is called without parameters, all changes to all instances are restored.
54
+ * - If the function is called with only the `entity` parameter, all changes to instances of that entity are restored.
55
+ * - If both `entity` and `id` parameters are provided, only the changes to that specific instance are restored.
56
+ *
57
+ * @throws {HTTPError} when no git repository is found
58
+ */
59
+ restore(entity?: EntityName<T>, id?: string): Promise<void>;
60
+ /**
61
+ * Commits staged changes to the git repository with the provided commit message.
62
+ *
63
+ * @throws {HTTPError} when no git repository is found
64
+ * @throws {HTTPError} when the commit message is empty
65
+ */
66
+ commit(message: string): Promise<void>;
67
+ /**
68
+ * Pushes committed changes to the remote git repository.
69
+ *
70
+ * @throws {HTTPError} when no git repository is found
71
+ */
72
+ push(): Promise<void>;
73
+ /**
74
+ * Pulls changes from the remote git repository.
75
+ *
76
+ * @throws {HTTPError} when no git repository is found
77
+ */
78
+ pull(): Promise<void>;
79
+ /**
80
+ * Retrieves information about the branches in the git repository.
81
+ */
82
+ branch(): Promise<{
83
+ isDetached: boolean;
84
+ currentBranch: string;
85
+ allBranches: string[];
86
+ branches: {
87
+ [key: string]: {
88
+ current: boolean;
89
+ name: string;
90
+ commit: string;
91
+ label: string;
92
+ linkedWorkTree: boolean;
93
+ };
94
+ };
95
+ }>;
96
+ /**
97
+ * Creates a new branch in the git repository and checks it out.
98
+ *
99
+ * @param branchName The name of the branch to create.
100
+ * @throws {HTTPError} when no git repository is found
101
+ */
102
+ createBranch(branchName: string): Promise<void>;
103
+ /**
104
+ * Switches to an existing branch in the git repository.
105
+ *
106
+ * @param branchName The name of the branch to switch to.
107
+ * @throws {HTTPError} when no git repository is found
108
+ */
109
+ switchBranch(branchName: string): Promise<void>;
110
+ /**
111
+ * Deletes a branch in the git repository.
112
+ *
113
+ * @param branchName The name of the branch to delete.
114
+ * @param options Options for deleting the branch.
115
+ * @throws {HTTPError} when no git repository is found
116
+ * @throws {HTTPError} when trying to delete the currently checked out branch
117
+ */
118
+ deleteBranch(branchName: string, options: {
119
+ force?: boolean;
120
+ }): Promise<void>;
121
+ /**
122
+ * Run a custom git operation.
123
+ *
124
+ * @param commands The git commands to run as an array of strings.
125
+ * @param commandMayChangeDataOnDisk Whether the command may change data on disk and thus requires a sync afterwards.
126
+ * @returns The raw output of the git command.
127
+ * @throws {HTTPError} when no git repository is found
128
+ */
129
+ raw(commands: string[], commandMayChangeDataOnDisk: boolean): Promise<string>;
130
+ }
@@ -0,0 +1,233 @@
1
+ import Debug from "debug";
2
+ import { join } from "path";
3
+ import { getAllInstanceOverviewsByEntityName } from "./utils/displayName.js";
4
+ import { HTTPError } from "./utils/error.js";
5
+ import { getFileNameForId } from "./utils/files.js";
6
+ import { attachGitStatusToDatabaseInMemory } from "./utils/git.js";
7
+ const debug = Debug("tsondb:git");
8
+ export class Git {
9
+ #db;
10
+ #git;
11
+ #gitRoot;
12
+ #dataRoot;
13
+ #getData;
14
+ #setData;
15
+ constructor(db, git, gitRoot, dataRoot, getData, setData) {
16
+ this.#db = db;
17
+ this.#git = git;
18
+ this.#gitRoot = gitRoot;
19
+ this.#dataRoot = dataRoot;
20
+ this.#getData = getData;
21
+ this.#setData = setData;
22
+ }
23
+ /**
24
+ * Runs a `git status` operation and updates the in-memory database with the latest statuses.
25
+ *
26
+ * @throws {HTTPError} when no git repository is found
27
+ */
28
+ async status() {
29
+ debug("status");
30
+ const status = await this.#git.status();
31
+ const data = attachGitStatusToDatabaseInMemory(this.#getData(), this.#dataRoot, this.#gitRoot, status);
32
+ this.#setData(data);
33
+ return {
34
+ currentBranch: status.current,
35
+ trackingBranch: status.tracking,
36
+ commitsAhead: status.ahead,
37
+ commitsBehind: status.behind,
38
+ instances: getAllInstanceOverviewsByEntityName(this.#db.getInstanceContainerOfEntityById.bind(this.#db), this.#db.getAllChildInstanceContainersForParent.bind(this.#db), this.#db.schema.getEntity.bind(this.#db.schema), data, this.#db.locales),
39
+ latestCommit: await this.#git.revparse(["HEAD"]),
40
+ };
41
+ }
42
+ /**
43
+ * Fetches updates from remote git repositories.
44
+ *
45
+ * @throws {HTTPError} when no git repository is found
46
+ */
47
+ async fetch(options) {
48
+ debug("fetch");
49
+ const args = {
50
+ all: options.all ? "--all" : undefined,
51
+ prune: options.prune ? "-p" : undefined,
52
+ };
53
+ await this.#git.fetch(Object.values(args));
54
+ }
55
+ /**
56
+ * Adds files to the git staging area.
57
+ *
58
+ * - If the function is called without parameters, all changes to all instances are added.
59
+ * - If the function is called with only the `entity` parameter, all changes to instances of that entity are added.
60
+ * - If both `entity` and `id` parameters are provided, only the changes to that specific instance are added.
61
+ *
62
+ * @throws {HTTPError} when no git repository is found
63
+ */
64
+ async stage(entity, id) {
65
+ if (entity) {
66
+ if (id) {
67
+ debug("add instance %s of entity %s", id, entity);
68
+ await this.#git.add(join(this.#dataRoot, entity, getFileNameForId(id)));
69
+ }
70
+ else {
71
+ debug("add all instances of entity %s", entity);
72
+ await this.#git.add(join(this.#dataRoot, entity));
73
+ }
74
+ }
75
+ else {
76
+ debug("add all instances");
77
+ await this.#git.add(this.#dataRoot);
78
+ }
79
+ }
80
+ /**
81
+ * Removes files from the git staging area.
82
+ *
83
+ * - If the function is called without parameters, all changes to all instances are removed.
84
+ * - If the function is called with only the `entity` parameter, all changes to instances of that entity are removed.
85
+ * - If both `entity` and `id` parameters are provided, only the changes to that specific instance are removed.
86
+ *
87
+ * @throws {HTTPError} when no git repository is found
88
+ */
89
+ async unstage(entity, id) {
90
+ if (entity) {
91
+ if (id) {
92
+ debug("reset HEAD instance %s of entity %s", id, entity);
93
+ await this.#git.reset(["HEAD", "--", join(this.#dataRoot, entity, getFileNameForId(id))]);
94
+ }
95
+ else {
96
+ debug("reset HEAD all instances of entity %s", entity);
97
+ await this.#git.reset(["HEAD", "--", join(this.#dataRoot, entity)]);
98
+ }
99
+ }
100
+ else {
101
+ debug("reset HEAD all instances");
102
+ await this.#git.reset(["HEAD", "--", this.#dataRoot]);
103
+ }
104
+ }
105
+ /**
106
+ * Restores files in the git repository to their last committed state.
107
+ *
108
+ * - If the function is called without parameters, all changes to all instances are restored.
109
+ * - If the function is called with only the `entity` parameter, all changes to instances of that entity are restored.
110
+ * - If both `entity` and `id` parameters are provided, only the changes to that specific instance are restored.
111
+ *
112
+ * @throws {HTTPError} when no git repository is found
113
+ */
114
+ async restore(entity, id) {
115
+ if (entity) {
116
+ if (id) {
117
+ debug("restore instance %s of entity %s", id, entity);
118
+ await this.#git.raw(["restore", "--", join(this.#dataRoot, entity, getFileNameForId(id))]);
119
+ }
120
+ else {
121
+ debug("restore all instances of entity %s", entity);
122
+ await this.#git.raw(["restore", "--", join(this.#dataRoot, entity)]);
123
+ }
124
+ }
125
+ else {
126
+ debug("restore all instances");
127
+ await this.#git.raw(["restore", "--", this.#dataRoot]);
128
+ }
129
+ await this.#db.sync();
130
+ }
131
+ /**
132
+ * Commits staged changes to the git repository with the provided commit message.
133
+ *
134
+ * @throws {HTTPError} when no git repository is found
135
+ * @throws {HTTPError} when the commit message is empty
136
+ */
137
+ async commit(message) {
138
+ debug("commit");
139
+ if (message.trim().length === 0) {
140
+ throw new HTTPError(400, "Commit message cannot be empty.");
141
+ }
142
+ await this.#git.commit(message);
143
+ }
144
+ /**
145
+ * Pushes committed changes to the remote git repository.
146
+ *
147
+ * @throws {HTTPError} when no git repository is found
148
+ */
149
+ async push() {
150
+ debug("push");
151
+ const status = await this.#git.status();
152
+ const remotes = await this.#git.getRemotes();
153
+ await this.#git.push(remotes[0]?.name, status.current ?? undefined);
154
+ }
155
+ /**
156
+ * Pulls changes from the remote git repository.
157
+ *
158
+ * @throws {HTTPError} when no git repository is found
159
+ */
160
+ async pull() {
161
+ debug("pull");
162
+ const status = await this.#git.status();
163
+ const remotes = await this.#git.getRemotes();
164
+ await this.#git.pull(remotes[0]?.name, status.current ?? undefined);
165
+ await this.#db.sync();
166
+ }
167
+ /**
168
+ * Retrieves information about the branches in the git repository.
169
+ */
170
+ async branch() {
171
+ debug("branch");
172
+ const branchSummary = await this.#git.branch();
173
+ return {
174
+ allBranches: branchSummary.all,
175
+ currentBranch: branchSummary.current,
176
+ isDetached: branchSummary.detached,
177
+ branches: branchSummary.branches,
178
+ };
179
+ }
180
+ /**
181
+ * Creates a new branch in the git repository and checks it out.
182
+ *
183
+ * @param branchName The name of the branch to create.
184
+ * @throws {HTTPError} when no git repository is found
185
+ */
186
+ async createBranch(branchName) {
187
+ debug("create branch %s", branchName);
188
+ await this.#git.checkoutLocalBranch(branchName);
189
+ }
190
+ /**
191
+ * Switches to an existing branch in the git repository.
192
+ *
193
+ * @param branchName The name of the branch to switch to.
194
+ * @throws {HTTPError} when no git repository is found
195
+ */
196
+ async switchBranch(branchName) {
197
+ debug("switch branch %s", branchName);
198
+ await this.#git.raw("switch", branchName);
199
+ await this.#db.sync();
200
+ }
201
+ /**
202
+ * Deletes a branch in the git repository.
203
+ *
204
+ * @param branchName The name of the branch to delete.
205
+ * @param options Options for deleting the branch.
206
+ * @throws {HTTPError} when no git repository is found
207
+ * @throws {HTTPError} when trying to delete the currently checked out branch
208
+ */
209
+ async deleteBranch(branchName, options) {
210
+ debug("delete branch %s", branchName);
211
+ const branchSummary = await this.#git.branchLocal();
212
+ if (branchSummary.current === branchName) {
213
+ throw new HTTPError(400, "Cannot delete the branch currently checked out.");
214
+ }
215
+ await this.#git.deleteLocalBranch(branchName, options.force ?? false);
216
+ }
217
+ /**
218
+ * Run a custom git operation.
219
+ *
220
+ * @param commands The git commands to run as an array of strings.
221
+ * @param commandMayChangeDataOnDisk Whether the command may change data on disk and thus requires a sync afterwards.
222
+ * @returns The raw output of the git command.
223
+ * @throws {HTTPError} when no git repository is found
224
+ */
225
+ async raw(commands, commandMayChangeDataOnDisk) {
226
+ debug("raw %O", commands);
227
+ const result = await this.#git.raw(...commands);
228
+ if (commandMayChangeDataOnDisk) {
229
+ await this.#db.sync();
230
+ }
231
+ return result;
232
+ }
233
+ }