@trebired/git-host 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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +451 -0
- package/dist/api/handler/action.d.ts +5 -0
- package/dist/api/handler/action.d.ts.map +1 -0
- package/dist/api/handler/action.js +47 -0
- package/dist/api/handler/action.js.map +1 -0
- package/dist/api/handler/response.d.ts +34 -0
- package/dist/api/handler/response.d.ts.map +1 -0
- package/dist/api/handler/response.js +87 -0
- package/dist/api/handler/response.js.map +1 -0
- package/dist/api/handler/route.d.ts +15 -0
- package/dist/api/handler/route.d.ts.map +1 -0
- package/dist/api/handler/route.js +51 -0
- package/dist/api/handler/route.js.map +1 -0
- package/dist/api/handler.d.ts +6 -0
- package/dist/api/handler.d.ts.map +1 -0
- package/dist/api/handler.js +117 -0
- package/dist/api/handler.js.map +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/create_git_host/branch_methods.d.ts +5 -0
- package/dist/core/create_git_host/branch_methods.d.ts.map +1 -0
- package/dist/core/create_git_host/branch_methods.js +137 -0
- package/dist/core/create_git_host/branch_methods.js.map +1 -0
- package/dist/core/create_git_host/content_methods.d.ts +5 -0
- package/dist/core/create_git_host/content_methods.d.ts.map +1 -0
- package/dist/core/create_git_host/content_methods.js +24 -0
- package/dist/core/create_git_host/content_methods.js.map +1 -0
- package/dist/core/create_git_host/remote_methods.d.ts +5 -0
- package/dist/core/create_git_host/remote_methods.d.ts.map +1 -0
- package/dist/core/create_git_host/remote_methods.js +48 -0
- package/dist/core/create_git_host/remote_methods.js.map +1 -0
- package/dist/core/create_git_host/shared.d.ts +21 -0
- package/dist/core/create_git_host/shared.d.ts.map +1 -0
- package/dist/core/create_git_host/shared.js +17 -0
- package/dist/core/create_git_host/shared.js.map +1 -0
- package/dist/core/create_git_host/working_tree_methods.d.ts +5 -0
- package/dist/core/create_git_host/working_tree_methods.d.ts.map +1 -0
- package/dist/core/create_git_host/working_tree_methods.js +63 -0
- package/dist/core/create_git_host/working_tree_methods.js.map +1 -0
- package/dist/core/create_git_host.d.ts +4 -0
- package/dist/core/create_git_host.d.ts.map +1 -0
- package/dist/core/create_git_host.js +167 -0
- package/dist/core/create_git_host.js.map +1 -0
- package/dist/core/inspect/helpers.d.ts +27 -0
- package/dist/core/inspect/helpers.d.ts.map +1 -0
- package/dist/core/inspect/helpers.js +86 -0
- package/dist/core/inspect/helpers.js.map +1 -0
- package/dist/core/inspect.d.ts +17 -0
- package/dist/core/inspect.d.ts.map +1 -0
- package/dist/core/inspect.js +174 -0
- package/dist/core/inspect.js.map +1 -0
- package/dist/core/locks.d.ts +5 -0
- package/dist/core/locks.d.ts.map +1 -0
- package/dist/core/locks.js +27 -0
- package/dist/core/locks.js.map +1 -0
- package/dist/core/operation_state.d.ts +4 -0
- package/dist/core/operation_state.d.ts.map +1 -0
- package/dist/core/operation_state.js +51 -0
- package/dist/core/operation_state.js.map +1 -0
- package/dist/core/remote.d.ts +13 -0
- package/dist/core/remote.d.ts.map +1 -0
- package/dist/core/remote.js +187 -0
- package/dist/core/remote.js.map +1 -0
- package/dist/core/repository/parsers.d.ts +12 -0
- package/dist/core/repository/parsers.d.ts.map +1 -0
- package/dist/core/repository/parsers.js +181 -0
- package/dist/core/repository/parsers.js.map +1 -0
- package/dist/core/repository.d.ts +11 -0
- package/dist/core/repository.d.ts.map +1 -0
- package/dist/core/repository.js +81 -0
- package/dist/core/repository.js.map +1 -0
- package/dist/core/run_git/env.d.ts +7 -0
- package/dist/core/run_git/env.d.ts.map +1 -0
- package/dist/core/run_git/env.js +18 -0
- package/dist/core/run_git/env.js.map +1 -0
- package/dist/core/run_git/process.d.ts +13 -0
- package/dist/core/run_git/process.d.ts.map +1 -0
- package/dist/core/run_git/process.js +96 -0
- package/dist/core/run_git/process.js.map +1 -0
- package/dist/core/run_git/repository_setup.d.ts +20 -0
- package/dist/core/run_git/repository_setup.d.ts.map +1 -0
- package/dist/core/run_git/repository_setup.js +116 -0
- package/dist/core/run_git/repository_setup.js.map +1 -0
- package/dist/core/run_git.d.ts +6 -0
- package/dist/core/run_git.d.ts.map +1 -0
- package/dist/core/run_git.js +6 -0
- package/dist/core/run_git.js.map +1 -0
- package/dist/core/working_tree/mutate.d.ts +9 -0
- package/dist/core/working_tree/mutate.d.ts.map +1 -0
- package/dist/core/working_tree/mutate.js +159 -0
- package/dist/core/working_tree/mutate.js.map +1 -0
- package/dist/core/working_tree/read.d.ts +6 -0
- package/dist/core/working_tree/read.d.ts.map +1 -0
- package/dist/core/working_tree/read.js +108 -0
- package/dist/core/working_tree/read.js.map +1 -0
- package/dist/core/working_tree/shared.d.ts +20 -0
- package/dist/core/working_tree/shared.d.ts.map +1 -0
- package/dist/core/working_tree/shared.js +87 -0
- package/dist/core/working_tree/shared.js.map +1 -0
- package/dist/core/working_tree.d.ts +3 -0
- package/dist/core/working_tree.d.ts.map +1 -0
- package/dist/core/working_tree.js +3 -0
- package/dist/core/working_tree.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/errors.js.map +1 -0
- package/dist/http/handler/helpers.d.ts +35 -0
- package/dist/http/handler/helpers.d.ts.map +1 -0
- package/dist/http/handler/helpers.js +146 -0
- package/dist/http/handler/helpers.js.map +1 -0
- package/dist/http/handler.d.ts +6 -0
- package/dist/http/handler.d.ts.map +1 -0
- package/dist/http/handler.js +208 -0
- package/dist/http/handler.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +4 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +134 -0
- package/dist/logging.js.map +1 -0
- package/dist/react/client/error.d.ts +14 -0
- package/dist/react/client/error.d.ts.map +1 -0
- package/dist/react/client/error.js +32 -0
- package/dist/react/client/error.js.map +1 -0
- package/dist/react/client/helpers.d.ts +12 -0
- package/dist/react/client/helpers.d.ts.map +1 -0
- package/dist/react/client/helpers.js +45 -0
- package/dist/react/client/helpers.js.map +1 -0
- package/dist/react/client/types.d.ts +61 -0
- package/dist/react/client/types.d.ts.map +1 -0
- package/dist/react/client/types.js +2 -0
- package/dist/react/client/types.js.map +1 -0
- package/dist/react/client.d.ts +6 -0
- package/dist/react/client.d.ts.map +1 -0
- package/dist/react/client.js +115 -0
- package/dist/react/client.js.map +1 -0
- package/dist/react/hooks/query.d.ts +10 -0
- package/dist/react/hooks/query.d.ts.map +1 -0
- package/dist/react/hooks/query.js +74 -0
- package/dist/react/hooks/query.js.map +1 -0
- package/dist/react/hooks/resources.d.ts +11 -0
- package/dist/react/hooks/resources.d.ts.map +1 -0
- package/dist/react/hooks/resources.js +113 -0
- package/dist/react/hooks/resources.js.map +1 -0
- package/dist/react/hooks/types.d.ts +40 -0
- package/dist/react/hooks/types.d.ts.map +1 -0
- package/dist/react/hooks/types.js +2 -0
- package/dist/react/hooks/types.js.map +1 -0
- package/dist/react/hooks.d.ts +4 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +3 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +5 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +3 -0
- package/dist/react/index.js.map +1 -0
- package/dist/ssh/keys.d.ts +18 -0
- package/dist/ssh/keys.d.ts.map +1 -0
- package/dist/ssh/keys.js +41 -0
- package/dist/ssh/keys.js.map +1 -0
- package/dist/ssh/server/audit.d.ts +5 -0
- package/dist/ssh/server/audit.d.ts.map +1 -0
- package/dist/ssh/server/audit.js +21 -0
- package/dist/ssh/server/audit.js.map +1 -0
- package/dist/ssh/server/shared.d.ts +30 -0
- package/dist/ssh/server/shared.d.ts.map +1 -0
- package/dist/ssh/server/shared.js +93 -0
- package/dist/ssh/server/shared.js.map +1 -0
- package/dist/ssh/server.d.ts +5 -0
- package/dist/ssh/server.d.ts.map +1 -0
- package/dist/ssh/server.js +265 -0
- package/dist/ssh/server.js.map +1 -0
- package/dist/types/common.d.ts +32 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +2 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/host.d.ts +142 -0
- package/dist/types/host.d.ts.map +1 -0
- package/dist/types/host.js +2 -0
- package/dist/types/host.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/repository.d.ts +163 -0
- package/dist/types/repository.d.ts.map +1 -0
- package/dist/types/repository.js +2 -0
- package/dist/types/repository.js.map +1 -0
- package/dist/types/transports.d.ts +156 -0
- package/dist/types/transports.d.ts.map +1 -0
- package/dist/types/transports.js +2 -0
- package/dist/types/transports.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +51 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/text.d.ts +4 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +10 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +81 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@trebired/git-host` will be documented here.
|
|
4
|
+
|
|
5
|
+
This project follows semantic versioning once published.
|
|
6
|
+
|
|
7
|
+
## 0.1.0
|
|
8
|
+
|
|
9
|
+
- Added the initial `@trebired/git-host` package scaffold with publishable metadata, README, MIT license, contribution guide, and TypeScript build setup.
|
|
10
|
+
- Added a reusable core `createGitHost()` API for worktree-backed repositories with real Git CLI execution, repository summary reads, tree and blob inspection, commit and ref comparison reads, working-tree mutation helpers, branch operations, checkout, remote fetch/pull/push helpers, and per-repository mutation locking.
|
|
11
|
+
- Added `createGitHttpHandler()` for plain Node smart HTTP hosting with host-owned repository resolution and optional authorization hooks.
|
|
12
|
+
- Added `createGitSshServer()` for plain Node SSH Git hosting with host-owned public key authentication, repository resolution, authorization hooks, and Git-only command execution.
|
|
13
|
+
- Added `createGitApiHandler()` for plain Node JSON API routing over repository summaries, branches, commits, trees, blobs, and diffs.
|
|
14
|
+
- Added `@trebired/git-host/react` as an optional React companion with a typed JSON API client, provider, and headless data hooks.
|
|
15
|
+
- Added hosted transport identity and audit hook support for smart HTTP and SSH adapters.
|
|
16
|
+
- Added `checkoutRef()`, `readStagedFile()`, and `readUnstagedFile()` to the core host API for detached ref checkout and pre-commit file inspection.
|
|
17
|
+
- Added host-owned remote transport auth ergonomics for clone, fetch, pull, and push through `remoteCredentials`, `httpHeaders`, and `sshCommand` options.
|
|
18
|
+
- Added SSH key utilities for generation, normalization, comparison, and fingerprinting.
|
|
19
|
+
- Added `@trebired/logger`-style logger support across the main git-host entrypoints with optional verbose diagnostics.
|
|
20
|
+
- Added hosted repository config defaults so worktree-backed repositories can accept smart HTTP push updates through the checked-out branch.
|
|
21
|
+
- Added tests covering repository init, clone, summary reads, tree and blob reads, staged and unstaged file reads, commit detail reads, working-tree staging and commit flows, branch operations, checkout, detached ref checkout, ref comparison, fetch/pull/push helpers, authenticated HTTP remote sync, operation continue and abort, JSON API reads, smart HTTP clone/push, smart HTTP auth hooks, SSH clone/push, SSH audit hooks, locking, and path rejection.
|
|
22
|
+
- Initial public release.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Miroslav M. and Trebired contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# @trebired/git-host
|
|
2
|
+
|
|
3
|
+
Embeddable Git host for Node.js and Bun apps.
|
|
4
|
+
|
|
5
|
+
`@trebired/git-host` gives your app real Git repository operations and real Git transports without making you adopt a full forge product. It runs the real Git CLI, helps you resolve repository paths safely, serializes mutations per repository, and exposes a reusable API for repository initialization, summary reads, content inspection, branch operations, working-tree changes, remote sync helpers, JSON API handlers, and smart HTTP and SSH hosting.
|
|
6
|
+
|
|
7
|
+
It is aimed at platforms and products that already own users, permissions, tokens, repository records, and UI, but want to stop hand-rolling the Git layer underneath all of that.
|
|
8
|
+
|
|
9
|
+
The package keeps auth, permission, and persistence decisions host-owned while giving you reusable Git behavior and transport adapters.
|
|
10
|
+
|
|
11
|
+
It also exposes an optional React companion at `@trebired/git-host/react` for typed API clients, providers, and headless data hooks on top of the JSON API.
|
|
12
|
+
|
|
13
|
+
In plain terms:
|
|
14
|
+
|
|
15
|
+
- it is a Git hosting layer you embed into your app
|
|
16
|
+
- it is not a full Git forge like GitLab, Gitea, or Forgejo
|
|
17
|
+
- it is not a reimplementation of Git
|
|
18
|
+
- it uses the real `git` binary for the hard parts
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
Runtime support: Bun 1+ and Node.js 18+.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install @trebired/git-host
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npm install @trebired/logger
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Optional React companion:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
npm install react
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createGitHost, resolveRepositoryPath } from "@trebired/git-host";
|
|
40
|
+
import { createLog } from "@trebired/logger";
|
|
41
|
+
|
|
42
|
+
const log = createLog({
|
|
43
|
+
console: true,
|
|
44
|
+
quiet: true,
|
|
45
|
+
save: false,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const repositoriesRoot = "/srv/git-workspaces";
|
|
49
|
+
|
|
50
|
+
const gitHost = createGitHost({
|
|
51
|
+
logger: log,
|
|
52
|
+
resolveRepository(repositoryId) {
|
|
53
|
+
return {
|
|
54
|
+
id: repositoryId,
|
|
55
|
+
path: resolveRepositoryPath({
|
|
56
|
+
rootDir: repositoriesRoot,
|
|
57
|
+
repositoryPath: `${repositoryId}/workspace`,
|
|
58
|
+
}),
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await gitHost.ensureRepository("demo", {
|
|
64
|
+
actor: {
|
|
65
|
+
name: "Alice",
|
|
66
|
+
email: "alice@example.com",
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const summary = await gitHost.readSummary("demo");
|
|
71
|
+
console.log(summary.repository.current_branch);
|
|
72
|
+
|
|
73
|
+
const workingTree = await gitHost.readWorkingTree("demo");
|
|
74
|
+
console.log(workingTree.unstaged_entries);
|
|
75
|
+
|
|
76
|
+
await gitHost.fetch("demo", {
|
|
77
|
+
remoteCredentials: {
|
|
78
|
+
username: "git-user",
|
|
79
|
+
password: process.env.GIT_TOKEN || "",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Smart HTTP hosting:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { createServer } from "node:http";
|
|
88
|
+
import { createGitHost, createGitHttpHandler } from "@trebired/git-host";
|
|
89
|
+
import { createLog } from "@trebired/logger";
|
|
90
|
+
|
|
91
|
+
const log = createLog({
|
|
92
|
+
console: true,
|
|
93
|
+
quiet: true,
|
|
94
|
+
save: false,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const gitHost = createGitHost({
|
|
98
|
+
resolveRepository(repositoryId) {
|
|
99
|
+
return {
|
|
100
|
+
id: repositoryId,
|
|
101
|
+
path: `/srv/git-workspaces/${repositoryId}/workspace`,
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const server = createServer(createGitHttpHandler({
|
|
107
|
+
basePath: "/git",
|
|
108
|
+
logger: log,
|
|
109
|
+
resolveRepository(repositoryKey) {
|
|
110
|
+
return {
|
|
111
|
+
id: repositoryKey,
|
|
112
|
+
path: `/srv/git-workspaces/${repositoryKey}/workspace`,
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
server.listen(3000);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Then clients can use:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
git clone http://127.0.0.1:3000/git/demo.git
|
|
124
|
+
git push
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
SSH hosting:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { createGitSshServer } from "@trebired/git-host";
|
|
131
|
+
import { createLog } from "@trebired/logger";
|
|
132
|
+
|
|
133
|
+
const log = createLog({
|
|
134
|
+
console: true,
|
|
135
|
+
quiet: true,
|
|
136
|
+
save: false,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const sshServer = createGitSshServer({
|
|
140
|
+
hostKeys: [hostPrivateKeyPem],
|
|
141
|
+
logger: log,
|
|
142
|
+
authenticate({ publicKey, username }) {
|
|
143
|
+
if (username !== "git") return null;
|
|
144
|
+
const account = findAccountBySshPublicKey(publicKey);
|
|
145
|
+
if (!account) return null;
|
|
146
|
+
return {
|
|
147
|
+
publicKey: account.publicKey,
|
|
148
|
+
remoteUser: account.username,
|
|
149
|
+
identity: account,
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
resolveRepository(repositoryKey) {
|
|
153
|
+
return {
|
|
154
|
+
id: repositoryKey,
|
|
155
|
+
path: `/srv/git-workspaces/${repositoryKey}/workspace`,
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
sshServer.listen(2222, "0.0.0.0");
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Then clients can use:
|
|
164
|
+
|
|
165
|
+
```sh
|
|
166
|
+
git clone ssh://git@127.0.0.1:2222/demo.git
|
|
167
|
+
git push
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
JSON API hosting:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { createServer } from "node:http";
|
|
174
|
+
import { createGitApiHandler, createGitHost } from "@trebired/git-host";
|
|
175
|
+
import { createLog } from "@trebired/logger";
|
|
176
|
+
|
|
177
|
+
const log = createLog({
|
|
178
|
+
console: true,
|
|
179
|
+
quiet: true,
|
|
180
|
+
save: false,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const gitHost = createGitHost({
|
|
184
|
+
resolveRepository(repositoryId) {
|
|
185
|
+
return {
|
|
186
|
+
id: repositoryId,
|
|
187
|
+
path: `/srv/git-workspaces/${repositoryId}/workspace`,
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const apiServer = createServer(createGitApiHandler({
|
|
193
|
+
basePath: "/api/git",
|
|
194
|
+
gitHost,
|
|
195
|
+
logger: log,
|
|
196
|
+
authorize({ action, repositoryId }) {
|
|
197
|
+
return canReadRepository(repositoryId, action);
|
|
198
|
+
},
|
|
199
|
+
}));
|
|
200
|
+
|
|
201
|
+
apiServer.listen(3100);
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Then apps can use routes like:
|
|
205
|
+
|
|
206
|
+
```txt
|
|
207
|
+
GET /api/git/repositories/demo/summary
|
|
208
|
+
GET /api/git/repositories/demo/branches
|
|
209
|
+
GET /api/git/repositories/demo/commits?limit=20
|
|
210
|
+
GET /api/git/repositories/demo/commits/<commit-ref>
|
|
211
|
+
GET /api/git/repositories/demo/tree?ref=HEAD&path=src
|
|
212
|
+
GET /api/git/repositories/demo/blob?ref=HEAD&path=README.md
|
|
213
|
+
GET /api/git/repositories/demo/diff?baseRef=main&headRef=feature%2Fx
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
React companion:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { createGitApiClient, GitApiClientProvider, useGitRepositorySummary } from "@trebired/git-host/react";
|
|
220
|
+
|
|
221
|
+
const gitClient = createGitApiClient({
|
|
222
|
+
baseUrl: "/api/git",
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
function RepositorySummaryCard() {
|
|
226
|
+
const summary = useGitRepositorySummary("demo");
|
|
227
|
+
|
|
228
|
+
if (summary.loading) return "Loading...";
|
|
229
|
+
if (summary.error) return summary.error.message;
|
|
230
|
+
if (!summary.data) return "Missing repository";
|
|
231
|
+
|
|
232
|
+
return `${summary.data.repository.current_branch} @ ${summary.data.repository.head_short}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function App() {
|
|
236
|
+
return (
|
|
237
|
+
<GitApiClientProvider client={gitClient}>
|
|
238
|
+
<RepositorySummaryCard />
|
|
239
|
+
</GitApiClientProvider>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The React entry is intentionally headless. It helps apps fetch and mutate Git data consistently, but it does not ship a bundled styled UI.
|
|
245
|
+
|
|
246
|
+
## Current API
|
|
247
|
+
|
|
248
|
+
The first public slice is intentionally small:
|
|
249
|
+
|
|
250
|
+
- `createGitHost()`
|
|
251
|
+
- `resolveRepositoryPath()`
|
|
252
|
+
- `runGit()`
|
|
253
|
+
- `buildGitEnv()`
|
|
254
|
+
- `RepositoryLockManager`
|
|
255
|
+
- `createGitApiHandler()`
|
|
256
|
+
- `createGitHttpHandler()`
|
|
257
|
+
- `generateSshKeyPair()`
|
|
258
|
+
- `normalizeSshPublicKey()`
|
|
259
|
+
- `compareSshPublicKeys()`
|
|
260
|
+
- `fingerprintSshPublicKey()`
|
|
261
|
+
- `createGitSshServer()`
|
|
262
|
+
- `@trebired/git-host/react`
|
|
263
|
+
|
|
264
|
+
And the main host instance methods:
|
|
265
|
+
|
|
266
|
+
- `ensureRepository()`
|
|
267
|
+
- `readSummary()`
|
|
268
|
+
- `listBranches()`
|
|
269
|
+
- `listCommits()`
|
|
270
|
+
- `listTree()`
|
|
271
|
+
- `readBlob()`
|
|
272
|
+
- `readCommit()`
|
|
273
|
+
- `diff()`
|
|
274
|
+
- `readWorkingTree()`
|
|
275
|
+
- `readStagedFile()`
|
|
276
|
+
- `readUnstagedFile()`
|
|
277
|
+
- `createBranch()`
|
|
278
|
+
- `checkoutBranch()`
|
|
279
|
+
- `checkoutRef()`
|
|
280
|
+
- `deleteBranch()`
|
|
281
|
+
- `stagePaths()`
|
|
282
|
+
- `unstagePaths()`
|
|
283
|
+
- `discardPaths()`
|
|
284
|
+
- `commit()`
|
|
285
|
+
- `continueOperation()`
|
|
286
|
+
- `abortOperation()`
|
|
287
|
+
- `fetch()`
|
|
288
|
+
- `pull()`
|
|
289
|
+
- `push()`
|
|
290
|
+
- `withRepositoryLock()`
|
|
291
|
+
|
|
292
|
+
The React entry currently exports:
|
|
293
|
+
|
|
294
|
+
- `createGitApiClient()`
|
|
295
|
+
- `GitApiClientProvider`
|
|
296
|
+
- `useGitRepositorySummary()`
|
|
297
|
+
- `useGitBranches()`
|
|
298
|
+
- `useGitCommits()`
|
|
299
|
+
- `useGitCommit()`
|
|
300
|
+
- `useGitTree()`
|
|
301
|
+
- `useGitBlob()`
|
|
302
|
+
- `useGitDiff()`
|
|
303
|
+
- `useGitApiQuery()`
|
|
304
|
+
|
|
305
|
+
## Repository Model
|
|
306
|
+
|
|
307
|
+
This package does not own your app database.
|
|
308
|
+
|
|
309
|
+
Your app resolves a repository id to an absolute repository path. The package then runs Git operations against that path. This keeps repository metadata, permissions, tokens, SSH keys, and UI decisions inside the host app where they belong.
|
|
310
|
+
|
|
311
|
+
The current public API is worktree-first because that keeps the reusable boundary compact and predictable.
|
|
312
|
+
|
|
313
|
+
Private remotes are still host-owned. The package now helps with the transport plumbing by supporting:
|
|
314
|
+
|
|
315
|
+
- `remoteCredentials` for clone, fetch, pull, and push
|
|
316
|
+
- `httpHeaders` for per-command HTTP headers such as bearer auth
|
|
317
|
+
- `sshCommand` for per-command SSH transport overrides
|
|
318
|
+
|
|
319
|
+
## Why This Package
|
|
320
|
+
|
|
321
|
+
Most alternatives fall into one of three buckets:
|
|
322
|
+
|
|
323
|
+
- full forge products such as GitLab, Gitea, or Forgejo
|
|
324
|
+
- Git implementation libraries that reimplement Git behavior in another runtime
|
|
325
|
+
- one-off app code that shells out to `git` without a reusable boundary
|
|
326
|
+
|
|
327
|
+
`@trebired/git-host` is aiming at the gap between those options.
|
|
328
|
+
|
|
329
|
+
Use it when you want:
|
|
330
|
+
|
|
331
|
+
- your app to keep owning users, permissions, tokens, SSH keys, repository records, and UI
|
|
332
|
+
- real Git behavior from the system `git` binary
|
|
333
|
+
- clone, fetch, pull, and push over smart HTTP and SSH
|
|
334
|
+
- a reusable Git runtime instead of spreading Git shell calls all over your platform code
|
|
335
|
+
- optional headless React helpers over the JSON API without coupling the core package to a UI framework
|
|
336
|
+
|
|
337
|
+
Do not use it when you want:
|
|
338
|
+
|
|
339
|
+
- a ready-made Git product with issues, pull requests, teams, admin screens, and built-in account management
|
|
340
|
+
- a pure JavaScript Git implementation with no `git` binary dependency
|
|
341
|
+
|
|
342
|
+
That makes it useful for internal developer platforms, product-specific source management, deployment systems, controlled automation environments, and apps that need Git as a capability rather than Git hosting as a separate product.
|
|
343
|
+
|
|
344
|
+
## Path Safety
|
|
345
|
+
|
|
346
|
+
Repository paths should never come straight from request input.
|
|
347
|
+
|
|
348
|
+
The intended flow is:
|
|
349
|
+
|
|
350
|
+
```txt
|
|
351
|
+
request repo id -> host app record lookup -> absolute repository path -> git-host
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`resolveRepositoryPath()` is provided as a safe join helper when your host app stores repository-relative paths under one known root.
|
|
355
|
+
|
|
356
|
+
## Hosted Transport Hooks
|
|
357
|
+
|
|
358
|
+
Hosted transports keep identity and permission policy in your app.
|
|
359
|
+
|
|
360
|
+
- `createGitHttpHandler()` supports host-owned repository resolution, optional identity resolution, permission checks, and request audit events.
|
|
361
|
+
- `createGitSshServer()` supports host-owned public key authentication, permission checks, and command audit events.
|
|
362
|
+
- `createGitApiHandler()` supports host-owned repository id mapping and per-route authorization.
|
|
363
|
+
- `generateSshKeyPair()`, `normalizeSshPublicKey()`, `compareSshPublicKeys()`, and `fingerprintSshPublicKey()` help host apps manage SSH transport setup without owning the parsing details themselves.
|
|
364
|
+
|
|
365
|
+
## Platform Fit
|
|
366
|
+
|
|
367
|
+
`@trebired/git-host` is a good fit when a larger platform already owns users, permissions, repository records, tokens, SSH keys, and UI, but wants to stop hand-rolling the reusable Git layer.
|
|
368
|
+
|
|
369
|
+
The package is meant to replace or simplify:
|
|
370
|
+
|
|
371
|
+
- Git CLI execution and environment shaping
|
|
372
|
+
- repository locking and mutation coordination
|
|
373
|
+
- repository summary, tree, blob, commit, diff, and working-tree reads
|
|
374
|
+
- branch, checkout, commit, fetch, pull, and push operations
|
|
375
|
+
- smart HTTP and SSH Git transport handling
|
|
376
|
+
- thin JSON API route internals around those Git operations
|
|
377
|
+
|
|
378
|
+
The host platform should still own:
|
|
379
|
+
|
|
380
|
+
- repository and source metadata persistence
|
|
381
|
+
- permission checks and route authorization policy
|
|
382
|
+
- access token issuance, revocation, and storage
|
|
383
|
+
- SSH key ownership, private key storage, and known-host persistence
|
|
384
|
+
- merge requests, reviews, UI flows, and other product-specific features
|
|
385
|
+
|
|
386
|
+
That boundary is where the package simplifies a platform the most without turning into a forge product of its own.
|
|
387
|
+
|
|
388
|
+
## Logger Support
|
|
389
|
+
|
|
390
|
+
`@trebired/git-host` works best with `@trebired/logger`, and that is the recommended logger.
|
|
391
|
+
|
|
392
|
+
Why we recommend it:
|
|
393
|
+
|
|
394
|
+
- it is simple
|
|
395
|
+
- it already matches git-host's expected method shape
|
|
396
|
+
- it keeps application logs and git-host diagnostics in one consistent format
|
|
397
|
+
|
|
398
|
+
The logger style:
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
log.info("git-host", "initializing repository", { repositoryId: "demo" });
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
comes from `@trebired/logger`.
|
|
405
|
+
|
|
406
|
+
You can pass that same `log` object into `createGitHost()`, `createGitHttpHandler()`, `createGitSshServer()`, and `createGitApiHandler()` through their `logger` option.
|
|
407
|
+
|
|
408
|
+
If you do not pass a logger and `@trebired/logger` is installed in the host app, git-host will create a quiet console-only logger automatically before falling back to raw `console`.
|
|
409
|
+
|
|
410
|
+
If you also set `verbose: true`, git-host will emit successful lifecycle and transport diagnostics through that logger. Without `verbose`, it stays much quieter and mainly reports rejected or failed operations.
|
|
411
|
+
|
|
412
|
+
Custom loggers can also use one of these shapes:
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
type Logger = {
|
|
416
|
+
info(group: string, message: string, metadata?: unknown): void;
|
|
417
|
+
warn(group: string, message: string, metadata?: unknown): void;
|
|
418
|
+
error(group: string, message: string, metadata?: unknown): void;
|
|
419
|
+
fail(group: string, message: string, metadata?: unknown): void;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
type Event = {
|
|
423
|
+
level: "info" | "warn" | "error" | "fail";
|
|
424
|
+
group: string;
|
|
425
|
+
message: string;
|
|
426
|
+
metadata?: unknown;
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
type EventLogger = (event: Event) => void;
|
|
430
|
+
|
|
431
|
+
type SinkLogger = {
|
|
432
|
+
log?(event: Event): void;
|
|
433
|
+
write?(event: Event): void;
|
|
434
|
+
fatal?(message: string, metadata?: unknown): void;
|
|
435
|
+
};
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Common logger objects such as `console`, pino-style level methods, or Winston-style sinks are also adapted as sensibly as possible.
|
|
439
|
+
|
|
440
|
+
If no logger is provided and `@trebired/logger` is not installed, git-host falls back to plain `console` output for its own diagnostics.
|
|
441
|
+
|
|
442
|
+
## Roadmap
|
|
443
|
+
|
|
444
|
+
The remaining package work is mostly convenience and hardening:
|
|
445
|
+
|
|
446
|
+
- thin Express wrappers when they stay truly thin
|
|
447
|
+
- broader examples for host-app integration patterns
|
|
448
|
+
|
|
449
|
+
## Contributing
|
|
450
|
+
|
|
451
|
+
See `CONTRIBUTING.md` for development commands and package guidelines.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CreateGitApiHandlerOptions } from "../../types.js";
|
|
2
|
+
import { parseGitApiRoute } from "./route.js";
|
|
3
|
+
declare function runGitApiAction(options: CreateGitApiHandlerOptions, route: ReturnType<typeof parseGitApiRoute>, repositoryId: string, searchParams: URLSearchParams): Promise<import("../../types.js").GitCommitDetail | import("../../types.js").GitCommitSummary[] | import("../../types.js").GitCompareSummary | import("../../types.js").GitBlob | import("../../types.js").GitBranchSummary[] | import("../../types.js").GitRepositorySummary | import("../../types.js").GitTreeEntry[]>;
|
|
4
|
+
export { runGitApiAction };
|
|
5
|
+
//# sourceMappingURL=action.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../../src/api/handler/action.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAGjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,iBAAe,eAAe,CAC5B,OAAO,EAAE,0BAA0B,EACnC,KAAK,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,EAC1C,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,eAAe,2TAwC9B;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { GitHostError } from "../../errors.js";
|
|
2
|
+
import { isTruthy, text } from "../../utils/text.js";
|
|
3
|
+
import { parsePositiveInt } from "./response.js";
|
|
4
|
+
async function runGitApiAction(options, route, repositoryId, searchParams) {
|
|
5
|
+
if (!route)
|
|
6
|
+
throw new GitHostError("git_command_failed", "API route is required.");
|
|
7
|
+
switch (route.action) {
|
|
8
|
+
case "summary":
|
|
9
|
+
return await options.gitHost.readSummary(repositoryId, {
|
|
10
|
+
commitLimit: parsePositiveInt(searchParams.get("commitLimit"), "commitLimit"),
|
|
11
|
+
});
|
|
12
|
+
case "branches":
|
|
13
|
+
return await options.gitHost.listBranches(repositoryId);
|
|
14
|
+
case "commits":
|
|
15
|
+
return await options.gitHost.listCommits(repositoryId, {
|
|
16
|
+
limit: parsePositiveInt(searchParams.get("limit"), "limit"),
|
|
17
|
+
});
|
|
18
|
+
case "commit":
|
|
19
|
+
return await options.gitHost.readCommit(repositoryId, route.commitRef);
|
|
20
|
+
case "tree":
|
|
21
|
+
return await options.gitHost.listTree(repositoryId, {
|
|
22
|
+
path: text(searchParams.get("path")),
|
|
23
|
+
recursive: isTruthy(searchParams.get("recursive")),
|
|
24
|
+
ref: text(searchParams.get("ref")),
|
|
25
|
+
});
|
|
26
|
+
case "blob": {
|
|
27
|
+
const blobPath = text(searchParams.get("path"));
|
|
28
|
+
if (!blobPath)
|
|
29
|
+
throw new GitHostError("invalid_repository_path", "blob path is required.");
|
|
30
|
+
return await options.gitHost.readBlob(repositoryId, {
|
|
31
|
+
path: blobPath,
|
|
32
|
+
ref: text(searchParams.get("ref")),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
case "diff": {
|
|
36
|
+
const baseRef = text(searchParams.get("baseRef"));
|
|
37
|
+
const headRef = text(searchParams.get("headRef"));
|
|
38
|
+
if (!baseRef || !headRef)
|
|
39
|
+
throw new GitHostError("git_command_failed", "baseRef and headRef are required.");
|
|
40
|
+
return await options.gitHost.diff(repositoryId, { baseRef, headRef });
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
throw new GitHostError("git_command_failed", "Unsupported Git API action.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export { runGitApiAction };
|
|
47
|
+
//# sourceMappingURL=action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action.js","sourceRoot":"","sources":["../../../src/api/handler/action.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,KAAK,UAAU,eAAe,CAC5B,OAAmC,EACnC,KAA0C,EAC1C,YAAoB,EACpB,YAA6B;IAE7B,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,YAAY,CAAC,oBAAoB,EAAE,wBAAwB,CAAC,CAAC;IAEnF,QAAQ,KAAK,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,YAAY,EAAE;gBACrD,WAAW,EAAE,gBAAgB,CAAC,YAAY,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;aAC9E,CAAC,CAAC;QACL,KAAK,UAAU;YACb,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QAC1D,KAAK,SAAS;YACZ,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,YAAY,EAAE;gBACrD,KAAK,EAAE,gBAAgB,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;aAC5D,CAAC,CAAC;QACL,KAAK,QAAQ;YACX,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACzE,KAAK,MAAM;YACT,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE;gBAClD,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACpC,SAAS,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBAClD,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;aACnC,CAAC,CAAC;QACL,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YAChD,IAAI,CAAC,QAAQ;gBAAE,MAAM,IAAI,YAAY,CAAC,yBAAyB,EAAE,wBAAwB,CAAC,CAAC;YAC3F,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,EAAE;gBAClD,IAAI,EAAE,QAAQ;gBACd,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;aACnC,CAAC,CAAC;QACL,CAAC;QACD,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;YAClD,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;YAClD,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,YAAY,CAAC,oBAAoB,EAAE,mCAAmC,CAAC,CAAC;YAC5G,OAAO,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACxE,CAAC;QACD;YACE,MAAM,IAAI,YAAY,CAAC,oBAAoB,EAAE,6BAA6B,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
import type { GitApiAuthorizationResult } from "../../types.js";
|
|
3
|
+
declare function applyAuthorizationHeaders(res: ServerResponse, headers: Record<string, string> | undefined): void;
|
|
4
|
+
declare function authorizationAllowed(value: GitApiAuthorizationResult | undefined): {
|
|
5
|
+
allowed: boolean;
|
|
6
|
+
status: number;
|
|
7
|
+
message: string;
|
|
8
|
+
headers?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
allowed: boolean;
|
|
11
|
+
headers: Record<string, string>;
|
|
12
|
+
message: string;
|
|
13
|
+
status: number;
|
|
14
|
+
};
|
|
15
|
+
declare function parsePositiveInt(value: string | null, name: string): number | undefined;
|
|
16
|
+
declare function statusForError(error: unknown): number;
|
|
17
|
+
declare function serializeError(error: unknown): {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
error: {
|
|
20
|
+
code: string;
|
|
21
|
+
details: Record<string, unknown>;
|
|
22
|
+
message: string;
|
|
23
|
+
};
|
|
24
|
+
} | {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
error: {
|
|
27
|
+
code: string;
|
|
28
|
+
message: string;
|
|
29
|
+
details?: undefined;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
declare function writeJson(req: IncomingMessage, res: ServerResponse, status: number, payload: unknown): void;
|
|
33
|
+
export { applyAuthorizationHeaders, authorizationAllowed, parsePositiveInt, serializeError, statusForError, writeJson };
|
|
34
|
+
//# sourceMappingURL=response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../src/api/handler/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAGhE,iBAAS,yBAAyB,CAAC,GAAG,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,QAMlG;AAED,iBAAS,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,GAAG,SAAS;;;;;;;;;;EAUzE;AAED,iBAAS,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOhF;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiB9C;AAED,iBAAS,cAAc,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;EA6BrC;AAED,iBAAS,SAAS,CAAC,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,QAQ7F;AAED,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { GitHostError, isGitHostError } from "../../errors.js";
|
|
2
|
+
import { text } from "../../utils/text.js";
|
|
3
|
+
function applyAuthorizationHeaders(res, headers) {
|
|
4
|
+
const nextHeaders = headers && typeof headers === "object" ? headers : {};
|
|
5
|
+
for (const [name, value] of Object.entries(nextHeaders)) {
|
|
6
|
+
if (!name || typeof value !== "string")
|
|
7
|
+
continue;
|
|
8
|
+
res.setHeader(name, value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function authorizationAllowed(value) {
|
|
12
|
+
if (value == null)
|
|
13
|
+
return { allowed: true, status: 200, message: "" };
|
|
14
|
+
if (typeof value === "boolean")
|
|
15
|
+
return { allowed: value, status: value ? 200 : 403, message: "" };
|
|
16
|
+
return {
|
|
17
|
+
allowed: value.allowed === true,
|
|
18
|
+
headers: value.headers,
|
|
19
|
+
message: text(value.message),
|
|
20
|
+
status: Number(value.status) || (value.allowed === true ? 200 : 403),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function parsePositiveInt(value, name) {
|
|
24
|
+
if (value == null || text(value) === "")
|
|
25
|
+
return undefined;
|
|
26
|
+
const next = Number(value);
|
|
27
|
+
if (!Number.isInteger(next) || next <= 0) {
|
|
28
|
+
throw new GitHostError("git_command_failed", `${name} must be a positive integer.`, { value });
|
|
29
|
+
}
|
|
30
|
+
return next;
|
|
31
|
+
}
|
|
32
|
+
function statusForError(error) {
|
|
33
|
+
if (isGitHostError(error)) {
|
|
34
|
+
switch (error.code) {
|
|
35
|
+
case "invalid_branch_name":
|
|
36
|
+
case "invalid_repository_path":
|
|
37
|
+
return 400;
|
|
38
|
+
case "repository_not_found":
|
|
39
|
+
return 404;
|
|
40
|
+
case "repository_not_initialized":
|
|
41
|
+
case "repository_clone_target_not_empty":
|
|
42
|
+
return 409;
|
|
43
|
+
default:
|
|
44
|
+
return 400;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return 500;
|
|
48
|
+
}
|
|
49
|
+
function serializeError(error) {
|
|
50
|
+
if (isGitHostError(error)) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
error: {
|
|
54
|
+
code: error.code,
|
|
55
|
+
details: error.details,
|
|
56
|
+
message: error.message,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (error instanceof Error) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: {
|
|
64
|
+
code: "internal_error",
|
|
65
|
+
message: error.message,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: {
|
|
72
|
+
code: "internal_error",
|
|
73
|
+
message: "Git API request failed.",
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function writeJson(req, res, status, payload) {
|
|
78
|
+
res.statusCode = status;
|
|
79
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
80
|
+
if (text(req.method).toUpperCase() === "HEAD") {
|
|
81
|
+
res.end();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
res.end(JSON.stringify(payload, null, 2));
|
|
85
|
+
}
|
|
86
|
+
export { applyAuthorizationHeaders, authorizationAllowed, parsePositiveInt, serializeError, statusForError, writeJson };
|
|
87
|
+
//# sourceMappingURL=response.js.map
|