@playcraft/devkit 1.0.16 → 1.0.17

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 (45) hide show
  1. package/README.md +259 -0
  2. package/cli/bin/playable-scripts.js +44 -1
  3. package/cli/commands/build.js +122 -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 +58 -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/LiftoffInjectorPlugin.js +26 -4
  20. package/core/plugins/MRAIDInjectorPlugin.js +24 -2
  21. package/core/plugins/MintegralInjectorPlugin.js +25 -2
  22. package/core/plugins/PangleInjectorPlugin.js +24 -2
  23. package/core/plugins/SnapchatInjectorPlugin.js +26 -4
  24. package/core/plugins/TikTokInjectorPlugin.js +24 -2
  25. package/core/plugins/UnityInjectorPlugin.js +37 -8
  26. package/core/plugins/ZipPlugin.js +138 -19
  27. package/core/utils/buildDefines.js +29 -2
  28. package/core/utils/buildTemplateString.js +40 -5
  29. package/core/utils/date.js +16 -1
  30. package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
  31. package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
  32. package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
  33. package/core/utils/injectSDKPlugins.js +58 -11
  34. package/core/utils/logOptions.js +37 -4
  35. package/core/utils/mergeOptions.js +19 -2
  36. package/core/utils/parseArgvOptions.js +110 -5
  37. package/core/utils/resolveChannelFold2Zip.js +15 -3
  38. package/core/utils/validateThemeData.js +220 -25
  39. package/core/validators/pre-build-checker.js +201 -21
  40. package/core/validators/tracking-validator.js +358 -40
  41. package/core/vite.dev.js +181 -15
  42. package/core/webpack.build.js +446 -52
  43. package/core/webpack.common.js +177 -11
  44. package/core/webpack.dev.js +82 -5
  45. package/package.json +3 -2
