@playcraft/build 0.0.17 → 0.0.19
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/dist/analyzers/scene-asset-collector.js +259 -135
- package/dist/audio-optimizer.d.ts +70 -0
- package/dist/audio-optimizer.js +226 -0
- package/dist/base-builder.d.ts +25 -13
- package/dist/base-builder.js +69 -29
- package/dist/engines/engine-detector.d.ts +13 -4
- package/dist/engines/engine-detector.js +74 -10
- package/dist/engines/generic-adapter.d.ts +12 -6
- package/dist/engines/generic-adapter.js +46 -15
- package/dist/engines/index.d.ts +1 -0
- package/dist/engines/index.js +1 -0
- package/dist/engines/playable-scripts-adapter.d.ts +148 -0
- package/dist/engines/playable-scripts-adapter.js +1084 -0
- package/dist/engines/playcanvas-adapter.js +3 -0
- package/dist/generators/config-generator.js +10 -17
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/platforms/google.d.ts +9 -0
- package/dist/platforms/google.js +68 -7
- package/dist/templates/__loading__.js +100 -0
- package/dist/templates/__modules__.js +47 -0
- package/dist/templates/__settings__.template.js +20 -0
- package/dist/templates/__start__.js +332 -0
- package/dist/templates/index.html +18 -0
- package/dist/templates/logo.png +0 -0
- package/dist/templates/manifest.json +1 -0
- package/dist/templates/patches/cannon.min.js +28 -0
- package/dist/templates/patches/lz4.js +10 -0
- package/dist/templates/patches/one-page-http-get.js +20 -0
- package/dist/templates/patches/one-page-inline-game-scripts.js +52 -0
- package/dist/templates/patches/one-page-mraid-resize-canvas.js +46 -0
- package/dist/templates/patches/p2.min.js +27 -0
- package/dist/templates/patches/playcraft-no-xhr.js +76 -0
- package/dist/templates/playcanvas-stable.min.js +16363 -0
- package/dist/templates/styles.css +43 -0
- package/dist/types.d.ts +60 -13
- package/dist/utils/build-mode-detector.js +2 -0
- package/dist/vite/plugin-playcanvas.js +14 -19
- package/dist/vite/plugin-source-builder.js +383 -97
- package/package.json +7 -4
- package/dist/utils/obfuscate.d.ts +0 -42
- package/dist/utils/obfuscate.js +0 -216
- package/dist/vite/plugin-obfuscate.d.ts +0 -22
- package/dist/vite/plugin-obfuscate.js +0 -52
- package/dist/vite/plugin-template-minifier.d.ts +0 -20
- package/dist/vite/plugin-template-minifier.js +0 -392
|
@@ -444,18 +444,16 @@ function validateAndCleanSceneScripts(sceneData, registeredScripts, _sceneName /
|
|
|
444
444
|
// 处理 ESM 格式:scripts 是对象 { scriptName: {...}, ... }
|
|
445
445
|
if (scriptComp.scripts && typeof scriptComp.scripts === 'object' && !Array.isArray(scriptComp.scripts)) {
|
|
446
446
|
const validScripts = {};
|
|
447
|
-
const validOrder = [];
|
|
448
447
|
for (const [scriptName, scriptData] of Object.entries(scriptComp.scripts)) {
|
|
449
448
|
if (registeredScripts.has(scriptName)) {
|
|
450
449
|
validScripts[scriptName] = scriptData;
|
|
451
|
-
if (scriptComp.order?.includes(scriptName)) {
|
|
452
|
-
validOrder.push(scriptName);
|
|
453
|
-
}
|
|
454
450
|
}
|
|
455
451
|
else {
|
|
456
452
|
removedScripts.push(`${entity.name || entityId}/${scriptName}`);
|
|
457
453
|
}
|
|
458
454
|
}
|
|
455
|
+
// BUG: Preserve original order: filter based on the original order array, not scripts object key order
|
|
456
|
+
const validOrder = (scriptComp.order || []).filter((name) => name in validScripts);
|
|
459
457
|
scriptComp.scripts = validScripts;
|
|
460
458
|
scriptComp.order = validOrder;
|
|
461
459
|
// 如果所有脚本都被移除,删除整个 script 组件
|
|
@@ -571,6 +569,52 @@ outputDir, selectedScenes) {
|
|
|
571
569
|
}
|
|
572
570
|
}
|
|
573
571
|
}
|
|
572
|
+
/**
|
|
573
|
+
* 构建文件夹 ID -> 完整目录路径的映射
|
|
574
|
+
* PlayCraft Git 项目的 assets.json 中,资产的 path 字段是文件夹 ID 数组(如 [30097022, 30097024]),
|
|
575
|
+
* 需要通过递归遍历构建每个 folder ID 对应的完整路径(如 "LiteCreator/System")
|
|
576
|
+
*/
|
|
577
|
+
function buildFolderFullPathMap(assets) {
|
|
578
|
+
const folderMap = new Map();
|
|
579
|
+
const buildPath = (folderId, visited = new Set()) => {
|
|
580
|
+
if (visited.has(folderId))
|
|
581
|
+
return '';
|
|
582
|
+
if (folderMap.has(folderId))
|
|
583
|
+
return folderMap.get(folderId);
|
|
584
|
+
visited.add(folderId);
|
|
585
|
+
const folder = assets[folderId];
|
|
586
|
+
if (!folder || folder.type !== 'folder')
|
|
587
|
+
return '';
|
|
588
|
+
const pathIds = folder.path || [];
|
|
589
|
+
if (pathIds.length === 0) {
|
|
590
|
+
folderMap.set(folderId, folder.name);
|
|
591
|
+
return folder.name;
|
|
592
|
+
}
|
|
593
|
+
// 最后一个 ID 是直接父文件夹
|
|
594
|
+
const parentId = String(pathIds[pathIds.length - 1]);
|
|
595
|
+
const parentPath = buildPath(parentId, visited);
|
|
596
|
+
const fullPath = parentPath ? `${parentPath}/${folder.name}` : folder.name;
|
|
597
|
+
folderMap.set(folderId, fullPath);
|
|
598
|
+
return fullPath;
|
|
599
|
+
};
|
|
600
|
+
for (const [id, asset] of Object.entries(assets)) {
|
|
601
|
+
if (asset.type === 'folder') {
|
|
602
|
+
buildPath(id);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return folderMap;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* 从资产的 path 数组(文件夹 ID 列表)重建 treePath
|
|
609
|
+
* path 数组中最后一个 ID 是直接父文件夹
|
|
610
|
+
*/
|
|
611
|
+
function resolveTreePathFromPathArray(pathIds, folderFullPathMap) {
|
|
612
|
+
if (!pathIds || !Array.isArray(pathIds) || pathIds.length === 0) {
|
|
613
|
+
return '';
|
|
614
|
+
}
|
|
615
|
+
const parentFolderId = String(pathIds[pathIds.length - 1]);
|
|
616
|
+
return folderFullPathMap.get(parentFolderId) || '';
|
|
617
|
+
}
|
|
574
618
|
/**
|
|
575
619
|
* 规范化资源 URL(补齐 file.url)
|
|
576
620
|
* 源码导出中的资源只有 file.filename,没有 file.url
|
|
@@ -580,6 +624,8 @@ async function normalizeAssetUrls(projectConfig, projectDir) {
|
|
|
580
624
|
const assets = projectConfig.format === 'playcanvas'
|
|
581
625
|
? projectConfig.assets
|
|
582
626
|
: projectConfig.assets;
|
|
627
|
+
// 构建文件夹 ID -> 完整路径的映射(用于从 path 数组重建 treePath)
|
|
628
|
+
const folderFullPathMap = buildFolderFullPathMap(assets);
|
|
583
629
|
let normalizedCount = 0;
|
|
584
630
|
let notFoundCount = 0;
|
|
585
631
|
for (const [assetId, asset] of Object.entries(assets)) {
|
|
@@ -620,13 +666,21 @@ async function normalizeAssetUrls(projectConfig, projectDir) {
|
|
|
620
666
|
path.join('files', 'assets', String(assetId), filename),
|
|
621
667
|
path.join('files', filename),
|
|
622
668
|
];
|
|
623
|
-
// PlayCraft 结构:使用 treePath
|
|
669
|
+
// PlayCraft 结构:使用 treePath 推断文件位置
|
|
624
670
|
if (assetData.treePath) {
|
|
671
|
+
candidates.push(path.join('assets', assetData.treePath, filename));
|
|
625
672
|
candidates.push(path.join(assetData.treePath, filename));
|
|
626
673
|
}
|
|
627
|
-
|
|
628
|
-
|
|
674
|
+
// PlayCraft Git 仓库结构:从 path 数组(文件夹 ID 列表)重建 treePath
|
|
675
|
+
if (assetData.path && Array.isArray(assetData.path) && assetData.path.length > 0) {
|
|
676
|
+
const computedTreePath = resolveTreePathFromPathArray(assetData.path, folderFullPathMap);
|
|
677
|
+
if (computedTreePath) {
|
|
678
|
+
candidates.push(path.join('assets', computedTreePath, filename));
|
|
679
|
+
candidates.push(path.join(computedTreePath, filename));
|
|
680
|
+
}
|
|
629
681
|
}
|
|
682
|
+
// 最后尝试直接在 assets/ 根目录下查找(顶层文件)
|
|
683
|
+
candidates.push(path.join('assets', filename));
|
|
630
684
|
let found = false;
|
|
631
685
|
for (const rel of candidates) {
|
|
632
686
|
const abs = path.join(projectDir, rel);
|
|
@@ -687,6 +741,156 @@ async function patchScriptAssets(config, outputDir) {
|
|
|
687
741
|
}
|
|
688
742
|
}
|
|
689
743
|
}
|
|
744
|
+
/**
|
|
745
|
+
* Analyze script file import dependencies and add missing dependency scripts to config.assets.
|
|
746
|
+
* This handles cases like: BoardRandomFiller.mjs imports ./BoardShapeGenerator.mjs,
|
|
747
|
+
* which is not directly referenced in the scene or templates.
|
|
748
|
+
*/
|
|
749
|
+
async function collectScriptImportDependencies(projectConfig, projectDir, importMap, configAssets) {
|
|
750
|
+
const assets = projectConfig.format === 'playcanvas'
|
|
751
|
+
? projectConfig.assets
|
|
752
|
+
: projectConfig.assets;
|
|
753
|
+
// Build folder ID -> name mapping
|
|
754
|
+
const folderMap = new Map();
|
|
755
|
+
for (const [assetId, asset] of Object.entries(assets)) {
|
|
756
|
+
if (asset.type === 'folder') {
|
|
757
|
+
folderMap.set(assetId, asset.name);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// Build reverse mapping: targetPath -> assetId (for ALL script assets, not just configAssets)
|
|
761
|
+
const targetPathToAssetId = new Map();
|
|
762
|
+
for (const [assetId, asset] of Object.entries(assets)) {
|
|
763
|
+
const assetData = asset;
|
|
764
|
+
if (assetData.type !== 'script')
|
|
765
|
+
continue;
|
|
766
|
+
const filename = assetData.file?.filename || assetData.name + '.mjs';
|
|
767
|
+
const pathIds = assetData.path || [];
|
|
768
|
+
const folderPath = buildScriptFolderPath(pathIds, folderMap);
|
|
769
|
+
const targetPath = resolveTargetPathFromImportMap(folderPath, filename, importMap);
|
|
770
|
+
if (targetPath) {
|
|
771
|
+
targetPathToAssetId.set(targetPath.replace(/\\/g, '/'), assetId);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
// Regex to extract import specifiers from script content
|
|
775
|
+
const importRegex = /(from\s+['"])([^'"]+)(['"])|(\bimport\s*\(\s*['"])([^'"]+)(['"]\s*\))/g;
|
|
776
|
+
const importsMap = importMap.content.imports || {};
|
|
777
|
+
// Iteratively discover new script dependencies
|
|
778
|
+
const processedScripts = new Set();
|
|
779
|
+
let newDepsFound = true;
|
|
780
|
+
while (newDepsFound) {
|
|
781
|
+
newDepsFound = false;
|
|
782
|
+
const currentScriptIds = Object.keys(configAssets).filter(id => configAssets[id].type === 'script' && !processedScripts.has(id));
|
|
783
|
+
for (const scriptId of currentScriptIds) {
|
|
784
|
+
processedScripts.add(scriptId);
|
|
785
|
+
const assetData = assets[scriptId];
|
|
786
|
+
if (!assetData)
|
|
787
|
+
continue;
|
|
788
|
+
// Resolve source file path
|
|
789
|
+
const sourcePath = await resolveScriptSourcePath(assetData, scriptId, projectDir);
|
|
790
|
+
if (!sourcePath)
|
|
791
|
+
continue;
|
|
792
|
+
let content;
|
|
793
|
+
try {
|
|
794
|
+
content = await fs.readFile(sourcePath, 'utf-8');
|
|
795
|
+
}
|
|
796
|
+
catch {
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
// Get current script's target path for relative path resolution
|
|
800
|
+
const filename = assetData.file?.filename || assetData.name + '.mjs';
|
|
801
|
+
const pathIds = assetData.path || [];
|
|
802
|
+
const folderPath = buildScriptFolderPath(pathIds, folderMap);
|
|
803
|
+
const scriptTargetPath = resolveTargetPathFromImportMap(folderPath, filename, importMap);
|
|
804
|
+
if (!scriptTargetPath)
|
|
805
|
+
continue;
|
|
806
|
+
const scriptDir = path.dirname(scriptTargetPath).replace(/\\/g, '/');
|
|
807
|
+
// Parse all import statements
|
|
808
|
+
let match;
|
|
809
|
+
importRegex.lastIndex = 0;
|
|
810
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
811
|
+
const modulePath = match[2] || match[5];
|
|
812
|
+
if (!modulePath)
|
|
813
|
+
continue;
|
|
814
|
+
let resolvedTargetPath = null;
|
|
815
|
+
if (modulePath.startsWith('./') || modulePath.startsWith('../')) {
|
|
816
|
+
// Relative import: resolve against script's directory
|
|
817
|
+
const joined = path.posix.join(scriptDir, modulePath);
|
|
818
|
+
resolvedTargetPath = path.posix.normalize(joined);
|
|
819
|
+
}
|
|
820
|
+
else if (!modulePath.startsWith('/') && !modulePath.startsWith('http')) {
|
|
821
|
+
// Import Map alias: resolve via import map
|
|
822
|
+
resolvedTargetPath = resolveModulePathViaImportMap(modulePath, importsMap);
|
|
823
|
+
}
|
|
824
|
+
if (!resolvedTargetPath)
|
|
825
|
+
continue;
|
|
826
|
+
resolvedTargetPath = resolvedTargetPath.replace(/\\/g, '/');
|
|
827
|
+
// Look up the asset by target path
|
|
828
|
+
const depAssetId = targetPathToAssetId.get(resolvedTargetPath);
|
|
829
|
+
if (depAssetId === undefined) {
|
|
830
|
+
console.warn(`Warning: resolvedTargetPath '${resolvedTargetPath}' not found in targetPathToAssetId`);
|
|
831
|
+
}
|
|
832
|
+
else if (!configAssets[depAssetId]) {
|
|
833
|
+
// Found a missing dependency — add it to configAssets
|
|
834
|
+
configAssets[depAssetId] = assets[depAssetId];
|
|
835
|
+
newDepsFound = true;
|
|
836
|
+
console.log(`📜 [ScriptDeps] ${assetData.name} imports ${assets[depAssetId].name} (${depAssetId}) — added to configAssets`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Resolve the source file path for a script asset.
|
|
844
|
+
* @param assets - 可选,所有资产的 map,用于从 path 数组重建 treePath(PlayCraft Git 仓库场景)
|
|
845
|
+
*/
|
|
846
|
+
async function resolveScriptSourcePath(assetData, scriptId, projectDir, assets) {
|
|
847
|
+
if (assetData.file?.url) {
|
|
848
|
+
const fullPath = path.join(projectDir, assetData.file.url);
|
|
849
|
+
try {
|
|
850
|
+
await fs.access(fullPath);
|
|
851
|
+
return fullPath;
|
|
852
|
+
}
|
|
853
|
+
catch {
|
|
854
|
+
// file.url 指向的文件不存在,继续搜索
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (assetData.file?.filename) {
|
|
858
|
+
const filename = assetData.file.filename;
|
|
859
|
+
const revision = assetData.revision ?? 1;
|
|
860
|
+
const candidates = [
|
|
861
|
+
// PlayCanvas 标准结构
|
|
862
|
+
path.join(projectDir, 'files', 'assets', String(scriptId), String(revision), filename),
|
|
863
|
+
path.join(projectDir, 'files', 'assets', String(scriptId), '1', filename),
|
|
864
|
+
path.join(projectDir, 'files', 'assets', String(scriptId), filename),
|
|
865
|
+
];
|
|
866
|
+
// PlayCraft 结构:使用 treePath
|
|
867
|
+
if (assetData.treePath) {
|
|
868
|
+
candidates.push(path.join(projectDir, 'assets', assetData.treePath, filename));
|
|
869
|
+
candidates.push(path.join(projectDir, assetData.treePath, filename));
|
|
870
|
+
}
|
|
871
|
+
// PlayCraft Git 仓库:从 path 数组重建 treePath
|
|
872
|
+
if (assets && assetData.path && Array.isArray(assetData.path) && assetData.path.length > 0) {
|
|
873
|
+
const folderFullPathMap = buildFolderFullPathMap(assets);
|
|
874
|
+
const computedTreePath = resolveTreePathFromPathArray(assetData.path, folderFullPathMap);
|
|
875
|
+
if (computedTreePath) {
|
|
876
|
+
candidates.push(path.join(projectDir, 'assets', computedTreePath, filename));
|
|
877
|
+
candidates.push(path.join(projectDir, computedTreePath, filename));
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// 最后尝试直接在 assets/ 根目录下查找
|
|
881
|
+
candidates.push(path.join(projectDir, 'assets', filename));
|
|
882
|
+
for (const candidate of candidates) {
|
|
883
|
+
try {
|
|
884
|
+
await fs.access(candidate);
|
|
885
|
+
return candidate;
|
|
886
|
+
}
|
|
887
|
+
catch {
|
|
888
|
+
// try next
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return null;
|
|
893
|
+
}
|
|
690
894
|
/**
|
|
691
895
|
* 生成 ESM 模板文件
|
|
692
896
|
*/
|
|
@@ -728,9 +932,13 @@ async function generateESMTemplate(projectConfig, options, config) {
|
|
|
728
932
|
catch (error) {
|
|
729
933
|
console.warn('[SourceBuilder] 警告: 无法复制 ESM 包装器:', error);
|
|
730
934
|
}
|
|
731
|
-
// 3.
|
|
732
|
-
|
|
733
|
-
|
|
935
|
+
// 3. 分析脚本文件的 import 依赖,将缺失的依赖脚本添加到 config.assets
|
|
936
|
+
if (options.importMap) {
|
|
937
|
+
await collectScriptImportDependencies(projectConfig, options.projectDir, options.importMap, config.assets);
|
|
938
|
+
}
|
|
939
|
+
// 4. 收集用户脚本的导入路径(传入 config.assets 以包含 template 依赖 + import 依赖的脚本)
|
|
940
|
+
const scriptImportPaths = await collectESMScriptImports(projectConfig, options, config.assets);
|
|
941
|
+
// 5. 从 Import Map 中提取 gameRule 路径
|
|
734
942
|
let gameRulePath = '';
|
|
735
943
|
if (options.importMap?.content?.imports) {
|
|
736
944
|
const imports = options.importMap.content.imports;
|
|
@@ -743,11 +951,99 @@ async function generateESMTemplate(projectConfig, options, config) {
|
|
|
743
951
|
console.log(`[SourceBuilder] GameRule 路径: ${gameRulePath}`);
|
|
744
952
|
}
|
|
745
953
|
}
|
|
746
|
-
// 5.
|
|
954
|
+
// 5.1 从场景 SystemManager 的 GameConfig.json 中收集 Rule 脚本
|
|
955
|
+
if (options.importMap && projectConfig.format === 'playcanvas') {
|
|
956
|
+
const allAssets = projectConfig.assets || {};
|
|
957
|
+
const pcScenes = projectConfig.scenes;
|
|
958
|
+
// Iterate over selected scenes (from config.scenes) and find the scene data
|
|
959
|
+
for (const sceneRef of (config.scenes || [])) {
|
|
960
|
+
// Extract scene file ID from url (e.g. "4577025.json" -> "4577025")
|
|
961
|
+
const sceneFileId = (sceneRef.url || '').replace(/\.json$/, '');
|
|
962
|
+
const sceneData = pcScenes?.[sceneFileId];
|
|
963
|
+
if (!sceneData?.entities)
|
|
964
|
+
continue;
|
|
965
|
+
// Find SystemManager entity
|
|
966
|
+
for (const [, entity] of Object.entries(sceneData.entities)) {
|
|
967
|
+
if (entity?.name !== 'SystemManager')
|
|
968
|
+
continue;
|
|
969
|
+
const smScript = entity.components?.script?.scripts?.systemManager;
|
|
970
|
+
if (!smScript?.attributes?.gameConfigAsset)
|
|
971
|
+
continue;
|
|
972
|
+
const jsonAssetId = String(smScript.attributes.gameConfigAsset);
|
|
973
|
+
const jsonAsset = allAssets[jsonAssetId];
|
|
974
|
+
if (!jsonAsset || jsonAsset.type !== 'json')
|
|
975
|
+
continue;
|
|
976
|
+
// Read the GameConfig JSON file
|
|
977
|
+
const jsonFilePath = await resolveScriptSourcePath(jsonAsset, jsonAssetId, options.projectDir, allAssets);
|
|
978
|
+
if (!jsonFilePath)
|
|
979
|
+
continue;
|
|
980
|
+
let gameConfig;
|
|
981
|
+
try {
|
|
982
|
+
const content = await fs.readFile(jsonFilePath, 'utf-8');
|
|
983
|
+
gameConfig = JSON.parse(content);
|
|
984
|
+
}
|
|
985
|
+
catch {
|
|
986
|
+
console.warn(`[SourceBuilder] 无法读取 GameConfig.json (${jsonAssetId})`);
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
const ruleName = gameConfig?.Gameplay?.Rule;
|
|
990
|
+
if (!ruleName)
|
|
991
|
+
continue;
|
|
992
|
+
// Resolve Rule script: look for a script asset named "{ruleName}.mjs"
|
|
993
|
+
const ruleFileName = `${ruleName}.mjs`;
|
|
994
|
+
let ruleAssetId = null;
|
|
995
|
+
for (const [assetId, asset] of Object.entries(allAssets)) {
|
|
996
|
+
if (asset.type === 'script' && (asset.file?.filename === ruleFileName || asset.name === ruleFileName)) {
|
|
997
|
+
ruleAssetId = assetId;
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (ruleAssetId && !config.assets[ruleAssetId]) {
|
|
1002
|
+
config.assets[ruleAssetId] = allAssets[ruleAssetId];
|
|
1003
|
+
console.log(`📜 [GameConfig] Rule "${ruleName}" → 资源 ${ruleAssetId} — added to configAssets`);
|
|
1004
|
+
// Also collect its import dependencies
|
|
1005
|
+
if (options.importMap) {
|
|
1006
|
+
await collectScriptImportDependencies(projectConfig, options.projectDir, options.importMap, config.assets);
|
|
1007
|
+
}
|
|
1008
|
+
// Rebuild script import paths to include the new Rule script
|
|
1009
|
+
const newPaths = await collectESMScriptImports(projectConfig, options, config.assets);
|
|
1010
|
+
for (const p of newPaths) {
|
|
1011
|
+
if (!scriptImportPaths.includes(p)) {
|
|
1012
|
+
scriptImportPaths.push(p);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
else if (ruleAssetId) {
|
|
1017
|
+
console.log(`📜 [GameConfig] Rule "${ruleName}" → 资源 ${ruleAssetId} — already in configAssets`);
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
console.warn(`⚠️ [GameConfig] Rule "${ruleName}" 对应的脚本 ${ruleFileName} 未找到`);
|
|
1021
|
+
}
|
|
1022
|
+
// Use the rule path from 5.1 if Import Map didn't provide a gameRule mapping (step 5)
|
|
1023
|
+
if (ruleAssetId && !gameRulePath) {
|
|
1024
|
+
const ruleAsset = allAssets[ruleAssetId];
|
|
1025
|
+
// Build folder map for path resolution
|
|
1026
|
+
const folderMap = new Map();
|
|
1027
|
+
for (const [aid, a] of Object.entries(allAssets)) {
|
|
1028
|
+
if (a.type === 'folder')
|
|
1029
|
+
folderMap.set(aid, a.name);
|
|
1030
|
+
}
|
|
1031
|
+
const ruleFilename = ruleAsset.file?.filename || ruleAsset.name + '.mjs';
|
|
1032
|
+
const ruleFolderPath = buildScriptFolderPath(ruleAsset.path || [], folderMap);
|
|
1033
|
+
const targetPath = resolveTargetPathFromImportMap(ruleFolderPath, ruleFilename, options.importMap);
|
|
1034
|
+
if (targetPath) {
|
|
1035
|
+
gameRulePath = '../' + targetPath;
|
|
1036
|
+
console.log(`📜 [GameConfig] 使用 5.1 收集的 Rule 路径: ${gameRulePath}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
// 6. 生成 js/index.mjs(包含脚本导入),使用过滤后的 config.scenes
|
|
747
1043
|
await generateESMEntry(projectConfig, outputDir, scriptImportPaths, config.scenes, gameRulePath);
|
|
748
|
-
//
|
|
1044
|
+
// 7. 在 ESM 模式下,复制用户脚本为独立文件,并更新 config.assets 中的 URL
|
|
749
1045
|
if (options.importMap) {
|
|
750
|
-
const scriptUrlMap = await copyUserScriptsForESM(projectConfig, options.projectDir, outputDir, options.importMap);
|
|
1046
|
+
const scriptUrlMap = await copyUserScriptsForESM(projectConfig, options.projectDir, outputDir, options.importMap, config.assets);
|
|
751
1047
|
console.log('[SourceBuilder] 用户脚本已复制为独立 ESM 模块');
|
|
752
1048
|
// 更新 config.assets 中脚本的 URL
|
|
753
1049
|
if (scriptUrlMap.size > 0 && config.assets) {
|
|
@@ -760,10 +1056,10 @@ async function generateESMTemplate(projectConfig, options, config) {
|
|
|
760
1056
|
await fs.writeFile(path.join(outputDir, 'config.json'), JSON.stringify(config, null, 2), 'utf-8');
|
|
761
1057
|
console.log(`[SourceBuilder] 更新了 ${scriptUrlMap.size} 个脚本的 URL`);
|
|
762
1058
|
}
|
|
763
|
-
//
|
|
1059
|
+
// 8. 复制 Import Map 中引用的第三方库文件
|
|
764
1060
|
await copyImportMapLibraries(projectConfig, options.projectDir, outputDir, options.importMap);
|
|
765
1061
|
}
|
|
766
|
-
//
|
|
1062
|
+
// 9. 复制其他必要的模板文件
|
|
767
1063
|
const templateFiles = [
|
|
768
1064
|
'__start__.js', // 可能不需要了,但先保留
|
|
769
1065
|
'__modules__.js',
|
|
@@ -804,20 +1100,43 @@ function fixImportMapPaths(importMap) {
|
|
|
804
1100
|
}
|
|
805
1101
|
/**
|
|
806
1102
|
* 收集 ESM 脚本的导入路径
|
|
1103
|
+
* @param configAssets - generateConfig 过滤后的 config.assets,包含 template 依赖的脚本
|
|
807
1104
|
*/
|
|
808
|
-
async function collectESMScriptImports(projectConfig, options) {
|
|
809
|
-
//
|
|
810
|
-
let scriptIds;
|
|
1105
|
+
async function collectESMScriptImports(projectConfig, options, configAssets) {
|
|
1106
|
+
// 获取资产(支持 PlayCanvas 和 PlayCraft 两种格式)
|
|
811
1107
|
let assets;
|
|
812
1108
|
if (projectConfig.format === 'playcanvas') {
|
|
813
|
-
|
|
814
|
-
scriptIds = pcProject.project.settings?.scripts || [];
|
|
815
|
-
assets = pcProject.assets;
|
|
1109
|
+
assets = projectConfig.assets;
|
|
816
1110
|
}
|
|
817
1111
|
else {
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1112
|
+
assets = projectConfig.assets;
|
|
1113
|
+
}
|
|
1114
|
+
// 收集所有需要导入的脚本 ID:
|
|
1115
|
+
// 1. config.assets 中的 script 类型资产(已包含场景依赖 + template 依赖的脚本)
|
|
1116
|
+
// 2. 回退到 settings.scripts(当没有 configAssets 时)
|
|
1117
|
+
const scriptIdsToImport = new Set();
|
|
1118
|
+
if (configAssets) {
|
|
1119
|
+
console.log(`📜 [DEBUG-ESMImports] 使用 configAssets 收集脚本, configAssets 总数: ${Object.keys(configAssets).length}`);
|
|
1120
|
+
for (const [assetId, asset] of Object.entries(configAssets)) {
|
|
1121
|
+
if (asset.type === 'script') {
|
|
1122
|
+
scriptIdsToImport.add(assetId);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
console.log(`[ESMImports] 从 configAssets 收集到 ${scriptIdsToImport.size} 个脚本`);
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
console.log(`[ESMImports] configAssets 未传入, 回退到 settings.scripts`);
|
|
1129
|
+
// Fallback: use settings.scripts
|
|
1130
|
+
let scriptIds;
|
|
1131
|
+
if (projectConfig.format === 'playcanvas') {
|
|
1132
|
+
scriptIds = projectConfig.project.settings?.scripts || [];
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
scriptIds = projectConfig.manifest.settings?.scripts || [];
|
|
1136
|
+
}
|
|
1137
|
+
for (const id of scriptIds) {
|
|
1138
|
+
scriptIdsToImport.add(String(id));
|
|
1139
|
+
}
|
|
821
1140
|
}
|
|
822
1141
|
const importPaths = [];
|
|
823
1142
|
// 构建文件夹 ID 到名称的映射
|
|
@@ -829,15 +1148,11 @@ async function collectESMScriptImports(projectConfig, options) {
|
|
|
829
1148
|
}
|
|
830
1149
|
}
|
|
831
1150
|
// 遍历每个脚本,构建导入路径
|
|
832
|
-
for (const scriptId of
|
|
1151
|
+
for (const scriptId of scriptIdsToImport) {
|
|
833
1152
|
const asset = assets[String(scriptId)];
|
|
834
1153
|
if (!asset || asset.type !== 'script')
|
|
835
1154
|
continue;
|
|
836
1155
|
const assetData = asset;
|
|
837
|
-
// 检查 preload 属性
|
|
838
|
-
const preload = assetData.preload !== false;
|
|
839
|
-
if (!preload)
|
|
840
|
-
continue;
|
|
841
1156
|
// 获取正确的文件名(带扩展名)
|
|
842
1157
|
const filename = assetData.file?.filename || assetData.name + '.mjs';
|
|
843
1158
|
// 获取脚本的文件夹路径
|
|
@@ -919,7 +1234,7 @@ async function generateESMEntry(projectConfig, outputDir, scriptImportPaths = []
|
|
|
919
1234
|
const preloadModules = [];
|
|
920
1235
|
// TODO: 从 assets 中提取 WASM 模块
|
|
921
1236
|
// 生成脚本导入路径数组(延迟加载)
|
|
922
|
-
console.log(`[
|
|
1237
|
+
console.log(`[ESMEntry] 生成 ${scriptImportPaths.length} 个脚本导入路径`);
|
|
923
1238
|
// 替换模板占位符
|
|
924
1239
|
const entry = esmTemplate
|
|
925
1240
|
.replace(/\{\{SCRIPT_IMPORT_PATHS\}\}/g, JSON.stringify(scriptImportPaths))
|
|
@@ -939,25 +1254,43 @@ async function generateESMEntry(projectConfig, outputDir, scriptImportPaths = []
|
|
|
939
1254
|
}
|
|
940
1255
|
/**
|
|
941
1256
|
* 在 ESM 模式下复制用户脚本为独立文件
|
|
1257
|
+
* @param configAssets - generateConfig 过滤后的 config.assets,包含 template 依赖的脚本
|
|
942
1258
|
* @returns 脚本ID到新URL的映射
|
|
943
1259
|
*/
|
|
944
|
-
async function copyUserScriptsForESM(projectConfig, projectDir, outputDir, importMap) {
|
|
1260
|
+
async function copyUserScriptsForESM(projectConfig, projectDir, outputDir, importMap, configAssets) {
|
|
945
1261
|
const scriptUrlMap = new Map();
|
|
946
|
-
|
|
947
|
-
// 获取脚本 ID 列表和资产(支持 PlayCanvas 和 PlayCraft 两种格式)
|
|
948
|
-
let scriptIds;
|
|
1262
|
+
// 获取资产(支持 PlayCanvas 和 PlayCraft 两种格式)
|
|
949
1263
|
let assets;
|
|
950
1264
|
if (projectConfig.format === 'playcanvas') {
|
|
951
|
-
|
|
952
|
-
scriptIds = pcProject.project.settings?.scripts || [];
|
|
953
|
-
assets = pcProject.assets;
|
|
1265
|
+
assets = projectConfig.assets;
|
|
954
1266
|
}
|
|
955
1267
|
else {
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1268
|
+
assets = projectConfig.assets;
|
|
1269
|
+
}
|
|
1270
|
+
// 收集所有需要复制的脚本 ID:
|
|
1271
|
+
// 1. config.assets 中的 script 类型资产(已包含场景依赖 + template 依赖的脚本)
|
|
1272
|
+
// 2. 回退到 settings.scripts(当没有 configAssets 时)
|
|
1273
|
+
const scriptIdsToProcess = new Set();
|
|
1274
|
+
if (configAssets) {
|
|
1275
|
+
for (const [assetId, asset] of Object.entries(configAssets)) {
|
|
1276
|
+
if (asset.type === 'script') {
|
|
1277
|
+
scriptIdsToProcess.add(assetId);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
959
1280
|
}
|
|
960
|
-
|
|
1281
|
+
else {
|
|
1282
|
+
let scriptIds;
|
|
1283
|
+
if (projectConfig.format === 'playcanvas') {
|
|
1284
|
+
scriptIds = projectConfig.project.settings?.scripts || [];
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
scriptIds = projectConfig.manifest.settings?.scripts || [];
|
|
1288
|
+
}
|
|
1289
|
+
for (const id of scriptIds) {
|
|
1290
|
+
scriptIdsToProcess.add(String(id));
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
console.log(`[copyUserScriptsForESM] 复制 ${scriptIdsToProcess.size} 个脚本`);
|
|
961
1294
|
// 构建文件夹 ID 到名称的映射
|
|
962
1295
|
const folderMap = new Map();
|
|
963
1296
|
for (const [assetId, asset] of Object.entries(assets)) {
|
|
@@ -967,42 +1300,13 @@ async function copyUserScriptsForESM(projectConfig, projectDir, outputDir, impor
|
|
|
967
1300
|
}
|
|
968
1301
|
}
|
|
969
1302
|
// 遍历每个脚本
|
|
970
|
-
for (const scriptId of
|
|
1303
|
+
for (const scriptId of scriptIdsToProcess) {
|
|
971
1304
|
const asset = assets[String(scriptId)];
|
|
972
1305
|
if (!asset || asset.type !== 'script')
|
|
973
1306
|
continue;
|
|
974
1307
|
const assetData = asset;
|
|
975
|
-
//
|
|
976
|
-
const
|
|
977
|
-
if (!preload) {
|
|
978
|
-
continue; // 跳过 non-preload 脚本
|
|
979
|
-
}
|
|
980
|
-
// 获取脚本源文件路径
|
|
981
|
-
let sourcePath = null;
|
|
982
|
-
const scriptIdStr = String(scriptId);
|
|
983
|
-
if (assetData.file?.url) {
|
|
984
|
-
sourcePath = path.join(projectDir, assetData.file.url);
|
|
985
|
-
}
|
|
986
|
-
else if (assetData.file?.filename) {
|
|
987
|
-
// 如果没有 file.url,根据 filename 和 revision 构建路径
|
|
988
|
-
const filename = assetData.file.filename;
|
|
989
|
-
const revision = assetData.revision ?? 1;
|
|
990
|
-
const candidates = [
|
|
991
|
-
path.join(projectDir, 'files', 'assets', scriptIdStr, String(revision), filename),
|
|
992
|
-
path.join(projectDir, 'files', 'assets', scriptIdStr, '1', filename),
|
|
993
|
-
path.join(projectDir, 'files', 'assets', scriptIdStr, filename),
|
|
994
|
-
];
|
|
995
|
-
for (const candidate of candidates) {
|
|
996
|
-
try {
|
|
997
|
-
await fs.access(candidate);
|
|
998
|
-
sourcePath = candidate;
|
|
999
|
-
break;
|
|
1000
|
-
}
|
|
1001
|
-
catch {
|
|
1002
|
-
// 继续尝试下一个
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1308
|
+
// 获取脚本源文件路径(使用统一的解析函数,支持 PlayCraft Git 仓库结构)
|
|
1309
|
+
const sourcePath = await resolveScriptSourcePath(assetData, String(scriptId), projectDir, assets);
|
|
1006
1310
|
if (!sourcePath) {
|
|
1007
1311
|
console.warn(`[ESM] 无法找到脚本 ${assetData.name} 的源文件,跳过`);
|
|
1008
1312
|
continue;
|
|
@@ -1312,7 +1616,7 @@ async function copyImportMapLibraries(projectConfig, projectDir, outputDir, impo
|
|
|
1312
1616
|
// 文件不存在,需要复制
|
|
1313
1617
|
}
|
|
1314
1618
|
// 查找源文件
|
|
1315
|
-
// 1. 首先检查是否是 asset 引用(通过 name
|
|
1619
|
+
// 1. 首先检查是否是 asset 引用(通过 name 查找),使用统一的解析函数
|
|
1316
1620
|
let sourceFound = false;
|
|
1317
1621
|
const filename = path.basename(outputRelPath);
|
|
1318
1622
|
for (const [assetId, asset] of Object.entries(assets)) {
|
|
@@ -1321,29 +1625,8 @@ async function copyImportMapLibraries(projectConfig, projectDir, outputDir, impo
|
|
|
1321
1625
|
continue;
|
|
1322
1626
|
if (assetData.name !== key && assetData.file?.filename !== filename)
|
|
1323
1627
|
continue;
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1326
|
-
if (assetData.file?.url) {
|
|
1327
|
-
sourcePath = path.join(projectDir, assetData.file.url);
|
|
1328
|
-
}
|
|
1329
|
-
else if (assetData.file?.filename) {
|
|
1330
|
-
const revision = assetData.revision ?? 1;
|
|
1331
|
-
const candidates = [
|
|
1332
|
-
path.join(projectDir, 'files', 'assets', assetId, String(revision), assetData.file.filename),
|
|
1333
|
-
path.join(projectDir, 'files', 'assets', assetId, '1', assetData.file.filename),
|
|
1334
|
-
path.join(projectDir, 'files', 'assets', assetId, assetData.file.filename),
|
|
1335
|
-
];
|
|
1336
|
-
for (const candidate of candidates) {
|
|
1337
|
-
try {
|
|
1338
|
-
await fs.access(candidate);
|
|
1339
|
-
sourcePath = candidate;
|
|
1340
|
-
break;
|
|
1341
|
-
}
|
|
1342
|
-
catch {
|
|
1343
|
-
// 继续尝试
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1628
|
+
// 使用统一的解析函数查找源文件(支持 PlayCraft Git 仓库结构)
|
|
1629
|
+
const sourcePath = await resolveScriptSourcePath(assetData, assetId, projectDir, assets);
|
|
1347
1630
|
if (sourcePath) {
|
|
1348
1631
|
try {
|
|
1349
1632
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
@@ -1365,6 +1648,9 @@ async function copyImportMapLibraries(projectConfig, projectDir, outputDir, impo
|
|
|
1365
1648
|
path.join(projectDir, originalPath),
|
|
1366
1649
|
path.join(projectDir, filename),
|
|
1367
1650
|
path.join(projectDir, 'Lib', filename),
|
|
1651
|
+
// PlayCraft Git 仓库结构
|
|
1652
|
+
path.join(projectDir, 'assets', filename),
|
|
1653
|
+
path.join(projectDir, 'assets', originalPath),
|
|
1368
1654
|
];
|
|
1369
1655
|
for (const candidate of candidates) {
|
|
1370
1656
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/build",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
11
12
|
"types": "./dist/index.d.ts"
|
|
12
13
|
},
|
|
13
14
|
"./types": {
|
|
14
15
|
"import": "./dist/types.js",
|
|
16
|
+
"require": "./dist/types.js",
|
|
15
17
|
"types": "./dist/types.d.ts"
|
|
16
|
-
}
|
|
18
|
+
},
|
|
19
|
+
"./dist/*": "./dist/*"
|
|
17
20
|
},
|
|
18
21
|
"files": [
|
|
19
22
|
"dist",
|
|
@@ -42,8 +45,8 @@
|
|
|
42
45
|
"sharp": "^0.34.5",
|
|
43
46
|
"javascript-obfuscator": "^5.3.0",
|
|
44
47
|
"terser": "^5.46.0",
|
|
45
|
-
"vite": "^
|
|
46
|
-
"vite-plugin-singlefile": "^2.3.
|
|
48
|
+
"vite": "^8.0.3",
|
|
49
|
+
"vite-plugin-singlefile": "^2.3.2"
|
|
47
50
|
},
|
|
48
51
|
"devDependencies": {
|
|
49
52
|
"@types/archiver": "^6.0.4",
|