@dpesch/mantisbt-mcp-server 1.10.3 → 1.10.6
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 +24 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/.gitea/PULL_REQUEST_TEMPLATE.md +0 -17
- package/.gitea/workflows/ci.yml +0 -95
- package/.github/workflows/ci.yml +0 -32
- package/scripts/hooks/pre-push.mjs +0 -220
- package/scripts/init.mjs +0 -95
- package/scripts/record-fixtures.ts +0 -160
- package/tests/cache.test.ts +0 -265
- package/tests/client.test.ts +0 -372
- package/tests/config.test.ts +0 -263
- package/tests/fixtures/get_current_user.json +0 -40
- package/tests/fixtures/get_issue.json +0 -133
- package/tests/fixtures/get_issue_enums.json +0 -67
- package/tests/fixtures/get_issue_fields_sample.json +0 -76
- package/tests/fixtures/get_project_categories.json +0 -157
- package/tests/fixtures/get_project_versions.json +0 -3
- package/tests/fixtures/get_project_versions_with_data.json +0 -52
- package/tests/fixtures/list_issues.json +0 -95
- package/tests/fixtures/list_projects.json +0 -65
- package/tests/helpers/mock-server.ts +0 -166
- package/tests/helpers/search-mocks.ts +0 -84
- package/tests/prompts/prompts.test.ts +0 -242
- package/tests/resources/resources.test.ts +0 -309
- package/tests/search/embedder.test.ts +0 -81
- package/tests/search/highlight.test.ts +0 -129
- package/tests/search/store.test.ts +0 -193
- package/tests/search/sync.test.ts +0 -249
- package/tests/search/tools.test.ts +0 -661
- package/tests/tools/config.test.ts +0 -212
- package/tests/tools/files.test.ts +0 -343
- package/tests/tools/issues.test.ts +0 -1180
- package/tests/tools/metadata.test.ts +0 -509
- package/tests/tools/monitors.test.ts +0 -101
- package/tests/tools/projects.test.ts +0 -338
- package/tests/tools/relationships.test.ts +0 -177
- package/tests/tools/string-coercion.test.ts +0 -317
- package/tests/tools/users.test.ts +0 -62
- package/tests/utils/date-filter.test.ts +0 -169
- package/tests/version-hint.test.ts +0 -230
- package/tsconfig.build.json +0 -8
- package/vitest.config.ts +0 -8
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,30 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.10.6] – 2026-06-29
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Restored automatic npm publishing via Gitea CI pipeline (triggered on version tags). The manual `--provenance` approach was reverted as Sigstore local signing is not supported outside CI environments with OIDC providers.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [1.10.5] – 2026-06-29
|
|
23
|
+
|
|
24
|
+
### Security
|
|
25
|
+
|
|
26
|
+
- Fixed `.npmignore` to explicitly exclude sensitive files (`.env*`, `.mcpregistry_*`) and dev artifacts (`src/`, `tests/`, `scripts/`, `.claude/`, `.gitea/`, `.github/`, `.idea/`). Previously, when `.npmignore` exists, npm ignores `.gitignore` entirely — requiring all exclusions to be duplicated. v1.10.4 was unpublished due to accidental inclusion of local credential files; this patch corrects the root cause.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## [1.10.4] – 2026-06-29
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- npm releases are now published with `--provenance`, creating a Sigstore attestation that cryptographically links each package to its source commit. Verifiable with `npm audit signatures @dpesch/mantisbt-mcp-server`.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
14
38
|
## [1.10.3] – 2026-05-15
|
|
15
39
|
|
|
16
40
|
### Fixed
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"name": "io.github.dpesch/mantisbt-mcp-server",
|
|
4
4
|
"title": "MantisBT MCP Server",
|
|
5
5
|
"description": "MantisBT MCP server – manage issues, notes, files, tags, and relationships. With semantic search.",
|
|
6
|
-
"version": "1.10.
|
|
6
|
+
"version": "1.10.6",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@dpesch/mantisbt-mcp-server",
|
|
11
|
-
"version": "1.10.
|
|
11
|
+
"version": "1.10.6",
|
|
12
12
|
"runtimeHint": "npx",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
## Description
|
|
2
|
-
|
|
3
|
-
<!-- Why is this change needed? What problem does it solve? -->
|
|
4
|
-
|
|
5
|
-
## Changes
|
|
6
|
-
|
|
7
|
-
<!-- Brief summary of what was changed -->
|
|
8
|
-
|
|
9
|
-
## Testing
|
|
10
|
-
|
|
11
|
-
<!-- How did you test this? -->
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
> **Note:** This repository is a public mirror. Your PR will be reviewed and
|
|
16
|
-
> integrated via cherry-pick — it will not be merged directly through Codeberg.
|
|
17
|
-
> You will be credited in the commit message and CHANGELOG.
|
package/.gitea/workflows/ci.yml
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
name: CI / Publish
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
tags:
|
|
7
|
-
- 'v*'
|
|
8
|
-
pull_request:
|
|
9
|
-
branches: [main]
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
ci:
|
|
13
|
-
runs-on: codeberg-small
|
|
14
|
-
if: |
|
|
15
|
-
github.event_name == 'pull_request' ||
|
|
16
|
-
startsWith(github.ref, 'refs/tags/') ||
|
|
17
|
-
!startsWith(github.event.head_commit.message, 'chore: release')
|
|
18
|
-
|
|
19
|
-
steps:
|
|
20
|
-
- uses: actions/checkout@v4
|
|
21
|
-
|
|
22
|
-
- name: Set up Node.js
|
|
23
|
-
uses: actions/setup-node@v4
|
|
24
|
-
with:
|
|
25
|
-
node-version: '20'
|
|
26
|
-
cache: 'npm'
|
|
27
|
-
|
|
28
|
-
- name: Install dependencies
|
|
29
|
-
run: npm ci
|
|
30
|
-
|
|
31
|
-
- name: Typecheck
|
|
32
|
-
run: npm run typecheck
|
|
33
|
-
|
|
34
|
-
- name: Test
|
|
35
|
-
run: npm test
|
|
36
|
-
|
|
37
|
-
- name: Build
|
|
38
|
-
run: npm run build
|
|
39
|
-
|
|
40
|
-
publish:
|
|
41
|
-
runs-on: codeberg-small
|
|
42
|
-
needs: ci
|
|
43
|
-
if: startsWith(github.ref, 'refs/tags/v')
|
|
44
|
-
|
|
45
|
-
steps:
|
|
46
|
-
- uses: actions/checkout@v4
|
|
47
|
-
|
|
48
|
-
- name: Set up Node.js
|
|
49
|
-
uses: actions/setup-node@v4
|
|
50
|
-
with:
|
|
51
|
-
node-version: '20'
|
|
52
|
-
cache: 'npm'
|
|
53
|
-
registry-url: 'https://registry.npmjs.org'
|
|
54
|
-
|
|
55
|
-
- name: Install dependencies
|
|
56
|
-
run: npm ci
|
|
57
|
-
|
|
58
|
-
- name: Build
|
|
59
|
-
run: npm run build
|
|
60
|
-
|
|
61
|
-
- name: Publish to npm
|
|
62
|
-
run: npm publish --access public
|
|
63
|
-
env:
|
|
64
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
65
|
-
|
|
66
|
-
- name: Extract release notes from CHANGELOG
|
|
67
|
-
run: |
|
|
68
|
-
VERSION="${{ github.ref_name }}"
|
|
69
|
-
VERSION_NUM="${VERSION#v}"
|
|
70
|
-
awk "/^## \[${VERSION_NUM}\]/{found=1; next} found && /^## \[/{exit} found{print}" CHANGELOG.md \
|
|
71
|
-
| sed '/^[[:space:]]*$/d' > /tmp/release_notes.md
|
|
72
|
-
echo "Release notes:"
|
|
73
|
-
cat /tmp/release_notes.md
|
|
74
|
-
|
|
75
|
-
- name: Create Codeberg release
|
|
76
|
-
run: |
|
|
77
|
-
curl -s -X POST "https://codeberg.org/api/v1/repos/dpesch/mantisbt-mcp-server/releases" \
|
|
78
|
-
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
|
79
|
-
-H "Content-Type: application/json" \
|
|
80
|
-
-d "$(jq -n \
|
|
81
|
-
--arg tag "${{ github.ref_name }}" \
|
|
82
|
-
--arg name "${{ github.ref_name }}" \
|
|
83
|
-
--rawfile body /tmp/release_notes.md \
|
|
84
|
-
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')"
|
|
85
|
-
|
|
86
|
-
- name: Create GitHub release
|
|
87
|
-
run: |
|
|
88
|
-
curl -s -X POST "https://api.github.com/repos/dpesch/mantisbt-mcp-server/releases" \
|
|
89
|
-
-H "Authorization: Bearer ${{ secrets.GH_RELEASE_TOKEN }}" \
|
|
90
|
-
-H "Content-Type: application/json" \
|
|
91
|
-
-d "$(jq -n \
|
|
92
|
-
--arg tag "${{ github.ref_name }}" \
|
|
93
|
-
--arg name "${{ github.ref_name }}" \
|
|
94
|
-
--rawfile body /tmp/release_notes.md \
|
|
95
|
-
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')"
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
|
|
16
|
-
- name: Set up Node.js
|
|
17
|
-
uses: actions/setup-node@v4
|
|
18
|
-
with:
|
|
19
|
-
node-version: '20'
|
|
20
|
-
cache: 'npm'
|
|
21
|
-
|
|
22
|
-
- name: Install dependencies
|
|
23
|
-
run: npm ci
|
|
24
|
-
|
|
25
|
-
- name: Type check
|
|
26
|
-
run: npm run typecheck
|
|
27
|
-
|
|
28
|
-
- name: Build
|
|
29
|
-
run: npm run build
|
|
30
|
-
|
|
31
|
-
- name: Test
|
|
32
|
-
run: npm test
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// pre-push hook — typecheck gate + Codeberg .claude/ filter
|
|
3
|
-
//
|
|
4
|
-
// Typecheck runs for every push (origin and upstream) to catch type errors
|
|
5
|
-
// before they reach CI.
|
|
6
|
-
//
|
|
7
|
-
// Codeberg filter handles multi-commit pushes correctly:
|
|
8
|
-
// 1. Fetches the actual Codeberg tip via ls-remote
|
|
9
|
-
// 2. Finds the local commit whose filtered tree matches that tip (anchor)
|
|
10
|
-
// 3. Filters every commit between anchor and tip in order (oldest first)
|
|
11
|
-
// 4. Builds a proper filtered chain anchored to the actual remote tip
|
|
12
|
-
// 5. Pushes the filtered tip with --force
|
|
13
|
-
// 6. Exits 2 to block git's unfiltered push
|
|
14
|
-
// Exit codes: 0 = no Codeberg refs, 1 = error, 2 = success (filtered push done)
|
|
15
|
-
//
|
|
16
|
-
// Branches are processed before tags so the shaMap is available for tags
|
|
17
|
-
// that point to commits already processed as part of the branch.
|
|
18
|
-
//
|
|
19
|
-
// ⚠ IMPORTANT: upstream (Codeberg) is a filtered mirror — push-only.
|
|
20
|
-
// Never run git pull, git fetch + merge, or git rebase against upstream.
|
|
21
|
-
// Filtered commits have different SHAs; pulling them back creates duplicate
|
|
22
|
-
// history. Use origin (Gitolite) as the authoritative source.
|
|
23
|
-
//
|
|
24
|
-
// --force is always required for Codeberg: filtered SHAs structurally diverge
|
|
25
|
-
// from local SHAs, so git rejects main as non-fast-forward before the hook
|
|
26
|
-
// runs unless --force bypasses that check.
|
|
27
|
-
|
|
28
|
-
import { execSync } from 'node:child_process';
|
|
29
|
-
import { createInterface } from 'node:readline';
|
|
30
|
-
|
|
31
|
-
// Recursion guard — must be first to avoid running typecheck during recursive pushes
|
|
32
|
-
if (process.env._PREPUSH_FILTER_ACTIVE) process.exit(0);
|
|
33
|
-
|
|
34
|
-
// Run typecheck before every push (catches type errors before they hit CI)
|
|
35
|
-
try {
|
|
36
|
-
execSync('npm run typecheck', { stdio: 'inherit' });
|
|
37
|
-
} catch {
|
|
38
|
-
console.error('✗ Typecheck failed — push aborted. Fix type errors first.');
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const [,, , remoteUrl] = process.argv;
|
|
43
|
-
|
|
44
|
-
// Only intercept Codeberg pushes for the filter step
|
|
45
|
-
if (!remoteUrl?.includes('codeberg.org')) process.exit(0);
|
|
46
|
-
|
|
47
|
-
console.log('→ Codeberg push detected — filtering .claude/ directory...');
|
|
48
|
-
|
|
49
|
-
const ZERO_SHA = '0'.repeat(40);
|
|
50
|
-
|
|
51
|
-
const git = (cmd, opts = {}) => {
|
|
52
|
-
try {
|
|
53
|
-
return execSync(`git ${cmd}`, { encoding: 'utf8', ...opts }).trim();
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.error(`✗ git ${cmd}\n${err.stderr || err.message}`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const gitOptional = (cmd, opts = {}) => {
|
|
61
|
-
try { return execSync(`git ${cmd}`, { encoding: 'utf8', ...opts }).trim(); }
|
|
62
|
-
catch { return ''; }
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const isSha = s => /^[0-9a-f]{40}$/.test(s);
|
|
66
|
-
|
|
67
|
-
// Remove .claude/ from the root tree of a commit and return the new tree SHA.
|
|
68
|
-
const filterTree = commitSha => {
|
|
69
|
-
const entries = git(`ls-tree "${commitSha}^{tree}"`);
|
|
70
|
-
const filtered = entries.split('\n').filter(e => !e.match(/\t\.claude$/)).join('\n');
|
|
71
|
-
const tree = git('mktree', { input: filtered });
|
|
72
|
-
if (!isSha(tree)) { console.error(`✗ mktree failed for ${commitSha}`); process.exit(1); }
|
|
73
|
-
return tree;
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
// Create a filtered commit object preserving all original metadata.
|
|
77
|
-
const makeFilteredCommit = (commitSha, filteredTree, mappedParents) => {
|
|
78
|
-
const log = git(`log -1 --format=%an%n%ae%n%aI%n%cn%n%ce%n%cI%n%n%B "${commitSha}"`);
|
|
79
|
-
const [an, ae, aI, cn, ce, cI, , ...msgLines] = log.split('\n');
|
|
80
|
-
const commitMsg = msgLines.join('\n').trimEnd();
|
|
81
|
-
const parentFlags = mappedParents.map(p => `-p ${p}`).join(' ');
|
|
82
|
-
const env = {
|
|
83
|
-
...process.env,
|
|
84
|
-
GIT_AUTHOR_NAME: an, GIT_AUTHOR_EMAIL: ae, GIT_AUTHOR_DATE: aI,
|
|
85
|
-
GIT_COMMITTER_NAME: cn, GIT_COMMITTER_EMAIL: ce, GIT_COMMITTER_DATE: cI,
|
|
86
|
-
};
|
|
87
|
-
const result = git(`commit-tree "${filteredTree}" ${parentFlags}`, { env, input: commitMsg });
|
|
88
|
-
if (!isSha(result)) { console.error(`✗ commit-tree failed for ${commitSha}`); process.exit(1); }
|
|
89
|
-
return result;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// Scan ancestors of tipSha (newest first) and return the most recent one whose
|
|
93
|
-
// filtered root tree equals the tree of remoteSha. Returns null if not found.
|
|
94
|
-
const findLocalAnchor = (tipSha, remoteSha, maxDepth = 100) => {
|
|
95
|
-
const remoteTree = gitOptional(`rev-parse "${remoteSha}^{tree}"`);
|
|
96
|
-
if (!remoteTree) return null;
|
|
97
|
-
const candidates = gitOptional(`rev-list --max-count=${maxDepth} "${tipSha}"`);
|
|
98
|
-
if (!candidates) return null;
|
|
99
|
-
for (const sha of candidates.split('\n').filter(Boolean)) {
|
|
100
|
-
if (filterTree(sha) === remoteTree) return sha;
|
|
101
|
-
}
|
|
102
|
-
return null;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const rl = createInterface({ input: process.stdin });
|
|
106
|
-
const lines = [];
|
|
107
|
-
rl.on('line', line => { if (line.trim()) lines.push(line.trim()); });
|
|
108
|
-
|
|
109
|
-
rl.on('close', () => {
|
|
110
|
-
// Process branches before tags so shaMap is populated when tags are handled.
|
|
111
|
-
const branches = lines.filter(l => l.split(' ')[2]?.startsWith('refs/heads/'));
|
|
112
|
-
const others = lines.filter(l => !l.split(' ')[2]?.startsWith('refs/heads/'));
|
|
113
|
-
|
|
114
|
-
// Batch-fetch all tags from Codeberg (single ls-remote call, ref → sha).
|
|
115
|
-
const remoteTagsRaw = gitOptional(`ls-remote --tags "${remoteUrl}"`);
|
|
116
|
-
const remoteTagMap = new Map(
|
|
117
|
-
remoteTagsRaw
|
|
118
|
-
? remoteTagsRaw.split('\n').filter(Boolean)
|
|
119
|
-
.map(l => { const [sha, ref] = l.split(/\s+/); return [ref, sha]; })
|
|
120
|
-
.filter(([ref]) => ref)
|
|
121
|
-
: []
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
// localSha → filteredSha, accumulated across all refs in this push.
|
|
125
|
-
const shaMap = {};
|
|
126
|
-
let pushed = 0;
|
|
127
|
-
|
|
128
|
-
for (const line of [...branches, ...others]) {
|
|
129
|
-
const parts = line.split(' ');
|
|
130
|
-
if (parts.length < 3) { console.error(`✗ Malformed push input: ${line}`); process.exit(1); }
|
|
131
|
-
const [, localSha, remoteRef] = parts;
|
|
132
|
-
|
|
133
|
-
if (localSha === ZERO_SHA) continue; // deletion — skip
|
|
134
|
-
|
|
135
|
-
const label = remoteRef.replace('refs/heads/', '').replace('refs/tags/', '');
|
|
136
|
-
|
|
137
|
-
if (remoteRef.startsWith('refs/tags/')) {
|
|
138
|
-
if (remoteTagMap.has(remoteRef)) {
|
|
139
|
-
console.log(` ↷ ${label} already on Codeberg — skipping`);
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
if (!gitOptional(`rev-parse --verify "${localSha}"`)) {
|
|
143
|
-
console.warn(` ⚠ ${label} — commit ${localSha.slice(0, 8)} not found locally, skipping`);
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// If this commit was already filtered as part of a branch push, reuse it.
|
|
149
|
-
if (localSha in shaMap) {
|
|
150
|
-
try {
|
|
151
|
-
execSync(`git push "${remoteUrl}" "${shaMap[localSha]}:${remoteRef}" --force`,
|
|
152
|
-
{ env: { ...process.env, _PREPUSH_FILTER_ACTIVE: '1' }, stdio: 'inherit' });
|
|
153
|
-
} catch {
|
|
154
|
-
console.error(`✗ Push failed for ${label}`); process.exit(1);
|
|
155
|
-
}
|
|
156
|
-
console.log(`✓ ${label} pushed to Codeberg (without .claude/)`);
|
|
157
|
-
pushed++;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Tags: existing ones were skipped above, so only new tags reach here — no remote anchor.
|
|
162
|
-
// Branches: query individually to find the incremental anchor for filtered chain building.
|
|
163
|
-
const actualRemoteSha = remoteRef.startsWith('refs/tags/')
|
|
164
|
-
? ''
|
|
165
|
-
: (gitOptional(`ls-remote "${remoteUrl}" "${remoteRef}"`)?.split(/\s+/)[0] ?? '');
|
|
166
|
-
|
|
167
|
-
if (!actualRemoteSha) {
|
|
168
|
-
// New ref on Codeberg — filter the tip with no parent.
|
|
169
|
-
shaMap[localSha] = makeFilteredCommit(localSha, filterTree(localSha), []);
|
|
170
|
-
} else {
|
|
171
|
-
// Find the local commit that corresponds to the current Codeberg tip.
|
|
172
|
-
const localAnchor = findLocalAnchor(localSha, actualRemoteSha);
|
|
173
|
-
|
|
174
|
-
if (localAnchor) {
|
|
175
|
-
shaMap[localAnchor] = actualRemoteSha;
|
|
176
|
-
|
|
177
|
-
// Filter all commits between anchor and tip, oldest first.
|
|
178
|
-
const revList = gitOptional(`rev-list --reverse "${localAnchor}..${localSha}"`);
|
|
179
|
-
const commits = revList ? revList.split('\n').filter(Boolean) : [];
|
|
180
|
-
|
|
181
|
-
for (const sha of commits) {
|
|
182
|
-
const filteredTree = filterTree(sha);
|
|
183
|
-
const parents = gitOptional(`log -1 --format=%P "${sha}"`);
|
|
184
|
-
const parentShas = parents ? parents.split(' ').filter(Boolean) : [];
|
|
185
|
-
// Map each parent through shaMap; fall back to actualRemoteSha for
|
|
186
|
-
// parents outside the current range (already on the remote).
|
|
187
|
-
const mappedParents = parentShas
|
|
188
|
-
.map(p => shaMap[p] ?? actualRemoteSha)
|
|
189
|
-
.filter(Boolean);
|
|
190
|
-
shaMap[sha] = makeFilteredCommit(sha, filteredTree, mappedParents);
|
|
191
|
-
}
|
|
192
|
-
} else {
|
|
193
|
-
// Fallback: anchor not found — branch only (tags exit early above).
|
|
194
|
-
const remoteExists = !!gitOptional(`rev-parse --verify "${actualRemoteSha}"`);
|
|
195
|
-
console.warn(` ⚠ Could not find local base for ${label}, filtering tip only`);
|
|
196
|
-
shaMap[localSha] = makeFilteredCommit(localSha, filterTree(localSha), remoteExists ? [actualRemoteSha] : []);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const filteredTip = shaMap[localSha];
|
|
201
|
-
if (!filteredTip) {
|
|
202
|
-
console.error(`✗ Could not compute filtered SHA for ${localSha}`);
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
execSync(`git push "${remoteUrl}" "${filteredTip}:${remoteRef}" --force`,
|
|
208
|
-
{ env: { ...process.env, _PREPUSH_FILTER_ACTIVE: '1' }, stdio: 'inherit' });
|
|
209
|
-
} catch {
|
|
210
|
-
console.error(`✗ Push to Codeberg failed for ${label}`); process.exit(1);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
console.log(`✓ ${label} pushed to Codeberg (without .claude/)`);
|
|
214
|
-
pushed++;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (pushed === 0) process.exit(0);
|
|
218
|
-
console.log('→ Filtered push complete. Blocking unfiltered push.');
|
|
219
|
-
process.exit(2); // 2 = success (filtered push done, unfiltered blocked); 1 = error
|
|
220
|
-
});
|
package/scripts/init.mjs
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Project setup script — run via: npm run init
|
|
3
|
-
//
|
|
4
|
-
// Steps:
|
|
5
|
-
// 1. Check Node.js version (requires >=18)
|
|
6
|
-
// 2. Install dependencies (npm install)
|
|
7
|
-
// 3. Install git hooks from scripts/hooks/ into .git/hooks/
|
|
8
|
-
// 4. Run typecheck to verify the setup
|
|
9
|
-
|
|
10
|
-
import { spawnSync } from 'node:child_process';
|
|
11
|
-
import { copyFileSync, chmodSync, existsSync, mkdirSync } from 'node:fs';
|
|
12
|
-
import { resolve, dirname, join } from 'node:path';
|
|
13
|
-
import { fileURLToPath } from 'node:url';
|
|
14
|
-
|
|
15
|
-
const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
16
|
-
// Auf Windows .cmd-Dateien via cmd.exe aufrufen — kein shell:true nötig, keine Deprecation-Warnung
|
|
17
|
-
const [npmBin, npmBaseArgs] = process.platform === 'win32'
|
|
18
|
-
? ['cmd.exe', ['/c', 'npm']]
|
|
19
|
-
: ['npm', []];
|
|
20
|
-
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// 1. Node.js version check
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
|
|
25
|
-
const [major] = process.versions.node.split('.').map(Number);
|
|
26
|
-
if (major < 18) {
|
|
27
|
-
console.error(`✗ Node.js >= 18 required, found ${process.version}`);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
console.log(`✓ Node.js ${process.version}`);
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// 2. Install dependencies
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
console.log('\n→ Installing dependencies...');
|
|
37
|
-
const install = spawnSync(npmBin, [...npmBaseArgs, 'install'], {
|
|
38
|
-
stdio: 'inherit',
|
|
39
|
-
cwd: root,
|
|
40
|
-
});
|
|
41
|
-
if (install.status !== 0) {
|
|
42
|
-
console.error('✗ npm install failed');
|
|
43
|
-
process.exit(1);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// 3. Install git hooks
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
console.log('\n→ Installing git hooks...');
|
|
51
|
-
|
|
52
|
-
const hooksSourceDir = resolve(root, 'scripts/hooks');
|
|
53
|
-
const gitHooksDir = resolve(root, '.git/hooks');
|
|
54
|
-
|
|
55
|
-
if (!existsSync(gitHooksDir)) {
|
|
56
|
-
mkdirSync(gitHooksDir, { recursive: true });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const hooks = ['pre-push'];
|
|
60
|
-
for (const hook of hooks) {
|
|
61
|
-
const src = resolve(hooksSourceDir, `${hook}.mjs`);
|
|
62
|
-
const dest = resolve(gitHooksDir, hook);
|
|
63
|
-
|
|
64
|
-
if (!existsSync(src)) {
|
|
65
|
-
console.warn(` ⚠ Hook source not found, skipping: scripts/hooks/${hook}.mjs`);
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
copyFileSync(src, dest);
|
|
70
|
-
|
|
71
|
-
// chmod +x (no-op on Windows — git runs hooks directly via shebang)
|
|
72
|
-
try {
|
|
73
|
-
chmodSync(dest, 0o755);
|
|
74
|
-
} catch {
|
|
75
|
-
// Silently ignore on platforms that don't support chmod
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
console.log(` ✓ .git/hooks/${hook} installed`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// ---------------------------------------------------------------------------
|
|
82
|
-
// 4. Typecheck
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
console.log('\n→ Running typecheck...');
|
|
86
|
-
const check = spawnSync(npmBin, [...npmBaseArgs, 'run', 'typecheck'], {
|
|
87
|
-
stdio: 'inherit',
|
|
88
|
-
cwd: root,
|
|
89
|
-
});
|
|
90
|
-
if (check.status !== 0) {
|
|
91
|
-
console.error('✗ Typecheck failed — check type errors above');
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
console.log('\n✓ Setup complete. Happy hacking!');
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { writeFileSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { MantisClient } from '../src/client.js';
|
|
5
|
-
import { ISSUE_ENUM_OPTIONS } from '../src/constants.js';
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = dirname(__filename);
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// .env.local laden (falls vorhanden)
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const envPath = join(__dirname, '..', '.env.local');
|
|
16
|
-
const lines = readFileSync(envPath, 'utf-8').split('\n');
|
|
17
|
-
for (const line of lines) {
|
|
18
|
-
const match = line.match(/^([^#=\s][^=]*)=(.*)$/);
|
|
19
|
-
if (match) {
|
|
20
|
-
const key = match[1].trim();
|
|
21
|
-
const value = match[2].trim().replace(/^["']|["']$/g, '');
|
|
22
|
-
if (!process.env[key]) process.env[key] = value;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
// .env.local nicht vorhanden – ENV-Vars direkt nutzen
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Config aus ENV
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
const baseUrlRaw = process.env['MANTIS_BASE_URL'];
|
|
34
|
-
const apiKey = process.env['MANTIS_API_KEY'];
|
|
35
|
-
|
|
36
|
-
if (!baseUrlRaw || !apiKey) {
|
|
37
|
-
console.error('Error: MANTIS_BASE_URL and MANTIS_API_KEY environment variables must be set.');
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const baseUrl = baseUrlRaw.replace(/\/$/, '');
|
|
42
|
-
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Fixtures-Verzeichnis
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
|
|
47
|
-
const fixturesDir = join(__dirname, '..', 'tests', 'fixtures', 'recorded');
|
|
48
|
-
mkdirSync(fixturesDir, { recursive: true });
|
|
49
|
-
|
|
50
|
-
function saveFixture(filename: string, data: unknown): void {
|
|
51
|
-
const filePath = join(fixturesDir, filename);
|
|
52
|
-
writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
53
|
-
console.log(`Saved: ${filePath}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// Client instanziieren
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
const client = new MantisClient(baseUrl, apiKey);
|
|
61
|
-
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
// Fixtures aufzeichnen
|
|
64
|
-
// ---------------------------------------------------------------------------
|
|
65
|
-
|
|
66
|
-
async function recordFixtures(): Promise<void> {
|
|
67
|
-
// GET projects
|
|
68
|
-
let firstProjectId: number | undefined;
|
|
69
|
-
let projectIdWithVersions: number | undefined;
|
|
70
|
-
try {
|
|
71
|
-
const projectsResult = await client.get<{ projects: Array<{ id: number; name: string; versions?: unknown[] }> }>('projects');
|
|
72
|
-
saveFixture('list_projects.json', projectsResult);
|
|
73
|
-
if (Array.isArray(projectsResult.projects) && projectsResult.projects.length > 0) {
|
|
74
|
-
firstProjectId = projectsResult.projects[0]?.id;
|
|
75
|
-
// Erstes Projekt mit nicht-leeren Versionen bevorzugen
|
|
76
|
-
projectIdWithVersions = projectsResult.projects.find(
|
|
77
|
-
(p) => Array.isArray(p.versions) && p.versions.length > 0,
|
|
78
|
-
)?.id ?? firstProjectId;
|
|
79
|
-
}
|
|
80
|
-
} catch (err) {
|
|
81
|
-
console.error('Failed to fetch projects:', err instanceof Error ? err.message : String(err));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// GET users/me
|
|
85
|
-
try {
|
|
86
|
-
const userResult = await client.get<unknown>('users/me');
|
|
87
|
-
saveFixture('get_current_user.json', userResult);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
console.error('Failed to fetch users/me:', err instanceof Error ? err.message : String(err));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// GET issues?page=1&page_size=3
|
|
93
|
-
let firstIssueId: number | undefined;
|
|
94
|
-
try {
|
|
95
|
-
const issuesResult = await client.get<{ issues: Array<{ id: number }> }>('issues', {
|
|
96
|
-
page: 1,
|
|
97
|
-
page_size: 3,
|
|
98
|
-
});
|
|
99
|
-
saveFixture('list_issues.json', issuesResult);
|
|
100
|
-
if (Array.isArray(issuesResult.issues) && issuesResult.issues.length > 0) {
|
|
101
|
-
firstIssueId = issuesResult.issues[0]?.id;
|
|
102
|
-
}
|
|
103
|
-
} catch (err) {
|
|
104
|
-
console.error('Failed to fetch issues:', err instanceof Error ? err.message : String(err));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// GET issues?page=1&page_size=1 — single issue sample for get_issue_fields field discovery
|
|
108
|
-
try {
|
|
109
|
-
const sampleResult = await client.get<{ issues: unknown[] }>('issues', {
|
|
110
|
-
page: 1,
|
|
111
|
-
page_size: 1,
|
|
112
|
-
});
|
|
113
|
-
saveFixture('get_issue_fields_sample.json', sampleResult);
|
|
114
|
-
} catch (err) {
|
|
115
|
-
console.error('Failed to fetch issues sample:', err instanceof Error ? err.message : String(err));
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// GET issues/{id}
|
|
119
|
-
if (firstIssueId !== undefined) {
|
|
120
|
-
try {
|
|
121
|
-
const issueResult = await client.get<unknown>(`issues/${firstIssueId}`);
|
|
122
|
-
saveFixture('get_issue.json', issueResult);
|
|
123
|
-
} catch (err) {
|
|
124
|
-
console.error(`Failed to fetch issues/${firstIssueId}:`, err instanceof Error ? err.message : String(err));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// GET config — enum values for severity, status, priority, resolution, reproducibility
|
|
129
|
-
try {
|
|
130
|
-
const enumParams: Record<string, string> = {};
|
|
131
|
-
ISSUE_ENUM_OPTIONS.forEach((opt, i) => { enumParams[`option[${i}]`] = opt; });
|
|
132
|
-
const configResult = await client.get<unknown>('config', enumParams);
|
|
133
|
-
saveFixture('get_issue_enums.json', configResult);
|
|
134
|
-
} catch (err) {
|
|
135
|
-
console.error('Failed to fetch config enums:', err instanceof Error ? err.message : String(err));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// GET projects/{id}/versions + categories
|
|
139
|
-
if (firstProjectId !== undefined) {
|
|
140
|
-
const versionProjectId = projectIdWithVersions ?? firstProjectId;
|
|
141
|
-
try {
|
|
142
|
-
const versionsResult = await client.get<unknown>(`projects/${versionProjectId}/versions`, { obsolete: 1, inherit: 1 });
|
|
143
|
-
saveFixture('get_project_versions.json', versionsResult);
|
|
144
|
-
} catch (err) {
|
|
145
|
-
console.error(`Failed to fetch projects/${versionProjectId}/versions:`, err instanceof Error ? err.message : String(err));
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
const projectResult = await client.get<{ projects: Array<{ categories?: unknown[] }> }>(`projects/${firstProjectId}`);
|
|
150
|
-
saveFixture('get_project_categories.json', { categories: projectResult.projects?.[0]?.categories ?? [] });
|
|
151
|
-
} catch (err) {
|
|
152
|
-
console.error(`Failed to fetch projects/${firstProjectId} (categories):`, err instanceof Error ? err.message : String(err));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
recordFixtures().catch((err) => {
|
|
158
|
-
console.error('Unexpected error:', err);
|
|
159
|
-
process.exit(1);
|
|
160
|
-
});
|