@@ -1,19 +1,138 @@
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");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();zipFile.addBuffer(Buffer.isBuffer(source)?source:Buffer.from?Buffer.from(source):new Buffer(source),path.join(pathPrefix,pathMapper(nameAndPath)),options.fileOptions)}zipFile.end(options.zipOptions);// accumulate each buffer containing a part of the zip file
8
- 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
9
- const outputPath=options.path||compilation.options.output.path;// default to webpack root filename if no filename provided, else the basename of the output path
10
- 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
11
- const outputPathAndFilename=path.resolve(compilation.options.output.path,// ...supporting both absolute and relative paths
12
- outputPath,path.basename(outputFilename,".zip")+extension// ...and filenames with and without a .zip extension
13
- );// resolve a relative output path with respect to webpack's root output path
14
- // since only relative paths are permitted for keys in `compilation.assets`
15
- const relativeOutputPath=path.relative(compilation.options.output.path,outputPathAndFilename);// add our zip file to the assets
16
- const zipFileSource=new RawSource(Buffer.concat(bufs));if(isWebpack4){compilation.assets[relativeOutputPath]=zipFileSource}else{compilation.emitAsset(relativeOutputPath,zipFileSource)}// 🗑️ 保存临时文件夹路径,用于后续删除
17
- if(options.cleanTempFolder!==false){const tempFolder=compilation.options.output.path;const zipOutputPath=options.path||compilation.options.output.path;// 确保临时文件夹存在且不是 zip 输出目录
18
- 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)))})}// 🗑️ 在所有文件写入完成后删除临时文件夹
19
- 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
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const ModuleFilenameHelpers = require('webpack/lib/ModuleFilenameHelpers');
6
+ const webpack = require('webpack');
7
+ const webpackSources = webpack.sources || require('webpack-sources');
8
+ const yazl = require('yazl');
9
+
10
+ function ZipPlugin(options) {
11
+ this.options = options || {};
12
+ }
13
+
14
+ ZipPlugin.prototype.apply = function (compiler) {
15
+ const options = this.options;
16
+ const isWebpack4 = webpack.version.startsWith('4.');
17
+ // Prefer `compiler.webpack` instead of webpack or webpack-sources import (rspack compatibility hack)
18
+ const { RawSource } = (compiler.webpack && compiler.webpack.sources) || webpackSources;
19
+
20
+ if (options.pathPrefix && path.isAbsolute(options.pathPrefix)) {
21
+ throw new Error('"pathPrefix" must be a relative path');
22
+ }
23
+
24
+ // 保存临时文件夹路径,用于后续删除
25
+ let tempFolderToClean = null;
26
+
27
+ const process = function (compilation, callback) {
28
+ // assets from child compilers will be included in the parent
29
+ // so we should not run in child compilers
30
+ if (compilation.compiler.isChild()) {
31
+ callback();
32
+ return;
33
+ }
34
+
35
+ const zipFile = new yazl.ZipFile();
36
+
37
+ const pathPrefix = options.pathPrefix || '';
38
+ const pathMapper =
39
+ options.pathMapper ||
40
+ function (x) {
41
+ return x;
42
+ };
43
+
44
+ // populate the zip file with each asset
45
+ for (const nameAndPath of Object.keys(compilation.assets)) {
46
+ // match against include and exclude, which may be strings, regexes, arrays of the previous or omitted
47
+ if (!ModuleFilenameHelpers.matchObject({ include: options.include, exclude: options.exclude }, nameAndPath)) continue;
48
+
49
+ const source = compilation.assets[nameAndPath].source();
50
+
51
+ zipFile.addBuffer(
52
+ Buffer.isBuffer(source) ? source : Buffer.from ? Buffer.from(source) : new Buffer(source),
53
+ path.join(pathPrefix, pathMapper(nameAndPath)),
54
+ options.fileOptions
55
+ );
56
+ }
57
+
58
+ zipFile.end(options.zipOptions);
59
+
60
+ // accumulate each buffer containing a part of the zip file
61
+ const bufs = [];
62
+
63
+ zipFile.outputStream.on('data', function (buf) {
64
+ bufs.push(buf);
65
+ });
66
+
67
+ zipFile.outputStream.on('end', function () {
68
+ // default to webpack's root output path if no path provided
69
+ const outputPath = options.path || compilation.options.output.path;
70
+ // default to webpack root filename if no filename provided, else the basename of the output path
71
+ let outputFilename = options.filename || compilation.options.output.filename || path.basename(outputPath);
72
+
73
+ const fullHash = compilation.fullHash || compilation.hash;
74
+ outputFilename = outputFilename.replace('[fullhash:6]', fullHash.slice(0, 6));
75
+
76
+ const extension = '.' + (options.extension || 'zip');
77
+
78
+ // combine the output path and filename
79
+ const outputPathAndFilename = path.resolve(
80
+ compilation.options.output.path, // ...supporting both absolute and relative paths
81
+ outputPath,
82
+ path.basename(outputFilename, '.zip') + extension // ...and filenames with and without a .zip extension
83
+ );
84
+
85
+ // resolve a relative output path with respect to webpack's root output path
86
+ // since only relative paths are permitted for keys in `compilation.assets`
87
+ const relativeOutputPath = path.relative(compilation.options.output.path, outputPathAndFilename);
88
+
89
+ // add our zip file to the assets
90
+ const zipFileSource = new RawSource(Buffer.concat(bufs));
91
+ if (isWebpack4) {
92
+ compilation.assets[relativeOutputPath] = zipFileSource;
93
+ } else {
94
+ compilation.emitAsset(relativeOutputPath, zipFileSource);
95
+ }
96
+
97
+ // 🗑️ 保存临时文件夹路径,用于后续删除
98
+ if (options.cleanTempFolder !== false) {
99
+ const tempFolder = compilation.options.output.path;
100
+ const zipOutputPath = options.path || compilation.options.output.path;
101
+ // 确保临时文件夹存在且不是 zip 输出目录
102
+ if (tempFolder && tempFolder !== zipOutputPath) {
103
+ tempFolderToClean = tempFolder;
104
+ }
105
+ }
106
+
107
+ callback();
108
+ });
109
+ };
110
+
111
+ if (isWebpack4) {
112
+ compiler.hooks.emit.tapAsync(ZipPlugin.name, process);
113
+ } else {
114
+ compiler.hooks.thisCompilation.tap(ZipPlugin.name, (compilation) => {
115
+ compilation.hooks.processAssets.tapPromise(
116
+ {
117
+ name: ZipPlugin.name,
118
+ stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER
119
+ },
120
+ () => new Promise((resolve) => process(compilation, resolve))
121
+ );
122
+ });
123
+ }
124
+
125
+ // 🗑️ 在所有文件写入完成后删除临时文件夹
126
+ compiler.hooks.done.tap(ZipPlugin.name, () => {
127
+ if (tempFolderToClean && fs.existsSync(tempFolderToClean)) {
128
+ try {
129
+ fs.rmSync(tempFolderToClean, { recursive: true, force: true });
130
+ console.log(`✅ 已删除临时文件夹: ${tempFolderToClean}`);
131
+ } catch (err) {
132
+ console.warn(`⚠️ 删除临时文件夹失败: ${tempFolderToClean}`, err.message);
133
+ }
134
+ }
135
+ });
136
+ };
137
+
138
+ exports.ZipPlugin = ZipPlugin;
@@ -1,3 +1,30 @@
1
- "use strict";const{options}=require("../options");/**
1
+ const { options } = require('../options');
2
+
3
+ /**
2
4
  * Build's default defines list for dev & prod builds
3
- */exports.buildDefines=function buildDefines(){const defines={__DEV__:JSON.stringify(options["dev"]===undefined?false:options["dev"]),GOOGLE_PLAY_URL:JSON.stringify(options.googlePlayUrl),APP_STORE_URL:JSON.stringify(options.appStoreUrl),AD_NETWORK:JSON.stringify(options["network"]),AD_PROTOCOL:JSON.stringify(options["protocol"]),APP:JSON.stringify(options.app),NAME:JSON.stringify(options.name),VERSION:JSON.stringify(options.version),BUILD_HASH:JSON.stringify((+new Date).toString(36)),LANGUAGE:JSON.stringify(options.language),ORIENTATION:JSON.stringify(options.orientation)};/** @type {AD_NETWORK} */let adNetworkDefine=options["network"];if(adNetworkDefine==="pangle")adNetworkDefine="tiktok";if(adNetworkDefine==="mytarget"||adNetworkDefine==="moloco")adNetworkDefine="facebook";defines["AD_NETWORK"]=JSON.stringify(adNetworkDefine);return defines};
5
+ */
6
+ exports.buildDefines = function buildDefines() {
7
+ const defines = {
8
+ __DEV__: JSON.stringify(options['dev'] === undefined ? false : options['dev']),
9
+ GOOGLE_PLAY_URL: JSON.stringify(options.googlePlayUrl),
10
+ APP_STORE_URL: JSON.stringify(options.appStoreUrl),
11
+ AD_NETWORK: JSON.stringify(options['network']),
12
+ AD_PROTOCOL: JSON.stringify(options['protocol']),
13
+ APP: JSON.stringify(options.app),
14
+ NAME: JSON.stringify(options.name),
15
+ VERSION: JSON.stringify(options.version),
16
+ BUILD_HASH: JSON.stringify((+new Date()).toString(36)),
17
+ LANGUAGE: JSON.stringify(options.language),
18
+ ORIENTATION: JSON.stringify(options.orientation)
19
+ };
20
+
21
+ /** @type {AD_NETWORK} */
22
+ let adNetworkDefine = options['network'];
23
+
24
+ if (adNetworkDefine === 'pangle') adNetworkDefine = 'tiktok';
25
+ if (adNetworkDefine === 'mytarget' || adNetworkDefine === 'moloco') adNetworkDefine = 'facebook';
26
+
27
+ defines['AD_NETWORK'] = JSON.stringify(adNetworkDefine);
28
+
29
+ return defines;
30
+ };
@@ -1,6 +1,41 @@
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"};/**
1
+ const { options } = require('../options');
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
+ /**
2
12
  * Build's result string from template. Used for outDir or filename
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("{orientation}",customOptions.orientation);template=template.replaceAll("{network}",networkName.toLowerCase());template=template.replaceAll("{hash}","[fullhash:6]");return template};
13
+ */
14
+ exports.buildTemplateString = function buildTemplateString(template, customOptions) {
15
+ customOptions = customOptions || options;
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('{orientation}', customOptions.orientation);
37
+ template = template.replaceAll('{network}', networkName.toLowerCase());
38
+ template = template.replaceAll('{hash}', '[fullhash:6]');
39
+
40
+ return template;
41
+ };
@@ -1 +1,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}`};
1
+ exports.getCurrentDateFormatted = function getCurrentDateFormatted(includeTime) {
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,4 +1,7 @@
1
- "use strict";const fs=require("fs");const path=require("path");/**
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
2
5
  * Adikteev 平台要求的占位符变量
3
6
  * 这些变量会在生产环境中被 Adikteev 平台替换为实际值
4
7
  *
@@ -9,13 +12,16 @@
9
12
  * 2. CSS 必须单独提取为 style.css,通过 <link rel="stylesheet"> 引用
10
13
  * 3. JS 必须单独为 creative.js,通过 <script src> 引用
11
14
  * 4. JS 和 CSS 文件会被上传到 Adikteev CDN
12
- */const AdikteevPlaceholders=`<meta charset="UTF-8">
15
+ */
16
+ const AdikteevPlaceholders = `<meta charset="UTF-8">
13
17
  <link rel="stylesheet" href="style.css">
14
18
  <script type="application/javascript">
15
19
  var AK_CLICK_DESTINATION_URL = "PLACEHOLDER_CLICK_REDIRECT";
16
20
  var AK_CLICK_PIXEL_URL = "PLACEHOLDER_CLICK_PIXEL";
17
21
  </script>
18
- <script type="application/javascript" src="creative.js"></script>`;/**
22
+ <script type="application/javascript" src="creative.js"></script>`;
23
+
24
+ /**
19
25
  * 生成 Adikteev 平台所需的 HTML 配置
20
26
  *
21
27
  * Adikteev 特殊要求:
@@ -30,7 +36,36 @@
30
36
  * - style.css → 上传到 Adikteev CDN
31
37
  * - creative.js → 上传到 Adikteev CDN
32
38
  * - index.html → HTML 片段(引用上述文件)
33
- */exports.generateAdikteevHtmlWebpackPluginConfig=function generateAdikteevHtmlWebpackPluginConfig(originalHtmlContentPath){originalHtmlContentPath=originalHtmlContentPath||"src/index.html";let originalBody="";try{const content=fs.readFileSync(path.resolve(originalHtmlContentPath),"utf8");const match=content.match(/<body>([\s\S]*?)<\/body>/);if(match){originalBody=match[1].trim();// 移除缩进以获得更清晰的输出
34
- originalBody=originalBody.replaceAll(" ","").replaceAll("\t","")}}catch(e){console.warn("[Adikteev] Could not read original HTML body:",e.message)}return{filename:"index.html",inject:false,minify:false,templateContent:({htmlWebpackPlugin})=>{// 生成 HTML 代码片段(非完整文档)
35
- // 结构: CSS 引用 → 占位符变量 → JS 引用 → body 内容
36
- return[AdikteevPlaceholders,originalBody].join("\n")}}};
39
+ */
40
+ exports.generateAdikteevHtmlWebpackPluginConfig = function generateAdikteevHtmlWebpackPluginConfig(originalHtmlContentPath) {
41
+ originalHtmlContentPath = originalHtmlContentPath || 'src/index.html';
42
+ let originalBody = '';
43
+
44
+ try {
45
+ const content = fs.readFileSync(path.resolve(originalHtmlContentPath), 'utf8');
46
+ const match = content.match(/<body>([\s\S]*?)<\/body>/);
47
+
48
+ if (match) {
49
+ originalBody = match[1].trim();
50
+ // 移除缩进以获得更清晰的输出
51
+ originalBody = originalBody.replaceAll(' ', '').replaceAll('\t', '');
52
+ }
53
+ } catch (e) {
54
+ console.warn('[Adikteev] Could not read original HTML body:', e.message);
55
+ }
56
+
57
+ return {
58
+ filename: 'index.html',
59
+ inject: false,
60
+ minify: false,
61
+ templateContent: ({ htmlWebpackPlugin }) => {
62
+ // 生成 HTML 代码片段(非完整文档)
63
+ // 结构: CSS 引用 → 占位符变量 → JS 引用 → body 内容
64
+ return [
65
+ AdikteevPlaceholders,
66
+ originalBody
67
+ ].join('\n');
68
+ }
69
+ };
70
+ };
71
+
@@ -1,4 +1,7 @@
1
- "use strict";const fs=require("fs");const path=require("path");const BigabidPlaceholders=`<meta charset="UTF-8">
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const BigabidPlaceholders = `<meta charset="UTF-8">
2
5
  <script src="mraid.js"></script>
