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 cdf8da193..057c29d50 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.42", + "version": "2026-08-26.44", "skills": [ { "name": "agc-unity-editor", @@ -204,7 +204,7 @@ "scripts/validate.mjs", "scripts/vite.config.xhs-minitool.mjs" ], - "sha256": "3ed83d2d8bdfbeaefe9e22c36687f146c20ee2b971c7a4f73cf8bdf4bc8dc69c" + "sha256": "f7d1f7e05863b45e307fe9ef6b281513774b130af2631cc2001b55f40b752fc9" } ] } 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 6563c7c40..18bc23a25 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 @@ -6,25 +6,23 @@ metadata: version: "1.7.0" --- -1. 新增 `scripts/vite.config.xhs-minitool.mjs` (示例)作为vite构建配置, 这个配置保证了产物符合小红书小工具的规范. +# 目标: +* 实现打包脚本 `build:xhs-minitool` 支持vite构建, validate, pack, 产出zip到固定文件: .export/xhs-minitool.zip + 1. 复制(并按实际情况修改) `scripts/vite.config.xhs-minitool.mjs` 作为vite构建配置, 这个配置保证了产物符合小红书小工具的规范. + 2. 使用 `scripts/validate.mjs` 验证上述配置构建产物目录, 这个脚本实现了一些硬性检查, 此外有一些手动检查项: + `references/manual-checks.md` + 3. 使用 `scripts/pack.mjs` 打包成zip + 4. 跑通流程后请把需要的脚本配置复制到项目里(以免依赖skill), 并形成最终的打包脚本 -2. 使用 `scripts/validate.mjs` 构建产物目录, 这个脚本实现了一些硬性检查, 此外有一些手动检查项: - `references/manual-checks.md` +* 不干扰正常的web构建 -3. 使用 `scripts/pack.mjs` 打包成zip - -## 被 AGC 导出面板调用时 - -宿主只按三条硬契约校验, 接入的项目 MUST 逐条满足: - -- npm 包 (声明 `package.json` 的那层目录) MUST 声明脚本 `build:xhs-minitool`. -- 该脚本跑完 MUST 在项目根留下非空的 `.export/xhs-minitool.zip`; 落点用 `pack.mjs --zip-out` 显式指定, 不要依赖默认文件名 (相对路径的解析规则见 `node pack.mjs --help`). -- `pack.mjs --out-dir` MUST 只指向 vite 构建输出目录: 它会就地删掉该目录里白名单之外的扩展名文件, 指向源码目录会删掉用户代码. - -把示例脚本拷贝到 `<项目根>/.export/` 下再改 (拷贝是项目自己的代码, 允许改路径与入口); -拷贝自身的位置就是项目根: `dirname(import.meta.url)/..`. +# 假设和默认项: +* vite项目目录: games/ ,各个工具(包括示例vite配置)的默认参数参照这个条件给出, 对于非标准的目录结构需要给出显式的参数来适应 +# 一些情况: +* 打包大小限制, 需要精简游戏内容/删减资源/压缩素材 请和用户讨论 * 如果使用了外部能力, 参考platform-abstract skill对项目先重构 +* 小红书的条件可能有变化, 请以用户反馈为准, 并调整本地的构建脚本 以下是其他参考文档, 非必要不读 diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.pack.test.mjs b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.pack.test.mjs index 2c7f51d8a..2c41324de 100644 --- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.pack.test.mjs +++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/.pack.test.mjs @@ -1,11 +1,95 @@ #!/usr/bin/env node import assert from 'node:assert/strict'; -import test from 'node:test'; +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { spawnSync } from 'node:child_process'; +import test, { after } from 'node:test'; +import { fileURLToPath } from 'node:url'; -import { auditZipFile } from './pack.mjs'; +import { auditZipFile, packMinitool } from './pack.mjs'; +import { findUnsupportedFiles } from './validate.mjs'; + +const PACK = fileURLToPath(new URL('./pack.mjs', import.meta.url)); +const BASE = mkdtempSync(join(tmpdir(), 'xhs-pack-tests-')); + +after(() => { + rmSync(BASE, { recursive: true, force: true }); +}); + +const VALID_HTML = '
' + + '' + + '' + + ''; + +/** 造一个最小的 vite 构建产物目录;extra 用来放白名单外的文件。 */ +function writeBuiltDir(name, extra = {}) { + const root = join(BASE, name); + mkdirSync(root, { recursive: true }); + writeFileSync(join(root, 'index.html'), VALID_HTML, 'utf8'); + writeFileSync(join(root, 'app.js'), "console.log('ok');\n", 'utf8'); + for (const [relativePath, content] of Object.entries(extra)) { + const path = join(root, relativePath); + mkdirSync(dirname(path), { recursive: true }); + writeFileSync(path, content, 'utf8'); + } + return root; +} test('zip budget is classified', () => { assert.deepEqual(auditZipFile('tool.zip', 1024), []); assert.equal(auditZipFile('tool.zip', 3 * 1024 * 1024)[0].code, 'ZIP_OVER_RECOMMENDED'); assert.equal(auditZipFile('tool.zip', 11 * 1024 * 1024)[0].code, 'ZIP_OVER_HARD_LIMIT'); }); + +test('findUnsupportedFiles 只报白名单外的扩展名', () => { + const root = writeBuiltDir('precheck', { + 'notes.md': 'readme\n', + 'src/config.ts': 'export const x = 1;\n', + }); + const found = findUnsupportedFiles(root).map((item) => item.path).sort(); + assert.deepEqual(found, ['notes.md', 'src/config.ts']); +}); + +test('存在不支持文件时打包整体失败,且不删除任何文件', () => { + const root = writeBuiltDir('no-delete', { + 'notes.md': 'readme\n', + 'style.scss': 'body { color: red; }\n', + }); + assert.throws( + () => packMinitool({ + cwd: BASE, + viteBuiltDir: 'no-delete', + zipOut: 'no-delete-out/xhs-minitool.zip', + skipValidate: true, + }), + /没有删除任何文件/, + ); + assert.ok(existsSync(join(root, 'notes.md')), 'notes.md 被删了'); + assert.ok(existsSync(join(root, 'style.scss')), 'style.scss 被删了'); + assert.equal(existsSync(join(BASE, 'no-delete-out')), false, '失败时不应写出 zip'); +}); + +test('干净产物目录仍能正常打包', () => { + writeBuiltDir('clean'); + const zipOut = join(BASE, 'clean-out', 'xhs-minitool.zip'); + const code = packMinitool({ + cwd: BASE, + viteBuiltDir: 'clean', + zipOut, + skipValidate: true, + }); + assert.equal(code, 0); + assert.ok(existsSync(zipOut), 'zip 未生成'); +}); + +test('CLI 拒绝旧名 --out-dir,不再静默退回默认目录', () => { + const dir = writeBuiltDir('cli'); + const result = spawnSync( + process.execPath, + [PACK, '--vite-built-dir', dir, '--out-dir', dir, '--skip-validate'], + { cwd: BASE, encoding: 'utf8' }, + ); + assert.equal(result.status, 1); + assert.match(result.stderr, /--vite-built-dir/); +}); diff --git a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/pack.mjs b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/pack.mjs index 91e4e5989..80d3f0e51 100644 --- a/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/pack.mjs +++ b/apps/ai-game-creator-shell/src-tauri/resources/agc-skills/vite-export-xhs-minitool/scripts/pack.mjs @@ -18,65 +18,51 @@ import { statSync, writeFileSync, } from 'node:fs'; -import { basename, dirname, extname, join, relative, resolve, sep } from 'node:path'; +import { basename, dirname, join, relative, resolve, sep } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { deflateRawSync } from 'node:zlib'; -import { runCli } from './validate.mjs'; +import { findUnsupportedFiles, runCli } from './validate.mjs'; const MIB = 1024 * 1024; const ZIP_HARD_LIMIT = 10 * MIB; const ZIP_RECOMMENDED = 2 * MIB; -const ALLOWED = new Set([ - '.html', - '.css', - '.js', - '.png', - '.jpg', - '.jpeg', - '.gif', - '.webp', - '.svg', - '.woff', - '.woff2', - '.json', -]); - const FORBIDDEN_ENTRY = /(^|\/)(?:node_modules|\.git)\/|(^|\/)\.DS_Store$|\.map$|(^|\/)(?:vite|webpack|rollup)\.config\.[^/]+$/i; -const USAGE = `用法:node pack.mjs [--out-dir