@playcraft/devkit 1.0.16 → 1.0.18

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 (57) hide show
  1. package/README.md +259 -0
  2. package/cli/bin/playable-scripts.js +44 -1
  3. package/cli/commands/build.js +130 -17
  4. package/cli/commands/builds.js +13 -2
  5. package/cli/commands/dev.js +7 -1
  6. package/cli/commands/pack.js +308 -39
  7. package/cli/commands/vite-dev.js +7 -1
  8. package/core/batch-build.js +1091 -124
  9. package/core/cos-uploader.js +264 -20
  10. package/core/index.js +71 -4
  11. package/core/loaders/gltf-loader.js +74 -28
  12. package/core/options.js +310 -15
  13. package/core/plugins/AdikteevInjectorPlugin.js +26 -4
  14. package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
  15. package/core/plugins/DAPIInjectorPlugin.js +25 -2
  16. package/core/plugins/DebuggerInjectionPlugin.js +31 -3
  17. package/core/plugins/ExitAPIInjectorPlugin.js +57 -3
  18. package/core/plugins/FflateCompressionPlugin.js +225 -37
  19. package/core/plugins/FontInjectionWebpackPlugin.js +59 -0
  20. package/core/plugins/LiftoffInjectorPlugin.js +26 -4
  21. package/core/plugins/MRAIDInjectorPlugin.js +24 -2
  22. package/core/plugins/MintegralInjectorPlugin.js +25 -2
  23. package/core/plugins/PangleInjectorPlugin.js +24 -2
  24. package/core/plugins/SnapchatInjectorPlugin.js +26 -4
  25. package/core/plugins/TikTokInjectorPlugin.js +24 -2
  26. package/core/plugins/UnityInjectorPlugin.js +37 -8
  27. package/core/plugins/ZipPlugin.js +138 -19
  28. package/core/resources/fonts/README.md +31 -0
  29. package/core/resources/fonts/maoken-regular.woff2 +0 -0
  30. package/core/resources/fonts/maoken-regular.woff2.chars +1 -0
  31. package/core/resources/fonts/maoken-zhuyuan.ttf +0 -0
  32. package/core/resources/fonts/noto-sans-sc-regular.woff2 +0 -0
  33. package/core/resources/fonts/pangmen-biaoti.ttf +0 -0
  34. package/core/resources/fonts/pangmen-regular.woff2 +0 -0
  35. package/core/resources/fonts/pangmen-regular.woff2.chars +1 -0
  36. package/core/resources/fonts/poppins-regular.woff2 +0 -0
  37. package/core/utils/buildDefines.js +29 -2
  38. package/core/utils/buildTemplateString.js +40 -5
  39. package/core/utils/date.js +16 -1
  40. package/core/utils/fontRegistry.js +97 -0
  41. package/core/utils/fontResolver.js +398 -0
  42. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
  43. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
  44. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
  45. package/core/utils/injectSDKPlugins.js +58 -11
  46. package/core/utils/logOptions.js +37 -4
  47. package/core/utils/mergeOptions.js +19 -2
  48. package/core/utils/parseArgvOptions.js +110 -5
  49. package/core/utils/resolveChannelFold2Zip.js +15 -3
  50. package/core/utils/validateThemeData.js +220 -25
  51. package/core/validators/pre-build-checker.js +201 -21
  52. package/core/validators/tracking-validator.js +358 -40
  53. package/core/vite.dev.js +332 -16
  54. package/core/webpack.build.js +494 -52
  55. package/core/webpack.common.js +177 -11
  56. package/core/webpack.dev.js +82 -5
  57. package/package.json +3 -2
