dsh-plugin-resolver 0.0.0-stage → 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.
- package/ABLATION.md +78 -0
- package/BENCHMARK.md +76 -0
- package/LICENSE +21 -0
- package/README.md +172 -2
- package/cordis.patch.yml +5 -0
- package/host/register.js +166 -0
- package/index.js +62 -0
- package/logic/api.js +270 -0
- package/logic/catalog.js +281 -0
- package/logic/install.js +303 -0
- package/logic/resolve.js +396 -0
- package/logic/semver.js +227 -0
- package/logic/util.js +193 -0
- package/package.json +55 -4
package/logic/semver.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slice of semver this plugin needs: parse versions, compare them, and
|
|
3
|
+
* decide whether a version satisfies a range.
|
|
4
|
+
*
|
|
5
|
+
* Semantics intentionally match the Host's own compatibility preflight, which
|
|
6
|
+
* compares with `includePrerelease: true`: prerelease versions are ordinary
|
|
7
|
+
* values, so `0.2.0-rc.2` satisfies `^0.2.0-rc.2` but not `^0.2.1-rc.1`.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-plugin-resolver/lib/semver
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse a full semver version.
|
|
16
|
+
*
|
|
17
|
+
* @param {unknown} value
|
|
18
|
+
* @returns {{major: number, minor: number, patch: number, pre: string[]} | null}
|
|
19
|
+
*/
|
|
20
|
+
export function parseVersion(value) {
|
|
21
|
+
const match = VERSION_RE.exec(String(value ?? '').trim())
|
|
22
|
+
if (!match) return null
|
|
23
|
+
return {
|
|
24
|
+
major: Number(match[1]),
|
|
25
|
+
minor: Number(match[2]),
|
|
26
|
+
patch: Number(match[3]),
|
|
27
|
+
pre: match[4] ? match[4].split('.') : [],
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Compare two parsed versions.
|
|
33
|
+
*
|
|
34
|
+
* @param {{major: number, minor: number, patch: number, pre: string[]}} left
|
|
35
|
+
* @param {{major: number, minor: number, patch: number, pre: string[]}} right
|
|
36
|
+
* @returns {number} -1, 0, or 1
|
|
37
|
+
*/
|
|
38
|
+
export function compareParsed(left, right) {
|
|
39
|
+
for (const key of ['major', 'minor', 'patch']) {
|
|
40
|
+
if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1
|
|
41
|
+
}
|
|
42
|
+
if (left.pre.length === 0 && right.pre.length === 0) return 0
|
|
43
|
+
if (left.pre.length === 0) return 1
|
|
44
|
+
if (right.pre.length === 0) return -1
|
|
45
|
+
const length = Math.max(left.pre.length, right.pre.length)
|
|
46
|
+
for (let index = 0; index < length; index += 1) {
|
|
47
|
+
const a = left.pre[index]
|
|
48
|
+
const b = right.pre[index]
|
|
49
|
+
if (a === undefined) return -1
|
|
50
|
+
if (b === undefined) return 1
|
|
51
|
+
const aNumeric = /^\d+$/.test(a)
|
|
52
|
+
const bNumeric = /^\d+$/.test(b)
|
|
53
|
+
if (aNumeric && bNumeric) {
|
|
54
|
+
const delta = Number(a) - Number(b)
|
|
55
|
+
if (delta !== 0) return delta < 0 ? -1 : 1
|
|
56
|
+
} else if (aNumeric) return -1
|
|
57
|
+
else if (bNumeric) return 1
|
|
58
|
+
else if (a !== b) return a < b ? -1 : 1
|
|
59
|
+
}
|
|
60
|
+
return 0
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Compare two version strings.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} left
|
|
67
|
+
* @param {string} right
|
|
68
|
+
* @returns {number}
|
|
69
|
+
*/
|
|
70
|
+
export function compareVersions(left, right) {
|
|
71
|
+
const a = parseVersion(left)
|
|
72
|
+
const b = parseVersion(right)
|
|
73
|
+
if (!a || !b) return 0
|
|
74
|
+
return compareParsed(a, b)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse a possibly partial version such as `1`, `1.2`, `^1.2.3-rc.1`.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} raw
|
|
81
|
+
* @returns {{major: number, minor: number, patch: number, pre: string[], minorGiven: boolean, patchGiven: boolean} | null}
|
|
82
|
+
*/
|
|
83
|
+
function parsePartial(raw) {
|
|
84
|
+
const text = raw.trim().replace(/^v/, '')
|
|
85
|
+
const match = /^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(text)
|
|
86
|
+
if (!match) return null
|
|
87
|
+
return {
|
|
88
|
+
major: Number(match[1]),
|
|
89
|
+
minor: match[2] === undefined ? 0 : Number(match[2]),
|
|
90
|
+
patch: match[3] === undefined ? 0 : Number(match[3]),
|
|
91
|
+
pre: match[4] ? match[4].split('.') : [],
|
|
92
|
+
minorGiven: match[2] !== undefined,
|
|
93
|
+
patchGiven: match[3] !== undefined,
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const LOWER = { major: 0, minor: 0, patch: 0, pre: [] }
|
|
98
|
+
|
|
99
|
+
/** Full parse of a version-like token, or null when it is a wildcard. */
|
|
100
|
+
function asVersion(partial) {
|
|
101
|
+
if (!partial) return null
|
|
102
|
+
return { major: partial.major, minor: partial.minor, patch: partial.patch, pre: partial.pre }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Upper bound of a caret range. The `-0` prerelease mirrors node-semver's
|
|
107
|
+
* `includePrerelease: true` desugaring: it excludes prereleases of the next
|
|
108
|
+
* minor/major, which is exactly the trap that silently disables a plugin whose
|
|
109
|
+
* caret range was written against the previous support line.
|
|
110
|
+
*/
|
|
111
|
+
function caretUpper(partial) {
|
|
112
|
+
if (partial.major > 0 || !partial.minorGiven) {
|
|
113
|
+
return { major: partial.major + 1, minor: 0, patch: 0, pre: ['0'] }
|
|
114
|
+
}
|
|
115
|
+
if (partial.minor > 0 || !partial.patchGiven) {
|
|
116
|
+
return { major: 0, minor: partial.minor + 1, patch: 0, pre: ['0'] }
|
|
117
|
+
}
|
|
118
|
+
return { major: 0, minor: 0, patch: partial.patch + 1, pre: ['0'] }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Upper bound of a tilde range, with the same prerelease ceiling. */
|
|
122
|
+
function tildeUpper(partial) {
|
|
123
|
+
return partial.minorGiven
|
|
124
|
+
? { major: partial.major, minor: partial.minor + 1, patch: 0, pre: ['0'] }
|
|
125
|
+
: { major: partial.major + 1, minor: 0, patch: 0, pre: ['0'] }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Upper bound of a bare partial range: `1.2` means `>=1.2.0 <1.3.0`. */
|
|
129
|
+
function partialUpper(partial) {
|
|
130
|
+
if (partial.minorGiven && partial.patchGiven) return null
|
|
131
|
+
return partial.minorGiven
|
|
132
|
+
? { major: partial.major, minor: partial.minor + 1, patch: 0, pre: ['0'] }
|
|
133
|
+
: { major: partial.major + 1, minor: 0, patch: 0, pre: ['0'] }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Whether one whitespace-separated comparator set matches.
|
|
138
|
+
*
|
|
139
|
+
* @param {{major: number, minor: number, patch: number, pre: string[]}} version
|
|
140
|
+
* @param {string} set
|
|
141
|
+
* @returns {boolean}
|
|
142
|
+
*/
|
|
143
|
+
function matchesSet(version, set) {
|
|
144
|
+
const hyphen = /^(\S+)\s+-\s+(\S+)$/.exec(set.trim())
|
|
145
|
+
if (hyphen) {
|
|
146
|
+
const lower = asVersion(parsePartial(hyphen[1]))
|
|
147
|
+
const upperPartial = parsePartial(hyphen[2])
|
|
148
|
+
const upper = asVersion(upperPartial)
|
|
149
|
+
if (!lower || !upper) return false
|
|
150
|
+
return compareParsed(version, lower) >= 0 && compareParsed(version, upper) <= 0
|
|
151
|
+
}
|
|
152
|
+
const tokens = set.trim().split(/\s+/).filter(Boolean)
|
|
153
|
+
if (tokens.length === 0) return true
|
|
154
|
+
for (const token of tokens) {
|
|
155
|
+
if (token === '*' || token === 'x' || token === 'X') continue
|
|
156
|
+
const operatorMatch = /^(\^|~|>=|<=|>|<|=)?\s*(.*)$/.exec(token)
|
|
157
|
+
if (!operatorMatch) return false
|
|
158
|
+
const operator = operatorMatch[1] ?? ''
|
|
159
|
+
const raw = operatorMatch[2]
|
|
160
|
+
if (raw === '' || raw === '*' || raw === 'x' || raw === 'X') continue
|
|
161
|
+
const partial = parsePartial(raw)
|
|
162
|
+
if (!partial) return false
|
|
163
|
+
const point = asVersion(partial)
|
|
164
|
+
if (operator === '^') {
|
|
165
|
+
if (compareParsed(version, point) < 0) return false
|
|
166
|
+
if (compareParsed(version, caretUpper(partial)) >= 0) return false
|
|
167
|
+
} else if (operator === '~') {
|
|
168
|
+
if (compareParsed(version, point) < 0) return false
|
|
169
|
+
if (compareParsed(version, tildeUpper(partial)) >= 0) return false
|
|
170
|
+
} else if (operator === '>=') {
|
|
171
|
+
if (compareParsed(version, point) < 0) return false
|
|
172
|
+
} else if (operator === '>') {
|
|
173
|
+
if (compareParsed(version, point) <= 0) return false
|
|
174
|
+
} else if (operator === '<=') {
|
|
175
|
+
if (compareParsed(version, point) > 0) return false
|
|
176
|
+
} else if (operator === '<') {
|
|
177
|
+
if (compareParsed(version, point) >= 0) return false
|
|
178
|
+
} else if (operator === '=') {
|
|
179
|
+
if (compareParsed(version, point) !== 0) return false
|
|
180
|
+
} else {
|
|
181
|
+
// Bare token: an exact version, or a partial range such as `1.2`.
|
|
182
|
+
const upper = partialUpper(partial)
|
|
183
|
+
if (upper === null) {
|
|
184
|
+
if (compareParsed(version, point) !== 0) return false
|
|
185
|
+
} else {
|
|
186
|
+
if (compareParsed(version, point) < 0) return false
|
|
187
|
+
if (compareParsed(version, upper) >= 0) return false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return true
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Whether `version` satisfies `range`.
|
|
196
|
+
*
|
|
197
|
+
* @param {string} version
|
|
198
|
+
* @param {string} range
|
|
199
|
+
* @returns {boolean}
|
|
200
|
+
*/
|
|
201
|
+
export function satisfies(version, range) {
|
|
202
|
+
const parsed = parseVersion(version)
|
|
203
|
+
if (!parsed) return false
|
|
204
|
+
const text = String(range ?? '').trim()
|
|
205
|
+
if (text === '' || text === '*' || text === 'x') return true
|
|
206
|
+
for (const alternative of text.split('||')) {
|
|
207
|
+
if (matchesSet(parsed, alternative.trim())) return true
|
|
208
|
+
}
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Sort version strings newest first.
|
|
214
|
+
*
|
|
215
|
+
* @param {string[]} versions
|
|
216
|
+
* @returns {string[]}
|
|
217
|
+
*/
|
|
218
|
+
export function sortVersionsDesc(versions) {
|
|
219
|
+
return [...versions].sort((a, b) => compareVersions(b, a))
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Lowest version above another, used only for error messages. */
|
|
223
|
+
export function isValidVersion(value) {
|
|
224
|
+
return parseVersion(value) !== null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export { LOWER }
|
package/logic/util.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small host-side utilities: DSH home discovery, cached JSON, HTTP JSON, and a
|
|
3
|
+
* Windows-aware child-process runner. No host package imports on purpose.
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-plugin-resolver/lib/util
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'node:child_process'
|
|
9
|
+
import { existsSync } from 'node:fs'
|
|
10
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
11
|
+
import { homedir } from 'node:os'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
|
|
14
|
+
/** Absolute DSH home for the running host. */
|
|
15
|
+
export function dshHome() {
|
|
16
|
+
return process.env.DSH_HOME && process.env.DSH_HOME !== ''
|
|
17
|
+
? process.env.DSH_HOME
|
|
18
|
+
: path.join(homedir(), '.dsh')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Cache directory owned by this plugin. */
|
|
22
|
+
export function cacheDir() {
|
|
23
|
+
return path.join(dshHome(), 'cache', 'plugin-resolver')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Read a JSON file, returning undefined when absent or unparsable.
|
|
28
|
+
*
|
|
29
|
+
* @param {string} file
|
|
30
|
+
* @returns {Promise<any>}
|
|
31
|
+
*/
|
|
32
|
+
export async function readJson(file) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(await readFile(file, 'utf8'))
|
|
35
|
+
} catch {
|
|
36
|
+
return undefined
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Write a JSON file, creating parent directories.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} file
|
|
44
|
+
* @param {unknown} value
|
|
45
|
+
* @returns {Promise<void>}
|
|
46
|
+
*/
|
|
47
|
+
export async function writeJson(file, value) {
|
|
48
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
49
|
+
await writeFile(file, JSON.stringify(value), 'utf8')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Fetch and parse JSON with a hard timeout.
|
|
54
|
+
*
|
|
55
|
+
* @param {string} url
|
|
56
|
+
* @param {{timeoutMs?: number, headers?: Record<string, string>}} [options]
|
|
57
|
+
* @returns {Promise<any>}
|
|
58
|
+
*/
|
|
59
|
+
export async function fetchJson(url, options = {}) {
|
|
60
|
+
const timeoutMs = options.timeoutMs ?? 30_000
|
|
61
|
+
const controller = new AbortController()
|
|
62
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(url, {
|
|
65
|
+
signal: controller.signal,
|
|
66
|
+
headers: { accept: 'application/json', 'user-agent': 'dsh-plugin-resolver', ...(options.headers ?? {}) },
|
|
67
|
+
})
|
|
68
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} for ${url}`)
|
|
69
|
+
return await response.json()
|
|
70
|
+
} finally {
|
|
71
|
+
clearTimeout(timer)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Fetch raw bytes with a hard timeout.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} url
|
|
79
|
+
* @param {{timeoutMs?: number}} [options]
|
|
80
|
+
* @returns {Promise<Buffer>}
|
|
81
|
+
*/
|
|
82
|
+
export async function fetchBuffer(url, options = {}) {
|
|
83
|
+
const timeoutMs = options.timeoutMs ?? 60_000
|
|
84
|
+
const controller = new AbortController()
|
|
85
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
86
|
+
try {
|
|
87
|
+
const response = await fetch(url, {
|
|
88
|
+
signal: controller.signal,
|
|
89
|
+
redirect: 'follow',
|
|
90
|
+
headers: { 'user-agent': 'dsh-plugin-resolver' },
|
|
91
|
+
})
|
|
92
|
+
if (!response.ok) throw new Error(`HTTP ${response.status} for ${url}`)
|
|
93
|
+
return Buffer.from(await response.arrayBuffer())
|
|
94
|
+
} finally {
|
|
95
|
+
clearTimeout(timer)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Trim a one-line description for compact tool output. */
|
|
100
|
+
export function oneLine(text, limit = 120) {
|
|
101
|
+
if (typeof text !== 'string') return ''
|
|
102
|
+
const flat = text.replace(/\s+/g, ' ').trim()
|
|
103
|
+
return flat.length <= limit ? flat : `${flat.slice(0, limit - 1)}…`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Format a star count compactly. */
|
|
107
|
+
export function stars(value) {
|
|
108
|
+
const n = Number(value)
|
|
109
|
+
if (!Number.isFinite(n) || n <= 0) return 'new'
|
|
110
|
+
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Run a child process and collect its output.
|
|
115
|
+
*
|
|
116
|
+
* `.cmd`/`.bat` shims on Windows go through `cmd.exe`, because Node cannot
|
|
117
|
+
* spawn them directly; every other executable is spawned without a shell so
|
|
118
|
+
* arguments keep their exact boundaries.
|
|
119
|
+
*
|
|
120
|
+
* @param {string} command
|
|
121
|
+
* @param {string[]} args
|
|
122
|
+
* @param {{cwd?: string, env?: Record<string, string>, timeoutMs?: number, onLine?: (line: string) => void}} [options]
|
|
123
|
+
* @returns {Promise<{code: number, output: string, timedOut: boolean}>}
|
|
124
|
+
*/
|
|
125
|
+
export function runProcess(command, args, options = {}) {
|
|
126
|
+
const isShim = process.platform === 'win32' && /\.(cmd|bat)$/i.test(command)
|
|
127
|
+
const file = isShim ? process.env.COMSPEC || 'cmd.exe' : command
|
|
128
|
+
const argv = isShim
|
|
129
|
+
? ['/d', '/s', '/c', quoteCommandLine([command, ...args])]
|
|
130
|
+
: args
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
let output = ''
|
|
133
|
+
let settled = false
|
|
134
|
+
let timedOut = false
|
|
135
|
+
const child = spawn(file, argv, {
|
|
136
|
+
cwd: options.cwd,
|
|
137
|
+
env: { ...process.env, ...(options.env ?? {}) },
|
|
138
|
+
windowsHide: true,
|
|
139
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
140
|
+
})
|
|
141
|
+
const timer = options.timeoutMs
|
|
142
|
+
? setTimeout(() => {
|
|
143
|
+
timedOut = true
|
|
144
|
+
child.kill()
|
|
145
|
+
}, options.timeoutMs)
|
|
146
|
+
: undefined
|
|
147
|
+
const append = (chunk) => {
|
|
148
|
+
const text = String(chunk)
|
|
149
|
+
output += text
|
|
150
|
+
if (options.onLine) {
|
|
151
|
+
for (const line of text.split(/\r?\n/)) {
|
|
152
|
+
if (line.trim() !== '') options.onLine(line.trimEnd())
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (output.length > 400_000) output = output.slice(-200_000)
|
|
156
|
+
}
|
|
157
|
+
child.stdout?.on('data', append)
|
|
158
|
+
child.stderr?.on('data', append)
|
|
159
|
+
child.on('error', (error) => {
|
|
160
|
+
if (settled) return
|
|
161
|
+
settled = true
|
|
162
|
+
if (timer) clearTimeout(timer)
|
|
163
|
+
resolve({ code: 127, output: `${output}\n${error.message}`.trim(), timedOut })
|
|
164
|
+
})
|
|
165
|
+
child.on('close', (code) => {
|
|
166
|
+
if (settled) return
|
|
167
|
+
settled = true
|
|
168
|
+
if (timer) clearTimeout(timer)
|
|
169
|
+
resolve({ code: code ?? 1, output, timedOut })
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Quote an argv for the Windows `cmd.exe /s /c` fallback. */
|
|
175
|
+
function quoteCommandLine(argv) {
|
|
176
|
+
return argv
|
|
177
|
+
.map((part) => {
|
|
178
|
+
const value = String(part)
|
|
179
|
+
if (value === '') return '""'
|
|
180
|
+
if (!/[\s"&|<>^]/.test(value)) return value
|
|
181
|
+
return `"${value.replace(/"/g, '""')}"`
|
|
182
|
+
})
|
|
183
|
+
.join(' ')
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** True when a directory exists. */
|
|
187
|
+
export function dirExists(target) {
|
|
188
|
+
try {
|
|
189
|
+
return existsSync(target)
|
|
190
|
+
} catch {
|
|
191
|
+
return false
|
|
192
|
+
}
|
|
193
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-resolver",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent-facing DSH plugin resolver: search the catalog, pick a version the running DSH accepts, install (registry or GitHub-source build), and verify - no trial and error.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.js",
|
|
9
|
+
"./package.json": "./package.json"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"index.js",
|
|
13
|
+
"host",
|
|
14
|
+
"logic",
|
|
15
|
+
"cordis.patch.yml",
|
|
16
|
+
"README.md",
|
|
17
|
+
"BENCHMARK.md",
|
|
18
|
+
"ABLATION.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"test": "node test/selftest.mjs && node test/harness.mjs",
|
|
22
|
+
"bench": "node test/benchmark.mjs",
|
|
23
|
+
"ablate": "node test/ablation.mjs"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20",
|
|
27
|
+
"dsh": ">=0.2.0-rc.2 <0.3.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"deepseek",
|
|
31
|
+
"harness",
|
|
32
|
+
"dsh",
|
|
33
|
+
"dsh-plugin",
|
|
34
|
+
"plugin-manager",
|
|
35
|
+
"plugin-installer",
|
|
36
|
+
"resolver",
|
|
37
|
+
"catalog"
|
|
38
|
+
],
|
|
39
|
+
"author": "sakikoTGW",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/sakikoTGW/dsh-plugin-resolver.git"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/sakikoTGW/dsh-plugin-resolver#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/sakikoTGW/dsh-plugin-resolver/issues"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"registry": "https://registry.npmjs.org/"
|
|
51
|
+
},
|
|
52
|
+
"dsh": {
|
|
53
|
+
"bundle": {
|
|
54
|
+
"patch": "./cordis.patch.yml"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|