Skip to content

Commit a2be188

Browse files
authored
Merge pull request #1 from git-stunts/hexagonal-architecture
feat: multi-runtime support and hexagonal architecture hardening
2 parents 164ad26 + 3e64f3c commit a2be188

100 files changed

Lines changed: 8669 additions & 207 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "Bun (Git Stunts Plumbing)",
3+
"build": {
4+
"dockerfile": "../../Dockerfile.bun",
5+
"context": "../.."
6+
},
7+
"customizations": {
8+
"vscode": {
9+
"extensions": [
10+
"oven.bun-vscode",
11+
"dbaeumer.vscode-eslint",
12+
"esbenp.prettier-vscode"
13+
]
14+
}
15+
},
16+
"postCreateCommand": "bun install",
17+
"remoteUser": "root"
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "Deno (Git Stunts Plumbing)",
3+
"build": {
4+
"dockerfile": "../../Dockerfile.deno",
5+
"context": "../.."
6+
},
7+
"customizations": {
8+
"vscode": {
9+
"settings": {
10+
"deno.enable": true,
11+
"deno.lint": true,
12+
"deno.unstable": true
13+
},
14+
"extensions": [
15+
"denoland.vscode-deno"
16+
]
17+
}
18+
},
19+
"remoteUser": "root"
20+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "Node.js (Git Stunts Plumbing)",
3+
"build": {
4+
"dockerfile": "../../Dockerfile.node",
5+
"context": "../.."
6+
},
7+
"customizations": {
8+
"vscode": {
9+
"extensions": [
10+
"dbaeumer.vscode-eslint",
11+
"esbenp.prettier-vscode",
12+
"vitest.explorer"
13+
]
14+
}
15+
},
16+
"postCreateCommand": "npm install",
17+
"remoteUser": "root"
18+
}

.dockerignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules
2+
.git
3+
.crush
4+
scripts
5+
docker-compose.yml
6+
Dockerfile.*

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Use Node.js
15+
uses: actions/setup-node@v4
16+
with:
17+
node-version: '20'
18+
cache: 'npm'
19+
- run: npm install
20+
- run: npm run lint
21+
22+
test-multi-runtime:
23+
runs-on: ubuntu-latest
24+
steps:
25+
- uses: actions/checkout@v4
26+
- name: Use Node.js
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: '20'
30+
cache: 'npm'
31+
- run: npm install
32+
- name: Run multi-runtime tests in Docker
33+
run: npm test

ARCHITECTURE.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Architecture & Design
2+
3+
This project is built as a robust, low-level building block for Git-based applications. It follows strict engineering standards to ensure it is the most reliable Git plumbing library in the JavaScript ecosystem.
4+
5+
## 🏗️ Hexagonal Architecture (Ports & Adapters)
6+
7+
The codebase is strictly partitioned into three layers:
8+
9+
### 1. The Domain (Core)
10+
Contains the business logic, entities, and value objects. It is **pure** and has zero dependencies on infrastructure or specific runtimes.
11+
- **Entities**: `GitCommit`, `GitTree`, `GitBlob`.
12+
- **Value Objects**: `GitSha`, `GitRef`, `GitFileMode`, `GitSignature`.
13+
- **Services**: `CommandSanitizer` (security), `ExecutionOrchestrator` (retry/backoff), `GitErrorClassifier`, `GitPersistenceService`, `ByteMeasurer`.
14+
15+
### 2. The Ports (Contracts)
16+
Functional interfaces that define how the domain interacts with the outside world.
17+
- **`CommandRunner`**: A functional port defined in `src/ports/`. It enforces a strict contract: every command must return a `stdoutStream` and an `exitPromise`.
18+
19+
## 💉 Dependency Injection
20+
21+
Core services (`CommandSanitizer`, `ExecutionOrchestrator`) are designed as injectable instances. This allows developers to:
22+
- Provide custom sanitization rules.
23+
- Inject mock orchestrators for testing failure modes.
24+
- Extend the `GitErrorClassifier` for specialized error handling.
25+
26+
## 🛡️ Defense-in-Depth Validation
27+
28+
We use **Zod** as our single source of truth for validation.
29+
- **Schema Location**: All schemas reside in `src/domain/schemas/`.
30+
- **Strict Enforcement**: No Entity or Value Object can be instantiated with invalid data. This ensures that errors are caught at the boundary, before any shell process is spawned.
31+
- **JSON Schema Ready**: The Zod schemas are designed to be easily exportable to standard JSON schemas for cross-system interoperability.
32+
33+
## 🌊 Streaming-Only Model
34+
35+
In version 2.0.0, we eliminated the "buffered" execution path in the infrastructure layer.
36+
- **Consistency**: Every runner behaves exactly the same way.
37+
- **Memory Safety**: Large outputs (like `cat-file` on a massive blob) never hit the heap unless explicitly requested via `collect()`.
38+
- **OOM Protection**: The `collect()` method enforces a `maxBytes` limit, preventing malicious or accidental memory exhaustion.
39+
40+
## 🧩 Engineering Mandates
41+
42+
1. **One File = One Class**: Every file in `src/` represents a single logical concept. No "utils.js" or "types.js" dumping grounds.
43+
2. **Total JSDoc**: 100% of the public API is documented with JSDoc, enabling excellent IDE intellisense and automated documentation generation.
44+
3. **Immutability**: All Value Objects are immutable. Operations that "change" a state (like `GitTree.addEntry`) return a new instance.
45+
4. **No Magic Literals**: Constants like the `Empty Tree SHA`, default timeouts (120s), and buffer limits are exported from the port layer.
46+
47+
## 🧪 Quality Assurance
48+
49+
- **Multi-Runtime CI**: We don't just "test in Node". Our CI environment (via Docker Compose) runs the exact same test suite in Bun and Deno simultaneously.
50+
- **Tests as Spec**: Our tests define the behavior of the system. A change in logic requires a change in the corresponding test to ensure the "red -> green" story is preserved.

