@aalis/plugin-package-manager 0.9.0 → 0.10.1
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/dist/index.d.ts +12 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +109 -128
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts +70 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/en.js +70 -0
- package/dist/locales/en.js.map +1 -0
- package/dist/texts.d.ts +139 -0
- package/dist/texts.d.ts.map +1 -0
- package/dist/texts.js +78 -0
- package/dist/texts.js.map +1 -0
- package/package.json +8 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type PackageManagerService, type UpdateTarget } from '@aalis/api-package-manager';
|
|
2
2
|
import { type ProcessService } from '@aalis/api-process';
|
|
3
3
|
import { type AppService, type PluginStatusEntry } from '@aalis/core';
|
|
4
|
+
import { type TextRef } from '@aalis/schema-i18n';
|
|
4
5
|
/**
|
|
5
6
|
* 根依赖里是否含 `workspace:` 协议。
|
|
6
7
|
*
|
|
@@ -20,11 +21,11 @@ export declare function hasWorkspaceProtocol(pkgJson: Record<string, unknown> |
|
|
|
20
21
|
export declare function declaresPlugin(pkgJson: Record<string, unknown> | undefined): boolean;
|
|
21
22
|
/**
|
|
22
23
|
* 包类型是否归市场装卸:插件(判据同 {@link declaresPlugin})与前端界面(`aalis-interface`)
|
|
23
|
-
* 归市场管,返回 `undefined
|
|
24
|
-
*
|
|
24
|
+
* 归市场管,返回 `undefined`;其余按类型关键词返回种类码(没有类型关键词的是 other),拒绝时显示 KIND_NAMES 里的名字。
|
|
25
|
+
* 装与卸共用这一份判据——同一件事维护两份同义判据,正是本仓已出事四次的形态(依赖来源判据、定级数学、包名正则、关键词分类)。
|
|
25
26
|
* 纯函数,便于单测。
|
|
26
27
|
*/
|
|
27
|
-
export declare function nonMarketKind(keywords: unknown):
|
|
28
|
+
export declare function nonMarketKind(keywords: unknown): 'core' | 'runtime' | 'api' | 'schema' | 'util' | 'other' | undefined;
|
|
28
29
|
/** 服务依赖者闸读的插件状态:判谁在提供、谁 required 什么,以及 pending 的目标是缺还是在等(api-doctor 的共享判据) */
|
|
29
30
|
type ServiceStatus = Pick<PluginStatusEntry, 'name' | 'instanceId' | 'state' | 'error' | 'provides' | 'uses'>;
|
|
30
31
|
/**
|
|
@@ -45,7 +46,7 @@ export declare function findServiceDependents(targetName: string, status: Readon
|
|
|
45
46
|
* 两者挡的东西不同,不是同一份的拷贝,别再把它们合并成一条。
|
|
46
47
|
* 纯函数,便于单测。
|
|
47
48
|
*/
|
|
48
|
-
export declare function validatePackageSpec(spec: unknown):
|
|
49
|
+
export declare function validatePackageSpec(spec: unknown): TextRef | undefined;
|
|
49
50
|
/** install/uninstall 的显式依赖(从服务选择解耦,便于集成测试) */
|
|
50
51
|
export interface PackageManagerDeps {
|
|
51
52
|
proc: ProcessService;
|
|
@@ -131,8 +132,11 @@ export declare function stripVersion(spec: string): string;
|
|
|
131
132
|
* 不猜用户意图。纯函数,便于单测。
|
|
132
133
|
*/
|
|
133
134
|
export declare function buildUpdateSpecs(targets: readonly UpdateTarget[]): {
|
|
134
|
-
specs
|
|
135
|
-
error?:
|
|
135
|
+
specs: string[];
|
|
136
|
+
error?: undefined;
|
|
137
|
+
} | {
|
|
138
|
+
specs?: undefined;
|
|
139
|
+
error: TextRef;
|
|
136
140
|
};
|
|
137
141
|
/**
|
|
138
142
|
* 从 npm 的失败输出里摘出冲突要点,供前端直接展示。保留根项目的声明行(`… from the root project`),看得出冲突由
|
|
@@ -147,7 +151,7 @@ export declare function npmFailureKind(output: string): NpmFailureKind | undefin
|
|
|
147
151
|
* 把 npm 的失败输出归成一句给用户看的话:认得出的写原因与修法,认不出的写 npm 的最后一条报错,并指向日志与排障页。
|
|
148
152
|
* 整段输出由调用方记进日志,回执与提示里只放这一句。纯函数,便于单测。
|
|
149
153
|
*/
|
|
150
|
-
export declare function describeNpmFailure(output: string):
|
|
154
|
+
export declare function describeNpmFailure(output: string): TextRef;
|
|
151
155
|
/**
|
|
152
156
|
* 从 **成功**(exit 0)的 dry-run 输出里找出因本次目标而无法满足的 peer 依赖。
|
|
153
157
|
*
|
|
@@ -160,7 +164,7 @@ export declare function describeNpmFailure(output: string): string;
|
|
|
160
164
|
* 而它确实会在 stderr 打出 `peer <name>@"<range>" from <dependent>`。只认**提到本次目标**
|
|
161
165
|
* 的那些行:工程里原有的、与本次无关的未满足 peer 不该阻断更新。
|
|
162
166
|
*/
|
|
163
|
-
export declare function findUnmetPeers(output: string, targetNames: readonly string[]):
|
|
167
|
+
export declare function findUnmetPeers(output: string, targetNames: readonly string[]): TextRef[];
|
|
164
168
|
/**
|
|
165
169
|
* 包管理核心:install/uninstall 的纯依赖实现(不碰服务选择,可单测)。
|
|
166
170
|
* 所有文件操作走 process 服务(子进程:npm/cp/test),目标是项目根 package.json 与
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,qBAAqB,EAI1B,KAAK,YAAY,EAClB,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EAAmB,KAAK,cAAc,EAAkB,MAAM,oBAAoB,CAAC;AAC1F,OAAO,EACL,KAAK,UAAU,EAMf,KAAK,iBAAiB,EAIvB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
|
-
import { unmetDependencies } from '@aalis/api-doctor';
|
|
2
|
+
import { failureText, unmetDependencies } from '@aalis/api-doctor';
|
|
3
3
|
import { hostConfig } from '@aalis/api-host-config';
|
|
4
4
|
import { hostPaths } from '@aalis/api-host-paths';
|
|
5
5
|
import { packageManager, } from '@aalis/api-package-manager';
|
|
6
6
|
import { pluginDefinitionOf, pluginSource } from '@aalis/api-plugin-source';
|
|
7
7
|
import { processService } from '@aalis/api-process';
|
|
8
8
|
import { appService, definePlugin, logger, optional, pluginsService, provide, services, } from '@aalis/core';
|
|
9
|
+
import { TextError, textClauses, textOf, toSource } from '@aalis/schema-i18n';
|
|
9
10
|
import { classifyDepSpec, isRegistryDep, isUpgrade } from '@aalis/util-dep-spec';
|
|
11
|
+
import { t } from './texts.js';
|
|
10
12
|
// ===== 实现 =====
|
|
11
13
|
/** 短命令(cp、npm view 等)的超时。 */
|
|
12
14
|
const QUICK_TIMEOUT_MS = 120_000;
|
|
@@ -76,8 +78,8 @@ export function declaresPlugin(pkgJson) {
|
|
|
76
78
|
}
|
|
77
79
|
/**
|
|
78
80
|
* 包类型是否归市场装卸:插件(判据同 {@link declaresPlugin})与前端界面(`aalis-interface`)
|
|
79
|
-
* 归市场管,返回 `undefined
|
|
80
|
-
*
|
|
81
|
+
* 归市场管,返回 `undefined`;其余按类型关键词返回种类码(没有类型关键词的是 other),拒绝时显示 KIND_NAMES 里的名字。
|
|
82
|
+
* 装与卸共用这一份判据——同一件事维护两份同义判据,正是本仓已出事四次的形态(依赖来源判据、定级数学、包名正则、关键词分类)。
|
|
81
83
|
* 纯函数,便于单测。
|
|
82
84
|
*/
|
|
83
85
|
export function nonMarketKind(keywords) {
|
|
@@ -85,17 +87,26 @@ export function nonMarketKind(keywords) {
|
|
|
85
87
|
if (declaresPlugin({ keywords: kw }) || kw.includes('aalis-interface'))
|
|
86
88
|
return undefined;
|
|
87
89
|
if (kw.includes('aalis-core'))
|
|
88
|
-
return '
|
|
90
|
+
return 'core';
|
|
89
91
|
if (kw.includes('aalis-runtime'))
|
|
90
|
-
return '
|
|
92
|
+
return 'runtime';
|
|
91
93
|
if (kw.includes('aalis-api'))
|
|
92
|
-
return '
|
|
94
|
+
return 'api';
|
|
93
95
|
if (kw.includes('aalis-schema'))
|
|
94
|
-
return '
|
|
96
|
+
return 'schema';
|
|
95
97
|
if (kw.includes('aalis-util'))
|
|
96
|
-
return '
|
|
97
|
-
return '
|
|
98
|
+
return 'util';
|
|
99
|
+
return 'other';
|
|
98
100
|
}
|
|
101
|
+
/** 不归市场装卸的包种类给人看的名字 */
|
|
102
|
+
const KIND_NAMES = {
|
|
103
|
+
core: t('kindCore'),
|
|
104
|
+
runtime: t('kindRuntime'),
|
|
105
|
+
api: t('kindApi'),
|
|
106
|
+
schema: t('kindSchema'),
|
|
107
|
+
util: t('kindUtil'),
|
|
108
|
+
other: t('kindOther'),
|
|
109
|
+
};
|
|
99
110
|
/**
|
|
100
111
|
* 找出「卸载 target 会断其服务依赖」的插件:target 的某个实例正在提供或马上就提供的某服务 S 没有别的插件正在提供,且有别的
|
|
101
112
|
* 插件 required 依赖 S(uses 里未包 optional)→ 这些插件会被打断。卸载连同 target 的全部实例一起卸下,所以按同名的每个实例判:算在提供的是
|
|
@@ -158,9 +169,9 @@ const PKG_SPEC_RE = /^(@[a-z0-9][a-z0-9\-_.]*\/)?[a-z0-9][a-z0-9\-_.]*(@[a-z0-9]
|
|
|
158
169
|
*/
|
|
159
170
|
export function validatePackageSpec(spec) {
|
|
160
171
|
if (typeof spec !== 'string' || spec.length === 0)
|
|
161
|
-
return '
|
|
172
|
+
return t('emptyName');
|
|
162
173
|
if (!PKG_SPEC_RE.test(spec))
|
|
163
|
-
return
|
|
174
|
+
return t('badName', { name: JSON.stringify(spec) });
|
|
164
175
|
return undefined;
|
|
165
176
|
}
|
|
166
177
|
// ===== 插件入口 =====
|
|
@@ -180,7 +191,7 @@ function createService(caps) {
|
|
|
180
191
|
const proc = caps.proc.use;
|
|
181
192
|
function getApp() {
|
|
182
193
|
if (!caps.app.present)
|
|
183
|
-
throw new
|
|
194
|
+
throw new TextError(t('noApp'));
|
|
184
195
|
return caps.app.use;
|
|
185
196
|
}
|
|
186
197
|
return createPackageManager({
|
|
@@ -335,20 +346,20 @@ const SPEC_ARGV_BUDGET = 128 * 1024;
|
|
|
335
346
|
*/
|
|
336
347
|
export function buildUpdateSpecs(targets) {
|
|
337
348
|
if (!Array.isArray(targets) || targets.length === 0)
|
|
338
|
-
return { error: '
|
|
349
|
+
return { error: t('noTargets') };
|
|
339
350
|
const specs = [];
|
|
340
351
|
const seen = new Set();
|
|
341
352
|
let bytes = 0;
|
|
342
|
-
for (const
|
|
343
|
-
const name = typeof
|
|
344
|
-
const version = typeof
|
|
353
|
+
for (const target of targets) {
|
|
354
|
+
const name = typeof target?.name === 'string' ? target.name : '';
|
|
355
|
+
const version = typeof target?.version === 'string' ? target.version : '';
|
|
345
356
|
if (!PKG_NAME_ONLY_RE.test(name))
|
|
346
|
-
return { error:
|
|
357
|
+
return { error: t('badName', { name: JSON.stringify(name) }) };
|
|
347
358
|
if (!VERSION_RE.test(version))
|
|
348
|
-
return { error:
|
|
359
|
+
return { error: t('badVersion', { name, version: JSON.stringify(version) }) };
|
|
349
360
|
// 同名多版本会让 npm 取最后一个,静默丢弃前面的选择——宁可让用户重选。
|
|
350
361
|
if (seen.has(name))
|
|
351
|
-
return { error:
|
|
362
|
+
return { error: t('duplicateTarget', { name }) };
|
|
352
363
|
seen.add(name);
|
|
353
364
|
const spec = `${name}@${version}`;
|
|
354
365
|
// 按**字节**计而非按个数:这些 spec 逐条成为 argv,而 execve 的约束是参数块的总字节数,
|
|
@@ -356,7 +367,7 @@ export function buildUpdateSpecs(targets) {
|
|
|
356
367
|
// 超限即整批停下,与本函数其余校验同一策略:不猜、不截断。
|
|
357
368
|
bytes += Buffer.byteLength(spec) + 1; // +1 = argv 之间的 NUL 分隔
|
|
358
369
|
if (bytes > SPEC_ARGV_BUDGET) {
|
|
359
|
-
return { error:
|
|
370
|
+
return { error: t('tooManyTargets', { budget: SPEC_ARGV_BUDGET }) };
|
|
360
371
|
}
|
|
361
372
|
specs.push(spec);
|
|
362
373
|
}
|
|
@@ -376,50 +387,44 @@ export function extractPeerConflicts(output) {
|
|
|
376
387
|
* 不同
|
|
377
388
|
*/
|
|
378
389
|
const NPM_FAILURES = [
|
|
379
|
-
{ kind: 'disk', match: /\bENOSPC\b/, describe: () => '
|
|
380
|
-
{
|
|
381
|
-
kind: 'permission',
|
|
382
|
-
match: /\bEACCES\b/,
|
|
383
|
-
describe: () => '没有写入权限(EACCES):把项目目录与 npm 的缓存目录(npm config get cache 查看)改回运行 Aalis 的用户所有后重试,不要用 sudo',
|
|
384
|
-
},
|
|
390
|
+
{ kind: 'disk', match: /\bENOSPC\b/, describe: () => t('npmDisk') },
|
|
391
|
+
{ kind: 'permission', match: /\bEACCES\b/, describe: () => t('npmPermission') },
|
|
385
392
|
{
|
|
386
393
|
kind: 'platform',
|
|
387
394
|
match: /\bEBADPLATFORM\b/,
|
|
388
395
|
describe: output => {
|
|
389
|
-
const pkg = /Unsupported platform for (\S+?)@\d/.exec(output)?.[1]
|
|
390
|
-
return
|
|
396
|
+
const pkg = /Unsupported platform for (\S+?)@\d/.exec(output)?.[1];
|
|
397
|
+
return pkg === undefined ? t('npmPlatformUnknown') : t('npmPlatform', { pkg });
|
|
391
398
|
},
|
|
392
399
|
},
|
|
393
400
|
{
|
|
394
401
|
kind: 'peer',
|
|
395
402
|
match: /\bERESOLVE\b/,
|
|
396
|
-
describe: output =>
|
|
403
|
+
describe: output => t('npmPeer', { conflicts: textClauses(extractPeerConflicts(output)) }),
|
|
397
404
|
},
|
|
398
405
|
{
|
|
399
406
|
kind: 'browser',
|
|
400
407
|
match: /Failed to set up (?:chrome|chrome-headless-shell|firefox)\b|node_modules[\\/]puppeteer\s*$/m,
|
|
401
|
-
describe: () => '
|
|
408
|
+
describe: () => t('npmBrowser'),
|
|
402
409
|
},
|
|
403
410
|
{
|
|
404
411
|
kind: 'native',
|
|
405
412
|
match: /gyp ERR!|node-gyp|prebuild-install/,
|
|
406
413
|
describe: output => {
|
|
407
|
-
const pkg = /^npm (?:error|ERR!) path \S*node_modules[\\/]((?:@[^\\/\s]+[\\/])?[^\\/\s]+)\s*$/m.exec(output)?.[1]
|
|
408
|
-
|
|
409
|
-
return (`原生模块 ${pkg} 编译失败:本机缺 C/C++ 编译工具。macOS 执行 xcode-select --install;` +
|
|
410
|
-
'Linux 装 build-essential 与 python3;Windows 装 Visual Studio 的「使用 C++ 的桌面开发」,装好后重试');
|
|
414
|
+
const pkg = /^npm (?:error|ERR!) path \S*node_modules[\\/]((?:@[^\\/\s]+[\\/])?[^\\/\s]+)\s*$/m.exec(output)?.[1];
|
|
415
|
+
return pkg === undefined ? t('npmNativeUnknown') : t('npmNative', { pkg });
|
|
411
416
|
},
|
|
412
417
|
},
|
|
413
418
|
{
|
|
414
419
|
kind: 'missing',
|
|
415
420
|
match: /\b(E404|ETARGET)\b/,
|
|
416
421
|
// 镜像源同步有延迟,刚发布的版本可能还没有
|
|
417
|
-
describe: (_output, [, code]) =>
|
|
422
|
+
describe: (_output, [, code]) => t('npmMissing', { code }),
|
|
418
423
|
},
|
|
419
424
|
{
|
|
420
425
|
kind: 'network',
|
|
421
426
|
match: /\b(ENOTFOUND|ETIMEDOUT|ECONNRESET|ECONNREFUSED|EAI_AGAIN)\b/,
|
|
422
|
-
describe: (_output, [, code]) =>
|
|
427
|
+
describe: (_output, [, code]) => t('npmNetwork', { code }),
|
|
423
428
|
},
|
|
424
429
|
];
|
|
425
430
|
/** 按先后认 npm 的失败输出:第一个对上的类别与匹配结果 */
|
|
@@ -448,7 +453,7 @@ export function describeNpmFailure(output) {
|
|
|
448
453
|
.map(line => /^npm (?:error|ERR!)\s*(.*)$/.exec(line.trim())?.[1]?.trim())
|
|
449
454
|
.filter((line) => !!line && !line.startsWith('A complete log of this run'));
|
|
450
455
|
const last = errors.at(-1) ?? output.trim().split('\n').at(-1)?.trim();
|
|
451
|
-
return
|
|
456
|
+
return t('npmOther', { error: String(last) });
|
|
452
457
|
}
|
|
453
458
|
/**
|
|
454
459
|
* 从 **成功**(exit 0)的 dry-run 输出里找出因本次目标而无法满足的 peer 依赖。
|
|
@@ -465,7 +470,7 @@ export function describeNpmFailure(output) {
|
|
|
465
470
|
export function findUnmetPeers(output, targetNames) {
|
|
466
471
|
const names = new Set(targetNames);
|
|
467
472
|
// npm 会把同一条 peer 冲突在不同上下文里重复打印,去重后再给用户。
|
|
468
|
-
const out = new
|
|
473
|
+
const out = new Map();
|
|
469
474
|
for (const raw of output.split('\n')) {
|
|
470
475
|
const line = raw.replace(/^npm (ERR!|error|warn)\s*/i, '').trim();
|
|
471
476
|
const m = /^peer\s+(\S+?)@"([^"]+)"\s+from\s+(.+)$/.exec(line);
|
|
@@ -473,9 +478,9 @@ export function findUnmetPeers(output, targetNames) {
|
|
|
473
478
|
continue;
|
|
474
479
|
if (!names.has(m[1]))
|
|
475
480
|
continue;
|
|
476
|
-
out.
|
|
481
|
+
out.set(`${m[3]}\0${m[1]}@${m[2]}`, t('needsPeer', { dependent: m[3], peer: `${m[1]}@${m[2]}` }));
|
|
477
482
|
}
|
|
478
|
-
return [...out];
|
|
483
|
+
return [...out.values()];
|
|
479
484
|
}
|
|
480
485
|
/**
|
|
481
486
|
* 包管理核心:install/uninstall 的纯依赖实现(不碰服务选择,可单测)。
|
|
@@ -513,7 +518,7 @@ export function createPackageManager(deps) {
|
|
|
513
518
|
*/
|
|
514
519
|
async function exclusive(what, run) {
|
|
515
520
|
if (inflight) {
|
|
516
|
-
return { ok: false, message:
|
|
521
|
+
return { ok: false, message: t('busy', { operation: inflight }) };
|
|
517
522
|
}
|
|
518
523
|
inflight = what;
|
|
519
524
|
try {
|
|
@@ -532,7 +537,7 @@ export function createPackageManager(deps) {
|
|
|
532
537
|
catch (err) {
|
|
533
538
|
const output = err instanceof Error ? err.message : String(err);
|
|
534
539
|
log.error(`npm ${args.join(' ')} 失败: ${output}`);
|
|
535
|
-
throw new
|
|
540
|
+
throw new TextError(describeNpmFailure(output));
|
|
536
541
|
}
|
|
537
542
|
}
|
|
538
543
|
/** 安装源查询的缓存:只留成功的结果,失败时清掉,下次调用重查 */
|
|
@@ -603,10 +608,7 @@ export function createPackageManager(deps) {
|
|
|
603
608
|
const kind = perVersion.map(kw => nonMarketKind(kw)).find(k => k !== undefined);
|
|
604
609
|
if (!kind)
|
|
605
610
|
return undefined;
|
|
606
|
-
return (
|
|
607
|
-
(kind === '非插件包'
|
|
608
|
-
? '市场只安装带 aalis-plugin / aalis-interface 关键词的包。'
|
|
609
|
-
: '它随脚手架或依赖它的插件自动就位;要升级已装的版本,请在市场勾选它后用「更新所选」提交。'));
|
|
611
|
+
return t(kind === 'other' ? 'installUnmarked' : 'installBundled', { name: npmPkg, kind: KIND_NAMES[kind] });
|
|
610
612
|
}
|
|
611
613
|
/**
|
|
612
614
|
* 装完的统一判定:rescan 出新插件即成功;没出新插件时按目标是否**声明自己是插件**分流。
|
|
@@ -625,19 +627,18 @@ export function createPackageManager(deps) {
|
|
|
625
627
|
const settled = (message) => ({ ok: true, message, plugin: defName });
|
|
626
628
|
switch (main?.state) {
|
|
627
629
|
case 'error':
|
|
628
|
-
return settled(
|
|
630
|
+
return settled(t('installedErrored', { target, error: failureText(main) ?? t('seeLogs') }));
|
|
629
631
|
case 'activating':
|
|
630
|
-
return settled(
|
|
632
|
+
return settled(t('installedActivating', { target }));
|
|
631
633
|
case 'pending':
|
|
632
|
-
return settled(
|
|
634
|
+
return settled(t('installedPending', { target }));
|
|
633
635
|
}
|
|
634
|
-
return settled(
|
|
636
|
+
return settled(t('installedLoaded', { target }));
|
|
635
637
|
}
|
|
636
638
|
const meta = await readJson(installedPkgJsonPath);
|
|
637
|
-
if (declaresPlugin(meta))
|
|
638
|
-
return { ok: false, message:
|
|
639
|
-
}
|
|
640
|
-
return { ok: true, message: `已安装 ${target}(非插件包,不进入插件列表)` };
|
|
639
|
+
if (declaresPlugin(meta))
|
|
640
|
+
return { ok: false, message: t('installedNotLoaded', { where }) };
|
|
641
|
+
return { ok: true, message: t('installedNonPlugin', { target }) };
|
|
641
642
|
}
|
|
642
643
|
/**
|
|
643
644
|
* 安装:写根 `dependencies` —— 加载器**唯一**的发现来源。
|
|
@@ -654,10 +655,7 @@ export function createPackageManager(deps) {
|
|
|
654
655
|
const root = deps.projectRoot();
|
|
655
656
|
const rootPkg = await readJson(`${root}/package.json`);
|
|
656
657
|
if (hasWorkspaceProtocol(rootPkg)) {
|
|
657
|
-
return {
|
|
658
|
-
ok: false,
|
|
659
|
-
message: '根依赖含 workspace: 协议,拒绝在此运行 npm install(会写出 package-lock.json 并搅坏 pnpm 工作区)',
|
|
660
|
-
};
|
|
658
|
+
return { ok: false, message: t('workspaceInstall') };
|
|
661
659
|
}
|
|
662
660
|
const refused = await installKindRefusal(npmPkg, root);
|
|
663
661
|
if (refused)
|
|
@@ -720,7 +718,7 @@ export function createPackageManager(deps) {
|
|
|
720
718
|
if (conflicts.length > 0)
|
|
721
719
|
return conflicts;
|
|
722
720
|
log.error(`更新预检失败: ${raw}`);
|
|
723
|
-
throw new
|
|
721
|
+
throw new TextError(describeNpmFailure(raw));
|
|
724
722
|
}
|
|
725
723
|
const unmet = findUnmetPeers(`${output.stderr}\n${output.stdout}`, specs.map(stripVersion));
|
|
726
724
|
return unmet.length > 0 ? unmet : undefined;
|
|
@@ -734,11 +732,12 @@ export function createPackageManager(deps) {
|
|
|
734
732
|
* 重启次数恒为 1,与改了多少个包无关(语义见 {@link PackageManagerService.update})。
|
|
735
733
|
*/
|
|
736
734
|
async function updateAll(targets) {
|
|
737
|
-
const
|
|
738
|
-
if (
|
|
739
|
-
return { ok: false, message: error
|
|
735
|
+
const built = buildUpdateSpecs(targets);
|
|
736
|
+
if (built.error)
|
|
737
|
+
return { ok: false, message: built.error };
|
|
738
|
+
const { specs } = built;
|
|
740
739
|
if (!deps.restartApp)
|
|
741
|
-
return { ok: false, message: '
|
|
740
|
+
return { ok: false, message: t('noRestart') };
|
|
742
741
|
const root = deps.projectRoot();
|
|
743
742
|
// 没有「项目形态」这一层判断了:能不能更新是**每个包自己的来源**说了算(下面的
|
|
744
743
|
// isRegistryDep 闸),不是整个项目的属性。旧写法拿 `pnpm-workspace.yaml` 是否存在
|
|
@@ -746,34 +745,29 @@ export function createPackageManager(deps) {
|
|
|
746
745
|
const rootPkgPath = `${root}/package.json`;
|
|
747
746
|
const rootPkgText = await deps.readText(rootPkgPath);
|
|
748
747
|
if (rootPkgText === undefined)
|
|
749
|
-
return { ok: false, message:
|
|
748
|
+
return { ok: false, message: t('noRootPackage', { path: rootPkgPath }) };
|
|
750
749
|
const rootPkg = await readJson(rootPkgPath);
|
|
751
|
-
if (hasWorkspaceProtocol(rootPkg))
|
|
752
|
-
return { ok: false, message: '
|
|
753
|
-
}
|
|
750
|
+
if (hasWorkspaceProtocol(rootPkg))
|
|
751
|
+
return { ok: false, message: t('workspaceUpdate') };
|
|
754
752
|
// 只允许更新「根依赖里以 semver 范围声明」的包。闸放在服务层而非 HTTP 路由——
|
|
755
753
|
// 本服务对外公开,任何插件都能绕过路由直接调用(理由见 isRegistryDep)。
|
|
756
754
|
const rootDependencies = (rootPkg?.dependencies ?? {});
|
|
757
|
-
const notUpdatable = targets.filter(
|
|
755
|
+
const notUpdatable = targets.filter(target => !isRegistryDep(rootDependencies[target.name]));
|
|
758
756
|
if (notUpdatable.length > 0) {
|
|
759
|
-
return {
|
|
760
|
-
ok: false,
|
|
761
|
-
message: '以下包不在根依赖中(由其它插件带入)或不是 registry 来源,单独更新只会装出第二份副本;' +
|
|
762
|
-
`请改为更新带入它们的插件:${notUpdatable.map(t => t.name).join('、')}`,
|
|
763
|
-
};
|
|
757
|
+
return { ok: false, message: t('notUpdatable', { names: notUpdatable.map(target => target.name) }) };
|
|
764
758
|
}
|
|
765
759
|
// ── 读取每个目标的当前实装版本 ──
|
|
766
760
|
// 两处都要用:(a) 降级守卫;(b) 回滚凭据钉精确旧版。两者都不能靠根 package.json 的
|
|
767
761
|
// 声明——那是**范围**,回滚时按范围重解析只会又解到刚崩掉的新版。
|
|
768
762
|
const installedVersions = new Map();
|
|
769
|
-
for (const
|
|
770
|
-
const text = await deps.readText(`${root}/node_modules/${
|
|
763
|
+
for (const target of targets) {
|
|
764
|
+
const text = await deps.readText(`${root}/node_modules/${target.name}/package.json`);
|
|
771
765
|
if (text === undefined)
|
|
772
766
|
continue;
|
|
773
767
|
try {
|
|
774
768
|
const v = JSON.parse(text).version;
|
|
775
769
|
if (typeof v === 'string')
|
|
776
|
-
installedVersions.set(
|
|
770
|
+
installedVersions.set(target.name, v);
|
|
777
771
|
}
|
|
778
772
|
catch {
|
|
779
773
|
/* 坏 JSON:当作读不到,由下面的守卫拒掉 */
|
|
@@ -784,22 +778,17 @@ export function createPackageManager(deps) {
|
|
|
784
778
|
// 拒绝而非放行的理由:registry 的 dist-tags.latest 可以**低于**本地已装版本(发布事故后
|
|
785
779
|
// `npm dist-tag add pkg@旧版 latest` 回滚,或用户曾装过预发布版),此时「更新」会静默降级;
|
|
786
780
|
// 读不到已装版本同样拒——无从判断方向就不动,用户可显式卸载重装。
|
|
787
|
-
const notUpgrade = targets.filter(
|
|
781
|
+
const notUpgrade = targets.filter(target => !isUpgrade(installedVersions.get(target.name), target.version));
|
|
788
782
|
if (notUpgrade.length > 0) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
message: '以下目标版本不高于本地已装版本(或本地版本号读不到),更新会变成降级,已拒绝:' +
|
|
792
|
-
notUpgrade
|
|
793
|
-
.map(t => `${t.name}(本地 ${installedVersions.get(t.name) ?? '未知'} → 目标 ${t.version})`)
|
|
794
|
-
.join('、'),
|
|
795
|
-
};
|
|
783
|
+
const items = notUpgrade.map(({ name, version }) => t('downgradeItem', { name, installed: installedVersions.get(name) ?? t('unknown'), version }));
|
|
784
|
+
return { ok: false, message: t('notUpgrade', { items }) };
|
|
796
785
|
}
|
|
797
786
|
// ── 预检:整组一次,不逐个 ──
|
|
798
787
|
// 逐个预检发现不了「A@new 要 core>=0.10、B@new 要 core<0.10」这类只在合并时冲突的组合。
|
|
799
788
|
log.info(`更新预检: ${specs.join(' ')}`);
|
|
800
789
|
const reject = (conflicts) => ({
|
|
801
790
|
ok: false,
|
|
802
|
-
message: '
|
|
791
|
+
message: t('preflightRejected'),
|
|
803
792
|
conflicts,
|
|
804
793
|
});
|
|
805
794
|
let verdict;
|
|
@@ -807,7 +796,7 @@ export function createPackageManager(deps) {
|
|
|
807
796
|
verdict = await preflightInSandbox(specs, root);
|
|
808
797
|
}
|
|
809
798
|
catch (err) {
|
|
810
|
-
return { ok: false, message:
|
|
799
|
+
return { ok: false, message: t('preflightFailed', { error: textOf(err) }) };
|
|
811
800
|
}
|
|
812
801
|
if (verdict)
|
|
813
802
|
return reject(verdict);
|
|
@@ -844,7 +833,7 @@ export function createPackageManager(deps) {
|
|
|
844
833
|
await npm(['install', ...specs, '--no-audit', '--no-fund'], root, INSTALL_TIMEOUT_MS);
|
|
845
834
|
}
|
|
846
835
|
catch (err) {
|
|
847
|
-
return { ok: false, message:
|
|
836
|
+
return { ok: false, message: t('installFailedRetry', { error: textOf(err) }) };
|
|
848
837
|
}
|
|
849
838
|
// ── 重启接管 ──
|
|
850
839
|
// 回滚凭据交给重启策略:只有它能观察到「新实例 ready 前夭折」。插件级失败不在此列
|
|
@@ -855,19 +844,19 @@ export function createPackageManager(deps) {
|
|
|
855
844
|
// postRestore 钉**精确旧版**,不用裸 `npm install`。
|
|
856
845
|
// 裸 install 只能按还原后的 package.json 重解析,而声明通常是 caret 范围(`^1.0.0`),
|
|
857
846
|
// 刚崩掉的新版(1.1.0)本身就满足它——于是 npm 又把它装回来,node_modules 分毫未变,
|
|
858
|
-
//
|
|
847
|
+
// 而重启策略照样报告已回滚并拉起旧版本。实测:有锁时正常,无锁时静默失效。
|
|
859
848
|
// `--no-save` 保证 package.json 维持 restore 写回的原样,不被 npm 改写成 `^旧版`。
|
|
860
849
|
// 降级守卫已保证每个 target 都有已装版本(读不到的在那里就被拒了),此处的 undefined
|
|
861
850
|
// 过滤只是让类型收窄,不承担逻辑。
|
|
862
851
|
const pinned = targets
|
|
863
|
-
.map(
|
|
864
|
-
const v = installedVersions.get(
|
|
865
|
-
return v === undefined ? undefined : `${
|
|
852
|
+
.map(target => {
|
|
853
|
+
const v = installedVersions.get(target.name);
|
|
854
|
+
return v === undefined ? undefined : `${target.name}@${v}`;
|
|
866
855
|
})
|
|
867
856
|
.filter((s) => s !== undefined);
|
|
868
857
|
// 再把「被这次安装顺带改了版本」的包一并钉回旧版(理由见上面 treeBefore 处的注释)。
|
|
869
858
|
// 只回退**本来就在**的包:新增的包钉不回去(它更新前不存在),留给 npm 的 extraneous。
|
|
870
|
-
const named = new Set(targets.map(
|
|
859
|
+
const named = new Set(targets.map(target => target.name));
|
|
871
860
|
const treeAfter = await readInstalledTree(deps, root);
|
|
872
861
|
for (const [name, before] of treeBefore) {
|
|
873
862
|
if (named.has(name))
|
|
@@ -881,11 +870,11 @@ export function createPackageManager(deps) {
|
|
|
881
870
|
restore,
|
|
882
871
|
postRestore: { cmd: 'npm', args: ['install', ...pinned, '--no-save', '--no-audit', '--no-fund'], cwd: root },
|
|
883
872
|
});
|
|
884
|
-
return { ok: true, restarting: true, message:
|
|
873
|
+
return { ok: true, restarting: true, message: t('updated', { specs }) };
|
|
885
874
|
}
|
|
886
875
|
return {
|
|
887
|
-
update:
|
|
888
|
-
install: npmPkg => exclusive(
|
|
876
|
+
update: targets => exclusive(t('opUpdate', { names: targets.map(x => x.name) }), () => updateAll(targets)),
|
|
877
|
+
install: npmPkg => exclusive(t('opInstall', { name: npmPkg }), async () => {
|
|
889
878
|
const bad = validatePackageSpec(npmPkg);
|
|
890
879
|
if (bad)
|
|
891
880
|
return { ok: false, message: bad };
|
|
@@ -893,12 +882,12 @@ export function createPackageManager(deps) {
|
|
|
893
882
|
return await installTo(npmPkg);
|
|
894
883
|
}
|
|
895
884
|
catch (err) {
|
|
896
|
-
const message =
|
|
897
|
-
log.error(`安装 "${npmPkg}" 失败: ${message}`);
|
|
885
|
+
const message = textOf(err);
|
|
886
|
+
log.error(`安装 "${npmPkg}" 失败: ${toSource(message)}`);
|
|
898
887
|
return { ok: false, message };
|
|
899
888
|
}
|
|
900
889
|
}),
|
|
901
|
-
uninstall: (pluginName, options) => exclusive(
|
|
890
|
+
uninstall: (pluginName, options) => exclusive(t('opUninstall', { name: pluginName }), () => uninstallOne(pluginName, options)),
|
|
902
891
|
serviceDependents: name => findServiceDependents(name, deps.pluginStatus?.() ?? [], deps.availableServices?.() ?? []),
|
|
903
892
|
registry,
|
|
904
893
|
};
|
|
@@ -916,10 +905,7 @@ export function createPackageManager(deps) {
|
|
|
916
905
|
const root = deps.projectRoot();
|
|
917
906
|
const meta = await readJson(`${root}/node_modules/${pluginName}/package.json`);
|
|
918
907
|
if (!meta) {
|
|
919
|
-
return {
|
|
920
|
-
ok: false,
|
|
921
|
-
message: `读不到 ${pluginName} 的 package.json,无法确认它是什么,市场不卸;确认要卸就在项目目录执行 npm uninstall ${pluginName} 后重启`,
|
|
922
|
-
};
|
|
908
|
+
return { ok: false, message: t('noPackageJson', { name: pluginName }) };
|
|
923
909
|
}
|
|
924
910
|
// ── 闸一:类型 ──
|
|
925
911
|
// 市场管的是**插件**。core / runtime / api / schema / util 不是插件,不在本工具职权内。
|
|
@@ -932,10 +918,10 @@ export function createPackageManager(deps) {
|
|
|
932
918
|
if (kind) {
|
|
933
919
|
return {
|
|
934
920
|
ok: false,
|
|
935
|
-
message:
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
921
|
+
message: t(kind === 'core' || kind === 'runtime' ? 'uninstallVital' : 'uninstallPruned', {
|
|
922
|
+
name: pluginName,
|
|
923
|
+
kind: KIND_NAMES[kind],
|
|
924
|
+
}),
|
|
939
925
|
};
|
|
940
926
|
}
|
|
941
927
|
// ── 闸一之二:承载「撤销通道」的服务提供者不能被卸掉 ──
|
|
@@ -952,27 +938,23 @@ export function createPackageManager(deps) {
|
|
|
952
938
|
// 那时它已不是提供者,本闸自然放行。
|
|
953
939
|
const lifeline = deps.recoveryChannelProviders?.() ?? [];
|
|
954
940
|
if (lifeline.includes(pluginName)) {
|
|
955
|
-
return {
|
|
956
|
-
ok: false,
|
|
957
|
-
message: `${pluginName} 正在承载市场/管理界面本身,卸掉它这个页面会立刻不可用,` +
|
|
958
|
-
'只能在终端里恢复。若确要更换实现,请先装上替代者并在设置「系统」的服务偏好里切换过去,再回来卸载它。',
|
|
959
|
-
};
|
|
941
|
+
return { ok: false, message: t('lifeline', { name: pluginName }) };
|
|
960
942
|
}
|
|
961
943
|
// ── 闸二:来源 ──
|
|
962
944
|
// 只有根依赖里以 semver 范围声明的才归市场管。workspace: 是用户自己的源码(删了不可逆,
|
|
963
945
|
// 且多半在 git 里);file:/link: 指向本地目录;git/URL 是外部源;不在根依赖里的是传递依赖。
|
|
964
946
|
const rootPkg = await readJson(`${root}/package.json`);
|
|
965
947
|
const request = (rootPkg?.dependencies ?? {})[pluginName];
|
|
966
|
-
|
|
967
|
-
|
|
948
|
+
const origin = classifyDepSpec(request);
|
|
949
|
+
if (origin !== 'registry') {
|
|
968
950
|
const why = {
|
|
969
|
-
workspace: '
|
|
970
|
-
link: '
|
|
971
|
-
git: '
|
|
972
|
-
alias: '
|
|
973
|
-
transitive: '
|
|
951
|
+
workspace: t('originWorkspace'),
|
|
952
|
+
link: t('originLink'),
|
|
953
|
+
git: t('originGit'),
|
|
954
|
+
alias: t('originAlias'),
|
|
955
|
+
transitive: t('originTransitive'),
|
|
974
956
|
};
|
|
975
|
-
return { ok: false, message:
|
|
957
|
+
return { ok: false, message: t('notFromMarket', { name: pluginName, why: why[origin] }) };
|
|
976
958
|
}
|
|
977
959
|
// 先解析定义 name 并枚举实例:npm uninstall 会拆掉 node_modules,之后就读不到入口了。
|
|
978
960
|
const defName = await definitionNameFor(pluginName);
|
|
@@ -982,10 +964,7 @@ export function createPackageManager(deps) {
|
|
|
982
964
|
// 与市场卸载前的预警同一份判定(serviceDependents),按加载器解析出的定义 name 查。
|
|
983
965
|
const dependents = findServiceDependents(defName, deps.pluginStatus?.() ?? [], deps.availableServices?.() ?? []);
|
|
984
966
|
if (dependents.length > 0) {
|
|
985
|
-
return {
|
|
986
|
-
ok: false,
|
|
987
|
-
message: `卸载会破坏依赖:${dependents.join('、')} 依赖此插件提供的服务,且没有别的插件正在提供。请先停用它们,或启用 / 安装替代提供者。`,
|
|
988
|
-
};
|
|
967
|
+
return { ok: false, message: t('breaksDependents', { dependents }) };
|
|
989
968
|
}
|
|
990
969
|
try {
|
|
991
970
|
await npm(['uninstall', pluginName, '--no-audit', '--no-fund'], root, INSTALL_TIMEOUT_MS);
|
|
@@ -994,10 +973,9 @@ export function createPackageManager(deps) {
|
|
|
994
973
|
await deps.unloadPlugin(id);
|
|
995
974
|
}
|
|
996
975
|
catch (err) {
|
|
997
|
-
return { ok: false, message:
|
|
976
|
+
return { ok: false, message: textOf(err) };
|
|
998
977
|
}
|
|
999
978
|
log.info(`${pluginName}: 已从根依赖与 node_modules 移除`);
|
|
1000
|
-
const removed = `插件 ${pluginName} 已卸载(已从根依赖与 node_modules 移除)`;
|
|
1001
979
|
// 包已经卸掉了,配置文件没写成不改变这一点:回执照样 ok,写明原因与残留
|
|
1002
980
|
try {
|
|
1003
981
|
await deps.cleanupConfig?.(instanceIds, removeConfig);
|
|
@@ -1005,18 +983,21 @@ export function createPackageManager(deps) {
|
|
|
1005
983
|
catch (err) {
|
|
1006
984
|
return {
|
|
1007
985
|
ok: true,
|
|
1008
|
-
message:
|
|
986
|
+
message: t(removeConfig ? 'uninstalledConfigUnsaved' : 'uninstalledFlagUnsaved', {
|
|
987
|
+
name: pluginName,
|
|
988
|
+
error: textOf(err),
|
|
989
|
+
}),
|
|
1009
990
|
};
|
|
1010
991
|
}
|
|
1011
992
|
return {
|
|
1012
993
|
ok: true,
|
|
1013
|
-
message:
|
|
994
|
+
message: t(removeConfig ? 'uninstalledConfigRemoved' : 'uninstalledConfigKept', { name: pluginName }),
|
|
1014
995
|
};
|
|
1015
996
|
}
|
|
1016
997
|
}
|
|
1017
998
|
export default definePlugin({
|
|
1018
999
|
name: '@aalis/plugin-package-manager',
|
|
1019
|
-
displayName: '
|
|
1000
|
+
displayName: t('displayName'),
|
|
1020
1001
|
subsystem: 'system',
|
|
1021
1002
|
provides: [packageManager],
|
|
1022
1003
|
uses,
|