@playcraft/devkit 1.0.24 → 1.0.25-beta.1.0
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/commands/build.js +7 -4
- package/core/plugins/SocialMediaInjectorPlugin.js +27 -0
- package/core/plugins/socialMediaVitePlugin.js +4 -0
- package/core/utils/injectSDKPlugins.js +9 -3
- package/core/utils/parseArgvOptions.js +1 -1
- package/core/utils/validateThemeData.js +2 -1
- package/core/vite.dev.js +2 -2
- package/defines.d.ts +2 -1
- package/package.json +1 -1
package/cli/commands/build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";process.env.BABEL_ENV="production";process.env.NODE_ENV="production";const fs=require("fs");const path=require("path");const{runBuild}=require("../../core/webpack.build.js");const{runThemeValidation,restoreSchema}=require("../../core/utils/validateThemeData");const{runPreBuildChecks}=require("../../core/validators/pre-build-checker.js");const{FontInjectionWebpackPlugin}=require("../../core/plugins/FontInjectionWebpackPlugin.js");/**
|
|
1
|
+
"use strict";process.env.BABEL_ENV="production";process.env.NODE_ENV="production";const fs=require("fs");const path=require("path");const{runBuild}=require("../../core/webpack.build.js");const{runThemeValidation,restoreSchema}=require("../../core/utils/validateThemeData");const{runPreBuildChecks}=require("../../core/validators/pre-build-checker.js");const{FontInjectionWebpackPlugin}=require("../../core/plugins/FontInjectionWebpackPlugin.js");const{SocialMediaInjectorPlugin}=require("../../core/plugins/SocialMediaInjectorPlugin.js");const{options}=require("../../core/options.js");/**
|
|
2
2
|
* 从主题入口文件中检测当前主题名称。
|
|
3
3
|
* 支持以下模式:
|
|
4
4
|
* export { default } from './themeName';
|
|
@@ -15,7 +15,8 @@ const projectRoot=process.cwd();const{themeName,themeConfig}=detectCurrentTheme(
|
|
|
15
15
|
// - PLAYABLE_BATCH_MODE=1: 批量构建模式,子进程自动跳过检查(主进程已执行)
|
|
16
16
|
const isBatchMode=process.env.PLAYABLE_BATCH_MODE==="1";const shouldSkipChecks=process.env.SKIP_PRE_BUILD_CHECKS==="1"||isBatchMode;async function runPreChecks(){if(shouldSkipChecks){console.log("\n\u26A0\uFE0F \u8DF3\u8FC7\u7F16\u8BD1\u524D\u68C0\u67E5\uFF08SKIP_PRE_BUILD_CHECKS=1\uFF09\n");return}const checkResult=await runPreBuildChecks(projectRoot,{verbose:false,showDetails:true,failFast:false});if(!checkResult.success){console.error("\n\u274C \u7F16\u8BD1\u524D\u68C0\u67E5\u5931\u8D25\uFF0C\u7EC8\u6B62\u6784\u5EFA\u3002");console.error("\u63D0\u793A\uFF1A\u5982\u9700\u8DF3\u8FC7\u68C0\u67E5\uFF0C\u53EF\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF: SKIP_PRE_BUILD_CHECKS=1\n");process.exit(1)}}// 执行编译前检查(异步)
|
|
17
17
|
runPreChecks().then(()=>{// 检查通过后继续原有流程
|
|
18
|
-
continueWithBuild()}).catch(err=>{console.error("\n\u274C \u7F16\u8BD1\u524D\u68C0\u67E5\u51FA\u9519:",err);process.exit(1)});function continueWithBuild(){//
|
|
18
|
+
continueWithBuild()}).catch(err=>{console.error("\n\u274C \u7F16\u8BD1\u524D\u68C0\u67E5\u51FA\u9519:",err);process.exit(1)});function continueWithBuild(){// 当前广告渠道(用于决定是否启用 social_media 的 OG meta 注入)
|
|
19
|
+
const adNetwork=options["network"]||"";// In batch mode (PLAYABLE_BATCH_MODE=1), the parent process has already
|
|
19
20
|
// executed runThemeValidation and overwritten theme.schema.json5 with merged data.
|
|
20
21
|
// Running it again in child processes would cause a race condition:
|
|
21
22
|
// 1. Child reads the already-overwritten schema (pure data, no "properties"/"default")
|
|
@@ -23,5 +24,7 @@ continueWithBuild()}).catch(err=>{console.error("\n\u274C \u7F16\u8BD1\u524D\u68
|
|
|
23
24
|
// 3. Child overwrites schema with {} → file becomes empty object
|
|
24
25
|
const isBatchMode=process.env.PLAYABLE_BATCH_MODE==="1";if(themeName&&!isBatchMode){const buildLog=(msg,level)=>{const colors={info:"\x1B[36m",success:"\x1B[32m",error:"\x1B[31m"};const reset="\x1B[0m";const color=colors[level]||"";console.log(`${color}[build] ${msg}${reset}`)};const{valid,schemaPath}=runThemeValidation(themeName,projectRoot,themeConfig,{log:buildLog});schemaPathToRestore=schemaPath;if(!valid){// 校验失败时恢复 schema(备份可能已创建)
|
|
25
26
|
if(schemaPathToRestore)restoreSchema(schemaPathToRestore);process.exit(1)}}// 注入字体插件(build 模式 base64 内联)
|
|
26
|
-
const fontPlugin=themeName?new FontInjectionWebpackPlugin({projectRoot,themeName,themeConfig}):null;//
|
|
27
|
-
|
|
27
|
+
const fontPlugin=themeName?new FontInjectionWebpackPlugin({projectRoot,themeName,themeConfig}):null;// social_media 渠道:注入 OG / Twitter meta 标签,让 Facebook / LinkedIn
|
|
28
|
+
// 等抓取预览时拿到正确的标题/描述/图片。无 shareMeta 时空跑,不影响构建
|
|
29
|
+
const socialMediaPlugin=themeName&&adNetwork==="social_media"?new SocialMediaInjectorPlugin({projectRoot,themeName,themeConfig}):null;// 执行构建,无论成功或失败都恢复 schema
|
|
30
|
+
runBuild(undefined,undefined,undefined,{plugins:[...(fontPlugin?[fontPlugin]:[]),...(socialMediaPlugin?[socialMediaPlugin]:[])]}).then(()=>{if(schemaPathToRestore)restoreSchema(schemaPathToRestore)}).catch(err=>{if(schemaPathToRestore)restoreSchema(schemaPathToRestore);console.error(err);process.exit(1)})}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* Webpack 插件:social_media 渠道专用 — 在构建产物 HTML 的 </head> 之前
|
|
3
|
+
* 注入 OG / Twitter / Facebook 分享卡片 meta 标签。
|
|
4
|
+
*
|
|
5
|
+
* 设计动机:
|
|
6
|
+
* Facebook sharer.php 自 2014 起废弃了 t/title 等参数,
|
|
7
|
+
* 完全靠爬取目标页面 <head> 的 og:* meta 标签生成预览卡。
|
|
8
|
+
* 我们的 index.html 模板只含基础 meta,必须在构建时把
|
|
9
|
+
* gamePlay.shareMeta 中的 title/description/imageUrl/pageUrl 注入。
|
|
10
|
+
*
|
|
11
|
+
* 数据源(按优先级):
|
|
12
|
+
* 1. opts.mergedData.gamePlay.shareMeta (显式传入,批量构建子进程用)
|
|
13
|
+
* 2. 从 <projectRoot>/<themeName>/theme.data.json5 读取
|
|
14
|
+
* 3. 都读不到 → 跳过注入(不报错,让用户后续补)
|
|
15
|
+
*
|
|
16
|
+
* 配合 injectSDKPlugins.js 的 social_media 分支在 build 时挂载。
|
|
17
|
+
*/const HtmlWebpackPlugin=require("html-webpack-plugin");const fs=require("fs");const path=require("path");const JSON5=require("json5");class SocialMediaInjectorPlugin{/**
|
|
18
|
+
* @param {object} [opts]
|
|
19
|
+
* @param {string} [opts.projectRoot] - 项目根目录
|
|
20
|
+
* @param {string} [opts.themeName] - 当前主题目录名(不含前缀斜线)
|
|
21
|
+
* @param {object} [opts.themeConfig] - builds.config.js 中的 themes 配置
|
|
22
|
+
* @param {object} [opts.mergedData] - 预合并数据(可选)
|
|
23
|
+
*/constructor(opts={}){this.opts=opts}/** 从 mergedData / theme.data.json5 读出 shareMeta 块 */_loadShareMeta(){// 1) 优先用已传入的 mergedData
|
|
24
|
+
let merged=this.opts.mergedData;if(merged&&merged.gamePlay&&merged.gamePlay.shareMeta){return merged.gamePlay.shareMeta}// 2) 退化为读 theme.data.json5
|
|
25
|
+
const root=this.opts.projectRoot||process.cwd();const name=this.opts.themeName;if(!name)return null;const dataPath=path.join(root,this.opts.themeConfig&&this.opts.themeConfig.sourceDir||"src/theme",name,"theme.data.json5");if(!fs.existsSync(dataPath))return null;try{const raw=fs.readFileSync(dataPath,"utf8");const parsed=JSON5.parse(raw);return parsed&&parsed.gamePlay&&parsed.gamePlay.shareMeta?parsed.gamePlay.shareMeta:null}catch(e){console.warn("[SocialMediaInjectorPlugin] Failed to read theme.data.json5:",e.message);return null}}/** 把 shareMeta 渲染成 <meta> 标签字符串 */_buildMetaTags(meta){if(!meta)return"";const esc=s=>String(s).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");const lines=[];const title=(meta.title||"").trim();const description=(meta.description||"").trim();const image=(meta.imageUrl||"").trim();const url=(meta.pageUrl||"").trim();const siteName=(meta.siteName||"").trim();// Open Graph
|
|
26
|
+
if(title){lines.push(`<meta property="og:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta property="og:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta property="og:image" content="${esc(image)}" />`)}if(url){lines.push(`<meta property="og:url" content="${esc(url)}" />`)}if(siteName){lines.push(`<meta property="og:site_name" content="${esc(siteName)}" />`)}lines.push(`<meta property="og:type" content="website" />`);// Twitter Card
|
|
27
|
+
lines.push(`<meta name="twitter:card" content="summary_large_image" />`);if(title){lines.push(`<meta name="twitter:title" content="${esc(title)}" />`)}if(description){lines.push(`<meta name="twitter:description" content="${esc(description)}" />`)}if(image){lines.push(`<meta name="twitter:image" content="${esc(image)}" />`)}return lines.join("\n ")}apply(compiler){const pluginName="SocialMediaInjectorPlugin";compiler.hooks.compilation.tap(pluginName,compilation=>{HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(pluginName,(data,cb)=>{const shareMeta=this._loadShareMeta();if(!shareMeta){return cb(null,data)}const tags=this._buildMetaTags(shareMeta);if(tags){data.html=data.html.replace("</head>",` ${tags}\n </head>`)}cb(null,data)})})}}module.exports={SocialMediaInjectorPlugin};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use strict";/**
|
|
2
|
+
* Vite dev 模式:social_media 渠道的 OG / Twitter meta 注入。
|
|
3
|
+
* 与 SocialMediaInjectorPlugin.js 输出完全一致,仅 hook 形态不同。
|
|
4
|
+
*/const path=require("path");const fs=require("fs");const JSON5=require("json5");/** 把 shareMeta 渲染成 <meta> 标签字符串(与 webpack plugin 完全一致) */function buildMetaTags(meta){if(!meta)return[];const esc=s=>String(s).replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">");const tags=[];const{title,description,imageUrl,pageUrl,siteName}=meta;const t=(title||"").trim();const d=(description||"").trim();const i=(imageUrl||"").trim();const u=(pageUrl||"").trim();const s=(siteName||"").trim();if(t)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:title",content:t}});if(d)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:description",content:d}});if(i)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:image",content:i}});if(u)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:url",content:u}});if(s)tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:site_name",content:s}});tags.push({tag:"meta",injectTo:"head",attrs:{property:"og:type",content:"website"}});tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:card",content:"summary_large_image"}});if(t)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:title",content:t}});if(d)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:description",content:d}});if(i)tags.push({tag:"meta",injectTo:"head",attrs:{name:"twitter:image",content:i}});return tags}/** 检测主题名(与 fontFamilyPlugin 逻辑保持一致) */function detectTheme(){const root=process.cwd();const buildsConfigPath=path.join(root,"builds.config.js");let config={sourceDir:"src/theme",entryFile:"src/theme/index.ts"};if(fs.existsSync(buildsConfigPath)){try{delete require.cache[require.resolve(buildsConfigPath)];const bc=require(buildsConfigPath);if(bc.themes)config={...config,...bc.themes}}catch(_){/* ignore */}}let name=null;const entryPath=path.join(root,config.entryFile);if(fs.existsSync(entryPath)){const content=fs.readFileSync(entryPath,"utf8");const m=content.match(/from\s+['"]\.\/([^'"]+)['"]/);if(m)name=m[1]}return{themeName:name,themeConfig:config}}/** 读取 shareMeta,缓存以避免 transformIndexHtml 多次触发时反复读盘 */let _cache={key:null,value:null};function loadShareMeta(){const{themeName,themeConfig}=detectTheme();if(!themeName)return null;const root=process.cwd();const dataPath=path.join(root,themeConfig.sourceDir||"src/theme",themeName,"theme.data.json5");if(!fs.existsSync(dataPath))return null;const stat=fs.statSync(dataPath);const key=`${dataPath}::${stat.mtimeMs}`;if(_cache.key===key)return _cache.value;try{const raw=fs.readFileSync(dataPath,"utf8");const parsed=JSON5.parse(raw);const value=parsed&&parsed.gamePlay&&parsed.gamePlay.shareMeta?parsed.gamePlay.shareMeta:null;_cache={key,value};return value}catch(e){console.warn("[socialMediaVitePlugin] Failed to read theme.data.json5:",e.message);return null}}function socialMediaVitePlugin(){return{name:"vite-plugin-social-media",apply:"serve",transformIndexHtml:{order:"pre",handler(){const meta=loadShareMeta();return buildMetaTags(meta)}}}}module.exports={socialMediaVitePlugin,buildMetaTags};
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
"use strict";/**
|
|
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
|
+
*/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");const{SocialMediaInjectorPlugin}=require("../plugins/SocialMediaInjectorPlugin.js");/**
|
|
5
5
|
* 注入渠道 SDK 插件到 webpack 配置中
|
|
6
6
|
* @param {import('webpack').Configuration} webpackConfig - webpack 配置对象
|
|
7
7
|
* @param {Object} options - 配置选项
|
|
8
8
|
* @param {AD_NETWORK} options.network - 广告网络
|
|
9
9
|
* @param {AD_PROTOCOL} options.protocol - 广告协议
|
|
10
10
|
* @param {string} [options.orientation] - 屏幕方向(google 渠道需要)
|
|
11
|
-
|
|
11
|
+
* @param {string} [options.projectRoot] - 项目根目录(social_media 渠道读取 theme.data.json5 用)
|
|
12
|
+
* @param {string} [options.themeName] - 当前主题名
|
|
13
|
+
* @param {object} [options.themeConfig] - builds.config.js 中的 themes 配置
|
|
14
|
+
* @param {object} [options.mergedData] - 预合并的主题数据(可选)
|
|
15
|
+
*/function injectSDKPlugins(webpackConfig,options){const{network:adNetwork,protocol:adProtocol,orientation,projectRoot,themeName,themeConfig,mergedData}=options;// 协议层注入
|
|
12
16
|
if("dapi"===adProtocol){webpackConfig.plugins.push(new DAPIInjectorPlugin)}else if("mraid"===adProtocol){// 只有 ironsource 才注入 MRAIDInjectorPlugin
|
|
13
17
|
// Unity 使用专门的 UnityInjectorPlugin(在渠道特定注入中处理)
|
|
14
18
|
if(adNetwork==="ironsource"){webpackConfig.plugins.push(new MRAIDInjectorPlugin)}}// 渠道特定注入
|
|
@@ -16,4 +20,6 @@ if(adNetwork==="google"){webpackConfig.plugins.push(new ExitAPIInjectorPlugin(or
|
|
|
16
20
|
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
21
|
webpackConfig.plugins.push(new BigoAdsInjectorPlugin)}else if(adNetwork==="liftoff"){webpackConfig.plugins.push(new LiftoffInjectorPlugin)}else if(adNetwork==="snapchat"){// Snapchat 注入 ScPlayableAd SDK
|
|
18
22
|
webpackConfig.plugins.push(new SnapchatInjectorPlugin)}else if(adNetwork==="adikteev"){// Adikteev 注入 MRAID mock 和占位符变量
|
|
19
|
-
webpackConfig.plugins.push(new AdikteevInjectorPlugin)}
|
|
23
|
+
webpackConfig.plugins.push(new AdikteevInjectorPlugin)}else if(adNetwork==="social_media"){// social_media 渠道:注入 OG / Twitter meta 标签,让 Facebook / LinkedIn 爬取时
|
|
24
|
+
// 能拿到正确的标题/描述/预览图。注意 Telegram / Slack / Discord 也读 OG meta
|
|
25
|
+
webpackConfig.plugins.push(new SocialMediaInjectorPlugin({projectRoot,themeName,themeConfig,mergedData}))}}exports.injectSDKPlugins=injectSDKPlugins;
|
|
@@ -1,4 +1,4 @@
|
|
|
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"];/**
|
|
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","social_media"];/** @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"];/**
|
|
2
2
|
* Parses command line arguments based on provided options configuration
|
|
3
3
|
* @param {import('../index').CLIOptionConfig[]} posiibleOptions - Array of possible options to parse
|
|
4
4
|
* @returns {Record<string, any>} Parsed options object with values from command line arguments
|
|
@@ -43,7 +43,8 @@ if(!fs.existsSync(schemaPath)){return{defaultData:null,mergedData:null,isPlainDa
|
|
|
43
43
|
if(!isJsonSchema(schemaContent)){return{defaultData:schemaContent,mergedData:schemaContent,isPlainData:true,errors:null}}// 从 schema 提取默认值
|
|
44
44
|
const defaultData=extractDefaultsFromSchema(schemaContent)||{};let themeData={};if(fs.existsSync(dataPath)){themeData=JSON5.parse(fs.readFileSync(dataPath,"utf8"))}// 深度合并(主题数据覆盖 schema 默认值),然后校验
|
|
45
45
|
const merged=deepMerge(defaultData,themeData);// --- 根据 schema 校验合并后的数据 ---
|
|
46
|
-
const ajv=new Ajv({allErrors:true,strict:false});
|
|
46
|
+
const ajv=new Ajv({allErrors:true,strict:false});let validate;try{validate=ajv.compile(schemaContent)}catch(schemaErr){// schema 文件被之前的构建中断污染(mergedData 覆盖了 schema)→ 按纯数据恢复
|
|
47
|
+
console.warn(`[validateThemeData] schema 编译失败(可能被覆盖),降级为纯数据: ${schemaErr.message}`);return{defaultData:merged,mergedData:merged,isPlainData:true,errors:null}}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}}/**
|
|
47
48
|
* 深度合并两个普通对象。`override` 中的值优先。
|
|
48
49
|
* 数组会被整体替换(不会拼接)。
|
|
49
50
|
*
|
package/core/vite.dev.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";const path=require("path");const fs=require("fs");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");const{getOrGenerateFontCSS}=require("./utils/fontResolver.js");// ── 公共 Vite 插件(供项目 vite.config.ts 直接引用) ──
|
|
1
|
+
"use strict";const path=require("path");const fs=require("fs");const{options}=require("./options.js");const{mergeOptions}=require("./utils/mergeOptions.js");const{buildDefines}=require("./utils/buildDefines.js");const{logOptions}=require("./utils/logOptions.js");const{getOrGenerateFontCSS}=require("./utils/fontResolver.js");const{socialMediaVitePlugin}=require("./plugins/socialMediaVitePlugin.js");// ── 公共 Vite 插件(供项目 vite.config.ts 直接引用) ──
|
|
2
2
|
/** JSON5 文件加载插件 */function json5Plugin(){return{name:"vite-plugin-json5",transform(code,id){if(id.endsWith(".json5")){const JSON5=require("json5");const raw=fs.readFileSync(id,"utf-8");const parsed=JSON5.parse(raw);return{code:`export default ${JSON.stringify(parsed)};`,map:null}}}}}/** 资源内联插件(模拟 webpack 的 asset/inline 行为) */function assetInlinePlugin(){return{name:"vite-plugin-asset-inline",enforce:"pre",transform(code,id){// .atlas 文件 -> base64 data URL
|
|
3
3
|
if(id.endsWith(".atlas")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/atlas;base64,${base64}";`,map:null}}// .fnt 文件 -> base64 data URL
|
|
4
4
|
if(id.endsWith(".fnt")){const content=fs.readFileSync(id);const base64=content.toString("base64");return{code:`export default "data:text/plain;base64,${base64}";`,map:null}}// .xml 文件 -> 原始文本
|
|
@@ -15,7 +15,7 @@ const defines={...buildDefines(),...devOptions.defines,__DEV__:JSON.stringify(de
|
|
|
15
15
|
const tsConfigPath=path.resolve(devOptions.tsConfig||"tsconfig.json");const jsConfigPath=path.resolve(devOptions.jsConfig||"jsconfig.json");let aliasConfig={};// 尝试从 tsconfig/jsconfig 中读取 paths 配置
|
|
16
16
|
let configFilePath=null;if(fs.existsSync(tsConfigPath)){configFilePath=tsConfigPath}else if(fs.existsSync(jsConfigPath)){configFilePath=jsConfigPath}if(configFilePath){try{var _config$compilerOptio;const JSON5=require("json5");const configContent=fs.readFileSync(configFilePath,"utf-8");const config=JSON5.parse(configContent);const paths=((_config$compilerOptio=config.compilerOptions)===null||_config$compilerOptio===void 0?void 0:_config$compilerOptio.paths)||{};for(const[alias,targets]of Object.entries(paths)){var _targets$;// 将 tsconfig paths 格式 "foo/*" -> ["src/foo/*"] 转换为 vite alias
|
|
17
17
|
const cleanAlias=alias.replace("/*","");const cleanTarget=((_targets$=targets[0])===null||_targets$===void 0?void 0:_targets$.replace("/*",""))||"";if(cleanAlias&&cleanTarget){aliasConfig[cleanAlias]=path.resolve(cleanTarget)}}}catch(err){console.log("\x1B[33m\u26A0\uFE0F \u8BFB\u53D6\u8DEF\u5F84\u914D\u7F6E\u5931\u8D25: "+err.message+"\x1B[0m")}}// 默认别名
|
|
18
|
-
aliasConfig={assets:path.resolve("assets"),"@":path.resolve("src"),...aliasConfig};/** @type {import('vite').UserConfig} */const viteConfig={root:".",resolve:{alias:aliasConfig},plugins:[json5Plugin(),assetInlinePlugin(),fontFamilyPlugin()],define:defines,server:{port:devOptions["port"]||3000,open:devOptions["open"]||false},build:{outDir:devOptions["outDir"]||"dist",sourcemap:true}};// 合并自定义配置
|
|
18
|
+
aliasConfig={assets:path.resolve("assets"),"@":path.resolve("src"),...aliasConfig};/** @type {import('vite').UserConfig} */const viteConfig={root:".",resolve:{alias:aliasConfig},plugins:[json5Plugin(),assetInlinePlugin(),fontFamilyPlugin(),socialMediaVitePlugin()],define:defines,server:{port:devOptions["port"]||3000,open:devOptions["open"]||false},build:{outDir:devOptions["outDir"]||"dist",sourcemap:true}};// 合并自定义配置
|
|
19
19
|
if(viteCustomConfig){const{plugins:customPlugins,define:customDefine,resolve:customResolve,server:customServer,build:customBuild,...rest}=viteCustomConfig;if(customPlugins){viteConfig.plugins=[...viteConfig.plugins,...customPlugins]}if(customDefine){viteConfig.define={...viteConfig.define,...customDefine}}if(customResolve){viteConfig.resolve={...viteConfig.resolve,...customResolve,alias:{...viteConfig.resolve.alias,...(customResolve.alias||{})}}}if(customServer){viteConfig.server={...viteConfig.server,...customServer}}if(customBuild){viteConfig.build={...viteConfig.build,...customBuild}}Object.assign(viteConfig,rest)}return viteConfig}/**
|
|
20
20
|
* 深度合并两个 Vite 配置对象。override 覆盖 base。
|
|
21
21
|
* 数组会拼接(plugins),其余字段浅覆盖。
|
package/defines.d.ts
CHANGED