dsh-plugin-pack 0.0.0-stage → 1.0.2
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 +537 -3
- package/bin/dsh-plugin-pack.js +200 -0
- package/client.js +2320 -0
- package/cordis.patch.yml +24 -0
- package/hotload.js +230 -0
- package/lib/export.js +444 -0
- package/lib/home.js +75 -0
- package/lib/import-plugin.js +356 -0
- package/lib/import-tgz.js +115 -0
- package/lib/import.js +831 -0
- package/lib/inbox.js +194 -0
- package/lib/index.js +725 -0
- package/lib/jobs.js +208 -0
- package/lib/patch-text.js +672 -0
- package/lib/scan.js +488 -0
- package/lib/server.js +926 -0
- package/lib/tar.js +241 -0
- package/lib/util.js +333 -0
- package/lib/zip-stream.js +166 -0
- package/lib/zip.js +514 -0
- package/package.json +72 -6
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 脱离 dsh 宿主的命令行入口。
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* dsh-plugin-pack list
|
|
7
|
+
* dsh-plugin-pack export [--out <zip|目录>] [--include a,b] [--exclude a,b]
|
|
8
|
+
* [--data] [--secrets] [--dry] [--max-bytes N]
|
|
9
|
+
* dsh-plugin-pack inspect <zip>
|
|
10
|
+
* dsh-plugin-pack import <zip> [--dry] [--skip] [--no-data] [--no-link] [--overwrite]
|
|
11
|
+
* dsh-plugin-pack rollback [备份名]
|
|
12
|
+
* dsh-plugin-pack backups
|
|
13
|
+
*
|
|
14
|
+
* 也可以直接:node bin/dsh-plugin-pack.js ...
|
|
15
|
+
*
|
|
16
|
+
* @module dsh-plugin-pack/bin
|
|
17
|
+
*/
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
|
|
20
|
+
import { scanProfile } from '../lib/scan.js';
|
|
21
|
+
import { exportBundle } from '../lib/export.js';
|
|
22
|
+
import { importBundle, inspectBundle, rollbackImport, listBackups } from '../lib/import.js';
|
|
23
|
+
import { importTarball, isTarball } from '../lib/import-tgz.js';
|
|
24
|
+
import { dshHome as resolveDshHome, profileDir as resolveProfileDir, humanBytes } from '../lib/home.js';
|
|
25
|
+
import { renderList, renderExport, renderImport, renderInspect } from '../lib/index.js';
|
|
26
|
+
|
|
27
|
+
/** 布尔 flag:后面即使跟普通参数也不吞。 */
|
|
28
|
+
const BOOL_FLAGS = new Set(['dry', 'skip', 'no-data', 'no-link', 'overwrite', 'data', 'secrets', 'help']);
|
|
29
|
+
|
|
30
|
+
function parseArgv(argv) {
|
|
31
|
+
const positional = [];
|
|
32
|
+
const flags = new Map();
|
|
33
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
34
|
+
const token = argv[i];
|
|
35
|
+
if (token.startsWith('--')) {
|
|
36
|
+
const [key, inline] = token.slice(2).split('=');
|
|
37
|
+
if (inline !== undefined) {
|
|
38
|
+
flags.set(key, inline);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (BOOL_FLAGS.has(key)) {
|
|
42
|
+
flags.set(key, true);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const next = argv[i + 1];
|
|
46
|
+
if (next && !next.startsWith('-')) {
|
|
47
|
+
flags.set(key, next);
|
|
48
|
+
i += 1;
|
|
49
|
+
} else {
|
|
50
|
+
flags.set(key, true);
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
positional.push(token);
|
|
55
|
+
}
|
|
56
|
+
return { positional, flags };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function splitList(value) {
|
|
60
|
+
if (typeof value !== 'string') return null;
|
|
61
|
+
return value.split(',').map((item) => item.trim()).filter(Boolean);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function context() {
|
|
65
|
+
return {
|
|
66
|
+
home: resolveDshHome(),
|
|
67
|
+
profileDir: resolveProfileDir(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function main() {
|
|
72
|
+
const { positional, flags } = parseArgv(process.argv.slice(2));
|
|
73
|
+
const sub = (positional[0] ?? 'help').toLowerCase();
|
|
74
|
+
const ctx = context();
|
|
75
|
+
|
|
76
|
+
switch (sub) {
|
|
77
|
+
case 'list':
|
|
78
|
+
case 'ls': {
|
|
79
|
+
const scan = await scanProfile({
|
|
80
|
+
includeData: Boolean(flags.get('data')),
|
|
81
|
+
includeSecrets: Boolean(flags.get('secrets')),
|
|
82
|
+
});
|
|
83
|
+
console.log(renderList(scan, ctx, listBackups(ctx.profileDir)));
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
case 'export':
|
|
87
|
+
case 'e': {
|
|
88
|
+
const result = await exportBundle({
|
|
89
|
+
out: positional[1] ?? (flags.get('out') || path.join(ctx.home, 'exports')),
|
|
90
|
+
include: splitList(flags.get('include')),
|
|
91
|
+
exclude: splitList(flags.get('exclude')) ?? [],
|
|
92
|
+
includeData: Boolean(flags.get('data')),
|
|
93
|
+
includeSecrets: Boolean(flags.get('secrets')),
|
|
94
|
+
maxBytes: flags.get('max-bytes') ? Number(flags.get('max-bytes')) : undefined,
|
|
95
|
+
dryRun: Boolean(flags.get('dry')),
|
|
96
|
+
onProgress: (message) => console.error(` ${message}`),
|
|
97
|
+
});
|
|
98
|
+
console.log(renderExport(result));
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
case 'inspect':
|
|
102
|
+
case 'i': {
|
|
103
|
+
const file = positional[1];
|
|
104
|
+
if (!file) {
|
|
105
|
+
console.error('用法: dsh-plugin-pack inspect <zip>');
|
|
106
|
+
return 2;
|
|
107
|
+
}
|
|
108
|
+
console.log(renderInspect(inspectBundle(file)));
|
|
109
|
+
return 0;
|
|
110
|
+
}
|
|
111
|
+
case 'import': {
|
|
112
|
+
const file = positional[1];
|
|
113
|
+
if (!file) {
|
|
114
|
+
console.error('用法: dsh-plugin-pack import <zip|tgz> [--dry] [--skip] [--no-data] [--no-link]');
|
|
115
|
+
return 2;
|
|
116
|
+
}
|
|
117
|
+
// 按扩展名分流:tgz/tar.gz 是单插件的 npm tarball,其余按整合包处理
|
|
118
|
+
if (isTarball(file)) {
|
|
119
|
+
const result = importTarball({
|
|
120
|
+
file,
|
|
121
|
+
home: ctx.home,
|
|
122
|
+
profileDir: ctx.profileDir,
|
|
123
|
+
dryRun: Boolean(flags.get('dry')),
|
|
124
|
+
mode: flags.get('skip') ? 'skip' : 'merge',
|
|
125
|
+
});
|
|
126
|
+
if (flags.get('dry')) {
|
|
127
|
+
console.log(`[dry-run] 将导入单插件: ${result.plan?.name ?? '(未知)'}`);
|
|
128
|
+
console.log(` 来自 tarball: ${file}`);
|
|
129
|
+
} else {
|
|
130
|
+
console.log(`✓ 已从 tarball 导入单插件: ${result.name}@${result.version}`);
|
|
131
|
+
console.log(` 落地: ${result.dest}`);
|
|
132
|
+
console.log(` 备份: ${result.backup}`);
|
|
133
|
+
console.log('重启 dsh 生效。');
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
const result = importBundle({
|
|
138
|
+
file,
|
|
139
|
+
dryRun: Boolean(flags.get('dry')),
|
|
140
|
+
mode: flags.get('skip') ? 'skip' : 'merge',
|
|
141
|
+
overwritePatchConfig: Boolean(flags.get('overwrite')),
|
|
142
|
+
restoreData: !flags.get('no-data'),
|
|
143
|
+
linkNodeModules: !flags.get('no-link'),
|
|
144
|
+
only: splitList(flags.get('only')),
|
|
145
|
+
});
|
|
146
|
+
console.log(renderImport(result));
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
case 'rollback':
|
|
150
|
+
case 'undo': {
|
|
151
|
+
const result = rollbackImport({ backup: positional[1] });
|
|
152
|
+
console.log(`✓ 已回滚到 ${result.backup}`);
|
|
153
|
+
console.log(`恢复: ${result.restored.join(', ') || '(无)'}`);
|
|
154
|
+
console.log(`删除: ${result.removed.length} 项 解链: ${result.unlinked.length} 项`);
|
|
155
|
+
console.log('重启 dsh 生效。');
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
case 'backups':
|
|
159
|
+
case 'b': {
|
|
160
|
+
const backups = listBackups(ctx.profileDir);
|
|
161
|
+
if (!backups.length) {
|
|
162
|
+
console.log('还没有导入备份。');
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
for (const item of backups) {
|
|
166
|
+
console.log(`${item.name} ${item.createdAt ?? ''} ${item.plugins} 项 ${item.source ?? ''}`);
|
|
167
|
+
}
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
case 'help':
|
|
171
|
+
case '--help':
|
|
172
|
+
case '-h':
|
|
173
|
+
default:
|
|
174
|
+
console.log(
|
|
175
|
+
[
|
|
176
|
+
'dsh-plugin-pack — dsh 插件整合包一键导出/导入',
|
|
177
|
+
'',
|
|
178
|
+
' dsh-plugin-pack list',
|
|
179
|
+
' dsh-plugin-pack export [--out <zip|目录>] [--include a,b] [--exclude a,b]',
|
|
180
|
+
' [--data] [--secrets] [--dry] [--max-bytes N]',
|
|
181
|
+
' dsh-plugin-pack inspect <zip>',
|
|
182
|
+
' dsh-plugin-pack import <zip> [--dry] [--skip] [--no-data] [--no-link] [--overwrite]',
|
|
183
|
+
' dsh-plugin-pack rollback [备份名]',
|
|
184
|
+
' dsh-plugin-pack backups',
|
|
185
|
+
'',
|
|
186
|
+
`当前 profile: ${ctx.profileDir}`,
|
|
187
|
+
`默认导出目录: ${path.join(ctx.home, 'exports')}`,
|
|
188
|
+
].join('\n'),
|
|
189
|
+
);
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
process.exitCode = await main();
|
|
196
|
+
} catch (error) {
|
|
197
|
+
console.error(`ERROR: ${error?.message ?? error}`);
|
|
198
|
+
if (process.env.DSH_PLUGIN_PACK_DEBUG) console.error(error?.stack);
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
}
|