@yottameta/yotta-skills 0.23.3 → 0.24.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/lib/deps.js ADDED
@@ -0,0 +1,173 @@
1
+ 'use strict';
2
+ /**
3
+ * lib/deps.js —— 依赖缺口的统一人话提示(元阁 0.24.0,方案 B)。
4
+ *
5
+ * 约定:按需触发、不常驻;每条提示回答四件事:
6
+ * 需要什么 / 为什么 / 怎么办(一条可复制修复)/ 不影响使用。
7
+ * 依赖矩阵:Node 18+ 必需;npm 与系统 tar 是回退通道(内置拉包 / 内置解包为主);
8
+ * 元信装前扫描需要 Python 3.8+,优先指向宿主自带 Python(--python / YOTTA_SKILLS_PYTHON)。
9
+ */
10
+
11
+ const NEED = {
12
+ node: { label: 'Node 18+', need: '>=18', why: '运行元阁 CLI 本体' },
13
+ npm: { label: 'npm', need: '随 Node 安装', why: '拉包回退通道(内置拉包失败时的备用)' },
14
+ python: { label: 'Python 3.8+', need: '>=3.8', why: '元信装前安全扫描' },
15
+ tar: { label: 'tar', need: '可用即可', why: '解包回退通道(内置解包失败时的备用)' },
16
+ };
17
+
18
+ function fixFor(dep, platform) {
19
+ const p = platform || process.platform;
20
+ if (dep === 'node' || dep === 'npm') {
21
+ if (p === 'win32') return 'winget install OpenJS.NodeJS.LTS';
22
+ if (p === 'darwin') return 'brew install node';
23
+ return 'sudo apt install nodejs npm(或到 https://nodejs.org/ 下载 LTS)';
24
+ }
25
+ if (dep === 'python') {
26
+ if (p === 'win32') {
27
+ return '优先用 --python <路径> 或 YOTTA_SKILLS_PYTHON 指向宿主自带 Python;否则 winget install Python.Python.3.12';
28
+ }
29
+ if (p === 'darwin') {
30
+ return '优先用 --python <路径> 或 YOTTA_SKILLS_PYTHON 指向宿主自带 Python;否则 brew install python';
31
+ }
32
+ return '优先用 --python <路径> 或 YOTTA_SKILLS_PYTHON 指向宿主自带 Python;否则 sudo apt install python3';
33
+ }
34
+ if (dep === 'tar') {
35
+ if (p === 'win32') return 'Windows 10+ 自带 tar(缺失时可 winget install bsdtar)';
36
+ if (p === 'darwin') return 'macOS 自带 tar;或 brew install gnu-tar';
37
+ return 'sudo apt install tar';
38
+ }
39
+ return '';
40
+ }
41
+
42
+ /**
43
+ * 统一三行模板:
44
+ * 需要 <依赖>(用途:<为什么>)—— 装好后重跑:<原命令>
45
+ * 修复:<一条可复制命令>
46
+ * 或者:继续用宿主自带技能,不影响正常使用。<补充>
47
+ */
48
+ function describe(dep, options) {
49
+ const meta = NEED[dep] || { label: dep, need: '', why: '' };
50
+ const opts = options || {};
51
+ const command = opts.command || 'yotta-skills <原命令>';
52
+ const status = opts.missing
53
+ ? '—— 未找到 ' + meta.label + ';装好后重跑:' + command
54
+ : '—— 装好后重跑:' + command;
55
+ const extra = opts.extra ? ' ' + opts.extra : '';
56
+ return [
57
+ '需要 ' + meta.label + '(用途:' + meta.why + ')' + status,
58
+ '修复:' + (opts.fix || fixFor(dep)),
59
+ '或者:继续用宿主自带技能,不影响正常使用。' + extra,
60
+ ].join('\n');
61
+ }
62
+
63
+ function semverAtLeast(version, minimum) {
64
+ const got = String(version || '').match(/(\d+)\.(\d+)(?:\.(\d+))?/);
65
+ const need = String(minimum || '').match(/(\d+)\.(\d+)(?:\.(\d+))?/);
66
+ if (!got || !need) return null;
67
+ for (let i = 1; i <= 3; i += 1) {
68
+ const a = Number(got[i] || 0);
69
+ const b = Number(need[i] || 0);
70
+ if (a > b) return true;
71
+ if (a < b) return false;
72
+ }
73
+ return true;
74
+ }
75
+
76
+ /** Node 版本自检;version 可注入用于测试。 */
77
+ function nodeCheck(version) {
78
+ const current = version || process.versions.node;
79
+ return {
80
+ name: 'node',
81
+ found: true,
82
+ version: current,
83
+ ok: semverAtLeast(current, '18.0.0') === true,
84
+ need: NEED.node.need,
85
+ why: NEED.node.why,
86
+ };
87
+ }
88
+
89
+ /** npm 缺失错误识别(spawn ENOENT / 命令不存在的中英文提示)。 */
90
+ function isNpmMissing(raw) {
91
+ const text = String(raw || '');
92
+ return /ENOENT|EINVAL|npm\.cmd'? ?(无法|不能被)|不是内部或外部命令|not recognized|系统找不到指定/i.test(text);
93
+ }
94
+
95
+ function versionLine(proc) {
96
+ if (!proc) return null;
97
+ const text = String((proc.stdout || '') + (proc.stderr || '')).trim();
98
+ if (!text) return null;
99
+ return text.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0].slice(0, 80) || null;
100
+ }
101
+
102
+ /**
103
+ * doctor 依赖自检块(只告警不失败)。
104
+ * detected: { node:{version,ok}, npm:{found,version}, python:{found,version,path}, tar:{found,version} }
105
+ */
106
+ function dependencyReport(detected) {
107
+ const d = detected || {};
108
+ const items = [];
109
+
110
+ const node = d.node || {};
111
+ const nodeVersion = node.version || process.versions.node;
112
+ const nodeOk = node.ok !== undefined ? !!node.ok : semverAtLeast(nodeVersion, '18.0.0') === true;
113
+ items.push({
114
+ name: 'node',
115
+ found: true,
116
+ version: 'v' + String(nodeVersion),
117
+ ok: nodeOk,
118
+ need: NEED.node.need,
119
+ why: NEED.node.why,
120
+ optional: false,
121
+ fix: nodeOk ? null : fixFor('node'),
122
+ });
123
+
124
+ const npm = d.npm || {};
125
+ items.push({
126
+ name: 'npm',
127
+ found: !!npm.found,
128
+ version: npm.version || null,
129
+ ok: !!npm.found,
130
+ need: NEED.npm.need,
131
+ why: NEED.npm.why,
132
+ optional: true,
133
+ fix: npm.found ? null : fixFor('npm'),
134
+ });
135
+
136
+ const py = d.python || {};
137
+ const pyVersionOk = py.found ? semverAtLeast(py.version || '', '3.8.0') : false;
138
+ items.push({
139
+ name: 'python',
140
+ found: !!py.found,
141
+ version: py.version || null,
142
+ ok: pyVersionOk === true,
143
+ need: NEED.python.need,
144
+ why: NEED.python.why,
145
+ optional: false,
146
+ fix: pyVersionOk === true ? null : fixFor('python'),
147
+ });
148
+
149
+ const tar = d.tar || {};
150
+ items.push({
151
+ name: 'tar',
152
+ found: !!tar.found,
153
+ version: tar.version || null,
154
+ ok: !!tar.found,
155
+ need: NEED.tar.need,
156
+ why: NEED.tar.why,
157
+ optional: true,
158
+ fix: tar.found ? null : fixFor('tar'),
159
+ });
160
+
161
+ return items;
162
+ }
163
+
164
+ module.exports = {
165
+ NEED,
166
+ fixFor,
167
+ describe,
168
+ nodeCheck,
169
+ isNpmMissing,
170
+ versionLine,
171
+ dependencyReport,
172
+ semverAtLeast,
173
+ };
@@ -151,6 +151,7 @@ function createInstaller(deps) {
151
151
  const scanResult = deps.scanTarget(gate.engine, extracted.pkgDir, {
152
152
  slug: skill.slug,
153
153
  version: packed.resolved,
154
+ opts,
154
155
  });
155
156
  if (!scanResult.ok) {
156
157
  safeRecord({ event: 'before_install', skill: skill.slug, decision: 'block', error: scanResult.error });
@@ -338,6 +339,8 @@ function createInstaller(deps) {
338
339
  verdict: scan.verdict,
339
340
  decision: scan.decision,
340
341
  scan_policy: scan.policy || null,
342
+ fetch_channel: packed.channel || null,
343
+ extract_channel: extracted.channel || null,
341
344
  npm_registry_fallback: Boolean(packed.registryFallback),
342
345
  snapshot,
343
346
  });
@@ -351,6 +354,8 @@ function createInstaller(deps) {
351
354
  package: skill.pkg,
352
355
  version: packed.resolved || skill.version,
353
356
  decision: 'allow',
357
+ fetch_channel: packed.channel || null,
358
+ extract_channel: extracted.channel || null,
354
359
  snapshot,
355
360
  lifecycle: {
356
361
  setup: lifecycleSummary(lifecycle.setup),
package/lib/npm-pack.js CHANGED
@@ -24,6 +24,12 @@ function briefOf(raw) {
24
24
  return lines.slice(-4).join(' | ');
25
25
  }
26
26
 
27
+ /** 把 spawn 层错误(如 ENOENT:npm 缺失)并入原始错误文本,供上层人话提示识别。 */
28
+ function rawOf(result) {
29
+ if (result && result.error && result.error.message) return String(result.error.message).trim();
30
+ return String((result && result.stderr) || (result && result.stdout) || 'npm pack 失败').trim();
31
+ }
32
+
27
33
  function findTarball(stdout, packDir, readdirSync) {
28
34
  const lines = String(stdout || '')
29
35
  .split(/\r?\n/)
@@ -71,14 +77,14 @@ function createPackRunner(deps) {
71
77
  let result = attempt(baseArgs);
72
78
  let registryFallback = false;
73
79
  if (result.status !== 0) {
74
- const raw = (result.stderr || result.stdout || 'npm pack 失败').trim();
80
+ const raw = rawOf(result);
75
81
  if (!fallbackDisabled() && !hasRegistryFlag(baseArgs) && isNotFound(raw)) {
76
82
  result = attempt([...baseArgs, '--registry', OFFICIAL_REGISTRY]);
77
83
  registryFallback = result.status === 0;
78
84
  }
79
85
  }
80
86
  if (result.status !== 0) {
81
- const raw = (result.stderr || result.stdout || 'npm pack 失败').trim();
87
+ const raw = rawOf(result);
82
88
  const brief = briefOf(raw) || 'npm pack 失败';
83
89
  const hint = isNotFound(raw) ? '\n ' + FALLBACK_HINT : '';
84
90
  return { error: brief + hint, detail: raw };
@@ -0,0 +1,29 @@
1
+ 'use strict';
2
+ /**
3
+ * lib/registry-fetch-child.js —— 内置拉包的子进程入口(内部工具,勿直接调用)。
4
+ *
5
+ * 父进程(lib/registry-fetch.js fetchPackageSync)经 stdin 传入 JSON:
6
+ * { skill: {slug, pkg, version}, opts: {pin, registry}, packDir }
7
+ * stdout 最后一行输出 JSON:{ ok: true, result } 或 { ok: false, error }。
8
+ * 这样安装管线保持同步契约,网络 I/O 仍走同一套异步核心。
9
+ */
10
+
11
+ const fetchLib = require('./registry-fetch');
12
+
13
+ async function main() {
14
+ let payload;
15
+ try {
16
+ payload = JSON.parse(require('fs').readFileSync(0, 'utf8'));
17
+ } catch (error) {
18
+ process.stdout.write(JSON.stringify({ ok: false, error: '入参解析失败: ' + error.message }) + '\n');
19
+ return;
20
+ }
21
+ try {
22
+ const result = await fetchLib.fetchPackage(payload.skill, payload.opts || {}, payload.packDir);
23
+ process.stdout.write(JSON.stringify(result.ok ? { ok: true, result } : { ok: false, error: result.error }) + '\n');
24
+ } catch (error) {
25
+ process.stdout.write(JSON.stringify({ ok: false, error: '内置拉包异常: ' + (error && error.message ? error.message : String(error)) }) + '\n');
26
+ }
27
+ }
28
+
29
+ main();