CHANGELOG.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [2.7.0] - 2026-01-07
9+
10+
### Added
11+
- **GitRepositoryService.save()**: Introduced a polymorphic persistence method that automatically delegates to the appropriate low-level operation based on the entity type (Blob, Tree, or Commit).
12+
- **Commit Lifecycle Guide**: Created `docs/COMMIT_LIFECYCLE.md`, a step-by-step tutorial covering manual graph construction and persistence.
13+
14+
### Changed
15+
- **Documentation Overhaul**: Updated `README.md` with enhanced security details and prominent links to the new lifecycle guide.
16+
- **Process Isolation**: Hardened shell runners with strict environment variable whitelisting and support for per-call overrides.
17+
- **Runtime Optimization**: Updated `ByteMeasurer` to use `Buffer.byteLength` where available and pinned Deno to 2.6.3 in development environments.
18+
- **Improved Validation**: Enhanced `GitRefSchema` to strictly follow Git's naming rules, including better handling of control characters and '@' symbol sequences.
19+
20+
### Fixed
21+
- **Node.js Shell Stability**: Resolved a critical bug in `NodeShellRunner` where processes were killed immediately if no timeout was specified.
22+
- **Backoff Logic**: Fixed an off-by-one error in `ExecutionOrchestrator` that caused incorrect delay calculations during retries.
23+
- **Type Safety**: Added type validation to `CommandSanitizer` to prevent `TypeError` when receiving non-string arguments.
24+
- **Object Mapping**: Fixed a bug in `GitObjectType` where delta types were incorrectly mapped to strings instead of integers.
25+
- **CI/CD Reliability**: Fixed GitHub Actions workflow by adding missing Node.js setup and dependency installation steps to the multi-runtime test job.
26+
- **Persistence Accuracy**: Fixed incorrect tree entry type detection in `GitPersistenceService` that could cause tree corruption.
27+
28+
## [2.5.0] - 2026-01-05
29+
30+
### Added
31+
- **GitCommandBuilder Fluent API**: Added static factory methods for all whitelisted Git commands (e.g., `.hashObject()`, `.catFile()`, `.writeTree()`) and fluent flag methods (e.g., `.stdin()`, `.write()`, `.pretty()`) for a more expressive command-building experience.
32+
33+
### Changed
34+
- **GitPlumbing DI Support**: Updated the constructor to accept optional `sanitizer` and `orchestrator` instances, enabling full Dependency Injection for easier testing and customization of core logic.
35+
36+
## [2.4.0] - 2026-01-03
37+
38+
### Added
39+
- **GitErrorClassifier**: Extracted error categorization logic from the orchestrator into a dedicated domain service. Uses regex and exit codes (e.g., 128) to identify lock contention and state issues.
40+
- **ProhibitedFlagError**: New specialized error thrown when restricted Git flags (like `--work-tree`) are detected, providing remediation guidance and documentation links.
41+
- **Dynamic Command Registration**: Added `CommandSanitizer.allow(commandName)` to permit runtime extension of the allowed plumbing command list.
42+
43+
### Changed
44+
- **Dependency Injection (DI)**: Refactored `CommandSanitizer` and `ExecutionOrchestrator` into injectable class instances, improving testability and modularity of the `GitPlumbing` core.
45+
- **Sanitizer Memoization**: Implemented an internal LRU-ish cache in `CommandSanitizer` to skip re-validation of identical repetitive commands, improving performance for high-frequency operations.
46+
- **Enhanced Deno Shim**: Updated the test shim to include `beforeEach`, `afterEach`, and other lifecycle hooks for full parity with Vitest.
47+
48+
## [2.3.0] - 2026-01-01
49+
50+
### Changed
51+
- **Validation Unification**: Completed the migration from `ajv` to `zod` for the entire library, reducing bundle size and unifying the type-safety engine.
52+
- **Security Hardening**: Expanded the `EnvironmentPolicy` whitelist to include `GIT_AUTHOR_TZ`, `GIT_COMMITTER_TZ`, and localization variables (`LANG`, `LC_ALL`, etc.) to ensure identity and encoding consistency.
53+
- **Universal Testing**: Updated the multi-runtime test suite to ensure 100% test parity across Node.js, Bun, and Deno, specifically adding missing builder and environment tests.
54+
55+
### Added
56+
- **EnvironmentPolicy**: Extracted environment variable whitelisting into a dedicated domain service used by all shell runners.
57+
58+
## [2.2.0] - 2025-12-28
59+
60+
### Added
61+
- **ExecutionOrchestrator**: Extracted command execution lifecycle (retry, backoff, lock detection) into a dedicated domain service to improve SRP compliance.
62+
- **Binary Stream Support**: Refactored `GitStream.collect()` to support raw `Uint8Array` accumulation, preventing corruption of non-UTF8 binary data (e.g., blobs, compressed trees).
63+
- **GitRepositoryLockedError**: Introduced a specialized error for repository lock contention with remediation guidance.
64+
- **CommandRetryPolicy**: Added a new value object to encapsulate configurable retry strategies and backoff logic.
65+
- **Custom Runner Registration**: Added `ShellRunnerFactory.register()` to allow developers to inject custom shell execution logic (e.g., SSH, WASM).
66+
- **Environment Overrides**: `GitPlumbing.createDefault()` and `ShellRunnerFactory.create()` now support explicit environment overrides.
67+
- **Repository Factory**: Added `GitPlumbing.createRepository()` for single-line high-level service instantiation.
68+
- **Workflow Recipes**: Created `docs/RECIPES.md` providing step-by-step guides for low-level Git workflows (e.g., 'Commit from Scratch').
69+
70+
### Changed
71+
- **Memory Optimization**: Enhanced `GitStream.collect()` to use chunk-based accumulation with `Uint8Array.set()`, reducing redundant string allocations during collection.
72+
- **Runtime Performance**: Optimized `ByteMeasurer` to use `Buffer.byteLength()` in Node.js and Bun, significantly improving performance for large string measurements.
73+
- **Development Tooling**: Upgraded `vitest` to version 3.0.0 for improved testing capabilities and performance.
74+
75+
## [2.1.0] - 2025-12-20
76+
77+
### Added
78+
- **GitRepositoryService**: Extracted high-level repository operations (`revParse`, `updateRef`, `deleteRef`) into a dedicated domain service.
79+
- **Resilience Layer**: Implemented exponential backoff retry logic for Git lock contention (`index.lock`) in `GitPlumbing.execute`.
80+
- **Telemetric Trace IDs**: Added automatic and manual `traceId` correlation across command execution for production traceability.
81+
- **Performance Monitoring**: Integrated latency tracking for all Git command executions.
82+
- **Secure Runtime Adapters**: Implemented "Clean Environment" isolation in Node, Bun, and Deno runners, preventing sensitive env var leakage.
83+
- **Resource Lifecycle Management**: Enhanced `GitStream` with `FinalizationRegistry` and `destroy()` for deterministic cleanup of shell processes.
84+
85+
### Changed
86+
- **Entity Unification**: Refactored `GitTreeEntry` to use object-based constructors, standardizing the entire domain entity API.
87+
- **Hardened Sanitizer**: Strengthened `CommandSanitizer` to block configuration overrides (`-c`, `--config`) globally and expanded the plumbing command whitelist.
88+
- **Enhanced Verification**: `GitPlumbing.verifyInstallation` now validates both the Git binary and the repository integrity of the current working directory.
89+
90+
### Fixed
91+
- **Deno Resource Leaks**: Resolved process leaks in Deno by ensuring proper stream consumption across all test cases.
92+
- **Node.js Stream Performance**: Optimized async iteration in `GitStream` using native protocols.
93+
94+
## [2.0.0] - 2025-12-10
95+
96+
### Added
97+
- Initial release of the plumbing library.

