@lingfan-engine/create 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,29 +41,37 @@ npm create @lingfan-engine my-story
41
41
  pnpm create @lingfan-engine my-story
42
42
  ```
43
43
 
44
- 生成器会问三件事:工程名、框架、壳。当前框架可选「原生(框架无关)」「Vue 3」「React」,
45
- 壳可选「浏览器」「小程序(UniApp)」与「桌面(Tauri 2)」。故事源形态不是问题——骨架自带整条 TS 创作链,
46
- 它就是缺省形态;要手写 `.story` 文件用 `--sources story` 指定。全部取缺省值(原生 + TypeScript 源 + 浏览器)可以跳过问答:
44
+ 生成器会问三件事:工程名、描述、形态。工程名写在命令行里就跳过这一问;描述可以留空,
45
+ 填了会同时写进 `package.json` 的 `description` 与 `Resources/project.json` 的 `description`。
47
46
 
48
- ```
49
- npm create @lingfan-engine my-story -- --yes
50
- ```
47
+ 形态是一次多选,按编号回答,空格或逗号分隔:
48
+
49
+ | 编号 | 形态 | 选中后得到什么 |
50
+ | ---- | ---------------- | ---------------------------------------------------------------- |
51
+ | 1 | Vue 3 | 页面结构换成单文件组件,装配与宿主不变 |
52
+ | 2 | React | 页面结构换成 JSX 组件,装配与宿主不变 |
53
+ | 3 | 桌面(Tauri 2) | 在同一份工程上再加一个桌面壳:存档落到应用数据目录,页面结构不变 |
54
+ | 4 | 小程序(UniApp) | 页面层换成 UniApp Vue 3 单文件组件,宿主装配走小程序端口 |
55
+
56
+ Vue 3 与 React 互斥(页面结构只能有一个所有者);小程序自带 Vue 页面层,与 Vue 3、React、
57
+ 桌面壳都互斥;桌面壳与 Vue 3 或 React 组合都可以。
58
+ 回车全跳过即骨架原生形态:框架无关 + 浏览器。
51
59
 
52
60
  生成后按提示进目录、装依赖、跑起来。生成器不指定包管理器——它按启动自己的那个(npm / pnpm / yarn / bun)给出命令,用哪个都行。骨架自带编译好的故事产物,开箱即跑;
53
61
  改过 `src/Stories.src/` 之后才需要跑一次 `pnpm stories:build`(或在开发时挂 `pnpm stories:watch`)。
54
62
  生成器默认不装依赖,想一步到位加 `--install`。
55
63
 
56
- 想不开交互问答、一次指定形态(写作 `框架[-壳[-故事源]]`):
64
+ 脚本里不想应答问答时,用参数跳过:
57
65
 
58
66
  ```
