gitx.do 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -353
- package/dist/do/logger.d.ts +50 -0
- package/dist/do/logger.d.ts.map +1 -0
- package/dist/do/logger.js +122 -0
- package/dist/do/logger.js.map +1 -0
- package/dist/{durable-object → do}/schema.d.ts +3 -3
- package/dist/do/schema.d.ts.map +1 -0
- package/dist/{durable-object → do}/schema.js +4 -3
- package/dist/do/schema.js.map +1 -0
- package/dist/do/types.d.ts +267 -0
- package/dist/do/types.d.ts.map +1 -0
- package/dist/do/types.js +62 -0
- package/dist/do/types.js.map +1 -0
- package/dist/index.d.ts +15 -415
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -483
- package/dist/index.js.map +1 -1
- package/package.json +13 -21
- package/dist/cli/commands/add.d.ts +0 -174
- package/dist/cli/commands/add.d.ts.map +0 -1
- package/dist/cli/commands/add.js +0 -131
- package/dist/cli/commands/add.js.map +0 -1
- package/dist/cli/commands/blame.d.ts +0 -259
- package/dist/cli/commands/blame.d.ts.map +0 -1
- package/dist/cli/commands/blame.js +0 -609
- package/dist/cli/commands/blame.js.map +0 -1
- package/dist/cli/commands/branch.d.ts +0 -249
- package/dist/cli/commands/branch.d.ts.map +0 -1
- package/dist/cli/commands/branch.js +0 -693
- package/dist/cli/commands/branch.js.map +0 -1
- package/dist/cli/commands/commit.d.ts +0 -182
- package/dist/cli/commands/commit.d.ts.map +0 -1
- package/dist/cli/commands/commit.js +0 -437
- package/dist/cli/commands/commit.js.map +0 -1
- package/dist/cli/commands/diff.d.ts +0 -464
- package/dist/cli/commands/diff.d.ts.map +0 -1
- package/dist/cli/commands/diff.js +0 -958
- package/dist/cli/commands/diff.js.map +0 -1
- package/dist/cli/commands/log.d.ts +0 -239
- package/dist/cli/commands/log.d.ts.map +0 -1
- package/dist/cli/commands/log.js +0 -535
- package/dist/cli/commands/log.js.map +0 -1
- package/dist/cli/commands/merge.d.ts +0 -106
- package/dist/cli/commands/merge.d.ts.map +0 -1
- package/dist/cli/commands/merge.js +0 -55
- package/dist/cli/commands/merge.js.map +0 -1
- package/dist/cli/commands/review.d.ts +0 -457
- package/dist/cli/commands/review.d.ts.map +0 -1
- package/dist/cli/commands/review.js +0 -533
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -269
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/status.js +0 -493
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/commands/web.d.ts +0 -199
- package/dist/cli/commands/web.d.ts.map +0 -1
- package/dist/cli/commands/web.js +0 -696
- package/dist/cli/commands/web.js.map +0 -1
- package/dist/cli/fs-adapter.d.ts +0 -656
- package/dist/cli/fs-adapter.d.ts.map +0 -1
- package/dist/cli/fs-adapter.js +0 -1179
- package/dist/cli/fs-adapter.js.map +0 -1
- package/dist/cli/fsx-cli-adapter.d.ts +0 -359
- package/dist/cli/fsx-cli-adapter.d.ts.map +0 -1
- package/dist/cli/fsx-cli-adapter.js +0 -619
- package/dist/cli/fsx-cli-adapter.js.map +0 -1
- package/dist/cli/index.d.ts +0 -387
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -523
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/ui/components/DiffView.d.ts +0 -7
- package/dist/cli/ui/components/DiffView.d.ts.map +0 -1
- package/dist/cli/ui/components/DiffView.js +0 -11
- package/dist/cli/ui/components/DiffView.js.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.d.ts +0 -6
- package/dist/cli/ui/components/ErrorDisplay.d.ts.map +0 -1
- package/dist/cli/ui/components/ErrorDisplay.js +0 -11
- package/dist/cli/ui/components/ErrorDisplay.js.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.d.ts +0 -9
- package/dist/cli/ui/components/FuzzySearch.d.ts.map +0 -1
- package/dist/cli/ui/components/FuzzySearch.js +0 -12
- package/dist/cli/ui/components/FuzzySearch.js.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.d.ts +0 -6
- package/dist/cli/ui/components/LoadingSpinner.d.ts.map +0 -1
- package/dist/cli/ui/components/LoadingSpinner.js +0 -10
- package/dist/cli/ui/components/LoadingSpinner.js.map +0 -1
- package/dist/cli/ui/components/NavigationList.d.ts +0 -9
- package/dist/cli/ui/components/NavigationList.d.ts.map +0 -1
- package/dist/cli/ui/components/NavigationList.js +0 -11
- package/dist/cli/ui/components/NavigationList.js.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.d.ts +0 -8
- package/dist/cli/ui/components/ScrollableContent.d.ts.map +0 -1
- package/dist/cli/ui/components/ScrollableContent.js +0 -11
- package/dist/cli/ui/components/ScrollableContent.js.map +0 -1
- package/dist/cli/ui/components/index.d.ts +0 -7
- package/dist/cli/ui/components/index.d.ts.map +0 -1
- package/dist/cli/ui/components/index.js +0 -9
- package/dist/cli/ui/components/index.js.map +0 -1
- package/dist/cli/ui/terminal-ui.d.ts +0 -52
- package/dist/cli/ui/terminal-ui.d.ts.map +0 -1
- package/dist/cli/ui/terminal-ui.js +0 -121
- package/dist/cli/ui/terminal-ui.js.map +0 -1
- package/dist/do/BashModule.d.ts +0 -871
- package/dist/do/BashModule.d.ts.map +0 -1
- package/dist/do/BashModule.js +0 -1143
- package/dist/do/BashModule.js.map +0 -1
- package/dist/do/FsModule.d.ts +0 -601
- package/dist/do/FsModule.d.ts.map +0 -1
- package/dist/do/FsModule.js +0 -1120
- package/dist/do/FsModule.js.map +0 -1
- package/dist/do/GitModule.d.ts +0 -635
- package/dist/do/GitModule.d.ts.map +0 -1
- package/dist/do/GitModule.js +0 -781
- package/dist/do/GitModule.js.map +0 -1
- package/dist/do/GitRepoDO.d.ts +0 -281
- package/dist/do/GitRepoDO.d.ts.map +0 -1
- package/dist/do/GitRepoDO.js +0 -479
- package/dist/do/GitRepoDO.js.map +0 -1
- package/dist/do/bash-ast.d.ts +0 -246
- package/dist/do/bash-ast.d.ts.map +0 -1
- package/dist/do/bash-ast.js +0 -888
- package/dist/do/bash-ast.js.map +0 -1
- package/dist/do/container-executor.d.ts +0 -491
- package/dist/do/container-executor.d.ts.map +0 -1
- package/dist/do/container-executor.js +0 -730
- package/dist/do/container-executor.js.map +0 -1
- package/dist/do/index.d.ts +0 -53
- package/dist/do/index.d.ts.map +0 -1
- package/dist/do/index.js +0 -91
- package/dist/do/index.js.map +0 -1
- package/dist/do/tiered-storage.d.ts +0 -403
- package/dist/do/tiered-storage.d.ts.map +0 -1
- package/dist/do/tiered-storage.js +0 -689
- package/dist/do/tiered-storage.js.map +0 -1
- package/dist/do/withBash.d.ts +0 -231
- package/dist/do/withBash.d.ts.map +0 -1
- package/dist/do/withBash.js +0 -244
- package/dist/do/withBash.js.map +0 -1
- package/dist/do/withFs.d.ts +0 -237
- package/dist/do/withFs.d.ts.map +0 -1
- package/dist/do/withFs.js +0 -387
- package/dist/do/withFs.js.map +0 -1
- package/dist/do/withGit.d.ts +0 -180
- package/dist/do/withGit.d.ts.map +0 -1
- package/dist/do/withGit.js +0 -271
- package/dist/do/withGit.js.map +0 -1
- package/dist/durable-object/object-store.d.ts +0 -633
- package/dist/durable-object/object-store.d.ts.map +0 -1
- package/dist/durable-object/object-store.js +0 -1161
- package/dist/durable-object/object-store.js.map +0 -1
- package/dist/durable-object/schema.d.ts.map +0 -1
- package/dist/durable-object/schema.js.map +0 -1
- package/dist/durable-object/wal.d.ts +0 -416
- package/dist/durable-object/wal.d.ts.map +0 -1
- package/dist/durable-object/wal.js +0 -445
- package/dist/durable-object/wal.js.map +0 -1
- package/dist/mcp/adapter.d.ts +0 -772
- package/dist/mcp/adapter.d.ts.map +0 -1
- package/dist/mcp/adapter.js +0 -895
- package/dist/mcp/adapter.js.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts +0 -22
- package/dist/mcp/sandbox/miniflare-evaluator.d.ts.map +0 -1
- package/dist/mcp/sandbox/miniflare-evaluator.js +0 -140
- package/dist/mcp/sandbox/miniflare-evaluator.js.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.d.ts +0 -32
- package/dist/mcp/sandbox/object-store-proxy.d.ts.map +0 -1
- package/dist/mcp/sandbox/object-store-proxy.js +0 -30
- package/dist/mcp/sandbox/object-store-proxy.js.map +0 -1
- package/dist/mcp/sandbox/template.d.ts +0 -17
- package/dist/mcp/sandbox/template.d.ts.map +0 -1
- package/dist/mcp/sandbox/template.js +0 -71
- package/dist/mcp/sandbox/template.js.map +0 -1
- package/dist/mcp/sandbox.d.ts +0 -764
- package/dist/mcp/sandbox.d.ts.map +0 -1
- package/dist/mcp/sandbox.js +0 -1362
- package/dist/mcp/sandbox.js.map +0 -1
- package/dist/mcp/sdk-adapter.d.ts +0 -835
- package/dist/mcp/sdk-adapter.d.ts.map +0 -1
- package/dist/mcp/sdk-adapter.js +0 -974
- package/dist/mcp/sdk-adapter.js.map +0 -1
- package/dist/mcp/tools/do.d.ts +0 -32
- package/dist/mcp/tools/do.d.ts.map +0 -1
- package/dist/mcp/tools/do.js +0 -115
- package/dist/mcp/tools/do.js.map +0 -1
- package/dist/mcp/tools.d.ts +0 -548
- package/dist/mcp/tools.d.ts.map +0 -1
- package/dist/mcp/tools.js +0 -1934
- package/dist/mcp/tools.js.map +0 -1
- package/dist/ops/blame.d.ts +0 -551
- package/dist/ops/blame.d.ts.map +0 -1
- package/dist/ops/blame.js +0 -1037
- package/dist/ops/blame.js.map +0 -1
- package/dist/ops/branch.d.ts +0 -766
- package/dist/ops/branch.d.ts.map +0 -1
- package/dist/ops/branch.js +0 -950
- package/dist/ops/branch.js.map +0 -1
- package/dist/ops/commit-traversal.d.ts +0 -349
- package/dist/ops/commit-traversal.d.ts.map +0 -1
- package/dist/ops/commit-traversal.js +0 -821
- package/dist/ops/commit-traversal.js.map +0 -1
- package/dist/ops/commit.d.ts +0 -555
- package/dist/ops/commit.d.ts.map +0 -1
- package/dist/ops/commit.js +0 -826
- package/dist/ops/commit.js.map +0 -1
- package/dist/ops/merge-base.d.ts +0 -397
- package/dist/ops/merge-base.d.ts.map +0 -1
- package/dist/ops/merge-base.js +0 -691
- package/dist/ops/merge-base.js.map +0 -1
- package/dist/ops/merge.d.ts +0 -855
- package/dist/ops/merge.d.ts.map +0 -1
- package/dist/ops/merge.js +0 -1551
- package/dist/ops/merge.js.map +0 -1
- package/dist/ops/tag.d.ts +0 -247
- package/dist/ops/tag.d.ts.map +0 -1
- package/dist/ops/tag.js +0 -649
- package/dist/ops/tag.js.map +0 -1
- package/dist/ops/tree-builder.d.ts +0 -178
- package/dist/ops/tree-builder.d.ts.map +0 -1
- package/dist/ops/tree-builder.js +0 -271
- package/dist/ops/tree-builder.js.map +0 -1
- package/dist/ops/tree-diff.d.ts +0 -291
- package/dist/ops/tree-diff.d.ts.map +0 -1
- package/dist/ops/tree-diff.js +0 -705
- package/dist/ops/tree-diff.js.map +0 -1
- package/dist/pack/delta.d.ts +0 -248
- package/dist/pack/delta.d.ts.map +0 -1
- package/dist/pack/delta.js +0 -736
- package/dist/pack/delta.js.map +0 -1
- package/dist/pack/format.d.ts +0 -446
- package/dist/pack/format.d.ts.map +0 -1
- package/dist/pack/format.js +0 -572
- package/dist/pack/format.js.map +0 -1
- package/dist/pack/full-generation.d.ts +0 -612
- package/dist/pack/full-generation.d.ts.map +0 -1
- package/dist/pack/full-generation.js +0 -1378
- package/dist/pack/full-generation.js.map +0 -1
- package/dist/pack/generation.d.ts +0 -441
- package/dist/pack/generation.d.ts.map +0 -1
- package/dist/pack/generation.js +0 -707
- package/dist/pack/generation.js.map +0 -1
- package/dist/pack/index.d.ts +0 -502
- package/dist/pack/index.d.ts.map +0 -1
- package/dist/pack/index.js +0 -833
- package/dist/pack/index.js.map +0 -1
- package/dist/refs/branch.d.ts +0 -668
- package/dist/refs/branch.d.ts.map +0 -1
- package/dist/refs/branch.js +0 -897
- package/dist/refs/branch.js.map +0 -1
- package/dist/refs/storage.d.ts +0 -833
- package/dist/refs/storage.d.ts.map +0 -1
- package/dist/refs/storage.js +0 -1023
- package/dist/refs/storage.js.map +0 -1
- package/dist/refs/tag.d.ts +0 -860
- package/dist/refs/tag.d.ts.map +0 -1
- package/dist/refs/tag.js +0 -996
- package/dist/refs/tag.js.map +0 -1
- package/dist/storage/backend.d.ts +0 -425
- package/dist/storage/backend.d.ts.map +0 -1
- package/dist/storage/backend.js +0 -41
- package/dist/storage/backend.js.map +0 -1
- package/dist/storage/fsx-adapter.d.ts +0 -204
- package/dist/storage/fsx-adapter.d.ts.map +0 -1
- package/dist/storage/fsx-adapter.js +0 -470
- package/dist/storage/fsx-adapter.js.map +0 -1
- package/dist/storage/lru-cache.d.ts +0 -691
- package/dist/storage/lru-cache.d.ts.map +0 -1
- package/dist/storage/lru-cache.js +0 -813
- package/dist/storage/lru-cache.js.map +0 -1
- package/dist/storage/object-index.d.ts +0 -585
- package/dist/storage/object-index.d.ts.map +0 -1
- package/dist/storage/object-index.js +0 -532
- package/dist/storage/object-index.js.map +0 -1
- package/dist/storage/r2-pack.d.ts +0 -1257
- package/dist/storage/r2-pack.d.ts.map +0 -1
- package/dist/storage/r2-pack.js +0 -1770
- package/dist/storage/r2-pack.js.map +0 -1
- package/dist/tiered/cdc-pipeline.d.ts +0 -1888
- package/dist/tiered/cdc-pipeline.d.ts.map +0 -1
- package/dist/tiered/cdc-pipeline.js +0 -1880
- package/dist/tiered/cdc-pipeline.js.map +0 -1
- package/dist/tiered/migration.d.ts +0 -1104
- package/dist/tiered/migration.d.ts.map +0 -1
- package/dist/tiered/migration.js +0 -1214
- package/dist/tiered/migration.js.map +0 -1
- package/dist/tiered/parquet-writer.d.ts +0 -1145
- package/dist/tiered/parquet-writer.d.ts.map +0 -1
- package/dist/tiered/parquet-writer.js +0 -1183
- package/dist/tiered/parquet-writer.js.map +0 -1
- package/dist/tiered/read-path.d.ts +0 -835
- package/dist/tiered/read-path.d.ts.map +0 -1
- package/dist/tiered/read-path.js +0 -487
- package/dist/tiered/read-path.js.map +0 -1
- package/dist/types/capability.d.ts +0 -1385
- package/dist/types/capability.d.ts.map +0 -1
- package/dist/types/capability.js +0 -36
- package/dist/types/capability.js.map +0 -1
- package/dist/types/index.d.ts +0 -13
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -18
- package/dist/types/index.js.map +0 -1
- package/dist/types/objects.d.ts +0 -692
- package/dist/types/objects.d.ts.map +0 -1
- package/dist/types/objects.js +0 -837
- package/dist/types/objects.js.map +0 -1
- package/dist/types/storage.d.ts +0 -603
- package/dist/types/storage.d.ts.map +0 -1
- package/dist/types/storage.js +0 -191
- package/dist/types/storage.js.map +0 -1
- package/dist/types/worker-loader.d.ts +0 -60
- package/dist/types/worker-loader.d.ts.map +0 -1
- package/dist/types/worker-loader.js +0 -62
- package/dist/types/worker-loader.js.map +0 -1
- package/dist/utils/hash.d.ts +0 -197
- package/dist/utils/hash.d.ts.map +0 -1
- package/dist/utils/hash.js +0 -268
- package/dist/utils/hash.js.map +0 -1
- package/dist/utils/sha1.d.ts +0 -290
- package/dist/utils/sha1.d.ts.map +0 -1
- package/dist/utils/sha1.js +0 -582
- package/dist/utils/sha1.js.map +0 -1
- package/dist/wire/capabilities.d.ts +0 -1044
- package/dist/wire/capabilities.d.ts.map +0 -1
- package/dist/wire/capabilities.js +0 -941
- package/dist/wire/capabilities.js.map +0 -1
- package/dist/wire/path-security.d.ts +0 -157
- package/dist/wire/path-security.d.ts.map +0 -1
- package/dist/wire/path-security.js +0 -307
- package/dist/wire/path-security.js.map +0 -1
- package/dist/wire/pkt-line.d.ts +0 -345
- package/dist/wire/pkt-line.d.ts.map +0 -1
- package/dist/wire/pkt-line.js +0 -381
- package/dist/wire/pkt-line.js.map +0 -1
- package/dist/wire/receive-pack.d.ts +0 -1059
- package/dist/wire/receive-pack.d.ts.map +0 -1
- package/dist/wire/receive-pack.js +0 -1414
- package/dist/wire/receive-pack.js.map +0 -1
- package/dist/wire/smart-http.d.ts +0 -799
- package/dist/wire/smart-http.d.ts.map +0 -1
- package/dist/wire/smart-http.js +0 -945
- package/dist/wire/smart-http.js.map +0 -1
- package/dist/wire/upload-pack.d.ts +0 -727
- package/dist/wire/upload-pack.d.ts.map +0 -1
- package/dist/wire/upload-pack.js +0 -1138
- package/dist/wire/upload-pack.js.map +0 -1
package/dist/refs/tag.js
DELETED
|
@@ -1,996 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Git Tag Operations
|
|
3
|
-
*
|
|
4
|
-
* This module provides comprehensive tag management functionality including
|
|
5
|
-
* creation (lightweight and annotated), deletion, listing, and verification.
|
|
6
|
-
*
|
|
7
|
-
* **Tag Types in Git**:
|
|
8
|
-
* - **Lightweight tags**: Simple refs under refs/tags/ pointing to commits
|
|
9
|
-
* - **Annotated tags**: Refs pointing to tag objects containing metadata
|
|
10
|
-
* (tagger, date, message, and optional GPG signature)
|
|
11
|
-
*
|
|
12
|
-
* Annotated tags are recommended for releases as they include metadata
|
|
13
|
-
* and can be cryptographically signed.
|
|
14
|
-
*
|
|
15
|
-
* @module refs/tag
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* import { TagManager, createTag, listTags } from './refs/tag'
|
|
20
|
-
*
|
|
21
|
-
* // Create manager
|
|
22
|
-
* const manager = new TagManager(refStorage, objectStorage, gpgSigner)
|
|
23
|
-
*
|
|
24
|
-
* // Create annotated tag
|
|
25
|
-
* const tag = await manager.createTag('v1.0.0', commitSha, {
|
|
26
|
-
* annotated: true,
|
|
27
|
-
* message: 'Release 1.0.0',
|
|
28
|
-
* tagger: { name: 'Alice', email: 'alice@example.com', timestamp: Date.now()/1000, timezone: '+0000' }
|
|
29
|
-
* })
|
|
30
|
-
*
|
|
31
|
-
* // List version tags
|
|
32
|
-
* const versions = await listTags(manager, { pattern: 'v*' })
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
// Re-export RefStorage for backward compatibility
|
|
36
|
-
export { RefStorage } from './storage';
|
|
37
|
-
/**
|
|
38
|
-
* Error thrown when a tag operation fails.
|
|
39
|
-
*
|
|
40
|
-
* @description
|
|
41
|
-
* Provides structured error information with error code
|
|
42
|
-
* for programmatic error handling.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```typescript
|
|
46
|
-
* try {
|
|
47
|
-
* await manager.createTag('v1.0.0', 'abc123')
|
|
48
|
-
* } catch (e) {
|
|
49
|
-
* if (e instanceof TagError) {
|
|
50
|
-
* switch (e.code) {
|
|
51
|
-
* case 'TAG_EXISTS':
|
|
52
|
-
* console.log('Tag already exists, use force=true to overwrite')
|
|
53
|
-
* break
|
|
54
|
-
* case 'MESSAGE_REQUIRED':
|
|
55
|
-
* console.log('Annotated tags require a message')
|
|
56
|
-
* break
|
|
57
|
-
* }
|
|
58
|
-
* }
|
|
59
|
-
* }
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
export class TagError extends Error {
|
|
63
|
-
code;
|
|
64
|
-
tagName;
|
|
65
|
-
/**
|
|
66
|
-
* Create a new TagError.
|
|
67
|
-
*
|
|
68
|
-
* @param message - Human-readable error description
|
|
69
|
-
* @param code - Error code for programmatic handling
|
|
70
|
-
* @param tagName - The tag that caused the error (optional)
|
|
71
|
-
*/
|
|
72
|
-
constructor(message, code, tagName) {
|
|
73
|
-
super(message);
|
|
74
|
-
this.code = code;
|
|
75
|
-
this.tagName = tagName;
|
|
76
|
-
this.name = 'TagError';
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
// ============================================================================
|
|
80
|
-
// TagManager Class
|
|
81
|
-
// ============================================================================
|
|
82
|
-
/**
|
|
83
|
-
* Tag manager for handling Git tag operations.
|
|
84
|
-
*
|
|
85
|
-
* @description
|
|
86
|
-
* Provides a comprehensive API for tag management including both
|
|
87
|
-
* lightweight and annotated tags. Uses RefStorage for refs and
|
|
88
|
-
* TagObjectStorage for tag objects.
|
|
89
|
-
*
|
|
90
|
-
* Note: Most methods are currently stubs (TODO) and will throw 'Not implemented'.
|
|
91
|
-
* These will be implemented in the GREEN phase of TDD development.
|
|
92
|
-
*
|
|
93
|
-
* @example
|
|
94
|
-
* ```typescript
|
|
95
|
-
* const manager = new TagManager(refStorage, objectStorage, gpgSigner)
|
|
96
|
-
*
|
|
97
|
-
* // Create a release tag
|
|
98
|
-
* const tag = await manager.createTag('v1.0.0', commitSha, {
|
|
99
|
-
* annotated: true,
|
|
100
|
-
* message: 'Version 1.0.0 release',
|
|
101
|
-
* tagger: { name: 'Alice', email: 'alice@example.com', timestamp: 1704067200, timezone: '+0000' }
|
|
102
|
-
* })
|
|
103
|
-
*
|
|
104
|
-
* // List version tags
|
|
105
|
-
* const versions = await manager.listTags({ pattern: 'v*' })
|
|
106
|
-
*
|
|
107
|
-
* // Verify signed tag
|
|
108
|
-
* const verification = await manager.verifyTag('v1.0.0')
|
|
109
|
-
* ```
|
|
110
|
-
*/
|
|
111
|
-
export class TagManager {
|
|
112
|
-
refStorage;
|
|
113
|
-
objectStorage;
|
|
114
|
-
gpgSigner;
|
|
115
|
-
// Simple in-memory lock to handle concurrent tag creation
|
|
116
|
-
pendingCreations = new Set();
|
|
117
|
-
/**
|
|
118
|
-
* Create a new TagManager.
|
|
119
|
-
*
|
|
120
|
-
* @param refStorage - RefStorage instance for managing tag refs
|
|
121
|
-
* @param objectStorage - Storage for reading/writing tag objects
|
|
122
|
-
* @param gpgSigner - Optional GPG signer for signed tags
|
|
123
|
-
*/
|
|
124
|
-
constructor(refStorage, objectStorage, gpgSigner) {
|
|
125
|
-
this.refStorage = refStorage;
|
|
126
|
-
this.objectStorage = objectStorage;
|
|
127
|
-
this.gpgSigner = gpgSigner;
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Create a new tag.
|
|
131
|
-
*
|
|
132
|
-
* @description
|
|
133
|
-
* Creates either a lightweight or annotated tag pointing to the
|
|
134
|
-
* specified target. For annotated tags, creates a tag object with
|
|
135
|
-
* metadata and optionally signs it.
|
|
136
|
-
*
|
|
137
|
-
* @param name - Tag name (without refs/tags/ prefix)
|
|
138
|
-
* @param target - Target SHA to tag (usually a commit)
|
|
139
|
-
* @param options - Creation options
|
|
140
|
-
* @returns The created tag
|
|
141
|
-
* @throws TagError with code 'INVALID_TAG_NAME' if name is invalid
|
|
142
|
-
* @throws TagError with code 'TAG_EXISTS' if tag exists and not forcing
|
|
143
|
-
* @throws TagError with code 'MESSAGE_REQUIRED' for annotated tag without message
|
|
144
|
-
* @throws TagError with code 'GPG_ERROR' if signing fails
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```typescript
|
|
148
|
-
* // Create lightweight tag
|
|
149
|
-
* const light = await manager.createTag('v0.1.0', commitSha)
|
|
150
|
-
*
|
|
151
|
-
* // Create annotated tag
|
|
152
|
-
* const annotated = await manager.createTag('v1.0.0', commitSha, {
|
|
153
|
-
* annotated: true,
|
|
154
|
-
* message: 'Release 1.0.0',
|
|
155
|
-
* tagger: { name: 'Alice', email: 'alice@example.com', timestamp: 1704067200, timezone: '+0000' }
|
|
156
|
-
* })
|
|
157
|
-
*
|
|
158
|
-
* // Create signed tag
|
|
159
|
-
* const signed = await manager.createTag('v1.0.0', commitSha, {
|
|
160
|
-
* annotated: true,
|
|
161
|
-
* message: 'Release 1.0.0',
|
|
162
|
-
* sign: true
|
|
163
|
-
* })
|
|
164
|
-
* ```
|
|
165
|
-
*/
|
|
166
|
-
async createTag(name, target, options) {
|
|
167
|
-
// Validate tag name
|
|
168
|
-
if (!isValidTagName(name)) {
|
|
169
|
-
throw new TagError(`Invalid tag name: ${name}`, 'INVALID_TAG_NAME', name);
|
|
170
|
-
}
|
|
171
|
-
const refName = `refs/tags/${name}`;
|
|
172
|
-
// Synchronous check-and-lock to handle concurrent creation attempts
|
|
173
|
-
if (this.pendingCreations.has(name)) {
|
|
174
|
-
throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
|
|
175
|
-
}
|
|
176
|
-
// Check if tag already exists
|
|
177
|
-
const existingSha = await this.refStorage.getRef(refName);
|
|
178
|
-
if (existingSha !== null && !options?.force) {
|
|
179
|
-
throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
|
|
180
|
-
}
|
|
181
|
-
// Mark as pending (synchronous operation for atomicity)
|
|
182
|
-
if (!options?.force) {
|
|
183
|
-
if (this.pendingCreations.has(name)) {
|
|
184
|
-
throw new TagError(`Tag already exists: ${name}`, 'TAG_EXISTS', name);
|
|
185
|
-
}
|
|
186
|
-
this.pendingCreations.add(name);
|
|
187
|
-
}
|
|
188
|
-
// Determine if this should be an annotated tag
|
|
189
|
-
const isAnnotated = options?.annotated === true || (options?.message !== undefined && options?.message !== '');
|
|
190
|
-
try {
|
|
191
|
-
if (isAnnotated) {
|
|
192
|
-
// Validate message for annotated tags
|
|
193
|
-
const rawMessage = options?.message;
|
|
194
|
-
if (!rawMessage || rawMessage.trim().length === 0) {
|
|
195
|
-
throw new TagError('Annotated tag requires a message', 'MESSAGE_REQUIRED', name);
|
|
196
|
-
}
|
|
197
|
-
const formattedMessage = formatTagMessage(rawMessage);
|
|
198
|
-
// Validate tagger (can have timestamp set to 0 or undefined, we'll use current time)
|
|
199
|
-
let tagger = options?.tagger;
|
|
200
|
-
if (tagger && tagger.timestamp === undefined) {
|
|
201
|
-
tagger = {
|
|
202
|
-
...tagger,
|
|
203
|
-
timestamp: Math.floor(Date.now() / 1000)
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
// Handle signing
|
|
207
|
-
let signature;
|
|
208
|
-
let finalMessage = formattedMessage;
|
|
209
|
-
if (options?.sign) {
|
|
210
|
-
if (!this.gpgSigner) {
|
|
211
|
-
throw new TagError('GPG signer not available', 'GPG_ERROR', name);
|
|
212
|
-
}
|
|
213
|
-
// Sign the tag content
|
|
214
|
-
const encoder = new TextEncoder();
|
|
215
|
-
signature = await this.gpgSigner.sign(encoder.encode(formattedMessage), options?.keyId);
|
|
216
|
-
// Append signature to message (Git stores signature in the tag object)
|
|
217
|
-
finalMessage = formattedMessage + '\n' + signature;
|
|
218
|
-
}
|
|
219
|
-
// Get the target object type
|
|
220
|
-
const targetType = await this.objectStorage.readObjectType(target);
|
|
221
|
-
// Create tag object
|
|
222
|
-
const tagObj = {
|
|
223
|
-
object: target,
|
|
224
|
-
objectType: targetType || 'commit',
|
|
225
|
-
name,
|
|
226
|
-
tagger,
|
|
227
|
-
message: finalMessage
|
|
228
|
-
};
|
|
229
|
-
// Write tag object and get its SHA
|
|
230
|
-
const tagObjSha = await this.objectStorage.writeTagObject(tagObj);
|
|
231
|
-
// Write ref pointing to tag object
|
|
232
|
-
await this.refStorage.setRef(refName, tagObjSha);
|
|
233
|
-
return {
|
|
234
|
-
name,
|
|
235
|
-
type: 'annotated',
|
|
236
|
-
sha: tagObjSha,
|
|
237
|
-
targetSha: target,
|
|
238
|
-
targetType: targetType || 'commit',
|
|
239
|
-
tagger,
|
|
240
|
-
message: formattedMessage,
|
|
241
|
-
signature
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
else {
|
|
245
|
-
// Lightweight tag - just write ref pointing to target
|
|
246
|
-
await this.refStorage.setRef(refName, target);
|
|
247
|
-
return {
|
|
248
|
-
name,
|
|
249
|
-
type: 'lightweight',
|
|
250
|
-
sha: target
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
finally {
|
|
255
|
-
// Clear the pending lock
|
|
256
|
-
this.pendingCreations.delete(name);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* Delete a tag.
|
|
261
|
-
*
|
|
262
|
-
* @description
|
|
263
|
-
* Removes a tag ref. Does not delete the tag object (if annotated)
|
|
264
|
-
* as it may be referenced elsewhere (reflog, etc.).
|
|
265
|
-
*
|
|
266
|
-
* @param name - Tag name to delete
|
|
267
|
-
* @param options - Deletion options
|
|
268
|
-
* @returns True if tag was deleted, false if it didn't exist (with force)
|
|
269
|
-
* @throws TagError with code 'TAG_NOT_FOUND' if tag doesn't exist (without force)
|
|
270
|
-
*
|
|
271
|
-
* @example
|
|
272
|
-
* ```typescript
|
|
273
|
-
* await manager.deleteTag('v0.9.0-beta')
|
|
274
|
-
*
|
|
275
|
-
* // Delete even if doesn't exist
|
|
276
|
-
* await manager.deleteTag('maybe-exists', { force: true })
|
|
277
|
-
* ```
|
|
278
|
-
*/
|
|
279
|
-
async deleteTag(name, options) {
|
|
280
|
-
const refName = `refs/tags/${name}`;
|
|
281
|
-
// Check if tag exists
|
|
282
|
-
const existingSha = await this.refStorage.getRef(refName);
|
|
283
|
-
if (existingSha === null) {
|
|
284
|
-
if (options?.force) {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
288
|
-
}
|
|
289
|
-
// Delete the ref (but not the tag object)
|
|
290
|
-
await this.refStorage.deleteRef(refName);
|
|
291
|
-
return true;
|
|
292
|
-
}
|
|
293
|
-
/**
|
|
294
|
-
* List all tags.
|
|
295
|
-
*
|
|
296
|
-
* @description
|
|
297
|
-
* Returns tags matching the specified criteria.
|
|
298
|
-
* By default returns all tags sorted by name.
|
|
299
|
-
*
|
|
300
|
-
* @param options - Listing options
|
|
301
|
-
* @returns Array of tags matching criteria
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* ```typescript
|
|
305
|
-
* // List all tags
|
|
306
|
-
* const all = await manager.listTags()
|
|
307
|
-
*
|
|
308
|
-
* // List version tags with metadata
|
|
309
|
-
* const versions = await manager.listTags({
|
|
310
|
-
* pattern: 'v*',
|
|
311
|
-
* sort: 'version',
|
|
312
|
-
* includeMetadata: true
|
|
313
|
-
* })
|
|
314
|
-
*
|
|
315
|
-
* // Get latest 5 tags
|
|
316
|
-
* const latest = await manager.listTags({
|
|
317
|
-
* sort: 'date',
|
|
318
|
-
* sortDirection: 'desc',
|
|
319
|
-
* limit: 5,
|
|
320
|
-
* includeMetadata: true
|
|
321
|
-
* })
|
|
322
|
-
* ```
|
|
323
|
-
*/
|
|
324
|
-
async listTags(options) {
|
|
325
|
-
// List all refs under refs/tags/
|
|
326
|
-
const tagRefs = await this.refStorage.listRefs('refs/tags/');
|
|
327
|
-
// Determine if we need metadata (either explicitly requested or for date sorting)
|
|
328
|
-
const needMetadata = options?.includeMetadata || options?.sort === 'date';
|
|
329
|
-
// Build tag list
|
|
330
|
-
let tags = [];
|
|
331
|
-
for (const refEntry of tagRefs) {
|
|
332
|
-
const tagName = refEntry.name.replace('refs/tags/', '');
|
|
333
|
-
const sha = refEntry.sha;
|
|
334
|
-
// Check if it's an annotated tag by trying to read the tag object
|
|
335
|
-
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
336
|
-
if (tagObj) {
|
|
337
|
-
// Annotated tag
|
|
338
|
-
const tag = {
|
|
339
|
-
name: tagName,
|
|
340
|
-
type: 'annotated',
|
|
341
|
-
sha
|
|
342
|
-
};
|
|
343
|
-
if (needMetadata) {
|
|
344
|
-
tag.targetSha = tagObj.object;
|
|
345
|
-
tag.targetType = tagObj.objectType;
|
|
346
|
-
tag.tagger = tagObj.tagger;
|
|
347
|
-
tag.message = tagObj.message;
|
|
348
|
-
}
|
|
349
|
-
tags.push(tag);
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
// Lightweight tag
|
|
353
|
-
tags.push({
|
|
354
|
-
name: tagName,
|
|
355
|
-
type: 'lightweight',
|
|
356
|
-
sha
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
// Filter by pattern if provided
|
|
361
|
-
if (options?.pattern) {
|
|
362
|
-
tags = filterTagsByPattern(tags, options.pattern);
|
|
363
|
-
}
|
|
364
|
-
// Sort tags
|
|
365
|
-
const sortType = options?.sort || 'name';
|
|
366
|
-
const sortDirection = options?.sortDirection || 'asc';
|
|
367
|
-
if (sortType === 'version') {
|
|
368
|
-
tags = sortTagsByVersion(tags, sortDirection);
|
|
369
|
-
}
|
|
370
|
-
else if (sortType === 'date') {
|
|
371
|
-
// Filter out tags without timestamps when sorting by date
|
|
372
|
-
tags = tags.filter(t => t.tagger?.timestamp !== undefined);
|
|
373
|
-
// Sort by tagger timestamp
|
|
374
|
-
tags.sort((a, b) => {
|
|
375
|
-
const aTime = a.tagger.timestamp;
|
|
376
|
-
const bTime = b.tagger.timestamp;
|
|
377
|
-
const timeDiff = sortDirection === 'asc' ? aTime - bTime : bTime - aTime;
|
|
378
|
-
// Secondary sort by name when timestamps are equal
|
|
379
|
-
if (timeDiff === 0) {
|
|
380
|
-
return a.name.localeCompare(b.name);
|
|
381
|
-
}
|
|
382
|
-
return timeDiff;
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
// Sort by name (default)
|
|
387
|
-
tags.sort((a, b) => {
|
|
388
|
-
const cmp = a.name.localeCompare(b.name);
|
|
389
|
-
return sortDirection === 'asc' ? cmp : -cmp;
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
// Limit results
|
|
393
|
-
if (options?.limit !== undefined) {
|
|
394
|
-
tags = tags.slice(0, options.limit);
|
|
395
|
-
}
|
|
396
|
-
return tags;
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Get a tag by name.
|
|
400
|
-
*
|
|
401
|
-
* @description
|
|
402
|
-
* Retrieves tag information. Use resolve=true to get full
|
|
403
|
-
* annotated tag metadata.
|
|
404
|
-
*
|
|
405
|
-
* @param name - Tag name
|
|
406
|
-
* @param options - Get options
|
|
407
|
-
* @returns Tag info or null if not found
|
|
408
|
-
*
|
|
409
|
-
* @example
|
|
410
|
-
* ```typescript
|
|
411
|
-
* // Quick lookup
|
|
412
|
-
* const tag = await manager.getTag('v1.0.0')
|
|
413
|
-
*
|
|
414
|
-
* // Get full metadata
|
|
415
|
-
* const full = await manager.getTag('v1.0.0', { resolve: true })
|
|
416
|
-
* if (full?.type === 'annotated') {
|
|
417
|
-
* console.log(`Tagged by: ${full.tagger?.name}`)
|
|
418
|
-
* console.log(`Message: ${full.message}`)
|
|
419
|
-
* }
|
|
420
|
-
* ```
|
|
421
|
-
*/
|
|
422
|
-
async getTag(name, options) {
|
|
423
|
-
const refName = `refs/tags/${name}`;
|
|
424
|
-
const sha = await this.refStorage.getRef(refName);
|
|
425
|
-
if (sha === null) {
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
428
|
-
// Try to read as tag object (annotated tag)
|
|
429
|
-
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
430
|
-
if (tagObj) {
|
|
431
|
-
// Annotated tag
|
|
432
|
-
const tag = {
|
|
433
|
-
name,
|
|
434
|
-
type: 'annotated',
|
|
435
|
-
sha
|
|
436
|
-
};
|
|
437
|
-
if (options?.resolve) {
|
|
438
|
-
tag.targetSha = tagObj.object;
|
|
439
|
-
tag.targetType = tagObj.objectType;
|
|
440
|
-
tag.tagger = tagObj.tagger;
|
|
441
|
-
tag.message = tagObj.message;
|
|
442
|
-
// Check for signature in message
|
|
443
|
-
const parsed = parseTagMessage(tagObj.message);
|
|
444
|
-
if (parsed.signature) {
|
|
445
|
-
tag.signature = parsed.signature;
|
|
446
|
-
tag.message = parsed.message;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return tag;
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
// Lightweight tag
|
|
453
|
-
return {
|
|
454
|
-
name,
|
|
455
|
-
type: 'lightweight',
|
|
456
|
-
sha
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
/**
|
|
461
|
-
* Check if a tag exists.
|
|
462
|
-
*
|
|
463
|
-
* @description
|
|
464
|
-
* Quick check for tag existence without fetching full info.
|
|
465
|
-
*
|
|
466
|
-
* @param name - Tag name
|
|
467
|
-
* @returns True if tag exists
|
|
468
|
-
*
|
|
469
|
-
* @example
|
|
470
|
-
* ```typescript
|
|
471
|
-
* if (await manager.tagExists('v1.0.0')) {
|
|
472
|
-
* console.log('Tag already exists')
|
|
473
|
-
* }
|
|
474
|
-
* ```
|
|
475
|
-
*/
|
|
476
|
-
async tagExists(name) {
|
|
477
|
-
const refName = `refs/tags/${name}`;
|
|
478
|
-
const sha = await this.refStorage.getRef(refName);
|
|
479
|
-
return sha !== null;
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Get the target (commit SHA) that a tag points to.
|
|
483
|
-
*
|
|
484
|
-
* @description
|
|
485
|
-
* Resolves through annotated tags to get the final target.
|
|
486
|
-
* For lightweight tags, returns the sha directly.
|
|
487
|
-
* For annotated tags, returns the targetSha.
|
|
488
|
-
*
|
|
489
|
-
* @param name - Tag name
|
|
490
|
-
* @returns Target commit SHA
|
|
491
|
-
* @throws TagError with code 'TAG_NOT_FOUND' if tag doesn't exist
|
|
492
|
-
*
|
|
493
|
-
* @example
|
|
494
|
-
* ```typescript
|
|
495
|
-
* const commitSha = await manager.getTagTarget('v1.0.0')
|
|
496
|
-
* ```
|
|
497
|
-
*/
|
|
498
|
-
async getTagTarget(name) {
|
|
499
|
-
const refName = `refs/tags/${name}`;
|
|
500
|
-
let sha = await this.refStorage.getRef(refName);
|
|
501
|
-
if (sha === null) {
|
|
502
|
-
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
503
|
-
}
|
|
504
|
-
// Resolve through tag objects to get final commit
|
|
505
|
-
let depth = 0;
|
|
506
|
-
const maxDepth = 10;
|
|
507
|
-
while (depth < maxDepth) {
|
|
508
|
-
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
509
|
-
if (!tagObj) {
|
|
510
|
-
// Not a tag object, this is the final target
|
|
511
|
-
return sha;
|
|
512
|
-
}
|
|
513
|
-
// Follow the tag to its target
|
|
514
|
-
sha = tagObj.object;
|
|
515
|
-
depth++;
|
|
516
|
-
}
|
|
517
|
-
return sha;
|
|
518
|
-
}
|
|
519
|
-
/**
|
|
520
|
-
* Verify a tag's GPG signature.
|
|
521
|
-
*
|
|
522
|
-
* @description
|
|
523
|
-
* Verifies the GPG signature on a signed annotated tag.
|
|
524
|
-
* Returns verification result with signer info.
|
|
525
|
-
*
|
|
526
|
-
* @param name - Tag name to verify
|
|
527
|
-
* @returns Verification result
|
|
528
|
-
* @throws TagError with code 'TAG_NOT_FOUND' if tag doesn't exist
|
|
529
|
-
*
|
|
530
|
-
* @example
|
|
531
|
-
* ```typescript
|
|
532
|
-
* const result = await manager.verifyTag('v1.0.0')
|
|
533
|
-
* if (result.valid) {
|
|
534
|
-
* console.log(`Signed by: ${result.signer}`)
|
|
535
|
-
* console.log(`Trust: ${result.trustLevel}`)
|
|
536
|
-
* } else {
|
|
537
|
-
* console.log(`Verification failed: ${result.error}`)
|
|
538
|
-
* }
|
|
539
|
-
* ```
|
|
540
|
-
*/
|
|
541
|
-
async verifyTag(name) {
|
|
542
|
-
const refName = `refs/tags/${name}`;
|
|
543
|
-
const sha = await this.refStorage.getRef(refName);
|
|
544
|
-
if (sha === null) {
|
|
545
|
-
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
546
|
-
}
|
|
547
|
-
// Get tag object
|
|
548
|
-
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
549
|
-
if (!tagObj) {
|
|
550
|
-
// Lightweight tag - cannot be signed
|
|
551
|
-
return { valid: false };
|
|
552
|
-
}
|
|
553
|
-
// Parse message to check for signature
|
|
554
|
-
const parsed = parseTagMessage(tagObj.message);
|
|
555
|
-
if (!parsed.signature) {
|
|
556
|
-
// No signature
|
|
557
|
-
return { valid: false };
|
|
558
|
-
}
|
|
559
|
-
// Verify signature using GPG signer
|
|
560
|
-
if (!this.gpgSigner) {
|
|
561
|
-
return { valid: false, error: 'GPG signer not available' };
|
|
562
|
-
}
|
|
563
|
-
const encoder = new TextEncoder();
|
|
564
|
-
return this.gpgSigner.verify(encoder.encode(parsed.message), parsed.signature);
|
|
565
|
-
}
|
|
566
|
-
/**
|
|
567
|
-
* Check if a tag is annotated.
|
|
568
|
-
*
|
|
569
|
-
* @description
|
|
570
|
-
* Determines if a tag is annotated (has a tag object) or lightweight.
|
|
571
|
-
*
|
|
572
|
-
* @param name - Tag name
|
|
573
|
-
* @returns True if the tag is annotated
|
|
574
|
-
* @throws TagError with code 'TAG_NOT_FOUND' if tag doesn't exist
|
|
575
|
-
*
|
|
576
|
-
* @example
|
|
577
|
-
* ```typescript
|
|
578
|
-
* if (await manager.isAnnotatedTag('v1.0.0')) {
|
|
579
|
-
* console.log('This is an annotated tag')
|
|
580
|
-
* }
|
|
581
|
-
* ```
|
|
582
|
-
*/
|
|
583
|
-
async isAnnotatedTag(name) {
|
|
584
|
-
const refName = `refs/tags/${name}`;
|
|
585
|
-
const sha = await this.refStorage.getRef(refName);
|
|
586
|
-
if (sha === null) {
|
|
587
|
-
throw new TagError(`Tag not found: ${name}`, 'TAG_NOT_FOUND', name);
|
|
588
|
-
}
|
|
589
|
-
// Try to read as tag object
|
|
590
|
-
const tagObj = await this.objectStorage.readTagObject(sha);
|
|
591
|
-
return tagObj !== null;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
// ============================================================================
|
|
595
|
-
// Validation Functions
|
|
596
|
-
// ============================================================================
|
|
597
|
-
/**
|
|
598
|
-
* Validate a tag name according to Git rules.
|
|
599
|
-
*
|
|
600
|
-
* @description
|
|
601
|
-
* Tags follow the same rules as refs but under refs/tags/.
|
|
602
|
-
* This validates against the full git-check-ref-format rules.
|
|
603
|
-
*
|
|
604
|
-
* **Rules**:
|
|
605
|
-
* - Cannot be empty
|
|
606
|
-
* - Cannot end with '/' or '.lock'
|
|
607
|
-
* - Cannot contain '..', '@{', control chars, space, ~, ^, :, ?, *, [, \
|
|
608
|
-
* - Components cannot start or end with '.'
|
|
609
|
-
*
|
|
610
|
-
* Note: This is a stub implementation. Full validation will be added in GREEN phase.
|
|
611
|
-
*
|
|
612
|
-
* @param name - Tag name to validate
|
|
613
|
-
* @returns True if valid
|
|
614
|
-
*
|
|
615
|
-
* @see https://git-scm.com/docs/git-check-ref-format
|
|
616
|
-
*
|
|
617
|
-
* @example
|
|
618
|
-
* ```typescript
|
|
619
|
-
* isValidTagName('v1.0.0') // true
|
|
620
|
-
* isValidTagName('release/1.0') // true
|
|
621
|
-
* isValidTagName('v1.0.0.lock') // false (ends with .lock)
|
|
622
|
-
* isValidTagName('v1..0') // false (contains ..)
|
|
623
|
-
* isValidTagName('') // false (empty)
|
|
624
|
-
* ```
|
|
625
|
-
*/
|
|
626
|
-
export function isValidTagName(name) {
|
|
627
|
-
// Empty name is invalid
|
|
628
|
-
if (!name || name.length === 0 || name.trim().length === 0) {
|
|
629
|
-
return false;
|
|
630
|
-
}
|
|
631
|
-
// Cannot end with .lock
|
|
632
|
-
if (name.endsWith('.lock')) {
|
|
633
|
-
return false;
|
|
634
|
-
}
|
|
635
|
-
// Cannot contain ..
|
|
636
|
-
if (name.includes('..')) {
|
|
637
|
-
return false;
|
|
638
|
-
}
|
|
639
|
-
// Cannot contain @{
|
|
640
|
-
if (name.includes('@{')) {
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
// Cannot contain control characters (ASCII 0-31, 127), space, ~, ^, :, ?, *, [, \
|
|
644
|
-
const invalidChars = /[\x00-\x1f\x7f ~^:?*[\]\\]/;
|
|
645
|
-
if (invalidChars.test(name)) {
|
|
646
|
-
return false;
|
|
647
|
-
}
|
|
648
|
-
// Cannot end with /
|
|
649
|
-
if (name.endsWith('/')) {
|
|
650
|
-
return false;
|
|
651
|
-
}
|
|
652
|
-
// Split into components and check each
|
|
653
|
-
const components = name.split('/');
|
|
654
|
-
for (const component of components) {
|
|
655
|
-
// Cannot have empty components (// in path)
|
|
656
|
-
if (component.length === 0) {
|
|
657
|
-
return false;
|
|
658
|
-
}
|
|
659
|
-
// Cannot start with .
|
|
660
|
-
if (component.startsWith('.')) {
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
// Cannot end with .
|
|
664
|
-
if (component.endsWith('.')) {
|
|
665
|
-
return false;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
return true;
|
|
669
|
-
}
|
|
670
|
-
/**
|
|
671
|
-
* Type guard for annotated tags.
|
|
672
|
-
*
|
|
673
|
-
* @description
|
|
674
|
-
* Checks if a tag is annotated with full metadata.
|
|
675
|
-
* Narrows the type to include tagger and message.
|
|
676
|
-
*
|
|
677
|
-
* @param tag - Tag to check
|
|
678
|
-
* @returns True if the tag is annotated with full metadata
|
|
679
|
-
*
|
|
680
|
-
* @example
|
|
681
|
-
* ```typescript
|
|
682
|
-
* if (isAnnotatedTag(tag)) {
|
|
683
|
-
* // tag.tagger and tag.message are now guaranteed
|
|
684
|
-
* console.log(`Tagged by: ${tag.tagger.name}`)
|
|
685
|
-
* }
|
|
686
|
-
* ```
|
|
687
|
-
*/
|
|
688
|
-
export function isAnnotatedTag(tag) {
|
|
689
|
-
return tag.type === 'annotated' &&
|
|
690
|
-
tag.tagger !== undefined &&
|
|
691
|
-
tag.message !== undefined;
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Format a tag message.
|
|
695
|
-
*
|
|
696
|
-
* @description
|
|
697
|
-
* Normalizes a tag message: handles line endings, trims whitespace,
|
|
698
|
-
* ensures proper formatting.
|
|
699
|
-
*
|
|
700
|
-
* @param message - Raw message input
|
|
701
|
-
* @returns Formatted message
|
|
702
|
-
*
|
|
703
|
-
* @example
|
|
704
|
-
* ```typescript
|
|
705
|
-
* formatTagMessage(' Hello World \r\n') // 'Hello World\n'
|
|
706
|
-
* ```
|
|
707
|
-
*/
|
|
708
|
-
export function formatTagMessage(message) {
|
|
709
|
-
// Normalize line endings (CRLF -> LF)
|
|
710
|
-
let formatted = message.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
|
|
711
|
-
// Trim leading and trailing whitespace, but preserve internal structure
|
|
712
|
-
// The test expects 'Hello\r\nWorld\r\n' to become 'Hello\nWorld\n'
|
|
713
|
-
// So we trim leading whitespace, convert line endings, but preserve trailing newline if present
|
|
714
|
-
const hadTrailingNewline = formatted.endsWith('\n');
|
|
715
|
-
formatted = formatted.trim();
|
|
716
|
-
if (hadTrailingNewline && formatted.length > 0) {
|
|
717
|
-
formatted += '\n';
|
|
718
|
-
}
|
|
719
|
-
return formatted;
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Parse a tag message from raw content.
|
|
723
|
-
*
|
|
724
|
-
* @description
|
|
725
|
-
* Separates the message from any GPG signature.
|
|
726
|
-
* GPG signatures start with '-----BEGIN PGP SIGNATURE-----'.
|
|
727
|
-
*
|
|
728
|
-
* @param content - Raw tag content
|
|
729
|
-
* @returns Parsed message and optional signature
|
|
730
|
-
*
|
|
731
|
-
* @example
|
|
732
|
-
* ```typescript
|
|
733
|
-
* const { message, signature } = parseTagMessage(content)
|
|
734
|
-
* if (signature) {
|
|
735
|
-
* console.log('Tag is signed')
|
|
736
|
-
* }
|
|
737
|
-
* ```
|
|
738
|
-
*/
|
|
739
|
-
export function parseTagMessage(content) {
|
|
740
|
-
const sigMarker = '-----BEGIN PGP SIGNATURE-----';
|
|
741
|
-
const sigIndex = content.indexOf(sigMarker);
|
|
742
|
-
if (sigIndex === -1) {
|
|
743
|
-
// No signature
|
|
744
|
-
return { message: content.trim() };
|
|
745
|
-
}
|
|
746
|
-
// Split message and signature
|
|
747
|
-
const message = content.slice(0, sigIndex).trim();
|
|
748
|
-
const signature = content.slice(sigIndex);
|
|
749
|
-
return { message, signature };
|
|
750
|
-
}
|
|
751
|
-
// ============================================================================
|
|
752
|
-
// Convenience Functions
|
|
753
|
-
// ============================================================================
|
|
754
|
-
/**
|
|
755
|
-
* Create a tag (lightweight or annotated).
|
|
756
|
-
*
|
|
757
|
-
* @description
|
|
758
|
-
* Convenience function that wraps TagManager.createTag.
|
|
759
|
-
*
|
|
760
|
-
* @param manager - TagManager instance
|
|
761
|
-
* @param name - Tag name
|
|
762
|
-
* @param target - Target SHA to tag
|
|
763
|
-
* @param options - Creation options
|
|
764
|
-
* @returns Created tag
|
|
765
|
-
*
|
|
766
|
-
* @example
|
|
767
|
-
* ```typescript
|
|
768
|
-
* const tag = await createTag(manager, 'v1.0.0', commitSha, {
|
|
769
|
-
* annotated: true,
|
|
770
|
-
* message: 'Release 1.0.0'
|
|
771
|
-
* })
|
|
772
|
-
* ```
|
|
773
|
-
*/
|
|
774
|
-
export async function createTag(manager, name, target, options) {
|
|
775
|
-
return manager.createTag(name, target, options);
|
|
776
|
-
}
|
|
777
|
-
/**
|
|
778
|
-
* Create an annotated tag with message.
|
|
779
|
-
*
|
|
780
|
-
* @description
|
|
781
|
-
* Convenience function for creating annotated tags.
|
|
782
|
-
* Automatically sets annotated=true.
|
|
783
|
-
*
|
|
784
|
-
* @param manager - TagManager instance
|
|
785
|
-
* @param name - Tag name
|
|
786
|
-
* @param target - Target SHA to tag
|
|
787
|
-
* @param message - Tag message
|
|
788
|
-
* @param tagger - Tagger information
|
|
789
|
-
* @param options - Additional options (excluding annotated, message, tagger)
|
|
790
|
-
* @returns Created annotated tag
|
|
791
|
-
*
|
|
792
|
-
* @example
|
|
793
|
-
* ```typescript
|
|
794
|
-
* const tag = await createAnnotatedTag(
|
|
795
|
-
* manager,
|
|
796
|
-
* 'v1.0.0',
|
|
797
|
-
* commitSha,
|
|
798
|
-
* 'Release 1.0.0',
|
|
799
|
-
* { name: 'Alice', email: 'alice@example.com', timestamp: Date.now()/1000, timezone: '+0000' }
|
|
800
|
-
* )
|
|
801
|
-
* ```
|
|
802
|
-
*/
|
|
803
|
-
export async function createAnnotatedTag(manager, name, target, message, tagger, options) {
|
|
804
|
-
return manager.createTag(name, target, {
|
|
805
|
-
...options,
|
|
806
|
-
annotated: true,
|
|
807
|
-
message,
|
|
808
|
-
tagger
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
/**
|
|
812
|
-
* Delete a tag.
|
|
813
|
-
*
|
|
814
|
-
* @description
|
|
815
|
-
* Convenience function that wraps TagManager.deleteTag.
|
|
816
|
-
*
|
|
817
|
-
* @param manager - TagManager instance
|
|
818
|
-
* @param name - Tag name to delete
|
|
819
|
-
* @param options - Deletion options
|
|
820
|
-
* @returns True if deleted
|
|
821
|
-
*
|
|
822
|
-
* @example
|
|
823
|
-
* ```typescript
|
|
824
|
-
* await deleteTag(manager, 'v0.9.0-beta')
|
|
825
|
-
* ```
|
|
826
|
-
*/
|
|
827
|
-
export async function deleteTag(manager, name, options) {
|
|
828
|
-
return manager.deleteTag(name, options);
|
|
829
|
-
}
|
|
830
|
-
/**
|
|
831
|
-
* List all tags.
|
|
832
|
-
*
|
|
833
|
-
* @description
|
|
834
|
-
* Convenience function that wraps TagManager.listTags.
|
|
835
|
-
*
|
|
836
|
-
* @param manager - TagManager instance
|
|
837
|
-
* @param options - Listing options
|
|
838
|
-
* @returns Array of tags
|
|
839
|
-
*
|
|
840
|
-
* @example
|
|
841
|
-
* ```typescript
|
|
842
|
-
* const tags = await listTags(manager, { pattern: 'v1.*' })
|
|
843
|
-
* ```
|
|
844
|
-
*/
|
|
845
|
-
export async function listTags(manager, options) {
|
|
846
|
-
return manager.listTags(options);
|
|
847
|
-
}
|
|
848
|
-
/**
|
|
849
|
-
* Get a tag by name.
|
|
850
|
-
*
|
|
851
|
-
* @description
|
|
852
|
-
* Convenience function that wraps TagManager.getTag.
|
|
853
|
-
*
|
|
854
|
-
* @param manager - TagManager instance
|
|
855
|
-
* @param name - Tag name
|
|
856
|
-
* @param options - Get options
|
|
857
|
-
* @returns Tag info or null
|
|
858
|
-
*
|
|
859
|
-
* @example
|
|
860
|
-
* ```typescript
|
|
861
|
-
* const tag = await getTag(manager, 'v1.0.0', { resolve: true })
|
|
862
|
-
* ```
|
|
863
|
-
*/
|
|
864
|
-
export async function getTag(manager, name, options) {
|
|
865
|
-
return manager.getTag(name, options);
|
|
866
|
-
}
|
|
867
|
-
/**
|
|
868
|
-
* Check if a tag is annotated.
|
|
869
|
-
*
|
|
870
|
-
* @description
|
|
871
|
-
* Convenience function that wraps TagManager.isAnnotatedTag.
|
|
872
|
-
*
|
|
873
|
-
* @param manager - TagManager instance
|
|
874
|
-
* @param name - Tag name
|
|
875
|
-
* @returns True if annotated
|
|
876
|
-
*
|
|
877
|
-
* @example
|
|
878
|
-
* ```typescript
|
|
879
|
-
* if (await checkIsAnnotatedTag(manager, 'v1.0.0')) {
|
|
880
|
-
* console.log('Annotated tag')
|
|
881
|
-
* }
|
|
882
|
-
* ```
|
|
883
|
-
*/
|
|
884
|
-
export async function checkIsAnnotatedTag(manager, name) {
|
|
885
|
-
return manager.isAnnotatedTag(name);
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Verify a tag's signature.
|
|
889
|
-
*
|
|
890
|
-
* @description
|
|
891
|
-
* Convenience function that wraps TagManager.verifyTag.
|
|
892
|
-
*
|
|
893
|
-
* @param manager - TagManager instance
|
|
894
|
-
* @param name - Tag name
|
|
895
|
-
* @returns Verification result
|
|
896
|
-
*
|
|
897
|
-
* @example
|
|
898
|
-
* ```typescript
|
|
899
|
-
* const result = await verifyTagSignature(manager, 'v1.0.0')
|
|
900
|
-
* ```
|
|
901
|
-
*/
|
|
902
|
-
export async function verifyTagSignature(manager, name) {
|
|
903
|
-
return manager.verifyTag(name);
|
|
904
|
-
}
|
|
905
|
-
/**
|
|
906
|
-
* Get the target commit SHA for a tag.
|
|
907
|
-
*
|
|
908
|
-
* @description
|
|
909
|
-
* Convenience function that wraps TagManager.getTagTarget.
|
|
910
|
-
*
|
|
911
|
-
* @param manager - TagManager instance
|
|
912
|
-
* @param name - Tag name
|
|
913
|
-
* @returns Target commit SHA
|
|
914
|
-
*
|
|
915
|
-
* @example
|
|
916
|
-
* ```typescript
|
|
917
|
-
* const sha = await getTagTarget(manager, 'v1.0.0')
|
|
918
|
-
* ```
|
|
919
|
-
*/
|
|
920
|
-
export async function getTagTarget(manager, name) {
|
|
921
|
-
return manager.getTagTarget(name);
|
|
922
|
-
}
|
|
923
|
-
/**
|
|
924
|
-
* Sort tags by semantic version.
|
|
925
|
-
*
|
|
926
|
-
* @description
|
|
927
|
-
* Sorts tags that look like semantic versions (v1.2.3).
|
|
928
|
-
* Non-semver tags are sorted lexicographically at the end.
|
|
929
|
-
*
|
|
930
|
-
* @param tags - Array of tags to sort
|
|
931
|
-
* @param direction - Sort direction ('asc' or 'desc')
|
|
932
|
-
* @returns Sorted array of tags
|
|
933
|
-
*
|
|
934
|
-
* @example
|
|
935
|
-
* ```typescript
|
|
936
|
-
* const sorted = sortTagsByVersion(tags, 'desc')
|
|
937
|
-
* // ['v2.0.0', 'v1.10.0', 'v1.9.0', 'v1.0.0', ...]
|
|
938
|
-
* ```
|
|
939
|
-
*/
|
|
940
|
-
export function sortTagsByVersion(tags, direction = 'asc') {
|
|
941
|
-
// Parse version from tag name (handles v1.2.3, 1.2.3, v1.2.3-beta, etc.)
|
|
942
|
-
const parseVersion = (name) => {
|
|
943
|
-
// Remove 'v' prefix if present
|
|
944
|
-
const normalized = name.startsWith('v') ? name.slice(1) : name;
|
|
945
|
-
// Extract numeric version parts (split on non-digit, non-dot)
|
|
946
|
-
const parts = normalized.split(/[^0-9.]/)[0].split('.');
|
|
947
|
-
return parts.map(p => parseInt(p, 10) || 0);
|
|
948
|
-
};
|
|
949
|
-
const compareVersions = (a, b) => {
|
|
950
|
-
const maxLen = Math.max(a.length, b.length);
|
|
951
|
-
for (let i = 0; i < maxLen; i++) {
|
|
952
|
-
const aVal = a[i] || 0;
|
|
953
|
-
const bVal = b[i] || 0;
|
|
954
|
-
if (aVal !== bVal) {
|
|
955
|
-
return aVal - bVal;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
return 0;
|
|
959
|
-
};
|
|
960
|
-
const sorted = [...tags].sort((a, b) => {
|
|
961
|
-
const aVer = parseVersion(a.name);
|
|
962
|
-
const bVer = parseVersion(b.name);
|
|
963
|
-
const cmp = compareVersions(aVer, bVer);
|
|
964
|
-
return direction === 'asc' ? cmp : -cmp;
|
|
965
|
-
});
|
|
966
|
-
return sorted;
|
|
967
|
-
}
|
|
968
|
-
/**
|
|
969
|
-
* Filter tags by glob pattern.
|
|
970
|
-
*
|
|
971
|
-
* @description
|
|
972
|
-
* Filters tags matching a glob pattern.
|
|
973
|
-
* Supports * (any chars) and ? (single char) wildcards.
|
|
974
|
-
*
|
|
975
|
-
* @param tags - Array of tags to filter
|
|
976
|
-
* @param pattern - Glob pattern (e.g., 'v1.*', 'release-*')
|
|
977
|
-
* @returns Filtered array of tags
|
|
978
|
-
*
|
|
979
|
-
* @example
|
|
980
|
-
* ```typescript
|
|
981
|
-
* const v1Tags = filterTagsByPattern(tags, 'v1.*')
|
|
982
|
-
* ```
|
|
983
|
-
*/
|
|
984
|
-
export function filterTagsByPattern(tags, pattern) {
|
|
985
|
-
// Convert glob pattern to regex
|
|
986
|
-
// * matches any number of characters
|
|
987
|
-
// ? matches a single character
|
|
988
|
-
// Escape special regex characters except * and ?
|
|
989
|
-
const regexPattern = pattern
|
|
990
|
-
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
|
|
991
|
-
.replace(/\*/g, '.*') // * -> .*
|
|
992
|
-
.replace(/\?/g, '.'); // ? -> .
|
|
993
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
994
|
-
return tags.filter(tag => regex.test(tag.name));
|
|
995
|
-
}
|
|
996
|
-
//# sourceMappingURL=tag.js.map
|