dsh-my-computer 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +257 -2
- package/cordis.patch.yml +11 -0
- package/lib/client.js +1566 -0
- package/lib/index.js +840 -0
- package/lib/zip.js +365 -0
- package/package.json +56 -4
- package/tools/client-check.mjs +429 -0
- package/tools/right-tab-check.mjs +118 -0
- package/tools/smoke.mjs +489 -0
package/lib/zip.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// dsh-my-computer —— 整目录打包(零依赖、流式、边读边发)
|
|
2
|
+
//
|
|
3
|
+
// 为什么自己写:这个插件承诺零 npm 依赖(profile 里 pnpm prune 会干掉
|
|
4
|
+
// extraneous 包),为了一个 zip 引入 archiver / yazl 不划算,而 Node 自带
|
|
5
|
+
// 的 zlib.deflateRawSync 已经足够——zip 的「压缩」就是把 deflateRaw 的字节
|
|
6
|
+
// 塞进文件条目里。
|
|
7
|
+
//
|
|
8
|
+
// 策略(对手机远程下载友好):
|
|
9
|
+
// * 小文件(<= 8MB)先读进内存 → 试压 → 压不动的就 STORE,压得动的用 DEFLATE,
|
|
10
|
+
// 两种都能在写本地头之前知道大小和 CRC,不必用数据描述符;
|
|
11
|
+
// * 大文件 STORE + 流式,靠数据描述符(bit 3)补 CRC/大小,内存恒定;
|
|
12
|
+
// * 一律带 UTF-8 名字标志(bit 11),中文文件名解压不乱码;
|
|
13
|
+
// * 目录条目带尾部 '/',空目录也能还原;
|
|
14
|
+
// * 打包前先扫一遍做上限检查(条目数 / 总字节),超了在发响应头之前就报错,
|
|
15
|
+
// 避免「下载了一半才失败」。
|
|
16
|
+
//
|
|
17
|
+
// 不支持(明确报错,不静默降级):Zip64(>4GB 或 >65535 个条目)。真需要就分批。
|
|
18
|
+
|
|
19
|
+
import fs from 'node:fs'
|
|
20
|
+
import fsp from 'node:fs/promises'
|
|
21
|
+
import path from 'node:path'
|
|
22
|
+
import zlib from 'node:zlib'
|
|
23
|
+
|
|
24
|
+
const MAX_ENTRIES = 20000
|
|
25
|
+
const MAX_TOTAL_BYTES = 3.9 * 1000 * 1000 * 1000 // 留出 Zip64 阈值的余量
|
|
26
|
+
const INLINE_LIMIT = 8 * 1024 * 1024 // 进内存试压的上限
|
|
27
|
+
|
|
28
|
+
/** 已经是压缩格式的扩展名,再 deflate 只是白烧 CPU。 */
|
|
29
|
+
const INCOMPRESSIBLE = new Set([
|
|
30
|
+
'.jpg', '.jpeg', '.png', '.gif', '.webp', '.avif', '.heic', '.bmp', '.ico',
|
|
31
|
+
'.mp4', '.mov', '.mkv', '.avi', '.webm', '.mp3', '.m4a', '.aac', '.ogg', '.flac', '.wav',
|
|
32
|
+
'.zip', '.7z', '.rar', '.gz', '.bz2', '.xz', '.zst', '.tgz',
|
|
33
|
+
'.pdf', '.docx', '.xlsx', '.pptx', '.odt', '.epub',
|
|
34
|
+
'.apk', '.exe', '.dll', '.msi', '.iso', '.bin', '.pak',
|
|
35
|
+
'.db', '.sqlite', '.sqlite3', '.woff', '.woff2', '.ttf', '.otf',
|
|
36
|
+
])
|
|
37
|
+
|
|
38
|
+
/* ------------------------------------------------------------------ *
|
|
39
|
+
* CRC32(zip 用的多项式,查表法)
|
|
40
|
+
* ------------------------------------------------------------------ */
|
|
41
|
+
|
|
42
|
+
const CRC_TABLE = (() => {
|
|
43
|
+
const table = new Int32Array(256)
|
|
44
|
+
for (let i = 0; i < 256; i += 1) {
|
|
45
|
+
let c = i
|
|
46
|
+
for (let k = 0; k < 8; k += 1) {
|
|
47
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
|
|
48
|
+
}
|
|
49
|
+
table[i] = c
|
|
50
|
+
}
|
|
51
|
+
return table
|
|
52
|
+
})()
|
|
53
|
+
|
|
54
|
+
function crc32(buffer) {
|
|
55
|
+
let crc = -1
|
|
56
|
+
for (let i = 0; i < buffer.length; i += 1) {
|
|
57
|
+
crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ buffer[i]) & 0xff]
|
|
58
|
+
}
|
|
59
|
+
return (crc ^ -1) >>> 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* ------------------------------------------------------------------ *
|
|
63
|
+
* 时间戳 → DOS 时间
|
|
64
|
+
* ------------------------------------------------------------------ */
|
|
65
|
+
|
|
66
|
+
function dosDateTime(ms) {
|
|
67
|
+
const d = ms ? new Date(ms) : new Date()
|
|
68
|
+
const year = d.getFullYear()
|
|
69
|
+
if (!Number.isFinite(year) || year < 1980) return { date: 0x21, time: 0 }
|
|
70
|
+
const date = ((year - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate()
|
|
71
|
+
const time = (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1)
|
|
72
|
+
return { date: date & 0xffff, time: time & 0xffff }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* ------------------------------------------------------------------ *
|
|
76
|
+
* 扫描:先看清要打什么,才能在写响应头之前做上限判断
|
|
77
|
+
* ------------------------------------------------------------------ */
|
|
78
|
+
|
|
79
|
+
async function scanDir(rootDir, { fail, onWarn }) {
|
|
80
|
+
const items = []
|
|
81
|
+
let totalBytes = 0
|
|
82
|
+
|
|
83
|
+
async function walk(absDir, relDir) {
|
|
84
|
+
let dirents
|
|
85
|
+
try {
|
|
86
|
+
dirents = await fsp.readdir(absDir, { withFileTypes: true })
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// 读不动的子目录不要整个打包跟着失败,跳过并记一笔
|
|
89
|
+
onWarn?.({ path: absDir, message: String(error?.message || error) })
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
dirents.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
93
|
+
for (const dirent of dirents) {
|
|
94
|
+
const abs = path.win32.join(absDir, dirent.name)
|
|
95
|
+
const rel = relDir ? relDir + '/' + dirent.name : dirent.name
|
|
96
|
+
const isLink = dirent.isSymbolicLink()
|
|
97
|
+
let st
|
|
98
|
+
try {
|
|
99
|
+
// 快捷方式/链接按「不跟随」处理:跟随可能绕成环,也可能跨到别的盘
|
|
100
|
+
st = isLink ? await fsp.lstat(abs) : await fsp.stat(abs)
|
|
101
|
+
} catch (error) {
|
|
102
|
+
onWarn?.({ path: abs, message: String(error?.message || error) })
|
|
103
|
+
continue
|
|
104
|
+
}
|
|
105
|
+
if (isLink) {
|
|
106
|
+
onWarn?.({ path: abs, message: '跳过符号链接/快捷方式(避免打包出环)' })
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (st.isDirectory()) {
|
|
110
|
+
items.push({ kind: 'dir', abs, rel, size: 0, mtimeMs: st.mtimeMs })
|
|
111
|
+
if (items.length > MAX_ENTRIES) fail('ZIP_TOO_MANY_ENTRIES', `目录内条目超过 ${MAX_ENTRIES} 个,请分批下载`, 413)
|
|
112
|
+
await walk(abs, rel)
|
|
113
|
+
} else if (st.isFile()) {
|
|
114
|
+
totalBytes += st.size
|
|
115
|
+
items.push({ kind: 'file', abs, rel, size: st.size, mtimeMs: st.mtimeMs })
|
|
116
|
+
if (items.length > MAX_ENTRIES) fail('ZIP_TOO_MANY_ENTRIES', `目录内条目超过 ${MAX_ENTRIES} 个,请分批下载`, 413)
|
|
117
|
+
if (totalBytes > MAX_TOTAL_BYTES) {
|
|
118
|
+
fail('ZIP_TOO_LARGE', '目录内容超过 3.9GB(Zip64 未支持),请分批下载', 413)
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
onWarn?.({ path: abs, message: '跳过非常规文件(设备/管道等)' })
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
await walk(rootDir, '')
|
|
127
|
+
return { items, totalBytes }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* ------------------------------------------------------------------ *
|
|
131
|
+
* 结构体小工具
|
|
132
|
+
* ------------------------------------------------------------------ */
|
|
133
|
+
|
|
134
|
+
function localHeader({ nameBuf, method, crc, compSize, size, dos, flags }) {
|
|
135
|
+
const head = Buffer.alloc(30)
|
|
136
|
+
head.writeUInt32LE(0x04034b50, 0)
|
|
137
|
+
head.writeUInt16LE(20, 4) // version needed
|
|
138
|
+
head.writeUInt16LE(flags, 6)
|
|
139
|
+
head.writeUInt16LE(method, 8)
|
|
140
|
+
head.writeUInt16LE(dos.time, 10)
|
|
141
|
+
head.writeUInt16LE(dos.date, 12)
|
|
142
|
+
head.writeUInt32LE(crc, 14)
|
|
143
|
+
head.writeUInt32LE(compSize, 18)
|
|
144
|
+
head.writeUInt32LE(size, 22)
|
|
145
|
+
head.writeUInt16LE(nameBuf.length, 26)
|
|
146
|
+
head.writeUInt16LE(0, 28) // extra
|
|
147
|
+
return Buffer.concat([head, nameBuf])
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function dataDescriptor({ crc, compSize, size }) {
|
|
151
|
+
const buf = Buffer.alloc(16)
|
|
152
|
+
buf.writeUInt32LE(0x08074b50, 0)
|
|
153
|
+
buf.writeUInt32LE(crc, 4)
|
|
154
|
+
buf.writeUInt32LE(compSize, 8)
|
|
155
|
+
buf.writeUInt32LE(size, 12)
|
|
156
|
+
return buf
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function centralHeader(entry) {
|
|
160
|
+
const head = Buffer.alloc(46)
|
|
161
|
+
head.writeUInt32LE(0x02014b50, 0)
|
|
162
|
+
head.writeUInt16LE(20, 4) // version made by
|
|
163
|
+
head.writeUInt16LE(20, 6) // version needed
|
|
164
|
+
head.writeUInt16LE(entry.flags, 8)
|
|
165
|
+
head.writeUInt16LE(entry.method, 10)
|
|
166
|
+
head.writeUInt16LE(entry.dos.time, 12)
|
|
167
|
+
head.writeUInt16LE(entry.dos.date, 14)
|
|
168
|
+
head.writeUInt32LE(entry.crc, 16)
|
|
169
|
+
head.writeUInt32LE(entry.compSize, 20)
|
|
170
|
+
head.writeUInt32LE(entry.size, 24)
|
|
171
|
+
head.writeUInt16LE(entry.nameBuf.length, 28)
|
|
172
|
+
head.writeUInt16LE(0, 30) // extra len
|
|
173
|
+
head.writeUInt16LE(0, 32) // comment len
|
|
174
|
+
head.writeUInt16LE(0, 34) // disk number start
|
|
175
|
+
head.writeUInt16LE(0, 36) // internal attrs
|
|
176
|
+
head.writeUInt32LE(entry.kind === 'dir' ? 0x10 : 0x20, 38) // external attrs
|
|
177
|
+
head.writeUInt32LE(entry.offset, 42)
|
|
178
|
+
return Buffer.concat([head, entry.nameBuf])
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function endOfCentralDirectory({ count, cdSize, cdOffset }) {
|
|
182
|
+
const buf = Buffer.alloc(22)
|
|
183
|
+
buf.writeUInt32LE(0x06054b50, 0)
|
|
184
|
+
buf.writeUInt16LE(0, 4)
|
|
185
|
+
buf.writeUInt16LE(0, 6)
|
|
186
|
+
buf.writeUInt16LE(count, 8)
|
|
187
|
+
buf.writeUInt16LE(count, 10)
|
|
188
|
+
buf.writeUInt32LE(cdSize, 12)
|
|
189
|
+
buf.writeUInt32LE(cdOffset, 16)
|
|
190
|
+
buf.writeUInt16LE(0, 20)
|
|
191
|
+
return buf
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const FLAG_UTF8 = 0x0800
|
|
195
|
+
const FLAG_DESCRIPTOR = 0x0008
|
|
196
|
+
|
|
197
|
+
async function* streamFile(abs, size) {
|
|
198
|
+
const stream = fs.createReadStream(abs, { highWaterMark: 256 * 1024 })
|
|
199
|
+
for await (const chunk of stream) {
|
|
200
|
+
yield chunk
|
|
201
|
+
}
|
|
202
|
+
if (size === 0) yield Buffer.alloc(0)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* ------------------------------------------------------------------ *
|
|
206
|
+
* 主入口:返回 { parts, entries, bytes, skipped }
|
|
207
|
+
* parts —— 异步生成器,yield Buffer(可以直接 Readable.from 成响应体)
|
|
208
|
+
* ------------------------------------------------------------------ */
|
|
209
|
+
|
|
210
|
+
export async function createZipArchive(rootDir, options = {}) {
|
|
211
|
+
const { fail, onWarn } = options
|
|
212
|
+
const mustFail = fail || ((code, message, status) => {
|
|
213
|
+
const error = new Error(message)
|
|
214
|
+
error.code = code
|
|
215
|
+
error.status = status
|
|
216
|
+
throw error
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
const rootName = path.win32.basename(rootDir) || 'archive'
|
|
220
|
+
const { items, totalBytes } = await scanDir(rootDir, { fail: mustFail, onWarn })
|
|
221
|
+
if (items.length === 0) {
|
|
222
|
+
mustFail('ZIP_EMPTY', '这个目录是空的,没有可打包的内容', 400)
|
|
223
|
+
}
|
|
224
|
+
if (items.length >= 0xffff) {
|
|
225
|
+
mustFail('ZIP_TOO_MANY_ENTRIES', `条目数 ${items.length} 超过 65535(Zip64 未支持)`, 413)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function* parts() {
|
|
229
|
+
let offset = 0
|
|
230
|
+
const central = []
|
|
231
|
+
const skipped = []
|
|
232
|
+
|
|
233
|
+
for (const item of items) {
|
|
234
|
+
const entryName = item.rel ? rootName + '/' + item.rel : rootName + '/'
|
|
235
|
+
const nameForZip = item.kind === 'dir' ? entryName.replace(/\/?$/, '/') : entryName
|
|
236
|
+
const nameBuf = Buffer.from(nameForZip, 'utf8')
|
|
237
|
+
const dos = dosDateTime(item.mtimeMs)
|
|
238
|
+
const entryOffset = offset
|
|
239
|
+
|
|
240
|
+
if (item.kind === 'dir') {
|
|
241
|
+
const header = localHeader({
|
|
242
|
+
nameBuf,
|
|
243
|
+
method: 0,
|
|
244
|
+
crc: 0,
|
|
245
|
+
compSize: 0,
|
|
246
|
+
size: 0,
|
|
247
|
+
dos,
|
|
248
|
+
flags: FLAG_UTF8,
|
|
249
|
+
})
|
|
250
|
+
yield header
|
|
251
|
+
offset += header.length
|
|
252
|
+
central.push({ kind: 'dir', nameBuf, method: 0, crc: 0, compSize: 0, size: 0, dos, flags: FLAG_UTF8, offset: entryOffset })
|
|
253
|
+
continue
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let handled = false
|
|
257
|
+
if (item.size <= INLINE_LIMIT) {
|
|
258
|
+
let data
|
|
259
|
+
try {
|
|
260
|
+
data = await fsp.readFile(item.abs)
|
|
261
|
+
} catch (error) {
|
|
262
|
+
skipped.push({ path: item.abs, message: String(error?.message || error) })
|
|
263
|
+
onWarn?.({ path: item.abs, message: String(error?.message || error) })
|
|
264
|
+
continue
|
|
265
|
+
}
|
|
266
|
+
const crc = crc32(data)
|
|
267
|
+
let method = 0
|
|
268
|
+
let payload = data
|
|
269
|
+
const ext = path.win32.extname(item.abs).toLowerCase()
|
|
270
|
+
if (data.length > 0 && !INCOMPRESSIBLE.has(ext)) {
|
|
271
|
+
const deflated = zlib.deflateRawSync(data, { level: 6 })
|
|
272
|
+
if (deflated.length < data.length) {
|
|
273
|
+
method = 8
|
|
274
|
+
payload = deflated
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const header = localHeader({
|
|
278
|
+
nameBuf,
|
|
279
|
+
method,
|
|
280
|
+
crc,
|
|
281
|
+
compSize: payload.length,
|
|
282
|
+
size: data.length,
|
|
283
|
+
dos,
|
|
284
|
+
flags: FLAG_UTF8,
|
|
285
|
+
})
|
|
286
|
+
yield header
|
|
287
|
+
if (payload.length > 0) yield payload
|
|
288
|
+
offset += header.length + payload.length
|
|
289
|
+
central.push({
|
|
290
|
+
kind: 'file',
|
|
291
|
+
nameBuf,
|
|
292
|
+
method,
|
|
293
|
+
crc,
|
|
294
|
+
compSize: payload.length,
|
|
295
|
+
size: data.length,
|
|
296
|
+
dos,
|
|
297
|
+
flags: FLAG_UTF8,
|
|
298
|
+
offset: entryOffset,
|
|
299
|
+
})
|
|
300
|
+
handled = true
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!handled) {
|
|
304
|
+
// 大文件:STORE + 数据描述符,内存恒定
|
|
305
|
+
const flags = FLAG_UTF8 | FLAG_DESCRIPTOR
|
|
306
|
+
const header = localHeader({ nameBuf, method: 0, crc: 0, compSize: 0, size: 0, dos, flags })
|
|
307
|
+
yield header
|
|
308
|
+
offset += header.length
|
|
309
|
+
let crc = -1
|
|
310
|
+
let written = 0
|
|
311
|
+
try {
|
|
312
|
+
for await (const chunk of streamFile(item.abs, item.size)) {
|
|
313
|
+
if (chunk.length === 0) continue
|
|
314
|
+
for (let i = 0; i < chunk.length; i += 1) {
|
|
315
|
+
crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ chunk[i]) & 0xff]
|
|
316
|
+
}
|
|
317
|
+
written += chunk.length
|
|
318
|
+
offset += chunk.length
|
|
319
|
+
yield chunk
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
// 已经写出去一半的文件没法撤回,只能在中央目录里标成 0 长度并记一笔
|
|
323
|
+
skipped.push({ path: item.abs, message: String(error?.message || error) })
|
|
324
|
+
onWarn?.({ path: item.abs, message: String(error?.message || error) })
|
|
325
|
+
written = 0
|
|
326
|
+
crc = -1
|
|
327
|
+
}
|
|
328
|
+
const finalCrc = (crc ^ -1) >>> 0
|
|
329
|
+
const descriptor = dataDescriptor({ crc: finalCrc, compSize: written, size: written })
|
|
330
|
+
yield descriptor
|
|
331
|
+
offset += descriptor.length
|
|
332
|
+
central.push({
|
|
333
|
+
kind: 'file',
|
|
334
|
+
nameBuf,
|
|
335
|
+
method: 0,
|
|
336
|
+
crc: finalCrc,
|
|
337
|
+
compSize: written,
|
|
338
|
+
size: written,
|
|
339
|
+
dos,
|
|
340
|
+
flags,
|
|
341
|
+
offset: entryOffset,
|
|
342
|
+
})
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const cdOffset = offset
|
|
347
|
+
let cdSize = 0
|
|
348
|
+
for (const entry of central) {
|
|
349
|
+
const header = centralHeader(entry)
|
|
350
|
+
yield header
|
|
351
|
+
cdSize += header.length
|
|
352
|
+
}
|
|
353
|
+
const eocd = endOfCentralDirectory({ count: central.length, cdSize, cdOffset })
|
|
354
|
+
yield eocd
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
filename: rootName + '.zip',
|
|
359
|
+
entries: items.length,
|
|
360
|
+
bytes: totalBytes,
|
|
361
|
+
parts: parts(),
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export const internals = { crc32, dosDateTime, MAX_ENTRIES, MAX_TOTAL_BYTES, INLINE_LIMIT }
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-my-computer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "DSH Web 右栏「我的电脑」:远程浏览与操作整台 Windows 磁盘(跨盘符、不受会话工作区限制)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"lib",
|
|
15
|
+
"tools",
|
|
16
|
+
"cordis.patch.yml",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"deepseek-harness",
|
|
22
|
+
"dsh",
|
|
23
|
+
"dsh-plugin",
|
|
24
|
+
"file-manager",
|
|
25
|
+
"windows",
|
|
26
|
+
"remote"
|
|
27
|
+
],
|
|
28
|
+
"author": "ymf2317",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/ymf2317-tech/dsh-my-computer.git"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/ymf2317-tech/dsh-my-computer#readme",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/ymf2317-tech/dsh-my-computer/issues"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.5.0",
|
|
40
|
+
"dsh": ">=0.2.2-rc.1 <0.3.0-0"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"smoke": "node tools/smoke.mjs"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dsh": {
|
|
49
|
+
"bundle": {
|
|
50
|
+
"patch": "./cordis.patch.yml"
|
|
51
|
+
},
|
|
52
|
+
"client": {
|
|
53
|
+
"platform": "web",
|
|
54
|
+
"inject": []
|
|
55
|
+
},
|
|
56
|
+
"manifestVersion": 1
|
|
57
|
+
}
|
|
58
|
+
}
|