59
- npm create @lingfan-engine my-story -- --template vue-browser
60
- npm create @lingfan-engine my-story -- --template react-browser
61
- npm create @lingfan-engine my-story -- --template none-miniprogram
62
- npm create @lingfan-engine my-story -- --template vue-tauri
63
- npm create @lingfan-engine my-story -- --template none-browser-story
67
+ npm create @lingfan-engine my-story --description "试菜单的小工程" --framework vue --shell tauri
68
+ npm create @lingfan-engine my-story --yes
64
69
  ```
65
70
 
66
- 也可以逐项给:`--framework vue --shell browser`、`--shell miniprogram`、`--sources story`;两者冲突时逐项参数优先。
71
+ `--framework` 取 `none` / `vue` / `react`,`--shell` 取 `browser` / `tauri` / `miniprogram`。
72
+ `--sources` 取 `ts` / `story`:`story` 额外附一份手写 `.story` 示例,不改变骨架里 TS 创作链的去留。
73
+ `--template 框架[-壳[-故事源]]` 是等价写法,例如 `--template vue-tauri`;形态选择以问答为准,这个参数留给既有脚本。
74
+ (npm 会先解析自己的同名开关,用 npm 时要在选项前加 `--`,如 `npm create @lingfan-engine my-story -- --yes`。)
67
75
  目标目录非空时生成器会问怎么处置(保留现有文件 / 清空 / 取消),`--overwrite` 是「清空」的非交互写法。
68
76
 
69
77
  **框架层只换页面结构与入口,`src/host/**`(装配、订阅、帧循环、输入映射)一行不改,
@@ -72,22 +80,36 @@ npm create @lingfan-engine my-story -- --template none-browser-story
72
80
  加 `src/App.vue` 与 Vue 版组合根;选 React 是同一套做法换成 `src/App.tsx` 与 React 版组合根,
73
81
  外加 `tsconfig.json` 的 `jsx: react-jsx`。
74
82
 
75
- 壳层与框架层可以叠加(先框架后壳):`--framework react --shell tauri` 得到 React 页面 + 桌面外壳。
76
- 小程序是例外——它自带 Vue 页面层,不与其他框架层叠加。
83
+ **壳层分两种**:桌面壳只补一层桌面外壳,页面结构与宿主原样保留,所以能叠在 Vue 3 或 React 之上;
84
+ 小程序壳换掉整套页面与宿主,自带 Vue 页面层,因此不与其他框架层叠加。
85
+
86
+ **故事怎么写与形态那一问无关**:三种创作形态落在同一棵 `Resources/Stories/` 树上,构建期一起被扫描,
87
+ 可以混装在一个工程里,谁也不排斥谁——
88
+
89
+ | 创作形态 | 写在哪 | 改完怎么生效 |
90
+ | ------------- | ------------------------------ | --------------------------------------------------- |
91
+ | TS 源 | `src/Stories.src/**/*.ts` | 跑 `pnpm stories:build`(开发时挂 `stories:watch`) |
92
+ | 手写 `.story` | `Resources/Stories/**/*.story` | 直接改文本,保存即生效 |
93
+ | 手写 `.json` | `Resources/Stories/**/*.json` | 直接改 JSON,保存即生效 |
94
+
95
+ **TS 源**那一条链骨架自带:改 `src/Stories.src/**/*.ts`,跑 `pnpm stories:build` 编译成
96
+ `Resources/Stories/**/*.json`;类型检查走独立的 `tsconfig.stories.json`(故事源有 ambient 全局,
97
+ 不该和页面 DOM 类型面混在一个工程里)。骨架自带三条脚本(`stories:build` / `stories:watch` /
98
+ `stories-typecheck`)与开箱可跑的产物,`pnpm dev` 不必先构建。
99
+
100
+ **手写示例**由 `--sources story` 额外交付:`overlays/story/` 只往工程里加一份
101
+ `Resources/Stories/manual/manual_intro.story`(列 id 全在 `manual_` 命名空间,与骨架自带的两列不撞名),
102
+ 不删任何骨架文件,TS 创作链原样保留。想改就改那份 `.story`,想回 TS 就往 `src/Stories.src/` 加列,两边互不影响。
77
103
 
78
- **故事源形态层与框架层、壳层正交**,只决定故事从哪来。缺省形态是 **TypeScript 源**,整条链在骨架里:
79
- 改 `src/Stories.src/**/*.ts`,跑 `pnpm stories:build` 编译成 `Resources/Stories/**/*.json`。产物目录由构建
80
- **全量管理**——源里没有的列构建会当陈旧列删掉,所以别在产物目录里手改;类型检查走独立的
81
- `tsconfig.stories.json`(故事源有 ambient 全局,不该和页面 DOM 类型面混在一个工程里)。骨架自带三条脚本
82
- (`stories:build` / `stories:watch` / `stories-typecheck`)与开箱可跑的产物,`pnpm dev` 不必先构建。
104
+ 三条注意:
83
105
 
84
- 选「故事文件(`.story`)」则是**反向叠加**:`overlays/story/` 用 `create.remove` 摘掉骨架的整条 TS 创作链
85
- (`src/Stories.src`、`src/gen/*.gen.d.ts`、`tsconfig.stories.json`),换上一份手写的
86
- `Resources/Stories/title/title_main.story`,改完保存即生效。这条形态的产物目录里可以 `.story` 与 `.json` 混存。
106
+ - 列 `id` 全工程唯一:不同文件里的列只要 id 不撞就能共存,撞了构建会报出冲突的两个来源。
107
+ - 产物目录 `Resources/Stories/**` 里由构建写出的 `.json` 由构建自己维护:它只清理上一轮写过、这一轮不再产出的文件,
108
+ 手写的 `.story` 与 `.json` 它不碰。改 TS 源比改它生成的 `.json` 可靠。
109
+ - `type=ui` 的列不进历史、不进存档、不建检查点——它是界面,不是剧情。
87
110
 
88
- **壳层是换一整套页面与宿主**:小程序形态的页面结构由 `src/components/**` 承接,宿主装配在
111
+ **小程序壳换的是一整套页面与宿主**:小程序形态的页面结构由 `src/components/**` 承接,宿主装配在
89
112
  `src/mp/**`(`uni.*` 只在 `src/mp/platform.ts` 出现一处),内容不进包体、走工程接口按基址取。
90
- 因此它是壳而不是框架,与 `--framework` 不叠加:小程序已自带 Vue 页面层,框架取缺省值即可。
91
113
  它的 `vite` 与 `vue` 是一组钉住的版本(UniApp 插件对这些版本有硬约束),由
92
114
  `package.patch.json` 的 `create.pinned` 保住,不会被改写成「跟最新」。
93
115
 
@@ -102,8 +124,8 @@ npm create @lingfan-engine my-story -- --template none-browser-story
102
124
  模板依赖三个引擎包:`@lingfan-engine/core`(核心)、`@lingfan-engine/adapters`(预设适配器)、`@lingfan-engine/ui`(参考展示层)。
103
125
  它们以 **TS 源码出口**(`exports: "./src/index.ts"`)发布——消费方由 Vite/TS 直接编译,**无中间构建步骤**,
104
126
  `pnpm install` 直接可用。骨架还声明两个创作期依赖(`@lingfan-engine/stories-build` 与
105
- `@lingfan-engine/editor`)——缺省故事源形态是 TS,这条链归骨架;选「故事文件」形态时
106
- `overlays/story/` 会把链上的文件、脚本与依赖条目一并摘掉(见下文叠加层说明)。
127
+ `@lingfan-engine/editor`)——TS 创作链归骨架,任何形态的生成结果都带着它;
128
+ `--sources story` 只是额外多一份手写示例,这两条依赖照旧保留。
107
129
 
108
130
  ## 工程结构(单资源根)
109
131
 
@@ -131,7 +153,7 @@ __PROJECT__/
131
153
  ├── src/styles/stage.css # 页面与层样式(换框架不动这份:叠加层共用同一张表)
132
154
  └── Resources/ # 故事工程根(自包含、可整体搬运)
133
155
  ├── project.json # 工程清单:id/name/entry/lang/formatVersion
134
- ├── Stories/ # 故事产物(构建全量管理;故事文件形态下可 .story 与 .json 混存)
156
+ ├── Stories/ # 故事(构建写的 .json 与手写的 .story/.json 可混存)
135
157
  ├── Audio/ Images/ Video/
136
158
  ├── Lang/{lang}/ # I18N overlay
137
159
  └── Saves/ # 运行期产物(不入 git)
@@ -183,11 +205,12 @@ pwsh -File pack-template.ps1 -SourceDir ./__PROJECT__ -OutputFile ./__PROJECT__.
183
205
 
184
206
  ## 叠加层与占位符
185
207
 
186
- `template-meta.json` 的 `frameworks` / `sources` / `shells` 声明生成器会问哪些选项,每项的 `id` 对应
208
+ `template-meta.json` 的 `frameworks` / `sources` / `shells` 声明模板有哪些层,每项的 `id` 对应
187
209
  `overlays/<id>/` 目录:同路径文件**覆盖**骨架,根目录的 `*.patch.json` **深合并**进 `package.json`,
188
- 其中 `create.remove` 列出本层要删掉的骨架路径(换形态时清残留)。缺省项(`none` / `ts` / `browser`)
189
- 没有目录,表示「这一层什么都不加」,不是错误——缺省故事源 `ts` 正是如此:整条创作链在骨架里,
190
- 没有叠加层可加。`story` 反过来是「摘掉骨架的 TS 链、换上手写故事」,所以它有目录。合并顺序固定为「骨架 → 框架 → 故事源 → 壳」:
210
+ 其中 `create.remove` 列出本层要删掉的骨架路径(换形态时清残留)。`frameworks` / `shells` 决定生成器会问哪些选项;
211
+ `sources` 不参与形态问答——TS 源、手写 `.story`、手写 `.json` 在同一棵树上共存,它只表示要不要额外附一份手写示例。
212
+ 缺省项(`none` / `ts` / `browser`)没有目录,表示「这一层什么都不加」,不是错误——缺省故事源 `ts` 正是如此:
213
+ 整条创作链在骨架里,没有叠加层可加。`story` 有目录,但只为多放一份示例文件,不删骨架的任何东西。合并顺序固定为「骨架 → 框架 → 故事源 → 壳」:
191
214
  故事源要晚于框架层才能把各框架入口的故事表指向生成物;壳放最后,因为它对工程形态有最终发言权。
192
215
 
193
216
  骨架里的占位符有两种大小写形态:`__PROJECT__`(清单 id/name、页面标题、目录名)与
@@ -1,5 +1,7 @@
1
1
  # 运行期产物不进版本库(存档由 Rust 安全层落盘)
2
2
  Resources/Saves/
3
+ # 构建产物账:记录上一轮生成的产物清单,用于只清理自己生成过的陈旧文件
4
+ .lingfan/
3
5
  node_modules/
4
6
  dist/
5
7
  target/
@@ -3,6 +3,8 @@
3
3
  每个子目录是一层可选的工程内容,目录名就是 `template-meta.json` 里 `frameworks[]` / `sources[]` / `shells[]` 的 `id`。
4
4
  生成器按「骨架 → 框架层 → 故事源层 → 壳层」的顺序合并,同路径文件由后一层覆盖前一层。
5
5
  故事源层的两个 id 不对称:缺省 `ts` 没有目录(链在骨架里),可选 `story` 有目录(要摘掉那条链)。
6
+ 有些层不能共存,这一层与谁相斥写在 `template-meta.json` 的 `excludes` 里;生成器据此推导合法形态,
7
+ 不维护白名单。
6
8
 
7
9
  ## 缺省项没有目录
8
10
 
@@ -96,6 +98,10 @@
96
98
  1. 在 `template-meta.json` 的 `frameworks`、`sources` 或 `shells` 里加一项 `{ id, label, hint }`。
97
99
  2. 建同名目录,放入该层独有的文件与 `package.patch.json`。
98
100
  3. 生成器不改:它对目录是缺席即跳过,不需要为新 `id` 写分支。
101
+ 4. 这一层与别的层不能共存时,用 `excludes` 声明:写上对方层的 id,可以跨维度(框架排斥壳、
102
+ 壳排斥框架都行)。只写单边就生效,但建议两边都写,读的人一眼能看清这对互斥。
103
+ 尤其当这一层也要写 `src/main.*` 或 `src/App.*` 时——页面结构只能有一个所有者,
104
+ 必须靠 `excludes` 明确谁赢,不能让合并顺序替你做这个决定。
99
105
 
100
106
  一层只加自己真正需要的东西。能被骨架覆盖的差异(比如只换一个入口文件的 import)优先留在骨架里,
101
107
  叠加层越多,同一处逻辑就有越多份副本要跟着改。
@@ -15,5 +15,10 @@
15
15
  "noUnusedParameters": true,
16
16
  "noFallthroughCasesInSwitch": true
17
17
  },
18
+ /* 故事源不吃本工程:它是独立的 TS 工程,由 tsconfig.stories.json 的纯 tsc 检查。
19
+ 本文件要么是 vue-tsc(Vue 要 SFC 类型),要么带 jsx(React 要 .tsx)——
20
+ 两者都会把故事源 import 的 *.vue / *.tsx 解析成真实组件,
21
+ 盖过 authoring 声明的 UiHandle 通配,把 mount(句柄) 判成型不匹配。 */
22
+ "exclude": ["src/Stories.src/**"],
18
23
  "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"]
