@aalis/plugin-package-manager 0.9.0 → 0.10.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/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): string | undefined;
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): string | undefined;
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?: string[];
135
- error?: string;
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): 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[]): 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 与
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
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 `非法包名: ${JSON.stringify(spec)}`;
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 Error('app 服务不可用,无法执行包管理操作');
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 t of targets) {
343
- const name = typeof t?.name === 'string' ? t.name : '';
344
- const version = typeof t?.version === 'string' ? t.version : '';
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: `非法包名: ${JSON.stringify(name)}` };
357
+ return { error: t('badName', { name: JSON.stringify(name) }) };
347
358
  if (!VERSION_RE.test(version))
348
- return { error: `非法版本号: ${name}@${JSON.stringify(version)}` };
359
+ return { error: t('badVersion', { name, version: JSON.stringify(version) }) };
349
360
  // 同名多版本会让 npm 取最后一个,静默丢弃前面的选择——宁可让用户重选。
350
361
  if (seen.has(name))
351
- return { error: `同一包被指定了多次: ${name}` };
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: `更新目标过多:参数总长已超 ${SPEC_ARGV_BUDGET} 字节,请分批提交` };
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: () => '磁盘空间不够(ENOSPC):腾出空间后重试' },
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 `依赖 ${pkg} 不支持这台机器的系统或 CPU 架构(EBADPLATFORM),这个插件在这里装不了`;
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 => `peer 依赖冲突(市场安装不沿用 .npmrc 里的 legacy-peer-deps):${extractPeerConflicts(output).join(';')}`,
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: () => '下载浏览器失败:这个插件装的时候要下载 Chrome。给 Aalis 的进程设好镜像地址(环境变量 PUPPETEER_CHROME_DOWNLOAD_BASE_URL)后重启再装',
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]) => `源上没有这个包或这个版本(${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]) => `连不上 npm 源(${code}):检查网络与代理后重试;默认源不通时在终端执行 npm config set registry <源地址> 换一个源`,
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 `npm 出错:${last}。完整输出在日志里,常见原因与修法见 https://aalis.acenyan.com/guide/troubleshooting`;
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 Set();
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.add(`${m[3]} 需要 ${m[1]}@${m[2]}`);
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: `有包管理操作正在进行中(${inflight}),请稍候重试` };
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 Error(describeNpmFailure(output));
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 (`${npmPkg} 是${kind},不是可装卸的插件,市场不负责它的安装。` +
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(`已安装 ${target},但激活失败,已转为 error 态(${main.error ?? '详见日志'})`);
630
+ return settled(t('installedErrored', { target, error: failureText(main) ?? t('seeLogs') }));
629
631
  case 'activating':
630
- return settled(`已安装 ${target},仍在激活(超过慢激活阈值,已转入后台),结果以插件列表为准`);
632
+ return settled(t('installedActivating', { target }));
631
633
  case 'pending':
632
- return settled(`已安装 ${target},尚未激活,正在等待 required 依赖满足`);
634
+ return settled(t('installedPending', { target }));
633
635
  }
634
- return settled(`已安装并加载: ${target}`);
636
+ return settled(t('installedLoaded', { target }));
635
637
  }
636
638
  const meta = await readJson(installedPkgJsonPath);
637
- if (declaresPlugin(meta)) {
638
- return { ok: false, message: `已装到 ${where},但它声明为插件却未被加载——请检查其 keywords 与入口导出` };
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 Error(describeNpmFailure(raw));
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 { specs, error } = buildUpdateSpecs(targets);
738
- if (!specs)
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: `读不到根 package.json: ${rootPkgPath}` };
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: '根依赖含 workspace: 协议,拒绝在此运行 npm install' };
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(t => !isRegistryDep(rootDependencies[t.name]));
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 t of targets) {
770
- const text = await deps.readText(`${root}/node_modules/${t.name}/package.json`);
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(t.name, v);
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(t => !isUpgrade(installedVersions.get(t.name), t.version));
781
+ const notUpgrade = targets.filter(target => !isUpgrade(installedVersions.get(target.name), target.version));
788
782
  if (notUpgrade.length > 0) {
789
- return {
790
- ok: false,
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: '依赖预检未通过,未改动任何文件。若是 peer 版本要求,请把被依赖方一并勾选后重试。',
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: `依赖预检没跑成:${err instanceof Error ? err.message : String(err)}` };
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: `安装失败(工程未重启,可重试):${err.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(t => {
864
- const v = installedVersions.get(t.name);
865
- return v === undefined ? undefined : `${t.name}@${v}`;
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(t => t.name));
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: `已更新 ${specs.join('、')},正在重启…` };
873
+ return { ok: true, restarting: true, message: t('updated', { specs }) };
885
874
  }
886
875
  return {
887
- update: t => exclusive(`更新 ${t.map(x => x.name).join('、')}`, () => updateAll(t)),
888
- install: npmPkg => exclusive(`安装 ${npmPkg}`, async () => {
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 = err instanceof Error ? err.message : String(err);
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(`卸载 ${pluginName}`, () => uninstallOne(pluginName, options)),
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: `${pluginName} 是${kind},不是可装卸的插件,市场不负责它的卸载。` +
936
- (kind === '内核' || kind === '宿主'
937
- ? '删掉它实例将无法启动、市场也随之消失,只能在终端里恢复;确需如此请手动执行 npm uninstall。'
938
- : '它随依赖它的插件被 npm 自动剪枝;确需单独删除请手动执行 npm uninstall。'),
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
- if (!isRegistryDep(request)) {
967
- const origin = classifyDepSpec(request);
948
+ const origin = classifyDepSpec(request);
949
+ if (origin !== 'registry') {
968
950
  const why = {
969
- workspace: '它是工作区里的本地源码包,删除请走 git',
970
- link: '它由 file:/link: 指向本地目录,删除请改那份源码与依赖声明',
971
- git: '它由 git/URL/user-repo 简写安装,删除请改依赖声明',
972
- alias: '它由 npm: 别名指向另一个包,删除请改依赖声明',
973
- transitive: '它不在根依赖里(由其它插件带入),会随带入它的插件被 npm 自动剪枝',
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: `${pluginName} 不是经市场安装的:${why[origin] ?? '来源不明'}。` };
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: err instanceof Error ? err.message : String(err) };
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: `${removed},但配置文件没写成(${err instanceof Error ? err.message : String(err)}):需要时手动删掉配置文件里它的${removeConfig ? '配置段与' : ''}停用标记。`,
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: `${removed}。${removeConfig ? '它的配置已从配置文件删除,只有它用的密钥一并删除' : '它的配置留在配置文件里,重装后照原配置启动'};写在家目录 state/ 等处的数据不会删除,需要时请手动清理。`,
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,