Repository navigation
Expand file tree
/
Copy pathcore.js
More file actions
135 lines (122 loc) · 5.37 KB
/
Copy pathcore.js
File metadata and controls
135 lines (122 loc) · 5.37 KB
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { basename, dirname, join } from 'node:path'
import { readFile } from 'node:fs/promises'
export const DEFAULT_MAX_STARS = null
export const MAX_REQUEST_BYTES = 64 * 1024
export function clampMaxStars(value, fallback = DEFAULT_MAX_STARS) {
if (value === undefined || value === null || value === '') return fallback
const parsed = Number(value)
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : fallback
}
export function packageNameFromSpecifier(specifier) {
if (typeof specifier !== 'string' || specifier.length === 0) return null
if (specifier.startsWith('@')) {
const parts = specifier.split('/')
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null
}
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')) return null
return specifier.split('/')[0] || null
}
export function shortPluginName(moduleName) {
const packageName = packageNameFromSpecifier(moduleName) || basename(String(moduleName || 'plugin'))
const unscoped = packageName.startsWith('@') ? packageName.split('/')[1] : packageName
return unscoped
.replace(/^cordis-plugin-/, '')
.replace(/^dsh-(?:host-|client-)?/, '')
}
export function normalizeDescription(value, maxLength = 500) {
if (typeof value !== 'string') return ''
return value.replace(/\s+/g, ' ').trim().slice(0, maxLength)
}
export function normalizeRepositoryUrl(repository) {
const value = typeof repository === 'string' ? repository : repository?.url
if (typeof value !== 'string') return null
const normalized = value
.replace(/^git\+/, '')
.replace(/^git:\/\/github\.com\//, 'https://github.com/')
.replace(/^git@github\.com:/, 'https://github.com/')
.replace(/\.git$/, '')
return normalized.startsWith('https://github.com/') ? normalized : null
}
export function categoryFor(repo) {
const haystack = [repo.name, repo.description, ...(repo.topics || [])].join(' ').toLowerCase()
const rules = [
['记忆', /memory|memento|mneme|recall|记忆/],
['界面', /(?:^|\W)(ui|tui|theme|sidebar|panel|skin)(?:\W|$)|界面|侧边栏/],
['自动化', /workflow|schedule|timer|automation|job|任务|自动化/],
['视觉', /vision|image|ocr|visual|图片|视觉/],
['开发工具', /git|test|debug|toolkit|review|code|开发|审查/],
['连接器', /mcp|browser|bridge|acp|connector|连接/],
]
return rules.find(([, pattern]) => pattern.test(haystack))?.[0] || '其他'
}
export function normalizeGitHubRepository(repo, maxStars = DEFAULT_MAX_STARS) {
const stars = Number(repo?.stargazers_count || 0)
const fullName = String(repo?.full_name || '')
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(fullName)) return null
return {
repo: fullName,
name: String(repo?.name || fullName.split('/')[1]),
description: normalizeDescription(repo?.description),
stars,
forks: Number(repo?.forks_count || 0),
url: String(repo?.html_url || `https://github.com/${fullName}`),
updatedAt: String(repo?.updated_at || ''),
topics: Array.isArray(repo?.topics) ? repo.topics.slice(0, 20).map(String) : [],
category: categoryFor(repo),
eligible: maxStars === null || stars <= maxStars,
installSpec: `github:${fullName}`,
}
}
export function validatePluginSpec(value) {
if (typeof value !== 'string') return null
const spec = value.trim()
const npm = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-zA-Z0-9._~^*+<>=|-]+)?$/
const github = /^github:[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:#[A-Za-z0-9._/-]+)?$/
return spec.length <= 180 && (npm.test(spec) || github.test(spec)) ? spec : null
}
export function validatePackageName(value) {
if (typeof value !== 'string') return null
const name = value.trim()
const pattern = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/
return name.length <= 128 && pattern.test(name) ? name : null
}
export function commandForAction(action, value) {
if (action === 'install') {
const spec = validatePluginSpec(value)
return spec ? ['add', spec] : null
}
if (action === 'update' || action === 'remove') {
const name = validatePackageName(value)
return name ? [action, name] : null
}
return null
}
export async function findPackageJson(entryFile, stopAt) {
let cursor = dirname(entryFile)
const boundary = stopAt ? dirname(stopAt) : null
while (true) {
const candidate = join(cursor, 'package.json')
try {
const parsed = JSON.parse(await readFile(candidate, 'utf8'))
if (parsed && typeof parsed === 'object') return { path: candidate, value: parsed }
} catch (error) {
if (error?.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error
}
const parent = dirname(cursor)
if (parent === cursor || cursor === boundary) return null
cursor = parent
}
}
export function mergeDescription({ moduleName, packageDescription, overrides, locale = 'zh' }) {
const record = overrides?.[moduleName]
const preferred = locale === 'en' ? record?.en : record?.zh
const alternate = locale === 'en' ? record?.zh : record?.en
const chosen = normalizeDescription(preferred) || normalizeDescription(alternate)
if (chosen) return { text: chosen, source: 'user' }
const packageText = normalizeDescription(packageDescription)
if (packageText) return { text: packageText, source: 'package' }
return {
text: `${shortPluginName(moduleName)} 是当前 DeepSeek Harness 组合中的一个功能模块。`,
source: 'generated',
}
}