Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
## Summary

<!-- What does this PR change and why? -->

## Type of change

- [ ] Bug fix (user-visible)
- [ ] Feature (user-visible)
- [ ] Documentation / site only
- [ ] Tests, CI, or refactor (no user-visible change)

## Checklist

- [ ] I ran `npm test` locally (required for code changes)
- [ ] I smoke-tested with `npm start` if `dashboard.html` or API behavior changed
- [ ] No secrets in commits (`config.json` tokens, `.env`, PATs)
- [ ] Updated `CHANGELOG.md` under **Unreleased** when the change is notable

## Release

- [ ] **No GitHub Release needed** (default for tests, docs, CI-only)
- [ ] **Release needed** — describe why users should download a new build:

<!-- e.g. packaged binary, breaking setup, critical security fix -->

## Test plan

<!-- Steps a reviewer can follow -->
23 changes: 23 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "22"

- name: Install dependencies
run: npm install

- name: Run test suite
run: npm test
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Automated test suite (`npm test`) using Node.js built-in test runner and supertest.
- Shared libraries under `lib/` for security validation, git status parsing, and dormant repo logic.
- GitHub Actions workflow to run tests on push and pull requests to `main`.
- `CHANGELOG.md`, pull request template, and release policy in `CONTRIBUTING.md`.

### Changed

- `server.js` imports validation helpers from `lib/` (behavior preserved; easier to test).
- Account name validation now rejects names that require stripping unsafe characters (e.g. `bad name!`, `work;rm`).

### Security

- Stricter `sanitizeAccountName` so shell-like input cannot be silently normalized into a valid account id.

## [1.2.0] - 2026-05-21

### Added

- Five-step account setup with GitHub-aligned SSH and fine-grained PAT guidance.
- Optional token storage in the OS credential store (not in `config.json`).
- Official GitHub `known_hosts` fingerprints and SSH verify feedback in setup status.
- Dormant repo filter and terminology (replaces stale/inactive).
- Modal close on outside click and Escape; segmented repo action bar; cloned repo green border.

### Changed

- Activity Log removed; feedback via toasts and SSE.
- README, privacy page, and site copy aligned with real token and dormant behavior.

[Unreleased]: https://github.com/gitdock-dev/gitdock/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/gitdock-dev/gitdock/releases/tag/v1.2.0
28 changes: 27 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,32 @@ npm start

Hub runs at **http://localhost:3848** by default.

## Testing

