@playcraft/cli 0.0.32 → 0.0.33
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/prefab.js +233 -56
- package/package.json +3 -3
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, 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,39 @@ function resolveThemeId(projectDir, opts) {
|
|
|
240
242
|
throw new Error('unreachable');
|
|
241
243
|
}
|
|
242
244
|
// ─── PlayCanvas / LiteCreator helpers ────────────────────────────────────────
|
|
245
|
+
/**
|
|
246
|
+
* manifest 中的主场景 ID(与编辑器「当前入口场景」对齐)。
|
|
247
|
+
*
|
|
248
|
+
* 优先级:1) isMain: true 2) 仅有一个场景时自动视为当前活跃场景 3) 无法确定则 null
|
|
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
|
+
const scenes = manifestJson.scenes;
|
|
256
|
+
if (!Array.isArray(scenes) || scenes.length === 0)
|
|
257
|
+
return null;
|
|
258
|
+
const mainScene = scenes.find((s) => !!s && typeof s === 'object' && s.isMain === true);
|
|
259
|
+
if (mainScene != null && (mainScene.id != null || mainScene.uniqueId != null)) {
|
|
260
|
+
return String(mainScene.id ?? mainScene.uniqueId);
|
|
261
|
+
}
|
|
262
|
+
if (scenes.length === 1) {
|
|
263
|
+
const only = scenes[0];
|
|
264
|
+
if (only && typeof only === 'object') {
|
|
265
|
+
const o = only;
|
|
266
|
+
if (o.id != null)
|
|
267
|
+
return String(o.id);
|
|
268
|
+
if (o.uniqueId != null)
|
|
269
|
+
return String(o.uniqueId);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
243
278
|
function loadGameConfig(projectDir, configPath) {
|
|
244
279
|
const raw = readLocalFile(projectDir, configPath);
|
|
245
280
|
return JSON.parse(raw);
|
|
@@ -266,24 +301,102 @@ function scanExtensionMetaData(projectDir) {
|
|
|
266
301
|
}
|
|
267
302
|
return map;
|
|
268
303
|
}
|
|
269
|
-
function
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
304
|
+
function extensionMetaDataFile(projectDir, extensionName) {
|
|
305
|
+
return join(projectDir, LITE_CREATOR_EXTENSIONS_DIR, extensionName, 'MetaData.json');
|
|
306
|
+
}
|
|
307
|
+
function mutateExtensionMetaData(projectDir, extensionName, apply) {
|
|
308
|
+
const metaPath = extensionMetaDataFile(projectDir, extensionName);
|
|
309
|
+
const raw = readFileSync(metaPath, 'utf-8');
|
|
310
|
+
const metaData = JSON.parse(raw);
|
|
311
|
+
const configSchema = metaData.configSchema;
|
|
312
|
+
if (!configSchema) {
|
|
313
|
+
throw new Error(`MetaData.json for "${extensionName}" has no configSchema`);
|
|
314
|
+
}
|
|
315
|
+
apply(configSchema);
|
|
316
|
+
writeFileSync(metaPath, JSON.stringify(metaData, null, 2), 'utf-8');
|
|
317
|
+
}
|
|
318
|
+
function writeMetaDataDefault(projectDir, extensionName, field, value) {
|
|
319
|
+
mutateExtensionMetaData(projectDir, extensionName, (configSchema) => {
|
|
320
|
+
if (!configSchema[field]) {
|
|
321
|
+
throw new Error(`MetaData.json for "${extensionName}" has no configSchema.${field}`);
|
|
322
|
+
}
|
|
323
|
+
configSchema[field].default = value;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
function writeMetaDataDefaults(projectDir, extensionName, values) {
|
|
327
|
+
mutateExtensionMetaData(projectDir, extensionName, (configSchema) => {
|
|
328
|
+
for (const [field, value] of Object.entries(values)) {
|
|
329
|
+
if (configSchema[field]) {
|
|
330
|
+
configSchema[field].default = value;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
/** 仅按场景 ID 解析 GameConfig(不查主场景)。 */
|
|
336
|
+
function resolveGameConfigPathForSceneId(projectDir, sceneId) {
|
|
337
|
+
if (!fileExists(projectDir, scenePath(sceneId)))
|
|
338
|
+
return null;
|
|
339
|
+
const sceneJson = JSON.parse(readLocalFile(projectDir, scenePath(sceneId)));
|
|
275
340
|
if (!fileExists(projectDir, ASSETS_JSON_PATH))
|
|
276
|
-
return
|
|
341
|
+
return null;
|
|
277
342
|
const assetsJson = JSON.parse(readLocalFile(projectDir, ASSETS_JSON_PATH));
|
|
278
|
-
|
|
279
|
-
|
|
343
|
+
return resolveGameConfigPath(sceneJson, assetsJson);
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* 解析当前变体下的 GameConfig 路径。无法解析时返回 null(list 回退 DefaultGame 扩展;set 可写 MetaData)。
|
|
347
|
+
* 未传 variantId 时用 manifest 主场景(isMain / 单场景)。
|
|
348
|
+
*/
|
|
349
|
+
function resolveGameConfigPathForVariant(projectDir, variantId) {
|
|
350
|
+
const sceneId = variantId ?? getMainSceneId(projectDir);
|
|
351
|
+
if (!sceneId)
|
|
352
|
+
return null;
|
|
353
|
+
return resolveGameConfigPathForSceneId(projectDir, sceneId);
|
|
354
|
+
}
|
|
355
|
+
/** PlayCanvas:`prefab list` 等人读「变体上下文」一行话。 */
|
|
356
|
+
function liteCreatorVariantHumanNote(projectDir, explicitVariant) {
|
|
357
|
+
if (explicitVariant) {
|
|
358
|
+
const p = resolveGameConfigPathForSceneId(projectDir, explicitVariant);
|
|
359
|
+
return p != null ? `场景 ${explicitVariant} → ${p}` : `场景 ${explicitVariant}(${NO_SCENE_GAMECONFIG_LABEL};set 可走 MetaData)`;
|
|
360
|
+
}
|
|
361
|
+
const mainId = getMainSceneId(projectDir);
|
|
362
|
+
const gc = resolveGameConfigPathForVariant(projectDir, undefined);
|
|
363
|
+
if (mainId != null) {
|
|
364
|
+
return gc != null
|
|
365
|
+
? `主场景 ${mainId} → ${gc}`
|
|
366
|
+
: `主场景 ${mainId}(未解析到 GameConfig;list 读 ${DEFAULT_GAME_PATH} 扩展,set 写 MetaData)`;
|
|
367
|
+
}
|
|
368
|
+
return `无主场景标记 → 扩展列表:${DEFAULT_GAME_PATH};set / set-batch:各扩展 MetaData.json`;
|
|
280
369
|
}
|
|
281
370
|
function listLocalScenes(projectDir) {
|
|
282
371
|
if (!fileExists(projectDir, MANIFEST_PATH))
|
|
283
372
|
return [];
|
|
284
373
|
const manifestJson = JSON.parse(readLocalFile(projectDir, MANIFEST_PATH));
|
|
374
|
+
if (fileExists(projectDir, ASSETS_JSON_PATH)) {
|
|
375
|
+
const assetsJson = JSON.parse(readLocalFile(projectDir, ASSETS_JSON_PATH));
|
|
376
|
+
return listScenesWithGameConfig(manifestJson, (sceneId) => {
|
|
377
|
+
const sp = scenePath(sceneId);
|
|
378
|
+
if (!fileExists(projectDir, sp))
|
|
379
|
+
return null;
|
|
380
|
+
return JSON.parse(readLocalFile(projectDir, sp));
|
|
381
|
+
}, assetsJson);
|
|
382
|
+
}
|
|
285
383
|
return listScenesFromManifest(manifestJson);
|
|
286
384
|
}
|
|
385
|
+
/** 列表展示:补全 GameConfig 路径,并用主场景 ID 标记 isActive(与未指定 --variant 的 prefab 上下文一致)。 */
|
|
386
|
+
function annotatePlaycanvasScenesForMainScene(projectDir, scenes) {
|
|
387
|
+
const mainSceneId = getMainSceneId(projectDir);
|
|
388
|
+
const annotated = scenes.map((s) => {
|
|
389
|
+
const resolved = s.gameConfigPath ?? resolveGameConfigPathForSceneId(projectDir, s.id);
|
|
390
|
+
const out = {
|
|
391
|
+
...s,
|
|
392
|
+
isActive: mainSceneId !== null && s.id === mainSceneId,
|
|
393
|
+
};
|
|
394
|
+
if (resolved != null)
|
|
395
|
+
out.gameConfigPath = resolved;
|
|
396
|
+
return out;
|
|
397
|
+
});
|
|
398
|
+
return { scenes: annotated, mainSceneId };
|
|
399
|
+
}
|
|
287
400
|
function loadPrefabs(projectDir, systemType, opts) {
|
|
288
401
|
if (systemType === 'external-theme') {
|
|
289
402
|
const themeId = resolveThemeId(projectDir, opts);
|
|
@@ -291,11 +404,19 @@ function loadPrefabs(projectDir, systemType, opts) {
|
|
|
291
404
|
const data = loadThemeData(projectDir, themeId);
|
|
292
405
|
return parseThemePrefabs(schema, data, themeId);
|
|
293
406
|
}
|
|
294
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
295
|
-
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
296
|
-
const { extensions, config } = parseGameConfig(gameConfigJson);
|
|
297
407
|
const metaMap = scanExtensionMetaData(projectDir);
|
|
298
|
-
|
|
408
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
409
|
+
if (configPath) {
|
|
410
|
+
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
411
|
+
const { extensions, config } = parseGameConfig(gameConfigJson);
|
|
412
|
+
return buildExtensionPrefabs(metaMap, { extensions, config }, configPath);
|
|
413
|
+
}
|
|
414
|
+
if (fileExists(projectDir, DEFAULT_GAME_PATH)) {
|
|
415
|
+
const defaultGameJson = loadGameConfig(projectDir, DEFAULT_GAME_PATH);
|
|
416
|
+
const { extensions } = parseGameConfig(defaultGameJson);
|
|
417
|
+
return buildExtensionPrefabs(metaMap, { extensions, config: {} }, DEFAULT_GAME_PATH);
|
|
418
|
+
}
|
|
419
|
+
return buildExtensionPrefabs(metaMap, { extensions: [], config: {} }, '');
|
|
299
420
|
}
|
|
300
421
|
// ─── dotpath helper ──────────────────────────────────────────────────────────
|
|
301
422
|
function getByDotPath(obj, path) {
|
|
@@ -422,8 +543,18 @@ export function registerPrefabCommands(program) {
|
|
|
422
543
|
});
|
|
423
544
|
}
|
|
424
545
|
else {
|
|
425
|
-
const
|
|
426
|
-
const
|
|
546
|
+
const scenesRaw = listLocalScenes(projectDir);
|
|
547
|
+
const { scenes, mainSceneId } = annotatePlaycanvasScenesForMainScene(projectDir, scenesRaw);
|
|
548
|
+
const gameConfigPath = resolveGameConfigPathForVariant(projectDir, undefined);
|
|
549
|
+
const payload = {
|
|
550
|
+
projectType: 'playcanvas',
|
|
551
|
+
variants: scenes,
|
|
552
|
+
context: {
|
|
553
|
+
mainSceneId,
|
|
554
|
+
gameConfigPath,
|
|
555
|
+
usesMetaDataForSet: gameConfigPath == null,
|
|
556
|
+
},
|
|
557
|
+
};
|
|
427
558
|
outputResult(asJson, payload, () => {
|
|
428
559
|
console.log(`项目类型: ${projectTypeLabel('lite-creator')}`);
|
|
429
560
|
console.log('');
|
|
@@ -432,8 +563,19 @@ export function registerPrefabCommands(program) {
|
|
|
432
563
|
return;
|
|
433
564
|
}
|
|
434
565
|
for (const s of scenes) {
|
|
435
|
-
const
|
|
436
|
-
|
|
566
|
+
const mark = s.isActive ? ' *' : '';
|
|
567
|
+
const pathLabel = s.gameConfigPath ?? NO_SCENE_GAMECONFIG_LABEL;
|
|
568
|
+
console.log(` ${s.name} (${s.id}) [${pathLabel}]${mark}`);
|
|
569
|
+
}
|
|
570
|
+
if (scenes.some((v) => v.isActive)) {
|
|
571
|
+
console.log('');
|
|
572
|
+
console.log('标 *:manifest 主场景(isMain,或仅 1 个场景时自动认定)。' +
|
|
573
|
+
' 未带 --variant:优先该场景 GameConfig;若无路径则 list 读 DefaultGame 扩展、set 写 MetaData。');
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
console.log('');
|
|
577
|
+
console.log('无主场景:多场景请在 manifest 设 isMain。未带 --variant 时扩展来自 ' +
|
|
578
|
+
`${DEFAULT_GAME_PATH},set / set-batch 写入各扩展 MetaData.json。`);
|
|
437
579
|
}
|
|
438
580
|
});
|
|
439
581
|
}
|
|
@@ -466,6 +608,9 @@ export function registerPrefabCommands(program) {
|
|
|
466
608
|
if (systemType === 'external-theme' && !opts.variant) {
|
|
467
609
|
variantNote = resolveThemeId(projectDir, opts);
|
|
468
610
|
}
|
|
611
|
+
else if (systemType === 'lite-creator') {
|
|
612
|
+
variantNote = liteCreatorVariantHumanNote(projectDir, opts.variant);
|
|
613
|
+
}
|
|
469
614
|
const summary = buildSummary(systemType, variantResolved, all);
|
|
470
615
|
const filtered = filterPrefabsList(all, {
|
|
471
616
|
match: opts.match,
|
|
@@ -818,18 +963,12 @@ export function registerPrefabCommands(program) {
|
|
|
818
963
|
});
|
|
819
964
|
}
|
|
820
965
|
else {
|
|
821
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
822
|
-
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
823
|
-
const { config } = parseGameConfig(gameConfigJson);
|
|
824
966
|
const metaMap = scanExtensionMetaData(projectDir);
|
|
825
967
|
const meta = metaMap.get(key);
|
|
826
968
|
if (!meta) {
|
|
827
969
|
console.error(`Error: extension "${key}" 不存在。`);
|
|
828
970
|
process.exit(1);
|
|
829
971
|
}
|
|
830
|
-
const configKey = meta.configPath ? parseConfigKey(meta.configPath) : meta.name;
|
|
831
|
-
const existingKey = findConfigKeyCaseInsensitive(config, configKey);
|
|
832
|
-
const currentValues = (existingKey ? config[existingKey] : {}) ?? {};
|
|
833
972
|
const fieldDef = meta.configSchema?.[field];
|
|
834
973
|
const fieldType = fieldDef?.type ?? 'string';
|
|
835
974
|
const coerced = coerceValue(rawValue, fieldType);
|
|
@@ -840,9 +979,20 @@ export function registerPrefabCommands(program) {
|
|
|
840
979
|
process.exit(1);
|
|
841
980
|
}
|
|
842
981
|
}
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
982
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
983
|
+
if (configPath) {
|
|
984
|
+
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
985
|
+
const { config } = parseGameConfig(gameConfigJson);
|
|
986
|
+
const configKey = meta.configPath ? parseConfigKey(meta.configPath) : meta.name;
|
|
987
|
+
const existingKey = findConfigKeyCaseInsensitive(config, configKey);
|
|
988
|
+
const currentValues = (existingKey ? config[existingKey] : {}) ?? {};
|
|
989
|
+
const newValues = { ...currentValues, [field]: coerced };
|
|
990
|
+
const updated = updateGameConfigValue(gameConfigJson, configKey, newValues);
|
|
991
|
+
writeGameConfig(projectDir, configPath, updated);
|
|
992
|
+
}
|
|
993
|
+
else {
|
|
994
|
+
writeMetaDataDefault(projectDir, key, field, coerced);
|
|
995
|
+
}
|
|
846
996
|
const payload = { success: true, key, field, value: coerced };
|
|
847
997
|
outputResult(asJson, payload, () => {
|
|
848
998
|
console.log(`已更新 ${key}.${field} = ${formatHumanValue(coerced)}`);
|
|
@@ -940,38 +1090,65 @@ export function registerPrefabCommands(program) {
|
|
|
940
1090
|
}
|
|
941
1091
|
else {
|
|
942
1092
|
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
943
|
-
let gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
944
1093
|
const metaMap = scanExtensionMetaData(projectDir);
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1094
|
+
if (configPath) {
|
|
1095
|
+
let gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
1096
|
+
for (const [extKey, fieldMap] of Object.entries(batch)) {
|
|
1097
|
+
if (!fieldMap || typeof fieldMap !== 'object' || Array.isArray(fieldMap)) {
|
|
1098
|
+
throw new Error(`extension "${extKey}" 的值必须是对象(字段 map)`);
|
|
1099
|
+
}
|
|
1100
|
+
const meta = metaMap.get(extKey);
|
|
1101
|
+
if (!meta) {
|
|
1102
|
+
throw new Error(`extension "${extKey}" 不存在`);
|
|
1103
|
+
}
|
|
1104
|
+
const configKey = meta.configPath ? parseConfigKey(meta.configPath) : meta.name;
|
|
1105
|
+
const { config } = parseGameConfig(gameConfigJson);
|
|
1106
|
+
const existingKey = findConfigKeyCaseInsensitive(config, configKey);
|
|
1107
|
+
let currentValues = (existingKey ? config[existingKey] : {}) ?? {};
|
|
1108
|
+
currentValues = typeof currentValues === 'object' && !Array.isArray(currentValues)
|
|
1109
|
+
? { ...currentValues }
|
|
1110
|
+
: {};
|
|
1111
|
+
for (const [field, rawVal] of Object.entries(fieldMap)) {
|
|
1112
|
+
const fieldDef = meta.configSchema?.[field];
|
|
1113
|
+
const fieldType = fieldDef?.type ?? 'string';
|
|
1114
|
+
const coerced = coerceBatchLeaf(rawVal, fieldType);
|
|
1115
|
+
if (fieldDef) {
|
|
1116
|
+
const result = validateConfigValue(fieldDef, coerced);
|
|
1117
|
+
if (!result.valid) {
|
|
1118
|
+
throw new Error(`${extKey}.${field}: ${result.errors.join('; ')}`);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
currentValues = setByDotPath(currentValues, field, coerced);
|
|
1122
|
+
}
|
|
1123
|
+
gameConfigJson = updateGameConfigValue(gameConfigJson, configKey, currentValues);
|
|
952
1124
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1125
|
+
writeGameConfig(projectDir, configPath, gameConfigJson);
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
for (const [extKey, fieldMap] of Object.entries(batch)) {
|
|
1129
|
+
if (!fieldMap || typeof fieldMap !== 'object' || Array.isArray(fieldMap)) {
|
|
1130
|
+
throw new Error(`extension "${extKey}" 的值必须是对象(字段 map)`);
|
|
1131
|
+
}
|
|
1132
|
+
const meta = metaMap.get(extKey);
|
|
1133
|
+
if (!meta) {
|
|
1134
|
+
throw new Error(`extension "${extKey}" 不存在`);
|
|
1135
|
+
}
|
|
1136
|
+
const coercedValues = {};
|
|
1137
|
+
for (const [field, rawVal] of Object.entries(fieldMap)) {
|
|
1138
|
+
const fieldDef = meta.configSchema?.[field];
|
|
1139
|
+
const fieldType = fieldDef?.type ?? 'string';
|
|
1140
|
+
const coerced = coerceBatchLeaf(rawVal, fieldType);
|
|
1141
|
+
if (fieldDef) {
|
|
1142
|
+
const result = validateConfigValue(fieldDef, coerced);
|
|
1143
|
+
if (!result.valid) {
|
|
1144
|
+
throw new Error(`${extKey}.${field}: ${result.errors.join('; ')}`);
|
|
1145
|
+
}
|
|
968
1146
|
}
|
|
1147
|
+
coercedValues[field] = coerced;
|
|
969
1148
|
}
|
|
970
|
-
|
|
1149
|
+
writeMetaDataDefaults(projectDir, extKey, coercedValues);
|
|
971
1150
|
}
|
|
972
|
-
gameConfigJson = updateGameConfigValue(gameConfigJson, configKey, currentValues);
|
|
973
1151
|
}
|
|
974
|
-
writeGameConfig(projectDir, configPath, gameConfigJson);
|
|
975
1152
|
}
|
|
976
1153
|
const payload = { success: true, updatedKeys: Object.keys(batch) };
|
|
977
1154
|
outputResult(asJson, payload, () => {
|
|
@@ -1011,7 +1188,7 @@ export function registerPrefabCommands(program) {
|
|
|
1011
1188
|
});
|
|
1012
1189
|
}
|
|
1013
1190
|
else {
|
|
1014
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
1191
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant) ?? DEFAULT_GAME_PATH;
|
|
1015
1192
|
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
1016
1193
|
const { extensions } = parseGameConfig(gameConfigJson);
|
|
1017
1194
|
if (extensions.includes(key)) {
|
|
@@ -1055,7 +1232,7 @@ export function registerPrefabCommands(program) {
|
|
|
1055
1232
|
});
|
|
1056
1233
|
}
|
|
1057
1234
|
else {
|
|
1058
|
-
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant);
|
|
1235
|
+
const configPath = resolveGameConfigPathForVariant(projectDir, opts.variant) ?? DEFAULT_GAME_PATH;
|
|
1059
1236
|
const gameConfigJson = loadGameConfig(projectDir, configPath);
|
|
1060
1237
|
const { extensions } = parseGameConfig(gameConfigJson);
|
|
1061
1238
|
const filtered = extensions.filter((n) => n !== key);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playcraft/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.33",
|
|
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.34",
|
|
27
|
+
"@playcraft/common": "^0.0.22",
|
|
28
28
|
"chokidar": "^4.0.3",
|
|
29
29
|
"commander": "^13.1.0",
|
|
30
30
|
"cors": "^2.8.6",
|