@playcraft/devkit 1.0.31-beta.11 → 1.0.31-beta.12
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.
- package/cli/bin/playable-scripts.js +6 -53
- package/cli/commands/build.js +32 -241
- package/cli/commands/builds.js +2 -13
- package/cli/commands/dev.js +1 -22
- package/cli/commands/pack.js +40 -320
- package/cli/commands/vite-dev.js +1 -7
- package/core/artifacts/emit-fonts.test.js +1 -0
- package/core/artifacts/emit.js +4 -98
- package/core/artifacts/fflate.js +1 -31
- package/core/artifacts/zip.js +1 -39
- package/core/batch-build.js +147 -1261
- package/core/constants.js +2 -20
- package/core/cos-uploader.js +23 -289
- package/core/engines/cocos/clean-cache.js +5 -179
- package/core/engines/cocos/clean-cache.test.js +1 -75
- package/core/engines/cocos/cli-build.js +2 -116
- package/core/engines/cocos/cli.js +11 -38
- package/core/engines/cocos/constants.js +1 -21
- package/core/engines/cocos/creator-cli.js +1 -108
- package/core/engines/cocos/dev-server.js +4 -412
- package/core/engines/cocos/fs.js +1 -105
- package/core/engines/cocos/injects/3x/init.js +19 -438
- package/core/engines/cocos/injects/3x/main.js +2 -52
- package/core/engines/cocos/injects/libs/pako.js +1 -2
- package/core/engines/cocos/mcp-refresh.js +2 -76
- package/core/engines/cocos/merge.js +3 -148
- package/core/engines/cocos/pack.js +1 -187
- package/core/engines/cocos/preview-cache.js +2 -48
- package/core/engines/cocos/preview-cache.test.js +1 -32
- package/core/engines/cocos/preview-concat.js +5 -189
- package/core/engines/cocos/preview-concat.test.js +1 -73
- package/core/engines/cocos/preview-json-hook.js +1 -200
- package/core/engines/cocos/preview-json-pack.js +3 -376
- package/core/engines/cocos/preview-json-pack.test.js +2 -226
- package/core/engines/cocos/preview-proxy.js +1 -132
- package/core/engines/cocos/rebundle-preview.js +2 -54
- package/core/engines/cocos/strip-social-share.js +3 -115
- package/core/engines/cocos/theme-assets.js +2 -161
- package/core/engines/cocos/writable-cli.js +2 -248
- package/core/engines/cocos/writable-cli.test.js +1 -112
- package/core/engines/web/index.js +1 -20
- package/core/engines/web/vite.dev.js +17 -322
- package/core/engines/web/webpack.build.js +37 -522
- package/core/engines/web/webpack.common.js +11 -177
- package/core/engines/web/webpack.dev.js +6 -88
- package/core/index.js +7 -101
- package/core/injectors/apply.js +1 -94
- package/core/injectors/check-channel-matrix.js +2 -130
- package/core/injectors/spec.js +9 -217
- package/core/loaders/gltf-loader.js +28 -74
- package/core/options.js +16 -353
- package/core/plugins/AdikteevInjectorPlugin.js +4 -26
- package/core/plugins/BigoAdsInjectorPlugin.js +4 -21
- package/core/plugins/DAPIInjectorPlugin.js +2 -25
- package/core/plugins/DebuggerInjectionPlugin.js +3 -31
- package/core/plugins/ExitAPIInjectorPlugin.js +3 -56
- package/core/plugins/FflateCompressionPlugin.js +40 -231
- package/core/plugins/FontInjectionWebpackPlugin.js +4 -46
- package/core/plugins/LiftoffInjectorPlugin.js +4 -26
- package/core/plugins/MRAIDInjectorPlugin.js +2 -24
- package/core/plugins/MintegralInjectorPlugin.js +2 -25
- package/core/plugins/PangleInjectorPlugin.js +2 -24
- package/core/plugins/PlaycraftOrientationInjectorPlugin.js +3 -29
- package/core/plugins/SnapchatInjectorPlugin.js +4 -26
- package/core/plugins/SocialMediaInjectorPlugin.js +7 -119
- package/core/plugins/TikTokInjectorPlugin.js +2 -24
- package/core/plugins/UnityInjectorPlugin.js +8 -37
- package/core/plugins/ZipPlugin.js +20 -154
- package/core/plugins/socialMediaVitePlugin.js +2 -108
- package/core/utils/buildDefines.js +2 -30
- package/core/utils/buildTemplateString.js +5 -39
- package/core/utils/date.js +1 -16
- package/core/utils/fontRegistry.js +13 -81
- package/core/utils/fontResolver.js +37 -335
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +7 -42
- package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +3 -30
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +7 -43
- package/core/utils/getPlaycraftOrientationSuffix.js +2 -9
- package/core/utils/injectSDKPlugins.js +13 -68
- package/core/utils/logOptions.js +5 -46
- package/core/utils/mergeOptions.js +2 -21
- package/core/utils/obfuscateLevels.js +10 -0
- package/core/utils/obfuscateLevels.test.js +10 -0
- package/core/utils/parseArgvOptions.js +5 -125
- package/core/utils/resolveChannelFold2Zip.js +3 -15
- package/core/utils/validateThemeData.js +26 -227
- package/core/validators/pre-build-checker.js +25 -287
- package/core/validators/tracking-validator.js +40 -441
- package/core/validators/tracking-validator.test.js +2 -84
- package/core/vite.dev.js +1 -1
- package/core/webpack.build.js +1 -1
- package/core/webpack.common.js +1 -1
- package/core/webpack.dev.js +1 -1
- package/package.json +2 -3
- package/README.md +0 -262
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";/**
|
|
4
2
|
* Webpack 插件:social_media 渠道专用 — 在构建产物 HTML 的 </head> 之前
|
|
5
3
|
* 注入 OG / Twitter / Facebook 分享卡片 meta 标签。
|
|
6
4
|
*
|
|
@@ -16,124 +14,14 @@
|
|
|
16
14
|
* 3. 都读不到 → 跳过注入(不报错,让用户后续补)
|
|
17
15
|
*
|
|
18
16
|
* 配合 injectSDKPlugins.js 的 social_media 分支在 build 时挂载。
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
22
|
-
const fs = require('fs');
|
|
23
|
-
const path = require('path');
|
|
24
|
-
const JSON5 = require('json5');
|
|
25
|
-
|
|
26
|
-
class SocialMediaInjectorPlugin {
|
|
27
|
-
/**
|
|
17
|
+
*/const HtmlWebpackPlugin=require("html-webpack-plugin");const fs=require("fs");const path=require("path");const JSON5=require("json5");class SocialMediaInjectorPlugin{/**
|
|
28
18
|
* @param {object} [opts]
|
|
29
19
|
* @param {string} [opts.projectRoot] - 项目根目录
|
|
30
20
|
* @param {string} [opts.themeName] - 当前主题目录名(不含前缀斜线)
|
|
31
21
|
* @param {object} [opts.themeConfig] - builds.config.js 中的 themes 配置
|
|
32
22
|
* @param {object} [opts.mergedData] - 预合并数据(可选)
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
/** 从 mergedData / theme.data.json5 读出 shareMeta 块 */
|
|
39
|
-
_loadShareMeta() {
|
|
40
|
-
// 1) 优先用已传入的 mergedData
|
|
41
|
-
let merged = this.opts.mergedData;
|
|
42
|
-
if (merged && merged.gamePlay && merged.gamePlay.shareMeta) {
|
|
43
|
-
return merged.gamePlay.shareMeta;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 2) 退化为读 theme.data.json5
|
|
47
|
-
const root = this.opts.projectRoot || process.cwd();
|
|
48
|
-
const name = this.opts.themeName;
|
|
49
|
-
if (!name) return null;
|
|
50
|
-
const dataPath = path.join(
|
|
51
|
-
root,
|
|
52
|
-
(this.opts.themeConfig && this.opts.themeConfig.sourceDir) || 'src/theme',
|
|
53
|
-
name,
|
|
54
|
-
'theme.data.json5'
|
|
55
|
-
);
|
|
56
|
-
if (!fs.existsSync(dataPath)) return null;
|
|
57
|
-
try {
|
|
58
|
-
const raw = fs.readFileSync(dataPath, 'utf8');
|
|
59
|
-
const parsed = JSON5.parse(raw);
|
|
60
|
-
return parsed && parsed.gamePlay && parsed.gamePlay.shareMeta
|
|
61
|
-
? parsed.gamePlay.shareMeta
|
|
62
|
-
: null;
|
|
63
|
-
} catch (e) {
|
|
64
|
-
console.warn('[SocialMediaInjectorPlugin] Failed to read theme.data.json5:', e.message);
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** 把 shareMeta 渲染成 <meta> 标签字符串 */
|
|
70
|
-
_buildMetaTags(meta) {
|
|
71
|
-
if (!meta) return '';
|
|
72
|
-
const esc = (s) => String(s)
|
|
73
|
-
.replace(/&/g, '&')
|
|
74
|
-
.replace(/"/g, '"')
|
|
75
|
-
.replace(/</g, '<')
|
|
76
|
-
.replace(/>/g, '>');
|
|
77
|
-
|
|
78
|
-
const lines = [];
|
|
79
|
-
const title = (meta.title || '').trim();
|
|
80
|
-
const description = (meta.description || '').trim();
|
|
81
|
-
const image = (meta.imageUrl || '').trim();
|
|
82
|
-
const url = (meta.pageUrl || '').trim();
|
|
83
|
-
const siteName = (meta.siteName || '').trim();
|
|
84
|
-
|
|
85
|
-
// Open Graph
|
|
86
|
-
if (title) {
|
|
87
|
-
lines.push(`<meta property="og:title" content="${esc(title)}" />`);
|
|
88
|
-
}
|
|
89
|
-
if (description) {
|
|
90
|
-
lines.push(`<meta property="og:description" content="${esc(description)}" />`);
|
|
91
|
-
}
|
|
92
|
-
if (image) {
|
|
93
|
-
lines.push(`<meta property="og:image" content="${esc(image)}" />`);
|
|
94
|
-
}
|
|
95
|
-
if (url) {
|
|
96
|
-
lines.push(`<meta property="og:url" content="${esc(url)}" />`);
|
|
97
|
-
}
|
|
98
|
-
if (siteName) {
|
|
99
|
-
lines.push(`<meta property="og:site_name" content="${esc(siteName)}" />`);
|
|
100
|
-
}
|
|
101
|
-
lines.push(`<meta property="og:type" content="website" />`);
|
|
102
|
-
|
|
103
|
-
// Twitter Card
|
|
104
|
-
lines.push(`<meta name="twitter:card" content="summary_large_image" />`);
|
|
105
|
-
if (title) {
|
|
106
|
-
lines.push(`<meta name="twitter:title" content="${esc(title)}" />`);
|
|
107
|
-
}
|
|
108
|
-
if (description) {
|
|
109
|
-
lines.push(`<meta name="twitter:description" content="${esc(description)}" />`);
|
|
110
|
-
}
|
|
111
|
-
if (image) {
|
|
112
|
-
lines.push(`<meta name="twitter:image" content="${esc(image)}" />`);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return lines.join('\n ');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
apply(compiler) {
|
|
119
|
-
const pluginName = 'SocialMediaInjectorPlugin';
|
|
120
|
-
compiler.hooks.compilation.tap(pluginName, (compilation) => {
|
|
121
|
-
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
|
|
122
|
-
pluginName,
|
|
123
|
-
(data, cb) => {
|
|
124
|
-
const shareMeta = this._loadShareMeta();
|
|
125
|
-
if (!shareMeta) {
|
|
126
|
-
return cb(null, data);
|
|
127
|
-
}
|
|
128
|
-
const tags = this._buildMetaTags(shareMeta);
|
|
129
|
-
if (tags) {
|
|
130
|
-
data.html = data.html.replace('</head>', ` ${tags}\n </head>`);
|
|
131
|
-
}
|
|
132
|
-
cb(null, data);
|
|
133
|
-
}
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
module.exports = { SocialMediaInjectorPlugin };
|
|
23
|
+
*/constructor(opts={}){this.opts=opts}/** 从 mergedData / theme.data.json5 读出 shareMeta 块 */_loadShareMeta(){// 1) 优先用已传入的 mergedData
|
|
24
|
+
let merged=this.opts.mergedData;if(merged&&merged.gamePlay&&merged.gamePlay.shareMeta){return merged.gamePlay.shareMeta}// 2) 退化为读 theme.data.json5
|
|
25
|
+
const root=this.opts.projectRoot||process.cwd();const name=this.opts.themeName;if(!name)return null;const dataPath=path.join(root,this.opts.themeConfig&&this.opts.themeConfig.sourceDir||"src/theme",name,"theme.data.json5");if(!fs.existsSync(dataPath))return null;try{const raw=fs.readFileSync(dataPath,"utf8");const parsed=JSON5.parse(raw);return parsed&&parsed.gamePlay&&parsed.gamePlay.shareMeta?parsed.gamePlay.shareMeta:null}catch(e){console.warn("[SocialMediaInjectorPlugin] Failed to read theme.data.json5:",e.message);return null}}/** 把 shareMeta 渲染成 <meta> 标签字符串 */_buildMetaTags(meta){if(!meta)return"";const esc=s=>String(s).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");const lines=[];const title=(meta.title||"").trim();const description=(meta.description||"").trim();const image=(meta.imageUrl||"").trim();const url=(meta.pageUrl||"").trim();const siteName=(meta.siteName||"").trim();// Open Graph
|
|
26
|
+
if(title){lines.push(`<meta property="og:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta property="og:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta property="og:image" content="${esc(image)}" />`)}if(url){lines.push(`<meta property="og:url" content="${esc(url)}" />`)}if(siteName){lines.push(`<meta property="og:site_name" content="${esc(siteName)}" />`)}lines.push(`<meta property="og:type" content="website" />`);// Twitter Card
|
|
27
|
+
lines.push(`<meta name="twitter:card" content="summary_large_image" />`);if(title){lines.push(`<meta name="twitter:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta name="twitter:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta name="twitter:image" content="${esc(image)}" />`)}return lines.join("\n ")}apply(compiler){const pluginName="SocialMediaInjectorPlugin";compiler.hooks.compilation.tap(pluginName,compilation=>{HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(pluginName,(data,cb)=>{const shareMeta=this._loadShareMeta();if(!shareMeta){return cb(null,data)}const tags=this._buildMetaTags(shareMeta);if(tags){data.html=data.html.replace("</head>",` ${tags}\n </head>`)}cb(null,data)})})}}module.exports={SocialMediaInjectorPlugin};
|
|
@@ -1,26 +1,4 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Webpack plugin that injects TikTok Playable SDK script into the HTML head.
|
|
5
3
|
* @implements {import('webpack').WebpackPluginInstance}
|
|
6
|
-
*/
|
|
7
|
-
class TikTokInjectorPlugin {
|
|
8
|
-
apply(compiler) {
|
|
9
|
-
compiler.hooks.compilation.tap('TikTokInjectorPlugin', (compilation) => {
|
|
10
|
-
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('TikTokInjectorPlugin', (data, callback) => {
|
|
11
|
-
data.headTags.splice(0, 0, {
|
|
12
|
-
tagName: 'script',
|
|
13
|
-
voidTag: false,
|
|
14
|
-
meta: { plugin: 'html-inline-script-webpack-plugin' },
|
|
15
|
-
attributes: {
|
|
16
|
-
src: 'https://sf16-muse-va.ibytedtos.com/obj/union-fe-nc-i18n/playable/sdk/playable-sdk.js',
|
|
17
|
-
type: 'text/javascript'
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
callback(null, data);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exports.TikTokInjectorPlugin = TikTokInjectorPlugin;
|
|
4
|
+
*/class TikTokInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("TikTokInjectorPlugin",compilation=>{HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("TikTokInjectorPlugin",(data,callback)=>{data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"html-inline-script-webpack-plugin"},attributes:{src:"https://sf16-muse-va.ibytedtos.com/obj/union-fe-nc-i18n/playable/sdk/playable-sdk.js",type:"text/javascript"}});callback(null,data)})})}}exports.TikTokInjectorPlugin=TikTokInjectorPlugin;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const HtmlWebpackPlugin
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const HtmlWebpackPlugin=require("html-webpack-plugin");/**
|
|
4
2
|
* Unity Ads SDK 注入插件
|
|
5
3
|
* 根据 Unity Ads Playable 标准,注入 MRAID SDK 引用
|
|
6
4
|
*
|
|
@@ -13,37 +11,10 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
|
13
11
|
* 4. 必须调用 mraid.close() 关闭广告
|
|
14
12
|
* 5. 使用 mraid.fireTrackingEvent() 上报游戏事件
|
|
15
13
|
* 6. 不允许使用 window.top(Unity 平台限制)
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// 在生产环境中,mraid.js 由 Unity Ads 平台提供
|
|
24
|
-
data.headTags.splice(0, 0, {
|
|
25
|
-
tagName: 'script',
|
|
26
|
-
voidTag: false,
|
|
27
|
-
meta: { plugin: 'unity-injector-plugin' },
|
|
28
|
-
attributes: {
|
|
29
|
-
src: 'mraid.js',
|
|
30
|
-
type: 'text/javascript'
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
callback(null, data);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// 2. 替换 window.top 为 window(Unity 不允许使用 window.top)
|
|
38
|
-
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync('UnityInjectorPlugin', (data, callback) => {
|
|
39
|
-
// 替换所有 window.top 为 window
|
|
40
|
-
// 这是 Phaser 框架的代码,Unity 平台不允许访问 window.top
|
|
41
|
-
data.html = data.html.replace(/window\.top\b/g, 'window');
|
|
42
|
-
|
|
43
|
-
callback(null, data);
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
exports.UnityInjectorPlugin = UnityInjectorPlugin;
|
|
14
|
+
*/class UnityInjectorPlugin{apply(compiler){compiler.hooks.compilation.tap("UnityInjectorPlugin",compilation=>{// 1. 注入 mraid.js 标签
|
|
15
|
+
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync("UnityInjectorPlugin",(data,callback)=>{// Unity Ads 使用标准 MRAID 协议
|
|
16
|
+
// 在生产环境中,mraid.js 由 Unity Ads 平台提供
|
|
17
|
+
data.headTags.splice(0,0,{tagName:"script",voidTag:false,meta:{plugin:"unity-injector-plugin"},attributes:{src:"mraid.js",type:"text/javascript"}});callback(null,data)});// 2. 替换 window.top 为 window(Unity 不允许使用 window.top)
|
|
18
|
+
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync("UnityInjectorPlugin",(data,callback)=>{// 替换所有 window.top 为 window
|
|
19
|
+
// 这是 Phaser 框架的代码,Unity 平台不允许访问 window.top
|
|
20
|
+
data.html=data.html.replace(/window\.top\b/g,"window");callback(null,data)})})}}exports.UnityInjectorPlugin=UnityInjectorPlugin;
|
|
@@ -1,154 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (options.pathPrefix && path.isAbsolute(options.pathPrefix)) {
|
|
22
|
-
throw new Error('"pathPrefix" must be a relative path');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 保存临时文件夹路径,用于后续删除
|
|
26
|
-
let tempFolderToClean = null;
|
|
27
|
-
|
|
28
|
-
const process = function (compilation, callback) {
|
|
29
|
-
// assets from child compilers will be included in the parent
|
|
30
|
-
// so we should not run in child compilers
|
|
31
|
-
if (compilation.compiler.isChild()) {
|
|
32
|
-
callback();
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const zipFile = new yazl.ZipFile();
|
|
37
|
-
|
|
38
|
-
const pathPrefix = options.pathPrefix || '';
|
|
39
|
-
const pathMapper =
|
|
40
|
-
options.pathMapper ||
|
|
41
|
-
function (x) {
|
|
42
|
-
return x;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// populate the zip file with each asset
|
|
46
|
-
for (const nameAndPath of Object.keys(compilation.assets)) {
|
|
47
|
-
// match against include and exclude, which may be strings, regexes, arrays of the previous or omitted
|
|
48
|
-
if (!ModuleFilenameHelpers.matchObject({ include: options.include, exclude: options.exclude }, nameAndPath)) continue;
|
|
49
|
-
|
|
50
|
-
const source = compilation.assets[nameAndPath].source();
|
|
51
|
-
|
|
52
|
-
// 使用 posix 路径,保证 zip 内是「文件在根目录」而不是再套一层文件夹
|
|
53
|
-
const entryName = path.posix.join(pathPrefix.replace(/\\/g, '/'), pathMapper(nameAndPath).replace(/\\/g, '/'));
|
|
54
|
-
zipFile.addBuffer(
|
|
55
|
-
Buffer.isBuffer(source) ? source : Buffer.from ? Buffer.from(source) : new Buffer(source),
|
|
56
|
-
entryName,
|
|
57
|
-
options.fileOptions
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
zipFile.end(options.zipOptions);
|
|
62
|
-
|
|
63
|
-
// accumulate each buffer containing a part of the zip file
|
|
64
|
-
const bufs = [];
|
|
65
|
-
|
|
66
|
-
zipFile.outputStream.on('data', function (buf) {
|
|
67
|
-
bufs.push(buf);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
zipFile.outputStream.on('end', function () {
|
|
71
|
-
// default to webpack's root output path if no path provided
|
|
72
|
-
const outputPath = options.path || compilation.options.output.path;
|
|
73
|
-
// default to webpack root filename if no filename provided, else the basename of the output path
|
|
74
|
-
let outputFilename = options.filename || compilation.options.output.filename || path.basename(outputPath);
|
|
75
|
-
|
|
76
|
-
const fullHash = compilation.fullHash || compilation.hash;
|
|
77
|
-
outputFilename = outputFilename.replace('[fullhash:6]', fullHash.slice(0, 6));
|
|
78
|
-
|
|
79
|
-
const extension = '.' + (options.extension || 'zip');
|
|
80
|
-
|
|
81
|
-
// combine the output path and filename
|
|
82
|
-
const outputPathAndFilename = path.resolve(
|
|
83
|
-
compilation.options.output.path, // ...supporting both absolute and relative paths
|
|
84
|
-
outputPath,
|
|
85
|
-
path.basename(outputFilename, '.zip') + extension // ...and filenames with and without a .zip extension
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
// resolve a relative output path with respect to webpack's root output path
|
|
89
|
-
// since only relative paths are permitted for keys in `compilation.assets`
|
|
90
|
-
const relativeOutputPath = path.relative(compilation.options.output.path, outputPathAndFilename);
|
|
91
|
-
|
|
92
|
-
// add our zip file to the assets
|
|
93
|
-
const zipBuffer = Buffer.concat(bufs);
|
|
94
|
-
const zipSize = zipBuffer.length;
|
|
95
|
-
const zipSizeMb = (zipSize / (1024 * 1024)).toFixed(2);
|
|
96
|
-
const maxSizeMb = (MAX_PLAYABLE_PACKAGE_BYTES / (1024 * 1024)).toFixed(0);
|
|
97
|
-
console.log(`📦 ZIP size: ${zipSizeMb}MB (limit: ${maxSizeMb}MB)`);
|
|
98
|
-
|
|
99
|
-
if (zipSize > MAX_PLAYABLE_PACKAGE_BYTES) {
|
|
100
|
-
const sizeError = new Error(
|
|
101
|
-
`ZIP 产物超过 ${maxSizeMb}MB 上限: ${zipSizeMb}MB (${path.basename(outputPathAndFilename)})`
|
|
102
|
-
);
|
|
103
|
-
compilation.errors.push(sizeError);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const zipFileSource = new RawSource(zipBuffer);
|
|
107
|
-
if (isWebpack4) {
|
|
108
|
-
compilation.assets[relativeOutputPath] = zipFileSource;
|
|
109
|
-
} else {
|
|
110
|
-
compilation.emitAsset(relativeOutputPath, zipFileSource);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// 🗑️ 保存临时文件夹路径,用于后续删除
|
|
114
|
-
if (options.cleanTempFolder !== false) {
|
|
115
|
-
const tempFolder = compilation.options.output.path;
|
|
116
|
-
const zipOutputPath = options.path || compilation.options.output.path;
|
|
117
|
-
// 确保临时文件夹存在且不是 zip 输出目录
|
|
118
|
-
if (tempFolder && tempFolder !== zipOutputPath) {
|
|
119
|
-
tempFolderToClean = tempFolder;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
callback();
|
|
124
|
-
});
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
if (isWebpack4) {
|
|
128
|
-
compiler.hooks.emit.tapAsync(ZipPlugin.name, process);
|
|
129
|
-
} else {
|
|
130
|
-
compiler.hooks.thisCompilation.tap(ZipPlugin.name, (compilation) => {
|
|
131
|
-
compilation.hooks.processAssets.tapPromise(
|
|
132
|
-
{
|
|
133
|
-
name: ZipPlugin.name,
|
|
134
|
-
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER
|
|
135
|
-
},
|
|
136
|
-
() => new Promise((resolve) => process(compilation, resolve))
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 🗑️ 在所有文件写入完成后删除临时文件夹
|
|
142
|
-
compiler.hooks.done.tap(ZipPlugin.name, () => {
|
|
143
|
-
if (tempFolderToClean && fs.existsSync(tempFolderToClean)) {
|
|
144
|
-
try {
|
|
145
|
-
fs.rmSync(tempFolderToClean, { recursive: true, force: true });
|
|
146
|
-
console.log(`✅ 已删除临时文件夹: ${tempFolderToClean}`);
|
|
147
|
-
} catch (err) {
|
|
148
|
-
console.warn(`⚠️ 删除临时文件夹失败: ${tempFolderToClean}`, err.message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
exports.ZipPlugin = ZipPlugin;
|
|
1
|
+
"use strict";const path=require("path");const fs=require("fs");const ModuleFilenameHelpers=require("webpack/lib/ModuleFilenameHelpers");const webpack=require("webpack");const webpackSources=webpack.sources||require("webpack-sources");const yazl=require("yazl");const{MAX_PLAYABLE_PACKAGE_BYTES}=require("../constants.js");function ZipPlugin(options){this.options=options||{}}ZipPlugin.prototype.apply=function(compiler){const options=this.options;const isWebpack4=webpack.version.startsWith("4.");// Prefer `compiler.webpack` instead of webpack or webpack-sources import (rspack compatibility hack)
|
|
2
|
+
const{RawSource}=compiler.webpack&&compiler.webpack.sources||webpackSources;if(options.pathPrefix&&path.isAbsolute(options.pathPrefix)){throw new Error("\"pathPrefix\" must be a relative path")}// 保存临时文件夹路径,用于后续删除
|
|
3
|
+
let tempFolderToClean=null;const process=function(compilation,callback){// assets from child compilers will be included in the parent
|
|
4
|
+
// so we should not run in child compilers
|
|
5
|
+
if(compilation.compiler.isChild()){callback();return}const zipFile=new yazl.ZipFile;const pathPrefix=options.pathPrefix||"";const pathMapper=options.pathMapper||function(x){return x};// populate the zip file with each asset
|
|
6
|
+
for(const nameAndPath of Object.keys(compilation.assets)){// match against include and exclude, which may be strings, regexes, arrays of the previous or omitted
|
|
7
|
+
if(!ModuleFilenameHelpers.matchObject({include:options.include,exclude:options.exclude},nameAndPath))continue;const source=compilation.assets[nameAndPath].source();// 使用 posix 路径,保证 zip 内是「文件在根目录」而不是再套一层文件夹
|
|
8
|
+
const entryName=path.posix.join(pathPrefix.replace(/\\/g,"/"),pathMapper(nameAndPath).replace(/\\/g,"/"));zipFile.addBuffer(Buffer.isBuffer(source)?source:Buffer.from?Buffer.from(source):new Buffer(source),entryName,options.fileOptions)}zipFile.end(options.zipOptions);// accumulate each buffer containing a part of the zip file
|
|
9
|
+
const bufs=[];zipFile.outputStream.on("data",function(buf){bufs.push(buf)});zipFile.outputStream.on("end",function(){// default to webpack's root output path if no path provided
|
|
10
|
+
const outputPath=options.path||compilation.options.output.path;// default to webpack root filename if no filename provided, else the basename of the output path
|
|
11
|
+
let outputFilename=options.filename||compilation.options.output.filename||path.basename(outputPath);const fullHash=compilation.fullHash||compilation.hash;outputFilename=outputFilename.replace("[fullhash:6]",fullHash.slice(0,6));const extension="."+(options.extension||"zip");// combine the output path and filename
|
|
12
|
+
const outputPathAndFilename=path.resolve(compilation.options.output.path,// ...supporting both absolute and relative paths
|
|
13
|
+
outputPath,path.basename(outputFilename,".zip")+extension// ...and filenames with and without a .zip extension
|
|
14
|
+
);// resolve a relative output path with respect to webpack's root output path
|
|
15
|
+
// since only relative paths are permitted for keys in `compilation.assets`
|
|
16
|
+
const relativeOutputPath=path.relative(compilation.options.output.path,outputPathAndFilename);// add our zip file to the assets
|
|
17
|
+
const zipBuffer=Buffer.concat(bufs);const zipSize=zipBuffer.length;const zipSizeMb=(zipSize/(1024*1024)).toFixed(2);const maxSizeMb=(MAX_PLAYABLE_PACKAGE_BYTES/(1024*1024)).toFixed(0);console.log(`📦 ZIP size: ${zipSizeMb}MB (limit: ${maxSizeMb}MB)`);if(zipSize>MAX_PLAYABLE_PACKAGE_BYTES){const sizeError=new Error(`ZIP 产物超过 ${maxSizeMb}MB 上限: ${zipSizeMb}MB (${path.basename(outputPathAndFilename)})`);compilation.errors.push(sizeError)}const zipFileSource=new RawSource(zipBuffer);if(isWebpack4){compilation.assets[relativeOutputPath]=zipFileSource}else{compilation.emitAsset(relativeOutputPath,zipFileSource)}// 🗑️ 保存临时文件夹路径,用于后续删除
|
|
18
|
+
if(options.cleanTempFolder!==false){const tempFolder=compilation.options.output.path;const zipOutputPath=options.path||compilation.options.output.path;// 确保临时文件夹存在且不是 zip 输出目录
|
|
19
|
+
if(tempFolder&&tempFolder!==zipOutputPath){tempFolderToClean=tempFolder}}callback()})};if(isWebpack4){compiler.hooks.emit.tapAsync(ZipPlugin.name,process)}else{compiler.hooks.thisCompilation.tap(ZipPlugin.name,compilation=>{compilation.hooks.processAssets.tapPromise({name:ZipPlugin.name,stage:webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER},()=>new Promise(resolve=>process(compilation,resolve)))})}// 🗑️ 在所有文件写入完成后删除临时文件夹
|
|
20
|
+
compiler.hooks.done.tap(ZipPlugin.name,()=>{if(tempFolderToClean&&fs.existsSync(tempFolderToClean)){try{fs.rmSync(tempFolderToClean,{recursive:true,force:true});console.log(`✅ 已删除临时文件夹: ${tempFolderToClean}`)}catch(err){console.warn(`⚠️ 删除临时文件夹失败: ${tempFolderToClean}`,err.message)}}})};exports.ZipPlugin=ZipPlugin;
|
|
@@ -1,110 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";/**
|
|
4
2
|
* Vite dev 模式:social_media 渠道的 OG / Twitter meta 注入。
|
|
5
3
|
* 与 SocialMediaInjectorPlugin.js 输出完全一致,仅 hook 形态不同。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const JSON5 = require('json5');
|
|
11
|
-
|
|
12
|
-
/** 把 shareMeta 渲染成 <meta> 标签字符串(与 webpack plugin 完全一致) */
|
|
13
|
-
function buildMetaTags(meta) {
|
|
14
|
-
if (!meta) return [];
|
|
15
|
-
const esc = (s) => String(s)
|
|
16
|
-
.replace(/&/g, '&')
|
|
17
|
-
.replace(/"/g, '"')
|
|
18
|
-
.replace(/</g, '<')
|
|
19
|
-
.replace(/>/g, '>');
|
|
20
|
-
|
|
21
|
-
const tags = [];
|
|
22
|
-
const { title, description, imageUrl, pageUrl, siteName } = meta;
|
|
23
|
-
const t = (title || '').trim();
|
|
24
|
-
const d = (description || '').trim();
|
|
25
|
-
const i = (imageUrl || '').trim();
|
|
26
|
-
const u = (pageUrl || '').trim();
|
|
27
|
-
const s = (siteName || '').trim();
|
|
28
|
-
|
|
29
|
-
if (t) tags.push({ tag: 'meta', injectTo: 'head', attrs: { property: 'og:title', content: t } });
|
|
30
|
-
if (d) tags.push({ tag: 'meta', injectTo: 'head', attrs: { property: 'og:description', content: d } });
|
|
31
|
-
if (i) tags.push({ tag: 'meta', injectTo: 'head', attrs: { property: 'og:image', content: i } });
|
|
32
|
-
if (u) tags.push({ tag: 'meta', injectTo: 'head', attrs: { property: 'og:url', content: u } });
|
|
33
|
-
if (s) tags.push({ tag: 'meta', injectTo: 'head', attrs: { property: 'og:site_name', content: s } });
|
|
34
|
-
tags.push({ tag: 'meta', injectTo: 'head', attrs: { property: 'og:type', content: 'website' } });
|
|
35
|
-
|
|
36
|
-
tags.push({ tag: 'meta', injectTo: 'head', attrs: { name: 'twitter:card', content: 'summary_large_image' } });
|
|
37
|
-
if (t) tags.push({ tag: 'meta', injectTo: 'head', attrs: { name: 'twitter:title', content: t } });
|
|
38
|
-
if (d) tags.push({ tag: 'meta', injectTo: 'head', attrs: { name: 'twitter:description', content: d } });
|
|
39
|
-
if (i) tags.push({ tag: 'meta', injectTo: 'head', attrs: { name: 'twitter:image', content: i } });
|
|
40
|
-
return tags;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** 检测主题名(与 fontFamilyPlugin 逻辑保持一致) */
|
|
44
|
-
function detectTheme() {
|
|
45
|
-
const root = process.cwd();
|
|
46
|
-
const buildsConfigPath = path.join(root, 'builds.config.js');
|
|
47
|
-
let config = { sourceDir: 'src/theme', entryFile: 'src/theme/index.ts' };
|
|
48
|
-
if (fs.existsSync(buildsConfigPath)) {
|
|
49
|
-
try {
|
|
50
|
-
delete require.cache[require.resolve(buildsConfigPath)];
|
|
51
|
-
const bc = require(buildsConfigPath);
|
|
52
|
-
if (bc.themes) config = { ...config, ...bc.themes };
|
|
53
|
-
} catch (_) { /* ignore */ }
|
|
54
|
-
}
|
|
55
|
-
let name = null;
|
|
56
|
-
const entryPath = path.join(root, config.entryFile);
|
|
57
|
-
if (fs.existsSync(entryPath)) {
|
|
58
|
-
const content = fs.readFileSync(entryPath, 'utf8');
|
|
59
|
-
const m = content.match(/from\s+['"]\.\/([^'"]+)['"]/);
|
|
60
|
-
if (m) name = m[1];
|
|
61
|
-
}
|
|
62
|
-
return { themeName: name, themeConfig: config };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** 读取 shareMeta,缓存以避免 transformIndexHtml 多次触发时反复读盘 */
|
|
66
|
-
let _cache = { key: null, value: null };
|
|
67
|
-
|
|
68
|
-
function loadShareMeta() {
|
|
69
|
-
const { themeName, themeConfig } = detectTheme();
|
|
70
|
-
if (!themeName) return null;
|
|
71
|
-
const root = process.cwd();
|
|
72
|
-
const dataPath = path.join(
|
|
73
|
-
root,
|
|
74
|
-
themeConfig.sourceDir || 'src/theme',
|
|
75
|
-
themeName,
|
|
76
|
-
'theme.data.json5'
|
|
77
|
-
);
|
|
78
|
-
if (!fs.existsSync(dataPath)) return null;
|
|
79
|
-
const stat = fs.statSync(dataPath);
|
|
80
|
-
const key = `${dataPath}::${stat.mtimeMs}`;
|
|
81
|
-
if (_cache.key === key) return _cache.value;
|
|
82
|
-
try {
|
|
83
|
-
const raw = fs.readFileSync(dataPath, 'utf8');
|
|
84
|
-
const parsed = JSON5.parse(raw);
|
|
85
|
-
const value = parsed && parsed.gamePlay && parsed.gamePlay.shareMeta
|
|
86
|
-
? parsed.gamePlay.shareMeta
|
|
87
|
-
: null;
|
|
88
|
-
_cache = { key, value };
|
|
89
|
-
return value;
|
|
90
|
-
} catch (e) {
|
|
91
|
-
console.warn('[socialMediaVitePlugin] Failed to read theme.data.json5:', e.message);
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function socialMediaVitePlugin() {
|
|
97
|
-
return {
|
|
98
|
-
name: 'vite-plugin-social-media',
|
|
99
|
-
apply: 'serve',
|
|
100
|
-
transformIndexHtml: {
|
|
101
|
-
order: 'pre',
|
|
102
|
-
handler() {
|
|
103
|
-
const meta = loadShareMeta();
|
|
104
|
-
return buildMetaTags(meta);
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
module.exports = { socialMediaVitePlugin, buildMetaTags };
|
|
4
|
+
*/const path=require("path");const fs=require("fs");const JSON5=require("json5");/** 把 shareMeta 渲染成 <meta> 标签字符串(与 webpack plugin 完全一致) */function buildMetaTags(meta){if(!meta)return[];const esc=s=>String(s).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");const tags=[];const{title,description,imageUrl,pageUrl,siteName}=meta;const t=(title||"").trim();const d=(description||"").trim();const i=(imageUrl||"").trim();const u=(pageUrl||"").trim();const s=(siteName||"").trim();if(t)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:title",content:t}});if(d)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:description",content:d}});if(i)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:image",content:i}});if(u)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:url",content:u}});if(s)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:site_name",content:s}});tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:type",content:"website"}});tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:card",content:"summary_large_image"}});if(t)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:title",content:t}});if(d)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:description",content:d}});if(i)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:image",content:i}});return tags}/** 检测主题名(与 fontFamilyPlugin 逻辑保持一致) */function detectTheme(){const root=process.cwd();const buildsConfigPath=path.join(root,"builds.config.js");let config={sourceDir:"src/theme",entryFile:"src/theme/index.ts"};if(fs.existsSync(buildsConfigPath)){try{delete require.cache[require.resolve(buildsConfigPath)];const bc=require(buildsConfigPath);if(bc.themes)config={...config,...bc.themes}}catch(_){/* ignore */}}let name=null;const entryPath=path.join(root,config.entryFile);if(fs.existsSync(entryPath)){const content=fs.readFileSync(entryPath,"utf8");const m=content.match(/from\s+['"]\.\/([^'"]+)['"]/);if(m)name=m[1]}return{themeName:name,themeConfig:config}}/** 读取 shareMeta,缓存以避免 transformIndexHtml 多次触发时反复读盘 */let _cache={key:null,value:null};function loadShareMeta(){const{themeName,themeConfig}=detectTheme();if(!themeName)return null;const root=process.cwd();const dataPath=path.join(root,themeConfig.sourceDir||"src/theme",themeName,"theme.data.json5");if(!fs.existsSync(dataPath))return null;const stat=fs.statSync(dataPath);const key=`${dataPath}::${stat.mtimeMs}`;if(_cache.key===key)return _cache.value;try{const raw=fs.readFileSync(dataPath,"utf8");const parsed=JSON5.parse(raw);const value=parsed&&parsed.gamePlay&&parsed.gamePlay.shareMeta?parsed.gamePlay.shareMeta:null;_cache={key,value};return value}catch(e){console.warn("[socialMediaVitePlugin] Failed to read theme.data.json5:",e.message);return null}}function socialMediaVitePlugin(){return{name:"vite-plugin-social-media",apply:"serve",transformIndexHtml:{order:"pre",handler(){const meta=loadShareMeta();return buildMetaTags(meta)}}}}module.exports={socialMediaVitePlugin,buildMetaTags};
|
|
@@ -1,32 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";const{options}=require("../options");/**
|
|
4
2
|
* Build's default defines list for dev & prod builds
|
|
5
3
|
* @param {import('../index').CLIOptions} [customOptions] - 合并后的当前构建选项(用于读取会随单次构建变化的字段,如 playcraftOrientationValue);未提供时回退到全局 options
|
|
6
|
-
*/
|
|
7
|
-
exports.buildDefines = function buildDefines(customOptions) {
|
|
8
|
-
const opts = customOptions || options;
|
|
9
|
-
const defines = {
|
|
10
|
-
__DEV__: JSON.stringify(opts['dev'] === undefined ? false : opts['dev']),
|
|
11
|
-
GOOGLE_PLAY_URL: JSON.stringify(opts.googlePlayUrl),
|
|
12
|
-
APP_STORE_URL: JSON.stringify(opts.appStoreUrl),
|
|
13
|
-
AD_NETWORK: JSON.stringify(opts['network']),
|
|
14
|
-
AD_PROTOCOL: JSON.stringify(opts['protocol']),
|
|
15
|
-
APP: JSON.stringify(opts.app),
|
|
16
|
-
NAME: JSON.stringify(opts.name),
|
|
17
|
-
VERSION: JSON.stringify(opts.version),
|
|
18
|
-
BUILD_HASH: JSON.stringify((+new Date()).toString(36)),
|
|
19
|
-
LANGUAGE: JSON.stringify(opts.language),
|
|
20
|
-
PLAYCRAFT_ORIENTATION: JSON.stringify(opts.playcraftOrientationValue || 'both')
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/** @type {AD_NETWORK} */
|
|
24
|
-
let adNetworkDefine = opts['network'];
|
|
25
|
-
|
|
26
|
-
if (adNetworkDefine === 'pangle') adNetworkDefine = 'tiktok';
|
|
27
|
-
if (adNetworkDefine === 'mytarget' || adNetworkDefine === 'moloco') adNetworkDefine = 'facebook';
|
|
28
|
-
|
|
29
|
-
defines['AD_NETWORK'] = JSON.stringify(adNetworkDefine);
|
|
30
|
-
|
|
31
|
-
return defines;
|
|
32
|
-
};
|
|
4
|
+
*/exports.buildDefines=function buildDefines(customOptions){const opts=customOptions||options;const defines={__DEV__:JSON.stringify(opts["dev"]===undefined?false:opts["dev"]),GOOGLE_PLAY_URL:JSON.stringify(opts.googlePlayUrl),APP_STORE_URL:JSON.stringify(opts.appStoreUrl),AD_NETWORK:JSON.stringify(opts["network"]),AD_PROTOCOL:JSON.stringify(opts["protocol"]),APP:JSON.stringify(opts.app),NAME:JSON.stringify(opts.name),VERSION:JSON.stringify(opts.version),BUILD_HASH:JSON.stringify((+new Date).toString(36)),LANGUAGE:JSON.stringify(opts.language),PLAYCRAFT_ORIENTATION:JSON.stringify(opts.playcraftOrientationValue||"both")};/** @type {AD_NETWORK} */let adNetworkDefine=opts["network"];if(adNetworkDefine==="pangle")adNetworkDefine="tiktok";if(adNetworkDefine==="mytarget"||adNetworkDefine==="moloco")adNetworkDefine="facebook";defines["AD_NETWORK"]=JSON.stringify(adNetworkDefine);return defines};
|
|
@@ -1,40 +1,6 @@
|
|
|
1
|
-
const
|
|
2
|
-
const { getCurrentDateFormatted } = require('./date');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
/** @type {Record<string, string>} Mapping of ad network identifiers to their display names in filenames */
|
|
6
|
-
const adNetworkFileNameMap = {
|
|
7
|
-
preview: 'Preview',
|
|
8
|
-
bigoads: 'bigo'
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
1
|
+
"use strict";const{options}=require("../options");const{getCurrentDateFormatted}=require("./date");const path=require("path");/** @type {Record<string, string>} Mapping of ad network identifiers to their display names in filenames */const adNetworkFileNameMap={preview:"Preview",bigoads:"bigo"};/**
|
|
12
2
|
* Build's result string from template. Used for outDir or filename
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
/** @type {AD_NETWORK} */
|
|
18
|
-
const adNetwork = customOptions['network'];
|
|
19
|
-
|
|
20
|
-
/** @type {AD_PROTOCOL} */
|
|
21
|
-
// const adProtocol = customOptions['protocol'];
|
|
22
|
-
|
|
23
|
-
let networkName = customOptions.adNetworkNames[adNetwork] || adNetworkFileNameMap[adNetwork] || adNetwork;
|
|
24
|
-
// if (adProtocol === 'dapi') networkName += '_DAPI';
|
|
25
|
-
|
|
26
|
-
// 如果是绝对路径,直接返回,不做模板替换
|
|
27
|
-
if (path.isAbsolute(template)) {
|
|
28
|
-
return template;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
template = template.replaceAll('{app}', customOptions.app);
|
|
32
|
-
template = template.replaceAll('{name}', customOptions.name);
|
|
33
|
-
template = template.replaceAll('{version}', customOptions.version);
|
|
34
|
-
template = template.replaceAll('{date}', getCurrentDateFormatted());
|
|
35
|
-
template = template.replaceAll('{language}', customOptions.language);
|
|
36
|
-
template = template.replaceAll('{network}', networkName.toLowerCase());
|
|
37
|
-
template = template.replaceAll('{hash}', '[fullhash:6]');
|
|
38
|
-
|
|
39
|
-
return template;
|
|
40
|
-
};
|
|
3
|
+
*/exports.buildTemplateString=function buildTemplateString(template,customOptions){customOptions=customOptions||options;/** @type {AD_NETWORK} */const adNetwork=customOptions["network"];/** @type {AD_PROTOCOL} */// const adProtocol = customOptions['protocol'];
|
|
4
|
+
let networkName=customOptions.adNetworkNames[adNetwork]||adNetworkFileNameMap[adNetwork]||adNetwork;// if (adProtocol === 'dapi') networkName += '_DAPI';
|
|
5
|
+
// 如果是绝对路径,直接返回,不做模板替换
|
|
6
|
+
if(path.isAbsolute(template)){return template}template=template.replaceAll("{app}",customOptions.app);template=template.replaceAll("{name}",customOptions.name);template=template.replaceAll("{version}",customOptions.version);template=template.replaceAll("{date}",getCurrentDateFormatted());template=template.replaceAll("{language}",customOptions.language);template=template.replaceAll("{network}",networkName.toLowerCase());template=template.replaceAll("{hash}","[fullhash:6]");return template};
|
package/core/utils/date.js
CHANGED
|
@@ -1,16 +1 @@
|
|
|
1
|
-
exports.getCurrentDateFormatted
|
|
2
|
-
const date = new Date();
|
|
3
|
-
const year = date.getFullYear();
|
|
4
|
-
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
5
|
-
const day = date.getDate().toString().padStart(2, '0');
|
|
6
|
-
|
|
7
|
-
if (includeTime) {
|
|
8
|
-
const hour = date.getHours().toString().padStart(2, '0');
|
|
9
|
-
const minute = date.getMinutes().toString().padStart(2, '0');
|
|
10
|
-
const second = date.getSeconds().toString().padStart(2, '0');
|
|
11
|
-
|
|
12
|
-
return `${year}${month}${day}-${hour}${minute}${second}`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return `${year}${month}${day}`;
|
|
16
|
-
};
|
|
1
|
+
"use strict";exports.getCurrentDateFormatted=function getCurrentDateFormatted(includeTime){const date=new Date;const year=date.getFullYear();const month=(date.getMonth()+1).toString().padStart(2,"0");const day=date.getDate().toString().padStart(2,"0");if(includeTime){const hour=date.getHours().toString().padStart(2,"0");const minute=date.getMinutes().toString().padStart(2,"0");const second=date.getSeconds().toString().padStart(2,"0");return`${year}${month}${day}-${hour}${minute}${second}`}return`${year}${month}${day}`};
|