3
6
  <script>window.BIGABID_BIDTIMEMACROS = {
4
7
  mraid_viewable: unescape("#IMP_TRACE_MRAID_VIEWABLE_ESC#"),
@@ -9,5 +12,29 @@
9
12
  final_url: unescape("#FINAL_LANDING_URL_ESC#")
10
13
  }</script>
11
14
  <script src="#BIGABID_PLAYABLE_CDN_URL#"></script>
12
- %{IMP_BEACON}`;exports.generateBigabidHtmlWebpackPluginConfig=function generateBigabidHtmlWebpackPluginConfig(originalHtmlContentPath){originalHtmlContentPath=originalHtmlContentPath||"src/index.html";let originalBody="";try{const content=fs.readFileSync(path.resolve(originalHtmlContentPath),"utf8");const match=content.match(/<body>([\s\S]*?)<\/body>/);if(match){originalBody=match[1].trim();// Remove tabs for a better look
13
- originalBody=originalBody.replaceAll(" ","").replaceAll("\t","")}}catch(e){}return{filename:"index.html",inject:false,minify:false,templateContent:({htmlWebpackPlugin})=>{return[BigabidPlaceholders,htmlWebpackPlugin.tags.headTags,originalBody].join("\n")}}};
15
+ %{IMP_BEACON}`;
16
+
17
+ exports.generateBigabidHtmlWebpackPluginConfig = function generateBigabidHtmlWebpackPluginConfig(originalHtmlContentPath) {
18
+ originalHtmlContentPath = originalHtmlContentPath || 'src/index.html';
19
+ let originalBody = '';
20
+
21
+ try {
22
+ const content = fs.readFileSync(path.resolve(originalHtmlContentPath), 'utf8');
23
+ const match = content.match(/<body>([\s\S]*?)<\/body>/);
24
+
25
+ if (match) {
26
+ originalBody = match[1].trim();
27
+ // Remove tabs for a better look
28
+ originalBody = originalBody.replaceAll(' ', '').replaceAll('\t', '');
29
+ }
30
+ } catch (e) {}
31
+
32
+ return {
33
+ filename: 'index.html',
34
+ inject: false,
35
+ minify: false,
36
+ templateContent: ({ htmlWebpackPlugin }) => {
37
+ return [BigabidPlaceholders, htmlWebpackPlugin.tags.headTags, originalBody].join('\n');
38
+ }
39
+ };
40
+ };
@@ -1,4 +1,7 @@
1
- "use strict";const fs=require("fs");const path=require("path");/**
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
2
5
  * InMobi 追踪宏定义
3
6
  * 文档: https://support.inmobi.com/dsp/in/inmobi-dsp-india/creative-specifications-and-guidelines/
4
7
  *
@@ -7,7 +10,8 @@
7
10
  * 2. 必须包含 INMOBI_DSPMACROS 追踪变量
8
11
  * 3. 引用外部 mraid.js(由 InMobi 平台提供)
9
12
  * 4. 支持 MRAID 3.0 标准
10
- */const InMobiPlaceholders=`<script>
13
+ */
14
+ const InMobiPlaceholders = `<script>
11
15
  var INMOBI_DSPMACROS = {
12
16
  "Ad_Load_Start": ["$P_AD_LOAD_START"],
13
17
  "Ad_Viewable": ["$P_AD_READY"],
@@ -23,14 +27,46 @@
23
27
  "Spent_30_Seconds": ["$P_TIMESPENT_30"]
24
28
  };
