dsh-github-router 0.3.1 → 0.4.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 +79 -56
- package/CONTRIBUTING.md +17 -6
- package/README.md +26 -18
- package/README.zh.md +22 -17
- package/docs/design.md +51 -43
- package/lib/client.js +165 -116
- package/lib/config.js +167 -116
- package/lib/core/issue.js +212 -202
- package/lib/core/pr.js +454 -429
- package/lib/index.js +11 -6
- package/lib/routes/gh.js +120 -109
- package/lib/routes/html.js +266 -250
- package/lib/settings.js +23 -21
- package/lib/tunnel.js +363 -247
- package/package.json +11 -17
package/lib/config.js
CHANGED
|
@@ -1,116 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Plugin configuration schema and runtime-option resolution for
|
|
3
|
+
* dsh-github-router.
|
|
4
|
+
*
|
|
5
|
+
* The schema IS the settings surface. DSH reads the `Config` schema a plugin
|
|
6
|
+
* exports from its Loader entry, serves that entry's id
|
|
7
|
+
* (`dsh-github-router`) as the settings namespace to the browser, derives the
|
|
8
|
+
* configuration form from the schema, validates every write against it, and
|
|
9
|
+
* redacts `.role('secret')` fields on every wire boundary. There is no
|
|
10
|
+
* registration call anymore — `lib/settings.js` only reads the live values.
|
|
11
|
+
*
|
|
12
|
+
* Every field is `.volatile()`, which is what makes it editable from the
|
|
13
|
+
* configuration page AND live: the framework hands the plugin a Volatile
|
|
14
|
+
* reference whose `.get()` is the current value (user layer over composition
|
|
15
|
+
* layer over schema default), and a saved write applies to the NEXT tool call.
|
|
16
|
+
*
|
|
17
|
+
* The schema is deliberately FLAT: the plugin's card writes scalar fields by
|
|
18
|
+
* name, so nested objects cannot be edited from it. `resolveOptions` projects
|
|
19
|
+
* the flat section into the nested runtime shape the core aggregators use.
|
|
20
|
+
* @module dsh-github-router/config
|
|
21
|
+
*/
|
|
22
|
+
import z from '@deepseek-ai/schemastery'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Settings namespace carrying the plugin's configuration.
|
|
26
|
+
*
|
|
27
|
+
* Since DSH 0.1.7-rc.2 the namespace is the Loader entry id the bundle patch
|
|
28
|
+
* declares (`cordis.patch.yml` inserts the row as `dsh-github-router`); the
|
|
29
|
+
* browser half addresses the same string through `ctx.configForms.get(...)`
|
|
30
|
+
* and keys its Plugins-page card with it.
|
|
31
|
+
*/
|
|
32
|
+
export const NAMESPACE = 'dsh-github-router'
|
|
33
|
+
|
|
34
|
+
/** Composition/settings schema. All fields optional with safe defaults. */
|
|
35
|
+
export const Config = z.object({
|
|
36
|
+
/** Literal GitHub token (redacted on wire). Prefer tokenEnv. */
|
|
37
|
+
token: z.string().role('secret').volatile(),
|
|
38
|
+
/** Environment variable / credential ref naming the token. */
|
|
39
|
+
tokenEnv: z.string().role('credential-ref').default('GITHUB_TOKEN').volatile(),
|
|
40
|
+
/** Proxy URL for proxy routes. '' = inherit ambient proxy env; 'direct' = never proxy. */
|
|
41
|
+
proxy: z.string().default('').volatile(),
|
|
42
|
+
directTimeoutMs: z.number().min(1000).max(60000).default(8000).volatile(),
|
|
43
|
+
proxyTimeoutMs: z.number().min(1000).max(60000).default(15000).volatile(),
|
|
44
|
+
/** Retries for idempotent GETs on 429/5xx. */
|
|
45
|
+
retries: z.number().min(0).max(3).default(1).volatile(),
|
|
46
|
+
routesApi: z.boolean().default(true).volatile(),
|
|
47
|
+
routesGh: z.boolean().default(true).volatile(),
|
|
48
|
+
routesGit: z.boolean().default(true).volatile(),
|
|
49
|
+
routesHtml: z.boolean().default(true).volatile(),
|
|
50
|
+
/** Raw-content mirrors: OFF by default — the user must opt in. */
|
|
51
|
+
routesMirror: z.boolean().default(false).volatile(),
|
|
52
|
+
/** Raw-content mirror base URLs, e.g. ["https://ghproxy.net"]. Empty = none. */
|
|
53
|
+
mirrors: z.array(z.string()).default([]).volatile(),
|
|
54
|
+
/** PR/issue metadata cache TTL. */
|
|
55
|
+
cacheTtlMeta: z.number().min(0).default(300).volatile(),
|
|
56
|
+
/** Immutable-ish content (files, commits, diffs at a sha) cache TTL. */
|
|
57
|
+
cacheTtlContent: z.number().min(0).default(86400).volatile(),
|
|
58
|
+
/** Cap for every response body read by this plugin, in bytes. */
|
|
59
|
+
maxBytes: z.number().min(16384).max(8388608).default(1048576).volatile(),
|
|
60
|
+
/** Local repo paths granted for read-only git-route reads (log/diff/show only). */
|
|
61
|
+
repos: z.array(z.string()).default([]).volatile(),
|
|
62
|
+
/** Plugin-owned git fetch cache dir. '' = <DSH_HOME>/storages/dsh-github-router/git. */
|
|
63
|
+
gitCacheDir: z.string().default('').volatile(),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/** Every schema field, in the order the configuration card renders them. */
|
|
67
|
+
const CONFIG_FIELDS = [
|
|
68
|
+
'token',
|
|
69
|
+
'tokenEnv',
|
|
70
|
+
'proxy',
|
|
71
|
+
'directTimeoutMs',
|
|
72
|
+
'proxyTimeoutMs',
|
|
73
|
+
'retries',
|
|
74
|
+
'routesApi',
|
|
75
|
+
'routesGh',
|
|
76
|
+
'routesGit',
|
|
77
|
+
'routesHtml',
|
|
78
|
+
'routesMirror',
|
|
79
|
+
'mirrors',
|
|
80
|
+
'cacheTtlMeta',
|
|
81
|
+
'cacheTtlContent',
|
|
82
|
+
'maxBytes',
|
|
83
|
+
'repos',
|
|
84
|
+
'gitCacheDir',
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read one live configuration section out of the config the framework passes
|
|
89
|
+
* to `apply(ctx, config)`.
|
|
90
|
+
*
|
|
91
|
+
* A `.volatile()` field arrives as a Volatile reference, so its current value
|
|
92
|
+
* is read with `.get()` at the moment of the call; a plain value (a hand-built
|
|
93
|
+
* options object, or a unit test) passes through unchanged. Reading per call is
|
|
94
|
+
* what makes a settings write apply to the next tool call.
|
|
95
|
+
* @param config - the resolved plugin config, volatile references or plain values.
|
|
96
|
+
* @returns a flat section usable by {@link resolveOptions}.
|
|
97
|
+
*/
|
|
98
|
+
export function liveSection(config) {
|
|
99
|
+
const source = config ?? {}
|
|
100
|
+
const section = {}
|
|
101
|
+
for (const field of CONFIG_FIELDS) {
|
|
102
|
+
const value = source[field]
|
|
103
|
+
section[field] =
|
|
104
|
+
value !== null && typeof value === 'object' && typeof value.get === 'function'
|
|
105
|
+
? value.get()
|
|
106
|
+
: value
|
|
107
|
+
}
|
|
108
|
+
return section
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const PROXY_ENV_NAMES = ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy']
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Resolve one settings/composition section into fully-defaulted runtime
|
|
115
|
+
* options. A thunk-producing source is consumed at call time so a settings
|
|
116
|
+
* change applies to the NEXT tool call.
|
|
117
|
+
* @param section - the resolved settings section (already schema-defaulted).
|
|
118
|
+
*/
|
|
119
|
+
export function resolveOptions(section) {
|
|
120
|
+
const s = section ?? {}
|
|
121
|
+
const proxy =
|
|
122
|
+
typeof s.proxy === 'string' && s.proxy.trim().length > 0
|
|
123
|
+
? s.proxy.trim()
|
|
124
|
+
: undefined
|
|
125
|
+
const routes = {
|
|
126
|
+
api: s.routesApi !== false,
|
|
127
|
+
gh: s.routesGh !== false,
|
|
128
|
+
git: s.routesGit !== false,
|
|
129
|
+
html: s.routesHtml !== false,
|
|
130
|
+
mirror: s.routesMirror === true,
|
|
131
|
+
}
|
|
132
|
+
const mirrors = Array.isArray(s.mirrors)
|
|
133
|
+
? s.mirrors.filter((m) => typeof m === 'string' && m.trim().length > 0).map((m) => m.trim())
|
|
134
|
+
: []
|
|
135
|
+
const repos = Array.isArray(s.repos)
|
|
136
|
+
? s.repos.filter((r) => typeof r === 'string' && r.trim().length > 0).map((r) => r.trim())
|
|
137
|
+
: []
|
|
138
|
+
return {
|
|
139
|
+
token: typeof s.token === 'string' && s.token.length > 0 ? s.token : undefined,
|
|
140
|
+
tokenEnv: typeof s.tokenEnv === 'string' && s.tokenEnv.length > 0 ? s.tokenEnv : 'GITHUB_TOKEN',
|
|
141
|
+
proxy, // undefined = ambient env decides; 'direct' = never proxy
|
|
142
|
+
directTimeoutMs: Number.isFinite(s.directTimeoutMs) ? s.directTimeoutMs : 8000,
|
|
143
|
+
proxyTimeoutMs: Number.isFinite(s.proxyTimeoutMs) ? s.proxyTimeoutMs : 15000,
|
|
144
|
+
retries: Number.isFinite(s.retries) ? Math.min(3, Math.max(0, s.retries)) : 1,
|
|
145
|
+
routes,
|
|
146
|
+
mirrors,
|
|
147
|
+
cacheTtlSeconds: {
|
|
148
|
+
meta: Number.isFinite(s.cacheTtlMeta) ? s.cacheTtlMeta : 300,
|
|
149
|
+
content: Number.isFinite(s.cacheTtlContent) ? s.cacheTtlContent : 86400,
|
|
150
|
+
},
|
|
151
|
+
maxBytes: Number.isFinite(s.maxBytes) ? Math.min(8388608, Math.max(16384, s.maxBytes)) : 1048576,
|
|
152
|
+
repos,
|
|
153
|
+
gitCacheDir: typeof s.gitCacheDir === 'string' && s.gitCacheDir.trim().length > 0 ? s.gitCacheDir.trim() : undefined,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The proxy URL one route attempt uses: explicit override > ambient env > none. */
|
|
158
|
+
export function effectiveProxy(options, explicit) {
|
|
159
|
+
if (explicit !== undefined && explicit !== null && explicit !== '') return explicit
|
|
160
|
+
if (options.proxy === 'direct') return undefined
|
|
161
|
+
if (options.proxy !== undefined && options.proxy !== '') return options.proxy
|
|
162
|
+
for (const name of PROXY_ENV_NAMES) {
|
|
163
|
+
const value = process.env[name]
|
|
164
|
+
if (value && value.length > 0) return value
|
|
165
|
+
}
|
|
166
|
+
return undefined
|
|
167
|
+
}
|