dsh-plugin-pack 1.0.16 → 1.0.18
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/client.js +79 -13
- package/lib/import.js +88 -16
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -190,6 +190,8 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
190
190
|
'conflict.titleNew': '确认导入',
|
|
191
191
|
'conflict.subtitleAll': '个插件待安装',
|
|
192
192
|
'conflict.create': '新建',
|
|
193
|
+
'conflict.older': '包内更旧 · 覆盖会降级',
|
|
194
|
+
'conflict.installedFrom': '已装来源',
|
|
193
195
|
'conflict.subtitle': '个插件与已安装的重名,请选择如何处理:',
|
|
194
196
|
'conflict.overwrite': '覆盖',
|
|
195
197
|
'conflict.skip': '跳过',
|
|
@@ -389,6 +391,8 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
389
391
|
'conflict.titleNew': 'Confirm import',
|
|
390
392
|
'conflict.subtitleAll': 'plugins to install',
|
|
391
393
|
'conflict.create': 'New',
|
|
394
|
+
'conflict.older': 'Older in bundle · overwrite downgrades',
|
|
395
|
+
'conflict.installedFrom': 'installed via',
|
|
392
396
|
'conflict.subtitle': 'plugins already exist. Choose what to do:',
|
|
393
397
|
'conflict.overwrite': 'Overwrite',
|
|
394
398
|
'conflict.skip': 'Skip',
|
|
@@ -1365,6 +1369,27 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
1365
1369
|
* @param {() => void} props.onCancel
|
|
1366
1370
|
* @param {(msg: string) => string} props.t
|
|
1367
1371
|
*/
|
|
1372
|
+
/** 比较两个版本号:a>b → 正数。只认三段数字,够判断"包里是否更旧"。 */
|
|
1373
|
+
function compareVersions(a, b) {
|
|
1374
|
+
const parse = (text) =>
|
|
1375
|
+
String(text ?? '')
|
|
1376
|
+
.trim()
|
|
1377
|
+
.replace(/^v/i, '')
|
|
1378
|
+
.split('-')[0]
|
|
1379
|
+
.split('.')
|
|
1380
|
+
.map((part) => {
|
|
1381
|
+
const n = Number.parseInt(part.replace(/[^0-9].*$/, ''), 10);
|
|
1382
|
+
return Number.isFinite(n) ? n : 0;
|
|
1383
|
+
});
|
|
1384
|
+
const left = parse(a);
|
|
1385
|
+
const right = parse(b);
|
|
1386
|
+
for (let i = 0; i < 3; i += 1) {
|
|
1387
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
1388
|
+
if (diff !== 0) return diff;
|
|
1389
|
+
}
|
|
1390
|
+
return 0;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1368
1393
|
/**
|
|
1369
1394
|
* 「添加整合包」弹窗。
|
|
1370
1395
|
*
|
|
@@ -1706,6 +1731,31 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
1706
1731
|
item.exists && item.sameVersion
|
|
1707
1732
|
? h('span', { className: 'pp-ver' }, t('conflict.sameVersion'))
|
|
1708
1733
|
: null,
|
|
1734
|
+
// 包内版本更旧:覆盖等于降级,必须显眼
|
|
1735
|
+
item.exists && item.older
|
|
1736
|
+
? h(
|
|
1737
|
+
'span',
|
|
1738
|
+
{
|
|
1739
|
+
className: 'pp-ver',
|
|
1740
|
+
style: {
|
|
1741
|
+
color: 'var(--dsw-alias-state-warning-primary,#b45309)',
|
|
1742
|
+
borderColor: 'color-mix(in srgb,var(--dsw-alias-state-warning-primary,#d97706) 45%,transparent)',
|
|
1743
|
+
},
|
|
1744
|
+
},
|
|
1745
|
+
t('conflict.older'),
|
|
1746
|
+
)
|
|
1747
|
+
: null,
|
|
1748
|
+
// 已装来源不是 local-plugins 时说明一下(覆盖会改掉原来的安装方式)
|
|
1749
|
+
item.installSource && item.installSource !== 'local-plugins'
|
|
1750
|
+
? h(
|
|
1751
|
+
'span',
|
|
1752
|
+
{ className: 'pp-ver' },
|
|
1753
|
+
`${t('conflict.installedFrom')} ${
|
|
1754
|
+
{ npm: 'npm', link: 'link:', remote: 'git/url', unknown: '?' }[item.installSource] ??
|
|
1755
|
+
item.installSource
|
|
1756
|
+
}`,
|
|
1757
|
+
)
|
|
1758
|
+
: null,
|
|
1709
1759
|
),
|
|
1710
1760
|
item.dest ? h('div', { className: 'pp-meta pp-mono', style: { fontSize: 11 } }, item.dest) : null,
|
|
1711
1761
|
),
|
|
@@ -2178,7 +2228,13 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
2178
2228
|
}
|
|
2179
2229
|
|
|
2180
2230
|
if (quick && Array.isArray(quick.names) && quick.names.length) {
|
|
2181
|
-
|
|
2231
|
+
/* 现取一次状态,不要用打开面板时缓存的那份。
|
|
2232
|
+
* 用户可能刚装完插件就拖包进来,缓存里还没有那两个,
|
|
2233
|
+
* 结果全部被标成「新建」——既不弹覆盖/跳过,又会重复装一遍。
|
|
2234
|
+
* 服务端扫描结果有缓存(按目录 mtime 失效),所以这次调用很快。 */
|
|
2235
|
+
const freshStatus = await getStatus().catch(() => null);
|
|
2236
|
+
const sourcePlugins = freshStatus?.plugins ?? status?.plugins ?? [];
|
|
2237
|
+
const installed = new Map(sourcePlugins.map((plugin) => [plugin.name, plugin]));
|
|
2182
2238
|
const packPlugins = (quick.manifest?.plugins ?? []).filter(Boolean);
|
|
2183
2239
|
const conflicts = packPlugins
|
|
2184
2240
|
.filter((plugin) => installed.has(plugin.name))
|
|
@@ -2216,22 +2272,28 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
2216
2272
|
dest: hit?.dir ?? null,
|
|
2217
2273
|
bytes: plugin.bytes ?? null,
|
|
2218
2274
|
sameVersion: (hit?.version ?? null) === (plugin.version ?? null),
|
|
2275
|
+
// 包内版本比已装的更旧 → 覆盖等于降级,要显式标出来
|
|
2276
|
+
older: Boolean(hit?.version && plugin.version && compareVersions(hit.version, plugin.version) > 0),
|
|
2219
2277
|
exists: Boolean(hit),
|
|
2220
2278
|
};
|
|
2221
2279
|
});
|
|
2222
2280
|
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2281
|
+
/* manifest 里没有 plugins 清单(老包或手工拼的)就别弹空框,
|
|
2282
|
+
* 让下面的上传 + dryRun 用服务端的真实结果来问。 */
|
|
2283
|
+
if (rows.length) {
|
|
2284
|
+
setPendingImport({
|
|
2285
|
+
kind: 'bundle',
|
|
2286
|
+
pendingFile: file, // 还没上传,先拿着 File 对象
|
|
2287
|
+
file: null,
|
|
2288
|
+
conflicts: rows.filter((row) => row.exists),
|
|
2289
|
+
rows,
|
|
2290
|
+
plugins: packPlugins,
|
|
2291
|
+
quick: true,
|
|
2292
|
+
});
|
|
2293
|
+
setBusy(false);
|
|
2294
|
+
setPhase('');
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2235
2297
|
}
|
|
2236
2298
|
|
|
2237
2299
|
// 第二步:确认无冲突(或拿不到快速清单)才上传
|
|
@@ -2252,6 +2314,8 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
2252
2314
|
installedVersion: hit?.installedVersion ?? null,
|
|
2253
2315
|
dest: hit?.dest ?? null,
|
|
2254
2316
|
sameVersion: Boolean(hit?.sameVersion),
|
|
2317
|
+
older: Boolean(hit?.older),
|
|
2318
|
+
installSource: hit?.installSource ?? null,
|
|
2255
2319
|
exists: Boolean(hit),
|
|
2256
2320
|
};
|
|
2257
2321
|
});
|
|
@@ -2371,6 +2435,8 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
2371
2435
|
installedVersion: hit?.installedVersion ?? null,
|
|
2372
2436
|
dest: hit?.dest ?? null,
|
|
2373
2437
|
sameVersion: Boolean(hit?.sameVersion),
|
|
2438
|
+
older: Boolean(hit?.older),
|
|
2439
|
+
installSource: hit?.installSource ?? null,
|
|
2374
2440
|
exists: Boolean(hit),
|
|
2375
2441
|
};
|
|
2376
2442
|
});
|
package/lib/import.js
CHANGED
|
@@ -16,7 +16,7 @@ import { readTgz } from './tar.js';
|
|
|
16
16
|
import { dshHome as resolveDshHome, profileDir as resolveProfileDir, profileName as resolveProfileName, packSegment, stamp, humanBytes } from './home.js';
|
|
17
17
|
import { readJson, readText, writeJson, writeText, ensureDir, isDir, isFile, realOrSelf, copyTree, removeTree } from './util.js';
|
|
18
18
|
import { existingIds, rowId, appendInsertBlock, splitTopLevel, yamlScalar } from './patch-text.js';
|
|
19
|
-
import { isAbsoluteSpec, clearScanCache } from './scan.js';
|
|
19
|
+
import { isAbsoluteSpec, clearScanCache, resolvePackageDir } from './scan.js';
|
|
20
20
|
import { repairProfile } from './repair.js';
|
|
21
21
|
|
|
22
22
|
export const PACK_FORMAT = 'dsh-plugin-pack@2';
|
|
@@ -286,7 +286,34 @@ export function importBundle(options = {}) {
|
|
|
286
286
|
for (const plugin of chosen) {
|
|
287
287
|
const dest = path.join(localPluginsDir, plugin.segment);
|
|
288
288
|
const nodeLink = path.join(profile, 'node_modules', ...String(plugin.name).split('/'));
|
|
289
|
-
|
|
289
|
+
/* 判断"是否已装"不能只看 local-plugins 目录 ——
|
|
290
|
+
* 插件可能是 npm / github / 绝对路径 link 装的,文件在 node_modules 里。
|
|
291
|
+
* 只看本地目录会把它当成"新建",于是重复装入、还把原来的
|
|
292
|
+
* dependencies 声明覆盖成 link:,用户原来的版本控制就丢了。
|
|
293
|
+
* 所以用真实的模块解析结果来判定。 */
|
|
294
|
+
const resolvedDir = resolvePackageDir(plugin.name, profile, dependencies);
|
|
295
|
+
const localExists = isDir(dest);
|
|
296
|
+
const exists = localExists || Boolean(resolvedDir);
|
|
297
|
+
const installedDir = localExists ? dest : resolvedDir;
|
|
298
|
+
const installedSpec = dependencies[plugin.name] ?? null;
|
|
299
|
+
const installedMeta = installedDir ? readJson(path.join(installedDir, 'package.json'), null) : null;
|
|
300
|
+
const installedVersion = installedMeta?.version ?? null;
|
|
301
|
+
const installSource = localExists
|
|
302
|
+
? 'local-plugins'
|
|
303
|
+
: !installedSpec
|
|
304
|
+
? 'unknown'
|
|
305
|
+
: /^link:/i.test(installedSpec)
|
|
306
|
+
? 'link'
|
|
307
|
+
: /^(github:|git\+|https?:)/i.test(installedSpec)
|
|
308
|
+
? 'remote'
|
|
309
|
+
: 'npm';
|
|
310
|
+
// 包里的版本比已装的更旧时提醒一句,覆盖等于降级
|
|
311
|
+
const older = exists && installedVersion && plugin.version ? compareVersions(installedVersion, plugin.version) > 0 : false;
|
|
312
|
+
if (older) {
|
|
313
|
+
warnings.push(
|
|
314
|
+
`${plugin.name}: 整合包里是旧版本(已装 ${installedVersion},包内 ${plugin.version}),覆盖会把它降级`,
|
|
315
|
+
);
|
|
316
|
+
}
|
|
290
317
|
const linkState = describeLink(nodeLink, dest);
|
|
291
318
|
// 路径插件:只在 cordis.patch.yml 里用绝对路径 insert、没进 bundles 的(如 pwsh7-preference)。
|
|
292
319
|
// 导入后 name 重写成新位置的绝对文件路径,不进 bundles / dependencies / 链接。
|
|
@@ -303,6 +330,11 @@ export function importBundle(options = {}) {
|
|
|
303
330
|
bytes: plugin.bytes,
|
|
304
331
|
dest,
|
|
305
332
|
exists,
|
|
333
|
+
installedDir,
|
|
334
|
+
installedVersion,
|
|
335
|
+
installedSpec,
|
|
336
|
+
installSource,
|
|
337
|
+
older,
|
|
306
338
|
action: exists ? decisionFor(plugin.name) : 'create',
|
|
307
339
|
nodeLink,
|
|
308
340
|
linkState,
|
|
@@ -325,18 +357,20 @@ export function importBundle(options = {}) {
|
|
|
325
357
|
* dryRun 也返回它 —— 这正是界面判断"要不要弹窗"的依据。 */
|
|
326
358
|
const conflicts = plan
|
|
327
359
|
.filter((item) => item.exists)
|
|
328
|
-
.map((item) => {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
360
|
+
.map((item) => ({
|
|
361
|
+
name: item.name,
|
|
362
|
+
version: item.version,
|
|
363
|
+
installedVersion: item.installedVersion,
|
|
364
|
+
// 已装位置不一定是 local-plugins:npm/link/github 装的要走不同的提示
|
|
365
|
+
dest: item.installedDir,
|
|
366
|
+
bytes: item.bytes,
|
|
367
|
+
// 同一个版本还是不同版本,界面可以据此给更明确的提示
|
|
368
|
+
sameVersion: item.installedVersion === item.version,
|
|
369
|
+
// 包内版本更旧 → 覆盖等于降级,界面要显式警告
|
|
370
|
+
older: Boolean(item.older),
|
|
371
|
+
installSource: item.installSource,
|
|
372
|
+
installedSpec: item.installedSpec,
|
|
373
|
+
}));
|
|
340
374
|
|
|
341
375
|
if (dryRun) {
|
|
342
376
|
return {
|
|
@@ -396,7 +430,12 @@ export function importBundle(options = {}) {
|
|
|
396
430
|
continue;
|
|
397
431
|
}
|
|
398
432
|
onProgress(`解包 ${item.name}`, basePercent);
|
|
399
|
-
|
|
433
|
+
/* 只有 dest 真的存在(=本地插件目录)才需要"搬走备份"。
|
|
434
|
+
* 已装但装在 node_modules 的(npm/link/github),local-plugins 下没有这个目录,
|
|
435
|
+
* 直接往空目录里写就行 —— 早先按 item.exists 判断会去 copy 一个不存在的路径,
|
|
436
|
+
* 报 ENOENT: no such file or directory, stat '...\local-plugins\xxx'。 */
|
|
437
|
+
const localDestExists = isDir(item.dest);
|
|
438
|
+
if (localDestExists) {
|
|
400
439
|
const backupDir = path.join(backupRoot, 'local-plugins', item.segment);
|
|
401
440
|
copyTree(item.dest, backupDir);
|
|
402
441
|
record.replacedDirs.push({ dest: item.dest, backup: backupDir });
|
|
@@ -420,7 +459,7 @@ export function importBundle(options = {}) {
|
|
|
420
459
|
}
|
|
421
460
|
}
|
|
422
461
|
|
|
423
|
-
if (!
|
|
462
|
+
if (!localDestExists) record.addedDirs.push(item.dest);
|
|
424
463
|
applied.push({ name: item.name, action: item.action, files: written, dest: item.dest });
|
|
425
464
|
}
|
|
426
465
|
|
|
@@ -669,6 +708,39 @@ function ensurePackageLink(profile, name, dest, { backupRoot, record }) {
|
|
|
669
708
|
}
|
|
670
709
|
}
|
|
671
710
|
|
|
711
|
+
/**
|
|
712
|
+
* 比较两个 semver 版本号,返回 a>b → 正数,a<b → 负数,相等 → 0。
|
|
713
|
+
*
|
|
714
|
+
* 只做够用的三段数字比较:够判断"整合包里的版本是不是更旧"。
|
|
715
|
+
* 预发布后缀(-beta.1)按字符串兜底比较。
|
|
716
|
+
*
|
|
717
|
+
* @param {string} a
|
|
718
|
+
* @param {string} b
|
|
719
|
+
* @returns {number}
|
|
720
|
+
*/
|
|
721
|
+
export function compareVersions(a, b) {
|
|
722
|
+
const parse = (text) => {
|
|
723
|
+
const [core, pre = ''] = String(text ?? '').trim().replace(/^v/i, '').split('-');
|
|
724
|
+
const nums = core.split('.').map((part) => {
|
|
725
|
+
const n = Number.parseInt(part.replace(/[^0-9].*$/, ''), 10);
|
|
726
|
+
return Number.isFinite(n) ? n : 0;
|
|
727
|
+
});
|
|
728
|
+
while (nums.length < 3) nums.push(0);
|
|
729
|
+
return { nums, pre };
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
const left = parse(a);
|
|
733
|
+
const right = parse(b);
|
|
734
|
+
for (let i = 0; i < 3; i += 1) {
|
|
735
|
+
if (left.nums[i] !== right.nums[i]) return left.nums[i] - right.nums[i];
|
|
736
|
+
}
|
|
737
|
+
// 主版本相同:有预发布后缀的视为更小
|
|
738
|
+
if (left.pre === right.pre) return 0;
|
|
739
|
+
if (!left.pre) return 1;
|
|
740
|
+
if (!right.pre) return -1;
|
|
741
|
+
return left.pre < right.pre ? -1 : 1;
|
|
742
|
+
}
|
|
743
|
+
|
|
672
744
|
/** 备份目录撞名(同一秒内多次导入)时追加序号,不覆盖旧备份。 */
|
|
673
745
|
function uniqueBackupDir(base) {
|
|
674
746
|
if (!isDir(base) && !isFile(base)) return base;
|
package/package.json
CHANGED