@birdie_moblie/plugin-marketplace-cli 1.0.1 → 1.3.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/CHANGELOG.md +68 -0
- package/README.md +103 -6
- package/dist/adapters/agent-plugins.d.ts +11 -0
- package/dist/adapters/agent-plugins.js +66 -0
- package/dist/adapters/agent-plugins.js.map +1 -0
- package/dist/adapters/claude.d.ts +32 -1
- package/dist/adapters/claude.js +119 -14
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/codex.d.ts +7 -0
- package/dist/adapters/codex.js +10 -14
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/marketplace-registration.d.ts +17 -0
- package/dist/adapters/marketplace-registration.js +367 -0
- package/dist/adapters/marketplace-registration.js.map +1 -0
- package/dist/adapters/openspec.d.ts +66 -0
- package/dist/adapters/openspec.js +232 -0
- package/dist/adapters/openspec.js.map +1 -0
- package/dist/application/marketplace-registration-contract.d.ts +51 -0
- package/dist/application/marketplace-registration-contract.js +11 -0
- package/dist/application/marketplace-registration-contract.js.map +1 -0
- package/dist/application/marketplace-registration.d.ts +20 -0
- package/dist/application/marketplace-registration.js +304 -0
- package/dist/application/marketplace-registration.js.map +1 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +25 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/build.d.ts +1 -1
- package/dist/commands/build.js +27 -5
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/index-gen.d.ts +10 -0
- package/dist/commands/index-gen.js +81 -20
- package/dist/commands/index-gen.js.map +1 -1
- package/dist/commands/init-repo.js +11 -2
- package/dist/commands/init-repo.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +61 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install-repo.d.ts +20 -0
- package/dist/commands/install-repo.js +200 -0
- package/dist/commands/install-repo.js.map +1 -0
- package/dist/commands/release-local.d.ts +1 -0
- package/dist/commands/release-local.js +310 -7
- package/dist/commands/release-local.js.map +1 -1
- package/dist/commands/validate.js +399 -16
- package/dist/commands/validate.js.map +1 -1
- package/dist/core/errors.d.ts +5 -0
- package/dist/core/errors.js +12 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/generated/skill-content.js +1 -1
- package/dist/generated/skill-content.js.map +1 -1
- package/dist/infrastructure/process-runner.d.ts +29 -0
- package/dist/infrastructure/process-runner.js +130 -0
- package/dist/infrastructure/process-runner.js.map +1 -0
- package/dist/infrastructure/vscode-user-settings.d.ts +42 -0
- package/dist/infrastructure/vscode-user-settings.js +509 -0
- package/dist/infrastructure/vscode-user-settings.js.map +1 -0
- package/dist/schema/agent-plugins.d.ts +221 -0
- package/dist/schema/agent-plugins.js +155 -0
- package/dist/schema/agent-plugins.js.map +1 -0
- package/dist/schema/plugin-yaml.d.ts +37 -3
- package/dist/schema/plugin-yaml.js +89 -4
- package/dist/schema/plugin-yaml.js.map +1 -1
- package/dist/utils/helpers.d.ts +1 -0
- package/dist/utils/helpers.js +1 -0
- package/dist/utils/helpers.js.map +1 -1
- package/dist/validation/agent-plugins.d.ts +3 -0
- package/dist/validation/agent-plugins.js +418 -0
- package/dist/validation/agent-plugins.js.map +1 -0
- package/package.json +14 -5
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline/promises';
|
|
2
|
+
import { executeMarketplaceRegistration, inspectMarketplaceRegistration, normalizeTargetIds, } from '../application/marketplace-registration.js';
|
|
3
|
+
import { AGENT_TARGET_IDS, } from '../application/marketplace-registration-contract.js';
|
|
4
|
+
import { CommandError } from '../core/errors.js';
|
|
5
|
+
const INSPECTION_LABELS = {
|
|
6
|
+
ready: '可自动',
|
|
7
|
+
'missing-cli': '缺少 CLI',
|
|
8
|
+
'manual-required': '需手动',
|
|
9
|
+
failed: '不可用',
|
|
10
|
+
interrupted: '中断',
|
|
11
|
+
};
|
|
12
|
+
const RESULT_LABELS = {
|
|
13
|
+
completed: '完成',
|
|
14
|
+
'missing-cli': '缺少 CLI',
|
|
15
|
+
'manual-required': '需手动',
|
|
16
|
+
failed: '失败',
|
|
17
|
+
interrupted: '中断',
|
|
18
|
+
};
|
|
19
|
+
function defaultWrite(text) {
|
|
20
|
+
process.stdout.write(text);
|
|
21
|
+
}
|
|
22
|
+
async function defaultReadSelection(interruptController) {
|
|
23
|
+
if (interruptController.signal.aborted) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const terminal = createInterface({ input: process.stdin, output: process.stdout });
|
|
27
|
+
const onSigint = () => {
|
|
28
|
+
interruptController.abort('SIGINT');
|
|
29
|
+
};
|
|
30
|
+
terminal.on('SIGINT', onSigint);
|
|
31
|
+
try {
|
|
32
|
+
if (interruptController.signal.aborted) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
return await terminal.question('> ', { signal: interruptController.signal });
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (interruptController.signal.aborted ||
|
|
39
|
+
error?.name === 'AbortError') {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
terminal.removeListener('SIGINT', onSigint);
|
|
46
|
+
terminal.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function parseTargetSelection(input, availableTargetIds, defaultTargetIds) {
|
|
50
|
+
const value = input.trim();
|
|
51
|
+
if (value.length === 0) {
|
|
52
|
+
if (defaultTargetIds.length === 0) {
|
|
53
|
+
throw new CommandError('当前没有默认可自动处理的 agent;请输入明确编号或 all。');
|
|
54
|
+
}
|
|
55
|
+
return normalizeTargetIds(defaultTargetIds);
|
|
56
|
+
}
|
|
57
|
+
if (value.toLowerCase() === 'all') {
|
|
58
|
+
return normalizeTargetIds(availableTargetIds);
|
|
59
|
+
}
|
|
60
|
+
const tokens = value.split(',').map((token) => token.trim());
|
|
61
|
+
if (tokens.some((token) => !/^\d+$/u.test(token))) {
|
|
62
|
+
throw new CommandError('请选择逗号分隔的 agent 编号,或输入 all。');
|
|
63
|
+
}
|
|
64
|
+
const selected = [];
|
|
65
|
+
for (const token of tokens) {
|
|
66
|
+
const index = Number(token) - 1;
|
|
67
|
+
const target = availableTargetIds[index];
|
|
68
|
+
if (!target) {
|
|
69
|
+
throw new CommandError(`agent 编号超出范围: ${token}`);
|
|
70
|
+
}
|
|
71
|
+
selected.push(target);
|
|
72
|
+
}
|
|
73
|
+
return normalizeTargetIds(selected);
|
|
74
|
+
}
|
|
75
|
+
function renderPrompt(inspection, write) {
|
|
76
|
+
write('请选择要注册 Marketplace 的 agent:\n');
|
|
77
|
+
inspection.targets.forEach((target, index) => {
|
|
78
|
+
write(` ${index + 1}. ${target.label.padEnd(18)} [${INSPECTION_LABELS[target.status]}]\n`);
|
|
79
|
+
});
|
|
80
|
+
const defaults = inspection.targets
|
|
81
|
+
.filter((target) => target.status === 'ready')
|
|
82
|
+
.map((target) => target.id);
|
|
83
|
+
const defaultNumbers = defaults.map((id) => inspection.targets.findIndex((target) => target.id === id) + 1);
|
|
84
|
+
write(`\n默认:${defaultNumbers.length > 0 ? defaultNumbers.join(',') : '无'}\n`);
|
|
85
|
+
write('输入编号(逗号分隔),输入 all 选择全部,直接回车接受默认:\n');
|
|
86
|
+
return defaults;
|
|
87
|
+
}
|
|
88
|
+
function formatInvocation(target) {
|
|
89
|
+
if (!target.invocation) {
|
|
90
|
+
return target.message;
|
|
91
|
+
}
|
|
92
|
+
return [target.invocation.executable, ...target.invocation.args.map((arg) => JSON.stringify(arg))]
|
|
93
|
+
.join(' ');
|
|
94
|
+
}
|
|
95
|
+
function renderTargetStart(target, write) {
|
|
96
|
+
if (target.status === 'ready') {
|
|
97
|
+
write(`→ ${target.label}: ${formatInvocation(target)}\n`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function renderResultLine(result, write) {
|
|
101
|
+
write(` [${RESULT_LABELS[result.status]}] ${result.label}:${result.message}\n`);
|
|
102
|
+
if (result.status !== 'completed' && result.recovery) {
|
|
103
|
+
write(` 下一步:${result.recovery}\n`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function renderReport(report, write) {
|
|
107
|
+
const completed = report.results.filter((result) => result.status === 'completed');
|
|
108
|
+
const incomplete = report.results.filter((result) => result.status !== 'completed');
|
|
109
|
+
write('\n注册结果:\n');
|
|
110
|
+
write('已完成:\n');
|
|
111
|
+
if (completed.length === 0) {
|
|
112
|
+
write(' 无\n');
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
completed.forEach((result) => renderResultLine(result, write));
|
|
116
|
+
}
|
|
117
|
+
write('未完成:\n');
|
|
118
|
+
if (incomplete.length === 0) {
|
|
119
|
+
write(' 无\n');
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
incomplete.forEach((result) => renderResultLine(result, write));
|
|
123
|
+
}
|
|
124
|
+
write(`退出码:${report.exitCode}\n`);
|
|
125
|
+
}
|
|
126
|
+
function selectedInspection(inspection, targetIds) {
|
|
127
|
+
const selected = new Set(targetIds);
|
|
128
|
+
return {
|
|
129
|
+
source: inspection.source,
|
|
130
|
+
targets: inspection.targets.filter((target) => selected.has(target.id)),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
export async function runInstallRepo(source, options, dependencies = {}) {
|
|
134
|
+
const requested = [...(options.agent ?? [])];
|
|
135
|
+
if (options.all && requested.length > 0) {
|
|
136
|
+
throw new CommandError('不能同时使用 --agent 与 --all。');
|
|
137
|
+
}
|
|
138
|
+
const interactive = dependencies.interactive ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
139
|
+
const explicitTargetIds = options.all
|
|
140
|
+
? [...AGENT_TARGET_IDS]
|
|
141
|
+
: requested.length > 0
|
|
142
|
+
? normalizeTargetIds(requested)
|
|
143
|
+
: undefined;
|
|
144
|
+
if (!explicitTargetIds && !interactive) {
|
|
145
|
+
throw new CommandError('非交互终端必须显式传入至少一个 --agent,或使用 --all。');
|
|
146
|
+
}
|
|
147
|
+
const write = dependencies.write ?? defaultWrite;
|
|
148
|
+
const interruptController = new AbortController();
|
|
149
|
+
const onSigint = () => interruptController.abort('SIGINT');
|
|
150
|
+
const runtime = {
|
|
151
|
+
...dependencies.runtime,
|
|
152
|
+
signal: interruptController.signal,
|
|
153
|
+
};
|
|
154
|
+
process.on('SIGINT', onSigint);
|
|
155
|
+
try {
|
|
156
|
+
let inspection = await inspectMarketplaceRegistration(source, {
|
|
157
|
+
baseDir: dependencies.baseDir,
|
|
158
|
+
homeDir: dependencies.homeDir,
|
|
159
|
+
targetIds: explicitTargetIds ?? AGENT_TARGET_IDS,
|
|
160
|
+
runtime,
|
|
161
|
+
});
|
|
162
|
+
if (!explicitTargetIds) {
|
|
163
|
+
const interrupted = inspection.targets.some((target) => target.status === 'interrupted');
|
|
164
|
+
if (interrupted) {
|
|
165
|
+
const report = await executeMarketplaceRegistration(inspection, {
|
|
166
|
+
runtime,
|
|
167
|
+
onTargetResult: (result) => renderResultLine(result, write),
|
|
168
|
+
});
|
|
169
|
+
renderReport(report, write);
|
|
170
|
+
return { exitCode: 130, report };
|
|
171
|
+
}
|
|
172
|
+
const defaultTargetIds = renderPrompt(inspection, write);
|
|
173
|
+
if (interruptController.signal.aborted) {
|
|
174
|
+
write('\n[中断] 用户取消了 Marketplace 注册。\n');
|
|
175
|
+
return { exitCode: 130 };
|
|
176
|
+
}
|
|
177
|
+
const answer = dependencies.readSelection
|
|
178
|
+
? await dependencies.readSelection(interruptController.signal)
|
|
179
|
+
: await defaultReadSelection(interruptController);
|
|
180
|
+
if (answer === undefined || interruptController.signal.aborted) {
|
|
181
|
+
write('\n[中断] 用户取消了 Marketplace 注册。\n');
|
|
182
|
+
return { exitCode: 130 };
|
|
183
|
+
}
|
|
184
|
+
const selected = parseTargetSelection(answer, inspection.targets.map((target) => target.id), defaultTargetIds);
|
|
185
|
+
inspection = selectedInspection(inspection, selected);
|
|
186
|
+
}
|
|
187
|
+
write(`Marketplace 来源:${inspection.source.displayValue}\n`);
|
|
188
|
+
const report = await executeMarketplaceRegistration(inspection, {
|
|
189
|
+
runtime,
|
|
190
|
+
onTargetStart: (target) => renderTargetStart(target, write),
|
|
191
|
+
onTargetResult: (result) => renderResultLine(result, write),
|
|
192
|
+
});
|
|
193
|
+
renderReport(report, write);
|
|
194
|
+
return { exitCode: report.exitCode, report };
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
process.removeListener('SIGINT', onSigint);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=install-repo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-repo.js","sourceRoot":"","sources":["../../src/commands/install-repo.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,21 +4,296 @@
|
|
|
4
4
|
* 该命令假定调用前已执行完整构建流程。
|
|
5
5
|
*/
|
|
6
6
|
import { spawnSync } from 'node:child_process';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { gunzipSync } from 'node:zlib';
|
|
8
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, readdirSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
|
|
9
|
+
import { basename, dirname, isAbsolute, join, posix, relative, resolve } from 'node:path';
|
|
9
10
|
import chalk from 'chalk';
|
|
10
11
|
import { loadAllPlugins, loadMarketplaceMetadata, resolveRepoContext, } from '../utils/helpers.js';
|
|
12
|
+
import { loadOpenSpecAdapters } from '../adapters/openspec.js';
|
|
11
13
|
function assertExists(path, label) {
|
|
12
14
|
if (!existsSync(path)) {
|
|
13
15
|
throw new Error(`${label} 不存在,请先运行 npm run ci:local`);
|
|
14
16
|
}
|
|
15
17
|
}
|
|
16
|
-
function
|
|
18
|
+
function isGeneratedMetadataPath(path) {
|
|
19
|
+
const name = basename(path);
|
|
20
|
+
return (name === '.DS_Store' ||
|
|
21
|
+
name === '__pycache__' ||
|
|
22
|
+
name.startsWith('._') ||
|
|
23
|
+
/\.py[co]$/i.test(name));
|
|
24
|
+
}
|
|
25
|
+
function copyDirectory(from, to, options = {}) {
|
|
26
|
+
const sourceRoot = resolve(from);
|
|
17
27
|
cpSync(from, to, {
|
|
18
28
|
recursive: true,
|
|
19
|
-
|
|
29
|
+
verbatimSymlinks: true,
|
|
30
|
+
filter: (src) => {
|
|
31
|
+
if (isGeneratedMetadataPath(src))
|
|
32
|
+
return false;
|
|
33
|
+
if (options.excludePluginTests) {
|
|
34
|
+
const parts = relative(sourceRoot, resolve(src)).split(/[\\/]/);
|
|
35
|
+
if (parts.length >= 2 && parts[1] === 'tests')
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
},
|
|
20
40
|
});
|
|
21
41
|
}
|
|
42
|
+
function isInside(root, candidate) {
|
|
43
|
+
const value = relative(root, candidate);
|
|
44
|
+
return value === '' || (!value.startsWith('..') && !isAbsolute(value));
|
|
45
|
+
}
|
|
46
|
+
function assertPortableTree(root) {
|
|
47
|
+
const resolvedRoot = realpathSync(root);
|
|
48
|
+
const visit = (directory) => {
|
|
49
|
+
for (const name of readdirSync(directory)) {
|
|
50
|
+
const path = join(directory, name);
|
|
51
|
+
if (isGeneratedMetadataPath(path)) {
|
|
52
|
+
throw new Error(`发布包包含解释器或文件系统元数据: ${relative(root, path)}`);
|
|
53
|
+
}
|
|
54
|
+
const metadata = lstatSync(path);
|
|
55
|
+
if (metadata.isSymbolicLink()) {
|
|
56
|
+
const target = readlinkSync(path);
|
|
57
|
+
if (isAbsolute(target)) {
|
|
58
|
+
throw new Error(`发布包包含绝对符号链接: ${relative(root, path)}`);
|
|
59
|
+
}
|
|
60
|
+
const lexicalTarget = resolve(dirname(path), target);
|
|
61
|
+
if (!isInside(resolvedRoot, lexicalTarget)) {
|
|
62
|
+
throw new Error(`发布包包含越界符号链接: ${relative(root, path)}`);
|
|
63
|
+
}
|
|
64
|
+
let realTarget;
|
|
65
|
+
try {
|
|
66
|
+
realTarget = realpathSync(path);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
throw new Error(`发布包包含断链符号链接: ${relative(root, path)}`);
|
|
70
|
+
}
|
|
71
|
+
if (!isInside(resolvedRoot, realTarget)) {
|
|
72
|
+
throw new Error(`发布包符号链接最终目标越界: ${relative(root, path)}`);
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (metadata.isDirectory()) {
|
|
77
|
+
visit(path);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!metadata.isFile()) {
|
|
81
|
+
throw new Error(`发布包包含不支持的文件类型: ${relative(root, path)}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
visit(resolvedRoot);
|
|
86
|
+
}
|
|
87
|
+
function tarString(buffer, offset, length) {
|
|
88
|
+
const field = buffer.subarray(offset, offset + length);
|
|
89
|
+
const end = field.indexOf(0);
|
|
90
|
+
return field.subarray(0, end < 0 ? field.length : end).toString('utf-8').trim();
|
|
91
|
+
}
|
|
92
|
+
function tarNumber(buffer, offset, length) {
|
|
93
|
+
const field = buffer.subarray(offset, offset + length);
|
|
94
|
+
if (field.length && (field[0] & 0x80) !== 0) {
|
|
95
|
+
let value = BigInt(field[0] & 0x7f);
|
|
96
|
+
for (const byte of field.subarray(1))
|
|
97
|
+
value = (value << 8n) | BigInt(byte);
|
|
98
|
+
const numeric = Number(value);
|
|
99
|
+
if (!Number.isSafeInteger(numeric))
|
|
100
|
+
throw new Error('发布包 tar 数值字段超出安全范围');
|
|
101
|
+
return numeric;
|
|
102
|
+
}
|
|
103
|
+
const text = field.toString('ascii').replace(/\0.*$/s, '').trim();
|
|
104
|
+
if (!text)
|
|
105
|
+
return 0;
|
|
106
|
+
if (!/^[0-7]+$/.test(text))
|
|
107
|
+
throw new Error('发布包 tar 数值字段无效');
|
|
108
|
+
return Number.parseInt(text, 8);
|
|
109
|
+
}
|
|
110
|
+
function parsePax(payload) {
|
|
111
|
+
const values = {};
|
|
112
|
+
let offset = 0;
|
|
113
|
+
while (offset < payload.length) {
|
|
114
|
+
const space = payload.indexOf(0x20, offset);
|
|
115
|
+
if (space < 0)
|
|
116
|
+
throw new Error('发布包 PAX header 无效');
|
|
117
|
+
const lengthText = payload.subarray(offset, space).toString('ascii');
|
|
118
|
+
if (!/^[1-9][0-9]*$/.test(lengthText))
|
|
119
|
+
throw new Error('发布包 PAX 长度无效');
|
|
120
|
+
const length = Number.parseInt(lengthText, 10);
|
|
121
|
+
const end = offset + length;
|
|
122
|
+
if (!Number.isSafeInteger(length) || end > payload.length || end <= space + 1) {
|
|
123
|
+
throw new Error('发布包 PAX record 越界');
|
|
124
|
+
}
|
|
125
|
+
const record = payload.subarray(space + 1, end - 1).toString('utf-8');
|
|
126
|
+
const equals = record.indexOf('=');
|
|
127
|
+
if (equals > 0)
|
|
128
|
+
values[record.slice(0, equals)] = record.slice(equals + 1);
|
|
129
|
+
offset = end;
|
|
130
|
+
}
|
|
131
|
+
return values;
|
|
132
|
+
}
|
|
133
|
+
function safeArchivePath(value, label) {
|
|
134
|
+
if (!value ||
|
|
135
|
+
value.includes('\0') ||
|
|
136
|
+
value.includes('\\') ||
|
|
137
|
+
posix.isAbsolute(value) ||
|
|
138
|
+
/^[A-Za-z]:[\\/]/.test(value)) {
|
|
139
|
+
throw new Error(`发布包包含不安全${label}: ${value}`);
|
|
140
|
+
}
|
|
141
|
+
const parts = value.split('/').filter((part) => part !== '' && part !== '.');
|
|
142
|
+
if (parts.includes('..'))
|
|
143
|
+
throw new Error(`发布包包含不安全${label}: ${value}`);
|
|
144
|
+
const normalized = posix.normalize(value).replace(/^\.\//, '').replace(/\/$/, '');
|
|
145
|
+
if (!normalized || normalized === '..' || normalized.startsWith('../')) {
|
|
146
|
+
throw new Error(`发布包包含不安全${label}: ${value}`);
|
|
147
|
+
}
|
|
148
|
+
return normalized;
|
|
149
|
+
}
|
|
150
|
+
function safeArchiveLinkTarget(member, value) {
|
|
151
|
+
if (!value ||
|
|
152
|
+
value.includes('\0') ||
|
|
153
|
+
value.includes('\\') ||
|
|
154
|
+
posix.isAbsolute(value) ||
|
|
155
|
+
/^[A-Za-z]:[\\/]/.test(value)) {
|
|
156
|
+
throw new Error(`发布包包含不安全链接目标: ${member.path}`);
|
|
157
|
+
}
|
|
158
|
+
const target = member.type === 'symlink'
|
|
159
|
+
? posix.normalize(posix.join(posix.dirname(member.path), value))
|
|
160
|
+
: posix.normalize(value);
|
|
161
|
+
if (!target || target === '..' || target.startsWith('../')) {
|
|
162
|
+
throw new Error(`发布包包含越界链接目标: ${member.path}`);
|
|
163
|
+
}
|
|
164
|
+
return target.replace(/^\.\//, '').replace(/\/$/, '');
|
|
165
|
+
}
|
|
166
|
+
function parseTarMembers(tar) {
|
|
167
|
+
const members = [];
|
|
168
|
+
let offset = 0;
|
|
169
|
+
let pendingName;
|
|
170
|
+
let pendingLink;
|
|
171
|
+
let pendingPax = {};
|
|
172
|
+
let globalPax = {};
|
|
173
|
+
let ended = false;
|
|
174
|
+
while (offset + 512 <= tar.length) {
|
|
175
|
+
const header = tar.subarray(offset, offset + 512);
|
|
176
|
+
if (header.every((byte) => byte === 0)) {
|
|
177
|
+
const second = tar.subarray(offset + 512, offset + 1024);
|
|
178
|
+
if (second.length !== 512 || !second.every((byte) => byte === 0)) {
|
|
179
|
+
throw new Error('发布包 tar 缺少两个结束块');
|
|
180
|
+
}
|
|
181
|
+
if (!tar.subarray(offset + 1024).every((byte) => byte === 0)) {
|
|
182
|
+
throw new Error('发布包 tar 结束块后包含非零内容');
|
|
183
|
+
}
|
|
184
|
+
ended = true;
|
|
185
|
+
offset = tar.length;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
const storedChecksum = tarNumber(header, 148, 8);
|
|
189
|
+
let computedChecksum = 0;
|
|
190
|
+
for (let index = 0; index < header.length; index += 1) {
|
|
191
|
+
computedChecksum += index >= 148 && index < 156 ? 0x20 : header[index];
|
|
192
|
+
}
|
|
193
|
+
if (storedChecksum !== computedChecksum) {
|
|
194
|
+
throw new Error('发布包 tar header checksum 无效');
|
|
195
|
+
}
|
|
196
|
+
const magic = header.subarray(257, 263);
|
|
197
|
+
const version = header.subarray(263, 265).toString('ascii');
|
|
198
|
+
if (!magic.equals(Buffer.from([0x75, 0x73, 0x74, 0x61, 0x72, 0x00])) || version !== '00') {
|
|
199
|
+
throw new Error('发布包 tar magic/version 无效');
|
|
200
|
+
}
|
|
201
|
+
const size = tarNumber(header, 124, 12);
|
|
202
|
+
const typeFlag = String.fromCharCode(header[156] || 0);
|
|
203
|
+
const prefix = tarString(header, 345, 155);
|
|
204
|
+
const headerName = [prefix, tarString(header, 0, 100)].filter(Boolean).join('/');
|
|
205
|
+
const dataStart = offset + 512;
|
|
206
|
+
const dataEnd = dataStart + size;
|
|
207
|
+
if (dataEnd > tar.length)
|
|
208
|
+
throw new Error('发布包 tar member 内容越界');
|
|
209
|
+
const payload = tar.subarray(dataStart, dataEnd);
|
|
210
|
+
if (typeFlag === 'x' || typeFlag === 'g') {
|
|
211
|
+
const values = parsePax(payload);
|
|
212
|
+
if (typeFlag === 'g')
|
|
213
|
+
globalPax = { ...globalPax, ...values };
|
|
214
|
+
else
|
|
215
|
+
pendingPax = values;
|
|
216
|
+
}
|
|
217
|
+
else if (typeFlag === 'L') {
|
|
218
|
+
pendingName = payload.toString('utf-8').replace(/\0.*$/s, '').replace(/\n$/, '');
|
|
219
|
+
}
|
|
220
|
+
else if (typeFlag === 'K') {
|
|
221
|
+
pendingLink = payload.toString('utf-8').replace(/\0.*$/s, '').replace(/\n$/, '');
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
const attributes = { ...globalPax, ...pendingPax };
|
|
225
|
+
const memberPath = safeArchivePath(attributes.path || pendingName || headerName, '成员路径');
|
|
226
|
+
const type = typeFlag === '\0' || typeFlag === '0'
|
|
227
|
+
? 'file'
|
|
228
|
+
: typeFlag === '5'
|
|
229
|
+
? 'directory'
|
|
230
|
+
: typeFlag === '2'
|
|
231
|
+
? 'symlink'
|
|
232
|
+
: typeFlag === '1'
|
|
233
|
+
? 'hardlink'
|
|
234
|
+
: null;
|
|
235
|
+
if (type === null) {
|
|
236
|
+
throw new Error(`发布包包含不支持的 tar header type ${JSON.stringify(typeFlag)}: ${memberPath}`);
|
|
237
|
+
}
|
|
238
|
+
const rawLink = attributes.linkpath || pendingLink || tarString(header, 157, 100);
|
|
239
|
+
members.push({ path: memberPath, type, ...(rawLink ? { linkName: rawLink } : {}) });
|
|
240
|
+
pendingName = undefined;
|
|
241
|
+
pendingLink = undefined;
|
|
242
|
+
pendingPax = {};
|
|
243
|
+
}
|
|
244
|
+
offset = dataStart + Math.ceil(size / 512) * 512;
|
|
245
|
+
}
|
|
246
|
+
if (offset > tar.length || !ended)
|
|
247
|
+
throw new Error('发布包 tar 对齐或结束区无效');
|
|
248
|
+
return members;
|
|
249
|
+
}
|
|
250
|
+
export function assertPortableArchive(archivePath) {
|
|
251
|
+
let tar;
|
|
252
|
+
try {
|
|
253
|
+
tar = gunzipSync(readFileSync(archivePath));
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
throw new Error(`发布包压缩流无效: ${archivePath}`);
|
|
257
|
+
}
|
|
258
|
+
const sensitiveText = tar.toString('utf-8');
|
|
259
|
+
if (/\/(?:Users|home)\/[A-Za-z0-9._-]+\//.test(sensitiveText) ||
|
|
260
|
+
/\/(?:private\/tmp|private\/var\/folders|var\/folders)\//.test(sensitiveText) ||
|
|
261
|
+
/[A-Za-z]:\\Users\\[^\\\r\n\0]+\\/i.test(sensitiveText)) {
|
|
262
|
+
throw new Error('发布包包含本机绝对路径');
|
|
263
|
+
}
|
|
264
|
+
const members = parseTarMembers(tar);
|
|
265
|
+
const memberPaths = new Set();
|
|
266
|
+
for (const member of members) {
|
|
267
|
+
if (memberPaths.has(member.path)) {
|
|
268
|
+
throw new Error(`发布包包含重复成员路径: ${member.path}`);
|
|
269
|
+
}
|
|
270
|
+
memberPaths.add(member.path);
|
|
271
|
+
}
|
|
272
|
+
for (const member of members) {
|
|
273
|
+
const parts = member.path.split('/');
|
|
274
|
+
if (parts.some((part) => part.startsWith('._') || part === '__pycache__' || /\.py[co]$/i.test(part))) {
|
|
275
|
+
throw new Error(`发布包包含解释器或文件系统元数据: ${member.path}`);
|
|
276
|
+
}
|
|
277
|
+
if (member.type === 'symlink' || member.type === 'hardlink') {
|
|
278
|
+
const target = safeArchiveLinkTarget(member, member.linkName || '');
|
|
279
|
+
if (!memberPaths.has(target)) {
|
|
280
|
+
throw new Error(`发布包包含越界或断开的 ${member.type}: ${member.path}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function listComponentLabels(config) {
|
|
286
|
+
const labels = [];
|
|
287
|
+
if (config.components.skills?.length)
|
|
288
|
+
labels.push('Skill');
|
|
289
|
+
if (config.components.mcp?.length)
|
|
290
|
+
labels.push('MCP');
|
|
291
|
+
if (config.components.lsp?.length)
|
|
292
|
+
labels.push('LSP');
|
|
293
|
+
if (config.components.hooks?.length)
|
|
294
|
+
labels.push('Hook');
|
|
295
|
+
return labels;
|
|
296
|
+
}
|
|
22
297
|
function writeReleaseReadme(releaseDir, marketplaceName, pluginCount) {
|
|
23
298
|
const readme = `# ${marketplaceName} 本地发布包
|
|
24
299
|
|
|
@@ -26,9 +301,15 @@ function writeReleaseReadme(releaseDir, marketplaceName, pluginCount) {
|
|
|
26
301
|
|
|
27
302
|
## 内容
|
|
28
303
|
|
|
29
|
-
- \`plugins/\`:
|
|
304
|
+
- \`plugins/\`: 每个目录都是带根 \`plugin.json\` 的 Agent Plugins 1.0 包,同时保留 Claude Code / Codex manifest
|
|
305
|
+
- 插件源码中的顶层 \`tests/\` 不进入消费者发布包
|
|
306
|
+
- 插件内 \`mcp.json\`: Agent Plugins 1.0 MCP 配置(仅 MCP 插件)
|
|
307
|
+
- 插件内 \`.lsp.json\`: Claude Code LSP server 配置(仅 LSP 插件)
|
|
30
308
|
- \`.claude-plugin/marketplace.json\`: Claude Code marketplace manifest
|
|
309
|
+
- \`.github/plugin/marketplace.json\`: GitHub Copilot / VS Code marketplace manifest
|
|
31
310
|
- \`.agents/plugins/marketplace.json\`: Codex repo marketplace manifest
|
|
311
|
+
- \`.cursor-plugin/marketplace.json\`: Cursor team marketplace manifest(仓库导入要求 GitHub/GitHub Enterprise)
|
|
312
|
+
- \`.openspec-plugin/source-adapters.json\`: OpenSpec source adapter sidecar 索引
|
|
32
313
|
- \`marketplace.json\`: 根目录兼容镜像
|
|
33
314
|
- \`marketplace.yaml\`: marketplace 元数据
|
|
34
315
|
- \`release-manifest.json\`: 本次发布包的元信息
|
|
@@ -46,10 +327,12 @@ function createArchive(distDir, marketplaceName) {
|
|
|
46
327
|
const archivePath = join(distDir, archiveName);
|
|
47
328
|
const result = spawnSync('tar', ['-czf', archivePath, '-C', distDir, 'release'], {
|
|
48
329
|
stdio: 'inherit',
|
|
330
|
+
env: { ...process.env, COPYFILE_DISABLE: '1' },
|
|
49
331
|
});
|
|
50
332
|
if (result.status !== 0) {
|
|
51
333
|
throw new Error(`发布包压缩失败: ${archiveName}`);
|
|
52
334
|
}
|
|
335
|
+
assertPortableArchive(archivePath);
|
|
53
336
|
return archivePath;
|
|
54
337
|
}
|
|
55
338
|
export function runReleaseLocal(options = {}) {
|
|
@@ -59,25 +342,39 @@ export function runReleaseLocal(options = {}) {
|
|
|
59
342
|
const plugins = loadAllPlugins(context);
|
|
60
343
|
const releaseDir = join(context.distDir, 'release');
|
|
61
344
|
assertExists(join(context.rootDir, '.claude-plugin/marketplace.json'), 'Claude marketplace manifest');
|
|
345
|
+
assertExists(join(context.rootDir, '.github/plugin/marketplace.json'), 'GitHub Copilot marketplace manifest');
|
|
62
346
|
assertExists(join(context.rootDir, '.agents/plugins/marketplace.json'), 'Codex marketplace manifest');
|
|
347
|
+
assertExists(join(context.rootDir, '.cursor-plugin/marketplace.json'), 'Cursor marketplace manifest');
|
|
348
|
+
assertExists(join(context.rootDir, '.openspec-plugin/source-adapters.json'), 'OpenSpec source adapter sidecar 索引');
|
|
63
349
|
assertExists(join(context.rootDir, 'marketplace.json'), 'marketplace.json');
|
|
64
350
|
assertExists(context.marketplaceYamlPath, 'marketplace.yaml');
|
|
65
351
|
assertExists(join(context.pluginsDir, 'CATALOG.md'), 'plugins/CATALOG.md');
|
|
66
352
|
for (const { name, dir, config } of plugins) {
|
|
353
|
+
assertExists(join(dir, 'plugin.json'), `${name} Agent Plugins manifest`);
|
|
67
354
|
assertExists(join(dir, '.claude-plugin/plugin.json'), `${name} Claude manifest`);
|
|
68
355
|
assertExists(join(dir, '.codex-plugin/plugin.json'), `${name} Codex manifest`);
|
|
69
356
|
if (config.components.mcp?.length) {
|
|
357
|
+
assertExists(join(dir, 'mcp.json'), `${name} Agent Plugins MCP 配置`);
|
|
70
358
|
assertExists(join(dir, '.mcp.json'), `${name} MCP 配置`);
|
|
71
359
|
}
|
|
360
|
+
if (config.components.lsp?.length) {
|
|
361
|
+
assertExists(join(dir, '.lsp.json'), `${name} LSP 配置`);
|
|
362
|
+
}
|
|
72
363
|
if (config.components.hooks?.length) {
|
|
73
364
|
assertExists(join(dir, 'hooks/hooks.json'), `${name} hooks 配置`);
|
|
74
365
|
}
|
|
366
|
+
if (loadOpenSpecAdapters(dir).length) {
|
|
367
|
+
assertExists(join(dir, '.openspec-plugin/plugin.json'), `${name} OpenSpec sidecar`);
|
|
368
|
+
}
|
|
75
369
|
}
|
|
76
370
|
rmSync(releaseDir, { recursive: true, force: true });
|
|
77
371
|
mkdirSync(releaseDir, { recursive: true });
|
|
78
|
-
copyDirectory(context.pluginsDir, join(releaseDir, 'plugins'));
|
|
372
|
+
copyDirectory(context.pluginsDir, join(releaseDir, 'plugins'), { excludePluginTests: true });
|
|
79
373
|
copyDirectory(join(context.rootDir, '.claude-plugin'), join(releaseDir, '.claude-plugin'));
|
|
80
|
-
copyDirectory(join(context.rootDir, '.
|
|
374
|
+
copyDirectory(join(context.rootDir, '.github', 'plugin'), join(releaseDir, '.github', 'plugin'));
|
|
375
|
+
copyDirectory(join(context.rootDir, '.agents', 'plugins'), join(releaseDir, '.agents', 'plugins'));
|
|
376
|
+
copyDirectory(join(context.rootDir, '.cursor-plugin'), join(releaseDir, '.cursor-plugin'));
|
|
377
|
+
copyDirectory(join(context.rootDir, '.openspec-plugin'), join(releaseDir, '.openspec-plugin'));
|
|
81
378
|
cpSync(join(context.rootDir, 'marketplace.json'), join(releaseDir, 'marketplace.json'));
|
|
82
379
|
cpSync(context.marketplaceYamlPath, join(releaseDir, 'marketplace.yaml'));
|
|
83
380
|
const manifest = {
|
|
@@ -87,17 +384,23 @@ export function runReleaseLocal(options = {}) {
|
|
|
87
384
|
name,
|
|
88
385
|
version: config.version,
|
|
89
386
|
category: config.category || 'general',
|
|
387
|
+
components: listComponentLabels(config),
|
|
90
388
|
})),
|
|
91
389
|
artifacts: {
|
|
92
390
|
plugins: 'plugins/',
|
|
391
|
+
agentPluginRoots: 'plugins/*/',
|
|
93
392
|
claudeMarketplace: '.claude-plugin/marketplace.json',
|
|
393
|
+
copilotMarketplace: '.github/plugin/marketplace.json',
|
|
94
394
|
codexMarketplace: '.agents/plugins/marketplace.json',
|
|
395
|
+
cursorMarketplace: '.cursor-plugin/marketplace.json',
|
|
396
|
+
openSpecSourceAdapters: '.openspec-plugin/source-adapters.json',
|
|
95
397
|
marketplaceMirror: 'marketplace.json',
|
|
96
398
|
marketplaceMetadata: 'marketplace.yaml',
|
|
97
399
|
},
|
|
98
400
|
};
|
|
99
401
|
writeFileSync(join(releaseDir, 'release-manifest.json'), JSON.stringify(manifest, null, 2) + '\n');
|
|
100
402
|
writeReleaseReadme(releaseDir, marketplace.name, plugins.length);
|
|
403
|
+
assertPortableTree(releaseDir);
|
|
101
404
|
const archivePath = createArchive(context.distDir, marketplace.name);
|
|
102
405
|
console.log(chalk.green(` ✓ dist/release (${plugins.length} 个插件)`));
|
|
103
406
|
console.log(chalk.green(` ✓ ${archivePath}`));
|