@playcraft/devkit 1.0.18 → 1.0.20
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 +1 -44
- package/cli/commands/build.js +18 -130
- package/cli/commands/builds.js +2 -13
- package/cli/commands/dev.js +1 -7
- package/cli/commands/pack.js +39 -308
- package/cli/commands/vite-dev.js +1 -7
- package/core/batch-build.js +125 -1091
- package/core/constants.js +6 -0
- package/core/cos-uploader.js +20 -264
- package/core/index.d.ts +9 -0
- package/core/index.js +7 -71
- package/core/loaders/gltf-loader.js +28 -74
- package/core/options.js +15 -310
- 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 -57
- package/core/plugins/FflateCompressionPlugin.js +37 -225
- 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/SnapchatInjectorPlugin.js +4 -26
- package/core/plugins/TikTokInjectorPlugin.js +2 -24
- package/core/plugins/UnityInjectorPlugin.js +8 -37
- package/core/plugins/ZipPlugin.js +19 -138
- package/core/utils/buildDefines.js +2 -29
- package/core/utils/buildTemplateString.js +5 -40
- 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/injectSDKPlugins.js +11 -58
- package/core/utils/logOptions.js +4 -37
- package/core/utils/mergeOptions.js +2 -19
- package/core/utils/parseArgvOptions.js +5 -110
- package/core/utils/resolveChannelFold2Zip.js +3 -15
- package/core/utils/validateThemeData.js +25 -220
- package/core/validators/pre-build-checker.js +21 -201
- package/core/validators/tracking-validator.js +40 -358
- package/core/vite.dev.js +17 -326
- package/core/webpack.build.js +56 -489
- package/core/webpack.common.js +11 -177
- package/core/webpack.dev.js +5 -82
- package/package.json +2 -3
- package/README.md +0 -259
|
@@ -1,66 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";/**
|
|
2
2
|
* 根据 AD_NETWORK 和 AD_PROTOCOL 注入对应的渠道 SDK 脚本插件
|
|
3
3
|
* 此函数被 webpack.dev.js 和 webpack.build.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
|
-
/**
|
|
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");/**
|
|
19
5
|
* 注入渠道 SDK 插件到 webpack 配置中
|
|
20
6
|
* @param {import('webpack').Configuration} webpackConfig - webpack 配置对象
|
|
21
7
|
* @param {Object} options - 配置选项
|
|
22
8
|
* @param {AD_NETWORK} options.network - 广告网络
|
|
23
9
|
* @param {AD_PROTOCOL} options.protocol - 广告协议
|
|
24
10
|
* @param {string} [options.orientation] - 屏幕方向(google 渠道需要)
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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;
|
|
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;
|
package/core/utils/logOptions.js
CHANGED
|
@@ -1,38 +1,5 @@
|
|
|
1
|
-
var prettyjson
|
|
2
|
-
|
|
3
|
-
/**
|
|
1
|
+
"use strict";var prettyjson=require("prettyjson");/**
|
|
4
2
|
* Log provided options into console output
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
};
|
|
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))};
|
|
@@ -1,23 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";/**
|
|
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
|
-
*/
|
|
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
|
-
};
|
|
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};
|
|
@@ -1,113 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
const allowedAdNetworks = [
|
|
3
|
-
'preview',
|
|
4
|
-
'applovin',
|
|
5
|
-
'unity',
|
|
6
|
-
'google',
|
|
7
|
-
'ironsource',
|
|
8
|
-
'facebook',
|
|
9
|
-
'moloco',
|
|
10
|
-
'adcolony',
|
|
11
|
-
'mintegral',
|
|
12
|
-
'vungle',
|
|
13
|
-
'tapjoy',
|
|
14
|
-
'snapchat',
|
|
15
|
-
'tiktok',
|
|
16
|
-
'appreciate',
|
|
17
|
-
'chartboost',
|
|
18
|
-
'pangle',
|
|
19
|
-
'mytarget',
|
|
20
|
-
'liftoff',
|
|
21
|
-
'smadex',
|
|
22
|
-
'adikteev',
|
|
23
|
-
'bigabid',
|
|
24
|
-
'inmobi',
|
|
25
|
-
'bigoads'
|
|
26
|
-
];
|
|
27
|
-
/** @type {AD_PROTOCOL[]} List of supported advertising protocols */
|
|
28
|
-
const allowedAdProtocols = ['none', 'mraid', 'dapi'];
|
|
29
|
-
|
|
30
|
-
/** @type {AD_NETWORK[]} List of advertising partners that support MRAID protocol */
|
|
31
|
-
const mraidPartners = [
|
|
32
|
-
'ironsource',
|
|
33
|
-
'applovin',
|
|
34
|
-
'unity',
|
|
35
|
-
'appreciate',
|
|
36
|
-
'chartboost',
|
|
37
|
-
'mytarget',
|
|
38
|
-
'liftoff',
|
|
39
|
-
'adcolony',
|
|
40
|
-
'adikteev',
|
|
41
|
-
'bigabid',
|
|
42
|
-
'inmobi'
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
/** @type {LANGUAGE[]} */
|
|
46
|
-
const allowedLanguages = ['auto', 'en', 'es', 'zh', 'hi', 'ar', 'fr', 'de', 'ja', 'pt'];
|
|
47
|
-
|
|
48
|
-
/** @type {ORIENTATION[]} */
|
|
49
|
-
const allowedOrientations = ['both', 'portrait', 'landscape', 'square'];
|
|
50
|
-
|
|
51
|
-
/**
|
|
1
|
+
"use strict";/** @type {string[]} List of supported advertising networks */const allowedAdNetworks=["preview","applovin","unity","google","ironsource","facebook","moloco","adcolony","mintegral","vungle","tapjoy","snapchat","tiktok","appreciate","chartboost","pangle","mytarget","liftoff","smadex","adikteev","bigabid","inmobi","bigoads"];/** @type {AD_PROTOCOL[]} List of supported advertising protocols */const allowedAdProtocols=["none","mraid","dapi"];/** @type {AD_NETWORK[]} List of advertising partners that support MRAID protocol */const mraidPartners=["ironsource","applovin","unity","appreciate","chartboost","mytarget","liftoff","adcolony","adikteev","bigabid","inmobi"];/** @type {LANGUAGE[]} */const allowedLanguages=["auto","en","es","zh","hi","ar","fr","de","ja","pt"];/** @type {ORIENTATION[]} */const allowedOrientations=["both","portrait","landscape","square"];/**
|
|
52
2
|
* Parses command line arguments based on provided options configuration
|
|
53
3
|
* @param {import('../index').CLIOptionConfig[]} posiibleOptions - Array of possible options to parse
|
|
54
4
|
* @returns {Record<string, any>} Parsed options object with values from command line arguments
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const cliExplicitKeys = new Set();
|
|
60
|
-
|
|
61
|
-
for (let possibleOption of posiibleOptions) {
|
|
62
|
-
if (possibleOption.defaultValue !== undefined)
|
|
63
|
-
argvOptions[possibleOption.alias || possibleOption.name] = possibleOption.defaultValue;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
let adNetwork = null;
|
|
67
|
-
|
|
68
|
-
for (let i = 2; i < process.argv.length; i++) {
|
|
69
|
-
let key = process.argv[i];
|
|
70
|
-
let inlineValue = null;
|
|
71
|
-
|
|
72
|
-
// 支持 --key=value 格式
|
|
73
|
-
if (key.includes('=')) {
|
|
74
|
-
const eqIndex = key.indexOf('=');
|
|
75
|
-
inlineValue = key.slice(eqIndex + 1);
|
|
76
|
-
key = key.slice(0, eqIndex);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const allowedOption = posiibleOptions.find((e) => '--' + e.name === key);
|
|
80
|
-
|
|
81
|
-
if (allowedOption) {
|
|
82
|
-
let value = true;
|
|
83
|
-
if (allowedOption.hasValue) {
|
|
84
|
-
// 优先使用内联值(--key=value),否则使用下一个参数
|
|
85
|
-
value = inlineValue !== null ? inlineValue : process.argv[++i];
|
|
86
|
-
if (allowedOption.parser) value = allowedOption.parser(value);
|
|
87
|
-
}
|
|
88
|
-
const optionKey = allowedOption.alias || allowedOption.name;
|
|
89
|
-
argvOptions[optionKey] = value;
|
|
90
|
-
cliExplicitKeys.add(optionKey);
|
|
91
|
-
} else if (!adNetwork) adNetwork = key;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (posiibleOptions.find((e) => e.name === 'network')) {
|
|
95
|
-
if (adNetwork && allowedAdNetworks.includes(adNetwork)) {
|
|
96
|
-
argvOptions['network'] = adNetwork;
|
|
97
|
-
}
|
|
98
|
-
if (posiibleOptions.find((e) => e.name === 'protocol')) {
|
|
99
|
-
if (mraidPartners.includes(argvOptions['network']) && argvOptions['protocol'] === 'none') {
|
|
100
|
-
argvOptions['protocol'] = 'mraid';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
argvOptions._cliExplicitKeys = cliExplicitKeys;
|
|
106
|
-
return argvOptions;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
exports.allowedAdNetworks = allowedAdNetworks;
|
|
110
|
-
exports.allowedAdProtocols = allowedAdProtocols;
|
|
111
|
-
exports.mraidPartners = mraidPartners;
|
|
112
|
-
exports.allowedLanguages = allowedLanguages;
|
|
113
|
-
exports.allowedOrientations = allowedOrientations;
|
|
5
|
+
*/exports.parseArgvOptions=function parseArgvOptions(posiibleOptions){const argvOptions={};// Track which options were explicitly set via CLI args
|
|
6
|
+
const cliExplicitKeys=new Set;for(let possibleOption of posiibleOptions){if(possibleOption.defaultValue!==undefined)argvOptions[possibleOption.alias||possibleOption.name]=possibleOption.defaultValue}let adNetwork=null;for(let i=2;i<process.argv.length;i++){let key=process.argv[i];let inlineValue=null;// 支持 --key=value 格式
|
|
7
|
+
if(key.includes("=")){const eqIndex=key.indexOf("=");inlineValue=key.slice(eqIndex+1);key=key.slice(0,eqIndex)}const allowedOption=posiibleOptions.find(e=>"--"+e.name===key);if(allowedOption){let value=true;if(allowedOption.hasValue){// 优先使用内联值(--key=value),否则使用下一个参数
|
|
8
|
+
value=inlineValue!==null?inlineValue:process.argv[++i];if(allowedOption.parser)value=allowedOption.parser(value)}const optionKey=allowedOption.alias||allowedOption.name;argvOptions[optionKey]=value;cliExplicitKeys.add(optionKey)}else if(!adNetwork)adNetwork=key}if(posiibleOptions.find(e=>e.name==="network")){if(adNetwork&&allowedAdNetworks.includes(adNetwork)){argvOptions["network"]=adNetwork}if(posiibleOptions.find(e=>e.name==="protocol")){if(mraidPartners.includes(argvOptions["network"])&&argvOptions["protocol"]==="none"){argvOptions["protocol"]="mraid"}}}argvOptions._cliExplicitKeys=cliExplicitKeys;return argvOptions};exports.allowedAdNetworks=allowedAdNetworks;exports.allowedAdProtocols=allowedAdProtocols;exports.mraidPartners=mraidPartners;exports.allowedLanguages=allowedLanguages;exports.allowedOrientations=allowedOrientations;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";/**
|
|
2
2
|
* 解析 isChannelFold2Zip 相关标志
|
|
3
3
|
*
|
|
4
4
|
* isChannelFold2Zip:所有渠道(排除 preview)输出 dist/渠道名称.zip
|
|
@@ -10,17 +10,5 @@
|
|
|
10
10
|
* @param {string} params.adNetwork - 当前渠道名称
|
|
11
11
|
* @param {boolean} params.isZipOutput - 当前渠道是否本身就走 ZIP 输出流程
|
|
12
12
|
* @returns {{ isChannelFold2Zip: boolean, isZipOutputFinal: boolean }}
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const isChannelFold2Zip = !!(isChannelFold2ZipOption && adNetwork !== 'preview');
|
|
16
|
-
// 非 ZIP 渠道启用 isChannelFold2Zip 时,需要强制走 ZIP 输出流程
|
|
17
|
-
const isZipOutputFinal = isZipOutput || isChannelFold2Zip;
|
|
18
|
-
|
|
19
|
-
if (isChannelFold2Zip) {
|
|
20
|
-
console.log(`✅ isChannelFold2Zip enabled: output will be dist/${adNetwork}.zip`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return { isChannelFold2Zip, isZipOutputFinal };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
exports.resolveChannelFold2Zip = resolveChannelFold2Zip;
|
|
13
|
+
*/function resolveChannelFold2Zip({isChannelFold2ZipOption,adNetwork,isZipOutput}){const isChannelFold2Zip=!!(isChannelFold2ZipOption&&adNetwork!=="preview");// 非 ZIP 渠道启用 isChannelFold2Zip 时,需要强制走 ZIP 输出流程
|
|
14
|
+
const isZipOutputFinal=isZipOutput||isChannelFold2Zip;if(isChannelFold2Zip){console.log(`✅ isChannelFold2Zip enabled: output will be dist/${adNetwork}.zip`)}return{isChannelFold2Zip,isZipOutputFinal}}exports.resolveChannelFold2Zip=resolveChannelFold2Zip;
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const JSON5 = require('json5');
|
|
6
|
-
const Ajv = require('ajv');
|
|
7
|
-
|
|
8
|
-
const SCHEMA_BACKUP_SUFFIX = '.backup';
|
|
9
|
-
const SCHEMA_BACKUP_COMMENT =
|
|
10
|
-
'// [自动生成的备份] 此文件由打包流程创建。\n' +
|
|
11
|
-
'// 如果打包中断,可以移除 ".backup" 后缀,\n' +
|
|
12
|
-
'// 并用此文件替换原始的 "theme.schema.json5" 来恢复。\n';
|
|
13
|
-
|
|
14
|
-
/**
|
|
1
|
+
"use strict";const fs=require("fs");const path=require("path");const JSON5=require("json5");const Ajv=require("ajv");const SCHEMA_BACKUP_SUFFIX=".backup";const SCHEMA_BACKUP_COMMENT="// [\u81EA\u52A8\u751F\u6210\u7684\u5907\u4EFD] \u6B64\u6587\u4EF6\u7531\u6253\u5305\u6D41\u7A0B\u521B\u5EFA\u3002\n"+"// \u5982\u679C\u6253\u5305\u4E2D\u65AD\uFF0C\u53EF\u4EE5\u79FB\u9664 \".backup\" \u540E\u7F00\uFF0C\n"+"// \u5E76\u7528\u6B64\u6587\u4EF6\u66FF\u6362\u539F\u59CB\u7684 \"theme.schema.json5\" \u6765\u6062\u590D\u3002\n";/**
|
|
15
2
|
* 递归提取 JSON Schema 定义中的 `default` 值。
|
|
16
3
|
*
|
|
17
4
|
* - 对于 "object" 类型:递归遍历 `properties` 并构建嵌套对象。
|
|
@@ -19,42 +6,15 @@ const SCHEMA_BACKUP_COMMENT =
|
|
|
19
6
|
*
|
|
20
7
|
* @param {object} schema - JSON Schema(或子 schema)对象
|
|
21
8
|
* @returns {*} 提取的默认值树,如果没有则返回 undefined
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
if (!schema || typeof schema !== 'object') return undefined;
|
|
25
|
-
|
|
26
|
-
if (schema.type === 'object' && schema.properties) {
|
|
27
|
-
const result = {};
|
|
28
|
-
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
29
|
-
const val = extractDefaultsFromSchema(propSchema);
|
|
30
|
-
if (val !== undefined) {
|
|
31
|
-
result[key] = val;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return Object.keys(result).length > 0 ? result : undefined;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// 基本类型或数组类型,带有默认值
|
|
38
|
-
if ('default' in schema) {
|
|
39
|
-
return schema.default;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
9
|
+
*/function extractDefaultsFromSchema(schema){if(!schema||typeof schema!=="object")return undefined;if(schema.type==="object"&&schema.properties){const result={};for(const[key,propSchema]of Object.entries(schema.properties)){const val=extractDefaultsFromSchema(propSchema);if(val!==undefined){result[key]=val}}return Object.keys(result).length>0?result:undefined}// 基本类型或数组类型,带有默认值
|
|
10
|
+
if("default"in schema){return schema.default}return undefined}/**
|
|
46
11
|
* Check whether the parsed content of theme.schema.json5 is a real JSON Schema
|
|
47
12
|
* (has "type" and "properties" fields) or has already been overwritten with
|
|
48
13
|
* plain merged data (e.g. due to a previous interrupted build).
|
|
49
14
|
*
|
|
50
15
|
* @param {object} obj - Parsed content of theme.schema.json5
|
|
51
16
|
* @returns {boolean} true if it looks like a valid JSON Schema definition
|
|
52
|
-
*/
|
|
53
|
-
function isJsonSchema(obj) {
|
|
54
|
-
return obj && typeof obj === 'object' && obj.type === 'object' && typeof obj.properties === 'object';
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
17
|
+
*/function isJsonSchema(obj){return obj&&typeof obj==="object"&&obj.type==="object"&&typeof obj.properties==="object"}/**
|
|
58
18
|
* 根据 schema 校验指定主题的主题数据。
|
|
59
19
|
*
|
|
60
20
|
* 项目中预期的文件结构:
|
|
@@ -75,144 +35,39 @@ function isJsonSchema(obj) {
|
|
|
75
35
|
* mergedData – schema 默认值与主题数据合并后的完整数据
|
|
76
36
|
* isPlainData – 如果 schema 文件已经是纯数据(非 schema 格式)则为 true
|
|
77
37
|
* errors – 校验通过为 null,否则为错误信息数组
|
|
78
|
-
*/
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// --- 读取并解析文件 ---
|
|
88
|
-
if (!fs.existsSync(schemaPath)) {
|
|
89
|
-
return {
|
|
90
|
-
defaultData: null,
|
|
91
|
-
mergedData: null,
|
|
92
|
-
isPlainData: false,
|
|
93
|
-
errors: [`未找到 Schema 文件: ${schemaPath}`],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const schemaContent = JSON5.parse(fs.readFileSync(schemaPath, 'utf8'));
|
|
98
|
-
|
|
99
|
-
// Safety check: if the file is already plain data (not a JSON Schema),
|
|
100
|
-
// it was likely left over from a previous interrupted build.
|
|
101
|
-
// Use it directly without validation or overwriting.
|
|
102
|
-
if (!isJsonSchema(schemaContent)) {
|
|
103
|
-
return {
|
|
104
|
-
defaultData: schemaContent,
|
|
105
|
-
mergedData: schemaContent,
|
|
106
|
-
isPlainData: true,
|
|
107
|
-
errors: null,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 从 schema 提取默认值
|
|
112
|
-
const defaultData = extractDefaultsFromSchema(schemaContent) || {};
|
|
113
|
-
|
|
114
|
-
let themeData = {};
|
|
115
|
-
if (fs.existsSync(dataPath)) {
|
|
116
|
-
themeData = JSON5.parse(fs.readFileSync(dataPath, 'utf8'));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// 深度合并(主题数据覆盖 schema 默认值),然后校验
|
|
120
|
-
const merged = deepMerge(defaultData, themeData);
|
|
121
|
-
|
|
122
|
-
// --- 根据 schema 校验合并后的数据 ---
|
|
123
|
-
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
124
|
-
const validate = ajv.compile(schemaContent);
|
|
125
|
-
const valid = validate(merged);
|
|
126
|
-
|
|
127
|
-
if (!valid) {
|
|
128
|
-
const errors = validate.errors.map(err => {
|
|
129
|
-
const field = err.instancePath || '(root)';
|
|
130
|
-
return `${field} ${err.message}`;
|
|
131
|
-
});
|
|
132
|
-
return { defaultData, mergedData: merged, isPlainData: false, errors };
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return { defaultData, mergedData: merged, isPlainData: false, errors: null };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
38
|
+
*/function validateThemeData(themeName,projectRoot,themeConfig){const sourceDir=themeConfig&&themeConfig.sourceDir||"src/theme";const themeBaseDir=path.join(projectRoot,sourceDir);// --- 解析文件路径 ---
|
|
39
|
+
const schemaPath=path.join(themeBaseDir,"theme.schema.json5");const dataPath=path.join(themeBaseDir,themeName,"theme.data.json5");// --- 读取并解析文件 ---
|
|
40
|
+
if(!fs.existsSync(schemaPath)){return{defaultData:null,mergedData:null,isPlainData:false,errors:[`未找到 Schema 文件: ${schemaPath}`]}}const schemaContent=JSON5.parse(fs.readFileSync(schemaPath,"utf8"));// Safety check: if the file is already plain data (not a JSON Schema),
|
|
41
|
+
// it was likely left over from a previous interrupted build.
|
|
42
|
+
// Use it directly without validation or overwriting.
|
|
43
|
+
if(!isJsonSchema(schemaContent)){return{defaultData:schemaContent,mergedData:schemaContent,isPlainData:true,errors:null}}// 从 schema 提取默认值
|
|
44
|
+
const defaultData=extractDefaultsFromSchema(schemaContent)||{};let themeData={};if(fs.existsSync(dataPath)){themeData=JSON5.parse(fs.readFileSync(dataPath,"utf8"))}// 深度合并(主题数据覆盖 schema 默认值),然后校验
|
|
45
|
+
const merged=deepMerge(defaultData,themeData);// --- 根据 schema 校验合并后的数据 ---
|
|
46
|
+
const ajv=new Ajv({allErrors:true,strict:false});const validate=ajv.compile(schemaContent);const valid=validate(merged);if(!valid){const errors=validate.errors.map(err=>{const field=err.instancePath||"(root)";return`${field} ${err.message}`});return{defaultData,mergedData:merged,isPlainData:false,errors}}return{defaultData,mergedData:merged,isPlainData:false,errors:null}}/**
|
|
139
47
|
* 深度合并两个普通对象。`override` 中的值优先。
|
|
140
48
|
* 数组会被整体替换(不会拼接)。
|
|
141
49
|
*
|
|
142
50
|
* @param {object} base
|
|
143
51
|
* @param {object} override
|
|
144
52
|
* @returns {object}
|
|
145
|
-
*/
|
|
146
|
-
function deepMerge(base, override) {
|
|
147
|
-
const result = { ...base };
|
|
148
|
-
|
|
149
|
-
for (const key of Object.keys(override)) {
|
|
150
|
-
const baseVal = base[key];
|
|
151
|
-
const overVal = override[key];
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
overVal !== null &&
|
|
155
|
-
typeof overVal === 'object' &&
|
|
156
|
-
!Array.isArray(overVal) &&
|
|
157
|
-
baseVal !== null &&
|
|
158
|
-
typeof baseVal === 'object' &&
|
|
159
|
-
!Array.isArray(baseVal)
|
|
160
|
-
) {
|
|
161
|
-
result[key] = deepMerge(baseVal, overVal);
|
|
162
|
-
} else {
|
|
163
|
-
result[key] = overVal;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return result;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
53
|
+
*/function deepMerge(base,override){const result={...base};for(const key of Object.keys(override)){const baseVal=base[key];const overVal=override[key];if(overVal!==null&&typeof overVal==="object"&&!Array.isArray(overVal)&&baseVal!==null&&typeof baseVal==="object"&&!Array.isArray(baseVal)){result[key]=deepMerge(baseVal,overVal)}else{result[key]=overVal}}return result}/**
|
|
171
54
|
* 备份原始的 theme.schema.json5 文件。
|
|
172
55
|
* 备份文件包含注释头,说明如何手动恢复。
|
|
173
56
|
*
|
|
174
57
|
* @param {string} schemaPath - theme.schema.json5 的绝对路径
|
|
175
58
|
* @returns {string} 备份文件路径
|
|
176
|
-
*/
|
|
177
|
-
function backupSchema(schemaPath) {
|
|
178
|
-
const backupPath = schemaPath + SCHEMA_BACKUP_SUFFIX;
|
|
179
|
-
const originalContent = fs.readFileSync(schemaPath, 'utf8');
|
|
180
|
-
fs.writeFileSync(backupPath, SCHEMA_BACKUP_COMMENT + originalContent, 'utf8');
|
|
181
|
-
return backupPath;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
59
|
+
*/function backupSchema(schemaPath){const backupPath=schemaPath+SCHEMA_BACKUP_SUFFIX;const originalContent=fs.readFileSync(schemaPath,"utf8");fs.writeFileSync(backupPath,SCHEMA_BACKUP_COMMENT+originalContent,"utf8");return backupPath}/**
|
|
185
60
|
* 从备份文件恢复 theme.schema.json5 并删除备份。
|
|
186
61
|
*
|
|
187
62
|
* @param {string} schemaPath - theme.schema.json5 的绝对路径
|
|
188
|
-
*/
|
|
189
|
-
|
|
190
|
-
const backupPath = schemaPath + SCHEMA_BACKUP_SUFFIX;
|
|
191
|
-
if (!fs.existsSync(backupPath)) return;
|
|
192
|
-
|
|
193
|
-
const backupContent = fs.readFileSync(backupPath, 'utf8');
|
|
194
|
-
// 去除自动生成的注释头(以 "//" 开头的行)
|
|
195
|
-
const lines = backupContent.split('\n');
|
|
196
|
-
const contentStart = lines.findIndex(line => !line.startsWith('//'));
|
|
197
|
-
const originalContent = lines.slice(contentStart >= 0 ? contentStart : 0).join('\n');
|
|
198
|
-
|
|
199
|
-
fs.writeFileSync(schemaPath, originalContent, 'utf8');
|
|
200
|
-
fs.unlinkSync(backupPath);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
63
|
+
*/function restoreSchema(schemaPath){const backupPath=schemaPath+SCHEMA_BACKUP_SUFFIX;if(!fs.existsSync(backupPath))return;const backupContent=fs.readFileSync(backupPath,"utf8");// 去除自动生成的注释头(以 "//" 开头的行)
|
|
64
|
+
const lines=backupContent.split("\n");const contentStart=lines.findIndex(line=>!line.startsWith("//"));const originalContent=lines.slice(contentStart>=0?contentStart:0).join("\n");fs.writeFileSync(schemaPath,originalContent,"utf8");fs.unlinkSync(backupPath)}/**
|
|
204
65
|
* 用合并后的数据(schema 默认值 + 主题覆盖值)覆写 theme.schema.json5,
|
|
205
66
|
* 以便打包工具在构建时可以直接使用。
|
|
206
67
|
*
|
|
207
68
|
* @param {string} schemaPath - theme.schema.json5 的绝对路径
|
|
208
69
|
* @param {object} mergedData - 要写入的合并后的主题数据
|
|
209
|
-
*/
|
|
210
|
-
function overwriteSchemaWithMerged(schemaPath, mergedData) {
|
|
211
|
-
const content = JSON.stringify(mergedData, null, 2);
|
|
212
|
-
fs.writeFileSync(schemaPath, content, 'utf8');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
70
|
+
*/function overwriteSchemaWithMerged(schemaPath,mergedData){const content=JSON.stringify(mergedData,null,2);fs.writeFileSync(schemaPath,content,"utf8")}/**
|
|
216
71
|
* 执行主题校验并输出标准日志。
|
|
217
72
|
* 单次构建(build.js)和批量构建(batch-build.js)共用此函数。
|
|
218
73
|
*
|
|
@@ -233,60 +88,10 @@ function overwriteSchemaWithMerged(schemaPath, mergedData) {
|
|
|
233
88
|
* valid – schema 文件不存在(跳过)或校验通过时为 true
|
|
234
89
|
* errors – 校验通过为 null,否则为错误信息数组
|
|
235
90
|
* schemaPath – theme.schema.json5 的绝对路径(用于 restoreSchema 调用),或 null
|
|
236
|
-
*/
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (!fs.existsSync(schemaPath)) {
|
|
244
|
-
return { valid: true, errors: null, schemaPath: null };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const logFn = (options && options.log) || function defaultLog(msg, level) {
|
|
248
|
-
const colors = { info: '\x1b[36m', success: '\x1b[32m', error: '\x1b[31m', warn: '\x1b[33m' };
|
|
249
|
-
const reset = '\x1b[0m';
|
|
250
|
-
const color = colors[level] || '';
|
|
251
|
-
console.log(`${color}${msg}${reset}`);
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
logFn(`校验主题数据: ${themeName}`, 'info');
|
|
255
|
-
|
|
256
|
-
const { defaultData, mergedData, isPlainData, errors } = validateThemeData(themeName, projectRoot, themeConfig);
|
|
257
|
-
|
|
258
|
-
// Safety: schema file is already plain data (previous build was interrupted)
|
|
259
|
-
// Use it directly, no need to backup/overwrite
|
|
260
|
-
if (isPlainData) {
|
|
261
|
-
logFn(`⚠️ theme.schema.json5 已经是纯数据格式(可能上次构建中断未恢复),直接使用`, 'warn');
|
|
262
|
-
// schemaPath is returned as null so that restoreSchema won't be called
|
|
263
|
-
// (there's nothing to restore — the file is already data, not a schema)
|
|
264
|
-
return { valid: true, errors: null, schemaPath: null };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (errors) {
|
|
268
|
-
logFn(`主题 "${themeName}" 数据校验失败:`, 'error');
|
|
269
|
-
errors.forEach(e => logFn(` - ${e}`, 'error'));
|
|
270
|
-
return { valid: false, errors, schemaPath };
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
logFn(`主题 "${themeName}" 数据校验通过`, 'success');
|
|
274
|
-
|
|
275
|
-
// 备份原始 schema,然后用合并后的数据覆写以供构建使用
|
|
276
|
-
backupSchema(schemaPath);
|
|
277
|
-
overwriteSchemaWithMerged(schemaPath, mergedData);
|
|
278
|
-
logFn(`已备份 schema 并写入合并后的主题数据`, 'info');
|
|
279
|
-
|
|
280
|
-
return { valid: true, errors: null, schemaPath };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
module.exports = {
|
|
284
|
-
validateThemeData,
|
|
285
|
-
deepMerge,
|
|
286
|
-
extractDefaultsFromSchema,
|
|
287
|
-
isJsonSchema,
|
|
288
|
-
runThemeValidation,
|
|
289
|
-
backupSchema,
|
|
290
|
-
restoreSchema,
|
|
291
|
-
overwriteSchemaWithMerged,
|
|
292
|
-
};
|
|
91
|
+
*/function runThemeValidation(themeName,projectRoot,themeConfig,options){const sourceDir=themeConfig&&themeConfig.sourceDir||"src/theme";const themeBaseDir=path.join(projectRoot,sourceDir);const schemaPath=path.join(themeBaseDir,"theme.schema.json5");// 没有 schema 文件 → 完全跳过校验
|
|
92
|
+
if(!fs.existsSync(schemaPath)){return{valid:true,errors:null,schemaPath:null}}const logFn=options&&options.log||function defaultLog(msg,level){const colors={info:"\x1B[36m",success:"\x1B[32m",error:"\x1B[31m",warn:"\x1B[33m"};const reset="\x1B[0m";const color=colors[level]||"";console.log(`${color}${msg}${reset}`)};logFn(`校验主题数据: ${themeName}`,"info");const{defaultData,mergedData,isPlainData,errors}=validateThemeData(themeName,projectRoot,themeConfig);// Safety: schema file is already plain data (previous build was interrupted)
|
|
93
|
+
// Use it directly, no need to backup/overwrite
|
|
94
|
+
if(isPlainData){logFn(`⚠️ theme.schema.json5 已经是纯数据格式(可能上次构建中断未恢复),直接使用`,"warn");// schemaPath is returned as null so that restoreSchema won't be called
|
|
95
|
+
// (there's nothing to restore — the file is already data, not a schema)
|
|
96
|
+
return{valid:true,errors:null,schemaPath:null}}if(errors){logFn(`主题 "${themeName}" 数据校验失败:`,"error");errors.forEach(e=>logFn(` - ${e}`,"error"));return{valid:false,errors,schemaPath}}logFn(`主题 "${themeName}" 数据校验通过`,"success");// 备份原始 schema,然后用合并后的数据覆写以供构建使用
|
|
97
|
+
backupSchema(schemaPath);overwriteSchemaWithMerged(schemaPath,mergedData);logFn(`已备份 schema 并写入合并后的主题数据`,"info");return{valid:true,errors:null,schemaPath}}module.exports={validateThemeData,deepMerge,extractDefaultsFromSchema,isJsonSchema,runThemeValidation,backupSchema,restoreSchema,overwriteSchemaWithMerged};
|