25
29
  </script>
26
- <script src="mraid.js"></script>`;/**
30
+ <script src="mraid.js"></script>`;
31
+
32
+ /**
27
33
  * 生成 InMobi 平台所需的 HTML 配置
28
34
  *
29
35
  * 策略:
30
36
  * 1. 使用标准的 HtmlWebpackPlugin 配置(不是 templateContent)
31
37
  * 2. JS 会通过 HtmlInlineScriptPlugin 自动内联到 HTML 中
32
38
  * 3. 通过修改 template 文件注入 InMobi 追踪脚本
33
- */exports.generateInMobiHtmlWebpackPluginConfig=function generateInMobiHtmlWebpackPluginConfig(originalHtmlContentPath,buildOptions){originalHtmlContentPath=originalHtmlContentPath||"src/index.html";const placeholder=InMobiPlaceholders.replaceAll("{GOOGLE_PLAY_URL}",buildOptions.googlePlayUrl).replaceAll("{APP_STORE_URL}",buildOptions.appStoreUrl);// 读取原始 HTML 模板
34
- let htmlContent="";try{htmlContent=fs.readFileSync(path.resolve(originalHtmlContentPath),"utf8")}catch(e){console.warn("[InMobi] Could not read HTML template:",e.message)}// 在 </head> 之前注入 InMobi 追踪脚本
35
- htmlContent=htmlContent.replace("</head>",`${placeholder}\n</head>`);return{filename:"index.html",inject:"head",// webpack 会自动注入 JS,然后 HtmlInlineScriptPlugin 内联
36
- minify:false,templateContent:htmlContent,meta:{charset:"UTF-8",viewport:"width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"}}};
39
+ */
40
+ exports.generateInMobiHtmlWebpackPluginConfig = function generateInMobiHtmlWebpackPluginConfig(
41
+ originalHtmlContentPath,
42
+ buildOptions
43
+ ) {
44
+ originalHtmlContentPath = originalHtmlContentPath || 'src/index.html';
45
+
46
+ const placeholder = InMobiPlaceholders
47
+ .replaceAll('{GOOGLE_PLAY_URL}', buildOptions.googlePlayUrl)
48
+ .replaceAll('{APP_STORE_URL}', buildOptions.appStoreUrl);
49
+
50
+ // 读取原始 HTML 模板
51
+ let htmlContent = '';
52
+ try {
53
+ htmlContent = fs.readFileSync(path.resolve(originalHtmlContentPath), 'utf8');
54
+ } catch (e) {
55
+ console.warn('[InMobi] Could not read HTML template:', e.message);
56
+ }
57
+
58
+ // 在 </head> 之前注入 InMobi 追踪脚本
59
+ htmlContent = htmlContent.replace('</head>', `${placeholder}\n</head>`);
60
+
61
+ return {
62
+ filename: 'index.html',
63
+ inject: 'head', // webpack 会自动注入 JS,然后 HtmlInlineScriptPlugin 内联
64
+ minify: false,
65
+ templateContent: htmlContent,
66
+ meta: {
67
+ charset: 'UTF-8',
68
+ viewport: 'width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no'
69
+ }
70
+ };
71
+ };
72
+
@@ -1,19 +1,66 @@
1
- "use strict";/**
1
+ /**
2
2
  * 根据 AD_NETWORK 和 AD_PROTOCOL 注入对应的渠道 SDK 脚本插件
3
3
  * 此函数被 webpack.dev.js 和 webpack.build.js 共用
4
- */const{ExitAPIInjectorPlugin}=require("../plugins/ExitAPIInjectorPlugin.js");const{DAPIInjectorPlugin}=require("../plugins/DAPIInjectorPlugin.js");const{PangleInjectorPlugin}=require("../plugins/PangleInjectorPlugin.js");const{TikTokInjectorPlugin}=require("../plugins/TikTokInjectorPlugin.js");const{MintegralInjectorPlugin}=require("../plugins/MintegralInjectorPlugin.js");const{MRAIDInjectorPlugin}=require("../plugins/MRAIDInjectorPlugin.js");const{BigoAdsInjectorPlugin}=require("../plugins/BigoAdsInjectorPlugin.js");const{LiftoffInjectorPlugin}=require("../plugins/LiftoffInjectorPlugin.js");const{SnapchatInjectorPlugin}=require("../plugins/SnapchatInjectorPlugin.js");const{AdikteevInjectorPlugin}=require("../plugins/AdikteevInjectorPlugin.js");const{UnityInjectorPlugin}=require("../plugins/UnityInjectorPlugin.js");/**
4
+ */
5
+
6
+ const { ExitAPIInjectorPlugin } = require('../plugins/ExitAPIInjectorPlugin.js');
7
+ const { DAPIInjectorPlugin } = require('../plugins/DAPIInjectorPlugin.js');
8
+ const { PangleInjectorPlugin } = require('../plugins/PangleInjectorPlugin.js');
9
+ const { TikTokInjectorPlugin } = require('../plugins/TikTokInjectorPlugin.js');
10
+ const { MintegralInjectorPlugin } = require('../plugins/MintegralInjectorPlugin.js');
11
+ const { MRAIDInjectorPlugin } = require('../plugins/MRAIDInjectorPlugin.js');
12
+ const { BigoAdsInjectorPlugin } = require('../plugins/BigoAdsInjectorPlugin.js');
13
+ const { LiftoffInjectorPlugin } = require('../plugins/LiftoffInjectorPlugin.js');
14
+ const { SnapchatInjectorPlugin } = require('../plugins/SnapchatInjectorPlugin.js');
15
+ const { AdikteevInjectorPlugin } = require('../plugins/AdikteevInjectorPlugin.js');
16
+ const { UnityInjectorPlugin } = require('../plugins/UnityInjectorPlugin.js');
17
+
18
+ /**
5
19
  * 注入渠道 SDK 插件到 webpack 配置中
6
20
  * @param {import('webpack').Configuration} webpackConfig - webpack 配置对象
7
21
  * @param {Object} options - 配置选项
8
22
  * @param {AD_NETWORK} options.network - 广告网络
9
23
  * @param {AD_PROTOCOL} options.protocol - 广告协议
10
24
  * @param {string} [options.orientation] - 屏幕方向(google 渠道需要)
11
- */function injectSDKPlugins(webpackConfig,options){const{network:adNetwork,protocol:adProtocol,orientation}=options;// 协议层注入
12
- if("dapi"===adProtocol){webpackConfig.plugins.push(new DAPIInjectorPlugin)}else if("mraid"===adProtocol){// 只有 ironsource 才注入 MRAIDInjectorPlugin
13
- // Unity 使用专门的 UnityInjectorPlugin(在渠道特定注入中处理)
14
- if(adNetwork==="ironsource"){webpackConfig.plugins.push(new MRAIDInjectorPlugin)}}// 渠道特定注入
15
- if(adNetwork==="google"){webpackConfig.plugins.push(new ExitAPIInjectorPlugin(orientation))}else if(adNetwork==="unity"){// Unity Ads 使用专门的 UnityInjectorPlugin
16
- webpackConfig.plugins.push(new UnityInjectorPlugin)}else if(adNetwork==="pangle"){webpackConfig.plugins.push(new PangleInjectorPlugin)}else if(adNetwork==="tiktok"){webpackConfig.plugins.push(new TikTokInjectorPlugin)}else if(adNetwork==="mintegral"){webpackConfig.plugins.push(new MintegralInjectorPlugin)}else if(adNetwork==="bigoads"){// BigoAds 在 dev 模式下注入模拟配置
17
- webpackConfig.plugins.push(new BigoAdsInjectorPlugin)}else if(adNetwork==="liftoff"){webpackConfig.plugins.push(new LiftoffInjectorPlugin)}else if(adNetwork==="snapchat"){// Snapchat 注入 ScPlayableAd SDK
18
- webpackConfig.plugins.push(new SnapchatInjectorPlugin)}else if(adNetwork==="adikteev"){// Adikteev 注入 MRAID mock 和占位符变量
19
- webpackConfig.plugins.push(new AdikteevInjectorPlugin)}}exports.injectSDKPlugins=injectSDKPlugins;
25
+ */
26
+ function injectSDKPlugins(webpackConfig, options) {
27
+ const { network: adNetwork, protocol: adProtocol, orientation } = options;
28
+
29
+ // 协议层注入
30
+ if ('dapi' === adProtocol) {
31
+ webpackConfig.plugins.push(new DAPIInjectorPlugin());
32
+ } else if ('mraid' === adProtocol) {
33
+ // 只有 ironsource 才注入 MRAIDInjectorPlugin
34
+ // Unity 使用专门的 UnityInjectorPlugin(在渠道特定注入中处理)
35
+ if (adNetwork === 'ironsource') {
36
+ webpackConfig.plugins.push(new MRAIDInjectorPlugin());
37
+ }
38
+ }
39
+
40
+ // 渠道特定注入
41
+ if (adNetwork === 'google') {
42
+ webpackConfig.plugins.push(new ExitAPIInjectorPlugin(orientation));
43
+ } else if (adNetwork === 'unity') {
44
+ // Unity Ads 使用专门的 UnityInjectorPlugin
45
+ webpackConfig.plugins.push(new UnityInjectorPlugin());
46
+ } else if (adNetwork === 'pangle') {
47
+ webpackConfig.plugins.push(new PangleInjectorPlugin());
48
+ } else if (adNetwork === 'tiktok') {
49
+ webpackConfig.plugins.push(new TikTokInjectorPlugin());
50
+ } else if (adNetwork === 'mintegral') {
51
+ webpackConfig.plugins.push(new MintegralInjectorPlugin());
52
+ } else if (adNetwork === 'bigoads') {
53
+ // BigoAds 在 dev 模式下注入模拟配置
54
+ webpackConfig.plugins.push(new BigoAdsInjectorPlugin());
55
+ } else if (adNetwork === 'liftoff') {
56
+ webpackConfig.plugins.push(new LiftoffInjectorPlugin());
57
+ } else if (adNetwork === 'snapchat') {
58
+ // Snapchat 注入 ScPlayableAd SDK
59
+ webpackConfig.plugins.push(new SnapchatInjectorPlugin());
60
+ } else if (adNetwork === 'adikteev') {
61
+ // Adikteev 注入 MRAID mock 和占位符变量
62
+ webpackConfig.plugins.push(new AdikteevInjectorPlugin());
63
+ }
64
+ }
65
+
66
+ exports.injectSDKPlugins = injectSDKPlugins;
@@ -1,5 +1,38 @@
1
- "use strict";var prettyjson=require("prettyjson");/**
1
+ var prettyjson = require('prettyjson');
2
+
3
+ /**
2
4
  * Log provided options into console output
3
- */exports.logOptions=function logOptions(options){let logOptions={mode:process.env.NODE_ENV,...options};let isProduction=false;if(process.env.NODE_ENV==="production"){isProduction=true;delete logOptions.port;delete logOptions.open}else if(process.env.NODE_ENV==="development"){delete logOptions.outDir}if(Object.keys(logOptions.compilation).length===0||!isProduction)delete logOptions.compilation;if(Object.keys(logOptions.adNetworkNames).length===0||!isProduction)delete logOptions.adNetworkNames;if(Object.keys(logOptions.defines).length===0)delete logOptions.defines;if(logOptions.zip===false||!isProduction)delete logOptions.zip;if(logOptions.tsConfig==="tsconfig.json")delete logOptions.tsConfig;if(logOptions.jsConfig==="jsconfig.json")delete logOptions.jsConfig;if(logOptions.buildConfig==="build.json")delete logOptions.buildConfig;if(logOptions.language==="en")delete logOptions.language;if(logOptions.orientation==="both")delete logOptions.orientation;// 在 dev 模式下,如果 network 为默认值 preview 则不显示
4
- if(!isProduction&&logOptions.network==="preview")delete logOptions.network;// dev 模式下,如果 protocol 为默认值 none 则不显示
5
- if(!isProduction&&logOptions.protocol==="none")delete logOptions.protocol;delete logOptions.filename;delete logOptions.app;delete logOptions.name;delete logOptions.googlePlayUrl;delete logOptions.appStoreUrl;if(logOptions.obfuscateLevel===2)delete logOptions.obfuscateLevel;console.log(prettyjson.render(logOptions,{},2))};
5
+ */
6
+ exports.logOptions = function logOptions(options) {
7
+ let logOptions = { mode: process.env.NODE_ENV, ...options };
8
+ let isProduction = false;
9
+ if (process.env.NODE_ENV === 'production') {
10
+ isProduction = true;
11
+ delete logOptions.port;
12
+ delete logOptions.open;
13
+ } else if (process.env.NODE_ENV === 'development') {
14
+ delete logOptions.outDir;
15
+ }
16
+
17
+ if (Object.keys(logOptions.compilation).length === 0 || !isProduction) delete logOptions.compilation;
18
+ if (Object.keys(logOptions.adNetworkNames).length === 0 || !isProduction) delete logOptions.adNetworkNames;
19
+ if (Object.keys(logOptions.defines).length === 0) delete logOptions.defines;
20
+ if (logOptions.zip === false || !isProduction) delete logOptions.zip;
21
+ if (logOptions.tsConfig === 'tsconfig.json') delete logOptions.tsConfig;
22
+ if (logOptions.jsConfig === 'jsconfig.json') delete logOptions.jsConfig;
23
+ if (logOptions.buildConfig === 'build.json') delete logOptions.buildConfig;
24
+ if (logOptions.language === 'en') delete logOptions.language;
25
+ if (logOptions.orientation === 'both') delete logOptions.orientation;
26
+ // 在 dev 模式下,如果 network 为默认值 preview 则不显示
27
+ if (!isProduction && logOptions.network === 'preview') delete logOptions.network;
28
+ // 在 dev 模式下,如果 protocol 为默认值 none 则不显示
29
+ if (!isProduction && logOptions.protocol === 'none') delete logOptions.protocol;
30
+ delete logOptions.filename;
31
+ delete logOptions.app;
32
+ delete logOptions.name;
33
+ delete logOptions.googlePlayUrl;
34
+ delete logOptions.appStoreUrl;
35
+ if (logOptions.obfuscateLevel === 2) delete logOptions.obfuscateLevel;
36
+
37
+ console.log(prettyjson.render(logOptions, {}, 2));
38
+ };
@@ -1,6 +1,23 @@
1
- "use strict";/**
1
+ /**
2
2
  * Merges two option objects with deep merge for 'defines' and 'build' properties
3
3
  * @param {import('../index').CLIOptions} options1 - First options object
4
4
  * @param {Partial<import('../index').CLIOptions>} [options2] - Second options object
5
5
  * @returns {import('../index').CLIOptions} Merged options object with combined defines and build properties
6
- */exports.mergeOptions=function mergeOptions(options1,options2){if(!options1)return{};if(!options2)return options1;const defines=Object.assign({},options1.defines,options2.defines);const build=Object.assign({},options1.build,options2.build);const compilation=Object.assign({},options1.compilation,options2.compilation);const adNetworkNames=Object.assign({},options1.adNetworkNames,options2.adNetworkNames);const options=Object.assign({},options1,options2);options.defines=defines;options.compilation=compilation;options.adNetworkNames=adNetworkNames;options.build=build;return options};
6
+ */
7
+ exports.mergeOptions = function mergeOptions(options1, options2) {
8
+ if (!options1) return {};
9
+ if (!options2) return options1;
10
+
11
+ const defines = Object.assign({}, options1.defines, options2.defines);
12
+ const build = Object.assign({}, options1.build, options2.build);
13
+ const compilation = Object.assign({}, options1.compilation, options2.compilation);
14
+ const adNetworkNames = Object.assign({}, options1.adNetworkNames, options2.adNetworkNames);
15
+ const options = Object.assign({}, options1, options2);
16
+
17
+ options.defines = defines;
18
+ options.compilation = compilation;
19
+ options.adNetworkNames = adNetworkNames;
20
+ options.build = build;
21
+
22
+ return options;
23
+ };