package/core/vite.dev.js CHANGED
@@ -1,25 +1,341 @@
1
- "use strict";const path=require("path");const fs=require("fs");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");/**
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const { options } = require('./options.js');
6
+ const { mergeOptions } = require('./utils/mergeOptions.js');
7
+ const { buildDefines } = require('./utils/buildDefines.js');
8
+ const { logOptions } = require('./utils/logOptions.js');
9
+
10
+ /**
2
11
  * 创建 Vite 开发配置
3
12
  * @param {Partial<import('./index').CLIOptions>} [customOptions] - 自定义选项,会与默认选项合并
4
13
  * @param {Record<string, any>} [customDefines] - 额外的 define 常量
5
14
  * @param {import('vite').UserConfig} [viteCustomConfig] - 自定义 Vite 配置,会与默认配置合并
6
15
  * @returns {import('vite').UserConfig} 最终的 Vite 开发配置
7
- */function makeViteDevConfig(customOptions,customDefines,viteCustomConfig){const devOptions=mergeOptions(options,customOptions);logOptions(devOptions);customDefines=customDefines||{};viteCustomConfig=viteCustomConfig||{};// 构建 define 常量(复用 webpack 的 buildDefines 逻辑)
8
- const defines={...buildDefines(),...devOptions.defines,__DEV__:JSON.stringify(devOptions["dev"]===undefined?true:devOptions["dev"]),...customDefines};// 解析 tsconfig/jsconfig 路径别名
9
- const tsConfigPath=path.resolve(devOptions.tsConfig||"tsconfig.json");const jsConfigPath=path.resolve(devOptions.jsConfig||"jsconfig.json");let aliasConfig={};// 尝试从 tsconfig/jsconfig 中读取 paths 配置
10
- let configFilePath=null;if(fs.existsSync(tsConfigPath)){configFilePath=tsConfigPath}else if(fs.existsSync(jsConfigPath)){configFilePath=jsConfigPath}if(configFilePath){try{var _config$compilerOptio;const JSON5=require("json5");const configContent=fs.readFileSync(configFilePath,"utf-8");const config=JSON5.parse(configContent);const paths=((_config$compilerOptio=config.compilerOptions)===null||_config$compilerOptio===void 0?void 0:_config$compilerOptio.paths)||{};for(const[alias,targets]of Object.entries(paths)){var _targets$;// 将 tsconfig paths 格式 "foo/*" -> ["src/foo/*"] 转换为 vite alias
11
- const cleanAlias=alias.replace("/*","");const cleanTarget=((_targets$=targets[0])===null||_targets$===void 0?void 0:_targets$.replace("/*",""))||"";if(cleanAlias&&cleanTarget){aliasConfig[cleanAlias]=path.resolve(cleanTarget)}}}catch(err){console.log("\x1B[33m\u26A0\uFE0F \u8BFB\u53D6\u8DEF\u5F84\u914D\u7F6E\u5931\u8D25: "+err.message+"\x1B[0m")}}// 默认别名
12
- aliasConfig={assets:path.resolve("assets"),"@":path.resolve("src"),...aliasConfig};// JSON5 插件
13
- function json5Plugin(){return{name:"vite-plugin-json5",transform(code,id){if(id.endsWith(".json5")){const JSON5=require("json5");const raw=fs.readFileSync(id,"utf-8");const parsed=JSON5.parse(raw);return{code:`export default ${JSON.stringify(parsed)};`,map:null}}}}}// 资源内联插件(模拟 webpack 的 asset/inline 行为)
14
- function assetInlinePlugin(){return{name:"vite-plugin-asset-inline",enforce:"pre",transform(code,id){// .atlas 文件 -> base64 data URL
15
- if(id.endsWith(".atlas")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/atlas;base64,${base64}";`,map:null}}// .fnt 文件 -> base64 data URL
16
- if(id.endsWith(".fnt")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/plain;base64,${base64}";`,map:null}}// .xml 文件 -> 原始文本
17
- if(id.endsWith(".xml")){const raw=fs.readFileSync(id,"utf-8");return{code:`export default ${JSON.stringify(raw)};`,map:null}}}}}/** @type {import('vite').UserConfig} */const viteConfig={root:".",resolve:{alias:aliasConfig},plugins:[json5Plugin(),assetInlinePlugin()],define:defines,server:{port:devOptions["port"]||3000,open:devOptions["open"]||false},build:{outDir:devOptions["outDir"]||"dist",sourcemap:true}};// 合并自定义配置
18
- if(viteCustomConfig){// 简单合并:自定义配置中的字段会覆盖默认配置
19
- const{plugins:customPlugins,define:customDefine,resolve:customResolve,server:customServer,build:customBuild,...rest}=viteCustomConfig;if(customPlugins){viteConfig.plugins=[...viteConfig.plugins,...customPlugins]}if(customDefine){viteConfig.define={...viteConfig.define,...customDefine}}if(customResolve){viteConfig.resolve={...viteConfig.resolve,...customResolve,alias:{...viteConfig.resolve.alias,...(customResolve.alias||{})}}}if(customServer){viteConfig.server={...viteConfig.server,...customServer}}if(customBuild){viteConfig.build={...viteConfig.build,...customBuild}}Object.assign(viteConfig,rest)}return viteConfig}/**
20
- * 启动 Vite 开发服务器
16
+ */
17
+ function makeViteDevConfig(customOptions, customDefines, viteCustomConfig) {
18
+ const devOptions = mergeOptions(options, customOptions);
19
+ logOptions(devOptions);
20
+ customDefines = customDefines || {};
21
+ viteCustomConfig = viteCustomConfig || {};
22
+
23
+ // 构建 define 常量(复用 webpack buildDefines 逻辑)
24
+ const defines = {
25
+ ...buildDefines(),
26
+ ...devOptions.defines,
27
+ __DEV__: JSON.stringify(devOptions['dev'] === undefined ? true : devOptions['dev']),
28
+ ...customDefines,
29
+ };
30
+
31
+ // 解析 tsconfig/jsconfig 路径别名
32
+ const tsConfigPath = path.resolve(devOptions.tsConfig || 'tsconfig.json');
33
+ const jsConfigPath = path.resolve(devOptions.jsConfig || 'jsconfig.json');
34
+ let aliasConfig = {};
35
+
36
+ // 尝试从 tsconfig/jsconfig 中读取 paths 配置
37
+ let configFilePath = null;
38
+ if (fs.existsSync(tsConfigPath)) {
39
+ configFilePath = tsConfigPath;
40
+ } else if (fs.existsSync(jsConfigPath)) {
41
+ configFilePath = jsConfigPath;
42
+ }
43
+
44
+ if (configFilePath) {
45
+ try {
46
+ const JSON5 = require('json5');
47
+ const configContent = fs.readFileSync(configFilePath, 'utf-8');
48
+ const config = JSON5.parse(configContent);
49
+ const paths = config.compilerOptions?.paths || {};
50
+ for (const [alias, targets] of Object.entries(paths)) {
51
+ // 将 tsconfig paths 格式 "foo/*" -> ["src/foo/*"] 转换为 vite alias
52
+ const cleanAlias = alias.replace('/*', '');
53
+ const cleanTarget = targets[0]?.replace('/*', '') || '';
54
+ if (cleanAlias && cleanTarget) {
55
+ aliasConfig[cleanAlias] = path.resolve(cleanTarget);
56
+ }
57
+ }
58
+ } catch (err) {
59
+ console.log('\x1b[33m⚠️ 读取路径配置失败: ' + err.message + '\x1b[0m');
60
+ }
61
+ }
62
+
63
+ // 默认别名
64
+ aliasConfig = {
65
+ assets: path.resolve('assets'),
66
+ '@': path.resolve('src'),
67
+ ...aliasConfig,
68
+ };
69
+
70
+ // JSON5 插件
71
+ function json5Plugin() {
72
+ return {
73
+ name: 'vite-plugin-json5',
74
+ transform(code, id) {
75
+ if (id.endsWith('.json5')) {
76
+ const JSON5 = require('json5');
77
+ const raw = fs.readFileSync(id, 'utf-8');
78
+ const parsed = JSON5.parse(raw);
79
+ return {
80
+ code: `export default ${JSON.stringify(parsed)};`,
81
+ map: null,
82
+ };
83
+ }
84
+ },
85
+ };
86
+ }
87
+
88
+ // 资源内联插件(模拟 webpack 的 asset/inline 行为)
89
+ function assetInlinePlugin() {
90
+ return {
91
+ name: 'vite-plugin-asset-inline',
92
+ enforce: 'pre',
93
+ transform(code, id) {
94
+ // .atlas 文件 -> base64 data URL
95
+ if (id.endsWith('.atlas')) {
96
+ const content = fs.readFileSync(id);
97
+ const base64 = content.toString('base64');
98
+ return {
99
+ code: `export default "data:text/atlas;base64,${base64}";`,
100
+ map: null,
101
+ };
102
+ }
103
+ // .fnt 文件 -> base64 data URL
104
+ if (id.endsWith('.fnt')) {
105
+ const content = fs.readFileSync(id);
106
+ const base64 = content.toString('base64');
107
+ return {
108
+ code: `export default "data:text/plain;base64,${base64}";`,
109
+ map: null,
110
+ };
111
+ }
112
+ // .xml 文件 -> 原始文本
113
+ if (id.endsWith('.xml')) {
114
+ const raw = fs.readFileSync(id, 'utf-8');
115
+ return {
116
+ code: `export default ${JSON.stringify(raw)};`,
117
+ map: null,
118
+ };
119
+ }
120
+ },
121
+ };
122
+ }
123
+
124
+ // 字体注入插件(dev 模式:读取字体文件 base64 内联到 index.html)
125
+ function fontFamilyPlugin() {
126
+ const { getOrGenerateFontCSS } = require('./utils/fontResolver');
127
+ let _themeName = null;
128
+ let _themeConfig = null;
129
+
130
+ // 尝试检测当前主题名(与 build.js 中 detectCurrentTheme 逻辑一致)
131
+ function detectTheme() {
132
+ const root = process.cwd();
133
+ const buildsConfigPath = path.join(root, 'builds.config.js');
134
+ let config = { sourceDir: 'src/theme', entryFile: 'src/theme/index.ts' };
135
+ if (fs.existsSync(buildsConfigPath)) {
136
+ try {
137
+ delete require.cache[require.resolve(buildsConfigPath)];
138
+ const buildsConfig = require(buildsConfigPath);
139
+ if (buildsConfig.themes) {
140
+ config = { ...config, ...buildsConfig.themes };
141
+ }
142
+ } catch (_) { /* ignore */ }
143
+ }
144
+ let name = null;
145
+ const entryPath = path.join(root, config.entryFile);
146
+ if (fs.existsSync(entryPath)) {
147
+ const content = fs.readFileSync(entryPath, 'utf8');
148
+ const match = content.match(/from\s+['"]\.\/([^'"]+)['"]/);
149
+ if (match) name = match[1];
150
+ }
151
+ return { themeName: name, themeConfig: config };
152
+ }
153
+
154
+ return {
155
+ name: 'vite-plugin-font-family',
156
+ apply: 'serve',
157
+ transformIndexHtml: {
158
+ order: 'pre',
159
+ handler() {
160
+ if (_themeName === null) {
161
+ const detected = detectTheme();
162
+ _themeName = detected.themeName;
163
+ _themeConfig = detected.themeConfig;
164
+ }
165
+ if (!_themeName) return '';
166
+ const css = getOrGenerateFontCSS({
167
+ projectRoot: process.cwd(),
168
+ themeName: _themeName,
169
+ themeConfig: _themeConfig,
170
+ });
171
+ return css ? [{ tag: 'style', attrs: { 'data-playable-fonts': '' }, children: css.replace(/<\/?style[^>]*>/g, ''), injectTo: 'head' }] : [];
172
+ },
173
+ },
174
+ };
175
+ }
176
+
177
+ /** @type {import('vite').UserConfig} */
178
+ const viteConfig = {
179
+ root: '.',
180
+ resolve: {
181
+ alias: aliasConfig,
182
+ },
183
+ plugins: [json5Plugin(), assetInlinePlugin(), fontFamilyPlugin()],
184
+ define: defines,
185
+ server: {
186
+ port: devOptions['port'] || 3000,
187
+ open: devOptions['open'] || false,
188
+ },
189
+ build: {
190
+ outDir: devOptions['outDir'] || 'dist',
191
+ sourcemap: true,
192
+ },
193
+ };
194
+
195
+ // 合并自定义配置
196
+ if (viteCustomConfig) {
197
+ // 简单合并:自定义配置中的字段会覆盖默认配置
198
+ const { plugins: customPlugins, define: customDefine, resolve: customResolve, server: customServer, build: customBuild, ...rest } = viteCustomConfig;
199
+
200
+ if (customPlugins) {
201
+ viteConfig.plugins = [...viteConfig.plugins, ...customPlugins];
202
+ }
203
+ if (customDefine) {
204
+ viteConfig.define = { ...viteConfig.define, ...customDefine };
205
+ }
206
+ if (customResolve) {
207
+ viteConfig.resolve = {
208
+ ...viteConfig.resolve,
209
+ ...customResolve,
210
+ alias: { ...viteConfig.resolve.alias, ...(customResolve.alias || {}) },
211
+ };
212
+ }
213
+ if (customServer) {
214
+ viteConfig.server = { ...viteConfig.server, ...customServer };
215
+ }
216
+ if (customBuild) {
217
+ viteConfig.build = { ...viteConfig.build, ...customBuild };
218
+ }
219
+ Object.assign(viteConfig, rest);
220
+ }
221
+
222
+ return viteConfig;
223
+ }
224
+
225
+ /**
226
+ * 深度合并两个 Vite 配置对象。override 覆盖 base。
227
+ * 数组会拼接(plugins),其余字段浅覆盖。
228
+ */
229
+ function deepMergeViteConfig(base, override) {
230
+ if (!override || typeof override !== 'object') return base;
231
+ const result = { ...base };
232
+ for (const key of Object.keys(override)) {
233
+ const bv = base[key];
234
+ const ov = override[key];
235
+ if (key === 'plugins' && Array.isArray(bv) && Array.isArray(ov)) {
236
+ result[key] = [...bv, ...ov];
237
+ } else if (
238
+ ov !== null && typeof ov === 'object' && !Array.isArray(ov) &&
239
+ bv !== null && typeof bv === 'object' && !Array.isArray(bv)
240
+ ) {
241
+ result[key] = { ...bv, ...ov };
242
+ } else {
243
+ result[key] = ov;
244
+ }
245
+ }
246
+ return result;
247
+ }
248
+
249
+ /**
250
+ * 尝试加载项目根目录的 vite.config.ts。
251
+ * 使用 Vite 内置的 loadConfigFromFile API。
252
+ * 注意:vite 是项目的 devDependency,不是 scripts 的依赖,
253
+ * 因此需要通过 createRequire 从项目根目录解析 require 路径。
254
+ */
255
+ async function loadProjectViteConfig(root) {
256
+ const candidates = ['vite.config.ts', 'vite.config.js', 'vite.config.mjs'];
257
+ let configPath = null;
258
+ for (const c of candidates) {
259
+ const p = path.join(root, c);
260
+ if (fs.existsSync(p)) { configPath = p; break; }
261
+ }
262
+ if (!configPath) return null;
263
+
264
+ // 从项目根目录创建 require,解决 vite 模块解析路径问题
265
+ const { createRequire } = require('module');
266
+ let projRequire;
267
+ try {
268
+ projRequire = createRequire(path.join(root, 'package.json'));
269
+ } catch (_) {
270
+ projRequire = createRequire(path.join(root, 'noop.js'));
271
+ }
272
+
273
+ // 通过项目的 require 路径加载 vite
274
+ let loadConfigFromFile;
275
+ try {
276
+ loadConfigFromFile = projRequire('vite').loadConfigFromFile;
277
+ } catch (_) {
278
+ try {
279
+ loadConfigFromFile = projRequire('vite/dist/node/config').loadConfigFromFile;
280
+ } catch (_2) {
281
+ console.log('\x1b[33m⚠️ [vite] Cannot load project config (loadConfigFromFile unavailable), using scripts default\x1b[0m');
282
+ return null;
283
+ }
284
+ }
285
+
286
+ if (!loadConfigFromFile) return null;
287
+
288
+ try {
289
+ const result = await loadConfigFromFile(
290
+ { command: 'serve', mode: process.env.NODE_ENV || 'development' },
291
+ configPath,
292
+ root
293
+ );
294
+ if (result && result.config) {
295
+ console.log(`[vite] Loaded project config: ${path.relative(root, configPath)}`);
296
+ return result.config;
297
+ }
298
+ return null;
299
+ } catch (err) {
300
+ console.log(`\x1b[33m⚠️ [vite] Failed to load ${path.relative(root, configPath)}: ${err.message}, using scripts default\x1b[0m`);
301
+ return null;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * 启动 Vite 开发服务器。
307
+ *
308
+ * 配置优先级(从低到高):
309
+ * 1. makeViteDevConfig 构建的默认配置(fontFamilyPlugin、json5Plugin 等)
310
+ * 2. 项目根目录的 vite.config.ts(如果存在,覆盖/扩充默认配置)
311
+ * 3. 传入的 viteConfig / customOptions / customDefines / viteCustomConfig
312
+ *
21
313
  * @param {import('vite').UserConfig} [viteConfig] - Vite 配置,不传则自动创建
22
314
  * @param {Partial<import('./index').CLIOptions>} [customOptions] - 自定义选项
23
315
  * @param {Record<string, any>} [customDefines] - 额外的 define 常量
24
316
  * @param {import('vite').UserConfig} [viteCustomConfig] - 自定义 Vite 配置
25
- */async function runViteDev(viteConfig,customOptions,customDefines,viteCustomConfig){if(!viteConfig){viteConfig=makeViteDevConfig(customOptions,customDefines,viteCustomConfig)}const{createServer}=require("vite");const server=await createServer(viteConfig);await server.listen();server.printUrls()}exports.makeViteDevConfig=makeViteDevConfig;exports.runViteDev=runViteDev;
317
+ */
318
+ async function runViteDev(viteConfig, customOptions, customDefines, viteCustomConfig) {
319
+ if (!viteConfig) {
320
+ viteConfig = makeViteDevConfig(customOptions, customDefines, viteCustomConfig);
321
+ }
322
+
323
+ // 加载项目根目录的 vite 配置文件并合并(项目配置覆盖 scripts 默认)
324
+ const projectRoot = process.cwd();
325
+ const projectConfig = await loadProjectViteConfig(projectRoot);
326
+ if (projectConfig) {
327
+ viteConfig = deepMergeViteConfig(viteConfig, projectConfig);
328
+ }
329
+
330
+ // vite 是项目的 devDependency,从项目目录解析
331
+ const { createRequire } = require('module');
332
+ const projRequire = createRequire(path.join(projectRoot, 'package.json'));
333
+ const { createServer } = projRequire('vite');
334
+ // configFile: false 防止 Vite 再次加载项目的 vite.config.ts(已经手动合并了)
335
+ const server = await createServer({ ...viteConfig, configFile: false });
336
+ await server.listen();
337
+ server.printUrls();
338
+ }
339
+
340
+ exports.makeViteDevConfig = makeViteDevConfig;
341
+ exports.runViteDev = runViteDev;