@jonathanleelx/meddle 0.5.0-beta.10 → 0.5.0-beta.11

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/README.md CHANGED
@@ -36,6 +36,8 @@ irm https://raw.githubusercontent.com/JonathanLee-LX/meddle/main/scripts/install
36
36
 
37
37
  二进制产物随 [GitHub Release](https://github.com/JonathanLee-LX/meddle/releases) 发布,也可手动下载。安装目录默认 `~/.local/bin`(已有 `~/.meddle/bin` 旧安装时原地升级),可通过 `MEDDLE_BIN_DIR` 环境变量自定义。`meddle update` 始终更新到当前运行二进制所在目录。
38
38
 
39
+ **私有仓库:** `meddle update`(含 `meddle update --beta`)/ 安装脚本下载 Release 资产时会读取 `GH_TOKEN`(优先)或 `GITHUB_TOKEN`(`repo` 或 fine-grained `contents:read`)。私有仓安装后升级也需要设置 token;未设置时会表现为 404(checksum sidecar missing)。也可手动:`gh release download -R JonathanLee-LX/meddle -p 'meddle-*'`。
40
+
39
41
  ### 命令
40
42
 
41
43
  ```bash
@@ -77,6 +77,10 @@ meddle update - 检查并升级 meddle
77
77
  meddle update --auto on|off 启用/禁用自动更新 (默认关闭)
78
78
  meddle update status 查看安装方式、当前版本和自动更新开关
79
79
  meddle update --help, -h 显示帮助
80
+
81
+ 私有仓库二进制更新需要设置 GH_TOKEN 或 GITHUB_TOKEN(优先 GH_TOKEN;
82
+ 权限:repo,或 fine-grained contents:read)。也可:
83
+ gh release download -R JonathanLee-LX/meddle -p 'meddle-*'
80
84
  `)
81
85
  }
82
86
 
@@ -27,6 +27,58 @@ const DEFAULT_CACHE_TTL_MS = 24 * 60 * 60 * 1000
27
27
  const DEFAULT_TIMEOUT_MS = 5000
28
28
  const DEFAULT_DOWNLOAD_TIMEOUT_MS = 120 * 1000
29
29
 
30
+ /**
31
+ * Resolve a GitHub token for private release downloads / latest redirects.
32
+ * Precedence: explicit argument → GH_TOKEN → GITHUB_TOKEN.
33
+ * @param {string} [explicit]
34
+ * @returns {string}
35
+ */
36
+ function resolveGithubToken(explicit) {
37
+ if (typeof explicit === 'string' && explicit.trim()) return explicit.trim()
38
+ const gh = process.env.GH_TOKEN
39
+ if (typeof gh === 'string' && gh.trim()) return gh.trim()
40
+ const github = process.env.GITHUB_TOKEN
41
+ if (typeof github === 'string' && github.trim()) return github.trim()
42
+ return ''
43
+ }
44
+
45
+ /**
46
+ * @param {string} token
47
+ * @returns {Record<string, string>}
48
+ */
49
+ function githubAuthHeaders(token) {
50
+ if (!token) return {}
51
+ // GitHub accepts Bearer for PATs / fine-grained tokens on release asset URLs.
52
+ return { Authorization: `Bearer ${token}` }
53
+ }
54
+
55
+ function privateReleaseAuthHint() {
56
+ return (
57
+ 'Private GitHub releases require GH_TOKEN or GITHUB_TOKEN '
58
+ + '(scope: repo, or fine-grained contents:read). '
59
+ + `Or: gh release download -R ${REPO} -p 'meddle-*'`
60
+ )
61
+ }
62
+
63
+ /**
64
+ * @param {'checksum' | 'download'} kind
65
+ * @param {number} status
66
+ * @param {boolean} hadToken
67
+ * @returns {string}
68
+ */
69
+ function formatDownloadStatusError(kind, status, hadToken) {
70
+ const base = kind === 'checksum'
71
+ ? `checksum sidecar missing (${status})`
72
+ : `download failed (${status})`
73
+ if ((status === 404 || status === 401 || status === 403) && !hadToken) {
74
+ return `${base}. ${privateReleaseAuthHint()}`
75
+ }
76
+ if ((status === 401 || status === 403) && hadToken) {
77
+ return `${base}. Token may lack repo/contents:read access to ${REPO}`
78
+ }
79
+ return base
80
+ }
81
+
30
82
  const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/
31
83
 