19
24
  }
@@ -16,5 +16,10 @@
16
16
  "noUnusedParameters": true,
17
17
  "noFallthroughCasesInSwitch": true
18
18
  },
19
+ /* 故事源不吃本工程:它是独立的 TS 工程,由 tsconfig.stories.json 的纯 tsc 检查。
20
+ 本文件要么是 vue-tsc(Vue 要 SFC 类型),要么带 jsx(React 要 .tsx)——
21
+ 两者都会把故事源 import 的 *.vue / *.tsx 解析成真实组件,
22
+ 盖过 authoring 声明的 UiHandle 通配,把 mount(句柄) 判成型不匹配。 */
23
+ "exclude": ["src/Stories.src/**"],
19
24
  "include": ["src/**/*.ts", "src/**/*.tsx"]
20
25
  }
@@ -1,29 +1,34 @@
1
- // __PROJECT__ 示例工程(文本形态 = JSON v1 的投影)
1
+ // __PROJECT__ 的手写故事示例(文本形态 = JSON v1 的投影)
2
2
  // 资源路径相对项目资源根 Resources/:如 bgm "Audio/main.mp3"
3
3
  // 一个文件里可以写多列(label / scene);拆文件直接放进 Resources/Stories/ 下,构建期扫描自动收录。
4
+ //
5
+ // 这份示例与骨架自带的 `Stories/title/*.json` 并存:TS 源、手写 .story、手写 .json
6
+ // 是同一棵树上的三种文件形态,可以混装。列 id 全在 `manual_` 命名空间里,
7
+ // 因此不会与骨架已有的 `title_main` / `look_around` 撞名(列 id 全工程唯一)。
8
+ // 想从这里接着写,改本文件即可;要切回 TS 源,往 src/Stories.src 里加列即可,两边互不影响。
4
9
 
