@trebired/git-host 1.4.0 → 1.7.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 +21 -0
- package/README.md +398 -23
- package/dist/api/forge/handler.d.ts.map +1 -1
- package/dist/api/forge/handler.js +80 -1
- package/dist/api/forge/handler.js.map +1 -1
- package/dist/api/handler/action.js +1 -1
- package/dist/api/handler/action.js.map +1 -1
- package/dist/api/handler/archive.d.ts +14 -0
- package/dist/api/handler/archive.d.ts.map +1 -0
- package/dist/api/handler/archive.js +61 -0
- package/dist/api/handler/archive.js.map +1 -0
- package/dist/api/handler/response.d.ts.map +1 -1
- package/dist/api/handler/response.js +8 -0
- package/dist/api/handler/response.js.map +1 -1
- package/dist/api/handler/route.d.ts +11 -0
- package/dist/api/handler/route.d.ts.map +1 -1
- package/dist/api/handler/route.js +10 -0
- package/dist/api/handler/route.js.map +1 -1
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +29 -1
- package/dist/api/handler.js.map +1 -1
- package/dist/browser/index.d.ts +36 -15
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +622 -592
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/styles.css +75 -0
- package/dist/core/archive.d.ts +25 -0
- package/dist/core/archive.d.ts.map +1 -0
- package/dist/core/archive.js +419 -0
- package/dist/core/archive.js.map +1 -0
- package/dist/core/archive_cache_filesystem.d.ts +8 -0
- package/dist/core/archive_cache_filesystem.d.ts.map +1 -0
- package/dist/core/archive_cache_filesystem.js +158 -0
- package/dist/core/archive_cache_filesystem.js.map +1 -0
- package/dist/core/create_git_host/content_methods.d.ts +1 -1
- package/dist/core/create_git_host/content_methods.d.ts.map +1 -1
- package/dist/core/create_git_host/content_methods.js +14 -3
- package/dist/core/create_git_host/content_methods.js.map +1 -1
- package/dist/core/create_git_host/shared.d.ts +2 -0
- package/dist/core/create_git_host/shared.d.ts.map +1 -1
- package/dist/core/create_git_host/shared.js.map +1 -1
- package/dist/core/create_git_host.d.ts.map +1 -1
- package/dist/core/create_git_host.js +7 -0
- package/dist/core/create_git_host.js.map +1 -1
- package/dist/core/inspect.d.ts +1 -1
- package/dist/core/inspect.d.ts.map +1 -1
- package/dist/core/inspect.js +11 -26
- package/dist/core/inspect.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/react/client/types.d.ts +5 -2
- package/dist/react/client/types.d.ts.map +1 -1
- package/dist/react/client.d.ts.map +1 -1
- package/dist/react/client.js +14 -0
- package/dist/react/client.js.map +1 -1
- package/dist/react/components.d.ts +44412 -0
- package/dist/react/components.d.ts.map +1 -0
- package/dist/react/components.js +758 -0
- package/dist/react/components.js.map +1 -0
- package/dist/react/hooks/query.d.ts.map +1 -1
- package/dist/react/hooks/query.js +30 -5
- package/dist/react/hooks/query.js.map +1 -1
- package/dist/react/hooks/resources.d.ts.map +1 -1
- package/dist/react/hooks/resources.js +1 -1
- package/dist/react/hooks/resources.js.map +1 -1
- package/dist/react/hooks/types.d.ts +1 -1
- package/dist/react/hooks/types.d.ts.map +1 -1
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/ui/context.d.ts +1907 -0
- package/dist/react/ui/context.d.ts.map +1 -0
- package/dist/react/ui/context.js +186 -0
- package/dist/react/ui/context.js.map +1 -0
- package/dist/types/forge.d.ts +2 -1
- package/dist/types/forge.d.ts.map +1 -1
- package/dist/types/host.d.ts +51 -2
- package/dist/types/host.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/repository.d.ts +30 -7
- package/dist/types/repository.d.ts.map +1 -1
- package/dist/types/transports.d.ts +2 -1
- package/dist/types/transports.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to `@trebired/git-host` will be documented here.
|
|
|
4
4
|
|
|
5
5
|
This project follows semantic versioning once published.
|
|
6
6
|
|
|
7
|
+
## 1.7.0
|
|
8
|
+
|
|
9
|
+
- Added first-class host-managed source archives for branches, tags, commits, and forge releases through SHA-resolved `zipball` and `tarball` download routes backed by `git archive`.
|
|
10
|
+
- Added streamed archive generation with deterministic commit-root folders, pluggable archive cache backends, filesystem cache support, TTL cleanup, structured archive error codes, and archive request/cache/generation audit logs.
|
|
11
|
+
- Extended release and tag API/browser models with automatic source archive links that stay distinct from uploaded release assets.
|
|
12
|
+
|
|
13
|
+
## 1.6.0
|
|
14
|
+
|
|
15
|
+
- Made package styling optional instead of required for frontend integration by adding `unstyled` support across the shared repository UI provider and browser pages.
|
|
16
|
+
- Added slot-based frontend skinning through `theme.classNames`, `theme.slots`, stable `data-slot` markers, and exported slot helpers so host apps can keep their own design system while reusing git-host structure.
|
|
17
|
+
- Added render-state component overrides for loading, error, and empty states through `GitRepositoryUiProvider`.
|
|
18
|
+
- Updated the README with explicit package structure plus app styling guidance and examples that do not depend on `@trebired/git-host/browser/styles.css`.
|
|
19
|
+
|
|
20
|
+
## 1.5.0
|
|
21
|
+
|
|
22
|
+
- Expanded the frontend ownership model across `@trebired/git-host/browser` and `@trebired/git-host/react` with a package-owned repository shell, route adapter system, diagnostics hooks, theme/token support, and shared UI provider state.
|
|
23
|
+
- Added reusable React repository components and action primitives such as `GitRepositoryShell`, `GitRepositoryHeader`, `GitRepositoryTabs`, `GitCommitList`, `GitReleaseList`, `GitForkList`, `GitTreeView`, `GitBlobView`, `GitBranchSelector`, `GitTagSelector`, and repository action buttons.
|
|
24
|
+
- Extended the browser entry to ship a broader first-class repository page surface including branches, tags, search, blame, compare, and richer release flows in addition to the existing overview/code/commits/releases/forks/activity pages.
|
|
25
|
+
- Added query dedupe/caching plus frontend diagnostics hooks for fetch, action, render, empty-state, and navigation observation.
|
|
26
|
+
- Updated the README with explicit frontend integration modes and thinner host-app examples.
|
|
27
|
+
|
|
7
28
|
## 1.4.0
|
|
8
29
|
|
|
9
30
|
- Added a new forge layer through `createGitForge()` with host-owned storage adapters for releases, forks, stars, watching, and repository activity timelines.
|
package/README.md
CHANGED
|
@@ -104,9 +104,13 @@ const search = await gitHost.search("demo", {
|
|
|
104
104
|
});
|
|
105
105
|
const archive = await gitHost.readArchive("demo", {
|
|
106
106
|
ref: "main",
|
|
107
|
-
format: "
|
|
107
|
+
format: "tar.gz",
|
|
108
108
|
});
|
|
109
|
-
|
|
109
|
+
const archiveLinks = gitHost.resolveArchiveLinks("demo", {
|
|
110
|
+
basePath: "/api/git",
|
|
111
|
+
ref: "main",
|
|
112
|
+
});
|
|
113
|
+
console.log(tags.length, blame.lines[0]?.author_name, search.match_count, archive.file_name, archiveLinks.zip.href);
|
|
110
114
|
|
|
111
115
|
const inspectionTarget = await gitHost.resolveInspectionTarget("demo", {
|
|
112
116
|
ref: "auto",
|
|
@@ -306,42 +310,365 @@ GET /api/git/repositories/demo/linguist?ref=HEAD
|
|
|
306
310
|
GET /api/git/repositories/demo/blame?ref=HEAD&path=src/app.ts
|
|
307
311
|
GET /api/git/repositories/demo/search?ref=HEAD&path=src&query=value
|
|
308
312
|
GET /api/git/repositories/demo/archive?ref=HEAD&format=zip
|
|
313
|
+
GET /api/git/repositories/demo/archive?ref=HEAD&format=tar.gz
|
|
314
|
+
GET /api/git/repositories/demo/zipball/main
|
|
315
|
+
GET /api/git/repositories/demo/tarball/v1
|
|
309
316
|
GET /api/git/repositories/demo/blob?ref=HEAD&path=README.md
|
|
310
317
|
GET /api/git/repositories/demo/diff?baseRef=main&headRef=feature%2Fx&path=src
|
|
311
318
|
```
|
|
312
319
|
|
|
313
|
-
|
|
320
|
+
## Source Archives
|
|
321
|
+
|
|
322
|
+
git-host now exposes GitHub-style source archive downloads for branches, tags, commit SHAs, and forge releases.
|
|
323
|
+
|
|
324
|
+
- `zipball/:ref` streams a `zip` archive.
|
|
325
|
+
- `tarball/:ref` streams a `tar.gz` archive.
|
|
326
|
+
- `readArchive()` still returns a base64 payload for JSON-first consumers.
|
|
327
|
+
- `resolveArchiveLinks()` builds typed download URLs for branches, tags, releases, and commit SHAs.
|
|
328
|
+
|
|
329
|
+
Archives are always generated from Git objects with `git archive`, never from the checked-out working tree.
|
|
330
|
+
|
|
331
|
+
- Branch and tag downloads resolve to the current target commit first, then use a SHA-based cache key.
|
|
332
|
+
- Commit SHA downloads are the stable choice when you need predictable contents.
|
|
333
|
+
- The extracted root directory is commit-based, so the inner folder stays stable for a fixed commit even if the request came from a branch or tag name.
|
|
334
|
+
- The outer archive bytes are streamed and cached, but git-host does not promise byte-for-byte identical compression output forever unless you choose to enforce that at the cache/backend layer.
|
|
335
|
+
|
|
336
|
+
### Releases, Tags, and Assets
|
|
337
|
+
|
|
338
|
+
A forge release is metadata attached to a Git tag. Its automatic source archives are not uploaded assets.
|
|
339
|
+
|
|
340
|
+
- `release.assets` remains your uploaded files.
|
|
341
|
+
- `release.source_archives.zip` and `release.source_archives.tar_gz` point at the automatic downloads for the tagged source.
|
|
342
|
+
- Tag list/detail responses also expose `source_archives`.
|
|
343
|
+
- If a release points at a missing tag, the forge API returns `release_tag_not_found`.
|
|
344
|
+
|
|
345
|
+
### Cache Behavior
|
|
346
|
+
|
|
347
|
+
The built-in archive service prefers on-demand generation plus cache.
|
|
348
|
+
|
|
349
|
+
- Cache keys include repository id, resolved commit SHA, archive format, and the configured archive cache key version.
|
|
350
|
+
- Moving a tag or branch to a different commit produces a new cache key automatically.
|
|
351
|
+
- The default cache backend is filesystem-backed under the system temp directory.
|
|
352
|
+
- You can inject your own `archive.cache` backend, including future object-storage implementations.
|
|
353
|
+
- Cached entries carry TTL metadata and the filesystem backend performs periodic expired-entry cleanup.
|
|
354
|
+
|
|
355
|
+
### Security Notes
|
|
356
|
+
|
|
357
|
+
- Public repositories can expose `zipball` and `tarball` routes without auth.
|
|
358
|
+
- Private repositories should authorize before archive generation just like any other read route.
|
|
359
|
+
- The API handlers authorize archive downloads before they resolve refs or touch the cache.
|
|
360
|
+
- Cache backends can optionally mint redirect URLs for object storage, but the default filesystem backend streams directly from git-host.
|
|
361
|
+
|
|
362
|
+
## Frontend Integration
|
|
363
|
+
|
|
364
|
+
The package now supports three frontend integration modes.
|
|
365
|
+
|
|
366
|
+
### 1. Full Browser Pages
|
|
367
|
+
|
|
368
|
+
Use `@trebired/git-host/browser` when you want git-host to own the whole repository area:
|
|
369
|
+
|
|
370
|
+
- repository shell
|
|
371
|
+
- tabs and navigation behavior
|
|
372
|
+
- stats, actions, and social controls
|
|
373
|
+
- loading, error, retry, and empty states
|
|
374
|
+
- code browsing, blame, compare, releases, forks, activity, branches, tags, and search pages
|
|
375
|
+
|
|
376
|
+
Host apps mostly provide:
|
|
377
|
+
|
|
378
|
+
- auth
|
|
379
|
+
- API base URL
|
|
380
|
+
- repository key
|
|
381
|
+
- route adapter
|
|
382
|
+
- theme tokens
|
|
383
|
+
- optional policy and diagnostics hooks
|
|
384
|
+
|
|
385
|
+
Default styling is optional.
|
|
386
|
+
|
|
387
|
+
- If you want the package look, import `@trebired/git-host/browser/styles.css`.
|
|
388
|
+
- If you want package-owned structure with your own design system, skip that stylesheet and pass theme slots/classes.
|
|
314
389
|
|
|
315
390
|
```ts
|
|
316
|
-
import {
|
|
391
|
+
import {
|
|
392
|
+
GitRepositoryOverviewPage,
|
|
393
|
+
createGitRepositoryRouteAdapter,
|
|
394
|
+
} from "@trebired/git-host/browser";
|
|
317
395
|
|
|
318
|
-
const
|
|
319
|
-
|
|
396
|
+
const routeAdapter = createGitRepositoryRouteAdapter({
|
|
397
|
+
repositoryBasePath: "/workspaces",
|
|
320
398
|
});
|
|
321
399
|
|
|
322
|
-
function
|
|
323
|
-
|
|
324
|
-
|
|
400
|
+
function RepositoryScreen() {
|
|
401
|
+
return (
|
|
402
|
+
<GitRepositoryOverviewPage
|
|
403
|
+
baseUrl="/api/git"
|
|
404
|
+
repositoryKey="demo"
|
|
405
|
+
routeAdapter={routeAdapter}
|
|
406
|
+
branding={{
|
|
407
|
+
getCloneUrl(repositoryKey) {
|
|
408
|
+
return `https://git.example.com/${repositoryKey}.git`;
|
|
409
|
+
},
|
|
410
|
+
}}
|
|
411
|
+
unstyled
|
|
412
|
+
theme={{
|
|
413
|
+
classNames: {
|
|
414
|
+
page: "repo-page",
|
|
415
|
+
header: "repo-header",
|
|
416
|
+
title: "repo-title",
|
|
417
|
+
tabs: "repo-tabs",
|
|
418
|
+
"tab-link": "repo-tab",
|
|
419
|
+
card: "repo-card",
|
|
420
|
+
button: "repo-button",
|
|
421
|
+
},
|
|
422
|
+
slots: {
|
|
423
|
+
page: {
|
|
424
|
+
attributes: {
|
|
425
|
+
"data-repository-surface": "git-host",
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
},
|
|
429
|
+
variables: {
|
|
430
|
+
"--git-browser-accent": "#0a7f5a",
|
|
431
|
+
},
|
|
432
|
+
}}
|
|
433
|
+
/>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### 2. Hybrid Shell + Package Sections
|
|
325
439
|
|
|
326
|
-
|
|
327
|
-
if (summary.error) return summary.error.message;
|
|
328
|
-
if (!summary.data) return "Missing repository";
|
|
440
|
+
Use `@trebired/git-host/react` when your app owns the outer chrome but git-host should own the repository section UI patterns:
|
|
329
441
|
|
|
330
|
-
|
|
442
|
+
```ts
|
|
443
|
+
import {
|
|
444
|
+
GitApiClientProvider,
|
|
445
|
+
GitCommitList,
|
|
446
|
+
GitRepositoryShell,
|
|
447
|
+
GitRepositoryUiProvider,
|
|
448
|
+
createGitApiClient,
|
|
449
|
+
createGitRepositoryRouteAdapter,
|
|
450
|
+
useGitCommits,
|
|
451
|
+
useGitOverview,
|
|
452
|
+
} from "@trebired/git-host/react";
|
|
453
|
+
|
|
454
|
+
const client = createGitApiClient({ baseUrl: "/api/git" });
|
|
455
|
+
const routes = createGitRepositoryRouteAdapter({
|
|
456
|
+
repositoryBasePath: "/app/repos",
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
function RepositoryCommitsSection({ repositoryKey }: { repositoryKey: string }) {
|
|
460
|
+
const overview = useGitOverview(repositoryKey);
|
|
461
|
+
const commits = useGitCommits(repositoryKey, {
|
|
462
|
+
ref: overview.data?.repository.repository.current_branch,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
<GitRepositoryShell
|
|
467
|
+
page="commits"
|
|
468
|
+
repositoryKey={repositoryKey}
|
|
469
|
+
loading={overview.loading || commits.loading}
|
|
470
|
+
error={overview.error || commits.error}
|
|
471
|
+
social={overview.data?.social}
|
|
472
|
+
stats={[
|
|
473
|
+
{ label: "Branch", value: overview.data?.repository.repository.current_branch || "-" },
|
|
474
|
+
{ label: "Forks", value: String(overview.data?.fork_count || 0) },
|
|
475
|
+
]}
|
|
476
|
+
>
|
|
477
|
+
<GitCommitList commits={commits.data || []} repositoryKey={repositoryKey} />
|
|
478
|
+
</GitRepositoryShell>
|
|
479
|
+
);
|
|
331
480
|
}
|
|
332
481
|
|
|
333
482
|
function App() {
|
|
334
483
|
return (
|
|
335
|
-
<GitApiClientProvider client={
|
|
336
|
-
<
|
|
484
|
+
<GitApiClientProvider client={client}>
|
|
485
|
+
<GitRepositoryUiProvider
|
|
486
|
+
routeAdapter={routes}
|
|
487
|
+
theme={{
|
|
488
|
+
unstyled: true,
|
|
489
|
+
classNames: {
|
|
490
|
+
page: "repo-surface",
|
|
491
|
+
header: "repo-shell-header",
|
|
492
|
+
card: "repo-panel",
|
|
493
|
+
button: "repo-action",
|
|
494
|
+
},
|
|
495
|
+
}}
|
|
496
|
+
>
|
|
497
|
+
<RepositoryCommitsSection repositoryKey="demo" />
|
|
498
|
+
</GitRepositoryUiProvider>
|
|
337
499
|
</GitApiClientProvider>
|
|
338
500
|
);
|
|
339
501
|
}
|
|
340
502
|
```
|
|
341
503
|
|
|
342
|
-
|
|
504
|
+
### 3. Fully Custom Layout
|
|
505
|
+
|
|
506
|
+
Use the typed client, hooks, diagnostics, route adapter, and low-level models when you want full control over the page structure:
|
|
507
|
+
|
|
508
|
+
```ts
|
|
509
|
+
import {
|
|
510
|
+
GitApiClientProvider,
|
|
511
|
+
createGitApiClient,
|
|
512
|
+
useGitOverview,
|
|
513
|
+
useGitSearch,
|
|
514
|
+
} from "@trebired/git-host/react";
|
|
515
|
+
|
|
516
|
+
const client = createGitApiClient({ baseUrl: "/api/git" });
|
|
517
|
+
|
|
518
|
+
function CustomRepositorySearch({ repositoryKey }: { repositoryKey: string }) {
|
|
519
|
+
const overview = useGitOverview(repositoryKey);
|
|
520
|
+
const search = useGitSearch(repositoryKey, {
|
|
521
|
+
query: "value",
|
|
522
|
+
ref: overview.data?.repository.repository.current_branch,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
if (search.loading) return "Searching...";
|
|
526
|
+
if (search.error) return search.error.message;
|
|
527
|
+
return JSON.stringify(search.data?.files || []);
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Frontend Surface
|
|
532
|
+
|
|
533
|
+
`@trebired/git-host/browser` ships package-owned repository pages for:
|
|
534
|
+
|
|
535
|
+
- overview
|
|
536
|
+
- code/tree/blob
|
|
537
|
+
- commits
|
|
538
|
+
- commit detail
|
|
539
|
+
- releases
|
|
540
|
+
- release detail
|
|
541
|
+
- forks
|
|
542
|
+
- activity
|
|
543
|
+
- blame
|
|
544
|
+
- diff/compare
|
|
545
|
+
- branches
|
|
546
|
+
- tags
|
|
547
|
+
- search
|
|
548
|
+
|
|
549
|
+
`@trebired/git-host/react` now ships reusable repository primitives such as:
|
|
550
|
+
|
|
551
|
+
- `GitRepositoryUiProvider`
|
|
552
|
+
- `GitRepositoryShell`
|
|
553
|
+
- `GitRepositoryHeader`
|
|
554
|
+
- `GitRepositoryTabs`
|
|
555
|
+
- `GitRepositoryStats`
|
|
556
|
+
- `GitRepositoryActionBar`
|
|
557
|
+
- `GitRepositorySocialButtons`
|
|
558
|
+
- `GitCommitList`
|
|
559
|
+
- `GitReleaseList`
|
|
560
|
+
- `GitForkList`
|
|
561
|
+
- `GitBranchList`
|
|
562
|
+
- `GitTagList`
|
|
563
|
+
- `GitTreeView`
|
|
564
|
+
- `GitBlobView`
|
|
565
|
+
- `GitBlameView`
|
|
566
|
+
- `GitDiffView`
|
|
567
|
+
- `GitSearchResults`
|
|
568
|
+
- `GitBranchSelector`
|
|
569
|
+
- `GitTagSelector`
|
|
570
|
+
- `GitEmptyState`
|
|
571
|
+
- `GitErrorState`
|
|
572
|
+
- `GitLoadingState`
|
|
573
|
+
|
|
574
|
+
### Styling And Skinning
|
|
575
|
+
|
|
576
|
+
The package is designed around three frontend ownership levels:
|
|
577
|
+
|
|
578
|
+
- full package UI: import `@trebired/git-host/browser/styles.css` and use the browser pages directly
|
|
579
|
+
- package structure + app styling: skip the stylesheet and pass `theme.unstyled`, `theme.classNames`, and `theme.slots`
|
|
580
|
+
- package logic + app rendering: use the typed hooks, mutations, diagnostics, and route adapter with fully custom host rendering
|
|
581
|
+
|
|
582
|
+
The package now treats styling as optional rather than required:
|
|
583
|
+
|
|
584
|
+
- no browser page requires `@trebired/git-host/browser/styles.css` to function
|
|
585
|
+
- structural components expose stable `data-slot` markers for host CSS targeting
|
|
586
|
+
- `GitRepositoryUiProvider` accepts `theme.classNames` and `theme.slots` for slot-level class and attribute overrides
|
|
587
|
+
- `GitBrowserProvider` and browser pages accept `unstyled` as a shortcut for `theme.unstyled: true`
|
|
588
|
+
- render-state components can be replaced through `components.LoadingState`, `components.ErrorState`, and `components.EmptyState`
|
|
589
|
+
|
|
590
|
+
```ts
|
|
591
|
+
import {
|
|
592
|
+
GitRepositoryUiProvider,
|
|
593
|
+
GitRepositoryShell,
|
|
594
|
+
GitCommitList,
|
|
595
|
+
} from "@trebired/git-host/react";
|
|
596
|
+
|
|
597
|
+
<GitRepositoryUiProvider
|
|
598
|
+
theme={{
|
|
599
|
+
unstyled: true,
|
|
600
|
+
classNames: {
|
|
601
|
+
page: "nativeRepoPage",
|
|
602
|
+
header: "nativeRepoHeader",
|
|
603
|
+
card: "nativeCard",
|
|
604
|
+
list: "nativeList",
|
|
605
|
+
"list-item": "nativeListItem",
|
|
606
|
+
button: "nativeButton",
|
|
607
|
+
},
|
|
608
|
+
slots: {
|
|
609
|
+
page: {
|
|
610
|
+
attributes: {
|
|
611
|
+
"data-app-surface": "repository",
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
}}
|
|
616
|
+
components={{
|
|
617
|
+
EmptyState({ title, message }) {
|
|
618
|
+
return <section className="nativeEmpty">{title}: {message}</section>;
|
|
619
|
+
},
|
|
620
|
+
}}
|
|
621
|
+
>
|
|
622
|
+
<GitRepositoryShell page="commits" repositoryKey="demo">
|
|
623
|
+
<GitCommitList commits={commits} repositoryKey="demo" />
|
|
624
|
+
</GitRepositoryShell>
|
|
625
|
+
</GitRepositoryUiProvider>;
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Route Adapter
|
|
629
|
+
|
|
630
|
+
Both `browser` and `react` use a package-owned route adapter:
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
import { createGitRepositoryRouteAdapter } from "@trebired/git-host/react";
|
|
634
|
+
|
|
635
|
+
const routes = createGitRepositoryRouteAdapter({
|
|
636
|
+
repositoryBasePath: "/repos",
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
routes.overview("demo");
|
|
640
|
+
routes.code("demo", "src/app.ts", "main");
|
|
641
|
+
routes.commit("demo", "abc123");
|
|
642
|
+
routes.release("demo", "release-1");
|
|
643
|
+
routes.compare("demo", "main", "feature/x");
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
### Diagnostics Hooks
|
|
647
|
+
|
|
648
|
+
Repository UIs are brittle, so the package also exposes lifecycle diagnostics through `GitRepositoryUiProvider`:
|
|
649
|
+
|
|
650
|
+
- `onNavigate`
|
|
651
|
+
- `onViewMount`
|
|
652
|
+
- `onFetchStart`
|
|
653
|
+
- `onFetchSuccess`
|
|
654
|
+
- `onFetchError`
|
|
655
|
+
- `onActionStart`
|
|
656
|
+
- `onActionSuccess`
|
|
657
|
+
- `onActionError`
|
|
658
|
+
- `onEmptyState`
|
|
659
|
+
- `onRenderStateChange`
|
|
660
|
+
|
|
661
|
+
### Initial Data
|
|
662
|
+
|
|
663
|
+
Browser pages and hybrid sections can take a stable `initialData` shape through `GitRepositoryFrontEndInitialData`, so host apps do not need to invent per-page bootstrap payloads.
|
|
664
|
+
|
|
665
|
+
For long-running repository scans, the typed client still exposes a live Socket.IO linguist stream:
|
|
343
666
|
|
|
344
667
|
```ts
|
|
668
|
+
const gitClient = createGitApiClient({
|
|
669
|
+
baseUrl: "/api/git",
|
|
670
|
+
});
|
|
671
|
+
|
|
345
672
|
const socket = gitClient.openLinguistSocket("demo", {
|
|
346
673
|
ref: "main",
|
|
347
674
|
onProgress(event) {
|
|
@@ -355,18 +682,18 @@ const socket = gitClient.openLinguistSocket("demo", {
|
|
|
355
682
|
await socket.completed;
|
|
356
683
|
```
|
|
357
684
|
|
|
358
|
-
The React entry is intentionally headless. It helps apps fetch and mutate Git data consistently, but it does not ship a bundled styled UI.
|
|
359
|
-
|
|
360
685
|
## Current API
|
|
361
686
|
|
|
362
|
-
The
|
|
687
|
+
The package now exposes three main frontend/backend layers:
|
|
363
688
|
|
|
364
689
|
- `createGitHost()`
|
|
690
|
+
- `createGitForge()`
|
|
365
691
|
- `resolveRepositoryPath()`
|
|
366
692
|
- `runGit()`
|
|
367
693
|
- `buildGitEnv()`
|
|
368
694
|
- `RepositoryLockManager`
|
|
369
695
|
- `createGitApiHandler()`
|
|
696
|
+
- `createGitForgeApiHandler()`
|
|
370
697
|
- `createGitApiSocketServer()`
|
|
371
698
|
- `createGitHttpHandler()`
|
|
372
699
|
- `generateSshKeyPair()`
|
|
@@ -375,6 +702,7 @@ The first public slice is intentionally small:
|
|
|
375
702
|
- `fingerprintSshPublicKey()`
|
|
376
703
|
- `createGitSshServer()`
|
|
377
704
|
- `@trebired/git-host/react`
|
|
705
|
+
- `@trebired/git-host/browser`
|
|
378
706
|
|
|
379
707
|
And the main host instance methods:
|
|
380
708
|
|
|
@@ -415,12 +743,16 @@ And the main host instance methods:
|
|
|
415
743
|
- `push()`
|
|
416
744
|
- `withRepositoryLock()`
|
|
417
745
|
|
|
418
|
-
The React entry
|
|
746
|
+
The React entry now exports:
|
|
419
747
|
|
|
420
748
|
- `createGitApiClient()`
|
|
421
749
|
- `GitApiClientProvider`
|
|
750
|
+
- `GitRepositoryUiProvider`
|
|
751
|
+
- `createGitRepositoryRouteAdapter()`
|
|
422
752
|
- `openLinguistSocket()` through the typed client instance
|
|
423
753
|
- `useGitRepositorySummary()`
|
|
754
|
+
- `useGitOverview()`
|
|
755
|
+
- `useGitSocialState()`
|
|
424
756
|
- `useGitBranches()`
|
|
425
757
|
- `useGitCommits()`
|
|
426
758
|
- `useGitCommit()`
|
|
@@ -434,6 +766,48 @@ The React entry currently exports:
|
|
|
434
766
|
- `useGitBlob()`
|
|
435
767
|
- `useGitDiff()`
|
|
436
768
|
- `useGitApiQuery()`
|
|
769
|
+
- `GitRepositoryShell`
|
|
770
|
+
- `GitRepositoryHeader`
|
|
771
|
+
- `GitRepositoryTabs`
|
|
772
|
+
- `GitRepositoryStats`
|
|
773
|
+
- `GitRepositoryActionBar`
|
|
774
|
+
- `GitRepositorySocialButtons`
|
|
775
|
+
- `GitCommitList`
|
|
776
|
+
- `GitReleaseList`
|
|
777
|
+
- `GitForkList`
|
|
778
|
+
- `GitBranchList`
|
|
779
|
+
- `GitTagList`
|
|
780
|
+
- `GitTreeView`
|
|
781
|
+
- `GitBlobView`
|
|
782
|
+
- `GitBlameView`
|
|
783
|
+
- `GitDiffView`
|
|
784
|
+
- `GitSearchResults`
|
|
785
|
+
- `GitBranchSelector`
|
|
786
|
+
- `GitTagSelector`
|
|
787
|
+
- `GitStarButton`
|
|
788
|
+
- `GitWatchButton`
|
|
789
|
+
- `GitForkButton`
|
|
790
|
+
- `GitSyncForkButton`
|
|
791
|
+
- `GitCreateReleaseButton`
|
|
792
|
+
- `GitDeleteReleaseButton`
|
|
793
|
+
- `GitCopyCloneUrlButton`
|
|
794
|
+
- `GitDownloadArchiveButton`
|
|
795
|
+
|
|
796
|
+
The browser entry exports full-page repository surfaces such as:
|
|
797
|
+
|
|
798
|
+
- `GitRepositoryOverviewPage`
|
|
799
|
+
- `GitRepositoryCodePage`
|
|
800
|
+
- `GitRepositoryCommitsPage`
|
|
801
|
+
- `GitRepositoryCommitPage`
|
|
802
|
+
- `GitRepositoryReleasesPage`
|
|
803
|
+
- `GitRepositoryReleasePage`
|
|
804
|
+
- `GitRepositoryForksPage`
|
|
805
|
+
- `GitRepositoryActivityPage`
|
|
806
|
+
- `GitRepositoryBranchesPage`
|
|
807
|
+
- `GitRepositoryTagsPage`
|
|
808
|
+
- `GitRepositorySearchPage`
|
|
809
|
+
- `GitRepositoryBlamePage`
|
|
810
|
+
- `GitRepositoryComparePage`
|
|
437
811
|
|
|
438
812
|
## Repository Model
|
|
439
813
|
|
|
@@ -441,7 +815,7 @@ This package does not own your app database.
|
|
|
441
815
|
|
|
442
816
|
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.
|
|
443
817
|
|
|
444
|
-
The
|
|
818
|
+
The repository runtime is still worktree-first, but the frontend surface is intentionally much broader now so host apps can stay thin.
|
|
445
819
|
|
|
446
820
|
Private remotes are still host-owned. The package now helps with the transport plumbing by supporting:
|
|
447
821
|
|
|
@@ -462,10 +836,11 @@ Most alternatives fall into one of three buckets:
|
|
|
462
836
|
Use it when you want:
|
|
463
837
|
|
|
464
838
|
- your app to keep owning users, permissions, tokens, SSH keys, repository records, and UI
|
|
839
|
+
- your app to keep owning users, permissions, tokens, SSH keys, repository records, branding, auth, and top-level route mounting while git-host owns most repository UI
|
|
465
840
|
- real Git behavior from the system `git` binary
|
|
466
841
|
- clone, fetch, pull, and push over smart HTTP and SSH
|
|
467
842
|
- a reusable Git runtime instead of spreading Git shell calls all over your platform code
|
|
468
|
-
-
|
|
843
|
+
- full browser-ready repository pages or reusable React repository primitives without rebuilding the same repository shell in every host app
|
|
469
844
|
|
|
470
845
|
Do not use it when you want:
|
|
471
846
|
|
|
@@ -515,7 +890,7 @@ The host platform should still own:
|
|
|
515
890
|
- permission checks and route authorization policy
|
|
516
891
|
- access token issuance, revocation, and storage
|
|
517
892
|
- SSH key ownership, private key storage, and known-host persistence
|
|
518
|
-
-
|
|
893
|
+
- top-level product chrome, auth flows, branding decisions, and any product-specific features outside the repository area
|
|
519
894
|
|
|
520
895
|
That boundary is where the package simplifies a platform the most without turning into a forge product of its own.
|
|
521
896
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/api/forge/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/api/forge/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAKjE,OAAO,KAAK,EAAE,+BAA+B,EAAiF,MAAM,gBAAgB,CAAC;AAWrJ,OAAO,EAAE,qBAAqB,EAAyB,MAAM,YAAY,CAAC;AA4V1E,iBAAS,wBAAwB,CAAC,OAAO,EAAE,+BAA+B,IAarC,KAAK,eAAe,EAAE,KAAK,cAAc,UAK7E;AAED,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,CAAC"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { logPackageInitialized } from "@trebired/logger-adapter";
|
|
2
2
|
import { resolveLogger } from "../../logging.js";
|
|
3
|
+
import { GitHostError } from "../../errors.js";
|
|
3
4
|
import { text } from "../../utils/text.js";
|
|
5
|
+
import { enrichRepositoryDataWithArchives, isArchiveDownloadAction, writeArchiveDownload } from "../handler/archive.js";
|
|
4
6
|
import { runGitApiAction } from "../handler/action.js";
|
|
5
7
|
import { applyAuthorizationHeaders, authorizationAllowed, serializeError, statusForError, writeJson, } from "../handler/response.js";
|
|
6
8
|
import { parseGitForgeApiRoute } from "./route.js";
|
|
@@ -131,6 +133,57 @@ async function runForgeAction(options, route, repositoryId, actor, body) {
|
|
|
131
133
|
}, route, repositoryId, new URLSearchParams());
|
|
132
134
|
}
|
|
133
135
|
}
|
|
136
|
+
async function attachReleaseSourceArchives(options, repositoryId, repositoryKey, release) {
|
|
137
|
+
try {
|
|
138
|
+
await options.gitHost.resolveArchive(repositoryId, {
|
|
139
|
+
format: "zip",
|
|
140
|
+
ref: release.tag_name,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
if (error instanceof GitHostError && error.code === "archive_ref_not_found") {
|
|
145
|
+
throw new GitHostError("release_tag_not_found", `Release "${release.id}" points at missing tag "${release.tag_name}" in repository "${repositoryId}".`, {
|
|
146
|
+
releaseId: release.id,
|
|
147
|
+
repositoryId,
|
|
148
|
+
tag: release.tag_name,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
...release,
|
|
155
|
+
source_archives: options.gitHost.resolveArchiveLinks(repositoryKey, {
|
|
156
|
+
basePath: options.basePath,
|
|
157
|
+
ref: release.tag_name,
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
async function enrichForgeDataWithArchives(options, route, repositoryId, data) {
|
|
162
|
+
if (route.resource === "repository") {
|
|
163
|
+
return await enrichRepositoryDataWithArchives(options, route, data);
|
|
164
|
+
}
|
|
165
|
+
if (route.action === "release" && data && typeof data === "object") {
|
|
166
|
+
return await attachReleaseSourceArchives(options, repositoryId, route.repositoryKey, data);
|
|
167
|
+
}
|
|
168
|
+
if (route.action === "releases" && Array.isArray(data)) {
|
|
169
|
+
return await Promise.all(data.map(async (release) => {
|
|
170
|
+
return await attachReleaseSourceArchives(options, repositoryId, route.repositoryKey, release);
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
if (route.action === "releases" && data && typeof data === "object") {
|
|
174
|
+
return await attachReleaseSourceArchives(options, repositoryId, route.repositoryKey, data);
|
|
175
|
+
}
|
|
176
|
+
if (route.action === "overview" && data && typeof data === "object") {
|
|
177
|
+
const overview = data;
|
|
178
|
+
return {
|
|
179
|
+
...overview,
|
|
180
|
+
latest_release: overview.latest_release
|
|
181
|
+
? await attachReleaseSourceArchives(options, repositoryId, route.repositoryKey, overview.latest_release)
|
|
182
|
+
: null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return data;
|
|
186
|
+
}
|
|
134
187
|
async function handleGitForgeApiRequest(req, res, options) {
|
|
135
188
|
const logger = resolveLogger(options.logger, options.loggerAdapter);
|
|
136
189
|
const verbose = options.verbose === true;
|
|
@@ -191,6 +244,16 @@ async function handleGitForgeApiRequest(req, res, options) {
|
|
|
191
244
|
: undefined);
|
|
192
245
|
applyAuthorizationHeaders(res, auth.headers);
|
|
193
246
|
if (!auth.allowed) {
|
|
247
|
+
if (isArchiveDownloadAction(route.action)) {
|
|
248
|
+
logger.warn(logGroup, "archive download denied", {
|
|
249
|
+
action: route.action,
|
|
250
|
+
method,
|
|
251
|
+
pathname: url.pathname,
|
|
252
|
+
repositoryId,
|
|
253
|
+
repositoryKey,
|
|
254
|
+
status: auth.status || 403,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
194
257
|
writeJson(req, res, auth.status || 403, {
|
|
195
258
|
ok: false,
|
|
196
259
|
error: {
|
|
@@ -207,11 +270,27 @@ async function handleGitForgeApiRequest(req, res, options) {
|
|
|
207
270
|
body._method = method;
|
|
208
271
|
}
|
|
209
272
|
try {
|
|
210
|
-
|
|
273
|
+
if (isArchiveDownloadAction(route.action)) {
|
|
274
|
+
logger.info(logGroup, "archive download authorized", {
|
|
275
|
+
action: route.action,
|
|
276
|
+
method,
|
|
277
|
+
pathname: url.pathname,
|
|
278
|
+
repositoryId,
|
|
279
|
+
repositoryKey,
|
|
280
|
+
});
|
|
281
|
+
await writeArchiveDownload(req, res, options.gitHost, {
|
|
282
|
+
ref: "refName" in route ? route.refName : "HEAD",
|
|
283
|
+
repositoryId,
|
|
284
|
+
routeAction: route.action,
|
|
285
|
+
});
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const rawData = route.resource === "repository" && route.action !== "overview" && route.action !== "social" && route.action !== "activity"
|
|
211
289
|
? await runGitApiAction({
|
|
212
290
|
gitHost: options.gitHost,
|
|
213
291
|
}, route, repositoryId, url.searchParams)
|
|
214
292
|
: await runForgeAction(options, route, repositoryId, actor, body);
|
|
293
|
+
const data = await enrichForgeDataWithArchives(options, route, repositoryId, rawData);
|
|
215
294
|
if (verbose) {
|
|
216
295
|
logger.info(logGroup, "forge api action completed", {
|
|
217
296
|
action: route.action,
|