@playcraft/cli 0.0.32 → 0.0.34
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/commands/build.js +3 -3
- package/dist/commands/prefab.js +224 -59
- package/package.json +3 -3
package/dist/commands/build.js
CHANGED
|
@@ -343,12 +343,12 @@ export async function buildCommand(projectPath, options) {
|
|
|
343
343
|
console.log(`\n🎬 选中场景: 全部场景`);
|
|
344
344
|
}
|
|
345
345
|
else if (scenesInput === 'default') {
|
|
346
|
-
//
|
|
346
|
+
// 打包默认场景(manifest.scenes 首位 = 当前活跃场景)
|
|
347
347
|
const projectScenes = await detectProjectScenes(resolvedProjectPath);
|
|
348
|
-
const defaultScene = projectScenes
|
|
348
|
+
const defaultScene = projectScenes[0];
|
|
349
349
|
if (defaultScene) {
|
|
350
350
|
selectedScenes = [defaultScene.name || String(defaultScene.id)];
|
|
351
|
-
console.log(`\n🎬 选中场景: ${defaultScene.name || defaultScene.id}
|
|
351
|
+
console.log(`\n🎬 选中场景: ${defaultScene.name || defaultScene.id} (默认场景)`);
|
|
352
352
|
}
|
|
353
353
|
else {
|
|
354
354
|
console.log(`\n⚠️ 未找到默认场景,将打包所有场景`);
|
package/dist/commands/prefab.js
CHANGED
|
@@ -2,8 +2,10 @@ import { inspect } from 'node:util';
|
|
|
2
2
|
import { readFileSync, writeFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
3
3
|
import { join, resolve } from 'path';
|
|
4
4
|
import JSON5 from 'json5';
|
|
5
|
-
import { THEME_SCHEMA_PATH, THEME_INDEX_PATH, THEME_DIR, DEFAULT_GAME_PATH, MANIFEST_PATH, ASSETS_JSON_PATH, LITE_CREATOR_EXTENSIONS_DIR, themeDataPath, scenePath, defaultValueFromJsonSchemaProperty, buildThemeIndexTs, parseThemeIndexImportPath, parseThemePrefabs, mergeThemeDataKey, deleteThemeDataKey, parseExtensionMetaData, parseGameConfig, buildExtensionPrefabs, updateGameConfigValue, updateGameConfigExtensions, findConfigKeyCaseInsensitive, parseConfigKey, listScenesFromManifest, resolveGameConfigPath, validateThemeValue, validateConfigValue, describeJsonSchemaFields, describeConfigSchemaFields, getPrefabFieldDescriptors, getPrefabFieldDiffs, prefabHasSchemaDiff, } from '@playcraft/common/prefab';
|
|
5
|
+
import { THEME_SCHEMA_PATH, THEME_INDEX_PATH, THEME_DIR, DEFAULT_GAME_PATH, MANIFEST_PATH, ASSETS_JSON_PATH, LITE_CREATOR_EXTENSIONS_DIR, themeDataPath, scenePath, defaultValueFromJsonSchemaProperty, buildThemeIndexTs, parseThemeIndexImportPath, parseThemePrefabs, mergeThemeDataKey, deleteThemeDataKey, parseExtensionMetaData, parseGameConfig, buildExtensionPrefabs, updateGameConfigValue, updateGameConfigExtensions, findConfigKeyCaseInsensitive, parseConfigKey, listScenesFromManifest, listScenesWithGameConfig, resolveGameConfigPath, getActiveSceneIdFromManifest, reorderManifestDefaultScene, validateThemeValue, validateConfigValue, describeJsonSchemaFields, describeConfigSchemaFields, getPrefabFieldDescriptors, getPrefabFieldDiffs, prefabHasSchemaDiff, } from '@playcraft/common/prefab';
|
|
6
6
|
const DEFAULT_PAGE_LIMIT = 50;
|
|
7
|
+
/** 场景在 manifest / CLI 中列出但解析不到 GameConfig 时的人读标签 */
|
|
8
|
+
const NO_SCENE_GAMECONFIG_LABEL = '(无场景级 GameConfig)';
|
|
7
9
|
/** 无法识别 External / PlayCanvas 项目类型(供测试在 mock process.exit 后通过 instanceof 识别)。 */
|
|
8
10
|
export class ProjectDetectError extends Error {
|
|
9
11
|
name = 'ProjectDetectError';
|
|
@@ -186,7 +188,7 @@ function resolveVariantResolved(projectDir, systemType, opts) {
|
|
|
186
188
|
if (systemType === 'external-theme') {
|
|
187
189
|
return resolveThemeId(projectDir, opts);
|
|
188
190
|
}
|
|
189
|
-
return
|
|
191
|
+
return getMainSceneId(projectDir);
|
|
190
192
|
}
|
|
191
193
|
// ─── External Theme helpers ──────────────────────────────────────────────────
|
|
192
194
|
function loadThemeSchema(projectDir) {
|
|
@@ -240,6 +242,22 @@ function resolveThemeId(projectDir, opts) {
|
|
|
240
242
|
throw new Error('unreachable');
|
|
241
243
|
}
|
|
242
244
|
// ─── PlayCanvas / LiteCreator helpers ────────────────────────────────────────
|
|
245
|
+
/**
|
|
246
|
+
* 当前活跃场景 ID = manifest.scenes[0]。
|
|
247
|
+
*
|
|
248
|
+
* 与 Remix 编辑器和后端 `setDefaultScene`(将目标场景移到首位)的 SSOT 一致。
|
|
249
|
+
*/
|
|
250
|
+
function getMainSceneId(projectDir) {
|
|
251
|
+
if (!fileExists(projectDir, MANIFEST_PATH))
|
|
252
|
+
return null;
|
|
253
|
+
try {
|
|
254
|
+
const manifestJson = JSON.parse(readLocalFile(projectDir, MANIFEST_PATH));
|
|
255
|
+
return getActiveSceneIdFromManifest(manifestJson);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
243
261
|
function loadGameConfig(projectDir, configPath) {
|
|
244
262
|
const raw = readLocalFile(projectDir, configPath);
|
|
245
263
|
return JSON.parse(raw);
|
|
@@ -266,24 +284,102 @@ function scanExtensionMetaData(projectDir) {
|
|
|
266
284
|
}
|
|
267
285
|
return map;
|
|
268
286
|
}
|
|
269
|
-
function
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
287
|
+
function extensionMetaDataFile(projectDir, extensionName) {
|
|
288
|
+
return join(projectDir, LITE_CREATOR_EXTENSIONS_DIR, extensionName, 'MetaData.json');
|
|
289
|
+
}
|
|
290
|
+
function mutateExtensionMetaData(projectDir, extensionName, apply) {
|
|
291
|
+
const metaPath = extensionMetaDataFile(projectDir, extensionName);
|
|
292
|
+
const raw = readFileSync(metaPath, 'utf-8');
|
|
293
|
+
const metaData = JSON.parse(raw);
|
|
294
|
+
const configSchema = metaData.configSchema;
|
|
295
|
+
if (!configSchema) {
|
|
296
|
+
throw new Error(`MetaData.json for "${extensionName}" has no configSchema`);
|
|
297
|
+
}
|
|
298
|
+
apply(configSchema);
|
|
299
|
+
writeFileSync(metaPath, JSON.stringify(metaData, null, 2), 'utf-8');
|
|
300
|
+
}
|
|
301
|
+
function writeMetaDataDefault(projectDir, extensionName, field, value) {
|
|
302
|
+
mutateExtensionMetaData(projectDir, extensionName, (configSchema) => {
|
|
303
|
+
if (!configSchema[field]) {
|
|
304
|
+
throw new Error(`MetaData.json for "${extensionName}" has no configSchema.${field}`);
|
|
305
|
+
}
|
|
306
|
+
configSchema[field].default = value;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
function writeMetaDataDefaults(projectDir, extensionName, values) {
|
|
310
|
+
mutateExtensionMetaData(projectDir, extensionName, (configSchema) => {
|
|
311
|
+
for (const [field, value] of Object.entries(values)) {
|
|
312
|
+
if (configSchema[field]) {
|
|
313
|
+
configSchema[field].default = value;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/** 仅按场景 ID 解析 GameConfig(不查活跃场景)。 */
|
|
319
|
+
function resolveGameConfigPathForSceneId(projectDir, sceneId) {
|
|
320
|
+
if (!fileExists(projectDir, scenePath(sceneId)))
|
|
321
|
+
return null;
|
|
322
|
+
const sceneJson = JSON.parse(readLocalFile(projectDir, scenePath(sceneId)));
|
|
275
323
|
if (!fileExists(projectDir, ASSETS_JSON_PATH))
|
|
276
|
-
return
|
|
324
|
+
return null;
|
|
277
325
|
const assetsJson = JSON.parse(readLocalFile(projectDir, ASSETS_JSON_PATH));
|
|
278
|
-
|
|
279
|
-
|
|
326
|
+
return resolveGameConfigPath(sceneJson, assetsJson);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* 解析当前变体下的 GameConfig 路径。无法解析时返回 null(list 回退 DefaultGame 扩展;set 可写 MetaData)。
|
|
330
|
+
* 未传 variantId 时用 manifest 首位场景(当前活跃场景)。
|
|
331
|
+
*/
|
|
332
|
+
function resolveGameConfigPathForVariant(projectDir, variantId) {
|
|
333
|
+
const sceneId = variantId ?? getMainSceneId(projectDir);
|
|
334
|
+
if (!sceneId)
|
|
335
|
+
return null;
|
|
336
|
+
return resolveGameConfigPathForSceneId(projectDir, sceneId);
|
|
337
|
+
}
|
|
338
|
+
/** PlayCanvas:`prefab list` 等人读「变体上下文」一行话。 */
|
|
339
|
+
function liteCreatorVariantHumanNote(projectDir, explicitVariant) {
|
|
340
|
+
if (explicitVariant) {
|
|
341
|
+
const p = resolveGameConfigPathForSceneId(projectDir, explicitVariant);
|
|
342
|
+
return p != null ? `场景 ${explicitVariant} → ${p}` : `场景 ${explicitVariant}(${NO_SCENE_GAMECONFIG_LABEL};set 可走 MetaData)`;
|
|
343
|
+
}
|
|
344
|
+
const mainId = getMainSceneId(projectDir);
|
|
345
|
+
const gc = resolveGameConfigPathForVariant(projectDir, undefined);
|
|
346
|
+
if (mainId != null) {
|
|
347
|
+
return gc != null
|
|
348
|
+
? `活跃场景 ${mainId} → ${gc}`
|
|
349
|
+
: `活跃场景 ${mainId}(未解析到 GameConfig;list 读 ${DEFAULT_GAME_PATH} 扩展,set 写 MetaData)`;
|
|
350
|
+
}
|
|
351
|
+
return `无场景 → 扩展列表:${DEFAULT_GAME_PATH};set / set-batch:各扩展 MetaData.json`;
|
|
280
352
|
}
|
|
281
353
|
function listLocalScenes(projectDir) {
|
|
282
354
|
if (!fileExists(projectDir, MANIFEST_PATH))
|
|
283
355
|
return [];
|
|
284
356
|
const manifestJson = JSON.parse(readLocalFile(projectDir, MANIFEST_PATH));
|
|
357
|
+
if (fileExists(projectDir, ASSETS_JSON_PATH)) {
|
|
358
|
+
const assetsJson = JSON.parse(readLocalFile(projectDir, ASSETS_JSON_PATH));
|
|
359
|
+
return listScenesWithGameConfig(manifestJson, (sceneId) => {
|
|
360
|
+
const sp = scenePath(sceneId);
|
|
361
|
+
if (!fileExists(projectDir, sp))
|
|
362
|
+
return null;
|
|
363
|
+
return JSON.parse(readLocalFile(projectDir, sp));
|
|
364
|
+
}, assetsJson);
|
|
365
|
+
}
|
|
285
366
|
return listScenesFromManifest(manifestJson);
|
|
286
367
|
}
|
|
368
|
+
/** 列表展示:补全 GameConfig 路径,并用活跃场景 ID 标记 isActive(与未指定 --variant 的 prefab 上下文一致)。 */
|
|
369
|
+
function annotatePlaycanvasScenesForMainScene(projectDir, scenes) {
|
|
370
|
+
const mainSceneId = getMainSceneId(projectDir);
|
|
371
|
+
const annotated = scenes.map((s) => {
|
|
372
|
+
const resolved = s.gameConfigPath ?? resolveGameConfigPathForSceneId(projectDir, s.id);
|
|
373
|
+
const out = {
|
|
374
|
+
...s,
|
|
375
|
+
isActive: mainSceneId !== null && s.id === mainSceneId,
|
|
376
|
+
};
|
|
377
|
+
if (resolved != null)
|
|
378
|
+
out.gameConfigPath = resolved;
|
|
379
|
+
return out;
|
|
380
|
+
});
|
|
381
|
+
return { scenes: annotated, mainSceneId };
|
|
382
|
+
}
|
|
287
383
|
function loadPrefabs(projectDir, systemType, opts) {
|
|
288
384
|
if (systemType === 'external-theme') {
|
|
289
385
|
const themeId = resolveThemeId(projectDir, opts);
|
|
@@ -291,11 +387,19 @@ function loadPrefabs(projectDir, systemType, opts) {
|
|
|
291
387
|
const data = loadThemeData(projectDir, themeId);
|
|
292
388
|
return parseThemePrefabs(schema, data, themeId);
|
|
293
389
|
}
|
|
294
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
295
|
-
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
296
|
-
const { extensions, config } = parseGameConfig(gameConfigJson);
|
|
297
390
|
const metaMap = scanExtensionMetaData(projectDir);
|
|
298
|
-
|
|
391
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
392
|
+
if (configPath) {
|
|
393
|
+
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
394
|
+
const { extensions, config } = parseGameConfig(gameConfigJson);
|
|
395
|
+
return buildExtensionPrefabs(metaMap, { extensions, config }, configPath);
|
|
396
|
+
}
|
|
397
|
+
if (fileExists(projectDir, DEFAULT_GAME_PATH)) {
|
|
398
|
+
const defaultGameJson = loadGameConfig(projectDir, DEFAULT_GAME_PATH);
|
|
399
|
+
const { extensions } = parseGameConfig(defaultGameJson);
|
|
400
|
+
return buildExtensionPrefabs(metaMap, { extensions, config: {} }, DEFAULT_GAME_PATH);
|
|
401
|
+
}
|
|
402
|
+
return buildExtensionPrefabs(metaMap, { extensions: [], config: {} }, '');
|
|
299
403
|
}
|
|
300
404
|
// ─── dotpath helper ──────────────────────────────────────────────────────────
|
|
301
405
|
function getByDotPath(obj, path) {
|
|
@@ -422,8 +526,18 @@ export function registerPrefabCommands(program) {
|
|
|
422
526
|
});
|
|
423
527
|
}
|
|
424
528
|
else {
|
|
425
|
-
const
|
|
426
|
-
const
|
|
529
|
+
const scenesRaw = listLocalScenes(projectDir);
|
|
530
|
+
const { scenes, mainSceneId } = annotatePlaycanvasScenesForMainScene(projectDir, scenesRaw);
|
|
531
|
+
const gameConfigPath = resolveGameConfigPathForVariant(projectDir, undefined);
|
|
532
|
+
const payload = {
|
|
533
|
+
projectType: 'playcanvas',
|
|
534
|
+
variants: scenes,
|
|
535
|
+
context: {
|
|
536
|
+
mainSceneId,
|
|
537
|
+
gameConfigPath,
|
|
538
|
+
usesMetaDataForSet: gameConfigPath == null,
|
|
539
|
+
},
|
|
540
|
+
};
|
|
427
541
|
outputResult(asJson, payload, () => {
|
|
428
542
|
console.log(`项目类型: ${projectTypeLabel('lite-creator')}`);
|
|
429
543
|
console.log('');
|
|
@@ -432,8 +546,19 @@ export function registerPrefabCommands(program) {
|
|
|
432
546
|
return;
|
|
433
547
|
}
|
|
434
548
|
for (const s of scenes) {
|
|
435
|
-
const
|
|
436
|
-
|
|
549
|
+
const mark = s.isActive ? ' *' : '';
|
|
550
|
+
const pathLabel = s.gameConfigPath ?? NO_SCENE_GAMECONFIG_LABEL;
|
|
551
|
+
console.log(` ${s.name} (${s.id}) [${pathLabel}]${mark}`);
|
|
552
|
+
}
|
|
553
|
+
if (scenes.some((v) => v.isActive)) {
|
|
554
|
+
console.log('');
|
|
555
|
+
console.log('标 *:manifest 首位场景(当前活跃场景,与 Remix 编辑器一致)。' +
|
|
556
|
+
' 未带 --variant:优先该场景 GameConfig;若无路径则 list 读 DefaultGame 扩展、set 写 MetaData。' +
|
|
557
|
+
' 可用 `prefab switch <sceneId>` 切换活跃场景。');
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
console.log('');
|
|
561
|
+
console.log(`无场景:未带 --variant 时扩展来自 ${DEFAULT_GAME_PATH},set / set-batch 写入各扩展 MetaData.json。`);
|
|
437
562
|
}
|
|
438
563
|
});
|
|
439
564
|
}
|
|
@@ -466,6 +591,9 @@ export function registerPrefabCommands(program) {
|
|
|
466
591
|
if (systemType === 'external-theme' && !opts.variant) {
|
|
467
592
|
variantNote = resolveThemeId(projectDir, opts);
|
|
468
593
|
}
|
|
594
|
+
else if (systemType === 'lite-creator') {
|
|
595
|
+
variantNote = liteCreatorVariantHumanNote(projectDir, opts.variant);
|
|
596
|
+
}
|
|
469
597
|
const summary = buildSummary(systemType, variantResolved, all);
|
|
470
598
|
const filtered = filterPrefabsList(all, {
|
|
471
599
|
match: opts.match,
|
|
@@ -818,18 +946,12 @@ export function registerPrefabCommands(program) {
|
|
|
818
946
|
});
|
|
819
947
|
}
|
|
820
948
|
else {
|
|
821
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
822
|
-
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
823
|
-
const { config } = parseGameConfig(gameConfigJson);
|
|
824
949
|
const metaMap = scanExtensionMetaData(projectDir);
|
|
825
950
|
const meta = metaMap.get(key);
|
|
826
951
|
if (!meta) {
|
|
827
952
|
console.error(`Error: extension "${key}" 不存在。`);
|
|
828
953
|
process.exit(1);
|
|
829
954
|
}
|
|
830
|
-
const configKey = meta.configPath ? parseConfigKey(meta.configPath) : meta.name;
|
|
831
|
-
const existingKey = findConfigKeyCaseInsensitive(config, configKey);
|
|
832
|
-
const currentValues = (existingKey ? config[existingKey] : {}) ?? {};
|
|
833
955
|
const fieldDef = meta.configSchema?.[field];
|
|
834
956
|
const fieldType = fieldDef?.type ?? 'string';
|
|
835
957
|
const coerced = coerceValue(rawValue, fieldType);
|
|
@@ -840,9 +962,20 @@ export function registerPrefabCommands(program) {
|
|
|
840
962
|
process.exit(1);
|
|
841
963
|
}
|
|
842
964
|
}
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
965
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
966
|
+
if (configPath) {
|
|
967
|
+
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
968
|
+
const { config } = parseGameConfig(gameConfigJson);
|
|
969
|
+
const configKey = meta.configPath ? parseConfigKey(meta.configPath) : meta.name;
|
|
970
|
+
const existingKey = findConfigKeyCaseInsensitive(config, configKey);
|
|
971
|
+
const currentValues = (existingKey ? config[existingKey] : {}) ?? {};
|
|
972
|
+
const newValues = { ...currentValues, [field]: coerced };
|
|
973
|
+
const updated = updateGameConfigValue(gameConfigJson, configKey, newValues);
|
|
974
|
+
writeGameConfig(projectDir, configPath, updated);
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
writeMetaDataDefault(projectDir, key, field, coerced);
|
|
978
|
+
}
|
|
846
979
|
const payload = { success: true, key, field, value: coerced };
|
|
847
980
|
outputResult(asJson, payload, () => {
|
|
848
981
|
console.log(`已更新 ${key}.${field} = ${formatHumanValue(coerced)}`);
|
|
@@ -940,38 +1073,65 @@ export function registerPrefabCommands(program) {
|
|
|
940
1073
|
}
|
|
941
1074
|
else {
|
|
942
1075
|
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
943
|
-
let gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
944
1076
|
const metaMap = scanExtensionMetaData(projectDir);
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1077
|
+
if (configPath) {
|
|
1078
|
+
let gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
1079
|
+
for (const [extKey, fieldMap] of Object.entries(batch)) {
|
|
1080
|
+
if (!fieldMap || typeof fieldMap !== 'object' || Array.isArray(fieldMap)) {
|
|
1081
|
+
throw new Error(`extension "${extKey}" 的值必须是对象(字段 map)`);
|
|
1082
|
+
}
|
|
1083
|
+
const meta = metaMap.get(extKey);
|
|
1084
|
+
if (!meta) {
|
|
1085
|
+
throw new Error(`extension "${extKey}" 不存在`);
|
|
1086
|
+
}
|
|
1087
|
+
const configKey = meta.configPath ? parseConfigKey(meta.configPath) : meta.name;
|
|
1088
|
+
const { config } = parseGameConfig(gameConfigJson);
|
|
1089
|
+
const existingKey = findConfigKeyCaseInsensitive(config, configKey);
|
|
1090
|
+
let currentValues = (existingKey ? config[existingKey] : {}) ?? {};
|
|
1091
|
+
currentValues = typeof currentValues === 'object' && !Array.isArray(currentValues)
|
|
1092
|
+
? { ...currentValues }
|
|
1093
|
+
: {};
|
|
1094
|
+
for (const [field, rawVal] of Object.entries(fieldMap)) {
|
|
1095
|
+
const fieldDef = meta.configSchema?.[field];
|
|
1096
|
+
const fieldType = fieldDef?.type ?? 'string';
|
|
1097
|
+
const coerced = coerceBatchLeaf(rawVal, fieldType);
|
|
1098
|
+
if (fieldDef) {
|
|
1099
|
+
const result = validateConfigValue(fieldDef, coerced);
|
|
1100
|
+
if (!result.valid) {
|
|
1101
|
+
throw new Error(`${extKey}.${field}: ${result.errors.join('; ')}`);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
currentValues = setByDotPath(currentValues, field, coerced);
|
|
1105
|
+
}
|
|
1106
|
+
gameConfigJson = updateGameConfigValue(gameConfigJson, configKey, currentValues);
|
|
952
1107
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1108
|
+
writeGameConfig(projectDir, configPath, gameConfigJson);
|
|
1109
|
+
}
|
|
1110
|
+
else {
|
|
1111
|
+
for (const [extKey, fieldMap] of Object.entries(batch)) {
|
|
1112
|
+
if (!fieldMap || typeof fieldMap !== 'object' || Array.isArray(fieldMap)) {
|
|
1113
|
+
throw new Error(`extension "${extKey}" 的值必须是对象(字段 map)`);
|
|
1114
|
+
}
|
|
1115
|
+
const meta = metaMap.get(extKey);
|
|
1116
|
+
if (!meta) {
|
|
1117
|
+
throw new Error(`extension "${extKey}" 不存在`);
|
|
1118
|
+
}
|
|
1119
|
+
const coercedValues = {};
|
|
1120
|
+
for (const [field, rawVal] of Object.entries(fieldMap)) {
|
|
1121
|
+
const fieldDef = meta.configSchema?.[field];
|
|
1122
|
+
const fieldType = fieldDef?.type ?? 'string';
|
|
1123
|
+
const coerced = coerceBatchLeaf(rawVal, fieldType);
|
|
1124
|
+
if (fieldDef) {
|
|
1125
|
+
const result = validateConfigValue(fieldDef, coerced);
|
|
1126
|
+
if (!result.valid) {
|
|
1127
|
+
throw new Error(`${extKey}.${field}: ${result.errors.join('; ')}`);
|
|
1128
|
+
}
|
|
968
1129
|
}
|
|
1130
|
+
coercedValues[field] = coerced;
|
|
969
1131
|
}
|
|
970
|
-
|
|
1132
|
+
writeMetaDataDefaults(projectDir, extKey, coercedValues);
|
|
971
1133
|
}
|
|
972
|
-
gameConfigJson = updateGameConfigValue(gameConfigJson, configKey, currentValues);
|
|
973
1134
|
}
|
|
974
|
-
writeGameConfig(projectDir, configPath, gameConfigJson);
|
|
975
1135
|
}
|
|
976
1136
|
const payload = { success: true, updatedKeys: Object.keys(batch) };
|
|
977
1137
|
outputResult(asJson, payload, () => {
|
|
@@ -1011,7 +1171,7 @@ export function registerPrefabCommands(program) {
|
|
|
1011
1171
|
});
|
|
1012
1172
|
}
|
|
1013
1173
|
else {
|
|
1014
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
1174
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant) ?? DEFAULT_GAME_PATH;
|
|
1015
1175
|
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
1016
1176
|
const { extensions } = parseGameConfig(gameConfigJson);
|
|
1017
1177
|
if (extensions.includes(key)) {
|
|
@@ -1055,7 +1215,7 @@ export function registerPrefabCommands(program) {
|
|
|
1055
1215
|
});
|
|
1056
1216
|
}
|
|
1057
1217
|
else {
|
|
1058
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
1218
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant) ?? DEFAULT_GAME_PATH;
|
|
1059
1219
|
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
1060
1220
|
const { extensions } = parseGameConfig(gameConfigJson);
|
|
1061
1221
|
const filtered = extensions.filter((n) => n !== key);
|
|
@@ -1105,14 +1265,19 @@ export function registerPrefabCommands(program) {
|
|
|
1105
1265
|
console.error(`Error: 场景 "${variant}" 不存在。可用: ${scenes.map((s) => `${s.name}(${s.id})`).join(', ')}`);
|
|
1106
1266
|
process.exit(1);
|
|
1107
1267
|
}
|
|
1268
|
+
const manifestRaw = readLocalFile(projectDir, MANIFEST_PATH);
|
|
1269
|
+
const manifestJson = JSON.parse(manifestRaw);
|
|
1270
|
+
const reordered = reorderManifestDefaultScene(manifestJson, found.id);
|
|
1271
|
+
if (reordered && reordered !== manifestJson) {
|
|
1272
|
+
writeFileSync(join(projectDir, MANIFEST_PATH), JSON.stringify(reordered, null, 2), 'utf-8');
|
|
1273
|
+
}
|
|
1108
1274
|
const payload = {
|
|
1109
1275
|
success: true,
|
|
1110
1276
|
activeVariant: found.id,
|
|
1111
|
-
message: `PlayCanvas
|
|
1277
|
+
message: `PlayCanvas 场景已切换为 "${found.name}" 并写入 manifest.json。`,
|
|
1112
1278
|
};
|
|
1113
1279
|
outputResult(asJson, payload, () => {
|
|
1114
|
-
console.log(
|
|
1115
|
-
console.log(`后续命令请使用 --variant ${found.id} 操作该场景的配置。`);
|
|
1280
|
+
console.log(`已切换活跃场景为「${found.name}」 (${found.id}),manifest.json 已更新。`);
|
|
1116
1281
|
});
|
|
1117
1282
|
}
|
|
1118
1283
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"release": "node scripts/release.js"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@playcraft/build": "^0.0.
|
|
27
|
-
"@playcraft/common": "^0.0.
|
|
26
|
+
"@playcraft/build": "^0.0.35",
|
|
27
|
+
"@playcraft/common": "^0.0.22",
|
|
28
28
|
"chokidar": "^4.0.3",
|
|
29
29
|
"commander": "^13.1.0",
|
|
30
30
|
"cors": "^2.8.6",
|