@lark-apaas/fullstack-rspack-preset 1.0.63 → 1.0.64-alpha.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.
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** 交付 Skill 正文及全部附件;先完整校验,再逐文件原子替换。 */
|
|
2
2
|
export declare function syncMcpSkill(rootDir: string): void;
|
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.syncMcpSkill = syncMcpSkill;
|
|
37
37
|
const fs = __importStar(require("node:fs"));
|
|
38
38
|
const path = __importStar(require("node:path"));
|
|
39
|
-
/**
|
|
39
|
+
/** 交付 Skill 正文及全部附件;先完整校验,再逐文件原子替换。 */
|
|
40
40
|
function syncMcpSkill(rootDir) {
|
|
41
41
|
const root = fs.realpathSync(rootDir);
|
|
42
42
|
const rootStat = fs.statSync(root);
|
|
@@ -49,7 +49,7 @@ function syncMcpSkill(rootDir) {
|
|
|
49
49
|
stat = fs.lstatSync(current);
|
|
50
50
|
}
|
|
51
51
|
catch (error) {
|
|
52
|
-
if (error.code
|
|
52
|
+
if (['ENOENT', 'ENOTDIR'].includes(error.code ?? ''))
|
|
53
53
|
continue;
|
|
54
54
|
throw error;
|
|
55
55
|
}
|
|
@@ -66,6 +66,19 @@ function syncMcpSkill(rootDir) {
|
|
|
66
66
|
};
|
|
67
67
|
const list = (base) => {
|
|
68
68
|
const files = [];
|
|
69
|
+
const walk = (relative) => {
|
|
70
|
+
const file = checkedPath(relative);
|
|
71
|
+
const stat = fs.lstatSync(file);
|
|
72
|
+
if (stat.isDirectory()) {
|
|
73
|
+
for (const name of fs.readdirSync(file).sort())
|
|
74
|
+
walk(`${relative}/${name}`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
if (!stat.isFile())
|
|
78
|
+
throw new Error(`MCP Skill 必须是普通文件:${relative}`);
|
|
79
|
+
files.push(relative);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
69
82
|
const skills = checkedPath(`${base}/skills`);
|
|
70
83
|
if (!fs.existsSync(skills))
|
|
71
84
|
return files;
|
|
@@ -74,24 +87,28 @@ function syncMcpSkill(rootDir) {
|
|
|
74
87
|
if (!fs.statSync(directory).isDirectory())
|
|
75
88
|
continue;
|
|
76
89
|
const relative = `${base}/skills/${name}/SKILL.md`;
|
|
77
|
-
if (fs.existsSync(checkedPath(relative))) {
|
|
90
|
+
if (base !== 'server/mcp' || fs.existsSync(checkedPath(relative))) {
|
|
91
|
+
if (fs.existsSync(checkedPath(relative)) &&
|
|
92
|
+
!fs.statSync(checkedPath(relative)).isFile())
|
|
93
|
+
throw new Error(`MCP Skill 必须是普通文件:${relative}`);
|
|
78
94
|
if (base === 'server/mcp' &&
|
|
79
95
|
(!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64))
|
|
80
96
|
throw new Error('MCP Skill 目录名须为1–64位小写字母、数字及单连字符,不能首尾连字符');
|
|
81
|
-
|
|
97
|
+
walk(`${base}/skills/${name}`);
|
|
82
98
|
}
|
|
83
99
|
}
|
|
84
100
|
return files;
|
|
85
101
|
};
|
|
86
102
|
const sourceBase = 'server/mcp';
|
|
87
|
-
//
|
|
103
|
+
// skills 目录由原始资源完整覆盖;不删除相邻的工具编译产物。
|
|
88
104
|
const targetBase = 'dist/server/mcp';
|
|
89
105
|
const sources = list(sourceBase);
|
|
90
|
-
if (sources.length > 100)
|
|
106
|
+
if (sources.filter(file => file.split('/').length === 5 && file.endsWith('/SKILL.md')).length > 100)
|
|
91
107
|
throw new Error('MCP Skills 不能超过 100 文件');
|
|
92
108
|
const outputs = list(targetBase);
|
|
93
109
|
const content = new Map();
|
|
94
110
|
let total = 0;
|
|
111
|
+
const sizes = new Map();
|
|
95
112
|
for (const relative of sources) {
|
|
96
113
|
const file = checkedPath(relative);
|
|
97
114
|
// 先拒绝 FIFO/设备,避免以只读方式打开时阻塞。打开后仍用 fstat 再检查。
|
|
@@ -124,11 +141,14 @@ function syncMcpSkill(rootDir) {
|
|
|
124
141
|
if (!stat.isFile())
|
|
125
142
|
throw new Error(`MCP Skill 必须是普通文件:${relative}`);
|
|
126
143
|
assertOpenedFile();
|
|
127
|
-
const
|
|
144
|
+
const isEntry = relative.split('/').length === 5 && relative.endsWith('/SKILL.md');
|
|
145
|
+
const limit = (isEntry ? 1 : 16) * 1024 * 1024;
|
|
128
146
|
if (stat.size > limit)
|
|
129
|
-
throw new Error(
|
|
147
|
+
throw new Error(isEntry
|
|
148
|
+
? 'MCP Skill 不能超过 1 MiB'
|
|
149
|
+
: 'MCP Skill 总大小不能超过 16 MiB');
|
|
130
150
|
// 有界读取,也覆盖 stat 后文件继续增长的情况。
|
|
131
|
-
const buffer = Buffer.alloc(limit + 1);
|
|
151
|
+
const buffer = Buffer.alloc(isEntry ? limit + 1 : Math.min(limit + 1, stat.size + 1));
|
|
132
152
|
let length = 0;
|
|
133
153
|
while (length < buffer.length) {
|
|
134
154
|
const count = fs.readSync(fd, buffer, length, buffer.length - length, null);
|
|
@@ -138,12 +158,27 @@ function syncMcpSkill(rootDir) {
|
|
|
138
158
|
}
|
|
139
159
|
assertOpenedFile();
|
|
140
160
|
if (length > limit)
|
|
141
|
-
throw new Error(
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
throw new Error(isEntry
|
|
162
|
+
? 'MCP Skill 不能超过 1 MiB'
|
|
163
|
+
: 'MCP Skill 总大小不能超过 16 MiB');
|
|
164
|
+
if (!isEntry && length > stat.size)
|
|
165
|
+
throw new Error('MCP Skill 附件读取期间发生变化,请重试');
|
|
166
|
+
const name = relative.split('/')[3];
|
|
167
|
+
const size = sizes.get(name) ?? { files: 0, bytes: 0 };
|
|
168
|
+
size.files++;
|
|
169
|
+
size.bytes += length;
|
|
170
|
+
if (size.files > 512)
|
|
171
|
+
throw new Error('MCP Skill 最多包含 512 个文件');
|
|
172
|
+
if (size.bytes > 16 * 1024 * 1024)
|
|
173
|
+
throw new Error('MCP Skill 总大小不能超过 16 MiB');
|
|
174
|
+
sizes.set(name, size);
|
|
175
|
+
if (isEntry)
|
|
176
|
+
total += length;
|
|
177
|
+
if (total > 8 * 1024 * 1024)
|
|
144
178
|
throw new Error('MCP Skills 总大小不能超过 8 MiB');
|
|
145
179
|
const bytes = buffer.subarray(0, length);
|
|
146
|
-
|
|
180
|
+
if (isEntry)
|
|
181
|
+
new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
147
182
|
const target = relative.replace(sourceBase, targetBase);
|
|
148
183
|
checkedPath(target);
|
|
149
184
|
content.set(target, Buffer.from(bytes));
|
|
@@ -157,8 +192,24 @@ function syncMcpSkill(rootDir) {
|
|
|
157
192
|
if (!fs.statSync(checkedPath(relative)).isFile())
|
|
158
193
|
throw new Error(`MCP Skill 产物必须是普通文件:${relative}`);
|
|
159
194
|
}
|
|
195
|
+
// 校验完成后先清理已移除文件,支持附件在文件和目录之间转换。
|
|
196
|
+
for (const relative of outputs) {
|
|
197
|
+
if (content.has(relative))
|
|
198
|
+
continue;
|
|
199
|
+
const target = checkedPath(relative);
|
|
200
|
+
fs.rmSync(target);
|
|
201
|
+
let directory = path.dirname(target);
|
|
202
|
+
const boundary = checkedPath(`${targetBase}/skills`);
|
|
203
|
+
while (directory.startsWith(`${boundary}${path.sep}`) &&
|
|
204
|
+
fs.readdirSync(directory).length === 0) {
|
|
205
|
+
fs.rmdirSync(directory);
|
|
206
|
+
directory = path.dirname(directory);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
160
209
|
for (const [relative, bytes] of content) {
|
|
161
210
|
const target = checkedPath(relative);
|
|
211
|
+
if (fs.existsSync(target) && fs.statSync(target).isDirectory())
|
|
212
|
+
fs.rmSync(target, { recursive: true });
|
|
162
213
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
163
214
|
const temporary = fs.mkdtempSync(path.join(path.dirname(target), '.skill-'));
|
|
164
215
|
try {
|
|
@@ -170,16 +221,6 @@ function syncMcpSkill(rootDir) {
|
|
|
170
221
|
fs.rmSync(temporary, { recursive: true, force: true });
|
|
171
222
|
}
|
|
172
223
|
}
|
|
173
|
-
for (const relative of outputs) {
|
|
174
|
-
if (content.has(relative))
|
|
175
|
-
continue;
|
|
176
|
-
const target = checkedPath(relative);
|
|
177
|
-
fs.rmSync(target);
|
|
178
|
-
if (relative.startsWith(`${targetBase}/skills/`) &&
|
|
179
|
-
fs.readdirSync(path.dirname(target)).length === 0) {
|
|
180
|
-
fs.rmdirSync(path.dirname(target));
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
224
|
const skills = checkedPath(`${targetBase}/skills`);
|
|
184
225
|
if (fs.existsSync(skills) && fs.readdirSync(skills).length === 0)
|
|
185
226
|
fs.rmdirSync(skills);
|