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();