diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json
index 8b90908b4..9ec4a20a4 100644
--- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json
+++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/manifest.json
@@ -1,6 +1,6 @@
{
"schemaVersion": "agc-skill-pack.v1",
- "version": "2026-08-26.51",
+ "version": "2026-08-26.54",
"skills": [
{
"name": "agc-unity-editor",
@@ -204,7 +204,7 @@
"scripts/validate.mjs",
"scripts/vite.config.xhs-minitool.mjs"
],
- "sha256": "25e302b74418c81511f95684f4752f227018d89ab1a3bf4e53a73d1516740f19"
+ "sha256": "f775b0fb628341fed30aa4149c82311a640671d6cd81f78d9d80c2e5c8e47912"
}
]
}
diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/SKILL.md b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/SKILL.md
index c693ee429..f9dd9a1ff 100644
--- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/SKILL.md
+++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/SKILL.md
@@ -8,10 +8,10 @@ metadata:
# 目标:
* 实现打包脚本 `build:xhs-minitool` 支持vite构建, validate, pack, 产出zip到固定文件: .export/xhs-minitool.zip
- 1. 复制(并按实际情况修改) `scripts/vite.config.xhs-minitool.mjs` 作为vite构建配置, 这个配置保证了产物符合小红书小工具的规范. 构建分两段: `minitoolBaseConfig` 管编译约束, 配置内置的 `xhsMinitoolArtifactPlugin` 在 build 收尾时把 dist 整理成可直接打 zip 的形状(相对路径 / 去 module 痕迹 / 脚本移到 body 末尾 / 清空空目录 / 兜底 icon). **不要**在 pack 或别处重复这些收尾.
+ 1. 复制(并按实际情况修改) `scripts/vite.config.xhs-minitool.mjs` 作为vite构建配置, 这个配置保证了产物符合小红书小工具的规范.
2. 使用 `scripts/validate.mjs` 验证上述配置构建产物目录, 这个脚本实现了一些硬性检查, 此外有一些手动检查项:
`references/manual-checks.md`
- 3. 使用 `scripts/pack.mjs` 打包成zip; 它只读 dist 打 zip, 不改写 dist
+ 3. 使用 `scripts/pack.mjs` 打包成zip
4. 跑通流程后请把需要的脚本配置复制到项目里(以免依赖skill), 并形成最终的打包脚本
* 不干扰正常的web构建
@@ -23,12 +23,11 @@ metadata:
# 脚本参数:
| 脚本 | 参数 |
|----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|
-| `scripts/vite.config.xhs-minitool.mjs` | `build.outDir`(与打包时的 `--vite-built-dir` 必须是同一个目录); `defineMinitoolConfig(overrides)`(项目已有 vite.config 时用它合并); `xhsMinitoolArtifactPlugin()`(已有 vite.config 时单独加插件做收尾) |
+| `scripts/vite.config.xhs-minitool.mjs` | `build.outDir`(与打包时的 `--vite-built-dir` 必须是同一个目录); `defineMinitoolConfig(overrides)`(项目已有 vite.config 时用它合并) |
| `scripts/validate.mjs` | `[project]`、`--json`、`--strict` |
| `scripts/pack.mjs` | `--vite-built-dir
`、`--zip-out ` |
# 一些情况:
-* 项目已有自己的 vite.config 时, 用 `defineMinitoolConfig(overrides)` 合并, 或至少把 `xhsMinitoolArtifactPlugin()` 加进 `plugins`; 漏掉插件会让 dist 保留 `type="module"` / 绝对路径, validate 会直接报错.
* 打包大小限制, 需要精简游戏内容/删减资源/压缩素材 请和用户讨论
* 如果使用了外部能力, 参考platform-abstract skill对项目先重构
* 小红书的条件可能有变化, 请以用户反馈为准, 并调整本地的构建脚本
diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.vite.config.xhs-minitool.test.mjs b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.vite.config.xhs-minitool.test.mjs
index ce88ab25d..1ebc6451d 100644
--- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.vite.config.xhs-minitool.test.mjs
+++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.vite.config.xhs-minitool.test.mjs
@@ -140,6 +140,22 @@ test('构建失败时不收尾:不覆盖真实报错,也不留下半成品 d
assert.equal(existsSync(join(outDir, 'icons')), false, '失败的构建不该被插件写入 icon');
});
+test('defineMinitoolConfig 始终追加收尾插件,且只跑一次', () => {
+ const withExtra = defineMinitoolConfig({ plugins: [{ name: 'extra' }] });
+ const names = withExtra.plugins.map((plugin) => plugin.name);
+ assert.ok(names.includes('xhs-minitool-artifact'), `缺少收尾插件: ${names}`);
+ assert.equal(
+ names.filter((name) => name === 'xhs-minitool-artifact').length,
+ 1,
+ `收尾插件重复: ${names}`,
+ );
+ assert.ok(names.includes('extra'), 'overrides 的插件丢了');
+ assert.equal(names.at(-1), 'xhs-minitool-artifact', `收尾插件不在最后: ${names}`);
+
+ const noPlugins = defineMinitoolConfig({ plugins: [] });
+ assert.deepEqual(noPlugins.plugins.map((plugin) => plugin.name), ['xhs-minitool-artifact']);
+});
+
test('pruneEmptyDirs 只删空目录,保留有内容的目录', () => {
const root = join(BASE, 'prune');
mkdirSync(join(root, 'empty', 'nested'), { recursive: true });
diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/vite.config.xhs-minitool.mjs b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/vite.config.xhs-minitool.mjs
index 037b5b5a2..6284dc01b 100644
--- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/vite.config.xhs-minitool.mjs
+++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/vite.config.xhs-minitool.mjs
@@ -42,6 +42,9 @@ export const MINITOOL_CSS_TARGET = ['chrome61'];
/** 小红书要求包内自带的小工具 icon;publicDir 被关掉时由插件兜底复制。 */
const FALLBACK_ICONS = ['icon-192.svg', 'icon-512.svg'];
+/** 收尾插件名;defineMinitoolConfig 按它去重,保证插件只跑一次。 */
+const ARTIFACT_PLUGIN_NAME = 'xhs-minitool-artifact';
+
/**
* 把 vite 生成的绝对资源路径改为相对路径,并抹掉 module / 预加载痕迹。
*
@@ -132,7 +135,7 @@ export function xhsMinitoolArtifactPlugin() {
let buildFailed = false;
return {
- name: 'xhs-minitool-artifact',
+ name: ARTIFACT_PLUGIN_NAME,
apply: 'build',
enforce: 'post',
configResolved(config) {
@@ -188,11 +191,22 @@ export const minitoolBaseConfig = {
};
/**
+ * 合并项目配置,并保证收尾插件一定在 `plugins` 里。
+ *
+ * `mergeConfig` 对数组是拼接,正常传 `{ plugins: [...] }` 不会丢掉 base 的插件;但调用方
+ * 若先展开 `minitoolBaseConfig` 再覆盖 `plugins`,插件就被挤掉了。这里按 name 去重后
+ * 追加一个新实例:无论 overrides 怎么给,插件都在最后且只跑一次。
+ *
* @param {import('vite').UserConfig} [overrides]
* @returns {import('vite').UserConfig}
*/
export function defineMinitoolConfig(overrides = {}) {
- return mergeConfig(defineConfig(minitoolBaseConfig), overrides);
+ const merged = mergeConfig(defineConfig(minitoolBaseConfig), overrides);
+ merged.plugins = [
+ ...(merged.plugins ?? []).filter((plugin) => plugin?.name !== ARTIFACT_PLUGIN_NAME),
+ xhsMinitoolArtifactPlugin(),
+ ];
+ return merged;
}
export default defineMinitoolConfig();