CODE_OF_CONDUCT.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Contributor Covenant Code of Conduct
2+
3+
## Our Pledge
4+
5+
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6+
7+
## Our Standards
8+
9+
Examples of behavior that contributes to creating a positive environment include:
10+
11+
* Using welcoming and inclusive language
12+
* Being respectful of differing viewpoints and experiences
13+
* Gracefully accepting constructive criticism
14+
* Focusing on what is best for the community
15+
* Showing empathy towards other community members
16+
17+
Examples of unacceptable behavior by participants include:
18+
19+
* The use of sexualized language or imagery and unwelcome sexual attention or advances
20+
* Trolling, insulting/derogatory comments, and personal or political attacks
21+
* Public or private harassment
22+
* Publishing others' private information, such as a physical or electronic address, without explicit permission
23+
* Other conduct which could reasonably be considered inappropriate in a professional setting
24+
25+
## Our Responsibilities
26+
27+
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28+
29+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30+
31+
## Scope
32+
33+
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34+
35+
## Enforcement
36+
37+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at james@flyingrobots.dev. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38+
39+
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40+
41+
## Attribution
42+
43+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
44+
45+
[homepage]: https://www.contributor-covenant.org
46+
47+
For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq

CONTRIBUTING.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Contributing to @git-stunts/plumbing
2+
3+
First off, thank you for considering contributing to this project! It's people like you that make the open-source community such a great place to learn, inspire, and create.
4+
5+
## 📜 Code of Conduct
6+
7+
By participating in this project, you are expected to uphold our Code of Conduct. Please be respectful and professional in all interactions.
8+
9+
## 🛠️ Development Process
10+
11+
### Prerequisites
12+
- Docker and Docker Compose
13+
- Node.js (for local linting)
14+
- **Windows Users**: Must use WSL or Git Bash to run shell-based test scripts locally.
15+
16+
### Workflow
17+
1. **Fork the repository** and create your branch from `main`.
18+
2. **Install dependencies**: `npm install`.
19+
3. **Make your changes**: Ensure you follow our architectural principles (SRP, one class per file, no magic values).
20+
4. **Write tests**: Any new feature or fix *must* include corresponding tests.
21+
5. **Verify locally**:
22+
- Run linting: `npm run lint`
23+
- Run cross-platform tests: `npm test` (requires Docker)
24+
6. **Commit**: Use [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: ...`, `fix: ...`).
25+
7. **Submit a Pull Request**: Provide a clear description of the changes and link to any relevant issues.
26+
27+
## 🏗️ Architectural Principles
28+
- **Hexagonal Architecture**: Keep the domain pure. Infrastructure details stay in `adapters`.
29+
- **Value Objects**: Use Value Objects for all domain concepts (SHAs, Refs, Signatures).
30+
- **Security First**: All shell commands must be sanitized via `CommandSanitizer`.
31+
- **Environment Agnostic**: Use `TextEncoder`/`TextDecoder` and avoid runtime-specific APIs in the domain layer.
32+
33+
## 🐞 Reporting Bugs
34+
- Use the GitHub issue tracker.
35+
- Provide a minimal reproducible example.
36+
- Include details about your environment (OS, runtime version).
37+
38+
## 📄 License
39+
By contributing, you agree that your contributions will be licensed under its Apache-2.0 License.

Dockerfile.bun

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM oven/bun:latest
2+
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
3+
WORKDIR /app
4+
COPY . .
5+
RUN bun install --ignore-scripts
6+
CMD ["bun", "test"]

0 commit comments

Comments
 (0)