GitDock uses the Node.js built-in test runner (`node --test`) plus [supertest](https://github.com/ladjs/supertest) for HTTP checks.

| Command | What it runs |
|---------|----------------|
| `npm test` | Full suite (security, git parsers, dormant logic, API integration) |
| `npm run test:unit` | Pure library tests only (no server) |
| `npm run test:api` | Express API integration tests in an isolated temp workspace |

Tests live under `test/`. Shared validation logic is in `lib/` so the server and tests stay aligned.

Before opening a PR, run `npm test` and ensure it passes.

## Releases and versioning

We use [Semantic Versioning](https://semver.org/). Not every merged PR becomes a GitHub Release.

| Change type | Version bump | GitHub Release (binaries) |
|-------------|--------------|---------------------------|
| Bug fix users depend on | Patch (e.g. 1.2.1) | Yes, when the fix matters for downloaded builds |
| New UX or API behavior | Minor (e.g. 1.3.0) | Yes, when bundling several user-visible changes |
| Tests, CI, docs, refactors | None required | No |

Track user-facing work in [CHANGELOG.md](CHANGELOG.md) under **Unreleased**. When cutting a release, move items to a version section, bump `package.json`, tag `vX.Y.Z`, and publish the release (that triggers the build workflow).

## Code style

- **Language:** All code and comments in English.
Expand All @@ -55,7 +81,7 @@ Hub runs at **http://localhost:3848** by default.
## Submitting changes

1. **Fork** the repository and create a branch from `main` (e.g. `feat/your-feature` or `fix/issue-description`).
2. **Make your changes** and test locally (`npm start` and, if relevant, the Hub).
2. **Make your changes** and run the automated suite: `npm test` (unit + API integration). Also smoke-test the dashboard when UI changes: `npm start`.
3. **Commit** with clear messages (e.g. `feat: add X`, `fix: resolve Y`).
4. **Push** to your fork and open a **Pull Request** against `gitdock-dev/gitdock` `main`.
5. Describe what you changed and why. Reference any related issues.
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,9 @@ npm run dev

# Check if it's running
curl http://127.0.0.1:3847/api/health

# Run automated tests (contributors)
npm test
```

### GitHub CLI
Expand Down Expand Up @@ -526,7 +529,7 @@ In the local GitDock dashboard use the dashboard’s **Configure Hub** to set th

## Contributing

We welcome contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style (vanilla JS, no frameworks), and how to submit pull requests and report issues.
We welcome contributions. See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, `npm test`, release policy, code style (vanilla JS, no frameworks), and how to submit pull requests and report issues. See [CHANGELOG.md](CHANGELOG.md) for version history.

---

Expand Down
28 changes: 28 additions & 0 deletions lib/dormant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Dormant repo detection (mirrors dashboard logic for tests and docs).
*/

function isDormant(repo, dormantDays) {
const days = Number(dormantDays);
if (!Number.isFinite(days) || days < 1) return false;
const refDate =
repo.isCloned && repo.lastCommit && repo.lastCommit.date
? repo.lastCommit.date
: repo.updatedAt;
if (!refDate) return false;
const elapsed = Math.floor((Date.now() - new Date(refDate)) / 86400000);
return elapsed > days;
}

function dormantPeriodLabel(dormantDays) {
const d = Number(dormantDays);
if (d <= 30) return "1 month";
if (d <= 90) return "3 months";
if (d <= 180) return "6 months";
return "1 year";
}

module.exports = {
isDormant,
dormantPeriodLabel,
};
59 changes: 59 additions & 0 deletions lib/git-parse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Pure parsers for git command output (used by server and tests).
*/

function parseStatusPorcelain(output) {
const lines = output ? output.split("\n").filter(Boolean) : [];
const files = [];
let stagedCount = 0;
let unstagedCount = 0;
let untrackedCount = 0;
let conflictCount = 0;
for (const line of lines) {
const xy = line.slice(0, 2);
const x = xy[0];
const y = xy[1];
const filePath = line.slice(3).trim().replace(/^["']|["']$/g, "");
if (filePath) {
let status = "modified";
if (xy === "??") status = "untracked";
else if (x === "A" || x === "M" || x === "D" || x === "R" || x === "C") status = "added";
else if (y === "M" || y === "D") status = "modified";
else if (x === "D" || y === "D") status = "deleted";
else if (x === "U" || y === "U") status = "unmerged";
files.push({ path: filePath, status });
}
if (xy === "??") {
untrackedCount += 1;
} else {
if (x !== " " && x !== "?") stagedCount += 1;
if (y !== " " && y !== "?") unstagedCount += 1;
if (x === "U" || y === "U") conflictCount += 1;
}
}
return { files, summary: { stagedCount, unstagedCount, untrackedCount, conflictCount } };
}

/** Parse "git status -sb" first line for branch, upstream, ahead, behind */
function parseStatusBranchLine(line) {
if (!line || !line.startsWith("## ")) {
return { branch: "unknown", hasUpstream: false, ahead: 0, behind: 0, upstreamRef: null };
}
const rest = line.slice(3).trim();
const branchMatch = rest.match(/^([^\s.]+)(?:\.\.\.(\S+))?(?:\s+\[(.*)\])?/);
const branch = branchMatch ? branchMatch[1] : "unknown";
const upstreamRef = branchMatch && branchMatch[2] ? branchMatch[2] : null;
const bracket = branchMatch && branchMatch[3] ? branchMatch[3] : "";
let ahead = 0;
let behind = 0;
const aheadM = bracket.match(/ahead\s+(\d+)/);
const behindM = bracket.match(/behind\s+(\d+)/);
if (aheadM) ahead = parseInt(aheadM[1], 10) || 0;
if (behindM) behind = parseInt(behindM[1], 10) || 0;
return { branch, hasUpstream: !!upstreamRef, ahead, behind, upstreamRef };
}

module.exports = {
parseStatusPorcelain,
parseStatusBranchLine,
};
Loading
Loading