@lark-apaas/fullstack-rspack-preset 1.0.63-beta.0 → 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
- /** 仅交付固定层级的 SKILL.md;先完整校验,再逐文件原子替换。 */
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
- /** 仅交付固定层级的 SKILL.md;先完整校验,再逐文件原子替换。 */
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 === 'ENOENT')
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
- files.push(relative);
97
+ walk(`${base}/skills/${name}`);
82
98
  }
83
99
  }
84
100
  return files;
85
101
  };
86
102
  const sourceBase = 'server/mcp';
87
- // 只同步 SKILL.md,不删除同目录的工具编译产物。
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 limit = 1024 * 1024;
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('MCP Skill 不能超过 1 MiB');
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('MCP Skill 不能超过 1 MiB');
142
- total += length;
143
- if (total > 8 * limit)
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
- new TextDecoder('utf-8', { fatal: true }).decode(bytes);
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-rspack-preset",
3
- "version": "1.0.63-beta.0",
3
+ "version": "1.0.64-alpha.0",
4
4
  "files": [
5
5
  "lib",
6
6
  "patches",