@goodandready/dsh-github-ops 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/LICENSE +21 -0
- package/README.md +163 -0
- package/README.ru.md +161 -0
- package/README.zh.md +151 -0
- package/cordis.patch.yml +3 -0
- package/lib/approval.js +168 -0
- package/lib/auth.js +59 -0
- package/lib/client.js +304 -0
- package/lib/commands.js +151 -0
- package/lib/github.js +315 -0
- package/lib/index.js +332 -0
- package/lib/jobs.js +107 -0
- package/lib/present.js +214 -0
- package/lib/repo-context.js +72 -0
- package/lib/review-rules.js +147 -0
- package/lib/tools/actions.js +107 -0
- package/lib/tools/api.js +44 -0
- package/lib/tools/issues.js +101 -0
- package/lib/tools/mirror.js +163 -0
- package/lib/tools/pulls.js +207 -0
- package/lib/tools/register-collab.js +233 -0
- package/lib/tools/register-core.js +259 -0
- package/lib/tools/register-jobs.js +57 -0
- package/lib/tools/register-mirror.js +88 -0
- package/lib/tools/register-ops.js +283 -0
- package/lib/tools/releases.js +111 -0
- package/lib/tools/repo-settings.js +207 -0
- package/lib/tools/repos.js +108 -0
- package/lib/tools/tags.js +68 -0
- package/package.json +73 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Repository and file operations: metadata, file content at a ref, repository
|
|
2
|
+
// search (its own quota), plus repository creation and edits.
|
|
3
|
+
|
|
4
|
+
function ensureRepo({ owner, repo }) {
|
|
5
|
+
if (!owner || !repo) {
|
|
6
|
+
throw new Error('owner and repo are required (pass "repository": "owner/repo" or set defaults in settings)')
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function normalizeRepo(data) {
|
|
11
|
+
if (!data || typeof data !== 'object') return data
|
|
12
|
+
return {
|
|
13
|
+
fullName: data.full_name,
|
|
14
|
+
description: data.description || '',
|
|
15
|
+
private: Boolean(data.private),
|
|
16
|
+
archived: Boolean(data.archived),
|
|
17
|
+
defaultBranch: data.default_branch,
|
|
18
|
+
language: data.language,
|
|
19
|
+
stars: data.stargazers_count,
|
|
20
|
+
forks: data.forks_count,
|
|
21
|
+
openIssues: data.open_issues_count,
|
|
22
|
+
topics: Array.isArray(data.topics) ? data.topics : [],
|
|
23
|
+
license: data.license && (data.license.spdx_id || data.license.name),
|
|
24
|
+
homepage: data.homepage || '',
|
|
25
|
+
pushedAt: data.pushed_at,
|
|
26
|
+
updatedAt: data.updated_at,
|
|
27
|
+
url: data.html_url,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function getRepo(client, { owner, repo }) {
|
|
32
|
+
ensureRepo({ owner, repo })
|
|
33
|
+
const { data } = await client.get(`/repos/${owner}/${repo}`)
|
|
34
|
+
return normalizeRepo(data)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getFile(client, { owner, repo, path, ref }) {
|
|
38
|
+
ensureRepo({ owner, repo })
|
|
39
|
+
if (!path) throw new Error('path is required')
|
|
40
|
+
const { data } = await client.get(`/repos/${owner}/${repo}/contents/${path}`, {
|
|
41
|
+
query: ref ? { ref } : undefined,
|
|
42
|
+
})
|
|
43
|
+
if (Array.isArray(data)) {
|
|
44
|
+
return {
|
|
45
|
+
kind: 'directory',
|
|
46
|
+
path,
|
|
47
|
+
entries: data.map((e) => ({ name: e.name, type: e.type, size: e.size, path: e.path })),
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const encoded = String((data && data.content) || '').replace(/\n/g, '')
|
|
51
|
+
const decoded = encoded && String(data.encoding || 'base64') === 'base64'
|
|
52
|
+
? Buffer.from(encoded, 'base64').toString('utf8')
|
|
53
|
+
: String((data && data.content) || '')
|
|
54
|
+
return {
|
|
55
|
+
kind: 'file',
|
|
56
|
+
path: data && data.path ? data.path : path,
|
|
57
|
+
sha: data && data.sha,
|
|
58
|
+
size: data && data.size,
|
|
59
|
+
truncated: Boolean(data && data.truncated),
|
|
60
|
+
content: decoded,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function searchRepos(client, { query, limit = 20 }) {
|
|
65
|
+
if (!query) throw new Error('query is required')
|
|
66
|
+
const capped = Math.max(1, Math.min(Number(limit) || 20, 100))
|
|
67
|
+
const { data } = await client.get('/search/repositories', {
|
|
68
|
+
query: { q: query, per_page: capped },
|
|
69
|
+
})
|
|
70
|
+
const items = (data && data.items) || []
|
|
71
|
+
return {
|
|
72
|
+
total: (data && data.total_count) || items.length,
|
|
73
|
+
repos: items.map(normalizeRepo),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function createRepo(client, { name, org, description, private: isPrivate = true, autoInit = false }) {
|
|
78
|
+
if (!name) throw new Error('name is required')
|
|
79
|
+
const payload = {
|
|
80
|
+
name,
|
|
81
|
+
description: description || '',
|
|
82
|
+
private: Boolean(isPrivate),
|
|
83
|
+
auto_init: Boolean(autoInit),
|
|
84
|
+
}
|
|
85
|
+
const path = org ? `/orgs/${org}/repos` : '/user/repos'
|
|
86
|
+
const { data } = await client.post(path, payload)
|
|
87
|
+
return normalizeRepo(data)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function editRepo(client, { owner, repo, description, homepage, private: isPrivate, archived, topics }) {
|
|
91
|
+
ensureRepo({ owner, repo })
|
|
92
|
+
const payload = {}
|
|
93
|
+
if (description !== undefined) payload.description = description
|
|
94
|
+
if (homepage !== undefined) payload.homepage = homepage
|
|
95
|
+
if (isPrivate !== undefined) payload.private = Boolean(isPrivate)
|
|
96
|
+
if (archived !== undefined) payload.archived = Boolean(archived)
|
|
97
|
+
const { data } = Object.keys(payload).length
|
|
98
|
+
? await client.patch(`/repos/${owner}/${repo}`, payload)
|
|
99
|
+
: { data: null }
|
|
100
|
+
const result = data ? normalizeRepo(data) : await getRepo(client, { owner, repo })
|
|
101
|
+
if (Array.isArray(topics)) {
|
|
102
|
+
// The topics endpoint is separate and its response is the only place the applied
|
|
103
|
+
// list is reported: a PATCH response may still carry the previous topics.
|
|
104
|
+
const { data: topicData } = await client.put(`/repos/${owner}/${repo}/topics`, { names: topics })
|
|
105
|
+
if (topicData && Array.isArray(topicData.names)) result.topics = topicData.names
|
|
106
|
+
}
|
|
107
|
+
return result
|
|
108
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Tag (git ref) operations. Tags are refs under refs/tags; creating an annotated
|
|
2
|
+
// tag needs a tag object first, a lightweight tag is a plain ref.
|
|
3
|
+
|
|
4
|
+
function ensureRepo({ owner, repo }) {
|
|
5
|
+
if (!owner || !repo) {
|
|
6
|
+
throw new Error('owner and repo are required (pass "repository": "owner/repo" or set defaults in settings)')
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function listTags(client, { owner, repo, limit = 100 } = {}) {
|
|
11
|
+
ensureRepo({ owner, repo })
|
|
12
|
+
const capped = Math.max(1, Math.min(Number(limit) || 100, 100))
|
|
13
|
+
const refs = await client.paginate(`/repos/${owner}/${repo}/git/refs/tags`, {
|
|
14
|
+
pageSize: Math.min(capped, 100),
|
|
15
|
+
maxItems: capped,
|
|
16
|
+
})
|
|
17
|
+
const tags = refs.map((ref) => ({
|
|
18
|
+
name: String(ref.ref || '').replace('refs/tags/', ''),
|
|
19
|
+
sha: ref.object && ref.object.sha,
|
|
20
|
+
type: ref.object && ref.object.type,
|
|
21
|
+
}))
|
|
22
|
+
return { tags, count: tags.length }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function getTag(client, { owner, repo, tag }) {
|
|
26
|
+
ensureRepo({ owner, repo })
|
|
27
|
+
if (!tag) throw new Error('tag is required')
|
|
28
|
+
const { data } = await client.get(`/repos/${owner}/${repo}/git/ref/tags/${encodeURIComponent(tag)}`)
|
|
29
|
+
return { name: tag, sha: data && data.object && data.object.sha, type: data && data.object && data.object.type }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create a tag. With `message` the tag is annotated: GitHub needs a tag object,
|
|
34
|
+
* so the tag object is created first and the ref points at it.
|
|
35
|
+
*/
|
|
36
|
+
export async function createTag(client, { owner, repo, tag, sha, message }) {
|
|
37
|
+
ensureRepo({ owner, repo })
|
|
38
|
+
if (!tag) throw new Error('tag is required')
|
|
39
|
+
let target = sha
|
|
40
|
+
if (!target) {
|
|
41
|
+
const { data } = await client.get(`/repos/${owner}/${repo}/commits/HEAD`)
|
|
42
|
+
target = data && data.sha
|
|
43
|
+
if (!target) throw new Error('cannot resolve HEAD to tag')
|
|
44
|
+
}
|
|
45
|
+
let refTarget = target
|
|
46
|
+
if (message) {
|
|
47
|
+
const { data: tagObject } = await client.post(`/repos/${owner}/${repo}/git/tags`, {
|
|
48
|
+
tag,
|
|
49
|
+
message,
|
|
50
|
+
object: target,
|
|
51
|
+
type: 'commit',
|
|
52
|
+
})
|
|
53
|
+
refTarget = tagObject && tagObject.sha
|
|
54
|
+
if (!refTarget) throw new Error('cannot create annotated tag object')
|
|
55
|
+
}
|
|
56
|
+
const { data } = await client.post(`/repos/${owner}/${repo}/git/refs`, {
|
|
57
|
+
ref: `refs/tags/${tag}`,
|
|
58
|
+
sha: refTarget,
|
|
59
|
+
})
|
|
60
|
+
return { name: tag, sha: refTarget, target, annotated: Boolean(message), url: data && data.url }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function deleteTag(client, { owner, repo, tag }) {
|
|
64
|
+
ensureRepo({ owner, repo })
|
|
65
|
+
if (!tag) throw new Error('tag is required')
|
|
66
|
+
await client.del(`/repos/${owner}/${repo}/git/refs/tags/${encodeURIComponent(tag)}`)
|
|
67
|
+
return { deleted: true, tag }
|
|
68
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@goodandready/dsh-github-ops",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GitHub operations for DeepSeek Harness: releases, tags, a guarded generic API pass-through, workflow runs, secrets and sanitized mirror publication.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./lib/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./lib/index.js",
|
|
10
|
+
"./client": "./lib/client.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib",
|
|
14
|
+
"cordis.patch.yml",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"CHANGELOG.md",
|
|
17
|
+
"README.md",
|
|
18
|
+
"README.zh.md",
|
|
19
|
+
"README.ru.md"
|
|
20
|
+
],
|
|
21
|
+
"dsh": {
|
|
22
|
+
"bundle": {
|
|
23
|
+
"patch": "./cordis.patch.yml"
|
|
24
|
+
},
|
|
25
|
+
"client": {
|
|
26
|
+
"platform": "web",
|
|
27
|
+
"inject": [
|
|
28
|
+
"@deepseek-ai/dsh-client-locale",
|
|
29
|
+
"@deepseek-ai/dsh-client-ui-settings"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"pretest": "for f in lib/index.js lib/github.js lib/tools/*.js; do node --check \"$f\" || exit 1; done",
|
|
35
|
+
"test": "node --test test/*.test.mjs"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
39
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.8",
|
|
40
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
|
|
41
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
|
|
42
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
46
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.8",
|
|
47
|
+
"@deepseek-ai/dsh-settings": "^0.1.0-rc.8",
|
|
48
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.8",
|
|
49
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/GooDAnDReaDY/dsh-github-ops.git"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/GooDAnDReaDY/dsh-github-ops#readme",
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/GooDAnDReaDY/dsh-github-ops/issues"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
},
|
|
62
|
+
"author": "GooDAnDReaDY <support@goodandready.app>",
|
|
63
|
+
"keywords": [
|
|
64
|
+
"dsh",
|
|
65
|
+
"dsh-plugin",
|
|
66
|
+
"deepseek-harness",
|
|
67
|
+
"github",
|
|
68
|
+
"releases",
|
|
69
|
+
"tags",
|
|
70
|
+
"mirror",
|
|
71
|
+
"ci"
|
|
72
|
+
]
|
|
73
|
+
}
|