5
10
  // 标题界面:`type=ui` 的列不进历史、不进存档、不建检查点——它是界面,不是剧情。
6
11
  // 引擎对这类列只管一件事:按 op 渲染(say 出一句、menu 出一组选项),所以下面几条开箱即跑,
7
12
  // 视图上就是标题界面本身。想换成自己的视觉有两条路:常驻舞台的组件放 src/ui/ 并用 mount 控显隐;
8
13
  // 整屏的标题页/设置页属宿主应用,落 src/pages/ 一类宿主目录、由宿主自己切进切出。
9
- scene title_main type=ui
10
- say "欢迎来到 __PROJECT__。"
11
- say "在 Resources/Stories/ 下继续写你的故事;媒体放 Resources/Audio、Resources/Images、Resources/Video。"
14
+ scene manual_intro type=ui
15
+ say "这一段来自 Resources/Stories/manual/manual_intro.story。"
16
+ say "文本故事、手写 JSON、TS 源可以同时在 Resources/Stories/ 下混装;列 id 全工程唯一即可。"
12
17
  menu "接下来去哪里?"
13
- "开始游戏" -> chapter1
14
- "看舞台元素" -> stage_demo
18
+ "看对话与输入" -> manual_talk
19
+ "看舞台元素" -> manual_stage
15
20
 
