dsh-plugin-pack 1.0.16 → 1.0.17
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 +56 -0
- 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
|
),
|
|
@@ -2216,6 +2266,8 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
2216
2266
|
dest: hit?.dir ?? null,
|
|
2217
2267
|
bytes: plugin.bytes ?? null,
|
|
2218
2268
|
sameVersion: (hit?.version ?? null) === (plugin.version ?? null),
|
|
2269
|
+
// 包内版本比已装的更旧 → 覆盖等于降级,要显式标出来
|
|
2270
|
+
older: Boolean(hit?.version && plugin.version && compareVersions(hit.version, plugin.version) > 0),
|
|
2219
2271
|
exists: Boolean(hit),
|
|
2220
2272
|
};
|
|
2221
2273
|
});
|
|
@@ -2252,6 +2304,8 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
2252
2304
|
installedVersion: hit?.installedVersion ?? null,
|
|
2253
2305
|
dest: hit?.dest ?? null,
|
|
2254
2306
|
sameVersion: Boolean(hit?.sameVersion),
|
|
2307
|
+
older: Boolean(hit?.older),
|
|
2308
|
+
installSource: hit?.installSource ?? null,
|
|
2255
2309
|
exists: Boolean(hit),
|
|
2256
2310
|
};
|
|
2257
2311
|
});
|
|
@@ -2371,6 +2425,8 @@ window.__ModuleLoader__.load({ id: 'dsh-plugin-pack', factory: (require) => {
|
|
|
2371
2425
|
installedVersion: hit?.installedVersion ?? null,
|
|
2372
2426
|
dest: hit?.dest ?? null,
|
|
2373
2427
|
sameVersion: Boolean(hit?.sameVersion),
|
|
2428
|
+
older: Boolean(hit?.older),
|
|
2429
|
+
installSource: hit?.installSource ?? null,
|
|
2374
2430
|
exists: Boolean(hit),
|
|
2375
2431
|
};
|
|
2376
2432
|
});
|
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