mirror of
https://github.com/motajs/template.git
synced 2026-10-21 14:50:26 +08:00
docs(09-21): revise plan per checker feedback (OI-11 + gate layering)
This commit is contained in:
parent
c05c96d5f1
commit
319d18d067
@ -14,7 +14,6 @@ files_modified:
|
||||
- script/build-lib.ts
|
||||
- package.json
|
||||
- index.html
|
||||
- src/bootstrap.ts
|
||||
- .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md
|
||||
autonomous: false
|
||||
requirements: []
|
||||
@ -25,79 +24,81 @@ estimate:
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-93)**:Task 0 必须先汇报「本计划 = 任务 11 · 构建重构(适配新引擎)」的**要做什么 / 为什么 / 大致怎么做 / 影响范围**,**必须列明:D-93 脚本重构与删除清单、D-94 数据端/渲染端分离插件的扫描口径与报错终止行为(取代 `checkClientData` 体积启发式)、D-95 数据端库模式 ESM 产物 `dist/data/data.js`、D-96 `src/content` → `dist/content` 的 jsonc 压缩(**必须保留 `// --- SYSTEM PREFIX END --- //` 标记行**)/ fontmin / 不分块 / 不 zip、D-97 的 dist 产物排除清单与 `LICENSE` 保留、`index.html` 去旧样式表 link、D-98 `package.json` script 调整、D-99 保留 `vue-tsc --noEmit`、D-100 移除二次压缩并微调 `manualChunks`、**全部拟新增命名(OI-1 / OI-2)**、写操作边界、验证门禁方式、已知阻断(用户 WIP 导致全量构建可能失败)与 `open_items`(OI-1..OI-10)**,然后必须**停下等待**用户回复;**回复为「可以执行」即视为通过(本计划不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行)**;用户若对任一 `open_items` 给出指定,按指定执行。"
|
||||
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-93)**:Task 0 必须先汇报「任务 11 · 构建重构(适配新引擎)」的**要做什么 / 为什么 / 大致怎么做 / 影响范围**,必须列明:D-93 脚本重构与删除清单、D-94 隔离插件(specifier + 解析路径双路扫描 + `this.error` 终止,取代 `checkClientData`)、D-95 数据端库模式 ESM 与 `dist/data/data.js`、D-96 content 压缩(保留 `// --- SYSTEM PREFIX END --- //`)/ fontmin / 不分块 / 不 zip、D-97 dist 产物排除清单与 `LICENSE` 保留、`index.html` 去旧样式表 link、D-98 `package.json` script 调整、D-99 保留 `vue-tsc --noEmit`、D-100 移除 babel 二次压缩并微调 `manualChunks`、**OI-11 的实测跨端引用链原文**、**数据端构建按设计终止属预期(OI-11)、门禁按渲染端 + content + LICENSE 分层**、全部拟新增命名(OI-1 / OI-2)、写操作边界(**不改 `src/**`**)、验证门禁方式与 `open_items`(OI-1..OI-11),然后必须**停下等待**;**回复「可以执行」即视为通过(本计划不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行)**;用户若对任一 `open_items` 给出指定,按指定执行。"
|
||||
- "**D-93 / D-98 脚本重构与删除:** 重构 `script/build-game.ts` 与 `script/build-resource.ts`;**删除** `script/build-packages.ts` 与 `script/build-lib.ts`;从 `package.json` 的 `scripts` 中删除 `build:packages` / `build:lib`;`script/dev.ts` **不改**;`build:game` 保留(按 OI-6 调整其前置链,仍保留 `vue-tsc --noEmit`)。"
|
||||
- "**D-94 数据端独立包不得引用渲染端(取代 `checkClientData`):** 新增 Vite 插件在**数据端构建**时扫描模块图;命中渲染端模块(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`,以及对应的 `packages-user/client-*` / `packages/render*` / `packages/system` / `packages/audio` / `packages/legacy-*` / `node_modules/vue|@vue|ant-design-vue|@ant-design` 解析路径)即 `this.error(...)` **报错并终止**构建;数据端自身依赖(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/types`、`@motajs/animate`、`anon-tokyo` 等)**不得**误判。**`checkClientData`、`getFileName`、`getFileSize`、`ClientDataLevel` 一并删除**。"
|
||||
- "**D-95 数据端产物:** 数据端构建改为 **Vite 库模式 + ESM**(`formats: ['es']`,不使用 iife),入口 `src/data.ts`,输出 `dist/data/data.js`;不生成 `data.iife.js` / `data.process.js`。"
|
||||
- "**D-94 数据端独立包不得引用渲染端(取代 `checkClientData`):** 新增 Vite 插件在**数据端构建**时扫描模块图;命中渲染端模块(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`,以及对应的 `packages-user/client-*` / `packages/render*` / `packages/system` / `packages/audio` / `packages/legacy-*` / `src/client/**` / `node_modules/vue|@vue|ant-design-vue|@ant-design` 解析路径)即 `this.error(...)` **报错并终止**构建;数据端自身依赖(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/types`、`@motajs/animate`、`anon-tokyo` 等)**不得**误判。**`checkClientData`、`getFileName`、`getFileSize`、`ClientDataLevel` 一并删除**。"
|
||||
- "**D-95 数据端产物:** 数据端构建改为 **Vite 库模式 + ESM**(`formats: ['es']`,不使用 iife),入口 `src/data.ts`,输出 `dist/data/data.js`;不生成 `data.iife.js` / `data.process.js`。**受 OI-11 约束:** 在用户修复 `src` 端跨端引用前,插件会按设计终止数据端构建,此产物**允许缺失**。"
|
||||
- "**D-96 content 处理:** 构建后 `src/content` → `dist/content`(路径去掉 `src` 前缀);`*.jsonc` 以 `JSON.parse + JSON.stringify` 压缩(去注释、**后缀仍为 `.jsonc`**);**压缩后必须保留且仅保留一行 `// --- SYSTEM PREFIX END --- //` 标记**,其后为压缩后的 JSON(理由:`PrefixedJSONCProcessor` 以该标记 `split` 且要求恰好 2 段,见 `packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`;丢失标记会在运行期触发 error 66 并解析为 `{}`);字体用 fontmin(glyph 子集)压缩;**不分块**(不生成 `resource.<hash>.h5data`)、**不 zip**。`build-resource` 改为从 `src/content` 读取素材。"
|
||||
- "**D-97 旧模板内容与 dist 产物:** 不复制 `public/**`(`copyPublicDir: false`,`public` 整体删除由用户执行,本计划不删);不产出 `editor.html`、`_server`、`_docs`、`libs`、`logo.png`、`styles.css`、`main.js`、`script/template/启动服务.exe`;不打包 `source-code.zip`;不生成 `loadList.json`;不生成 `dist.zip`;**保留 `LICENSE`(LGPL v3)并复制到 `dist/LICENSE`**;`index.html` 去掉旧 `<link id=\"mota-css\" ... href=\"/styles.css\">`,样式改由入口经 JS 打包引入(落点见 OI-5)。"
|
||||
- "**D-97 旧模板内容与 dist 产物:** 不复制 `public/**`(`copyPublicDir: false`,`public` 整体删除由用户执行,本计划不删);不产出 `editor.html`、`_server`、`_docs`、`libs`、`logo.png`、`styles.css`、`main.js`、`script/template/启动服务.exe`;不打包 `source-code.zip`;不生成 `loadList.json`;不生成 `dist.zip`;**保留 `LICENSE`(LGPL v3)并复制到 `dist/LICENSE`**;`index.html` 去掉旧 `<link id=\"mota-css\" ... href=\"/styles.css\">`。样式改由入口经 JS 打包引入属**用户侧 `src/**` 接线**(OI-5),本计划不改 `src/**`。"
|
||||
- "**D-99 / D-100:** 构建仍前置 `vue-tsc --noEmit`(保留于 `build:game`);移除 `main.js` 二次压缩(babel)步骤与相关依赖引用;`manualChunks` 按实际依赖微调——`audio` chunk(`codec-parser` / `opus-decoder` / `ogg-opus-decoder` / `@wasm-audio-decoders/ogg-vorbis`)保留,`antdv` chunk 移除(全仓源码无 `ant-design-vue` / `@ant-design/icons-vue` 导入);`@vitejs/plugin-legacy` 维持现状(目标浏览器列表不变)。"
|
||||
- "**写入边界(D-56 / AGENTS.md):** 只允许写 `files_modified` 所列路径;**不回退**用户既有改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等 WIP);除 OI-5 获批后的 `src/bootstrap.ts` **一行样式 import** 外,**不改 `src/**`**;不改数据端/渲染端源码、`public/**`、`vite.config.ts`、lockfile、`.planning/ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。"
|
||||
- "**命名须先获批(AGENTS.md / D-04):** 新增文件 `script/vite-plugin-data-isolation.ts`、导出 `dataIsolationPlugin`、测试文件 `script/vite-plugin-data-isolation.test.ts`(OI-1);`script/build-resource.ts` 新导出 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` 与测试文件 `script/build-resource.test.ts`(OI-2)。未获批前不得落码(测试文件按 AGENTS.md 免审批,但仍列入报备)。"
|
||||
- "**验证可运行性(Nyquist):** Task 1 以**真实 Vite `build()`** 驱动插件单测(渲染端 specifier / 渲染端解析路径分别被拒;纯数据端通过并输出 ESM);Task 2 以 `minifyJSONC` / `buildContent` 单测 + 构建脚本结构断言 + `git status` 范围门禁兜底;`pnpm exec tsx script/build-game.ts` **尽力实跑**,若因用户 WIP(`src/bootstrap.ts` 仍指向已移动的 UI 文件等)失败,必须记录完整错误与归因,**不得**为通过门禁而修改 `src/**` 或包源码。"
|
||||
- "**写入边界(D-56 / AGENTS.md / 用户裁定「你改 src,我做脚本」):** 只允许写 `files_modified` 所列路径;**完全不修改 `src/**`**(含 `src/bootstrap.ts`、`src/constant.ts`、`src/data/**`、`src/client/**`);不回退用户既有改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等 WIP);不改数据端/渲染端源码、`public/**`、`vite.config.ts`、lockfile、`.planning/ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。"
|
||||
- "**命名须先获批(AGENTS.md / D-04):** 新增文件 `script/vite-plugin-data-isolation.ts`、导出 `dataIsolationPlugin`、插件 `name: 'mota-data-isolation'`、测试文件 `script/vite-plugin-data-isolation.test.ts`(OI-1);`script/build-resource.ts` 新导出 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` 与测试文件 `script/build-resource.test.ts`(OI-2)。未获批前不得落码(测试文件按 AGENTS.md 免审批,但仍列入报备)。"
|
||||
- "**验证可运行性(Nyquist)与预期阻断分层:** Task 1 以**真实 Vite `build()`** 驱动插件单测(渲染端 specifier / 渲染端解析路径 / `src/client` 渲染目录分别被拒;纯数据端通过并输出 ESM);Task 2 以 `minifyJSONC` / `buildContent` 单测 + 构建脚本结构断言 + **渲染端 + content + LICENSE 产物门禁(今天即可通过)** + `git status` 范围门禁兜底;`pnpm exec tsx script/build-game.ts` **必须实跑**:数据端因 OI-11 已知跨端引用被插件按设计终止时,构建脚本记录为**预期 pending** 并继续产出渲染端 + content + LICENSE,**不得**据此判定本计划失败;`dist/data/data.js` 作为**条件门禁**(存在则断言 ESM,缺失则记录 OI-11 pending)。**不得**为通过门禁而修改 `src/**` 或包源码。"
|
||||
artifacts:
|
||||
- path: script/vite-plugin-data-isolation.ts
|
||||
provides: "新增 Vite 插件 `dataIsolationPlugin`:`enforce: 'pre'` + `resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径拦渲染端包(含传递依赖),命中即 `this.error(...)` 终止数据端构建(命名待批准,OI-1)"
|
||||
provides: "新增 Vite 插件 `dataIsolationPlugin`:`enforce: 'pre'` + `resolveId` 拦渲染端 specifier,`moduleParsed` 按解析后绝对路径拦渲染端包(含传递依赖与 `src/client/**`),命中即 `this.error(...)` 终止数据端构建(命名待批准,OI-1)"
|
||||
- path: script/vite-plugin-data-isolation.test.ts
|
||||
provides: "运行期单测:真实 `vite.build()`(`configFile:false`、临时目录 fixture、`write:false`)验证「渲染端 specifier 被拒」「渲染端解析路径被拒」「纯数据端通过且产物为 ESM」「数据端路径不误判」;每个 `it` 前一行注释(AGENTS.md)"
|
||||
provides: "运行期单测:真实 `vite.build()`(`configFile:false`、临时目录 fixture、`write:false`)验证「渲染端 specifier 被拒」「渲染端解析路径被拒」「`src/client` 渲染目录被拒」「纯数据端通过且产物为 ESM」「数据端路径不误判」;每个 `it` 前一行注释(AGENTS.md)"
|
||||
- path: script/build-resource.ts
|
||||
provides: "改为 content 处理模块:`minifyJSONC`(保留 `// --- SYSTEM PREFIX END --- //` 标记 + `JSON.parse/stringify` 去注释)、`buildContent`(递归 `src/content` → `dist/content`:jsonc 压缩、fontmin glyph 子集压缩字体、其它素材原样拷贝;无字体二进制时跳过并告警)与结果/选项接口(命名待批准,OI-2)"
|
||||
- path: script/build-resource.test.ts
|
||||
provides: "运行期单测:`minifyJSONC` 保留标记且压缩、去注释、非法 JSON 抛错、无标记纯 JSON 处理;`buildContent` 在临时目录完成 jsonc 压缩 + PNG 原样拷贝 + 无字体时跳过;每个 `it` 前一行注释"
|
||||
- path: script/build-game.ts
|
||||
provides: "重写编排:渲染端构建到 `dist`(`copyPublicDir:false`、`manualChunks` 微调、移除 babel/zip/资源地图/资源分块/旧模板拷贝)→ 收集字体字符 → 数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`)→ `buildContent` 处理 `dist/content` → 复制 `LICENSE` → 输出体积信息;删除 `checkClientData` 等旧逻辑"
|
||||
provides: "重写编排:渲染端构建到 `dist`(`copyPublicDir:false`、`manualChunks` 微调、移除 babel/zip/资源地图/资源分块/旧模板拷贝)→ 收集字体字符 → 数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`;OI-11 预期终止被分类记录后继续)→ `buildContent` 处理 `dist/content` → 复制 `LICENSE` → 输出体积与数据端状态;删除 `checkClientData` 等旧逻辑"
|
||||
- path: script/build-packages.ts
|
||||
provides: "**删除**(D-93 / D-98)"
|
||||
- path: script/build-lib.ts
|
||||
provides: "**删除**(D-93 / D-98)"
|
||||
- path: package.json
|
||||
provides: "删除 `build:packages` / `build:lib`;`build:game` 保留 `vue-tsc --noEmit`(按 OI-6 处理 `declare.ts` 前置)"
|
||||
provides: "删除 `build:packages` / `build:lib`;`build:game` 保留 `vue-tsc --noEmit`(按 OI-6 去掉 `declare.ts` 前置)"
|
||||
- path: index.html
|
||||
provides: "移除旧样式表 link(保留其余 head/body 内容与 `src/bootstrap.ts` module script)"
|
||||
- path: src/bootstrap.ts
|
||||
provides: "**仅当 OI-5 获批**:新增一行 `import './styles.less';`,使 D-97「样式走 JS 打包」成立;未获批则不改"
|
||||
- path: .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md
|
||||
provides: "本计划执行摘要:范围、插件扫描口径与错误行为、content 压缩口径(含标记保留这一兼容性约束)、dist 产物清单与排除项、删除清单、`open_items` 最终处置、门禁结果与全量构建阻断归因"
|
||||
provides: "本计划执行摘要:范围、插件扫描口径与错误行为、content 压缩口径(含标记保留这一兼容性约束)、dist 产物清单与排除项、删除清单、`open_items` 最终处置、门禁结果、OI-11 预期阻断的完整错误与归因、渲染端 + content + LICENSE 产物结果"
|
||||
key_links:
|
||||
- "`dataIsolationPlugin`(`script/vite-plugin-data-isolation.ts`)↔ `buildData` 的 `plugins` 数组(`script/build-game.ts`)—— D-94 的唯一执行点;取代 `checkClientData` 体积启发式"
|
||||
- "`buildData` 的 `build.lib.fileName: 'data'` + `formats: ['es']` ↔ `dist/data/data.js` —— D-95 产物契约"
|
||||
- "插件 `this.error` 错误文本(含 `mota-data-isolation` 与命中标识)↔ `buildGame` 的数据端 `.catch` 分类 —— 只有匹配 OI-11 已知链(`@motajs/render` 或 `src/client`)才记为预期 pending;其余数据端错误一律 `process.exit(1)`"
|
||||
- "`buildData` 的 `build.lib.fileName: 'data'` + `formats: ['es']` ↔ `dist/data/data.js` —— D-95 产物契约(受 OI-11 约束,允许暂缺)"
|
||||
- "`minifyJSONC` 的标记保留 ↔ `PrefixedJSONCProcessor.process`(`packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`,`split(prefixSplit)` 要求恰好 2 段)—— 丢失标记会导致运行期 error 66 / `{}`"
|
||||
- "`dist/content/**` ↔ `MotaDataLoader.resolveURL`(`packages-user/data-state/src/loader/loader.ts:74-82`,生产态为 `${BASE_URL}${url}`)与 `src/constant.ts` 的 `CORE_URL='content/core.jsonc'` / `CLIENT_CORE_URL='content/client.jsonc'` —— 目录落点契约"
|
||||
- "`buildClient` 的 `copyPublicDir: false` ↔ D-97 —— 阻止旧 `public/**` 泄入 `dist`"
|
||||
- "`package.json` 的 `build:game` ↔ `script/build-game.ts` / `vue-tsc --noEmit` —— D-98 / D-99"
|
||||
- "`index.html` 去 link ↔ `src/bootstrap.ts` 的 `import './styles.less'`(OI-5)—— D-97 样式链路"
|
||||
- "`index.html` 去 link ↔ 用户在 `src/**` 侧新增样式 import(OI-5,用户任务)—— D-97 样式链路由用户接线,本计划只负责移除 link"
|
||||
assumptions:
|
||||
- "**content 全部为「前缀注释 + 严格 JSON」:** 实测 `src/content/**/*.jsonc` 共 20 个文件均含 `// --- SYSTEM PREFIX END --- //`(含 `core.jsonc` / `client.jsonc` / `defaults/*` / `maps/*.jsonc` / `maps/.list.jsonc` / `texture/.list.jsonc` / `animation/.list.jsonc` / `audio/.list.jsonc` / `font/.list.jsonc`);`PrefixedJSONCProcessor` 要求标记恰好出现一次,故压缩必须保留该行。"
|
||||
- "**content 全部为「前缀注释 + 严格 JSON」且共 23 个:** 实测 `src/content/**/*.jsonc` 共 **23** 个文件,每个都**恰好包含一次** `// --- SYSTEM PREFIX END --- //`(含 `core.jsonc` / `client.jsonc` / `enemy.jsonc` / `item.jsonc` / `tile.jsonc` / `defaults/*` / `maps/*.jsonc` / `animation/.list.jsonc` / `audio/.list.jsonc` / `font/.list.jsonc` / `texture/.list.jsonc`);`PrefixedJSONCProcessor` 要求标记恰好出现一次且 `split` 后恰 2 段,故压缩必须保留该行。"
|
||||
- "**生产态 content URL 以 `BASE_URL` 为根:** `MotaDataLoader.resolveURL` 在 `import.meta.env.DEV === false` 时返回 `${BASE_URL}${url}`;核心配置传 `content/core.jsonc` / `content/client.jsonc`,额外配置传 `content/${url}`,故 `dist/content/**` 是唯一正确落点。"
|
||||
- "**字体目录当前无字体二进制:** `src/content/font/` 仅有 `.list.jsonc`(内容 `[\"testFont.ttf\"]`),`public/project/fonts/` 不存在;fontmin 无源可压,计划默认**跳过并告警**(OI-4),不得因缺字体阻断构建。"
|
||||
- "**数据端入口存在跨端引用链(实测 2026-10-19,OI-11):** `src/data.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 以 `new Font('Verdana', 16)` 值使用)。因此 D-94 隔离插件会在数据端构建时按设计 `this.error` 终止,`dist/data/data.js` 在用户打断该链路前**不可产出**。链路修复(拆分 `src/constant.ts`、内联常量、把渲染常量移出数据端依赖等)是**用户任务**,在本计划 `files_modified` 之外。"
|
||||
- "**`src/bootstrap.ts` 现指向工作树内的文件(上轮判断已过时):** 实测 `src/bootstrap.ts:2` 为 `import { createClientCore } from './client/entry'`、`:4` 为 `import { LoadSceneUI } from './client/ui/load'`,且 `src/client/ui/load.tsx` 存在。渲染端构建不再因「UI 文件被移动」而阻断。"
|
||||
- "**`src/content/item.ts` 是源码而非运行时素材:** `core.jsonc` 的 `itemEffectDir` 指向 `item.js`,但 `src/content` 中只有 `item.ts` 且当前无消费者(用户 WIP);本计划按 D-96 原样复制 content 目录,不改该数据/不改加载器(OI-8)。"
|
||||
- "**全量构建当前可能被用户 WIP 阻断:** `src/bootstrap.ts` 仍从 `../packages-user/client-modules/src/ui/load` 导入,而该文件在工作树中已被移动到 `src/client/ui/`(`git status` 显示删除 + 新增目录);因此 `tsx script/build-game.ts` 的渲染端构建可能失败。这是**用户 WIP**,本计划**不得**为让其通过而修改 `src/**`;门禁以插件/内容单测 + 结构断言为主,全量构建结果如实记录(OI-9)。"
|
||||
- "**`ant-design-vue` 已不再被源码引用:** 对 `packages/`、`packages-user/`、`src/` 的源码检索(排除 `node_modules`)未发现 `ant-design-vue` / `@ant-design/icons-vue` 导入;`audio` 系依赖仍由 `@motajs/audio` 使用(`codec-parser` / `ogg-opus-decoder`)。"
|
||||
- "**`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` 别名:** 数据端构建复用 `loadConfigFromFile` + `mergeConfig`,因此插件可在 `enforce: 'pre'` 阶段先拦截未解析 specifier,再由 `moduleParsed` 按解析后路径兜底。"
|
||||
- "**构建顺序安全:** 渲染端构建先执行并 `emptyOutDir` 清空 `dist`;数据端构建 `emptyOutDir:false` 写入 `dist/data`;content 最后写入 `dist/content`;`LICENSE` 最后复制。"
|
||||
- "**`vite.config.ts` 提供 `base: './'` 与 `@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名:** 数据端构建复用 `loadConfigFromFile` + `mergeConfig`,因此插件可在 `enforce: 'pre'` 阶段先拦未解析 specifier,再由 `moduleParsed` 按解析后路径兜底(即使别名解析先于用户插件执行,路径检查仍能拦截)。"
|
||||
- "**构建顺序安全:** 渲染端构建先执行并 `emptyOutDir` 清空 `dist`;数据端构建 `emptyOutDir:false` 写入 `dist/data`;content 最后写入 `dist/content`;`LICENSE` 最后复制。数据端按预期失败时,`dist` 中渲染端产物不受影响。"
|
||||
- "**行号可能漂移:** 本计划引用行号为规划日实测;门禁以「文件存在 + 结构/行为断言」为准,不硬编码行号。"
|
||||
prohibitions:
|
||||
- "不得在用户回复通过 Task 0 关卡之前执行本计划的任何任务、或派发任何执行子代理。"
|
||||
- "**未经批准的命名一律不得使用:** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `script/vite-plugin-data-isolation.test.ts`(OI-1)与 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` / `script/build-resource.test.ts`(OI-2)必须先经 Task 0 报备并获批准;未获批前不得落码。"
|
||||
- "**未经批准的命名一律不得使用:** `script/vite-plugin-data-isolation.ts` / `dataIsolationPlugin` / `mota-data-isolation` / `script/vite-plugin-data-isolation.test.ts`(OI-1)与 `buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` / `script/build-resource.test.ts`(OI-2)必须先经 Task 0 报备并获批准;未获批前不得落码。"
|
||||
- "**content 压缩必须保留标记行:** 不得只输出 `JSON.stringify` 结果而丢掉 `// --- SYSTEM PREFIX END --- //`;不得改后缀(仍为 `.jsonc`)。此为本计划对 D-96 的**兼容性补充**(OI-3),不是范围扩张。"
|
||||
- "**不得删除 `public/**`:** D-97 的「整体删除」由用户执行;本计划只通过 `copyPublicDir: false` 阻止其进入 `dist`。"
|
||||
- "**写入边界(D-56):** 只允许写 `files_modified`;`src/bootstrap.ts` **仅允许**在 OI-5 获批后新增一行样式 import;不得改 `vite.config.ts`、`packages/**`、`packages-user/**`、`public/**`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、`script/utils.ts`、lockfile;不得回退用户在工作树中的任何既有改动。"
|
||||
- "**写入边界(D-56 + 用户裁定「你改 src,我做脚本」):** 只允许写 `files_modified`;**不得修改 `src/**` 的任何文件**(包括 `src/bootstrap.ts`、`src/constant.ts`、`src/data/**`、`src/client/**`);不得改 `vite.config.ts`、`packages/**`、`packages-user/**`、`public/**`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、`script/utils.ts`、lockfile;不得回退用户在工作树中的任何既有改动。"
|
||||
- "**`.planning/**` 边界:** 不得改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 任何既有 PLAN 或 SUMMARY;只允许新增 `09-21-SUMMARY.md`(与 09-19 / 09-20 的既有做法一致)。"
|
||||
- "**不新增依赖:** 不得改 `package.json` 的 `dependencies` / `devDependencies`、不得改 lockfile;`vite-plugin-dts` 虽因 `build-lib.ts` 删除而失去使用点,也**不删除**(OI-10)。"
|
||||
- "**不得改既有注释 / jsDoc(AGENTS.md):** 重写 `build-game.ts` / `build-resource.ts` 时保留/更新其内部注释属重写范围,但不得改动其它文件的既有注释;新增注释须遵循 dev.md。"
|
||||
- "**不得以全仓门禁代替:** 不设 `pnpm check:type` / `pnpm build` / 全量 `pnpm test:ci` 作为通过条件(用户 WIP 存在历史类型错误)。"
|
||||
- "**不得为通过门禁而修改范围外文件:** 全量构建若因 `src/**` / 包源码的用户 WIP 失败,只能记录归因,不得修 `src/**` 或包源码。"
|
||||
- "**不得为通过门禁而修改范围外文件:** 数据端若因 OI-11 按预期终止,只能记录归因;渲染端若因用户其它 WIP 失败,也只能记录归因;不得修 `src/**` 或包源码。"
|
||||
- "**不得把预期阻断当作通过一切数据端错误的借口:** `.catch` 分类必须同时匹配插件错误标识与 OI-11 已知链(`@motajs/render` 或 `src/client`);任何不匹配的数据端错误必须 `process.exit(1)`,不得吞掉。"
|
||||
open_items:
|
||||
- "**OI-1(插件文件与导出命名):** 提议新增 `script/vite-plugin-data-isolation.ts`,导出 `dataIsolationPlugin(): Plugin`;测试文件 `script/vite-plugin-data-isolation.test.ts`。备选命名:`script/build-plugin-data-isolation.ts` / `forbidRenderDependencies` / `renderIsolationPlugin`。**待用户报备批准**(D-04;测试文件按 AGENTS.md 免审批,但仍一并报备)。"
|
||||
- "**OI-1(插件文件与导出命名):** 提议新增 `script/vite-plugin-data-isolation.ts`,导出 `dataIsolationPlugin(): Plugin`,插件 `name: 'mota-data-isolation'`;测试文件 `script/vite-plugin-data-isolation.test.ts`。备选命名:`script/build-plugin-data-isolation.ts` / `forbidRenderDependencies` / `renderIsolationPlugin`。**待用户报备批准**(D-04;测试文件按 AGENTS.md 免审批,但仍一并报备)。"
|
||||
- "**OI-2(content 模块命名):** 提议 `script/build-resource.ts` 导出 `buildContent(options: IContentBuildOptions): Promise<IContentBuildStats>`、`minifyJSONC(text: string): string`,接口 `IContentBuildOptions`(`sourceDir` / `outputDir` / `chars`)与 `IContentBuildStats`(`files` / `jsonc` / `fonts`);测试文件 `script/build-resource.test.ts`。备选:`processContent` / `compressJSONC`。**待批准**(dev.md 要求对象入参声明 interface)。"
|
||||
- "**OI-3(jsonc 标记保留与 `Type:` 注释丢失):** 提议压缩输出 = `// --- SYSTEM PREFIX END --- //` 一行 + 压缩 JSON;`// Type: ...` 等标记前注释**全部丢失**(含 `texture/.list.jsonc` 的类型指针)。D-96 说「去注释」,但加载器要求保留标记,故标记必须保留;`Type:` 指针丢失是否可接受**请用户确认**。"
|
||||
- "**OI-4(字体来源):** `src/content/font/` 仅 `.list.jsonc`(引用 `testFont.ttf`),无字体二进制;提议「无字体文件时跳过 fontmin 并告警」。若用户要求压缩特定字体,请指定字体文件落点。"
|
||||
- "**OI-5(样式引入落点):** D-97 要求样式走 JS 打包,但当前 `src/styles.less` 无任何导入点。提议在 `src/bootstrap.ts` 顶部新增一行 `import './styles.less';`(该文件是入口、已有顶层执行语句,故不受 dev.md「模块无副作用」约束)。因 `src/**` 属用户 WIP 边界,**须批准**。备选:不改 `src/**`,接受 `#game-draw` 居中样式缺失(记入 SUMMARY)。"
|
||||
- "**OI-5(样式引入落点 = 用户侧):** D-97 要求样式走 JS 打包,但 `src/styles.less` 当前无导入点;按用户裁定「你改 src,我做脚本」,**本计划不改 `src/**`**,样式 import 由用户后续在 `src/**` 侧添加。本计划只移除 `index.html` 的旧样式表 link;在用户接线前,`#game-draw` 居中样式可能缺失(记入 SUMMARY)。"
|
||||
- "**OI-6(`build:game` 前置链):** 现 `build:game` = `tsx script/declare.ts && vue-tsc --noEmit && tsx script/build-game.ts`,而 `script/declare.ts` 读取 `public/project/*`(D-97 将删除 `public/**`)。提议改为 `vue-tsc --noEmit && tsx script/build-game.ts`(保留 D-99 的类型检查),**不删除** `script/declare.ts` 文件。**待确认**。"
|
||||
- "**OI-7(`manualChunks` / legacy 插件):** 提议移除 `antdv` chunk、保留 `audio` chunk、`@vitejs/plugin-legacy` 维持现状(含目标浏览器列表与 `renderModernChunks:false`)。若用户希望移除 legacy 或调整目标,请在 Task 0 指明。"
|
||||
- "**OI-8(`itemEffectDir` 缺文件):** `core.jsonc` 引用 `item.js`,`src/content` 仅有 `item.ts` 且无消费者。本计划原样复制 content,不生成 `item.js`、不改数据;**请确认该缺口由用户后续处理**。"
|
||||
- "**OI-9(全量构建的已知阻断):** 用户 WIP 使 `src/bootstrap.ts` 指向已移动的 UI 文件,`tsx script/build-game.ts` 可能失败。提议:主门禁 = 插件单测 + content 单测 + 结构断言;全量构建**尽力实跑**,失败则记录完整错误与归因,不修改 `src/**`。**待确认**。"
|
||||
- "**OI-9(全量构建的已知阻断 = 指向 OI-11):** 唯一已测阻断是 OI-11 的数据端入口跨端引用(数据端构建按设计终止);渲染端依赖链已就位(`src/bootstrap.ts` → `./client/entry` / `./client/ui/load`,`src/client/ui/load.tsx` 存在),预期可构建。门禁分层:**渲染端 + `dist/content` + `LICENSE` 为必过**;`dist/data/data.js` 为条件门禁(存在则断言 ESM,缺失则记为 OI-11 pending)。若渲染端仍因用户其它 WIP 失败,记录完整错误与归因,不改范围外文件。**待确认**。"
|
||||
- "**OI-10(遗留依赖与文件):** `script/types.ts` 的 `RequiredData` / `RequiredIconsData` 及 `vite-plugin-dts`、`@babel/*`、`archiver`、`compressing` 等依赖在重构后可能失去使用点,但**不删除**(避免 lockfile/依赖面变更)。**请确认**保留。"
|
||||
- "**OI-11(数据端入口跨端引用 · 待用户修复):** 实测链路 `src/data.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 以 `new Font('Verdana', 16)` 值使用)。D-94 隔离插件会按设计终止数据端构建,`dist/data/data.js` 在用户打断该链路前**不可产出**。链路修复(拆分 `src/constant.ts` / 内联常量 / 移出渲染常量等)是**用户任务**,在本计划 `files_modified` 之外;本计划不改 `src/**`。Task 2 把「插件按预期终止」记为 expected pending blocker(**不是本计划失败**),并要求渲染端 + `dist/content` + `LICENSE` 仍产出并通过门禁。"
|
||||
---
|
||||
|
||||
<objective>
|
||||
@ -109,22 +110,24 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
1. **D-94** 用 Vite 插件扫描数据端模块图,命中渲染端模块即报错终止,**取代** `checkClientData`。
|
||||
2. **D-95** 数据端改为库模式 ESM,入口 `src/data.ts`,产物 `dist/data/data.js`。
|
||||
3. **D-96** `src/content` → `dist/content`:jsonc 压缩(**保留系统标记行**)、fontmin 压缩字体、**不分块 / 不 zip**;`build-resource` 改为从 `src/content` 读取。
|
||||
4. **D-97** dist 不再携带旧模板内容;保留 `LICENSE`;`index.html` 去掉旧样式表 link,样式走 JS 打包。
|
||||
4. **D-97** dist 不再携带旧模板内容;保留 `LICENSE`;`index.html` 去掉旧样式表 link,样式由用户在 `src/**` 侧经 JS 引入(OI-5)。
|
||||
5. **D-93 / D-98** 删除 `build-packages.ts` / `build-lib.ts` 及对应 npm script;`build:game` 调整并保留 `vue-tsc --noEmit`(D-99)。
|
||||
6. **D-100** 移除 `main.js` 二次压缩;`manualChunks` 按实际依赖微调(`antdv` 已无源码引用 → 移除;`audio` 保留);legacy 插件维持现状。
|
||||
|
||||
**关键兼容性约束(本计划对 D-96 的实现补充):** 所有 `src/content/**/*.jsonc` 都是「前缀注释 + 严格 JSON」,运行期由 `PrefixedJSONCProcessor` 以 `// --- SYSTEM PREFIX END --- //` **split** 并要求恰好 2 段(`packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`)。因此「`JSON.parse + JSON.stringify` 去注释」**必须保留该标记行**;否则运行期触发 error 66、配置解析为 `{}`。`// Type: ...` 等标记前注释会按 D-96 丢失(OI-3 待确认)。
|
||||
**关键兼容性约束(本计划对 D-96 的实现补充):** 所有 `src/content/**/*.jsonc`(**实测 23 个,每个恰好一次标记**)都是「前缀注释 + 严格 JSON」,运行期由 `PrefixedJSONCProcessor` 以 `// --- SYSTEM PREFIX END --- //` **split** 并要求恰好 2 段(`packages-user/data-state/src/loader/jsoncProcessor.ts:14-18`)。因此「`JSON.parse + JSON.stringify` 去注释」**必须保留该标记行**;否则运行期触发 error 66、配置解析为 `{}`。`// Type: ...` 等标记前注释会按 D-96 丢失(OI-3 待确认)。
|
||||
|
||||
**已知预期阻断(OI-11):** 数据端入口存在跨端引用链 `src/data.ts` → `src/data/entry.ts:2` → `src/constant.ts:2` → `src/client/ui/constant.ts:1`(`@motajs/render` 值使用)。D-94 插件会在数据端构建时**按设计终止**;这是**预期行为**(等待用户修复 `src`),不是本计划失败。本计划据此把门禁分层:渲染端 + `dist/content` + `LICENSE` 为必过;`dist/data/data.js` 为条件门禁。
|
||||
|
||||
**大致解决方案(本计划 = 09-21):**
|
||||
- **Task 0(`checkpoint:decision@blocking-human`)= 预执行汇报关卡**:汇报要做什么 / 为什么 / 大致怎么做 / 影响范围(含插件扫描口径、content 压缩口径与标记保留理由、dist 产物清单、删除清单、`manualChunks` 调整、命名报备、验证方式、已知阻断),停下等待「可以执行」。
|
||||
- **Task 1(`tracer`)= 分离插件端到端最小切片**:新增 Vite 插件并在**真实 `vite.build()`** 的临时 fixture 上验证——渲染端 specifier 被拒、渲染端解析路径(含传递依赖)被拒、纯数据端通过且产物为 ESM、数据端路径不误判。这是本增量唯一的全新机制,先用运行期测试把「扫描 + 终止」跑通。
|
||||
- **Task 2 = 构建编排重写与收口**:重写 `build-resource.ts`(content 压缩/拷贝/字体)与 `build-game.ts`(渲染端 → 数据端 ESM(注入插件)→ content → LICENSE);删除旧脚本;调整 `package.json` 与 `index.html`(OI-5 获批则加样式 import);补 `build-resource.test.ts`;尽力实跑全量构建并做产物门禁 / 范围门禁;写 `09-21-SUMMARY.md`。
|
||||
- **Task 0(`checkpoint:decision@blocking-human`)= 预执行汇报关卡**:汇报要做什么 / 为什么 / 大致怎么做 / 影响范围(含插件扫描口径、content 压缩口径与标记保留理由、dist 产物清单、删除清单、`manualChunks` 调整、命名报备、验证方式、OI-11 预期阻断与门禁分层),停下等待「可以执行」。
|
||||
- **Task 1(`tracer`)= 分离插件端到端最小切片**:新增 Vite 插件并在**真实 `vite.build()`** 的临时 fixture 上验证——渲染端 specifier 被拒、渲染端解析路径(含传递依赖)/ `src/client` 渲染目录被拒、纯数据端通过且产物为 ESM、数据端路径不误判。这是本增量唯一的全新机制,先用运行期测试把「扫描 + 终止」跑通。
|
||||
- **Task 2 = 构建编排重写与收口**:重写 `build-resource.ts`(content 压缩/拷贝/字体)与 `build-game.ts`(渲染端 → 数据端 ESM(注入插件,OI-11 预期失败分类)→ content → LICENSE);删除旧脚本;调整 `package.json` 与 `index.html`;补 `build-resource.test.ts`;实跑全量构建并按分层门禁收口;写 `09-21-SUMMARY.md`。
|
||||
|
||||
**为何 `depends_on: []`:** 本计划只改构建脚本与其直接产物契约,不依赖 09-19 / 09-20 的源码改动;它依赖的是**已存在**的数据端加载器、`src/content` 数据与 `src/data.ts` 入口(均已就位)。因此可与既有计划解耦,独占 wave 18。
|
||||
|
||||
**API coverage:** `detected:false`。**`No external API integration: 本计划为本地构建流程重构,不涉及任何外部 API。`**
|
||||
|
||||
**修改方式声明(AGENTS.md):** 本计划执行含**文件写入与删除**(新增插件与测试、重写两个构建脚本、删除两个旧脚本、改 `package.json` / `index.html`、条件性改 `src/bootstrap.ts`、写 SUMMARY),将由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 所列路径外无任何写入。
|
||||
**修改方式声明(AGENTS.md):** 本计划执行含**文件写入与删除**(新增插件与测试、重写两个构建脚本、删除两个旧脚本、改 `package.json` / `index.html`、写 SUMMARY),将由**派发子代理(`gsd-executor`)执行**,非内联执行;除 `files_modified` 所列路径外无任何写入,**`src/**` 零改动**。
|
||||
</objective>
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@ -136,16 +139,29 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md
|
||||
@.planning/phases/09-minimum-browser-runnable/09-20-PLAN.md
|
||||
@AGENTS.md
|
||||
@dev.md
|
||||
@script/build-game.ts
|
||||
@script/build-resource.ts
|
||||
@script/build-packages.ts
|
||||
@script/build-lib.ts
|
||||
@package.json
|
||||
@index.html
|
||||
@vite.config.ts
|
||||
@src/data.ts
|
||||
@src/data/entry.ts
|
||||
@src/constant.ts
|
||||
@src/client/ui/constant.ts
|
||||
@src/bootstrap.ts
|
||||
@packages-user/data-state/src/loader/jsoncProcessor.ts
|
||||
@packages-user/data-state/src/loader/loader.ts
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:decision" gate="blocking-human">
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围,等待用户回复「可以执行」</name>
|
||||
<precondition>`script/build-game.ts`(703 行)与 `script/build-resource.ts`(225 行)仍为旧引擎实现;`script/build-packages.ts` 与 `script/build-lib.ts` 存在;`package.json` scripts 含 `build:packages` / `build:lib` / `build:game`(`build:game` 含 `tsx script/declare.ts` 与 `vue-tsc --noEmit`);`index.html` 含 `<link id="mota-css" ... href="/styles.css">`;`src/content/**` 共 20 个 `.jsonc`(全部含 `// --- SYSTEM PREFIX END --- //`)与 `texture/*.png`;`src/content/font/` 无字体二进制;`src/data.ts` 存在且导出 `createCoreState`;`script/vite-plugin-data-isolation.ts` / `script/vite-plugin-data-isolation.test.ts` / `script/build-resource.test.ts` **尚不存在**;`src/bootstrap.ts` 仍指向已移动的 `packages-user/client-modules/src/ui/load`(用户 WIP)。</precondition>
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报「任务 11 · 构建重构(适配新引擎)」的要做什么 / 为什么 / 大致怎么做 / 影响范围(含 OI-11 预期阻断与门禁分层),等待用户回复「可以执行」</name>
|
||||
<precondition>`script/build-game.ts`(703 行)与 `script/build-resource.ts`(225 行)仍为旧引擎实现;`script/build-packages.ts` 与 `script/build-lib.ts` 存在;`package.json` scripts 含 `build:packages` / `build:lib` / `build:game`(`build:game` 含 `tsx script/declare.ts` 与 `vue-tsc --noEmit`);`index.html` 含 `<link id="mota-css" ... href="/styles.css">`;`src/content/**` 共 **23** 个 `.jsonc`(每个恰好一个 `// --- SYSTEM PREFIX END --- //`)与 `texture/*.png`;`src/content/font/` 无字体二进制;`src/data.ts` 存在并导出 `createCoreState`;`src/bootstrap.ts` 现导入 `./client/entry` 与 `./client/ui/load`(`src/client/ui/load.tsx` 存在);**OI-11 跨端引用链存在**(`src/data.ts` → `src/data/entry.ts` → `src/constant.ts` → `src/client/ui/constant.ts` → `@motajs/render`);`script/vite-plugin-data-isolation.ts` / `script/vite-plugin-data-isolation.test.ts` / `script/build-resource.test.ts` **尚不存在**。</precondition>
|
||||
<files>(只读汇报,不修改任何文件)</files>
|
||||
<read_first>
|
||||
- .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-93 / D-94 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100**;D-03 / D-04 / D-56)
|
||||
@ -153,10 +169,10 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
- script/build-resource.ts(全文:`splitResource:123` 从 `public/project/{animates,fonts,images,sounds,tilesets,autotiles,materials}` 读取并按 2MB 分块 zip 成 `resource.<hash>.h5data`)
|
||||
- script/build-packages.ts / script/build-lib.ts(待删除);script/types.ts / script/utils.ts(`formatSize`)
|
||||
- package.json(只读:scripts、依赖面);index.html(只读:`<link href='/styles.css'>` 与 `/src/bootstrap.ts` module script)
|
||||
- src/data.ts / src/data/entry.ts / src/client/entry.ts / src/constant.ts(`CORE_URL='content/core.jsonc'`、`CLIENT_CORE_URL='content/client.jsonc'`)
|
||||
- src/data.ts / src/data/entry.ts / src/client/entry.ts / src/constant.ts(`CORE_URL='content/core.jsonc'`、`CLIENT_CORE_URL='content/client.jsonc'`)/ src/client/ui/constant.ts(`@motajs/render` 值使用)
|
||||
- packages-user/data-state/src/loader/loader.ts:70-142(`resolveURL` 生产态 = `${BASE_URL}${url}`)与 packages-user/data-state/src/loader/jsoncProcessor.ts:4-28(标记 split 必须恰好 2 段)
|
||||
- src/content/**(`client.jsonc` / `core.jsonc` / `defaults/*` / `maps/*` / `texture/.list.jsonc` / `font/.list.jsonc` / `item.ts`)与 src/styles.less
|
||||
- vite.config.ts(`base: './'`、`@motajs/*` / `@user/*` / `@dp/*` 别名);`git status`(用户 WIP 面)
|
||||
- src/content/**(**23 个 jsonc**:`client.jsonc` / `core.jsonc` / `enemy.jsonc` / `item.jsonc` / `tile.jsonc` / `defaults/*` / `maps/*` / `texture/.list.jsonc` / `font/.list.jsonc` / `animation/.list.jsonc` / `audio/.list.jsonc`)与 src/styles.less
|
||||
- vite.config.ts(`base: './'`、`@motajs/*` / `@user/*` / `@dp/*` / `@/` 别名);`git status`(用户 WIP 面)
|
||||
- AGENTS.md(执行前须获批、命名须先反馈、修改方式须预先声明、禁改既有注释);dev.md(命名规则、对象类型须声明 interface、模块无副作用、非空判断约定、jsDoc 规范)
|
||||
</read_first>
|
||||
<action>
|
||||
@ -166,58 +182,64 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
|
||||
② **范围(D-93):** 重构 `script/build-game.ts` 与 `script/build-resource.ts`;删除 `script/build-packages.ts` 与 `script/build-lib.ts`;`script/dev.ts` 不改。
|
||||
|
||||
③ **D-94 分离插件:** 新增 Vite 插件,在数据端构建时扫描**解析后的模块图**:`enforce: 'pre'` 的 `resolveId` 先拦渲染端 specifier(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`),`moduleParsed` 再按绝对路径兜底(`packages-user/client-*`、`packages/render*`、`packages/system`、`packages/audio`、`packages/legacy-*`、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`),命中即 `this.error(...)` **终止构建**;数据端自身依赖不误判。删除 `checkClientData` / `getFileName` / `getFileSize` / `ClientDataLevel`。
|
||||
③ **D-94 分离插件:** 新增 Vite 插件,在数据端构建时扫描**解析后的模块图**:`enforce: 'pre'` 的 `resolveId` 先拦渲染端 specifier(`@user/client-*`、`@user/entry-client`、`@motajs/render*`、`@motajs/client*`、`@motajs/system`、`@motajs/audio`、`@motajs/legacy-*`、`vue`、`vue/*`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue`),`moduleParsed` 再按绝对路径兜底(`packages-user/client-*`、`packages/render*`、`packages/system`、`packages/audio`、`packages/legacy-*`、**`src/client/**`**、`node_modules/{vue,@vue,ant-design-vue,@ant-design}`),命中即 `this.error(...)` **终止构建**;数据端自身依赖不误判。删除 `checkClientData` / `getFileName` / `getFileSize` / `ClientDataLevel`。
|
||||
|
||||
④ **D-95 数据端:** 库模式 + `formats: ['es']`,入口 `src/data.ts`,产物 `dist/data/data.js`;`build.lib.fileName: 'data'`(package.json `type: module` → es 扩展名 `.js`);`emptyOutDir:false`;`copyPublicDir:false`;注入分离插件。
|
||||
|
||||
⑤ **D-96 content:** `src/content` → `dist/content`;`.jsonc` = **保留一行系统标记 + `JSON.stringify(JSON.parse(body))`**(去注释、后缀仍 `.jsonc`;标记必须保留,否则 `PrefixedJSONCProcessor` 运行期 error 66);字体用 fontmin glyph 子集压缩;**不分块、不 zip**。`build-resource` 改为从 `src/content` 读取素材。
|
||||
⑤ **D-96 content:** `src/content` → `dist/content`;`.jsonc` = **保留一行系统标记 + `JSON.stringify(JSON.parse(body))`**(去注释、后缀仍 `.jsonc`;标记必须保留,否则 `PrefixedJSONCProcessor` 运行期 error 66);字体用 fontmin glyph 子集压缩;**不分块、不 zip**。`build-resource` 改为从 `src/content` 读取素材。实测 `src/content/**` 共 **23** 个 jsonc,每个恰好一个标记。
|
||||
|
||||
⑥ **D-97 产物:** 不复制 `public/**`(`copyPublicDir:false`;整体删除由用户执行);dist 不携带旧模板内容(`editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`);不打包源码 zip;不生成资源地图 JSON;不生成 `dist.zip`;**复制 `LICENSE` 到 `dist/LICENSE`**;`index.html` 去掉旧样式表 link。
|
||||
⑥ **D-97 产物:** 不复制 `public/**`(`copyPublicDir:false`;整体删除由用户执行);dist 不携带旧模板内容(`editor.html` / `_server` / `_docs` / `libs` / `logo.png` / `styles.css` / `main.js` / `启动服务.exe`);不打包源码 zip;不生成资源地图 JSON;不生成 `dist.zip`;**复制 `LICENSE` 到 `dist/LICENSE`**;`index.html` 去掉旧样式表 link。样式 import 由用户后续在 `src/**` 侧添加(OI-5),本计划不改 `src/**`。
|
||||
|
||||
⑦ **D-98 / D-99 / D-100:** 删除 `build:packages` / `build:lib`;`build:game` 调整(OI-6:移除读取已删 `public/project` 的 `declare.ts` 前置;**保留 `vue-tsc --noEmit`**);移除 babel 二次压缩;`manualChunks` 移除 `antdv`(源码已无引用)、保留 `audio`;legacy 插件维持现状。
|
||||
|
||||
⑧ **拟新增命名(须报备批准):** `script/vite-plugin-data-isolation.ts` + `dataIsolationPlugin` + 测试文件(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` + `script/build-resource.test.ts`(OI-2)。
|
||||
⑧ **拟新增命名(须报备批准):** `script/vite-plugin-data-isolation.ts` + `dataIsolationPlugin` + 插件 `name: 'mota-data-isolation'` + 测试文件(OI-1);`buildContent` / `minifyJSONC` / `IContentBuildOptions` / `IContentBuildStats` + `script/build-resource.test.ts`(OI-2)。
|
||||
|
||||
⑨ **待确认项:** 标记保留与 `Type:` 注释丢失(OI-3);无字体二进制的处置(OI-4);`src/styles.less` 的引入落点 `src/bootstrap.ts`(OI-5);`build:game` 前置链(OI-6);`manualChunks` / legacy(OI-7);`itemEffectDir` 缺 `item.js`(OI-8);全量构建可能被用户 WIP 阻断(OI-9);遗留文件与依赖保留(OI-10)。
|
||||
⑨ **待确认项:** 标记保留与 `Type:` 注释丢失(OI-3);无字体二进制的处置(OI-4);样式 import 由用户在 `src/**` 侧完成(OI-5);`build:game` 前置链(OI-6);`manualChunks` / legacy(OI-7);`itemEffectDir` 缺 `item.js`(OI-8);全量构建阻断归因(OI-9,指向 OI-11);遗留文件与依赖保留(OI-10);**数据端入口跨端引用待用户修复(OI-11)**。
|
||||
|
||||
⑩ **产出文件:** 新增 `script/vite-plugin-data-isolation.ts`、`script/vite-plugin-data-isolation.test.ts`、`script/build-resource.test.ts`;重写 `script/build-resource.ts` / `script/build-game.ts`;删除 `script/build-packages.ts` / `script/build-lib.ts`;改 `package.json` / `index.html`(OI-5 获批则加 `src/bootstrap.ts` 一行 import);新增 `09-21-SUMMARY.md`。
|
||||
⑩ **OI-11 实测跨端引用链(原样告知,必须完整复述):** `src/data.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 以 `new Font('Verdana', 16)` 值使用)。因此 D-94 隔离插件会在数据端构建时**按设计** `this.error` 终止;在用户打断该链路前 **`dist/data/data.js` 不可产出**。链路修复(拆分 `src/constant.ts` / 内联常量 / 把渲染常量移出数据端依赖等)是**用户任务**,在本计划 `files_modified` 之外;**本计划不改 `src/**`**。
|
||||
|
||||
⑪ **不在本增量:** 不删 `public/**`;不改 `src/**`(除 OI-5 一行);不改数据端/渲染端源码、`vite.config.ts`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、lockfile;不新增依赖;不改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。
|
||||
⑪ **门禁分层(必须原样告知):** 数据端构建因 OI-11 被插件按设计终止 = **预期 pending blocker,不是本计划失败**;Task 2 的构建脚本必须把「插件按预期终止」与「本计划脚本/插件错误」分开:只有错误文本同时匹配插件标识与 OI-11 已知链(`@motajs/render` 或 `src/client`)才记为预期并继续;其余数据端错误一律 `process.exit(1)`。**渲染端产物 + `dist/content` + `dist/LICENSE` 为必过门禁(今天即可通过)**;`dist/data/data.js` 为条件门禁(存在则断言 ESM,缺失则记录 OI-11 pending)。
|
||||
|
||||
⑫ **已知阻断(须一并告知并记入 SUMMARY):** 用户 WIP 使 `src/bootstrap.ts` 指向已移动的 UI 文件,全量构建可能失败;本计划不为此修改 `src/**`,门禁以插件单测 + content 单测 + 结构断言为主,全量构建如实记录归因。
|
||||
⑫ **产出文件:** 新增 `script/vite-plugin-data-isolation.ts`、`script/vite-plugin-data-isolation.test.ts`、`script/build-resource.test.ts`;重写 `script/build-resource.ts` / `script/build-game.ts`;删除 `script/build-packages.ts` / `script/build-lib.ts`;改 `package.json` / `index.html`;新增 `09-21-SUMMARY.md`。**不修改 `src/**`。**
|
||||
|
||||
⑬ **验证方式:** Task 1 = `pnpm exec vitest run script/vite-plugin-data-isolation.test.ts`(真实 `vite.build()`:拒渲染端 specifier / 拒渲染端路径 / 通过纯数据端并输出 ESM / 不误判数据端路径)+ 结构断言;Task 2 = `pnpm exec vitest run script/build-resource.test.ts` + `script/build-game.ts` / `package.json` / `index.html` 结构断言 + `git status` 范围门禁 + `pnpm exec tsx script/build-game.ts` **尽力实跑**与产物门禁(成功时);**不设全仓 `check:type` / `build` / 全量 `test:ci` 门禁**。
|
||||
⑬ **不在本增量:** 不删 `public/**`;**不改 `src/**`(零改动)**;不改数据端/渲染端源码、`vite.config.ts`、`script/dev.ts`、`script/declare.ts`、`script/types.ts`、lockfile;不新增依赖;不改 `ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY。
|
||||
|
||||
⑭ **修改方式声明(AGENTS.md):** 本计划执行含文件写入与删除,由**派发子代理执行**(`gsd-executor`),非内联执行。
|
||||
⑭ **已知阻断(须一并告知并记入 SUMMARY):** 唯一已测阻断 = OI-11 数据端跨端引用;渲染端依赖链已就位(`src/bootstrap.ts` → `./client/entry` / `./client/ui/load`,后者存在),此前「`bootstrap.ts` 仍指向 `packages-user/client-modules/src/ui/load`」的判断**已过时**。若渲染端仍因用户其它 WIP 失败,记录完整错误与归因,**不得**修改范围外文件。
|
||||
|
||||
⑮ **关卡性质:** **无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户回复;回复「可以执行」即视为通过。** 若用户就 OI-1..OI-10 给出指定,按指定执行。
|
||||
⑮ **验证方式:** Task 1 = `pnpm exec vitest run script/vite-plugin-data-isolation.test.ts`(真实 `vite.build()`:拒渲染端 specifier / 拒渲染端解析路径(传递依赖)/ 拒 `src/client` 渲染目录 / 通过纯数据端并输出 ESM / 不误判数据端路径)+ 结构断言;Task 2 = `pnpm exec vitest run script/build-resource.test.ts` + 构建脚本结构断言 + **渲染端 + content + LICENSE 产物门禁** + `git status` 范围门禁 + `pnpm exec tsx script/build-game.ts` 实跑(数据端 OI-11 预期终止 → 记为 pending 并继续;条件门禁判定 `dist/data/data.js`);**不设全仓 `check:type` / `build` / 全量 `test:ci` 门禁**。
|
||||
|
||||
⑯ **修改方式声明(AGENTS.md):** 本计划执行含文件写入与删除,由**派发子代理执行**(`gsd-executor`),非内联执行。
|
||||
|
||||
⑰ **关卡性质:** **无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户回复;回复「可以执行」即视为通过。** 若用户就 OI-1..OI-11 给出指定,按指定执行。
|
||||
</action>
|
||||
<decision>是否按本计划(09-21)执行「任务 11 · 构建重构」——新增数据端/渲染端分离 Vite 插件并注入数据端库模式 ESM 构建(`dist/data/data.js`)、重写 `build-resource` 把 `src/content` 压缩拷贝到 `dist/content`(jsonc 保留系统标记、fontmin、不分块不 zip)、重写 `build-game` 去掉旧模板/babel/资源分块/源码 zip/资源地图/dist.zip 并保留 `LICENSE`、删除 `build-packages.ts` / `build-lib.ts` 与对应 npm script、`index.html` 去旧样式表 link(样式经入口 JS 引入)、保留 `vue-tsc --noEmit`、微调 `manualChunks`;不删 `public/**`、不改 `src/**`(除 OI-5 一行)、不动用户其它并发 WIP。</decision>
|
||||
<decision>是否按本计划(09-21)执行「任务 11 · 构建重构」——新增数据端/渲染端分离 Vite 插件并注入数据端库模式 ESM 构建(`dist/data/data.js`)、重写 `build-resource` 把 `src/content`(23 个 jsonc)压缩拷贝到 `dist/content`(jsonc 保留系统标记、fontmin、不分块不 zip)、重写 `build-game` 去掉旧模板/babel/资源分块/源码 zip/资源地图/dist.zip 并保留 `LICENSE`、删除 `build-packages.ts` / `build-lib.ts` 与对应 npm script、`index.html` 去旧样式表 link、保留 `vue-tsc --noEmit`、微调 `manualChunks`;**不删 `public/**`、不改 `src/**`(含跨端引用链,交由用户修复)、不动用户其它并发 WIP**;**接受 OI-11 数据端预期阻断并按渲染端 + content + LICENSE 分层门禁**。</decision>
|
||||
<context>
|
||||
AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作与**删除两个脚本**。`src/content` 全部 20 个 jsonc 均为「前缀注释 + 严格 JSON」,加载器 `PrefixedJSONCProcessor` 依赖 `// --- SYSTEM PREFIX END --- //` 的 split(必须恰好 2 段),故压缩保留标记是运行期兼容硬约束(与 D-96「去注释」并不冲突:去掉的是标记**之外**的注释)。`src/content/font` 无字体二进制、`src/content/item.ts` 与 `core.jsonc` 的 `itemEffectDir` 不匹配、`src/bootstrap.ts` 仍指向已移动的 UI 文件——均属用户 WIP 或既有缺口,本计划只记录不改。工作树含用户并发改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等),提交时须逐条核对暂存面、只暂存本计划 `files_modified`。行号为规划日实测、可能漂移,门禁以结构/行为断言为准。
|
||||
AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。执行期含写操作与**删除两个脚本**。`src/content` 共 **23** 个 jsonc,均为「前缀注释 + 严格 JSON」且每个恰好一个标记,加载器 `PrefixedJSONCProcessor` 依赖 `// --- SYSTEM PREFIX END --- //` 的 split(必须恰好 2 段),故压缩保留标记是运行期兼容硬约束(去的是标记**之外**的注释)。`src/content/font` 无字体二进制、`src/content/item.ts` 与 `core.jsonc` 的 `itemEffectDir` 不匹配——属既有缺口,本计划只记录不改。**OI-11 是本次最重要的已知事实:** 数据端入口跨端引用会让 D-94 插件按设计终止数据端构建;用户已明确「你改 src,我做脚本」,故该链路由用户修复,本计划不改 `src/**`,并以「渲染端 + content + LICENSE 必过、`data.js` 条件门禁」保证今天的可验证性。工作树含用户并发改动(`index.html`、`packages-user/client-modules/src/**`、`src/**` 等),提交时须逐条核对暂存面、只暂存本计划 `files_modified`。行号为规划日实测、可能漂移,门禁以结构/行为断言为准。
|
||||
</context>
|
||||
<options>
|
||||
<option id="execute-default">
|
||||
<name>可以执行(按本计划默认口径:插件命名按 OI-1、content 命名按 OI-2、jsonc 保留标记且 `Type:` 注释丢失按 OI-3、无字体时跳过压缩按 OI-4、样式 import 加到 `src/bootstrap.ts` 按 OI-5、`build:game` 去掉 `declare.ts` 前置但保留 `vue-tsc` 按 OI-6、`manualChunks` 去 `antdv` 保 `audio` 且 legacy 维持按 OI-7、`item.js` 缺口留给用户按 OI-8、全量构建尽力实跑并记录阻断按 OI-9、遗留文件/依赖保留按 OI-10)</name>
|
||||
<pros>D-93..D-100 逐条落地;分离检查从体积启发式升级为模块图硬门禁;数据端 ESM 与 `dist/content` 落点与加载器实际解析路径一致;jsonc 标记保留避免运行期配置解析失败;产物清单与 D-97 对齐;插件与 content 处理均有运行期单测,用户 WIP 不阻断门禁</pros>
|
||||
<cons>引入若干待批准命名;会删除两个旧构建脚本;全量构建当前可能因用户 WIP 失败,artifacts 门禁可能只能记为 BLOCKED</cons>
|
||||
<name>可以执行(按本计划默认口径:插件命名按 OI-1、content 命名按 OI-2、jsonc 保留标记且 `Type:` 注释丢失按 OI-3、无字体时跳过压缩按 OI-4、样式 import 交由用户在 `src/**` 侧完成按 OI-5、`build:game` 去掉 `declare.ts` 前置但保留 `vue-tsc` 按 OI-6、`manualChunks` 去 `antdv` 保 `audio` 且 legacy 维持按 OI-7、`item.js` 缺口留给用户按 OI-8、全量构建按 OI-9 / OI-11 分层门禁、遗留文件/依赖保留按 OI-10、数据端跨端引用交由用户修复按 OI-11)</name>
|
||||
<pros>D-93..D-100 逐条落地;分离检查从体积启发式升级为模块图硬门禁;数据端 ESM 与 `dist/content` 落点与加载器实际解析路径一致;jsonc 标记保留避免运行期配置解析失败;产物清单与 D-97 对齐;插件与 content 处理均有运行期单测;OI-11 预期阻断不阻塞渲染端 + content + LICENSE 验证</pros>
|
||||
<cons>引入若干待批准命名;会删除两个旧构建脚本;`dist/data/data.js` 在用户修复 `src` 前不可产出(已记录为 OI-11 pending,而非本计划失败)</cons>
|
||||
</option>
|
||||
<option id="revise">
|
||||
<name>修订 OI-1..OI-10 的任一取向后重新汇报</name>
|
||||
<name>修订 OI-1..OI-11 的任一取向后重新汇报</name>
|
||||
<pros>避免带着未确认的命名 / 样式落点 / 前置链 / 标注口径落码</pros>
|
||||
<cons>须修订 PLAN.md 后重新汇报</cons>
|
||||
</option>
|
||||
</options>
|
||||
<resume-signal>回复「可以执行」即可(默认采用 OI-1..OI-10 的提议口径)。如需调整任一 `open_items`,请在回复中一并指明。</resume-signal>
|
||||
<resume-signal>回复「可以执行」即可(默认采用 OI-1..OI-11 的提议口径)。如需调整任一 `open_items`,请在回复中一并指明。</resume-signal>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const must=['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','package.json','index.html','src/data.ts','src/content/core.jsonc','src/content/client.jsonc','src/content/texture/.list.jsonc','src/content/font/.list.jsonc','packages-user/data-state/src/loader/jsoncProcessor.ts','packages-user/data-state/src/loader/loader.ts'];for(const p of must){if(!fs.existsSync(p))bad.push('missing ref: '+p)}const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));for(const s of ['build:packages','build:lib','build:game']){if(!(s in pkg.scripts))bad.push('package.json missing script '+s)}const bg=fs.readFileSync('script/build-game.ts','utf8');for(const t of ['checkClientData','splitResource','source-code.zip','loadList.json']){if(bg.indexOf(t)<0)bad.push('build-game.ts no longer contains expected legacy token: '+t)}const html=fs.readFileSync('index.html','utf8');if(html.indexOf('styles.css')<0)bad.push('index.html no longer has the legacy stylesheet link');const jp=fs.readFileSync('packages-user/data-state/src/loader/jsoncProcessor.ts','utf8');if(jp.indexOf('SYSTEM PREFIX END')<0&&jp.indexOf('prefixSplit')<0)bad.push('jsoncProcessor.ts: expected prefix split logic');for(const p of ['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts','script/build-resource.test.ts']){if(fs.existsSync(p))bad.push('post-state already exists: '+p)}if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK pre-execution state: legacy build scripts + scripts entries + legacy index.html + content loader present; new plugin/tests absent')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "PRESTATE FAILED"(旧构建脚本 / `package.json` scripts / `index.html` 旧 link / 加载器标记逻辑任一缺失,或新插件与测试文件已存在)</fails_when>
|
||||
<human-check>用户在对话中明确回复「可以执行」(若就 OI-1..OI-10 给出非默认指定,须在汇报与后续任务中据其指定执行)</human-check>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const must=['script/build-game.ts','script/build-resource.ts','script/build-packages.ts','script/build-lib.ts','package.json','index.html','src/data.ts','src/content/core.jsonc','src/content/client.jsonc','src/content/texture/.list.jsonc','src/content/font/.list.jsonc','packages-user/data-state/src/loader/jsoncProcessor.ts','packages-user/data-state/src/loader/loader.ts'];for(const p of must){if(!fs.existsSync(p))bad.push('missing ref: '+p)}const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));for(const s of ['build:packages','build:lib','build:game']){if(!(s in pkg.scripts))bad.push('package.json missing script '+s)}const bg=fs.readFileSync('script/build-game.ts','utf8');for(const t of ['checkClientData','splitResource','source-code.zip','loadList.json']){if(bg.indexOf(t)<0)bad.push('build-game.ts missing legacy token: '+t)}const html=fs.readFileSync('index.html','utf8');if(html.indexOf('styles.css')<0)bad.push('index.html missing legacy stylesheet link');const jp=fs.readFileSync('packages-user/data-state/src/loader/jsoncProcessor.ts','utf8');if(jp.indexOf('SYSTEM PREFIX END')<0&&jp.indexOf('prefixSplit')<0)bad.push('jsoncProcessor.ts missing prefix split logic');for(const p of ['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts','script/build-resource.test.ts']){if(fs.existsSync(p))bad.push('post-state already exists: '+p)}const walk=d=>fs.readdirSync(d,{withFileTypes:true}).flatMap(e=>e.isDirectory()?walk(d+'/'+e.name):[d+'/'+e.name]);const j=walk('src/content').filter(p=>p.endsWith('.jsonc'));if(j.length!==23)bad.push('expected 23 jsonc under src/content, got '+j.length);for(const p of j){const t=fs.readFileSync(p,'utf8');const n=t.split('// --- SYSTEM PREFIX END --- //').length-1;if(n!==1)bad.push('marker count != 1: '+p)}if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}const chain=['src/data.ts','src/data/entry.ts','src/constant.ts','src/client/ui/constant.ts'].every(p=>fs.existsSync(p));console.log('OK pre-execution state: legacy build scripts + scripts entries + legacy index.html + 23 jsonc (marker x1) present; new plugin/tests absent; OI-11 chain present='+chain)"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "PRESTATE FAILED"(旧构建脚本 / `package.json` scripts / `index.html` 旧 link / 加载器标记逻辑任一缺失;jsonc 数量不是 23 或某文件标记次数不是 1;新插件与测试文件已存在)</fails_when>
|
||||
<human-check>用户在对话中明确回复「可以执行」(若就 OI-1..OI-11 给出非默认指定,须在汇报与后续任务中据其指定执行)</human-check>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 用户回复中明确包含「可以执行」
|
||||
- 汇报内容涵盖:要解决的问题、范围(两个脚本重构 + 两个脚本删除 + npm script 调整)、D-94 插件扫描口径(specifier + 解析路径 + 报错终止 + 不误判数据端)与取代 `checkClientData`、D-95 库模式 ESM 与 `dist/data/data.js`、D-96 content 压缩(**保留系统标记**、fontmin、不分块不 zip)、D-97 产物清单与 `LICENSE` 保留、`index.html` 去 link、D-99 保留 `vue-tsc`、D-100 去 babel/微调 `manualChunks`、**全部拟新增命名(OI-1 / OI-2)**、写操作边界(含不删 `public/**`、`src/**` 仅 OI-5 一行)、验证方式、已知阻断(用户 WIP)、OI-1..OI-10
|
||||
- 汇报中已原样告知:**删除 `build-packages.ts` / `build-lib.ts` 与 `build:packages` / `build:lib` 属不可逆的仓库内删除(可经 git 恢复)**、**content 压缩保留标记是对 D-96 的兼容性补充**、**全量构建可能因用户 WIP 失败且不得修 `src/**`**
|
||||
- 汇报内容涵盖:要解决的问题、范围(两个脚本重构 + 两个脚本删除 + npm script 调整)、D-94 插件扫描口径(specifier + 解析路径 + `src/client` + 报错终止 + 不误判数据端)与取代 `checkClientData`、D-95 库模式 ESM 与 `dist/data/data.js`、D-96 content 压缩(**保留系统标记**、**23 个 jsonc**、fontmin、不分块不 zip)、D-97 产物清单与 `LICENSE` 保留、`index.html` 去 link、D-99 保留 `vue-tsc`、D-100 去 babel/微调 `manualChunks`、**全部拟新增命名(OI-1 / OI-2)**、写操作边界(含不删 `public/**`、**`src/**` 零改动**)、验证方式、OI-1..OI-11
|
||||
- 汇报中已**原样完整复述 OI-11 的实测链**:`src/data.ts` → `src/data/entry.ts:2`(`import { CORE_URL, HERO_FACE } from '../constant'`)→ `src/constant.ts:2`(`import { MAIN_HEIGHT, MAIN_WIDTH } from './client/ui/constant'`)→ `src/client/ui/constant.ts:1`(`import { ElementLocator, Font } from '@motajs/render'`,`Font` 值使用),并说明 `dist/data/data.js` 在用户修复前不可产出
|
||||
- 汇报中已原样告知**门禁分层**:数据端被插件按设计终止 = 预期 pending(不是本计划失败);渲染端 + `dist/content` + `LICENSE` 必过;`dist/data/data.js` 为条件门禁
|
||||
- 汇报中已原样告知:**删除 `build-packages.ts` / `build-lib.ts` 与 `build:packages` / `build:lib` 属不可逆的仓库内删除(可经 git 恢复)**、**content 压缩保留标记是对 D-96 的兼容性补充**
|
||||
- 汇报中已原样告知**关卡性质**(必须停下等待;不因触发方式或 `yolo` / `auto_advance` 自动放行;回复「可以执行」即通过)
|
||||
</acceptance_criteria>
|
||||
<done>用户明确回复「可以执行」;在此之前未执行任何任务、未修改或删除任何文件、未派发任何执行子代理</done>
|
||||
@ -225,7 +247,7 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
|
||||
<task type="tracer">
|
||||
<name>Task 1: 数据端/渲染端分离 Vite 插件端到端最小切片 —— `dataIsolationPlugin` + 真实 `vite.build()` 运行期单测</name>
|
||||
<precondition>Task 0 已闭环(用户回复「可以执行」;OI-1 已获批或按用户指定命名)。`vite` 与 `vitest` 可用;`script/vite-plugin-data-isolation.ts` 与 `script/vite-plugin-data-isolation.test.ts` 尚不存在;`packages-user/*` 与 `packages/*` 的渲染端/数据端包名如前置读取所列。</precondition>
|
||||
<precondition>Task 0 已闭环(用户回复「可以执行」;OI-1 已获批或按用户指定命名)。`vite` 与 `vitest` 可用;`script/vite-plugin-data-isolation.ts` 与 `script/vite-plugin-data-isolation.test.ts` 尚不存在;`packages-user/*` 与 `packages/*` 的渲染端/数据端包名如前置读取所列;`src/client/**` 已存在(用户迁移后的渲染端 UI 目录)。</precondition>
|
||||
<files>script/vite-plugin-data-isolation.ts, script/vite-plugin-data-isolation.test.ts</files>
|
||||
<read_first>
|
||||
- .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-94**;D-04 / D-56)
|
||||
@ -239,10 +261,10 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
<action>
|
||||
(1) **新建 `script/vite-plugin-data-isolation.ts`(命名以 OI-1 获批结果为准):**
|
||||
- 自 `vite` 导入**类型** `Plugin`(`import { Plugin } from 'vite'`,按 dev.md 不使用 `import type`)。
|
||||
- 模块内声明两组模块级只读常量(不导出,避免额外命名面):**specifier 禁令**——`@user/client-` 前缀、`@user/entry-client`、`@motajs/render` / `@motajs/render-vue` / `@motajs/client` / `@motajs/client-base` / `@motajs/system` / `@motajs/audio` / `@motajs/legacy-*`、`vue` 与 `vue/*`、`@vue/*`、`ant-design-vue` 与子路径、`@ant-design/icons-vue` 与子路径;**解析路径禁令**——`[/\\]packages-user[\\/](client-modules|client-base|client-system|entry-client)[\\/]`、`[/\\]packages[\\/](render|render-vue|client|client-base|system|audio|legacy-[^\\/]+)[\\/]`、`[/\\]node_modules[\\/](vue|@vue|ant-design-vue|@ant-design)[\\/]`。
|
||||
- 导出 `dataIsolationPlugin(): Plugin`,返回对象包含:`name`(例如 `'mota-data-isolation'`)、`enforce: 'pre'`、`apply: 'build'`、`resolveId(source, importer)` 与 `moduleParsed(id)`。
|
||||
- `resolveId`:对 `source` 先做 specifier 禁令匹配;命中即调用 `this.error(...)`,错误消息必须包含**命中的模块标识**、**导入者**(`importer ?? '<entry>'`)与「data-side build must not depend on render-side modules」的明确说明,使构建立即终止且错误可定位。未命中返回 `null`(交回默认解析)。
|
||||
- `moduleParsed(id)`:对**解析后的绝对路径**做路径禁令匹配;命中即 `this.error(...)`(消息含该绝对路径与其 importers(可由 `this.getModuleInfo(id)?.importers` 提供,若为空则省略))。这一路覆盖经别名解析、传递依赖与 `node_modules` 的真实落点。
|
||||
- 模块内声明两组模块级只读常量(不导出,避免额外命名面):**specifier 禁令**——`@user/client-` 前缀、`@user/entry-client`、`@motajs/render` / `@motajs/render-vue` / `@motajs/client` / `@motajs/client-base` / `@motajs/system` / `@motajs/audio` / `@motajs/legacy-*`、`vue` 与 `vue/*`、`@vue/*`、`ant-design-vue` 与子路径、`@ant-design/icons-vue` 与子路径;**解析路径禁令**——`[/\\]packages-user[\\/](client-modules|client-base|client-system|entry-client)[\\/]`、`[/\\]packages[\\/](render|render-vue|client|client-base|system|audio|legacy-[^\\/]+)[\\/]`、**`[/\\]src[\\/]client[\\/]`**、`[/\\]node_modules[\\/](vue|@vue|ant-design-vue|@ant-design)[\\/]`。
|
||||
- 导出 `dataIsolationPlugin(): Plugin`,返回对象包含:`name: 'mota-data-isolation'`(OI-1)、`enforce: 'pre'`、`apply: 'build'`、`resolveId(source, importer)` 与 `moduleParsed(id)`。
|
||||
- `resolveId`:对 `source` 先做 specifier 禁令匹配;命中即调用 `this.error(...)`,错误消息必须以 **`[mota-data-isolation]`** 开头并包含**命中的模块标识**、**导入者**(`importer ?? '<entry>'`)与「data-side build must not depend on render-side modules」的明确说明,使构建立即终止且错误可定位。未命中返回 `null`(交回默认解析)。
|
||||
- `moduleParsed(id)`:对**解析后的绝对路径**做路径禁令匹配;命中即 `this.error(...)`(消息同样以 `[mota-data-isolation]` 开头,含该绝对路径与其 importers(可由 `this.getModuleInfo(id)?.importers` 提供,若为空则省略))。这一路覆盖经别名解析、传递依赖、`src/client/**` 与 `node_modules` 的真实落点——即使 Vite 别名解析先于用户插件执行,仍能拦截。
|
||||
- **不得**把数据端包(`@user/data-*`、`@motajs/common`、`@motajs/loader`、`@motajs/types`、`@motajs/animate`、`anon-tokyo`)列入禁令;**不得**对字符串做宽泛的 `client` 子串匹配(避免 `client.jsonc` 之类的误判)。
|
||||
- 按 dev.md 为导出函数与其参数写 jsDoc;模块顶层零副作用(只声明常量与函数)。
|
||||
|
||||
@ -250,78 +272,84 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
- 使用 `node:fs/promises` 的 `mkdtemp`(`os.tmpdir()`)、`mkdir`、`writeFile`、`rm` 与 `node:path`,每个用例独立临时目录,`afterEach` 清理。
|
||||
- 调用**真实** `vite.build()`:`configFile:false`、`root: <temp>`、`logLevel:'silent'`、`build: { write:false, minify:false, lib: { entry: <temp entry>, formats: ['es'], fileName: 'data' }, copyPublicDir:false }`、`plugins:[dataIsolationPlugin()]`。
|
||||
- 用例(每个 `it` 前一行注释说明测试内容):
|
||||
1. **渲染端 specifier 被拒**:entry 导入 `vue`;断言 `build()` rejects,错误消息含 `vue` 与插件错误标识;同时在临时根写入同名 `vue` 无关文件以确保拒绝来自插件而非解析失败(或用 `resolve.alias` 把 `vue` 指到本地 fixture,仍应先被 `resolveId` 拦截)。
|
||||
1. **渲染端 specifier 被拒**:entry 导入 `vue`;断言 `build()` rejects,错误消息含 `[mota-data-isolation]`、`vue` 与 `data-side build must not depend on render-side`;为排除「解析失败」干扰,可在 `resolve.alias` 把 `vue` 指到本地 fixture,但仍应先被 `resolveId` 拦截。
|
||||
2. **渲染端解析路径被拒(传递依赖)**:临时目录内构造 `<temp>/packages-user/client-modules/fake.ts`,entry 相对导入它;断言 rejects 且消息含该路径片段。
|
||||
3. **纯数据端通过且产物为 ESM**:entry 导入 `<temp>/packages-user/data-state/fake.ts`(导出常量)并 re-export;断言 `build()` resolve,输出含 chunk 且其 `code` 含 `export` 语句(ESM 产物),不抛错。
|
||||
4. **数据端路径不误判**:entry 导入 `@motajs` 风格名不可解析时改用相对路径 `<temp>/packages-user/data-common/fake.ts`;断言通过(证明禁令只覆盖渲染端路径)。
|
||||
3. **`src/client` 渲染目录被拒**:临时目录内构造 `<temp>/src/client/ui/fake.ts`,entry 相对导入它;断言 rejects 且消息含 `src/client` 路径片段(覆盖用户迁移后的真实渲染端目录形态)。
|
||||
4. **纯数据端通过且产物为 ESM**:entry 导入 `<temp>/packages-user/data-state/fake.ts`(导出常量)并 re-export;断言 `build()` resolve,输出含 chunk 且其 `code` 含 `export` 语句(ESM 产物),不抛错。
|
||||
5. **数据端路径不误判**:entry 相对导入 `<temp>/packages-user/data-common/fake.ts`;断言通过(证明禁令只覆盖渲染端路径)。
|
||||
- 断言尽量稳定(不依赖 chunk 文件名/顺序),只依赖 rejects/resolve 与错误消息关键字。
|
||||
</action>
|
||||
<behavior>
|
||||
- entry `import 'vue'` → `build()` rejects;错误消息含 `vue` 与 `data-side build must not depend on render-side`。
|
||||
- entry `import 'vue'` → `build()` rejects;错误消息含 `[mota-data-isolation]`、`vue` 与 `data-side build must not depend on render-side`。
|
||||
- entry 相对导入 `<temp>/packages-user/client-modules/fake.ts` → rejects;错误消息含该路径(证明 `moduleParsed` 路径兜底生效,且覆盖传递依赖)。
|
||||
- entry 相对导入 `<temp>/src/client/ui/fake.ts` → rejects;错误消息含 `src/client`(证明迁移后的渲染端 UI 目录被覆盖)。
|
||||
- entry 相对导入 `<temp>/packages-user/data-state/fake.ts` → resolve;输出 chunk `code` 含 `export`(ESM)。
|
||||
- entry 相对导入 `<temp>/packages-user/data-common/fake.ts` → resolve(不误判数据端)。
|
||||
</behavior>
|
||||
<reversibility rating="reversible">仅新增一个插件文件与一个测试文件;`Remove-Item` 两文件即可整体回退,未接线到构建脚本、未改任何既有文件。</reversibility>
|
||||
<verify>
|
||||
<automated>pnpm exec vitest run script/vite-plugin-data-isolation.test.ts</automated>
|
||||
<fails_when>non-zero exit, 或任一断言失败(渲染端 specifier 未被拒 / 渲染端解析路径未被拒 / 纯数据端构建失败 / 数据端路径被误判 / 错误消息缺少模块标识或导入者)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const p='script/vite-plugin-data-isolation.ts';if(!fs.existsSync(p))bad.push('plugin file missing');else{const t=fs.readFileSync(p,'utf8');if(!/export\s+function\s+dataIsolationPlugin\s*\(/.test(t))bad.push('dataIsolationPlugin not exported');if(!/enforce\s*:\s*'pre'/.test(t))bad.push('plugin is not enforce:pre');if(t.indexOf('moduleParsed')<0)bad.push('plugin lacks moduleParsed resolved-path check');if(t.indexOf('this.error(')<0)bad.push('plugin does not abort via this.error');for(const tok of ['@user/client-','@motajs/render','vue','ant-design-vue']){if(t.indexOf(tok)<0)bad.push('plugin forbidden set missing token: '+tok)}for(const tok of ['@user/data-','@motajs/common']){if(t.indexOf(tok)>=0)bad.push('plugin must not forbid data-side token: '+tok)}}const tp='script/vite-plugin-data-isolation.test.ts';if(!fs.existsSync(tp))bad.push('plugin test file missing');else{const t=fs.readFileSync(tp,'utf8');if(t.indexOf('vite')<0)bad.push('test does not use real vite build');if(t.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin')}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin structure: enforce pre + specifier + resolved-path checks + abort + no data-side false positives')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / 缺 `moduleParsed` 路径检查 / 未 `this.error` 终止 / 禁令缺少关键 token / 误把数据端包列入禁令)</fails_when>
|
||||
<fails_when>non-zero exit, 或任一断言失败(渲染端 specifier 未被拒 / 渲染端解析路径未被拒 / `src/client` 目录未被拒 / 纯数据端构建失败 / 数据端路径被误判 / 错误消息缺少插件标识、模块标识或导入者)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const p='script/vite-plugin-data-isolation.ts';if(!fs.existsSync(p))bad.push('plugin file missing');else{const t=fs.readFileSync(p,'utf8');if(!/export\s+function\s+dataIsolationPlugin\s*\(/.test(t))bad.push('dataIsolationPlugin not exported');if(!/enforce\s*:\s*'pre'/.test(t))bad.push('plugin is not enforce:pre');if(t.indexOf('moduleParsed')<0)bad.push('plugin lacks moduleParsed resolved-path check');if(t.indexOf('this.error(')<0)bad.push('plugin does not abort via this.error');if(t.indexOf('mota-data-isolation')<0)bad.push('plugin error tag missing');for(const tok of ['@user/client-','@motajs/render','vue','ant-design-vue']){if(t.indexOf(tok)<0)bad.push('plugin forbidden set missing token: '+tok)}if(!/src\[\\\\\\/\]client/.test(t))bad.push('plugin lacks src/client resolved-path guard');for(const tok of ['@user/data-','@motajs/common']){if(t.indexOf(tok)>=0)bad.push('plugin must not forbid data-side token: '+tok)}}const tp='script/vite-plugin-data-isolation.test.ts';if(!fs.existsSync(tp))bad.push('plugin test file missing');else{const t=fs.readFileSync(tp,'utf8');if(t.indexOf('vite')<0)bad.push('test does not use real vite build');if(t.indexOf('dataIsolationPlugin')<0)bad.push('test does not import the plugin')}if(bad.length>0){console.error('PLUGIN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK plugin structure: enforce pre + specifier + resolved-path (+ src/client) checks + abort + no data-side false positives')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "PLUGIN GATE FAILED"(插件文件缺失 / 未导出 `dataIsolationPlugin` / 非 `enforce: 'pre'` / 缺 `moduleParsed` 路径检查 / 未 `this.error` 终止 / 缺错误标识 / 禁令缺少关键 token 或 `src/client` 守卫 / 误把数据端包列入禁令)</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `script/vite-plugin-data-isolation.ts` 存在并按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` specifier 检查 + `moduleParsed` 解析路径检查 + `this.error(...)` 终止
|
||||
- `script/vite-plugin-data-isolation.ts` 存在并按 OI-1 命名导出 `dataIsolationPlugin`;`enforce: 'pre'` + `resolveId` specifier 检查 + `moduleParsed` 解析路径检查(含 `src/client/**`)+ `this.error(...)` 终止;错误消息带 `[mota-data-isolation]` 标识
|
||||
- 禁令覆盖 D-94 所列渲染端面(`@user/client-*`、`@user/entry-client`、`@motajs/render*` / `client*` / `system` / `audio` / `legacy-*`、`vue`、`@vue/*`、`ant-design-vue`、`@ant-design/icons-vue` 及其解析路径);未把 `@user/data-*` / `@motajs/common` 等数据端依赖列入
|
||||
- 运行期单测用**真实 `vite.build()`** 证明四类行为(拒 specifier / 拒解析路径(传递依赖)/ 纯数据端通过且 ESM / 数据端不误判),且每个 `it` 前有一行注释
|
||||
- 运行期单测用**真实 `vite.build()`** 证明五类行为(拒 specifier / 拒解析路径(传递依赖)/ 拒 `src/client` / 纯数据端通过且 ESM / 数据端不误判),且每个 `it` 前有一行注释
|
||||
- 未改任何既有文件;未接线到 `build-game.ts`(留给 Task 2)
|
||||
</acceptance_criteria>
|
||||
<done>分离插件与运行期单测就位:真实 Vite 构建在渲染端依赖上被阻断、在纯数据端构建通过并产出 ESM;结构断言全绿;零既有文件改动</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: 构建编排重写与收口 —— `build-resource`(content 压缩/拷贝/字体)+ `build-game`(渲染端 → 数据端 ESM → content → LICENSE)+ 删除旧脚本 + npm script / index.html + 产物与范围门禁 + SUMMARY</name>
|
||||
<precondition>Task 1 已完成(`script/vite-plugin-data-isolation.ts` 导出 `dataIsolationPlugin` 并通过单测);Task 0 已闭环(OI-2..OI-10 已获批或按用户指定)。`src/content/**` 的 20 个 jsonc 均含系统标记;`src/content/font/` 无字体二进制;`public/**` 仍存在(本计划不删,仅阻止其进入 dist)。</precondition>
|
||||
<files>script/build-resource.ts, script/build-resource.test.ts, script/build-game.ts, script/build-packages.ts, script/build-lib.ts, package.json, index.html, src/bootstrap.ts, .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md</files>
|
||||
<name>Task 2: 构建编排重写与收口 —— `build-resource`(content 压缩/拷贝/字体)+ `build-game`(渲染端 → 数据端 ESM(OI-11 预期失败分类)→ content → LICENSE)+ 删除旧脚本 + npm script / index.html + 分层产物门禁 + SUMMARY</name>
|
||||
<precondition>Task 1 已完成(`script/vite-plugin-data-isolation.ts` 导出 `dataIsolationPlugin` 并通过单测);Task 0 已闭环(OI-2..OI-11 已获批或按用户指定)。`src/content/**` 共 **23** 个 jsonc,每个恰好一个系统标记;`src/content/font/` 无字体二进制;`public/**` 仍存在(本计划不删,仅阻止其进入 dist);**OI-11 跨端引用链仍未修复时**,数据端构建会被插件按设计终止(预期,不是本计划失败)。</precondition>
|
||||
<files>script/build-resource.ts, script/build-resource.test.ts, script/build-game.ts, script/build-packages.ts, script/build-lib.ts, package.json, index.html, .planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md</files>
|
||||
<read_first>
|
||||
- .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(**D-93 / D-95 / D-96 / D-97 / D-98 / D-99 / D-100**;D-56)
|
||||
- script/build-game.ts(旧编排:`logProgress` 步骤表、`buildClient`、`buildData`、`checkClientData`、`getAllChars`、`generateResourceJSON`、`buildGame` 的 toCopy / archiver / compressing / 服务器 exe / `dist.zip`)
|
||||
- script/build-resource.ts(旧 `splitResource` / `CompressedUsage` / `LoadDataType` / `SplittedResource`)
|
||||
- script/utils.ts(`formatSize`);script/build-packages.ts / script/build-lib.ts(待删除内容,确认无其它引用)
|
||||
- package.json(scripts 面);index.html(样式 link 行);src/styles.less(仅 7 行 `#game-draw` 居中样式)
|
||||
- src/content/**(含 `texture/.list.jsonc` / `maps/.list.jsonc` / `defaults/*.jsonc` / `font/.list.jsonc` / `item.ts`)
|
||||
- packages-user/data-state/src/loader/jsoncProcessor.ts(标记 split);packages-user/data-state/src/loader/loader.ts:70-142(生产态路径);src/constant.ts(`content/core.jsonc` / `content/client.jsonc`)
|
||||
- src/content/**(**23 个 jsonc**:含 `texture/.list.jsonc` / `maps/.list.jsonc` / `defaults/*.jsonc` / `font/.list.jsonc` / `item.ts` / `texture/*.png`)
|
||||
- packages-user/data-state/src/loader/jsoncProcessor.ts(标记 split);packages-user/data-state/src/loader/loader.ts:70-142(生产态路径);src/constant.ts(`content/core.jsonc` / `content/client.jsonc`);src/data/entry.ts + src/client/ui/constant.ts(**OI-11 跨端引用链**)
|
||||
- packages/audio/package.json 与 packages/audio/src/{source,decoder}.ts(`audio` chunk 依据);全仓源码中 `ant-design-vue` 无导入的检索结论
|
||||
- AGENTS.md(禁改既有注释、修改方式须预先声明);dev.md(命名、interface、jsDoc、非空判断)
|
||||
- AGENTS.md(禁改既有注释、修改方式须预先声明);dev.md(命名、interface、jsDoc、非空判断、尽量不写 try-catch-finally——用 Promise `.catch` 与现有风格一致)
|
||||
</read_first>
|
||||
<action>
|
||||
(0) **前置确认:** 仅按 Task 0 获批口径执行;若 OI-2 命名被调整,以用户指定为准并在 SUMMARY 记录。
|
||||
(0) **前置确认:** 仅按 Task 0 获批口径执行;若 OI-2 命名被调整,以用户指定为准并在 SUMMARY 记录。**本任务不修改 `src/**`。**
|
||||
|
||||
(1) **重写 `script/build-resource.ts`(命名以 OI-2 为准):** 删除 `splitResource` / `CompressedUsage` / `LoadDataType` / `SplittedResource` / JSZip 相关实现,改为 content 处理模块:
|
||||
(1) **重写 `script/build-resource.ts`(命名以 OI-2 为准):** 删除 `splitResource` / `CompressedUsage` / `LoadDataType` / `SplittedResource` / JSZip 与 `RequiredData` / `RequiredIconsData` 引用,改为 content 处理模块:
|
||||
- 声明并导出 `IContentBuildOptions`:`readonly sourceDir: string`(`src/content`)、`readonly outputDir: string`(`dist/content`)、`readonly chars: string`(fontmin glyph 子集文本)。
|
||||
- 声明并导出 `IContentBuildStats`:`readonly files: number`(写入文件数)、`readonly jsonc: number`(压缩的 jsonc 数)、`readonly fonts: number`(fontmin 处理的字体数)。
|
||||
- 导出 `minifyJSONC(text: string): string`:以系统标记 `'// --- SYSTEM PREFIX END --- //'` 定位;若标记出现则要求 `split` 结果恰好 2 段(否则抛出带文件语义的错误),对标记之后的正文 `JSON.stringify(JSON.parse(body))`;输出 = 标记行 + `'\n'` + 压缩正文;若无标记则对整段 `JSON.stringify(JSON.parse(text))`(非法 JSON 抛出并带上上下文)。**严禁**丢掉标记行;**不得**改调用方后缀。
|
||||
- 导出 `minifyJSONC(text: string): string`:以系统标记 `'// --- SYSTEM PREFIX END --- //'` 定位;`split` 后:**1 段** = 无标记,对整段 `JSON.stringify(JSON.parse(text))`;**2 段** = 标记存在,输出 `标记 + '\n' + JSON.stringify(JSON.parse(parts[1]))`;**其它段数** = 标记异常,抛出带文件语义的错误。**严禁**丢掉标记行;**不得**改调用方后缀。
|
||||
- 导出 `buildContent(options: IContentBuildOptions): Promise<IContentBuildStats>`:递归遍历 `sourceDir`(`readdir` with `withFileTypes`)并在 `outputDir` 内镜像目录结构:
|
||||
- `*.jsonc` → `minifyJSONC` 后写入同名路径;
|
||||
- 字体扩展(`.ttf` / `.otf` / `.woff` / `.woff2`)→ 用 `Fontmin` 的 `glyph({ text: options.chars })` 插件压缩到 `outputDir` 对应子目录(保持文件名);
|
||||
- 其它文件(PNG 等)→ `fs-extra.copy` 原样拷贝;
|
||||
- 其它文件(PNG / `.ts` 等)→ `fs-extra.copy` 原样拷贝;
|
||||
- `sourceDir/font` 下若**无**字体二进制:跳过 fontmin 并 `process.stdout.write` 告警(含目录),不抛错(OI-4)。
|
||||
- 按 dev.md 写 jsDoc;模块无顶层副作用;非空判断用 `isNil`(字面量)/ `!`(对象)。
|
||||
- **注意:** `Fontmin` 的引入从 `build-game.ts` 移到本文件;`build-game.ts` 不再直接引用 fontmin。
|
||||
|
||||
(2) **重写 `script/build-game.ts`:**
|
||||
- **保留**:`DEBUG_BUILD` / `DEBUG_REPLAY`、`ansi`、`logProgress`(外部行为)、`buildClient` / `buildData` 名称、`distDir`、`getAllChars`(改为从新产物收集字符)、错误处理与 `process.exit(1)` 风格、`formatSize` 报告。
|
||||
- **删除**:`tempDir` / `_temp` 相关、`RESOUCE_SIZE`、`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize`、`generateResourceJSON` / `CompressedLoadList*`、`@babel/core` 与 `transformAsync`、`archiver` / `createWriteStream` / `compressing`、`RequiredData` / `RequiredIconsData` 及对 `public/project/{data,icons}.js` 的读取、`toCopy` 旧模板清单、源码压缩包、资源地图 JSON、资源分块拷贝、服务器 exe 拷贝、`dist.zip` 压缩与相关体积告警分支。
|
||||
- **保留**:`DEBUG_BUILD` / `DEBUG_REPLAY`、`ansi`、`logProgress`(外部行为)、`buildClient` / `buildData` 名称、`getAllChars` 名称、`distDir`、错误处理与 `process.exit(1)` 风格、`formatSize` 报告。
|
||||
- **删除**:`tempDir` / `_temp` 相关、`RESOUCE_SIZE`、`checkClientData` / `ClientDataLevel` / `getFileName` / `getFileSize`、`generateResourceJSON` / `CompressedLoadList*`、`@babel/core` 与 `transformAsync`、`archiver` / `createWriteStream` / `compressing`、`RequiredData` / `RequiredIconsData` 及对 `public/project/{data,icons}.js` 的读取、`toCopy` 旧模板清单、源码压缩包、资源地图 JSON、资源分块拷贝、服务器 exe 拷贝、`dist.zip` 压缩与相关体积告警分支、`Fontmin` 直接引用。
|
||||
- **`buildClient(outDir)`**:复用 `loadConfigFromFile` + `mergeConfig`;注入 `legacy(...)`(目标浏览器列表与 `renderModernChunks:false` 维持现状,OI-7);`build.outDir = outDir`(即 `distDir`)、`copyPublicDir: false`、`minify: !DEBUG_BUILD`、`emptyOutDir: true`;`rollupOptions.external` 维持 `['@wasm-audio-decoders/opus-ml']`;`output` 维持 `format:'es'` / `entryFileNames` / `chunkFileNames` / `assetFileNames`;`manualChunks` 仅保留 `audio`(移除 `antdv`)。
|
||||
- **`buildData(outDir, entry)`**:`build.outDir = resolve(distDir, 'data')`、`copyPublicDir:false`、`emptyOutDir:false`、`minify: !DEBUG_REPLAY`、`lib: { entry, fileName: 'data', formats: ['es'] }`(移除 iife 与 `name`)、`plugins: [dataIsolationPlugin()]`。构建成功后断言/记录产物为 `dist/data/data.js`。
|
||||
- **`getAllChars(distDir)`**:递归读取渲染端产物中的 `.js`(排除 `dist/data`)与 `src/content/**/*.jsonc` 的字符集合,返回字符串;供 fontmin 使用。
|
||||
- **`buildGame()` 编排(新进度表:① 构建前准备 ② 构建渲染端 ③ 构建数据端 ④ 处理 content ⑤ 收尾):**
|
||||
- **`buildData(outDir, entry)`**:`build.outDir = outDir`(调用方传 `resolve(distDir, 'data')`)、`copyPublicDir:false`、`emptyOutDir:false`、`minify: !DEBUG_REPLAY`、`lib: { entry, fileName: 'data', formats: ['es'] }`(移除 iife 与 `name`)、`plugins: [dataIsolationPlugin()]`。构建成功后记录产物为 `dist/data/data.js`。
|
||||
- **`getAllChars(distDir)`**:递归读取渲染端产物中的 `.js`(**排除 `dist/data` 与 `dist/content`**)与 `src/content/**/*.jsonc` 的字符集合,返回 `Set<string>`;供 fontmin 使用。此函数不依赖数据端产物,故数据端按预期失败时仍可执行。
|
||||
- **`buildGame()` 编排(新进度表 4 步:① 构建前准备 ② 构建渲染端 ③ 构建数据端 ④ 处理 content 与收尾):**
|
||||
1. `ensureDir(distDir)` + `emptyDir(distDir)`;
|
||||
2. `await buildClient(distDir)`;
|
||||
3. `const chars = await getAllChars(distDir)`;
|
||||
4. `await buildData(resolve(distDir, 'data'), resolve(process.cwd(), 'src/data.ts'))`;
|
||||
5. `const stats = await buildContent({ sourceDir: resolve(process.cwd(), 'src/content'), outputDir: resolve(distDir, 'content'), chars })`;
|
||||
6. `copy(resolve(process.cwd(), 'LICENSE'), resolve(distDir, 'LICENSE'))`;
|
||||
7. 输出构建信息(dist 体积、`dist/data/data.js` 体积、content 统计),**不再**生成任何压缩包。
|
||||
- 保留模块底部执行块(`buildGame()`),但按 Task 1 的测试模式无需导出。
|
||||
4. **数据端(关键:区分预期 OI-11 阻断与本计划错误):** `const message0 = ...` 不需要;直接调用 `buildData(resolve(distDir, 'data'), resolve(process.cwd(), 'src/data.ts'))`,用 Promise `.catch` 接收失败:
|
||||
- **预期分支(a):** 先把错误文本中的反斜杠归一化为 `/`(`String(reason).replace(/\\\\/g, '/')`),若文本**同时**含 `mota-data-isolation` **与**(`@motajs/render` **或** `src/client`),则视为 **OI-11 预期阻断**:`process.stdout.write` 打印完整错误与该结论,返回 `'pending'`,**不退出**;
|
||||
- **非预期分支(b/c):** 其它任何错误一律 `process.stderr.write(String(reason))` 并 `process.exit(1)`(不得吞掉)。
|
||||
5. `const stats = await buildContent({ sourceDir: resolve(process.cwd(), 'src/content'), outputDir: resolve(distDir, 'content'), chars })`(`.catch` 失败即退出);
|
||||
6. `copy(resolve(process.cwd(), 'LICENSE'), resolve(distDir, 'LICENSE'))`(`.catch` 失败即退出);
|
||||
7. 输出构建信息:渲染端 dist 体积、content 统计(`files` / `jsonc` / `fonts`);**数据端状态行**——`'pending'` 时输出 `⚠️ 数据端构建:按预期终止(OI-11,待用户修复 src 端跨端引用)`,成功时输出 `✅ 数据端构建:dist/data/data.js(ESM)`;**不再**生成任何压缩包。
|
||||
- `logProgress` 的步骤表改为上述 4 项;最终汇总前 `ansi.clear` 并打印状态行(数据端 pending 的说明不得被进度勾选覆盖)。
|
||||
- 保留模块底部执行块(`buildGame()`);无需导出。
|
||||
|
||||
(3) **删除旧脚本:** 删除 `script/build-packages.ts` 与 `script/build-lib.ts`(用 `Remove-Item` / `fs.rm`);确认全仓无其它引用(`Select-String` 检索 `build-packages` / `build-lib`,仅允许 `package.json` 旧 script 行在删除前命中)。
|
||||
|
||||
@ -329,7 +357,7 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
|
||||
(5) **`index.html`:** 仅删除旧样式表 `<link id="mota-css" ... href="/styles.css">` 行;保留 DOCTYPE / head 其它 meta / `<div id="game-draw">` / `<canvas id="render-main">` / `<script src="/src/bootstrap.ts" type="module">`。
|
||||
|
||||
(6) **`src/bootstrap.ts`(仅 OI-5 获批时):** 在文件顶部新增**一行** `import './styles.less';`;不改该文件其它任何行、不改其既有注释。若 OI-5 未获批:不改该文件,并在 SUMMARY 记「样式未随 JS 打包」的已知缺口。
|
||||
(6) **`src/**`(零改动):** 按用户裁定「你改 src,我做脚本」,**不修改 `src/bootstrap.ts` 或任何 `src/**` 文件**。D-97 的「样式经入口 JS 引入」由用户后续在 `src/**` 侧完成(OI-5);本任务只在 SUMMARY 记录「用户接线前 `#game-draw` 居中样式可能缺失」的已知缺口。
|
||||
|
||||
(7) **新增 `script/build-resource.test.ts`(OI-2):** 使用临时目录测试 `minifyJSONC` 与 `buildContent`:
|
||||
- `minifyJSONC`:输入「`// Type: X` + 标记 + 多行 JSON」→ 输出以标记开头、正文为单行压缩 JSON、`split` 后恰好 2 段、`JSON.parse` 成功、不含 `Type:` 注释;
|
||||
@ -338,37 +366,42 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
- `buildContent`:临时 `source/content` 放一个 jsonc、一个 PNG(任意字节)与一个空 `font/` 目录 → 输出 jsonc 已压缩、PNG 字节一致、`fonts === 0` 且不抛错。
|
||||
- 每个 `it` 前一行注释。
|
||||
|
||||
(8) **实跑与门禁:** 先跑单测;再尽力实跑 `pnpm exec tsx script/build-game.ts`:
|
||||
- **成功** → 跑产物门禁(见 `<verify>`);
|
||||
- **失败且错误可归因于用户 WIP(`src/**` / `packages-user/**` 的既有未完成改动,含 `src/bootstrap.ts` 指向已移动 UI 文件)** → 记录完整错误、归因与「未修改范围外文件」,把产物门禁标为 `BLOCKED` 写入 SUMMARY;**不得**为此修改 `src/**` 或包源码;
|
||||
- **失败且错误来自本计划文件(插件 / 两个构建脚本 / 测试)** → 必须修复后重跑。
|
||||
(8) **实跑与分层门禁:** 先跑单测;再实跑 `pnpm exec tsx script/build-game.ts`:
|
||||
- **(a)预期 OI-11 阻断:** 退出码 0、日志含预期 pending 说明、`dist/data/data.js` 缺失 → **合规**;继续跑渲染端 + content + LICENSE 产物门禁;
|
||||
- **(b)本计划错误:** 插件 / `build-resource.ts` / `build-game.ts` / 测试自身出错(含数据端错误**不匹配** OI-11 已知链)→ 必须修复后重跑;
|
||||
- **(c)范围外用户 WIP:** 渲染端因用户其它未完成改动失败 → 记录完整错误、归因与「未修改范围外文件」,产物门禁标 `BLOCKED` 写入 SUMMARY;**不得**为此修改 `src/**` 或包源码;
|
||||
- `dist/data/data.js` **条件门禁**:存在 → 必须为 ESM(含 `export`);缺失 → 仅允许在(a)成立时通过,并记录 OI-11 pending。
|
||||
|
||||
(9) **`09-21-SUMMARY.md`:** 记录范围、插件扫描口径与错误行为、数据端 ESM 产物、content 压缩口径(含标记保留理由与 `Type:` 注释丢失)、dist 产物清单与排除项(D-97)、删除清单、`manualChunks` / legacy 调整、`index.html` / 样式落点、命名获批情形、门禁结果(插件单测 / content 单测 / 结构断言 / 范围门禁 / 全量构建成功或阻断归因)、`open_items` 最终处置、遗留缺口(`item.js`、字体缺失、全量构建阻断)。
|
||||
(9) **`09-21-SUMMARY.md`:** 记录范围、插件扫描口径与错误行为、数据端 ESM 产物(或 OI-11 pending 的完整错误与归因)、content 压缩口径(含 23 个 jsonc、标记保留理由与 `Type:` 注释丢失)、dist 产物清单与排除项(D-97)、删除清单、`manualChunks` / legacy 调整、`index.html` / 样式落点(用户侧 OI-5)、命名获批情形、门禁结果(插件单测 / content 单测 / 结构断言 / **渲染端 + content + LICENSE 产物门禁** / 范围门禁 / 数据端条件门禁)、`open_items` 最终处置、遗留缺口(`item.js`、字体缺失、OI-11 待用户修复 `src` 跨端引用、样式 import 待用户接线)。
|
||||
</action>
|
||||
<reversibility rating="costly">删除 `build-packages.ts` / `build-lib.ts` 与修改 `package.json` scripts 属仓库内结构性删除(可经 git 恢复);其余为文件重写,`git checkout` 可回退。执行后 `dist/` 布局改变,旧产物形态(资源分块 / `dist.zip`)不再可用。</reversibility>
|
||||
<verify>
|
||||
<automated>pnpm exec vitest run script/build-resource.test.ts</automated>
|
||||
<fails_when>non-zero exit, 或任一断言失败(标记未保留 / 注释未去除 / 非法 JSON 未抛错 / 无标记输入处理错误 / PNG 拷贝字节不一致 / 无字体时抛错)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const bg=fs.readFileSync('script/build-game.ts','utf8');for(const t of ['transformAsync','archiver','compressing','checkClientData','generateResourceJSON','splitResource','source-code.zip','loadList.json','启动服务.exe','dist.zip']){if(bg.indexOf(t)>=0)bad.push('build-game.ts still contains legacy token: '+t)}for(const t of ['copyPublicDir: false','emptyOutDir: false','dataIsolationPlugin','buildContent','LICENSE','resolve(distDir, \'data\')']){if(bg.indexOf(t)<0)bad.push('build-game.ts missing required token: '+t)}if(!/formats\s*:\s*\['es'\]/.test(bg))bad.push('build-game.ts: data build is not ESM-only');if(!/manualChunks\s*:\s*\{[\s\S]*audio/.test(bg))bad.push('build-game.ts: audio manualChunk missing');if(/antdv\s*:/.test(bg))bad.push('build-game.ts: antdv manualChunk must be removed');for(const p of ['script/build-packages.ts','script/build-lib.ts']){if(fs.existsSync(p))bad.push('legacy script not deleted: '+p)}const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));for(const s of ['build:packages','build:lib']){if(s in pkg.scripts)bad.push('package.json still has script: '+s)}if(!pkg.scripts['build:game'].includes('vue-tsc --noEmit'))bad.push('build:game must keep vue-tsc --noEmit');const html=fs.readFileSync('index.html','utf8');if(html.indexOf('styles.css')>=0)bad.push('index.html still links the legacy stylesheet');if(html.indexOf('/src/bootstrap.ts')<0)bad.push('index.html entry script missing');const br=fs.readFileSync('script/build-resource.ts','utf8');if(br.indexOf('splitResource')>=0||br.indexOf('JSZip')>=0)bad.push('build-resource.ts still contains split/zip logic');for(const t of ['minifyJSONC','buildContent','SYSTEM PREFIX END']){if(br.indexOf(t)<0)bad.push('build-resource.ts missing '+t)}if(bad.length>0){console.error('BUILD STRUCTURE GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK build structure: legacy pipeline removed, ESM data lib wired to isolation plugin, content build + LICENSE kept, scripts deleted, index.html cleaned')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "BUILD STRUCTURE GATE FAILED"(`build-game.ts` 仍含二次压缩 / 旧 zip / 资源地图 / 分块 / 旧模板 token;缺 `copyPublicDir:false`、`emptyOutDir:false`、`dataIsolationPlugin`、`buildContent`、`LICENSE`、`dist/data` 落点;数据端非 ESM;`audio` chunk 缺失或 `antdv` chunk 未移除;旧脚本未删除;`package.json` 仍含被删 script 或 `build:game` 丢了 `vue-tsc --noEmit`;`index.html` 仍有旧样式表 link 或缺入口;`build-resource.ts` 仍含分块/zip 或缺压缩/标记实现)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const bg=fs.readFileSync('script/build-game.ts','utf8');for(const t of ['transformAsync','archiver','compressing','checkClientData','generateResourceJSON','splitResource','source-code.zip','loadList.json','启动服务.exe','dist.zip','data.iife.js','_temp']){if(bg.indexOf(t)>=0)bad.push('build-game.ts still contains legacy token: '+t)}for(const t of ['copyPublicDir: false','emptyOutDir: false','dataIsolationPlugin','buildContent','LICENSE','resolve(distDir, \'data\')','mota-data-isolation','@motajs/render']){if(bg.indexOf(t)<0)bad.push('build-game.ts missing required token: '+t)}if(!/formats\s*:\s*\['es'\]/.test(bg))bad.push('build-game.ts: data build is not ESM-only');if(!/manualChunks\s*:\s*\{[\s\S]*audio/.test(bg))bad.push('build-game.ts: audio manualChunk missing');if(/antdv\s*:/.test(bg))bad.push('build-game.ts: antdv manualChunk must be removed');for(const p of ['script/build-packages.ts','script/build-lib.ts']){if(fs.existsSync(p))bad.push('legacy script not deleted: '+p)}const pkg=JSON.parse(fs.readFileSync('package.json','utf8'));for(const s of ['build:packages','build:lib']){if(s in pkg.scripts)bad.push('package.json still has script: '+s)}if(!pkg.scripts['build:game'].includes('vue-tsc --noEmit'))bad.push('build:game must keep vue-tsc --noEmit');if(pkg.scripts['build:game'].includes('declare.ts'))bad.push('build:game must drop declare.ts pre-step');const html=fs.readFileSync('index.html','utf8');if(html.indexOf('styles.css')>=0)bad.push('index.html still links the legacy stylesheet');if(html.indexOf('/src/bootstrap.ts')<0)bad.push('index.html entry script missing');const br=fs.readFileSync('script/build-resource.ts','utf8');if(br.indexOf('splitResource')>=0||br.indexOf('JSZip')>=0)bad.push('build-resource.ts still contains split/zip logic');for(const t of ['minifyJSONC','buildContent','SYSTEM PREFIX END']){if(br.indexOf(t)<0)bad.push('build-resource.ts missing '+t)}if(bad.length>0){console.error('BUILD STRUCTURE GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK build structure: legacy pipeline removed, ESM data lib wired to isolation plugin, content + LICENSE, legacy scripts/scripts entries removed')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "BUILD STRUCTURE GATE FAILED"(`build-game.ts` 仍含二次压缩 / 旧 zip / 资源地图 / 分块 / 旧模板 token;缺 `copyPublicDir:false`、`emptyOutDir:false`、`dataIsolationPlugin`、`buildContent`、`LICENSE`、`dist/data` 落点、OI-11 分类标识;数据端非 ESM;`audio` chunk 缺失或 `antdv` chunk 未移除;旧脚本未删除;`package.json` 仍含被删 script、`build:game` 丢了 `vue-tsc --noEmit` 或仍含 `declare.ts`;`index.html` 仍有旧样式表 link 或缺入口;`build-resource.ts` 仍含分块/zip 或缺压缩/标记实现)</fails_when>
|
||||
<automated>pnpm exec tsx script/build-game.ts</automated>
|
||||
<fails_when>non-zero exit **且** 错误来自本计划文件(`script/vite-plugin-data-isolation.ts` / `script/build-resource.ts` / `script/build-game.ts` / 其测试);若错误来自用户 WIP 的范围外文件,则本命令的失败**不**单独判负,但必须在 SUMMARY 记录完整错误、归因与「未修改范围外文件」。成功时继续执行产物门禁。</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const must=['dist/index.html','dist/data/data.js','dist/content/core.jsonc','dist/content/client.jsonc','dist/content/maps/sample0.jsonc','dist/content/texture/terrains0.png','dist/LICENSE'];for(const p of must){if(!fs.existsSync(p))bad.push('missing '+p)}const mustNot=['dist/project','dist/main.js','dist/source-code.zip','dist/loadList.json','dist/styles.css','dist/logo.png','dist/editor.html','dist/_docs','dist/_server','dist/libs','dist/data.process.js','dist.zip'];for(const p of mustNot){if(fs.existsSync(p))bad.push('unexpected '+p)}const core=fs.readFileSync('dist/content/core.jsonc','utf8');const M='// --- SYSTEM PREFIX END --- //';const parts=core.split(M);if(parts.length!==2)bad.push('core.jsonc marker not preserved exactly once');else{try{JSON.parse(parts[1])}catch(e){bad.push('core.jsonc body is not valid JSON: '+e.message)}}if(core.indexOf('// Type:')>=0)bad.push('core.jsonc still contains the Type comment');const data=fs.readFileSync('dist/data/data.js','utf8');if(!/export\s*\{/.test(data))bad.push('data.js is not ESM (no export statement)');if(bad.length>0){console.error('ARTIFACT GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK artifacts: dist/index.html + dist/data/data.js (ESM) + compressed dist/content (marker preserved) + LICENSE; legacy outputs absent')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "ARTIFACT GATE FAILED"(必需产物缺失 / 旧产物仍存在 / `core.jsonc` 标记未恰好保留一次或正文非合法 JSON / `Type:` 注释仍在 / `data.js` 非 ESM),或全量构建因本计划文件失败</fails_when>
|
||||
<fails_when>non-zero exit **且** 错误来自本计划文件(`script/vite-plugin-data-isolation.ts` / `script/build-resource.ts` / `script/build-game.ts` / 其测试),或数据端错误不匹配 OI-11 已知链;**数据端因 OI-11 已知跨端引用被插件按设计终止时,构建脚本应吞掉该预期错误并以退出码 0 继续产出渲染端 + content + LICENSE——这不判负**;若错误来自用户 WIP 的范围外文件(渲染端),则记录完整错误、归因与「未修改范围外文件」,标 BLOCKED,不单独判负。</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const bad=[];const must=['dist/index.html','dist/content/core.jsonc','dist/content/client.jsonc','dist/content/maps/sample0.jsonc','dist/content/maps/.list.jsonc','dist/content/texture/.list.jsonc','dist/content/texture/terrains0.png','dist/LICENSE'];for(const p of must){if(!fs.existsSync(p))bad.push('missing '+p)}const mustNot=['dist/project','dist/main.js','dist/source-code.zip','dist/loadList.json','dist/styles.css','dist/logo.png','dist/editor.html','dist/_docs','dist/_server','dist/libs','dist/data.process.js','dist/data/data.iife.js','dist.zip'];for(const p of mustNot){if(fs.existsSync(p))bad.push('unexpected '+p)}const core=fs.readFileSync('dist/content/core.jsonc','utf8');const M='// --- SYSTEM PREFIX END --- //';const parts=core.split(M);if(parts.length!==2)bad.push('core.jsonc marker not preserved exactly once');else{try{JSON.parse(parts[1])}catch(e){bad.push('core.jsonc body is not valid JSON: '+e.message)}}if(core.indexOf('// Type:')>=0)bad.push('core.jsonc still contains the Type comment');const html=fs.readFileSync('dist/index.html','utf8');if(html.indexOf('styles.css')>=0)bad.push('dist/index.html still links styles.css');const dataPath='dist/data/data.js';if(fs.existsSync(dataPath)){const data=fs.readFileSync(dataPath,'utf8');if(!/export\s*\{/.test(data))bad.push('data.js is not ESM (no export statement)')}else{process.stdout.write('PENDING: dist/data/data.js absent (OI-11 known src cross-import; expected until user fixes src)\n')}if(bad.length>0){console.error('ARTIFACT GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK render+content artifacts: dist/index.html (no styles.css) + compressed dist/content (marker preserved, 23 jsonc source) + LICENSE; data.js '+(fs.existsSync(dataPath)?'ESM':'pending OI-11'))"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "ARTIFACT GATE FAILED"(渲染端 + content + LICENSE 必需产物缺失 / 旧产物仍存在 / `core.jsonc` 标记未恰好保留一次或正文非合法 JSON / `Type:` 注释仍在 / `dist/index.html` 仍含 `styles.css` / 存在的 `data.js` 非 ESM)</fails_when>
|
||||
<!-- plan-criteria-allow: R4 - SCOPE GATE 内的 git status 由 node execSync 调用并以 try/catch 捕获失败(失败即非零退出),同行的 `|` 属 JS 正则/字符串,非 shell 管道阶段 -->
|
||||
<automated>node -e "const {execSync}=require('child_process');let raw;try{raw=execSync('git status --porcelain',{encoding:'utf8'})}catch(e){console.error('SCOPE GATE FAILED :: git status failed :: '+e.message);process.exit(1)}const lines=raw.split(/\r?\n/).filter(Boolean);const allow=new Set(['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts','script/build-resource.ts','script/build-resource.test.ts','script/build-game.ts','script/build-packages.ts','script/build-lib.ts','package.json','index.html','src/bootstrap.ts','.planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md']);const wip=['packages-user/client-modules/src/client.ts','packages-user/client-modules/src/index.ts','packages-user/client-modules/src/render/','packages-user/client-modules/src/ui/','packages-user/client-modules/src/shared.ts','src/client/','src/main.ts','src/constant.ts','src/data.ts','src/package.json','.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md','.planning/phases/10-docs-restructure/'];const bad=[];for(const l of lines){const p=l.slice(3).trim().replace(/^\u0022|\u0022$/g,'');if(allow.has(p))continue;if(wip.some(w=>p===w||p.startsWith(w)))continue;bad.push(p)}if(bad.length>0){console.error('SCOPE GATE FAILED :: out-of-scope changes :: '+bad.join(' | '));process.exit(1)}console.log('OK scope: only this plan files changed (user WIP ignored)')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "SCOPE GATE FAILED"(改动了 `files_modified` 与用户 WIP 白名单之外的任何文件,例如 `packages-user/client-base/**`、`packages-user/data-*/**`、`packages/**`、`vite.config.ts`、`script/dev.ts`、`script/declare.ts`、lockfile、其它 `.planning/**`)</fails_when>
|
||||
<automated>node -e "const {execSync}=require('child_process');let raw;try{raw=execSync('git status --porcelain',{encoding:'utf8'})}catch(e){console.error('SCOPE GATE FAILED :: git status failed :: '+e.message);process.exit(1)}const lines=raw.split(/\r?\n/).filter(Boolean);const allow=new Set(['script/vite-plugin-data-isolation.ts','script/vite-plugin-data-isolation.test.ts','script/build-resource.ts','script/build-resource.test.ts','script/build-game.ts','script/build-packages.ts','script/build-lib.ts','package.json','index.html','.planning/phases/09-minimum-browser-runnable/09-21-SUMMARY.md']);const wip=['packages-user/client-base/','packages-user/client-modules/','src/','.planning/phases/09-minimum-browser-runnable/09-CONTEXT.md','.planning/phases/10-docs-restructure/'];const bad=[];for(const l of lines){const p=l.slice(3).trim().replace(/^\u0022|\u0022$/g,'');if(allow.has(p))continue;if(wip.some(w=>p===w||p.startsWith(w)))continue;bad.push(p)}if(bad.length>0){console.error('SCOPE GATE FAILED :: out-of-scope changes :: '+bad.join(' | '));process.exit(1)}console.log('OK scope: only this plan files changed; src/** and user WIP untouched by this plan')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "SCOPE GATE FAILED"(改动了 `files_modified` 与用户 WIP 白名单之外的任何文件,例如 `packages-user/client-base/**` 以外的新渲染端包路径、`packages-user/data-*/**`、`packages/**`、`vite.config.ts`、`script/dev.ts`、`script/declare.ts`、lockfile、其它 `.planning/**`)</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- **(a)预期阻断(不得判负):** 若 OI-11 跨端引用仍未修复,`pnpm exec tsx script/build-game.ts` 退出码 **0**,日志含「数据端构建:按预期终止(OI-11…)」,`dist/data/data.js` 缺失属于预期;构建脚本通过错误文本同时匹配 `mota-data-isolation` 与(`@motajs/render` 或 `src/client`)完成分类,**未吞掉其它数据端错误**
|
||||
- **(b)本计划错误(必须判负并修复):** 插件 / `build-resource.ts` / `build-game.ts` / 测试自身错误,或数据端错误不匹配 OI-11 已知链 → 构建非零退出或单测失败;必须修复后重跑
|
||||
- **(c)范围外用户 WIP(记录为 BLOCKED,不改范围外文件):** 渲染端因用户其它未完成改动失败 → `09-21-SUMMARY.md` 记录完整错误、归因与「未修改范围外文件」;不得修 `src/**` 或包源码
|
||||
- **渲染端 + content + LICENSE 门禁(今天即可通过):** `dist/index.html`(不含 `styles.css`)、`dist/content/core.jsonc` / `client.jsonc` / `maps/sample0.jsonc` / `maps/.list.jsonc` / `texture/.list.jsonc` / `texture/terrains0.png`、`dist/LICENSE` 存在;D-97 所列旧产物不存在;`core.jsonc` 标记恰好保留一次、正文为合法 JSON、`// Type:` 注释已去除
|
||||
- **数据端条件门禁:** `dist/data/data.js` 存在时必须为 ESM(含 `export`);缺失时仅允许在(a)成立时通过,并记录 OI-11 pending
|
||||
- `script/build-resource.ts` 已重写为 content 处理模块(`minifyJSONC` / `buildContent`,按 OI-2 命名);不再含分块 / zip / `JSZip`;`build-resource.test.ts` 全绿(标记保留、去注释、非法 JSON 抛错、PNG 原样、无字体跳过)
|
||||
- `script/build-game.ts` 已重写:渲染端构建到 `dist`(`copyPublicDir:false`、`emptyOutDir:true`、`audio` chunk、无 `antdv` chunk)、数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`,`emptyOutDir:false`)、`buildContent` 处理 `dist/content`、复制 `LICENSE`、输出体积信息;旧 pipeline token(babel / archiver / compressing / `checkClientData` / `generateResourceJSON` / `splitResource` / 源码 zip / 资源地图 / 服务器 exe / `dist.zip`)全部移除
|
||||
- `script/build-packages.ts` / `script/build-lib.ts` 已删除;`package.json` 不再含 `build:packages` / `build:lib`,`build:game` 保留 `vue-tsc --noEmit`
|
||||
- `index.html` 仅移除旧样式表 link(其余内容保留);按 OI-5 处理 `src/bootstrap.ts`(获批则仅新增一行样式 import,未获批则不改并记录缺口)
|
||||
- 全量构建:成功时产物门禁全绿(`dist/index.html`、`dist/data/data.js` 为 ESM、`dist/content/**` 已压缩且标记保留、`dist/LICENSE`;D-97 所列旧产物不存在);若因用户 WIP 失败,`09-21-SUMMARY.md` 记录完整错误、归因与产物门禁 `BLOCKED`,且**未修改范围外文件**
|
||||
- `script/build-game.ts` 已重写:渲染端构建到 `dist`(`copyPublicDir:false`、`emptyOutDir:true`、`audio` chunk、无 `antdv` chunk)、数据端 ESM 库构建到 `dist/data`(注入 `dataIsolationPlugin`,`emptyOutDir:false`,OI-11 分类)、`buildContent` 处理 `dist/content`、复制 `LICENSE`、输出体积与数据端状态;旧 pipeline token(babel / archiver / compressing / `checkClientData` / `generateResourceJSON` / `splitResource` / 源码 zip / 资源地图 / 服务器 exe / `dist.zip`)全部移除
|
||||
- `script/build-packages.ts` / `script/build-lib.ts` 已删除;`package.json` 不再含 `build:packages` / `build:lib`,`build:game` 保留 `vue-tsc --noEmit` 且去掉 `declare.ts` 前置
|
||||
- `index.html` 仅移除旧样式表 link(其余内容保留);**`src/**` 零改动**(样式 import 由用户侧完成,OI-5)
|
||||
- `git status` 范围门禁全绿(仅 `files_modified` 与用户 WIP 白名单变更)
|
||||
- `09-21-SUMMARY.md` 已写(范围、插件口径、ESM 产物、content 压缩与标记理由、产物/排除清单、删除清单、命名获批、门禁结果、`open_items` 最终处置、遗留缺口)
|
||||
- `09-21-SUMMARY.md` 已写(范围、插件口径、ESM 产物或 OI-11 pending 归因、content 压缩与标记理由(23 个 jsonc)、产物/排除清单、删除清单、命名获批、门禁结果、`open_items` 最终处置、遗留缺口)
|
||||
</acceptance_criteria>
|
||||
<done>构建流程完成新引擎适配:数据端 ESM 独立包由模块图硬门禁把关、content 以压缩形态落到 `dist/content`、渲染端产物不再夹带旧模板内容、旧脚本与旧 pipeline 全部移除、`LICENSE` 保留;运行期单测与结构/范围门禁全绿;全量构建结果(成功或用户 WIP 阻断归因)与 `open_items` 处置写入 `09-21-SUMMARY.md`</done>
|
||||
<done>构建流程完成新引擎适配:数据端 ESM 独立包由模块图硬门禁把关(OI-11 预期终止被正确分类而非误判为计划失败)、content 以压缩形态落到 `dist/content`、渲染端产物不再夹带旧模板内容、旧脚本与旧 pipeline 全部移除、`LICENSE` 保留;运行期单测与结构/范围门禁全绿;**渲染端 + content + LICENSE 产物门禁今天即可通过**;`dist/data/data.js` 条件门禁与 OI-11 处置写入 `09-21-SUMMARY.md`</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@ -378,51 +411,53 @@ Phase 9 · 任务 11 · 第 21 个计划 —— **构建重构(适配新引擎
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 写入/删除边界 | 本增量含写操作与两个脚本删除;只允许 `files_modified` 白名单 |
|
||||
| 数据端 ↔ 渲染端 | 数据端独立包不得依赖渲染端;由模块图插件硬门禁(取代体积启发式) |
|
||||
| content 输入 → dist | `src/content` 的 jsonc 被 `JSON.parse` 后重新序列化;格式非法会在构建期暴露 |
|
||||
| 写入/删除边界 | 本增量含写操作与两个脚本删除;只允许 `files_modified` 白名单,**`src/**` 零写入** |
|
||||
| 数据端 ↔ 渲染端 | 数据端独立包不得依赖渲染端;由模块图插件硬门禁(取代体积启发式),覆盖 `src/client/**` |
|
||||
| content 输入 → dist | `src/content` 的 23 个 jsonc 被 `JSON.parse` 后重新序列化;格式非法会在构建期暴露 |
|
||||
| 构建产物 → 浏览器 | `dist/content/**` 由加载器按 `BASE_URL` 相对路径读取;标记行是解析契约 |
|
||||
| 预期失败 vs 真实失败 | OI-11 跨端引用导致的数据端终止是预期;其它数据端错误必须仍使构建失败 |
|
||||
| 用户 WIP 边界 | 工作树含未提交改动;不得回退、不得为通过门禁而修改范围外文件 |
|
||||
| 依赖边界 | 不新增依赖、不改 lockfile、不改 `package.json` 依赖面 |
|
||||
| 命名边界 | 新文件/导出/接口及成员须先经 Task 0 报备批准(D-04) |
|
||||
|
||||
**威胁级别说明(security_enforcement = ASVS L1,block on high):** 本计划是**本地构建流程重构**,不执行第三方代码、不新增依赖、不暴露网络面;`high` 级(构建产物被篡改 / 依赖边界被破坏)均已给出 `mitigate`。
|
||||
**威胁级别说明(security_enforcement = ASVS L1,block on high):** 本计划是**本地构建流程重构**,不执行第三方代码、不新增依赖、不暴露网络面;`high` 级(构建产物被篡改 / 依赖边界被破坏 / 预期失败掩盖真实错误)均已给出 `mitigate`。
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-09-21-01 | Tampering | 数据端构建被渲染端依赖污染(体积启发式漏判) | high | mitigate | Task 1 插件在 `resolveId`(specifier)+ `moduleParsed`(解析路径)双路扫描并 `this.error` 终止;真实 `vite.build()` 单测覆盖 specifier / 传递依赖 / 纯数据端通过 / 数据端不误判;Task 2 `BUILD STRUCTURE GATE` 断言插件已注入数据端构建 |
|
||||
| T-09-21-01 | Tampering | 数据端构建被渲染端依赖污染(体积启发式漏判) | high | mitigate | Task 1 插件在 `resolveId`(specifier)+ `moduleParsed`(解析路径,含 `src/client/**`)双路扫描并 `this.error` 终止;真实 `vite.build()` 单测覆盖 specifier / 传递依赖 / `src/client` / 纯数据端通过 / 数据端不误判;Task 2 `BUILD STRUCTURE GATE` 断言插件已注入数据端构建 |
|
||||
| T-09-21-02 | Tampering | content jsonc 压缩丢失系统标记,导致运行期解析为 `{}` | high | mitigate | `minifyJSONC` 强制保留 `// --- SYSTEM PREFIX END --- //` 恰好一次;单测断言 `split` 后恰好 2 段且正文可 `JSON.parse`;产物门禁对 `dist/content/core.jsonc` 复验;SUMMARY 记录该兼容性约束 |
|
||||
| T-09-21-03 | Tampering | 把 `public/**` 旧模板内容带入 dist(D-97 违规) | medium | mitigate | `copyPublicDir: false`;不拷贝旧模板清单;产物门禁断言 D-97 所列旧产物不存在 |
|
||||
| T-09-21-04 | Tampering | 越界写入 / 回退用户 WIP | medium | mitigate | `files_modified` 白名单 + prohibitions + `git status` 范围门禁(用户 WIP 前缀白名单) |
|
||||
| T-09-21-04 | Tampering | 越界写入 / 回退用户 WIP / 误改 `src/**` | medium | mitigate | `files_modified` 白名单 + prohibitions(`src/**` 零改动)+ `git status` 范围门禁(用户 WIP 前缀白名单) |
|
||||
| T-09-21-05 | Tampering | 误删用户仍需要的脚本或 npm script(不可逆结构删除) | medium | mitigate | 删除范围仅 `build-packages.ts` / `build-lib.ts` 与对应两个 script(D-93 / D-98);Task 0 明示可经 git 恢复;结构门禁只允许这两个文件被删 |
|
||||
| T-09-21-06 | Tampering | 未经批准使用新命名(插件 / 接口 / 成员) | medium | mitigate | Task 0 报备 OI-1 / OI-2;prohibitions 明列「未获批前不得落码」 |
|
||||
| T-09-21-07 | DoS | 全量构建因用户 WIP 失败被误判为本计划失败,进而诱发越界修复 | medium | mitigate | 门禁分层:插件单测 + content 单测 + 结构断言为主;全量构建失败仅当来自本计划文件时判负;范围外失败记录归因,禁止修改 `src/**` / 包源码 |
|
||||
| T-09-21-07 | DoS | OI-11 预期阻断被误判为本计划失败(诱发越界修复),或反过来吞掉真实数据端错误 | high | mitigate | 分类必须同时匹配 `mota-data-isolation` 与(`@motajs/render` 或归一化后的 `src/client`);不匹配的数据端错误一律 `process.exit(1)`;门禁分层:渲染端 + content + LICENSE 必过、`data.js` 条件门禁;范围外失败只记录归因,禁止修改 `src/**` / 包源码 |
|
||||
| T-09-21-08 | Tampering | 字体缺失导致 fontmin 抛错阻断构建 | medium | mitigate | 无字体二进制时跳过 fontmin 并告警(OI-4);`buildContent` 单测覆盖空 `font/` 目录 |
|
||||
| T-09-21-09 | Tampering | `manualChunks` 引用不存在的 `ant-design-vue` 造成分块噪声/误解 | low | mitigate | 移除 `antdv` chunk,保留 `audio`;结构门禁断言 |
|
||||
| T-09-21-10 | Information Disclosure | 改动既有注释 / jsDoc(AGENTS.md 禁改) | low | mitigate | prohibitions 明列;仅重写两个构建脚本自身,其它文件保持原注释 |
|
||||
| T-09-21-11 | Repudiation | 未跑门禁即收口 / 未记录阻断与 `open_items` | low | mitigate | Task 2 运行单测 + 结构 + 范围门禁;SUMMARY 记录全量构建结果、归因与 `open_items` 处置 |
|
||||
| T-09-21-11 | Repudiation | 未跑门禁即收口 / 未记录阻断与 `open_items` | low | mitigate | Task 2 运行单测 + 结构 + 产物 + 范围门禁;SUMMARY 记录数据端状态、OI-11 归因与 `open_items` 处置 |
|
||||
| T-09-21-SC | Tampering | npm/pip/cargo installs | high | mitigate | 不新增依赖、不改 `package.json` 依赖面与 lockfile;范围门禁拒绝 lockfile 变更;如需新增依赖须回 Task 0 报备获批 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- **Task 0**:预执行状态(旧构建脚本 / `package.json` scripts / `index.html` 旧 link / content 加载器标记逻辑就位;新插件与测试文件不存在);人工关卡(用户回复「可以执行」;OI-1..OI-10 若有指定按其指定)。
|
||||
- **Task 1**:`dataIsolationPlugin` 结构(`enforce: 'pre'` + `resolveId` + `moduleParsed` + `this.error` + 禁令集合);运行期单测以真实 `vite.build()` 断言「拒渲染端 specifier / 拒渲染端解析路径(传递依赖)/ 纯数据端通过且 ESM / 数据端不误判」。
|
||||
- **Task 2**:`build-resource` 单测(标记保留、去注释、非法 JSON 抛错、PNG 原样、无字体跳过);`build-game` / `package.json` / `index.html` / `build-resource` 结构断言(旧 pipeline 移除、ESM 数据端 + 插件注入、content + LICENSE、旧脚本删除、scripts 调整、样式 link 移除);`pnpm exec tsx script/build-game.ts` 尽力实跑(成功则产物门禁:必需产物存在、D-97 旧产物不存在、`core.jsonc` 标记保留且正文合法、`data.js` 为 ESM;失败且归因于用户 WIP 则记 `BLOCKED`);`git status` 范围门禁。
|
||||
- **一致性**:`dist/content/**` 落点与 `MotaDataLoader.resolveURL` 生产态一致;jsonc 标记与 `PrefixedJSONCProcessor` 契约一致;`build:game` 与 `script/build-game.ts` 入口一致;`index.html` 样式链路与 OI-5 决定一致。
|
||||
- **Task 0**:预执行状态(旧构建脚本 / `package.json` scripts / `index.html` 旧 link / 加载器标记逻辑就位;`src/content` 共 23 个 jsonc 且标记各恰好一次;新插件与测试文件不存在;报告 OI-11 链是否存在);人工关卡(用户回复「可以执行」;OI-1..OI-11 若有指定按其指定)。
|
||||
- **Task 1**:`dataIsolationPlugin` 结构(`enforce: 'pre'` + `resolveId` + `moduleParsed` + `this.error` + 禁令集合 + `src/client` 守卫);运行期单测以真实 `vite.build()` 断言「拒渲染端 specifier / 拒渲染端解析路径(传递依赖)/ 拒 `src/client` / 纯数据端通过且 ESM / 数据端不误判」。
|
||||
- **Task 2**:`build-resource` 单测(标记保留、去注释、非法 JSON 抛错、PNG 原样、无字体跳过);`build-game` / `package.json` / `index.html` / `build-resource` 结构断言(旧 pipeline 移除、ESM 数据端 + 插件注入 + OI-11 分类标识、content + LICENSE、旧脚本删除、scripts 调整、样式 link 移除);`pnpm exec tsx script/build-game.ts` 实跑——**(a)OI-11 预期终止 → 退出码 0 并继续;渲染端 + content + LICENSE 产物门禁必须全绿**;(b)本计划错误必须修复;(c)范围外 WIP 记 `BLOCKED`;`dist/data/data.js` 条件门禁;`git status` 范围门禁。
|
||||
- **一致性**:`dist/content/**` 落点与 `MotaDataLoader.resolveURL` 生产态一致;jsonc 标记与 `PrefixedJSONCProcessor` 契约一致;`build:game` 与 `script/build-game.ts` 入口一致;`index.html` 去 link 与 OI-5(用户侧样式接线)一致。
|
||||
- **无全仓门禁**:全程不使用 `pnpm check:type` / `pnpm build` / 全量 `pnpm test:ci` / 全仓 TS 诊断数(用户 WIP 存在历史类型错误)。
|
||||
- **人工复核**:结论写入 `09-21-SUMMARY.md`(含范围、插件口径、content 压缩与标记理由、产物/排除清单、删除清单、命名获批、门禁结果、全量构建阻断归因、`open_items` 最终处置)。
|
||||
- **人工复核**:结论写入 `09-21-SUMMARY.md`(含范围、插件口径、content 压缩与标记理由(23 个 jsonc)、产物/排除清单、删除清单、命名获批、门禁结果、OI-11 预期阻断的完整错误与归因、渲染端 + content + LICENSE 产物结果、`open_items` 最终处置)。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- **D-94:** 数据端构建由模块图插件把关(specifier + 解析路径双路),命中渲染端模块即 `this.error` 终止;`checkClientData` 及体积启发式已删除;数据端依赖不误判。
|
||||
- **D-95:** 数据端库模式 `formats: ['es']`,产物 `dist/data/data.js`;无 iife 产物。
|
||||
- **D-96:** `src/content` → `dist/content`;jsonc 压缩且**保留系统标记**、后缀仍 `.jsonc`;fontmin 压缩(无字体时跳过告警);不分块、不 zip;`build-resource` 从 `src/content` 读取。
|
||||
- **D-97:** `copyPublicDir:false`;dist 不含 D-97 所列旧模板产物;`LICENSE` 复制到 `dist/LICENSE`;`index.html` 旧样式表 link 移除,样式按 OI-5 经 JS 引入。
|
||||
- **D-94:** 数据端构建由模块图插件把关(specifier + 解析路径双路,含 `src/client/**`),命中渲染端模块即 `this.error` 终止;`checkClientData` 及体积启发式已删除;数据端依赖不误判。
|
||||
- **D-95:** 数据端库模式 `formats: ['es']`,产物 `dist/data/data.js`;无 iife 产物。**OI-11 未修复时该产物允许缺失**,由条件门禁与 SUMMARY 记录。
|
||||
- **D-96:** `src/content`(23 个 jsonc)→ `dist/content`;jsonc 压缩且**保留系统标记**、后缀仍 `.jsonc`;fontmin 压缩(无字体时跳过告警);不分块、不 zip;`build-resource` 从 `src/content` 读取。
|
||||
- **D-97:** `copyPublicDir:false`;dist 不含 D-97 所列旧模板产物;`LICENSE` 复制到 `dist/LICENSE`;`index.html` 旧样式表 link 移除;样式 import 由用户侧 `src/**` 完成(OI-5,本计划不改 `src/**`)。
|
||||
- **D-93 / D-98 / D-99 / D-100:** 两个旧脚本删除、两个 npm script 删除、`build:game` 保留 `vue-tsc --noEmit`;babel 二次压缩移除;`manualChunks` 去 `antdv` 保 `audio`;legacy 插件维持。
|
||||
- **边界:** 未删 `public/**`;未改 `src/**`(除 OI-5 一行);未改数据端/渲染端源码、`vite.config.ts`、`script/dev.ts`、lockfile、`ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY;未回退用户 WIP。
|
||||
- **可运行验证:** 插件单测 + content 单测 + 结构断言 + 范围门禁全绿;全量构建成功并过产物门禁,或因用户 WIP 阻断并如实记录归因。
|
||||
- **边界:** 未删 `public/**`;**未改 `src/**`(零改动,含 OI-11 跨端引用链)**;未改数据端/渲染端源码、`vite.config.ts`、`script/dev.ts`、lockfile、`ROADMAP.md` / `STATE.md` / `09-CONTEXT.md` / 既有 PLAN 与 SUMMARY;未回退用户 WIP。
|
||||
- **预期阻断分层(OI-11):** 数据端按设计终止被记录为 expected pending(**非本计划失败**);分类严格,其它数据端错误仍使构建失败;渲染端 + `dist/content` + `LICENSE` 产物门禁今天即可通过。
|
||||
- **可运行验证:** 插件单测 + content 单测 + 结构断言 + 渲染端/content/LICENSE 产物门禁 + 范围门禁全绿;数据端条件门禁按 OI-11 状态给出 ESM 或 pending 记录。
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user