16
21
  // 游戏正文第一列:进入这里才开始有历史与存档。
17
- scene chapter1
22
+ scene manual_talk
18
23
  input "先问一句:你叫什么名字?" store="player.name"
19
24
  say "你好,{player.name}!"
20
25
  say "这一列是 game 列:从这里开始,每一步都可回溯、可存档。"
21
26
  menu "接下来去哪里?"
22
- "看舞台元素" -> stage_demo
23
- "回标题界面" -> title_main
27
+ "看舞台元素" -> manual_stage
28
+ "回这一列开头" -> manual_talk
24
29
 
25
30
  // scene 列 = 声明式空间层:进入本列时整体装载,切换列时清空
26
- scene stage_demo
31
+ scene manual_stage
27
32
  panel x=5% y=10% width=90% zindex=10
28
33
  text "scene 列 = 声明式空间层:这些方块是元素,不是对话"
29
34
  text "panel 是容器,子元素用更深缩进写"
@@ -1,17 +1 @@
1
- {
2
- "create": {
3
- "remove": [
4
- "Resources/Stories",
5
- "src/Stories.src",
6
- "src/gen/font.gen.d.ts",
7
- "src/gen/script.gen.d.ts",
8
- "src/gen/state.gen.d.ts",
9
- "src/gen/stories.gen.d.ts",
10
- "tsconfig.stories.json"
11
- ],
12
- "unset": {
13
- "scripts": ["stories:build", "stories:watch", "stories-typecheck"],
14
- "devDependencies": ["@lingfan-engine/editor"]
15
- }
16
- }
17
- }
1
+ {}
@@ -15,5 +15,10 @@
15
15
  "noUnusedParameters": true,
16
16
  "noFallthroughCasesInSwitch": true
17
17
  },
18
+ /* 故事源不吃本工程:它是独立的 TS 工程,由 tsconfig.stories.json 的纯 tsc 检查。
19
+ 本文件要么是 vue-tsc(Vue 要 SFC 类型),要么带 jsx(React 要 .tsx)——
20
+ 两者都会把故事源 import 的 *.vue / *.tsx 解析成真实组件,
21
+ 盖过 authoring 声明的 UiHandle 通配,把 mount(句柄) 判成型不匹配。 */
22
+ "exclude": ["src/Stories.src/**"],
18
23
  "include": ["src/**/*.ts", "src/**/*.vue"]
19
24
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.10.0",
2
+ "version": "1.13.0",
3
3
  "minEngineVersion": "0.3.2",
4
4
  "placeholder": "__PROJECT__",
