@ttmg/cli 0.4.1 → 0.4.3

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.
Files changed (127) hide show
  1. package/dist/index.js +146 -19
  2. package/dist/index.js.map +1 -1
  3. package/dist/package.json +1 -1
  4. package/dist/public/assets/Detail-1nSjYvRw.js +1 -0
  5. package/dist/public/assets/Detail-1nSjYvRw.js.br +0 -0
  6. package/dist/public/assets/MonetizationMode-hwWN5zUT.js +1 -0
  7. package/dist/public/assets/MonetizationModeSummary-BmvxAKbI.js +1 -0
  8. package/dist/public/assets/SectionHeader-BL_bJf61.js +1 -0
  9. package/dist/public/assets/Tag-B4bSl-_E.js +1 -0
  10. package/dist/public/assets/baseForm-BAvk37jF.css +1 -0
  11. package/dist/public/assets/baseForm-BAvk37jF.css.br +0 -0
  12. package/dist/public/assets/baseForm-zW5Gb1OH.js +10 -0
  13. package/dist/public/assets/baseForm-zW5Gb1OH.js.br +0 -0
  14. package/dist/public/assets/col-BXewy14r.css +1 -0
  15. package/dist/public/assets/col-BXewy14r.css.br +0 -0
  16. package/dist/public/assets/col-vAurEPaC.js +1 -0
  17. package/dist/public/assets/{compass-VJNhHwqW.js → compass-DEadzu-v.js} +1 -1
  18. package/dist/public/assets/index-3FxbJxwu.css +1 -0
  19. package/dist/public/assets/index-BCdOYP-_.js +1 -0
  20. package/dist/public/assets/{index-BO5MBsdm.js → index-BJmPZor5.js} +1 -1
  21. package/dist/public/assets/{index-BJZ4gOGZ.css → index-BRJFt3oz.css} +1 -1
  22. package/dist/public/assets/index-BRJFt3oz.css.br +0 -0
  23. package/dist/public/assets/{index-BBR3CPkN.js → index-BXllV1mP.js} +1 -1
  24. package/dist/public/assets/index-BXllV1mP.js.br +0 -0
  25. package/dist/public/assets/index-BdixrK5I.css +1 -0
  26. package/dist/public/assets/index-BdixrK5I.css.br +0 -0
  27. package/dist/public/assets/index-BlG-Rcwd.css +1 -0
  28. package/dist/public/assets/{index-s_30KWiA.js → index-Bpx0cjfq.js} +1 -1
  29. package/dist/public/assets/index-BuiCnshQ.js +1 -0
  30. package/dist/public/assets/{index-bQUQ2Wmz.js → index-C2jjb4tf.js} +1 -1
  31. package/dist/public/assets/index-C5k1IRcX.js +1 -0
  32. package/dist/public/assets/index-C5k1IRcX.js.br +0 -0
  33. package/dist/public/assets/{index-DMsjcIN8.js → index-CHODjAYV.js} +1 -1
  34. package/dist/public/assets/index-CYnulX57.css +1 -0
  35. package/dist/public/assets/index-CYnulX57.css.br +0 -0
  36. package/dist/public/assets/index-CaArjQkD.js +1 -0
  37. package/dist/public/assets/index-CaArjQkD.js.br +0 -0
  38. package/dist/public/assets/index-ChtjciM4.js +1 -0
  39. package/dist/public/assets/index-ChtjciM4.js.br +0 -0
  40. package/dist/public/assets/index-CozQgXOx.js +1 -0
  41. package/dist/public/assets/index-CozQgXOx.js.br +0 -0
  42. package/dist/public/assets/index-Cw4SLGVh.js +1 -0
  43. package/dist/public/assets/index-Czg0RYGI.css +1 -0
  44. package/dist/public/assets/index-Czg0RYGI.css.br +0 -0
  45. package/dist/public/assets/index-DBDUeSB8.css +1 -0
  46. package/dist/public/assets/index-DBDUeSB8.css.br +0 -0
  47. package/dist/public/assets/index-DKxf26_O.css +1 -0
  48. package/dist/public/assets/{index-RYY-l6Oq.css → index-DM7Cg9s9.css} +1 -1
  49. package/dist/public/assets/index-DVDX92Zz.js +1 -0
  50. package/dist/public/assets/{index-DbxY65Om.js → index-Db3F1FgQ.js} +1 -1
  51. package/dist/public/assets/{index-C1C34gtn.js → index-Dd2Oev0p.js} +1 -1
  52. package/dist/public/assets/index-DfVst8q1.css +1 -0
  53. package/dist/public/assets/{index-BH099q_U.js → index-DhK1XTMu.js} +1 -1
  54. package/dist/public/assets/index-Dj0vBPuW.js +1 -0
  55. package/dist/public/assets/{index-C_npSvtQ.js → index-Do7tPKef.js} +1 -1
  56. package/dist/public/assets/index-DpnThngH.js +1 -0
  57. package/dist/public/assets/index-DyHfBQPm.js +1 -0
  58. package/dist/public/assets/index-HqjZ5P-l.css +1 -0
  59. package/dist/public/assets/index-P3QtE2VP.js +1 -0
  60. package/dist/public/assets/index-P3QtE2VP.js.br +0 -0
  61. package/dist/public/assets/{index-Dn5HHf-0.js → index-QtH0wQu1.js} +1 -1
  62. package/dist/public/assets/index-qycdeV7S.js +1 -0
  63. package/dist/public/assets/index-qycdeV7S.js.br +0 -0
  64. package/dist/public/assets/index-vlN_-PRJ.css +1 -0
  65. package/dist/public/assets/index-vuGJOmRn.js +1 -0
  66. package/dist/public/assets/index-wqj8y8SZ.js +21 -0
  67. package/dist/public/assets/index-wqj8y8SZ.js.br +0 -0
  68. package/dist/public/assets/layers-DDrUeTNf.js +1 -0
  69. package/dist/public/assets/list-checks-C4ci4EKo.js +1 -0
  70. package/dist/public/assets/{sparkles-Bv2GxrFr.js → sparkles-PwlPP_sT.js} +1 -1
  71. package/dist/public/assets/triangle-alert-BAadCxAv.js +1 -0
  72. package/dist/public/assets/{zap-CjkC_qWZ.js → zap-CgIIDLY4.js} +1 -1
  73. package/dist/public/index.html +2 -2
  74. package/dist/scripts/docs/README.md +80 -0
  75. package/dist/scripts/docs/capture.js +91 -7
  76. package/dist/scripts/docs/check.js +184 -0
  77. package/dist/scripts/docs/render.js +372 -12
  78. package/dist/scripts/docs/scenarios/devtool-guide.mjs +921 -0
  79. package/package.json +1 -1
  80. package/CHANGELOG.md +0 -267
  81. package/dist/public/assets/Card-ASGGlJ8S.js +0 -1
  82. package/dist/public/assets/Detail-DY__Zw0r.js +0 -1
  83. package/dist/public/assets/Detail-DY__Zw0r.js.br +0 -0
  84. package/dist/public/assets/MonetizationMode-sHN_4ybQ.js +0 -1
  85. package/dist/public/assets/MonetizationModeSummary-DLvyriLM.js +0 -1
  86. package/dist/public/assets/SectionHeader-B7dueBCf.js +0 -1
  87. package/dist/public/assets/Tag-BsX0d6UL.js +0 -1
  88. package/dist/public/assets/arrow-left-uixm7Sd1.js +0 -1
  89. package/dist/public/assets/baseForm-C8XfTFyG.js +0 -10
  90. package/dist/public/assets/baseForm-C8XfTFyG.js.br +0 -0
  91. package/dist/public/assets/baseForm-Dl4zA6hU.css +0 -1
  92. package/dist/public/assets/baseForm-Dl4zA6hU.css.br +0 -0
  93. package/dist/public/assets/boxes-C7RoIbId.js +0 -1
  94. package/dist/public/assets/chevron-right-B1WBZrrD.js +0 -1
  95. package/dist/public/assets/circle-check-wUxXeINd.js +0 -1
  96. package/dist/public/assets/index-BAud2cRu.css +0 -1
  97. package/dist/public/assets/index-BBR3CPkN.js.br +0 -0
  98. package/dist/public/assets/index-BJZ4gOGZ.css.br +0 -0
  99. package/dist/public/assets/index-BNc8JtET.js +0 -1
  100. package/dist/public/assets/index-Bd-f8ERX.js +0 -1
  101. package/dist/public/assets/index-ByX5RuFA.css +0 -1
  102. package/dist/public/assets/index-C7Tmy9g3.js +0 -1
  103. package/dist/public/assets/index-CCKb9Y-j.js +0 -14
  104. package/dist/public/assets/index-CCKb9Y-j.js.br +0 -0
  105. package/dist/public/assets/index-CFd8iglC.css +0 -1
  106. package/dist/public/assets/index-CFd8iglC.css.br +0 -0
  107. package/dist/public/assets/index-CKhfKyA3.js +0 -1
  108. package/dist/public/assets/index-CKhfKyA3.js.br +0 -0
  109. package/dist/public/assets/index-Cc1ilXmc.css +0 -1
  110. package/dist/public/assets/index-DHXlsTU6.css +0 -1
  111. package/dist/public/assets/index-DHXlsTU6.css.br +0 -0
  112. package/dist/public/assets/index-DNd8ZTok.js +0 -1
  113. package/dist/public/assets/index-DNd8ZTok.js.br +0 -0
  114. package/dist/public/assets/index-DpkktYot.css +0 -1
  115. package/dist/public/assets/index-DtoDrber.js +0 -1
  116. package/dist/public/assets/index-DtoDrber.js.br +0 -0
  117. package/dist/public/assets/index-Dx5VGEQm.css +0 -1
  118. package/dist/public/assets/index-ZHPtAdJM.js +0 -1
  119. package/dist/public/assets/index-ZHPtAdJM.js.br +0 -0
  120. package/dist/public/assets/index-ZtlCPCV9.js +0 -1
  121. package/dist/public/assets/index-_6n0s04V.css +0 -1
  122. package/dist/public/assets/index-_6n0s04V.css.br +0 -0
  123. package/dist/public/assets/index-a-rMUS2V.js +0 -1
  124. package/dist/public/assets/index-qqnHwFjO.js +0 -1
  125. package/dist/public/assets/index-r3aSeNL1.js +0 -1
  126. package/dist/public/assets/index-t4JeqZ4a.js +0 -1
  127. package/dist/public/assets/layers-Biv61qtE.js +0 -1