32
84
  /**
@@ -120,12 +172,14 @@ async function getLatestVersionNpm({ fetchImpl, registryUrl, timeoutMs, distTag
120
172
  * @param {{ fetchImpl?: Function, latestUrl?: string, timeoutMs?: number }} opts
121
173
  * @returns {Promise<string>}
122
174
  */
123
- async function getLatestVersionGithub({ fetchImpl, latestUrl, timeoutMs } = {}) {
175
+ async function getLatestVersionGithub({ fetchImpl, latestUrl, timeoutMs, githubToken } = {}) {
124
176
  const doFetch = fetchImpl || fetch
125
177
  const url = latestUrl || process.env.MEDDLE_GITHUB_LATEST_URL || DEFAULT_GITHUB_LATEST_URL
178
+ const token = resolveGithubToken(githubToken)
126
179
  const response = await doFetch(url, {
127
180
  redirect: 'manual',
128
181
  signal: AbortSignal.timeout(timeoutMs || DEFAULT_TIMEOUT_MS),
182
+ headers: githubAuthHeaders(token),
129
183
  })
130
184
  if (response.status < 300 || response.status >= 400) {
131
185
  throw new Error(`GitHub latest release did not redirect (${response.status})`)
@@ -287,7 +341,7 @@ function sha256Hex(buffer) {
287
341
  * baseUrl?: string, fetchImpl?: Function, timeoutMs?: number,
288
342
  * retries?: number, retryDelayMs?: number,
289
343
  * onProgress?: Function, onAttempt?: Function,
290
- * fsImpl?: object }} opts
344
+ * githubToken?: string, fsImpl?: object }} opts
291
345
  * @returns {Promise<{ installed: string, backup: string, version: string }>}
292
346
  */
293
347
  async function downloadBinaryAsset(opts) {
@@ -300,6 +354,8 @@ async function downloadBinaryAsset(opts) {
300
354
  const retryDelayMs = opts.retryDelayMs === undefined ? 1000 : opts.retryDelayMs
301
355
  const onProgress = typeof opts.onProgress === 'function' ? opts.onProgress : () => {}
302
356
  const onAttempt = typeof opts.onAttempt === 'function' ? opts.onAttempt : () => {}
357
+ const token = resolveGithubToken(opts.githubToken)
358
+ const authHeaders = githubAuthHeaders(token)
303
359
 
304
360
  const asset = getAssetName({ platform, arch })
305
361
  const assetUrl = `${baseUrl}/v${version}/${asset}`
@@ -327,8 +383,8 @@ async function downloadBinaryAsset(opts) {
327
383
  }
328
384
  resetIdle()
329
385
  try {
330
- const response = await doFetch(assetUrl, { signal: controller.signal })
331
- if (!response.ok) throw new Error(`download failed (${response.status})`)
386
+ const response = await doFetch(assetUrl, { signal: controller.signal, headers: authHeaders })
387
+ if (!response.ok) throw new Error(formatDownloadStatusError('download', response.status, Boolean(token)))
332
388
  const total = Number(response.headers.get('content-length')) || 0
333
389
  const chunks = []
334
390
  let received = 0
@@ -365,8 +421,11 @@ async function downloadBinaryAsset(opts) {
365
421
  // download — otherwise the sidecar body stream aborts before it is read
366
422
  // and the whole install fails with "operation aborted" right after the
367
423
  // payload reached 100%.
368
- const shaResponse = await doFetch(shaUrl, { signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS) })
369
- if (!shaResponse.ok) throw new Error(`checksum sidecar missing (${shaResponse.status})`)
424
+ const shaResponse = await doFetch(shaUrl, {
425
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS),
426
+ headers: authHeaders,
427
+ })
428
+ if (!shaResponse.ok) throw new Error(formatDownloadStatusError('checksum', shaResponse.status, Boolean(token)))
370
429
  const shaText = await shaResponse.text()
371
430
 
372
431
  const payload = await fetchPayload()
@@ -559,5 +618,7 @@ module.exports = {
559
618
  setAutoUpdate,
560
619
  resolveUpdateBinDir,
561
620
  runAsyncUpdateCheck,
621
+ resolveGithubToken,
622
+ githubAuthHeaders,
562
623
  DEFAULT_DOWNLOAD_TIMEOUT_MS,
563
624
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jonathanleelx/meddle",
3
- "version": "0.5.0-beta.10",
3
+ "version": "0.5.0-beta.11",
4
4
  "description": "A simple development proxy server with custom routing rules, mock responses, HTTPS interception, and a plugin system",
5
5
  "type": "commonjs",
6
6
  "main": "index.js",