5
5
  "frameworks": [
@@ -11,12 +11,14 @@
11
11
  {
12
12
  "id": "vue",
13
13
  "label": "Vue 3",
14
- "hint": "页面结构换成单文件组件,装配与宿主不变"
14
+ "hint": "页面结构换成单文件组件,装配与宿主不变",
15
+ "excludes": ["react", "miniprogram"]
15
16
  },
16
17
  {
17
18
  "id": "react",
18
19
  "label": "React",
19
- "hint": "页面结构换成 JSX 组件,装配与宿主不变"
20
+ "hint": "页面结构换成 JSX 组件,装配与宿主不变",
21
+ "excludes": ["vue", "miniprogram"]
20
22
  }
21
23
  ],
22
24
  "shells": [
@@ -25,15 +27,17 @@
25
27
  "label": "浏览器",
26
28
  "hint": "只生成 Web 端工程,用 vite 起本地服务"
27
29
  },
28
- {
29
- "id": "miniprogram",
30
- "label": "小程序(UniApp)",
31
- "hint": "页面层换成 UniApp Vue 3 单文件组件,宿主装配走小程序端口"
32
- },
33
30
  {
34
31
  "id": "tauri",
35
32
  "label": "桌面(Tauri 2)",
36
- "hint": "给同一份工程再加一个桌面壳:存档落到应用数据目录,页面结构不变"
33
+ "hint": "给同一份工程再加一个桌面壳:存档落到应用数据目录,页面结构不变",
34
+ "excludes": ["miniprogram"]
35
+ },
36
+ {
37
+ "id": "miniprogram",
38
+ "label": "小程序(UniApp)",
39
+ "hint": "页面层换成 UniApp Vue 3 单文件组件,宿主装配走小程序端口",
40
+ "excludes": ["vue", "react", "tauri"]
37
41
  }
38
42
  ],
39
43
  "sources": [
@@ -44,9 +48,9 @@
44
48
  },
45
49
  {
46
50
  "id": "story",
47
- "label": "故事文件(.story)",
48
- "hint": "直接写引擎自带的文本故事格式:叠在骨架上,摘掉 TS 创作链,改完保存即生效"
51
+ "label": "附带手写故事示例",
52
+ "hint": "多放一份 .story 示例,与 TS 源共存,不摘任何东西"
49
53
  }
50
54
  ],
51
- "_comment": "模板版本唯一来源,独立于引擎版本:仅当 template/v1 内容真正变化时才手动 bump。placeholder 是目录/文件内的工程名占位符,生成时整体替换为工程名(对齐 _LingFanEngineTemplateTitle_ 占位符语义)。minEngineVersion 表示此模板要求的最低引擎版本——生成期会拿它跟 registry 上的实际版本比对,不达标即失败(离线查不到版本时跳过判定)。frameworks / shells / sources 决定生成器会问什么、能叠哪些层,各自首项为缺省值;每一项的 id 对应 template/v1/overlays/<id> 目录(缺省层可以没有目录——缺省故事源 `ts` 就是这样:整条创作链在骨架里,不需要叠加层)。合并顺序为「骨架 → 框架 → 故事源 → 壳」,同路径文件由后一层覆盖前一层。"
55
+ "_comment": "模板版本唯一来源,独立于引擎版本:仅当 template/v1 内容真正变化时才手动 bump。placeholder 是目录/文件内的工程名占位符,生成时整体替换为工程名(对齐 _LingFanEngineTemplateTitle_ 占位符语义)。minEngineVersion 表示此模板要求的最低引擎版本——生成期会拿它跟 registry 上的实际版本比对,不达标即失败(离线查不到版本时跳过判定)。frameworks / shells 决定生成器会问什么、能叠哪些层,各自首项为缺省值;每一项的 id 对应 template/v1/overlays/<id> 目录(缺省层可以没有目录——缺省的 none 与 browser 就是这样,不需要叠加层)。sources 不参与形态问答:TS 源、手写 .story、手写 .json 是同一棵 Resources/Stories/ 树上的三种文件形态,可以共存于一个工程,所以 story 层的目录只额外附一份手写示例、不摘任何骨架文件;缺省故事源 ts 同样没有目录。合并顺序为「骨架 → 框架 → 故事源 → 壳」,同路径文件由后一层覆盖前一层。excludes 声明该层与哪些层不能共存,元素是层 id、可以跨维度引用,生成器据此推导合法形态;只写单边即可生效,但成对声明能让读的人一眼看清——所以每一对互斥都在两边都写了。页面结构只能有一个所有者:两层都要写 src/main.* 或 src/App.* 时,必须在其中一方 excludes 里声明互斥,不得靠合并顺序决定谁活下来。"
52
56
  }