@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.
- package/README.md +259 -0
- package/cli/bin/playable-scripts.js +44 -1
- package/cli/commands/build.js +122 -17
- 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 -124
- package/core/cos-uploader.js +264 -20
- package/core/index.js +58 -4
- 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/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/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 +181 -15
- package/core/webpack.build.js +446 -52
- package/core/webpack.common.js +177 -11
- package/core/webpack.dev.js +82 -5
- package/package.json +3 -2
|
@@ -1,8 +1,113 @@
|
|
|
1
|
-
|
|
1
|
+
/** @type {string[]} List of supported advertising networks */
|
|
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
|
+
/**
|
|
2
52
|
* Parses command line arguments based on provided options configuration
|
|
3
53
|
* @param {import('../index').CLIOptionConfig[]} posiibleOptions - Array of possible options to parse
|
|
4
54
|
* @returns {Record<string, any>} Parsed options object with values from command line arguments
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
55
|
+
*/
|
|
56
|
+
exports.parseArgvOptions = function parseArgvOptions(posiibleOptions) {
|
|
57
|
+
const argvOptions = {};
|
|
58
|
+
// Track which options were explicitly set via CLI args
|
|
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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* 解析 isChannelFold2Zip 相关标志
|
|
3
3
|
*
|
|
4
4
|
* isChannelFold2Zip:所有渠道(排除 preview)输出 dist/渠道名称.zip
|
|
@@ -10,5 +10,17 @@
|
|
|
10
10
|
* @param {string} params.adNetwork - 当前渠道名称
|
|
11
11
|
* @param {boolean} params.isZipOutput - 当前渠道是否本身就走 ZIP 输出流程
|
|
12
12
|
* @returns {{ isChannelFold2Zip: boolean, isZipOutputFinal: boolean }}
|
|
13
|
-
*/
|
|
14
|
-
|
|
13
|
+
*/
|
|
14
|
+
function resolveChannelFold2Zip({ isChannelFold2ZipOption, adNetwork, isZipOutput }) {
|
|
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;
|
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict';
|
|
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
|
+
/**
|
|
2
15
|
* 递归提取 JSON Schema 定义中的 `default` 值。
|
|
3
16
|
*
|
|
4
17
|
* - 对于 "object" 类型:递归遍历 `properties` 并构建嵌套对象。
|
|
@@ -6,15 +19,42 @@
|
|
|
6
19
|
*
|
|
7
20
|
* @param {object} schema - JSON Schema(或子 schema)对象
|
|
8
21
|
* @returns {*} 提取的默认值树,如果没有则返回 undefined
|
|
9
|
-
*/
|
|
10
|
-
|
|
22
|
+
*/
|
|
23
|
+
function extractDefaultsFromSchema(schema) {
|
|
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
|
+
/**
|
|
11
46
|
* Check whether the parsed content of theme.schema.json5 is a real JSON Schema
|
|
12
47
|
* (has "type" and "properties" fields) or has already been overwritten with
|
|
13
48
|
* plain merged data (e.g. due to a previous interrupted build).
|
|
14
49
|
*
|
|
15
50
|
* @param {object} obj - Parsed content of theme.schema.json5
|
|
16
51
|
* @returns {boolean} true if it looks like a valid JSON Schema definition
|
|
17
|
-
*/
|
|
52
|
+
*/
|
|
53
|
+
function isJsonSchema(obj) {
|
|
54
|
+
return obj && typeof obj === 'object' && obj.type === 'object' && typeof obj.properties === 'object';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
18
58
|
* 根据 schema 校验指定主题的主题数据。
|
|
19
59
|
*
|
|
20
60
|
* 项目中预期的文件结构:
|
|
@@ -35,39 +75,144 @@ if("default"in schema){return schema.default}return undefined}/**
|
|
|
35
75
|
* mergedData – schema 默认值与主题数据合并后的完整数据
|
|
36
76
|
* isPlainData – 如果 schema 文件已经是纯数据(非 schema 格式)则为 true
|
|
37
77
|
* errors – 校验通过为 null,否则为错误信息数组
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
78
|
+
*/
|
|
79
|
+
function validateThemeData(themeName, projectRoot, themeConfig) {
|
|
80
|
+
const sourceDir = (themeConfig && themeConfig.sourceDir) || 'src/theme';
|
|
81
|
+
const themeBaseDir = path.join(projectRoot, sourceDir);
|
|
82
|
+
|
|
83
|
+
// --- 解析文件路径 ---
|
|
84
|
+
const schemaPath = path.join(themeBaseDir, 'theme.schema.json5');
|
|
85
|
+
const dataPath = path.join(themeBaseDir, themeName, 'theme.data.json5');
|
|
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
|
+
/**
|
|
47
139
|
* 深度合并两个普通对象。`override` 中的值优先。
|
|
48
140
|
* 数组会被整体替换(不会拼接)。
|
|
49
141
|
*
|
|
50
142
|
* @param {object} base
|
|
51
143
|
* @param {object} override
|
|
52
144
|
* @returns {object}
|
|
53
|
-
*/
|
|
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
|
+
/**
|
|
54
171
|
* 备份原始的 theme.schema.json5 文件。
|
|
55
172
|
* 备份文件包含注释头,说明如何手动恢复。
|
|
56
173
|
*
|
|
57
174
|
* @param {string} schemaPath - theme.schema.json5 的绝对路径
|
|
58
175
|
* @returns {string} 备份文件路径
|
|
59
|
-
*/
|
|
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
|
+
/**
|
|
60
185
|
* 从备份文件恢复 theme.schema.json5 并删除备份。
|
|
61
186
|
*
|
|
62
187
|
* @param {string} schemaPath - theme.schema.json5 的绝对路径
|
|
63
|
-
*/
|
|
64
|
-
|
|
188
|
+
*/
|
|
189
|
+
function restoreSchema(schemaPath) {
|
|
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
|
+
/**
|
|
65
204
|
* 用合并后的数据(schema 默认值 + 主题覆盖值)覆写 theme.schema.json5,
|
|
66
205
|
* 以便打包工具在构建时可以直接使用。
|
|
67
206
|
*
|
|
68
207
|
* @param {string} schemaPath - theme.schema.json5 的绝对路径
|
|
69
208
|
* @param {object} mergedData - 要写入的合并后的主题数据
|
|
70
|
-
*/
|
|
209
|
+
*/
|
|
210
|
+
function overwriteSchemaWithMerged(schemaPath, mergedData) {
|
|
211
|
+
const content = JSON.stringify(mergedData, null, 2);
|
|
212
|
+
fs.writeFileSync(schemaPath, content, 'utf8');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
71
216
|
* 执行主题校验并输出标准日志。
|
|
72
217
|
* 单次构建(build.js)和批量构建(batch-build.js)共用此函数。
|
|
73
218
|
*
|
|
@@ -88,10 +233,60 @@ const lines=backupContent.split("\n");const contentStart=lines.findIndex(line=>!
|
|
|
88
233
|
* valid – schema 文件不存在(跳过)或校验通过时为 true
|
|
89
234
|
* errors – 校验通过为 null,否则为错误信息数组
|
|
90
235
|
* schemaPath – theme.schema.json5 的绝对路径(用于 restoreSchema 调用),或 null
|
|
91
|
-
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
236
|
+
*/
|
|
237
|
+
function runThemeValidation(themeName, projectRoot, themeConfig, options) {
|
|
238
|
+
const sourceDir = (themeConfig && themeConfig.sourceDir) || 'src/theme';
|
|
239
|
+
const themeBaseDir = path.join(projectRoot, sourceDir);
|
|
240
|
+
const schemaPath = path.join(themeBaseDir, 'theme.schema.json5');
|
|
241
|
+
|
|
242
|
+
// 没有 schema 文件 → 完全跳过校验
|
|
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
|
+
};
|