@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.
@@ -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
+ }