@playcraft/devkit 1.0.1-8.beta.2 → 1.0.1-8.beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +259 -0
- package/cli/bin/playable-scripts.js +44 -1
- package/cli/commands/build.js +130 -18
- package/cli/commands/builds.js +13 -2
- package/cli/commands/dev.js +7 -1
- package/cli/commands/pack.js +308 -39
- package/cli/commands/vite-dev.js +7 -1
- package/core/batch-build.js +1091 -125
- package/core/cos-uploader.js +264 -20
- package/core/index.js +71 -6
- package/core/loaders/gltf-loader.js +74 -28
- package/core/options.js +310 -15
- package/core/plugins/AdikteevInjectorPlugin.js +26 -4
- package/core/plugins/BigoAdsInjectorPlugin.js +21 -4
- package/core/plugins/DAPIInjectorPlugin.js +25 -2
- package/core/plugins/DebuggerInjectionPlugin.js +31 -3
- package/core/plugins/ExitAPIInjectorPlugin.js +57 -3
- package/core/plugins/FflateCompressionPlugin.js +225 -37
- package/core/plugins/FontInjectionWebpackPlugin.js +46 -4
- package/core/plugins/LiftoffInjectorPlugin.js +26 -4
- package/core/plugins/MRAIDInjectorPlugin.js +24 -2
- package/core/plugins/MintegralInjectorPlugin.js +25 -2
- package/core/plugins/PangleInjectorPlugin.js +24 -2
- package/core/plugins/SnapchatInjectorPlugin.js +26 -4
- package/core/plugins/TikTokInjectorPlugin.js +24 -2
- package/core/plugins/UnityInjectorPlugin.js +37 -8
- package/core/plugins/ZipPlugin.js +138 -19
- package/core/utils/buildDefines.js +29 -2
- package/core/utils/buildTemplateString.js +40 -5
- package/core/utils/date.js +16 -1
- package/core/utils/fontRegistry.js +81 -13
- package/core/utils/fontResolver.js +335 -37
- package/core/utils/generateAdikteevHtmlWebpackPluginConfig.js +42 -7
- package/core/utils/generateBigabidHtmlWebpackPluginConfig.js +30 -3
- package/core/utils/generateInMobiHtmlWebpackPluginConfig.js +43 -7
- package/core/utils/injectSDKPlugins.js +58 -11
- package/core/utils/logOptions.js +37 -4
- package/core/utils/mergeOptions.js +19 -2
- package/core/utils/parseArgvOptions.js +110 -5
- package/core/utils/resolveChannelFold2Zip.js +15 -3
- package/core/utils/validateThemeData.js +220 -25
- package/core/validators/pre-build-checker.js +201 -21
- package/core/validators/tracking-validator.js +358 -40
- package/core/vite.dev.js +319 -24
- package/core/webpack.build.js +489 -56
- package/core/webpack.common.js +177 -11
- package/core/webpack.dev.js +82 -5
- package/package.json +3 -2
|
@@ -1,19 +1,138 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
1
|
+
const { options } = require('../options');
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* Build's default defines list for dev & prod builds
|
|
3
|
-
*/
|
|
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
|
-
|
|
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
|
-
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
};
|
package/core/utils/date.js
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
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,6 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* 平台预置字体字典。
|
|
3
5
|
*
|
|
4
6
|
* 所有字体文件内置于 playable-scripts/core/resources/fonts/ 目录下。
|
|
@@ -9,21 +11,87 @@
|
|
|
9
11
|
* - builtin=false: 需要从本地字体文件读取并 base64 内联到产物中
|
|
10
12
|
*
|
|
11
13
|
* key 命名规范:与 theme.schema.json5 中 fontFamily 的 enum 值一一对应。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
/** 字体资源目录(随 npm 包发布) */
|
|
19
|
+
const FONTS_DIR = path.join(__dirname, '..', 'resources', 'fonts');
|
|
20
|
+
|
|
21
|
+
const FONT_REGISTRY = {
|
|
22
|
+
// ===== 系统自带字体(无需注入) =====
|
|
23
|
+
system: {
|
|
24
|
+
builtin: true,
|
|
25
|
+
fallback: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
26
|
+
},
|
|
27
|
+
Arial: {
|
|
28
|
+
builtin: true,
|
|
29
|
+
fallback: 'Arial, Helvetica, sans-serif',
|
|
30
|
+
},
|
|
31
|
+
monaco: {
|
|
32
|
+
builtin: true,
|
|
33
|
+
fallback: 'Monaco, "Courier New", monospace',
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// ===== 平台内置字体(base64 内联注入) =====
|
|
37
|
+
// key 必须与 theme.schema.json5 fontFamily.enum 值完全一致
|
|
38
|
+
|
|
39
|
+
// Google Fonts 字体:单文件 woff2
|
|
40
|
+
Poppins: {
|
|
41
|
+
builtin: false,
|
|
42
|
+
fontFileName: 'poppins-regular.woff2',
|
|
43
|
+
cssFamily: 'Poppins, sans-serif',
|
|
44
|
+
},
|
|
45
|
+
'Noto Sans SC': {
|
|
46
|
+
builtin: false,
|
|
47
|
+
fontFileName: 'noto-sans-sc-regular.woff2',
|
|
48
|
+
cssFamily: '"Noto Sans SC", sans-serif',
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// 本地 ttf 种子字体(单文件 woff2,由 download-fonts.js 用 pyftsubset 生成)
|
|
52
|
+
'Pangmen Zhengdao': {
|
|
53
|
+
builtin: false,
|
|
54
|
+
fontFileName: 'pangmen-regular.woff2', // 由 pangmen-biaoti.ttf subset 生成
|
|
55
|
+
cssFamily: '"Pangmen Zhengdao", sans-serif',
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// ZeoSeven 字体:unicode-range 子集(build 时按需筛选)
|
|
59
|
+
'Maoken Zhuyuan Ti': {
|
|
60
|
+
builtin: false,
|
|
61
|
+
subsetsDir: 'maoken',
|
|
62
|
+
fontFileName: 'maoken-regular.woff2',
|
|
63
|
+
cssFamily: '"Maoken Zhuyuan Ti", sans-serif',
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
20
68
|
* @returns {string[]} 所有已注册的字体 key 列表
|
|
21
|
-
*/
|
|
69
|
+
*/
|
|
70
|
+
function getRegisteredFontKeys() {
|
|
71
|
+
return Object.keys(FONT_REGISTRY);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
22
75
|
* @param {string} key - 字体 key
|
|
23
76
|
* @returns {boolean} 是否为系统自带字体
|
|
24
|
-
*/
|
|
77
|
+
*/
|
|
78
|
+
function isBuiltin(key) {
|
|
79
|
+
return !!(FONT_REGISTRY[key] && FONT_REGISTRY[key].builtin);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
25
83
|
* @param {string} key - 字体 key
|
|
26
84
|
* @returns {object|null} 字体配置对象,未注册返回 null
|
|
27
|
-
*/
|
|
85
|
+
*/
|
|
86
|
+
function getFontEntry(key) {
|
|
87
|
+
return FONT_REGISTRY[key] || null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
28
91
|
* @returns {string} 字体资源目录绝对路径
|
|
29
|
-
*/
|
|
92
|
+
*/
|
|
93
|
+
function getFontsDir() {
|
|
94
|
+
return FONTS_DIR;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { FONT_REGISTRY, getRegisteredFontKeys, isBuiltin, getFontEntry, getFontsDir };
|