@@ -1,5 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const crypto = require('crypto');
4
+ const childProcess = require('child_process');
3
5
  const { pathToFileURL } = require('url');
4
6
 
5
7
  function parseArgs(argv) {
@@ -16,36 +18,239 @@ function parseArgs(argv) {
16
18
  async function loadRecipes(recipesPath) {
17
19
  const module = await import(pathToFileURL(recipesPath).href);
18
20
  return {
21
+ schemaVersion: module.schemaVersion || 1,
19
22
  recipes: module.recipes || module.default || [],
20
23
  locales: module.locales || ['zh-CN', 'en-US'],
21
24
  guideTitle: module.guideTitle || 'TikTok 小游戏调试工具使用指南',
25
+ guideSource: module.guideSource || null,
22
26
  guideSections: module.guideSections || {},
23
27
  guideFaqs: module.guideFaqs || {},
28
+ docVersion: module.docVersion || {},
29
+ documentationContract: module.documentationContract || {},
30
+ guideReleaseChecks: module.guideReleaseChecks || [],
31
+ screenshotInventory: module.screenshotInventory || [],
24
32
  };
25
33
  }
26
34
 
27
- function buildManifest({ recipes, locales, outputDir, guideTitle, guideSections, guideFaqs }) {
35
+ function readJsonIfExists(filePath) {
36
+ if (!fs.existsSync(filePath)) {
37
+ return null;
38
+ }
39
+ try {
40
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
46
+ function safeExec(command, cwd) {
47
+ try {
48
+ return childProcess.execSync(command, {
49
+ cwd,
50
+ encoding: 'utf8',
51
+ stdio: ['ignore', 'pipe', 'ignore'],
52
+ }).trim();
53
+ } catch {
54
+ return '';
55
+ }
56
+ }
57
+
58
+ function dropUndefined(value) {
59
+ if (Array.isArray(value)) {
60
+ return value.map(dropUndefined);
61
+ }
62
+ if (!value || typeof value !== 'object') {
63
+ return value;
64
+ }
65
+ return Object.fromEntries(
66
+ Object.entries(value)
67
+ .filter(([, nestedValue]) => nestedValue !== undefined)
68
+ .map(([key, nestedValue]) => [key, dropUndefined(nestedValue)]),
69
+ );
70
+ }
71
+
72
+ function stableStringify(value) {
73
+ if (Array.isArray(value)) {
74
+ return `[${value.map(stableStringify).join(',')}]`;
75
+ }
76
+ if (!value || typeof value !== 'object') {
77
+ return JSON.stringify(value);
78
+ }
79
+ return `{${Object.keys(value)
80
+ .sort()
81
+ .map(key => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
82
+ .join(',')}}`;
83
+ }
84
+
85
+ function createScenarioFingerprint({ schemaVersion, locales, guideTitle, guideSource, recipes }) {
86
+ return crypto
87
+ .createHash('sha256')
88
+ .update(stableStringify({ schemaVersion, locales, guideTitle, guideSource, recipes }))
89
+ .digest('hex')
90
+ .slice(0, 16);
91
+ }
92
+
93
+ function screenshotPath(recipe, locale) {
94
+ return `assets/${recipe.id}.${locale}.png`;
95
+ }
96
+
97
+ function buildScreenshotIndex({ recipes, locales, outputDir, guideTitle, guideSource, scenarioFingerprint, screenshotInventory }) {
28
98
  return {
99
+ schemaVersion: 1,
29
100
  title: guideTitle,
101
+ guideSource,
102
+ scenarioFingerprint,
103
+ generatedAt: new Date().toISOString(),
104
+ outputDir,
105
+ screenshotInventory,
106
+ screenshots: recipes.flatMap(recipe =>
107
+ locales.map(locale => dropUndefined({
108
+ id: `${recipe.id}.${locale}`,
109
+ recipeId: recipe.id,
110
+ locale,
111
+ route: recipe.route,
112
+ page: recipe.page,
113
+ productArea: recipe.productArea,
114
+ fixture: recipe.fixture,
115
+ capture: recipe.capture,
116
+ doc: recipe.doc,
117
+ path: screenshotPath(recipe, locale),
118
+ })),
119
+ ),
120
+ };
121
+ }
122
+
123
+ function buildManifest({
124
+ schemaVersion,
125
+ recipes,
126
+ locales,
127
+ outputDir,
128
+ guideTitle,
129
+ guideSource,
130
+ guideSections,
131
+ guideFaqs,
132
+ scenarioFingerprint,
133
+ docVersion,
134
+ documentationContract,
135
+ guideReleaseChecks,
136
+ screenshotInventory,
137
+ }) {
138
+ return {
139
+ schemaVersion,
140
+ title: guideTitle,
141
+ guideSource,
142
+ docVersion,
143
+ documentationContract,
144
+ guideReleaseChecks,
145
+ screenshotInventory,
146
+ scenarioFingerprint,
30
147
  generatedAt: new Date().toISOString(),
31
148
  outputDir,
32
149
  locales,
33
150
  guideSections,
34
151
  guideFaqs,
35
- pages: recipes.map(recipe => ({
152
+ pages: recipes.map(recipe => dropUndefined({
36
153
  id: recipe.id,
154
+ title: recipe.title,
37
155
  route: recipe.route,
156
+ page: recipe.page,
157
+ productArea: recipe.productArea,
158
+ fixture: recipe.fixture,
159
+ capture: recipe.capture,
38
160
  captureMode: recipe.captureMode,
39
161
  waitFor: recipe.waitFor,
162
+ doc: recipe.doc,
163
+ privacy: recipe.privacy,
164
+ acceptance: recipe.acceptance,
165
+ updatePolicy: recipe.updatePolicy,
40
166
  notes: recipe.notes,
41
167
  screenshots: locales.map(locale => ({
42
168
  locale,
43
- path: `assets/${recipe.id}.${locale}.png`,
169
+ path: screenshotPath(recipe, locale),
44
170
  })),
45
171
  })),
46
172
  };
47
173
  }
48
174
 
175
+ function buildDocVersion({
176
+ repoRoot,
177
+ outputDir,
178
+ guideTitle,
179
+ guideSource,
180
+ schemaVersion,
181
+ locales,
182
+ scenarioFingerprint,
183
+ docVersion,
184
+ documentationContract,
185
+ guideReleaseChecks,
186
+ screenshotInventory,
187
+ }) {
188
+ const rootPackage = readJsonIfExists(path.join(repoRoot, 'package.json')) || {};
189
+ const cliPackage = readJsonIfExists(path.join(repoRoot, 'packages/libs/dev-tools/cli/package.json')) || {};
190
+ const gitCommit = safeExec('git rev-parse --short HEAD', repoRoot);
191
+ const gitStatus = safeExec('git status --short', repoRoot);
192
+
193
+ return dropUndefined({
194
+ schemaVersion: 1,
195
+ id: docVersion.id,
196
+ status: docVersion.status,
197
+ title: guideTitle,
198
+ generatedAt: new Date().toISOString(),
199
+ outputDir,
200
+ guideSource,
201
+ sourceOfTruth: docVersion.sourceOfTruth,
202
+ publishTiming: docVersion.publishTiming,
203
+ ownerReviewRequired: docVersion.ownerReviewRequired,
204
+ scenario: {
205
+ schemaVersion,
206
+ scenarioFingerprint,
207
+ locales,
208
+ screenshotCount: screenshotInventory.length,
209
+ screenshotIds: screenshotInventory.map(item => item.id),
210
+ },
211
+ packages: {
212
+ workspace: rootPackage.version,
213
+ cli: cliPackage.version,
214
+ ttmgPack: cliPackage.dependencies?.['ttmg-pack'],
215
+ },
216
+ git: {
217
+ commit: gitCommit || undefined,
218
+ dirty: gitStatus ? true : undefined,
219
+ },
220
+ qualityGate: {
221
+ documentationContract,
222
+ checks: guideReleaseChecks,
223
+ },
224
+ artifacts: {
225
+ manifest: 'manifest.json',
226
+ screenshotIndex: 'screenshot-index.json',
227
+ qualityGate: 'quality-gate.json',
228
+ guideDraft: 'index.md',
229
+ screenshotsDir: 'assets/',
230
+ },
231
+ });
232
+ }
233
+
234
+ function buildQualityGate({
235
+ guideTitle,
236
+ guideSource,
237
+ scenarioFingerprint,
238
+ documentationContract,
239
+ guideReleaseChecks,
240
+ screenshotInventory,
241
+ }) {
242
+ return {
243
+ schemaVersion: 1,
244
+ title: guideTitle,
245
+ guideSource,
246
+ scenarioFingerprint,
247
+ generatedAt: new Date().toISOString(),
248
+ documentationContract,
249
+ guideReleaseChecks,
250
+ screenshotInventory,
251
+ };
252
+ }
253
+
49
254
  function renderRecipeBlock(locale, recipe, headingLevel = 3) {
50
255
  const note = recipe.notes[locale];
51
256
  const purposeLabel = locale === 'zh-CN' ? '用途' : 'Purpose';
@@ -89,6 +294,7 @@ function renderCommand({ title, description, command }, headingLevel = 3) {
89
294
  }
90
295
 
91
296
  const zhSectionNumbers = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
297
+ const enSectionNumbers = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X'];
92
298
 
93
299
  function formatSectionTitle(locale, index, title) {
94
300
  if (!index) {
@@ -97,7 +303,7 @@ function formatSectionTitle(locale, index, title) {
97
303
  if (locale === 'zh-CN') {
98
304
  return `${zhSectionNumbers[index - 1] || index}、${title}`;
99
305
  }
100
- return `${index}. ${title}`;
306
+ return `${enSectionNumbers[index - 1] || index}. ${title}`;
101
307
  }
102
308
 
103
309
  function renderGettingStarted(locale, sectionIndex) {
@@ -170,6 +376,7 @@ function renderIntro(locale, sectionIndex) {
170
376
  '- 如果你只是想把项目跑起来,先看“开始之前”和“开始调试”。',
171
377
  '- 如果你要确认项目配置是否完整,看“项目概览”。',
172
378
  '- 如果你要接入登录、分享、广告、内购、直玩卡等平台能力,看“接入助手”。',
379
+ '- 如果你要优化 Unity Wasm 或了解待开放的非 Unity 子包收集,看“性能优化”。',
173
380
  '- 如果你准备提交预览版本,看“上传发布”。',
174
381
  '',
175
382
  ].join('\n');
@@ -183,6 +390,7 @@ function renderIntro(locale, sectionIndex) {
183
390
  '- To get the project running, start with “Before You Start” and “Start Debugging”.',
184
391
  '- To verify project setup, read “Project Overview”.',
185
392
  '- To integrate login, sharing, ads, IAP, direct-play cards, or other platform capabilities, read “Integration Assistant”.',
393
+ '- To optimize Unity Wasm or preview the coming-soon non-Unity subpackage flow, read “Performance Optimization”.',
186
394
  '- To prepare a preview build, read “Upload & Publish”.',
187
395
  '',
188
396
  ].join('\n');
@@ -269,6 +477,39 @@ function renderIntegrationAssistantSection({ recipes, locale, sectionIndex }) {
269
477
  ].join('\n');
270
478
  }
271
479
 
480
+ function renderPerformanceOptimizationSection({ recipes, locale, sectionIndex }) {
481
+ const subpackage = getRecipeById(recipes, 'subpackage-collect');
482
+ const wasm = getRecipeById(recipes, 'wasm-split');
483
+
484
+ if (locale === 'zh-CN') {
485
+ return [
486
+ `## ${formatSectionTitle(locale, sectionIndex, '性能优化')}`,
487
+ '',
488
+ '### 如何选择',
489
+ '',
490
+ '- 非 Unity 小游戏可先了解“子包收集(待开放)”,并阅读[子包收集使用指南](https://bytedance.my.larkoffice.com/docx/MIyMdmzb0oOQAKxNHGNmR5hDyAh)。正式开放后,DevTool 会按真实启动路径采集预下载子包,并生成预加载配置。',
491
+ '- Unity 小游戏继续使用“Wasm 分包”,将 Wasm 代码拆分为按需加载的子包。',
492
+ '- 两条链路面向不同运行时,不能混用。当前子包收集仅展示入口和边界,不代表能力已经正式开放。',
493
+ '',
494
+ subpackage ? renderRecipeBlock(locale, subpackage, 3) : '',
495
+ wasm ? renderRecipeBlock(locale, wasm, 3) : '',
496
+ ].join('\n');
497
+ }
498
+
499
+ return [
500
+ `## ${formatSectionTitle(locale, sectionIndex, 'Performance Optimization')}`,
501
+ '',
502
+ '### Choose the Right Flow',
503
+ '',
504
+ '- Non-Unity mini games can preview Subpackage Collection (Coming Soon) and read the [Subpackage Collection Guide](https://bytedance.my.larkoffice.com/docx/MIyMdmzb0oOQAKxNHGNmR5hDyAh). After release, DevTool will capture subpackages downloaded along the real startup path and generate preload configuration.',
505
+ '- Unity mini games should continue to use Wasm Split to move Wasm code into on-demand subpackages.',
506
+ '- These flows target different runtimes and are not interchangeable. The current Subpackage Collection page previews the entry and boundaries; it does not mean the feature is generally available.',
507
+ '',
508
+ subpackage ? renderRecipeBlock(locale, subpackage, 3) : '',
509
+ wasm ? renderRecipeBlock(locale, wasm, 3) : '',
510
+ ].join('\n');
511
+ }
512
+
272
513
  function renderUploadPublishSection({ recipes, locale, sectionIndex }) {
273
514
  const upload = getRecipeById(recipes, 'upload-publish');
274
515
  const restricted = getRecipeById(recipes, 'upload-game-restricted');
@@ -371,7 +612,8 @@ function renderSidebarModules({ recipes, locale }) {
371
612
  recipes: [byId('project-detail')].filter(Boolean),
372
613
  }),
373
614
  renderIntegrationAssistantSection({ recipes, locale, sectionIndex: 5 }),
374
- renderUploadPublishSection({ recipes, locale, sectionIndex: 6 }),
615
+ renderPerformanceOptimizationSection({ recipes, locale, sectionIndex: 6 }),
616
+ renderUploadPublishSection({ recipes, locale, sectionIndex: 7 }),
375
617
  ].join('\n');
376
618
  }
377
619
 
@@ -397,7 +639,8 @@ function renderSidebarModules({ recipes, locale }) {
397
639
  recipes: [byId('project-detail')].filter(Boolean),
398
640
  }),
399
641
  renderIntegrationAssistantSection({ recipes, locale, sectionIndex: 5 }),
400
- renderUploadPublishSection({ recipes, locale, sectionIndex: 6 }),
642
+ renderPerformanceOptimizationSection({ recipes, locale, sectionIndex: 6 }),
643
+ renderUploadPublishSection({ recipes, locale, sectionIndex: 7 }),
401
644
  ].join('\n');
402
645
  }
403
646
 
@@ -414,26 +657,71 @@ function renderFaqSection(title, items = []) {
414
657
  ].join('\n');
415
658
  }
416
659
 
660
+ function renderReferencesSection(locale, sectionIndex) {
661
+ if (locale === 'zh-CN') {
662
+ return [
663
+ `## ${formatSectionTitle(locale, sectionIndex, '更多参考')}`,
664
+ '',
665
+ '- [子包收集使用指南](https://bytedance.my.larkoffice.com/docx/MIyMdmzb0oOQAKxNHGNmR5hDyAh)',
666
+ '- [Unity Wasm 分包指南](https://bytedance.larkoffice.com/wiki/DZDzwzYdDiJpBSkrhcTcWailnuh)',
667
+ '- [Unity 插件更新说明](https://bytedance.larkoffice.com/wiki/OyGMwHVW9iSeneknlOcc2qb6nUh)',
668
+ '- [小游戏运行时与 JSAPI 参考](https://bytedance.larkoffice.com/wiki/GQscwDOQSiPxyjkbzyfckh2pnDg)',
669
+ '',
670
+ ].join('\n');
671
+ }
672
+
673
+ return [
674
+ `## ${formatSectionTitle(locale, sectionIndex, 'More References')}`,
675
+ '',
676
+ '- [Subpackage Collection Guide](https://bytedance.my.larkoffice.com/docx/MIyMdmzb0oOQAKxNHGNmR5hDyAh)',
677
+ '- [Unity Wasm Split Guide](https://bytedance.larkoffice.com/wiki/DZDzwzYdDiJpBSkrhcTcWailnuh)',
678
+ '- [Unity Plugin Update Guide](https://bytedance.larkoffice.com/wiki/OyGMwHVW9iSeneknlOcc2qb6nUh)',
679
+ '- [Mini-game Runtime and JSAPI Reference](https://bytedance.larkoffice.com/wiki/GQscwDOQSiPxyjkbzyfckh2pnDg)',
680
+ '',
681
+ ].join('\n');
682
+ }
683
+
684
+ function renderNavigation(guideSections = {}) {
685
+ const zh = ['导读', ...(guideSections['zh-CN'] || []), '常见问题', '更多参考'];
686
+ const en = ['Overview', ...(guideSections['en-US'] || []), 'FAQ', 'More References'];
687
+
688
+ return [
689
+ '## 选择语言和模块 / Choose Language and Section',
690
+ '',
691
+ '- 中文',
692
+ ...zh.map((item, index) => ` - ${formatSectionTitle('zh-CN', index + 1, item)}`),
693
+ '- English',
694
+ ...en.map((item, index) => ` - ${formatSectionTitle('en-US', index + 1, item)}`),
695
+ '',
696
+ ].join('\n');
697
+ }
698
+
417
699
  function renderMarkdown({ recipes, guideTitle, guideSections, guideFaqs = {} }) {
418
- const zhFaqSection = renderFaqSection(formatSectionTitle('zh-CN', 7, '常见问题'), guideFaqs['zh-CN']);
419
- const enFaqSection = renderFaqSection(formatSectionTitle('en-US', 7, 'FAQ'), guideFaqs['en-US']);
700
+ const zhFaqSection = renderFaqSection(formatSectionTitle('zh-CN', 8, '常见问题'), guideFaqs['zh-CN']);
701
+ const enFaqSection = renderFaqSection(formatSectionTitle('en-US', 8, 'FAQ'), guideFaqs['en-US']);
420
702
 
421
703
  return [
422
704
  `# ${guideTitle}`,
423
705
  '',
706
+ renderNavigation(guideSections),
707
+ '# 中文',
708
+ '',
424
709
  renderIntro('zh-CN', 1),
425
710
  renderGettingStarted('zh-CN', 2),
426
711
  renderSidebarModules({ recipes, locale: 'zh-CN' }),
427
712
  '',
428
713
  zhFaqSection,
429
714
  '',
430
- '# TikTok Mini Game Debugging Tool Guide',
715
+ renderReferencesSection('zh-CN', 9),
716
+ '# English',
431
717
  '',
432
718
  renderIntro('en-US', 1),
433
719
  renderGettingStarted('en-US', 2),
434
720
  renderSidebarModules({ recipes, locale: 'en-US' }),
435
721
  '',
436
722
  enFaqSection,
723
+ '',
724
+ renderReferencesSection('en-US', 9),
437
725
  ].join('\n');
438
726
  }
439
727
 
@@ -449,10 +737,67 @@ async function main() {
449
737
  throw new Error('Missing required argument: --output-dir');
450
738
  }
451
739
 
452
- const { recipes, locales, guideTitle, guideSections, guideFaqs } = await loadRecipes(recipesPath);
740
+ const {
741
+ schemaVersion,
742
+ recipes,
743
+ locales,
744
+ guideTitle,
745
+ guideSource,
746
+ guideSections,
747
+ guideFaqs,
748
+ docVersion,
749
+ documentationContract,
750
+ guideReleaseChecks,
751
+ screenshotInventory,
752
+ } = await loadRecipes(recipesPath);
453
753
  fs.mkdirSync(outputDir, { recursive: true });
454
754
 
455
- const manifest = buildManifest({ recipes, locales, outputDir, guideTitle, guideSections, guideFaqs });
755
+ const scenarioFingerprint = createScenarioFingerprint({ schemaVersion, recipes, locales, guideTitle, guideSource });
756
+ const manifest = buildManifest({
757
+ schemaVersion,
758
+ recipes,
759
+ locales,
760
+ outputDir,
761
+ guideTitle,
762
+ guideSource,
763
+ guideSections,
764
+ guideFaqs,
765
+ scenarioFingerprint,
766
+ docVersion,
767
+ documentationContract,
768
+ guideReleaseChecks,
769
+ screenshotInventory,
770
+ });
771
+ const screenshotIndex = buildScreenshotIndex({
772
+ recipes,
773
+ locales,
774
+ outputDir,
775
+ guideTitle,
776
+ guideSource,
777
+ scenarioFingerprint,
778
+ screenshotInventory,
779
+ });
780
+ const docVersionSnapshot = buildDocVersion({
781
+ repoRoot: process.cwd(),
782
+ outputDir,
783
+ guideTitle,
784
+ guideSource,
785
+ schemaVersion,
786
+ locales,
787
+ scenarioFingerprint,
788
+ docVersion,
789
+ documentationContract,
790
+ guideReleaseChecks,
791
+ screenshotInventory,
792
+ });
793
+ const qualityGate = buildQualityGate({
794
+ guideTitle,
795
+ guideSource,
796
+ scenarioFingerprint,
797
+ documentationContract,
798
+ guideReleaseChecks,
799
+ screenshotInventory,
800
+ });
456
801
  const markdown = renderMarkdown({ recipes, locales, guideTitle, guideSections, guideFaqs });
457
802
 
458
803
  fs.writeFileSync(
@@ -460,9 +805,24 @@ async function main() {
460
805
  `${JSON.stringify(manifest, null, 2)}\n`,
461
806
  'utf8',
462
807
  );
808
+ fs.writeFileSync(
809
+ path.join(outputDir, 'screenshot-index.json'),
810
+ `${JSON.stringify(screenshotIndex, null, 2)}\n`,
811
+ 'utf8',
812
+ );
813
+ fs.writeFileSync(
814
+ path.join(outputDir, 'doc-version.json'),
815
+ `${JSON.stringify(docVersionSnapshot, null, 2)}\n`,
816
+ 'utf8',
817
+ );
818
+ fs.writeFileSync(
819
+ path.join(outputDir, 'quality-gate.json'),
820
+ `${JSON.stringify(qualityGate, null, 2)}\n`,
821
+ 'utf8',
822
+ );
463
823
  fs.writeFileSync(path.join(outputDir, 'index.md'), `${markdown}\n`, 'utf8');
464
824
 
465
- console.log(`[ttmg-docs] rendered manifest and markdown into ${outputDir}`);
825
+ console.log(`[ttmg-docs] rendered manifest, screenshot index, version snapshot, quality gate and markdown into ${outputDir}`);
466
826
  }
467
827
 
468
828
  main().catch(error => {