dsh-update-center 0.8.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,101 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, symlinkSync } from 'node:fs'
2
+ import { homedir } from 'node:os'
3
+ import { dirname, join, resolve } from 'node:path'
4
+ import { spawnSync } from 'node:child_process'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
8
+ const mode = process.argv[2] ?? '--all'
9
+
10
+ function packageName(dir) {
11
+ try {
12
+ return JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).name
13
+ } catch {
14
+ return null
15
+ }
16
+ }
17
+
18
+ function findCheckout() {
19
+ const candidates = [
20
+ process.env.DSH_CHECKOUT,
21
+ resolve(root, '..', '..', 'deepseek-harness'),
22
+ join(homedir(), 'dsh-harness'),
23
+ join(homedir(), 'deepseek-harness'),
24
+ join(homedir(), '.dsh', 'dsh-harness'),
25
+ ].filter(Boolean)
26
+ return candidates.find((candidate) => packageName(candidate) === '@deepseek-ai/dsh-root') ?? null
27
+ }
28
+
29
+ const checkout = findCheckout()
30
+ if (!checkout) {
31
+ console.error('build: cannot locate the dsh checkout; set DSH_CHECKOUT')
32
+ process.exit(1)
33
+ }
34
+
35
+ function linkPackage(name, targetRelative) {
36
+ const target = resolve(checkout, targetRelative)
37
+ const link = resolve(root, 'node_modules', ...name.split('/'))
38
+ if (!existsSync(target)) throw new Error(`dependency target missing: ${target}`)
39
+ rmSync(link, { recursive: true, force: true })
40
+ mkdirSync(dirname(link), { recursive: true })
41
+ symlinkSync(target, link, process.platform === 'win32' ? 'junction' : 'dir')
42
+ }
43
+
44
+ function linkStandardSchema() {
45
+ const store = join(checkout, 'node_modules', '.pnpm')
46
+ if (!existsSync(store)) return
47
+ const entry = readdirSync(store).find((name) => name.toLowerCase().startsWith('@standard-schema+spec@'))
48
+ if (!entry) return
49
+ const target = join(store, entry, 'node_modules', '@standard-schema', 'spec')
50
+ if (existsSync(target)) linkAbsolute('@standard-schema/spec', target)
51
+ }
52
+
53
+ function linkAbsolute(name, target) {
54
+ const link = resolve(root, 'node_modules', ...name.split('/'))
55
+ rmSync(link, { recursive: true, force: true })
56
+ mkdirSync(dirname(link), { recursive: true })
57
+ symlinkSync(resolve(target), link, process.platform === 'win32' ? 'junction' : 'dir')
58
+ }
59
+
60
+ function prepareDependencies() {
61
+ linkPackage('cordis', 'vendor/cordis')
62
+ linkPackage('cosmokit', 'vendor/cosmokit')
63
+ linkPackage('schemastery', 'vendor/schemastery')
64
+ linkPackage('@deepseek-ai/dsh-tools', 'packages/core/tools')
65
+ linkPackage('@deepseek-ai/dsh-llm', 'packages/llm/llm')
66
+ linkPackage('@deepseek-ai/dsh-system-prompt', 'packages/core/system-prompt')
67
+ linkPackage('@deepseek-ai/dsh-client-ui-slots', 'packages/client/ui-slots')
68
+ linkPackage('@types/node', 'node_modules/@types/node')
69
+ linkStandardSchema()
70
+ }
71
+
72
+ function runNode(entry, args) {
73
+ const result = spawnSync(process.execPath, [entry, ...args], {
74
+ cwd: root,
75
+ encoding: 'utf8',
76
+ stdio: 'inherit',
77
+ })
78
+ if (result.error) throw result.error
79
+ if (result.status !== 0) process.exit(result.status ?? 1)
80
+ }
81
+
82
+ prepareDependencies()
83
+
84
+ const tsc = join(checkout, 'node_modules', 'typescript', 'bin', 'tsc')
85
+ const tsdown = join(checkout, 'node_modules', 'tsdown', 'dist', 'run.mjs')
86
+
87
+ if (mode === '--typecheck') {
88
+ runNode(tsc, ['-p', 'tsconfig.json', '--noEmit'])
89
+ } else if (mode === '--client') {
90
+ runNode(tsdown, [])
91
+ } else if (mode === '--all') {
92
+ runNode(tsc, ['-p', 'tsconfig.json'])
93
+ runNode(tsdown, [])
94
+ // host 产物由 tsc 逐文件转译(不打包),共享的 .mjs 模块需随产物分发。
95
+ for (const shared of ['run-command.mjs', 'github-topic.mjs', 'npm-mapping.mjs']) {
96
+ copyFileSync(join(root, 'src', shared), join(root, 'lib', shared))
97
+ }
98
+ } else {
99
+ console.error(`build: unknown mode ${mode}`)
100
+ process.exit(2)
101
+ }
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ set -eu
3
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
4
+ exec node "$ROOT/scripts/build.mjs" --all
@@ -0,0 +1,93 @@
1
+ /**
2
+ * 维护本仓库根目录的 plugins.json 注册表(像 awesome-dsh-plugin 一样对外提供):
3
+ * plugins.json —— 已有注册表(若存在)作为基底累计合并 GitHub topic:dsh-plugin
4
+ * 星标前 500(排除 fork)+ 本插件自身,按 owner/name 去重:掉出扫描窗口
5
+ * 的老条目保留,新条目追加,已知条目刷新星标。
6
+ * data/registry-snapshot.json —— 同一份数据的随包离线快照。
7
+ * data/extra-plugins.json —— 常驻条目(加载时无条件合并,保证 0 星新插件可见)。
8
+ *
9
+ * 运行:npm run refresh:snapshot(需网络可达 api.github.com)
10
+ */
11
+ import { readFile, writeFile } from 'node:fs/promises'
12
+ import { dirname, join } from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+ import { buildRegistryFromRepos, mergeGithubTopic } from '../src/github-topic.mjs'
15
+ import { applyNpmMapping } from '../src/npm-mapping.mjs'
16
+
17
+ const root = join(dirname(fileURLToPath(import.meta.url)), '..')
18
+ const SELF_REPO = 'Britneycode/dsh-update-center'
19
+ const TOPIC_PAGES = 5
20
+ const MAX_ENTRIES = 2000
21
+
22
+ async function fetchJson(url) {
23
+ const response = await fetch(url, { headers: { accept: 'application/vnd.github+json' } })
24
+ if (!response.ok) throw new Error(`${response.status} ${url}`)
25
+ return response.json()
26
+ }
27
+
28
+ async function fetchGithubTopicRepos() {
29
+ const repos = []
30
+ for (let page = 1; page <= TOPIC_PAGES; page++) {
31
+ const url = new URL('https://api.github.com/search/repositories')
32
+ url.searchParams.set('q', 'topic:dsh-plugin')
33
+ url.searchParams.set('sort', 'stars')
34
+ url.searchParams.set('order', 'desc')
35
+ url.searchParams.set('per_page', '100')
36
+ url.searchParams.set('page', String(page))
37
+ if (url.protocol !== 'https:' || url.hostname !== 'api.github.com') throw new Error(`endpoint check failed: ${url.host}`)
38
+ const payload = await fetchJson(url)
39
+ if (!Array.isArray(payload?.items)) break
40
+ repos.push(...payload.items.filter((repo) => repo && repo.fork !== true))
41
+ if (payload.items.length < 100) break
42
+ }
43
+ return repos
44
+ }
45
+
46
+ async function loadExistingRegistry() {
47
+ try {
48
+ const data = JSON.parse(await readFile(join(root, 'plugins.json'), 'utf8'))
49
+ if (Array.isArray(data?.plugins) && data.plugins.length) return data
50
+ } catch { /* 首次生成时没有存量 */ }
51
+ return null
52
+ }
53
+
54
+ async function fetchNpmSearchObjects() {
55
+ const objects = []
56
+ for (let from = 0; from < 1000; from += 250) {
57
+ const url = new URL('https://registry.npmjs.org/-/v1/search')
58
+ url.searchParams.set('text', 'keywords:dsh-plugin')
59
+ url.searchParams.set('size', '250')
60
+ url.searchParams.set('from', String(from))
61
+ if (url.protocol !== 'https:' || url.hostname !== 'registry.npmjs.org') throw new Error(`endpoint check failed: ${url.host}`)
62
+ const payload = await fetchJson(url)
63
+ const items = Array.isArray(payload?.objects) ? payload.objects : []
64
+ if (!items.length) break
65
+ objects.push(...items)
66
+ if (from + items.length >= Number(payload?.total ?? 0)) break
67
+ }
68
+ return objects
69
+ }
70
+
71
+ const topicRepos = await fetchGithubTopicRepos()
72
+ if (!topicRepos.length) throw new Error('GitHub topic scan returned nothing')
73
+ const selfRepo = await fetchJson(`https://api.github.com/repos/${SELF_REPO}`)
74
+ if (!selfRepo?.name) throw new Error('self repo info invalid')
75
+
76
+ const existing = await loadExistingRegistry()
77
+ const data = existing ?? buildRegistryFromRepos(topicRepos)
78
+ const merged = mergeGithubTopic(data, [...topicRepos, selfRepo])
79
+ const npmObjects = await fetchNpmSearchObjects()
80
+ const mapped = applyNpmMapping(data, npmObjects)
81
+ if (data.plugins.length > MAX_ENTRIES) {
82
+ data.plugins.sort((a, b) => Number(b.stars ?? 0) - Number(a.stars ?? 0))
83
+ data.plugins = data.plugins.slice(0, MAX_ENTRIES)
84
+ }
85
+ data.updated = new Date().toISOString().slice(0, 10)
86
+ data.count = data.plugins.length
87
+
88
+ const text = JSON.stringify(data, null, 1) + '\n'
89
+ await writeFile(join(root, 'plugins.json'), text, 'utf8')
90
+ await writeFile(join(root, 'data', 'registry-snapshot.json'), text, 'utf8')
91
+ await writeFile(join(root, 'data', 'extra-plugins.json'), JSON.stringify([selfRepo], null, 1) + '\n', 'utf8')
92
+ const base = existing ? `存量 ${existing.plugins.length}` : '新建'
93
+ console.log(`plugins.json 已生成:${base} + 本轮扫描新增 ${merged.added}(含去重)+ npm 映射 ${mapped} = 共 ${data.plugins.length} 条(上限 ${MAX_ENTRIES})`)