unity-agentic-tools 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/dist/cli.js +2109 -657
- package/dist/index.js +1211 -188
- package/native/unity-file-tools.darwin-arm64.node +0 -0
- package/native/unity-file-tools.darwin-x64.node +0 -0
- package/native/unity-file-tools.linux-x64-gnu.node +0 -0
- package/native/unity-file-tools.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2299,7 +2299,7 @@ var require_package = __commonJS((exports2, module2) => {
|
|
|
2299
2299
|
module2.exports = {
|
|
2300
2300
|
name: "unity-agentic-tools",
|
|
2301
2301
|
packageManager: "bun@latest",
|
|
2302
|
-
version: "0.4.
|
|
2302
|
+
version: "0.4.3",
|
|
2303
2303
|
description: "Fast, token-efficient Unity YAML parser for AI agents",
|
|
2304
2304
|
exports: {
|
|
2305
2305
|
".": {
|
|
@@ -2595,8 +2595,8 @@ function removeDirectoryRecursive(dir) {
|
|
|
2595
2595
|
}
|
|
2596
2596
|
|
|
2597
2597
|
// src/cmd-create.ts
|
|
2598
|
-
var
|
|
2599
|
-
var
|
|
2598
|
+
var import_fs18 = require("fs");
|
|
2599
|
+
var import_path10 = require("path");
|
|
2600
2600
|
var import_crypto2 = require("crypto");
|
|
2601
2601
|
|
|
2602
2602
|
// src/utils.ts
|
|
@@ -2619,6 +2619,9 @@ function find_unity_project_root(startDir) {
|
|
|
2619
2619
|
}
|
|
2620
2620
|
return null;
|
|
2621
2621
|
}
|
|
2622
|
+
function resolve_project_path(project_path) {
|
|
2623
|
+
return import_path4.resolve(project_path || process.cwd());
|
|
2624
|
+
}
|
|
2622
2625
|
function ensure_parent_dir(file_path) {
|
|
2623
2626
|
const dir = import_path4.dirname(file_path);
|
|
2624
2627
|
if (dir && dir !== "." && !import_fs4.existsSync(dir)) {
|
|
@@ -2806,6 +2809,19 @@ function build_guid_cache(project_path, raw) {
|
|
|
2806
2809
|
}
|
|
2807
2810
|
}
|
|
2808
2811
|
return substringMatch;
|
|
2812
|
+
},
|
|
2813
|
+
find_all_by_name_exact(name, extension) {
|
|
2814
|
+
const nameLower = name.toLowerCase().replace(/\.[^.]+$/, "");
|
|
2815
|
+
const matches = [];
|
|
2816
|
+
for (const [guid, assetPath] of Object.entries(raw)) {
|
|
2817
|
+
if (extension && !assetPath.endsWith(extension))
|
|
2818
|
+
continue;
|
|
2819
|
+
const fileName = import_path5.basename(assetPath, import_path5.extname(assetPath)).toLowerCase();
|
|
2820
|
+
if (fileName === nameLower) {
|
|
2821
|
+
matches.push({ guid, path: assetPath });
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
return matches;
|
|
2809
2825
|
}
|
|
2810
2826
|
};
|
|
2811
2827
|
}
|
|
@@ -2904,15 +2920,14 @@ var UNITY_CLASS_IDS = {
|
|
|
2904
2920
|
114: "MonoBehaviour",
|
|
2905
2921
|
115: "MonoScript",
|
|
2906
2922
|
117: "Texture3D",
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
126: "
|
|
2915
|
-
127: "NavMeshProjectSettings",
|
|
2923
|
+
118: "NewAnimationTrack",
|
|
2924
|
+
119: "Projector",
|
|
2925
|
+
120: "LineRenderer",
|
|
2926
|
+
121: "Flare",
|
|
2927
|
+
122: "Halo",
|
|
2928
|
+
123: "LensFlare",
|
|
2929
|
+
124: "FlareLayer",
|
|
2930
|
+
126: "NavMeshProjectSettings",
|
|
2916
2931
|
128: "Font",
|
|
2917
2932
|
129: "PlayerSettings",
|
|
2918
2933
|
130: "NamedObject",
|
|
@@ -2939,6 +2954,7 @@ var UNITY_CLASS_IDS = {
|
|
|
2939
2954
|
164: "AudioReverbFilter",
|
|
2940
2955
|
165: "AudioHighPassFilter",
|
|
2941
2956
|
166: "AudioChorusFilter",
|
|
2957
|
+
1660057539: "SceneRoots",
|
|
2942
2958
|
167: "AudioReverbZone",
|
|
2943
2959
|
168: "AudioEchoFilter",
|
|
2944
2960
|
169: "AudioLowPassFilter",
|
|
@@ -3009,29 +3025,41 @@ var UNITY_CLASS_IDS = {
|
|
|
3009
3025
|
257: "TargetJoint2D",
|
|
3010
3026
|
258: "LightProbes",
|
|
3011
3027
|
259: "LightProbeProxyVolume",
|
|
3012
|
-
260: "
|
|
3013
|
-
261: "
|
|
3014
|
-
262: "
|
|
3028
|
+
260: "SampleClipLegacy",
|
|
3029
|
+
261: "AudioMixerSnapshotLegacy",
|
|
3030
|
+
262: "AudioMixerGroupLegacy",
|
|
3015
3031
|
265: "NScreenBridge",
|
|
3016
|
-
271: "
|
|
3032
|
+
271: "SampleClip",
|
|
3017
3033
|
272: "UnityAdsManager",
|
|
3018
|
-
273: "
|
|
3019
|
-
280: "
|
|
3020
|
-
281: "
|
|
3021
|
-
290: "
|
|
3022
|
-
292: "
|
|
3023
|
-
293: "
|
|
3024
|
-
294: "
|
|
3025
|
-
298: "
|
|
3034
|
+
273: "AudioMixerGroup",
|
|
3035
|
+
280: "UnityConnectSettingsLegacy",
|
|
3036
|
+
281: "AvatarMaskLegacy",
|
|
3037
|
+
290: "AssetBundleManifest",
|
|
3038
|
+
292: "VideoPlayerLegacy",
|
|
3039
|
+
293: "VideoClipLegacy",
|
|
3040
|
+
294: "ParticleSystemForceFieldLegacy",
|
|
3041
|
+
298: "SpriteMaskLegacy",
|
|
3026
3042
|
300: "WorldAnchor",
|
|
3027
|
-
301: "
|
|
3028
|
-
310: "
|
|
3029
|
-
|
|
3030
|
-
|
|
3043
|
+
301: "OcclusionCullingDataLegacy",
|
|
3044
|
+
310: "UnityConnectSettings",
|
|
3045
|
+
1001: "PrefabInstance",
|
|
3046
|
+
319: "AvatarMask",
|
|
3047
|
+
320: "PlayableDirector",
|
|
3048
|
+
328: "VideoPlayer",
|
|
3049
|
+
329: "VideoClip",
|
|
3050
|
+
330: "ParticleSystemForceField",
|
|
3051
|
+
331: "SpriteMask",
|
|
3052
|
+
363: "OcclusionCullingData",
|
|
3053
|
+
1006: "TextureImporter",
|
|
3054
|
+
181963792: "Preset",
|
|
3031
3055
|
687078895: "SpriteAtlas",
|
|
3056
|
+
156049354: "Grid",
|
|
3057
|
+
1742807556: "GridLayout",
|
|
3032
3058
|
1839735485: "Tilemap",
|
|
3033
|
-
|
|
3034
|
-
|
|
3059
|
+
19719996: "TilemapCollider2D",
|
|
3060
|
+
483693784: "TilemapRenderer",
|
|
3061
|
+
1839735486: "TilemapCollider2DLegacy",
|
|
3062
|
+
1839735487: "TilemapRendererLegacy"
|
|
3035
3063
|
};
|
|
3036
3064
|
var UNITY_CLASS_NAMES = Object.fromEntries(Object.entries(UNITY_CLASS_IDS).map(([id, name]) => [name, parseInt(id, 10)]));
|
|
3037
3065
|
function get_class_id_name(class_id) {
|
|
@@ -3097,8 +3125,12 @@ function resolve_source_prefab(doc, file_path, project_path) {
|
|
|
3097
3125
|
prefab_instance_block: pi_block
|
|
3098
3126
|
};
|
|
3099
3127
|
}
|
|
3100
|
-
function resolveScriptGuid(script, projectPath) {
|
|
3128
|
+
function resolveScriptGuid(script, projectPath, options) {
|
|
3129
|
+
const strictExactName = options?.strict_exact_name === true;
|
|
3101
3130
|
if (/^[a-f0-9]{32}$/i.test(script)) {
|
|
3131
|
+
if (/^0{32}$/i.test(script)) {
|
|
3132
|
+
throw new Error(`Invalid script GUID "${script}": all-zero GUID is not allowed. Provide a real script GUID from a .meta file.`);
|
|
3133
|
+
}
|
|
3102
3134
|
return { guid: script.toLowerCase(), path: null };
|
|
3103
3135
|
}
|
|
3104
3136
|
if (script.endsWith(".cs")) {
|
|
@@ -3123,9 +3155,20 @@ function resolveScriptGuid(script, projectPath) {
|
|
|
3123
3155
|
if (projectPath) {
|
|
3124
3156
|
const guidCache = load_guid_cache(projectPath);
|
|
3125
3157
|
if (guidCache) {
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3158
|
+
if (strictExactName) {
|
|
3159
|
+
const exactMatches = guidCache.find_all_by_name_exact(script, ".cs");
|
|
3160
|
+
if (exactMatches.length === 1) {
|
|
3161
|
+
return exactMatches[0];
|
|
3162
|
+
}
|
|
3163
|
+
if (exactMatches.length > 1) {
|
|
3164
|
+
const paths = exactMatches.map((m) => m.path).join(", ");
|
|
3165
|
+
throw new Error(`Ambiguous type "${script}": found ${exactMatches.length} exact script name matches (${paths}). ` + 'Use a qualified name (e.g., "Namespace.TypeName") or provide the full script path.');
|
|
3166
|
+
}
|
|
3167
|
+
} else {
|
|
3168
|
+
const result = guidCache.find_by_name(script, ".cs");
|
|
3169
|
+
if (result)
|
|
3170
|
+
return result;
|
|
3171
|
+
}
|
|
3129
3172
|
}
|
|
3130
3173
|
const registryPath = path.join(projectPath, ".unity-agentic", "type-registry.json");
|
|
3131
3174
|
if (import_fs6.existsSync(registryPath)) {
|
|
@@ -3276,16 +3319,49 @@ function resolveScriptGuid(script, projectPath) {
|
|
|
3276
3319
|
continue;
|
|
3277
3320
|
try {
|
|
3278
3321
|
const cache = JSON.parse(import_fs6.readFileSync(cachePath, "utf-8"));
|
|
3322
|
+
const exactMatches = [];
|
|
3279
3323
|
for (const [guid, assetPath] of Object.entries(cache)) {
|
|
3280
3324
|
if (!assetPath.endsWith(".cs"))
|
|
3281
3325
|
continue;
|
|
3282
3326
|
const fileName = path.basename(assetPath, ".cs").toLowerCase();
|
|
3283
3327
|
if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
|
|
3284
|
-
|
|
3328
|
+
exactMatches.push({ guid, path: assetPath });
|
|
3285
3329
|
}
|
|
3286
3330
|
}
|
|
3287
|
-
|
|
3331
|
+
if (exactMatches.length === 1) {
|
|
3332
|
+
return exactMatches[0];
|
|
3333
|
+
}
|
|
3334
|
+
if (exactMatches.length > 1) {
|
|
3335
|
+
const paths = exactMatches.map((m) => m.path).join(", ");
|
|
3336
|
+
throw new Error(`Ambiguous type "${script}": found ${exactMatches.length} exact script name matches (${paths}). ` + 'Use a qualified name (e.g., "Namespace.TypeName") or provide the full script path.');
|
|
3337
|
+
}
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
if (err instanceof Error && err.message.startsWith("Ambiguous type")) {
|
|
3340
|
+
throw err;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3288
3343
|
}
|
|
3344
|
+
if (strictExactName) {
|
|
3345
|
+
return null;
|
|
3346
|
+
}
|
|
3347
|
+
try {
|
|
3348
|
+
const assetsDir = path.join(projectPath, "Assets");
|
|
3349
|
+
if (import_fs6.existsSync(assetsDir)) {
|
|
3350
|
+
const entries = import_fs6.readdirSync(assetsDir, { recursive: true, withFileTypes: false });
|
|
3351
|
+
for (const entry of entries) {
|
|
3352
|
+
if (!entry.endsWith(".cs"))
|
|
3353
|
+
continue;
|
|
3354
|
+
const fileName = path.basename(entry, ".cs").toLowerCase();
|
|
3355
|
+
if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
|
|
3356
|
+
const fullPath = path.join(assetsDir, entry);
|
|
3357
|
+
const guid = extractGuidFromMeta(fullPath + ".meta");
|
|
3358
|
+
if (guid) {
|
|
3359
|
+
return { guid, path: path.join("Assets", entry) };
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
} catch {}
|
|
3289
3365
|
}
|
|
3290
3366
|
return null;
|
|
3291
3367
|
}
|
|
@@ -3337,19 +3413,38 @@ function resolveAssetPathToPPtr(value, file_path, project_path) {
|
|
|
3337
3413
|
return null;
|
|
3338
3414
|
return `{fileID: ${mapping.fileID}, guid: ${guid}, type: ${mapping.type}}`;
|
|
3339
3415
|
}
|
|
3340
|
-
function
|
|
3341
|
-
|
|
3416
|
+
function detect_abstract_class_in_source(file_path, class_name) {
|
|
3417
|
+
try {
|
|
3418
|
+
const source = import_fs6.readFileSync(file_path, "utf-8");
|
|
3419
|
+
const escaped_name = class_name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3420
|
+
const pattern = new RegExp(`(^|[^\\w])abstract\\s+class\\s+${escaped_name}(?=$|[^\\w])`, "m");
|
|
3421
|
+
return pattern.test(source);
|
|
3422
|
+
} catch {
|
|
3423
|
+
return false;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
function resolve_script_with_fields(script, project_path, options) {
|
|
3427
|
+
const resolved = resolveScriptGuid(script, project_path, options);
|
|
3342
3428
|
if (!resolved)
|
|
3343
3429
|
return null;
|
|
3344
3430
|
const result = {
|
|
3345
3431
|
guid: resolved.guid,
|
|
3346
3432
|
path: resolved.path
|
|
3347
3433
|
};
|
|
3434
|
+
if (resolved.path && resolved.path.endsWith(".cs")) {
|
|
3435
|
+
const full_path = path.isAbsolute(resolved.path) ? resolved.path : project_path ? path.join(project_path, resolved.path) : resolved.path;
|
|
3436
|
+
if (import_fs6.existsSync(full_path)) {
|
|
3437
|
+
const class_name = script.includes(".") ? script.split(".").pop() : script;
|
|
3438
|
+
if (class_name.length > 0) {
|
|
3439
|
+
result.is_abstract = detect_abstract_class_in_source(full_path, class_name);
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3348
3443
|
if (!project_path)
|
|
3349
3444
|
return result;
|
|
3350
3445
|
if (resolved.path) {
|
|
3351
3446
|
try {
|
|
3352
|
-
const full_path = resolved.path
|
|
3447
|
+
const full_path = path.isAbsolute(resolved.path) ? resolved.path : path.join(project_path, resolved.path);
|
|
3353
3448
|
if (!import_fs6.existsSync(full_path)) {
|
|
3354
3449
|
result.extraction_error = `Script file not found at resolved path: ${full_path}`;
|
|
3355
3450
|
} else if (resolved.path.endsWith(".cs")) {
|
|
@@ -3363,6 +3458,10 @@ function resolve_script_with_fields(script, project_path) {
|
|
|
3363
3458
|
result.fields = chosen.fields;
|
|
3364
3459
|
result.base_class = chosen.baseClass ?? undefined;
|
|
3365
3460
|
result.kind = chosen.kind;
|
|
3461
|
+
result.class_name = chosen.name;
|
|
3462
|
+
if (chosen.name) {
|
|
3463
|
+
result.is_abstract = detect_abstract_class_in_source(full_path, chosen.name);
|
|
3464
|
+
}
|
|
3366
3465
|
}
|
|
3367
3466
|
} else {
|
|
3368
3467
|
result.extraction_error = "Native extractSerializedFields function not available";
|
|
@@ -3440,8 +3539,8 @@ function build_type_lookup(project_path) {
|
|
|
3440
3539
|
const assetsDir = path.join(project_path, "Assets");
|
|
3441
3540
|
const csIndex = new Map;
|
|
3442
3541
|
try {
|
|
3443
|
-
const { readdirSync:
|
|
3444
|
-
const entries =
|
|
3542
|
+
const { readdirSync: readdirSync4 } = require("fs");
|
|
3543
|
+
const entries = readdirSync4(assetsDir, { recursive: true, withFileTypes: false });
|
|
3445
3544
|
for (const entry of entries) {
|
|
3446
3545
|
if (typeof entry === "string" && entry.endsWith(".cs")) {
|
|
3447
3546
|
const basename3 = entry.substring(entry.lastIndexOf("/") + 1).replace(/\.cs$/, "");
|
|
@@ -4462,7 +4561,21 @@ class UnityDocument {
|
|
|
4462
4561
|
}
|
|
4463
4562
|
}
|
|
4464
4563
|
}
|
|
4465
|
-
|
|
4564
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4565
|
+
const name_pattern = new RegExp(`propertyPath: m_Name\\s*\\n\\s*value:\\s*${escaped}\\s*$`, "m");
|
|
4566
|
+
for (const block of this._blocks) {
|
|
4567
|
+
if ((block.class_id === 1001 || block.class_id === 310) && name_pattern.test(block.raw)) {
|
|
4568
|
+
const pi_id = block.file_id;
|
|
4569
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
|
|
4570
|
+
const stripped_transform = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
|
|
4571
|
+
if (stripped_transform) {
|
|
4572
|
+
transform_ids.push(stripped_transform.file_id);
|
|
4573
|
+
} else {
|
|
4574
|
+
transform_ids.push(pi_id);
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
return Array.from(new Set(transform_ids));
|
|
4466
4579
|
}
|
|
4467
4580
|
require_unique_game_object(name_or_id) {
|
|
4468
4581
|
if (/^-?\d+$/.test(name_or_id)) {
|
|
@@ -4491,10 +4604,7 @@ class UnityDocument {
|
|
|
4491
4604
|
if (!block) {
|
|
4492
4605
|
return { error: `Block with fileID ${name_or_id} not found` };
|
|
4493
4606
|
}
|
|
4494
|
-
if (block.class_id === 4) {
|
|
4495
|
-
return block;
|
|
4496
|
-
}
|
|
4497
|
-
if (block.class_id === 224) {
|
|
4607
|
+
if (block.class_id === 4 || block.class_id === 224 || block.class_id === 1001 || block.class_id === 310) {
|
|
4498
4608
|
return block;
|
|
4499
4609
|
}
|
|
4500
4610
|
if (block.class_id === 1) {
|
|
@@ -4507,15 +4617,40 @@ class UnityDocument {
|
|
|
4507
4617
|
}
|
|
4508
4618
|
return { error: `Transform for GameObject fileID ${name_or_id} not found` };
|
|
4509
4619
|
}
|
|
4510
|
-
return { error: `fileID ${name_or_id} is not a GameObject or
|
|
4620
|
+
return { error: `fileID ${name_or_id} is not a GameObject, Transform, or PrefabInstance (class ${block.class_id})` };
|
|
4511
4621
|
}
|
|
4512
4622
|
const transform_ids = this.find_transforms_by_name(name_or_id);
|
|
4513
4623
|
if (transform_ids.length === 0) {
|
|
4514
|
-
return { error: `
|
|
4624
|
+
return { error: `Object "${name_or_id}" not found` };
|
|
4515
4625
|
}
|
|
4516
4626
|
if (transform_ids.length > 1) {
|
|
4517
|
-
const
|
|
4518
|
-
|
|
4627
|
+
const entity_ids = [];
|
|
4628
|
+
for (const tid of transform_ids) {
|
|
4629
|
+
const block = this.find_by_file_id(tid);
|
|
4630
|
+
if (block) {
|
|
4631
|
+
if (block.class_id === 4 || block.class_id === 224) {
|
|
4632
|
+
const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
4633
|
+
if (go_match) {
|
|
4634
|
+
entity_ids.push(go_match[1]);
|
|
4635
|
+
} else {
|
|
4636
|
+
entity_ids.push(tid);
|
|
4637
|
+
}
|
|
4638
|
+
} else if (block.class_id === 1001 || block.class_id === 310) {
|
|
4639
|
+
entity_ids.push(tid);
|
|
4640
|
+
} else if (block.is_stripped) {
|
|
4641
|
+
const pi_match = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(-?\d+)\}/);
|
|
4642
|
+
if (pi_match) {
|
|
4643
|
+
entity_ids.push(pi_match[1]);
|
|
4644
|
+
} else {
|
|
4645
|
+
entity_ids.push(tid);
|
|
4646
|
+
}
|
|
4647
|
+
} else {
|
|
4648
|
+
entity_ids.push(tid);
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
}
|
|
4652
|
+
const ids_str = Array.from(new Set(entity_ids)).join(", ");
|
|
4653
|
+
return { error: `Multiple GameObjects/PrefabInstances named "${name_or_id}" found (fileIDs: ${ids_str}). Use numeric fileID to specify which one.` };
|
|
4519
4654
|
}
|
|
4520
4655
|
const transform = this.find_by_file_id(transform_ids[0]);
|
|
4521
4656
|
if (!transform) {
|
|
@@ -4543,7 +4678,7 @@ class UnityDocument {
|
|
|
4543
4678
|
if (t_block.class_id === 4 && t_block.is_stripped) {
|
|
4544
4679
|
let name = "Variant";
|
|
4545
4680
|
for (const pi_block of this._blocks) {
|
|
4546
|
-
if (pi_block.class_id === 1001) {
|
|
4681
|
+
if (pi_block.class_id === 1001 || pi_block.class_id === 310) {
|
|
4547
4682
|
const name_mod = pi_block.raw.match(/propertyPath: m_Name\s*\n\s*value:\s*(.+)/);
|
|
4548
4683
|
if (name_mod) {
|
|
4549
4684
|
name = name_mod[1].trim();
|
|
@@ -4696,7 +4831,7 @@ class UnityDocument {
|
|
|
4696
4831
|
}
|
|
4697
4832
|
add_child_to_parent(parent_id, child_id) {
|
|
4698
4833
|
const parent = this.find_by_file_id(parent_id);
|
|
4699
|
-
if (!parent || parent.class_id !== 4)
|
|
4834
|
+
if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
|
|
4700
4835
|
return false;
|
|
4701
4836
|
let raw = parent.raw;
|
|
4702
4837
|
const inline_empty = /m_Children:\s*\[\]/;
|
|
@@ -4732,7 +4867,7 @@ class UnityDocument {
|
|
|
4732
4867
|
}
|
|
4733
4868
|
remove_child_from_parent(parent_id, child_id) {
|
|
4734
4869
|
const parent = this.find_by_file_id(parent_id);
|
|
4735
|
-
if (!parent || parent.class_id !== 4)
|
|
4870
|
+
if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
|
|
4736
4871
|
return false;
|
|
4737
4872
|
let raw = parent.raw;
|
|
4738
4873
|
const child_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${child_id}\\}`);
|
|
@@ -4753,7 +4888,49 @@ class UnityDocument {
|
|
|
4753
4888
|
}
|
|
4754
4889
|
_collect_hierarchy_recursive(transform_id, result) {
|
|
4755
4890
|
const transform = this.find_by_file_id(transform_id);
|
|
4756
|
-
if (!transform
|
|
4891
|
+
if (!transform)
|
|
4892
|
+
return;
|
|
4893
|
+
if (transform.class_id === 1001 || transform.class_id === 310) {
|
|
4894
|
+
const pi_id = transform.file_id;
|
|
4895
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
|
|
4896
|
+
for (const block of this._blocks) {
|
|
4897
|
+
if (block.is_stripped && pi_ref_pattern.test(block.raw)) {
|
|
4898
|
+
result.add(block.file_id);
|
|
4899
|
+
if (block.class_id === 4 || block.class_id === 224) {
|
|
4900
|
+
this._collect_hierarchy_recursive(block.file_id, result);
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
}
|
|
4904
|
+
const added_go_matches = transform.raw.matchAll(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)\}/g);
|
|
4905
|
+
for (const match of added_go_matches) {
|
|
4906
|
+
const added_id = match[1];
|
|
4907
|
+
if (added_id !== "0" && !result.has(added_id)) {
|
|
4908
|
+
result.add(added_id);
|
|
4909
|
+
const added_block = this.find_by_file_id(added_id);
|
|
4910
|
+
if (added_block && added_block.class_id === 1) {
|
|
4911
|
+
const comp_matches = added_block.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
|
|
4912
|
+
for (const cm of comp_matches) {
|
|
4913
|
+
const comp_block = this.find_by_file_id(cm[1]);
|
|
4914
|
+
if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
|
|
4915
|
+
if (!result.has(cm[1])) {
|
|
4916
|
+
result.add(cm[1]);
|
|
4917
|
+
this._collect_hierarchy_recursive(cm[1], result);
|
|
4918
|
+
}
|
|
4919
|
+
break;
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
}
|
|
4925
|
+
const added_comp_matches = transform.raw.matchAll(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)\}/g);
|
|
4926
|
+
for (const match of added_comp_matches) {
|
|
4927
|
+
const added_id = match[1];
|
|
4928
|
+
if (added_id !== "0")
|
|
4929
|
+
result.add(added_id);
|
|
4930
|
+
}
|
|
4931
|
+
return;
|
|
4932
|
+
}
|
|
4933
|
+
if (transform.class_id !== 4 && transform.class_id !== 224)
|
|
4757
4934
|
return;
|
|
4758
4935
|
const children_section = transform.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
|
|
4759
4936
|
if (!children_section)
|
|
@@ -4788,14 +4965,21 @@ class UnityDocument {
|
|
|
4788
4965
|
if (parent_id === "0") {
|
|
4789
4966
|
let count = 0;
|
|
4790
4967
|
for (const block of this._blocks) {
|
|
4791
|
-
if (block.class_id === 4 && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
4968
|
+
if ((block.class_id === 4 || block.class_id === 224) && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
4792
4969
|
count++;
|
|
4970
|
+
} else if ((block.class_id === 1001 || block.class_id === 310) && /m_TransformParent:\s*\{fileID:\s*0\}/.test(block.raw)) {
|
|
4971
|
+
const pi_id = block.file_id;
|
|
4972
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
|
|
4973
|
+
const has_stripped = this._blocks.some((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
|
|
4974
|
+
if (!has_stripped) {
|
|
4975
|
+
count++;
|
|
4976
|
+
}
|
|
4793
4977
|
}
|
|
4794
4978
|
}
|
|
4795
4979
|
return count;
|
|
4796
4980
|
}
|
|
4797
4981
|
const parent = this.find_by_file_id(parent_id);
|
|
4798
|
-
if (!parent || parent.class_id !== 4)
|
|
4982
|
+
if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
|
|
4799
4983
|
return 0;
|
|
4800
4984
|
const children_match = parent.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
|
|
4801
4985
|
if (children_match) {
|
|
@@ -4828,19 +5012,97 @@ ${new_children}`);
|
|
|
4828
5012
|
}
|
|
4829
5013
|
return false;
|
|
4830
5014
|
}
|
|
5015
|
+
add_root_to_scene_roots(root_id) {
|
|
5016
|
+
const pi_block = this.find_by_file_id(root_id);
|
|
5017
|
+
if (pi_block && (pi_block.class_id === 1001 || pi_block.class_id === 310)) {
|
|
5018
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${root_id}\\}`);
|
|
5019
|
+
const stripped_root = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw) && /m_Father:\s*\{fileID:\s*0\}/.test(b.raw));
|
|
5020
|
+
if (stripped_root) {
|
|
5021
|
+
root_id = stripped_root.file_id;
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
const sr_blocks = [...this.find_by_class_id(166), ...this.find_by_class_id(1660057539)];
|
|
5025
|
+
if (sr_blocks.length === 0)
|
|
5026
|
+
return false;
|
|
5027
|
+
const sr_block = sr_blocks[0];
|
|
5028
|
+
let raw = sr_block.raw;
|
|
5029
|
+
const roots_pattern = /(m_Roots:\s*\n(?:\s*-\s*\{fileID:\s*-?\d+\}\s*\n)*)/;
|
|
5030
|
+
if (roots_pattern.test(raw)) {
|
|
5031
|
+
raw = raw.replace(roots_pattern, `$1 - {fileID: ${root_id}}
|
|
5032
|
+
`);
|
|
5033
|
+
sr_block.replace_raw(raw);
|
|
5034
|
+
return true;
|
|
5035
|
+
}
|
|
5036
|
+
const empty_roots = /m_Roots:\s*\[\]/;
|
|
5037
|
+
if (empty_roots.test(raw)) {
|
|
5038
|
+
raw = raw.replace(empty_roots, `m_Roots:
|
|
5039
|
+
- {fileID: ${root_id}}`);
|
|
5040
|
+
sr_block.replace_raw(raw);
|
|
5041
|
+
return true;
|
|
5042
|
+
}
|
|
5043
|
+
return false;
|
|
5044
|
+
}
|
|
5045
|
+
remove_root_from_scene_roots(root_id) {
|
|
5046
|
+
const pi_block = this.find_by_file_id(root_id);
|
|
5047
|
+
if (pi_block && (pi_block.class_id === 1001 || pi_block.class_id === 310)) {
|
|
5048
|
+
const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${root_id}\\}`);
|
|
5049
|
+
const stripped_root = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
|
|
5050
|
+
if (stripped_root) {
|
|
5051
|
+
this._remove_id_from_scene_roots(stripped_root.file_id);
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
return this._remove_id_from_scene_roots(root_id);
|
|
5055
|
+
}
|
|
5056
|
+
_remove_id_from_scene_roots(id) {
|
|
5057
|
+
const sr_blocks = [...this.find_by_class_id(166), ...this.find_by_class_id(1660057539)];
|
|
5058
|
+
if (sr_blocks.length === 0)
|
|
5059
|
+
return false;
|
|
5060
|
+
const sr_block = sr_blocks[0];
|
|
5061
|
+
let raw = sr_block.raw;
|
|
5062
|
+
const root_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${id}\\}`);
|
|
5063
|
+
if (!root_line.test(raw))
|
|
5064
|
+
return false;
|
|
5065
|
+
raw = raw.replace(root_line, "");
|
|
5066
|
+
sr_block.replace_raw(raw);
|
|
5067
|
+
return true;
|
|
5068
|
+
}
|
|
4831
5069
|
reorder_entities(ordered_transform_ids) {
|
|
4832
5070
|
const entity_groups = [];
|
|
4833
5071
|
for (const transform_id of ordered_transform_ids) {
|
|
4834
|
-
const
|
|
4835
|
-
if (!
|
|
5072
|
+
const block = this.find_by_file_id(transform_id);
|
|
5073
|
+
if (!block)
|
|
5074
|
+
continue;
|
|
5075
|
+
let pi_block = null;
|
|
5076
|
+
if (block.class_id === 1001 || block.class_id === 310) {
|
|
5077
|
+
pi_block = block;
|
|
5078
|
+
} else if (block.is_stripped) {
|
|
5079
|
+
const pi_match = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(-?\d+)\}/);
|
|
5080
|
+
if (pi_match) {
|
|
5081
|
+
const found = this.find_by_file_id(pi_match[1]);
|
|
5082
|
+
if (found && (found.class_id === 1001 || found.class_id === 310)) {
|
|
5083
|
+
pi_block = found;
|
|
5084
|
+
}
|
|
5085
|
+
}
|
|
5086
|
+
}
|
|
5087
|
+
if (pi_block) {
|
|
5088
|
+
const group = [pi_block.file_id];
|
|
5089
|
+
const hierarchy = this.collect_hierarchy(pi_block.file_id);
|
|
5090
|
+
for (const id of hierarchy)
|
|
5091
|
+
group.push(id);
|
|
5092
|
+
entity_groups.push(group);
|
|
4836
5093
|
continue;
|
|
4837
|
-
|
|
4838
|
-
|
|
5094
|
+
}
|
|
5095
|
+
const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
|
|
5096
|
+
if (!go_match) {
|
|
5097
|
+
entity_groups.push([transform_id]);
|
|
4839
5098
|
continue;
|
|
5099
|
+
}
|
|
4840
5100
|
const go_id = go_match[1];
|
|
4841
5101
|
const go = this.find_by_file_id(go_id);
|
|
4842
|
-
if (!go)
|
|
5102
|
+
if (!go) {
|
|
5103
|
+
entity_groups.push([transform_id]);
|
|
4843
5104
|
continue;
|
|
5105
|
+
}
|
|
4844
5106
|
const comp_ids = [];
|
|
4845
5107
|
const comp_matches = go.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
|
|
4846
5108
|
for (const m of comp_matches) {
|
|
@@ -5223,48 +5485,320 @@ function find_game_object_in_variant(doc, name, file_path, project_path) {
|
|
|
5223
5485
|
}
|
|
5224
5486
|
return null;
|
|
5225
5487
|
}
|
|
5226
|
-
function
|
|
5227
|
-
const
|
|
5228
|
-
if (!
|
|
5229
|
-
return;
|
|
5230
|
-
const
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
|
|
5236
|
-
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5488
|
+
function parsePrefabRef(refText) {
|
|
5489
|
+
const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
|
|
5490
|
+
if (!fileIdMatch)
|
|
5491
|
+
return null;
|
|
5492
|
+
const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
|
|
5493
|
+
const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
|
|
5494
|
+
return {
|
|
5495
|
+
file_id: fileIdMatch[1],
|
|
5496
|
+
guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
|
|
5497
|
+
type: typeMatch ? typeMatch[1] : undefined
|
|
5498
|
+
};
|
|
5499
|
+
}
|
|
5500
|
+
function extractPrefabSourceGuid(prefabInstanceRaw) {
|
|
5501
|
+
const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
|
|
5502
|
+
return sourceMatch ? sourceMatch[1].toLowerCase() : null;
|
|
5503
|
+
}
|
|
5504
|
+
function normalizePrefabRef(refText, sourceGuid) {
|
|
5505
|
+
const parsed = parsePrefabRef(refText);
|
|
5506
|
+
if (!parsed) {
|
|
5507
|
+
return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
5508
|
+
}
|
|
5509
|
+
const guid = parsed.guid ?? sourceGuid;
|
|
5510
|
+
if (!guid) {
|
|
5511
|
+
return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
|
|
5240
5512
|
}
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
raw = raw.replace(existingPattern, `$1${entry}`);
|
|
5244
|
-
piBlock.replace_raw(raw);
|
|
5513
|
+
if (!/^[a-f0-9]{32}$/i.test(guid)) {
|
|
5514
|
+
return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
|
|
5245
5515
|
}
|
|
5516
|
+
const type = parsed.type ?? "3";
|
|
5517
|
+
if (!/^-?\d+$/.test(type)) {
|
|
5518
|
+
return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
|
|
5519
|
+
}
|
|
5520
|
+
return {
|
|
5521
|
+
ref: {
|
|
5522
|
+
file_id: parsed.file_id,
|
|
5523
|
+
guid,
|
|
5524
|
+
type
|
|
5525
|
+
}
|
|
5526
|
+
};
|
|
5246
5527
|
}
|
|
5247
|
-
function
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
const
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5528
|
+
function serializePrefabRef(ref) {
|
|
5529
|
+
return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
|
|
5530
|
+
}
|
|
5531
|
+
function ensurePrefabModificationSerializedVersion(prefabInstanceBlock) {
|
|
5532
|
+
const raw = prefabInstanceBlock.raw;
|
|
5533
|
+
const updated = raw.replace(/(^([ \t]*)m_Modification:[ \t]*\n)(?!\2[ \t]+serializedVersion:[ \t]*\d+)/m, (_match, prefix, indent) => `${prefix}${indent} serializedVersion: 3
|
|
5534
|
+
`);
|
|
5535
|
+
if (updated === raw) {
|
|
5536
|
+
return false;
|
|
5537
|
+
}
|
|
5538
|
+
prefabInstanceBlock.replace_raw(updated);
|
|
5539
|
+
return true;
|
|
5540
|
+
}
|
|
5541
|
+
function addedOverrideEntryKey(entry) {
|
|
5542
|
+
return `${entry.target.file_id}|${entry.target.guid}|${entry.target.type}|${entry.insert_index}|${entry.added_object_file_id}`;
|
|
5543
|
+
}
|
|
5544
|
+
function splitPrefabArraySection(rawText, key) {
|
|
5545
|
+
const lines = rawText.split(`
|
|
5546
|
+
`);
|
|
5547
|
+
const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
|
|
5548
|
+
let keyIndex = -1;
|
|
5549
|
+
let indent = "";
|
|
5550
|
+
for (let i = 0;i < lines.length; i++) {
|
|
5551
|
+
const match = lines[i].match(keyPattern);
|
|
5552
|
+
if (match) {
|
|
5553
|
+
keyIndex = i;
|
|
5554
|
+
indent = match[1];
|
|
5555
|
+
break;
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
if (keyIndex === -1) {
|
|
5559
|
+
return null;
|
|
5560
|
+
}
|
|
5561
|
+
const keyIndentLen = indent.length;
|
|
5562
|
+
let sectionEnd = keyIndex + 1;
|
|
5563
|
+
for (let i = keyIndex + 1;i < lines.length; i++) {
|
|
5564
|
+
const line = lines[i];
|
|
5565
|
+
const trimmed = line.trim();
|
|
5566
|
+
if (trimmed.length === 0) {
|
|
5567
|
+
sectionEnd = i + 1;
|
|
5568
|
+
continue;
|
|
5569
|
+
}
|
|
5570
|
+
const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
5571
|
+
if (indentLen < keyIndentLen) {
|
|
5572
|
+
break;
|
|
5573
|
+
}
|
|
5574
|
+
if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
|
|
5575
|
+
break;
|
|
5576
|
+
}
|
|
5577
|
+
sectionEnd = i + 1;
|
|
5578
|
+
}
|
|
5579
|
+
return {
|
|
5580
|
+
lines,
|
|
5581
|
+
key_index: keyIndex,
|
|
5582
|
+
section_end: sectionEnd,
|
|
5583
|
+
indent
|
|
5584
|
+
};
|
|
5585
|
+
}
|
|
5586
|
+
function collectAddedOverrideEntries(lines, key_index, section_end, sourceGuid) {
|
|
5587
|
+
const sectionLines = lines.slice(key_index + 1, section_end);
|
|
5588
|
+
const dedup = new Map;
|
|
5589
|
+
let i = 0;
|
|
5590
|
+
while (i < sectionLines.length) {
|
|
5591
|
+
const line = sectionLines[i];
|
|
5592
|
+
const trimmed = line.trimStart();
|
|
5593
|
+
if (!trimmed.startsWith("-")) {
|
|
5594
|
+
i++;
|
|
5595
|
+
continue;
|
|
5596
|
+
}
|
|
5597
|
+
const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
5598
|
+
const entryLines = [line];
|
|
5599
|
+
i++;
|
|
5600
|
+
while (i < sectionLines.length) {
|
|
5601
|
+
const nextLine = sectionLines[i];
|
|
5602
|
+
const nextTrimmed = nextLine.trim();
|
|
5603
|
+
const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
|
|
5604
|
+
if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
|
|
5605
|
+
break;
|
|
5606
|
+
}
|
|
5607
|
+
entryLines.push(nextLine);
|
|
5608
|
+
i++;
|
|
5609
|
+
}
|
|
5610
|
+
const entryText = entryLines.join(`
|
|
5611
|
+
`);
|
|
5612
|
+
const targetMatch = entryText.match(/targetCorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
|
|
5613
|
+
const insertMatch = entryText.match(/insertIndex:[ \t]*(-?\d+)/);
|
|
5614
|
+
const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
|
|
5615
|
+
if (!targetMatch || !addedObjectMatch) {
|
|
5616
|
+
continue;
|
|
5617
|
+
}
|
|
5618
|
+
const normalizedTarget = normalizePrefabRef(targetMatch[1], sourceGuid).ref;
|
|
5619
|
+
if (!normalizedTarget) {
|
|
5620
|
+
continue;
|
|
5621
|
+
}
|
|
5622
|
+
const insertIndex = insertMatch ? Number.parseInt(insertMatch[1], 10) : -1;
|
|
5623
|
+
const entry = {
|
|
5624
|
+
target: normalizedTarget,
|
|
5625
|
+
insert_index: Number.isNaN(insertIndex) ? -1 : insertIndex,
|
|
5626
|
+
added_object_file_id: addedObjectMatch[1]
|
|
5627
|
+
};
|
|
5628
|
+
const dedupKey = addedOverrideEntryKey(entry);
|
|
5629
|
+
if (!dedup.has(dedupKey)) {
|
|
5630
|
+
dedup.set(dedupKey, entry);
|
|
5631
|
+
}
|
|
5261
5632
|
}
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5633
|
+
return [...dedup.values()];
|
|
5634
|
+
}
|
|
5635
|
+
function appendToAddedOverrideArray(doc, piId, key, targetSourceRef, newObjectId) {
|
|
5636
|
+
const piBlock = doc.find_by_file_id(piId);
|
|
5637
|
+
if (!piBlock) {
|
|
5638
|
+
return { success: false, error: `PrefabInstance with fileID ${piId} not found` };
|
|
5639
|
+
}
|
|
5640
|
+
ensurePrefabModificationSerializedVersion(piBlock);
|
|
5641
|
+
const split = splitPrefabArraySection(piBlock.raw, key);
|
|
5642
|
+
if (!split) {
|
|
5643
|
+
return { success: false, error: `${key} property not found in PrefabInstance ${piId}` };
|
|
5644
|
+
}
|
|
5645
|
+
const sourceGuid = extractPrefabSourceGuid(piBlock.raw);
|
|
5646
|
+
const normalizedTarget = normalizePrefabRef(targetSourceRef, sourceGuid);
|
|
5647
|
+
if (!normalizedTarget.ref) {
|
|
5648
|
+
return { success: false, error: normalizedTarget.error };
|
|
5649
|
+
}
|
|
5650
|
+
const candidate = {
|
|
5651
|
+
target: normalizedTarget.ref,
|
|
5652
|
+
insert_index: -1,
|
|
5653
|
+
added_object_file_id: newObjectId
|
|
5654
|
+
};
|
|
5655
|
+
const candidateKey = addedOverrideEntryKey(candidate);
|
|
5656
|
+
const existing = collectAddedOverrideEntries(split.lines, split.key_index, split.section_end, sourceGuid);
|
|
5657
|
+
if (existing.some((entry) => addedOverrideEntryKey(entry) === candidateKey)) {
|
|
5658
|
+
return { success: true, changed: false };
|
|
5659
|
+
}
|
|
5660
|
+
const entryLines = [
|
|
5661
|
+
`${split.indent}- targetCorrespondingSourceObject: ${serializePrefabRef(candidate.target)}`,
|
|
5662
|
+
`${split.indent} insertIndex: ${candidate.insert_index}`,
|
|
5663
|
+
`${split.indent} addedObject: {fileID: ${candidate.added_object_file_id}}`
|
|
5664
|
+
];
|
|
5665
|
+
const keyLine = split.lines[split.key_index].trim();
|
|
5666
|
+
let insertIndex = split.section_end;
|
|
5667
|
+
while (insertIndex > split.key_index + 1 && split.lines[insertIndex - 1].trim().length === 0) {
|
|
5668
|
+
insertIndex--;
|
|
5669
|
+
}
|
|
5670
|
+
let updatedLines;
|
|
5671
|
+
if (keyLine === `${key}: []`) {
|
|
5672
|
+
const replacement = [`${split.indent}${key}:`, ...entryLines];
|
|
5673
|
+
updatedLines = [
|
|
5674
|
+
...split.lines.slice(0, split.key_index),
|
|
5675
|
+
...replacement,
|
|
5676
|
+
...split.lines.slice(split.section_end)
|
|
5677
|
+
];
|
|
5678
|
+
} else {
|
|
5679
|
+
updatedLines = [
|
|
5680
|
+
...split.lines.slice(0, insertIndex),
|
|
5681
|
+
...entryLines,
|
|
5682
|
+
...split.lines.slice(insertIndex)
|
|
5683
|
+
];
|
|
5266
5684
|
}
|
|
5685
|
+
piBlock.replace_raw(updatedLines.join(`
|
|
5686
|
+
`));
|
|
5687
|
+
return { success: true, changed: true };
|
|
5688
|
+
}
|
|
5689
|
+
function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
|
|
5690
|
+
return appendToAddedOverrideArray(doc, piId, "m_AddedGameObjects", targetSourceRef, newGoId);
|
|
5691
|
+
}
|
|
5692
|
+
function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
|
|
5693
|
+
return appendToAddedOverrideArray(doc, piId, "m_AddedComponents", targetSourceRef, newComponentId);
|
|
5267
5694
|
}
|
|
5695
|
+
var COMPONENT_FULL_TEMPLATES = {
|
|
5696
|
+
65: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
|
|
5697
|
+
m_Material: {fileID: 0}
|
|
5698
|
+
m_IncludeLayers:
|
|
5699
|
+
serializedVersion: 2
|
|
5700
|
+
m_Bits: 0
|
|
5701
|
+
m_ExcludeLayers:
|
|
5702
|
+
serializedVersion: 2
|
|
5703
|
+
m_Bits: 0
|
|
5704
|
+
m_LayerOverridePriority: 0
|
|
5705
|
+
m_IsTrigger: 0
|
|
5706
|
+
m_ProvidesContacts: 0
|
|
5707
|
+
m_Enabled: 1
|
|
5708
|
+
serializedVersion: 3
|
|
5709
|
+
m_Size: {x: 1, y: 1, z: 1}
|
|
5710
|
+
m_Center: {x: 0, y: 0, z: 0}`,
|
|
5711
|
+
82: (gameObjectId) => ` m_GameObject: {fileID: ${gameObjectId}}
|
|
5712
|
+
m_Enabled: 1
|
|
5713
|
+
serializedVersion: 4
|
|
5714
|
+
OutputAudioMixerGroup: {fileID: 0}
|
|
5715
|
+
m_audioClip: {fileID: 0}
|
|
5716
|
+
m_PlayOnAwake: 1
|
|
5717
|
+
m_Volume: 1
|
|
5718
|
+
m_Pitch: 1
|
|
5719
|
+
Loop: 0
|
|
5720
|
+
Mute: 0
|
|
5721
|
+
Spatialize: 0
|
|
5722
|
+
SpatializePostEffects: 0
|
|
5723
|
+
Priority: 128
|
|
5724
|
+
DopplerLevel: 1
|
|
5725
|
+
MinDistance: 1
|
|
5726
|
+
MaxDistance: 500
|
|
5727
|
+
Pan2D: 0
|
|
5728
|
+
rolloffMode: 0
|
|
5729
|
+
BypassEffects: 0
|
|
5730
|
+
BypassListenerEffects: 0
|
|
5731
|
+
BypassReverbZones: 0
|
|
5732
|
+
rolloffCustomCurve:
|
|
5733
|
+
serializedVersion: 2
|
|
5734
|
+
m_Curve:
|
|
5735
|
+
- serializedVersion: 3
|
|
5736
|
+
time: 0
|
|
5737
|
+
value: 1
|
|
5738
|
+
inSlope: 0
|
|
5739
|
+
outSlope: 0
|
|
5740
|
+
tangentMode: 0
|
|
5741
|
+
weightedMode: 0
|
|
5742
|
+
inWeight: 0.33333334
|
|
5743
|
+
outWeight: 0.33333334
|
|
5744
|
+
- serializedVersion: 3
|
|
5745
|
+
time: 1
|
|
5746
|
+
value: 0
|
|
5747
|
+
inSlope: 0
|
|
5748
|
+
outSlope: 0
|
|
5749
|
+
tangentMode: 0
|
|
5750
|
+
weightedMode: 0
|
|
5751
|
+
inWeight: 0.33333334
|
|
5752
|
+
outWeight: 0.33333334
|
|
5753
|
+
m_PreInfinity: 2
|
|
5754
|
+
m_PostInfinity: 2
|
|
5755
|
+
m_RotationOrder: 4
|
|
5756
|
+
panLevelCustomCurve:
|
|
5757
|
+
serializedVersion: 2
|
|
5758
|
+
m_Curve:
|
|
5759
|
+
- serializedVersion: 3
|
|
5760
|
+
time: 0
|
|
5761
|
+
value: 0
|
|
5762
|
+
inSlope: 0
|
|
5763
|
+
outSlope: 0
|
|
5764
|
+
tangentMode: 0
|
|
5765
|
+
weightedMode: 0
|
|
5766
|
+
inWeight: 0.33333334
|
|
5767
|
+
outWeight: 0.33333334
|
|
5768
|
+
m_PreInfinity: 2
|
|
5769
|
+
m_PostInfinity: 2
|
|
5770
|
+
m_RotationOrder: 4
|
|
5771
|
+
spreadCustomCurve:
|
|
5772
|
+
serializedVersion: 2
|
|
5773
|
+
m_Curve:
|
|
5774
|
+
- serializedVersion: 3
|
|
5775
|
+
time: 0
|
|
5776
|
+
value: 0
|
|
5777
|
+
inSlope: 0
|
|
5778
|
+
outSlope: 0
|
|
5779
|
+
tangentMode: 0
|
|
5780
|
+
weightedMode: 0
|
|
5781
|
+
inWeight: 0.33333334
|
|
5782
|
+
outWeight: 0.33333334
|
|
5783
|
+
m_PreInfinity: 2
|
|
5784
|
+
m_PostInfinity: 2
|
|
5785
|
+
m_RotationOrder: 4
|
|
5786
|
+
reverbZoneMixCustomCurve:
|
|
5787
|
+
serializedVersion: 2
|
|
5788
|
+
m_Curve:
|
|
5789
|
+
- serializedVersion: 3
|
|
5790
|
+
time: 0
|
|
5791
|
+
value: 1
|
|
5792
|
+
inSlope: 0
|
|
5793
|
+
outSlope: 0
|
|
5794
|
+
tangentMode: 0
|
|
5795
|
+
weightedMode: 0
|
|
5796
|
+
inWeight: 0.33333334
|
|
5797
|
+
outWeight: 0.33333334
|
|
5798
|
+
m_PreInfinity: 2
|
|
5799
|
+
m_PostInfinity: 2
|
|
5800
|
+
m_RotationOrder: 4`
|
|
5801
|
+
};
|
|
5268
5802
|
var COMPONENT_DEFAULTS = {
|
|
5269
5803
|
20: ` serializedVersion: 2
|
|
5270
5804
|
m_ClearFlags: 1
|
|
@@ -5391,6 +5925,17 @@ var COMPONENT_DEFAULTS = {
|
|
|
5391
5925
|
m_IgnoreParentGroups: 0`
|
|
5392
5926
|
};
|
|
5393
5927
|
function createGenericComponentYAML(componentName, classId, componentId, gameObjectId) {
|
|
5928
|
+
const fullTemplate = COMPONENT_FULL_TEMPLATES[classId];
|
|
5929
|
+
if (fullTemplate) {
|
|
5930
|
+
return `--- !u!${classId} &${componentId}
|
|
5931
|
+
${componentName}:
|
|
5932
|
+
m_ObjectHideFlags: 0
|
|
5933
|
+
m_CorrespondingSourceObject: {fileID: 0}
|
|
5934
|
+
m_PrefabInstance: {fileID: 0}
|
|
5935
|
+
m_PrefabAsset: {fileID: 0}
|
|
5936
|
+
${fullTemplate(gameObjectId)}
|
|
5937
|
+
`;
|
|
5938
|
+
}
|
|
5394
5939
|
const defaults = COMPONENT_DEFAULTS[classId] ? `
|
|
5395
5940
|
` + COMPONENT_DEFAULTS[classId] + `
|
|
5396
5941
|
` : "";
|
|
@@ -5543,7 +6088,14 @@ function createGameObject(options) {
|
|
|
5543
6088
|
doc.add_child_to_parent(parentTransformIdStr, transformIdStr);
|
|
5544
6089
|
}
|
|
5545
6090
|
if (variantPiId && strippedSourceRef) {
|
|
5546
|
-
appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
|
|
6091
|
+
const appendResult = appendToAddedGameObjects(doc, variantPiId, strippedSourceRef, gameObjectIdStr);
|
|
6092
|
+
if (!appendResult.success) {
|
|
6093
|
+
return {
|
|
6094
|
+
success: false,
|
|
6095
|
+
file_path,
|
|
6096
|
+
error: appendResult.error || `Failed to update m_AddedGameObjects for PrefabInstance ${variantPiId}`
|
|
6097
|
+
};
|
|
6098
|
+
}
|
|
5547
6099
|
}
|
|
5548
6100
|
const saveResult = doc.save();
|
|
5549
6101
|
if (!saveResult.success) {
|
|
@@ -6026,6 +6578,7 @@ PrefabInstance:
|
|
|
6026
6578
|
m_ObjectHideFlags: 0
|
|
6027
6579
|
serializedVersion: 2
|
|
6028
6580
|
m_Modification:
|
|
6581
|
+
serializedVersion: 3
|
|
6029
6582
|
m_TransformParent: {fileID: 0}
|
|
6030
6583
|
m_Modifications:
|
|
6031
6584
|
- target: {fileID: ${rootInfo.game_object.file_id}, guid: ${sourceGuid}, type: 3}
|
|
@@ -6172,6 +6725,7 @@ PrefabInstance:
|
|
|
6172
6725
|
m_ObjectHideFlags: 0
|
|
6173
6726
|
serializedVersion: 2
|
|
6174
6727
|
m_Modification:
|
|
6728
|
+
serializedVersion: 3
|
|
6175
6729
|
m_TransformParent: {fileID: ${parentTransformIdStr}}
|
|
6176
6730
|
m_Modifications:
|
|
6177
6731
|
- target: {fileID: ${rootGoFileId}, guid: ${sourceGuid}, type: 3}
|
|
@@ -6276,6 +6830,9 @@ function createScriptableObject(options) {
|
|
|
6276
6830
|
if (resolved.kind === "enum" || resolved.kind === "interface") {
|
|
6277
6831
|
return { success: false, output_path, error: `"${script}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a ScriptableObject.` };
|
|
6278
6832
|
}
|
|
6833
|
+
if (resolved.is_abstract) {
|
|
6834
|
+
return { success: false, output_path, error: `"${script}" is abstract and cannot be instantiated as a ScriptableObject asset.` };
|
|
6835
|
+
}
|
|
6279
6836
|
if (resolved.base_class && resolved.base_class !== "ScriptableObject") {
|
|
6280
6837
|
return { success: false, output_path, error: `"${script}" extends ${resolved.base_class}, not ScriptableObject. Cannot create as a ScriptableObject asset.` };
|
|
6281
6838
|
}
|
|
@@ -6476,6 +7033,8 @@ function is_valid_component_base(base_class, project_path, depth = 0) {
|
|
|
6476
7033
|
}
|
|
6477
7034
|
if (!fullPath.endsWith(".cs") || !import_fs8.existsSync(fullPath)) {
|
|
6478
7035
|
if (match.kind === "class") {
|
|
7036
|
+
if (fullPath.includes("Assembly-CSharp.dll"))
|
|
7037
|
+
return true;
|
|
6479
7038
|
return is_likely_component_base_name(base_class);
|
|
6480
7039
|
}
|
|
6481
7040
|
}
|
|
@@ -6557,6 +7116,13 @@ function addComponent(options) {
|
|
|
6557
7116
|
}
|
|
6558
7117
|
const gameObjectId = gameObjectIdStr;
|
|
6559
7118
|
const classId = get_class_id(component_type);
|
|
7119
|
+
if (classId === 114 && component_type.toLowerCase() === "monobehaviour") {
|
|
7120
|
+
return {
|
|
7121
|
+
success: false,
|
|
7122
|
+
file_path,
|
|
7123
|
+
error: '"MonoBehaviour" is a base class, not a concrete component script. Provide a script type/path/GUID (for example, "PlayerController", "Assets/Scripts/PlayerController.cs", or a 32-char GUID).'
|
|
7124
|
+
};
|
|
7125
|
+
}
|
|
6560
7126
|
let duplicateWarning;
|
|
6561
7127
|
const goBlock = doc.find_by_file_id(gameObjectIdStr);
|
|
6562
7128
|
if (goBlock && classId !== null) {
|
|
@@ -6581,7 +7147,7 @@ function addComponent(options) {
|
|
|
6581
7147
|
} else {
|
|
6582
7148
|
let resolved;
|
|
6583
7149
|
try {
|
|
6584
|
-
resolved = resolve_script_with_fields(component_type, project_path);
|
|
7150
|
+
resolved = resolve_script_with_fields(component_type, project_path, { strict_exact_name: true });
|
|
6585
7151
|
} catch (e) {
|
|
6586
7152
|
return {
|
|
6587
7153
|
success: false,
|
|
@@ -6619,13 +7185,20 @@ function addComponent(options) {
|
|
|
6619
7185
|
error: `"${component_type}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a MonoBehaviour. Cannot add as a component.`
|
|
6620
7186
|
};
|
|
6621
7187
|
}
|
|
6622
|
-
if (resolved.
|
|
7188
|
+
if (resolved.is_abstract) {
|
|
6623
7189
|
return {
|
|
6624
7190
|
success: false,
|
|
6625
7191
|
file_path,
|
|
6626
|
-
error: `"${component_type}"
|
|
7192
|
+
error: `"${component_type}" is abstract and cannot be added as a component.`
|
|
6627
7193
|
};
|
|
6628
7194
|
}
|
|
7195
|
+
if (resolved.base_class && !COMPONENT_BASE_CLASSES.has(resolved.base_class) && !is_valid_component_base(resolved.base_class, project_path)) {
|
|
7196
|
+
const warn = `"${component_type}" extends ${resolved.base_class}, which does not inherit from MonoBehaviour. Adding anyway, but ensure the script is a valid component. (Re-run "setup" if the inheritance graph is stale).`;
|
|
7197
|
+
if (!extractionError)
|
|
7198
|
+
extractionError = warn;
|
|
7199
|
+
else
|
|
7200
|
+
extractionError += " " + warn;
|
|
7201
|
+
}
|
|
6629
7202
|
let version;
|
|
6630
7203
|
if (project_path) {
|
|
6631
7204
|
try {
|
|
@@ -6640,10 +7213,22 @@ function addComponent(options) {
|
|
|
6640
7213
|
componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version, scriptFileId, type_lookup_comp);
|
|
6641
7214
|
scriptGuid = resolved.guid;
|
|
6642
7215
|
scriptPath = resolved.path || undefined;
|
|
6643
|
-
|
|
7216
|
+
if (resolved.extraction_error) {
|
|
7217
|
+
if (!extractionError)
|
|
7218
|
+
extractionError = resolved.extraction_error;
|
|
7219
|
+
else
|
|
7220
|
+
extractionError += " " + resolved.extraction_error;
|
|
7221
|
+
}
|
|
6644
7222
|
}
|
|
6645
7223
|
if (variantInfo) {
|
|
6646
|
-
appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
|
|
7224
|
+
const appendResult = appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
|
|
7225
|
+
if (!appendResult.success) {
|
|
7226
|
+
return {
|
|
7227
|
+
success: false,
|
|
7228
|
+
file_path,
|
|
7229
|
+
error: appendResult.error || `Failed to update m_AddedComponents for PrefabInstance ${variantInfo.prefab_instance_id}`
|
|
7230
|
+
};
|
|
7231
|
+
}
|
|
6647
7232
|
} else {
|
|
6648
7233
|
const added = addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
|
|
6649
7234
|
if (!added) {
|
|
@@ -7439,6 +8024,56 @@ function validate_value_type(current_value, new_value) {
|
|
|
7439
8024
|
}
|
|
7440
8025
|
return null;
|
|
7441
8026
|
}
|
|
8027
|
+
function findStrippedTransformForPrefabInstance(doc, prefabInstanceId) {
|
|
8028
|
+
const piRefPattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${prefabInstanceId}\\}`);
|
|
8029
|
+
for (const block of doc.blocks) {
|
|
8030
|
+
if ((block.class_id === 4 || block.class_id === 224) && block.is_stripped && piRefPattern.test(block.raw)) {
|
|
8031
|
+
return block;
|
|
8032
|
+
}
|
|
8033
|
+
}
|
|
8034
|
+
return null;
|
|
8035
|
+
}
|
|
8036
|
+
function resolvePrefabRootOrderTarget(prefabInstanceBlock) {
|
|
8037
|
+
const modificationPattern = /- target:\s*(\{[^}]+\})\s*\n\s*propertyPath:\s*('?)([^\n']+)\2/gm;
|
|
8038
|
+
const transformPropertyPattern = /^m_(?:RootOrder|LocalPosition|LocalRotation|LocalScale|ConstrainProportionsScale)(?:\.|$)/;
|
|
8039
|
+
let transformTarget = null;
|
|
8040
|
+
for (const match of prefabInstanceBlock.raw.matchAll(modificationPattern)) {
|
|
8041
|
+
const targetRef = match[1];
|
|
8042
|
+
const propertyPath = match[3].trim();
|
|
8043
|
+
if (propertyPath === "m_RootOrder") {
|
|
8044
|
+
return targetRef;
|
|
8045
|
+
}
|
|
8046
|
+
if (!transformTarget && transformPropertyPattern.test(propertyPath)) {
|
|
8047
|
+
transformTarget = targetRef;
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
return transformTarget;
|
|
8051
|
+
}
|
|
8052
|
+
function upsertPrefabRootOrderOverride(prefabInstanceBlock, newRootOrder) {
|
|
8053
|
+
const existingRootOrderPattern = /(- target:\s*\{[^}]+\}\s*\n\s*propertyPath:\s*'?m_RootOrder'?\s*\n\s*value:\s*)([^\n]*)(\s*\n\s*objectReference:\s*\{[^}]+\})/m;
|
|
8054
|
+
if (existingRootOrderPattern.test(prefabInstanceBlock.raw)) {
|
|
8055
|
+
prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(existingRootOrderPattern, `$1${newRootOrder}$3`));
|
|
8056
|
+
return true;
|
|
8057
|
+
}
|
|
8058
|
+
const targetRef = resolvePrefabRootOrderTarget(prefabInstanceBlock) ?? "{fileID: 400000}";
|
|
8059
|
+
const rootOrderOverride = ` - target: ${targetRef}
|
|
8060
|
+
` + ` propertyPath: m_RootOrder
|
|
8061
|
+
` + ` value: ${newRootOrder}
|
|
8062
|
+
` + " objectReference: {fileID: 0}";
|
|
8063
|
+
const removedPattern = /(\n\s*m_RemovedComponents:)/m;
|
|
8064
|
+
if (removedPattern.test(prefabInstanceBlock.raw)) {
|
|
8065
|
+
prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(removedPattern, `
|
|
8066
|
+
${rootOrderOverride}$1`));
|
|
8067
|
+
return true;
|
|
8068
|
+
}
|
|
8069
|
+
const modsPattern = /(m_Modifications:\s*\n)/;
|
|
8070
|
+
if (modsPattern.test(prefabInstanceBlock.raw)) {
|
|
8071
|
+
prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(modsPattern, `$1${rootOrderOverride}
|
|
8072
|
+
`));
|
|
8073
|
+
return true;
|
|
8074
|
+
}
|
|
8075
|
+
return false;
|
|
8076
|
+
}
|
|
7442
8077
|
function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
|
|
7443
8078
|
let currentId = candidateAncestorTransformId;
|
|
7444
8079
|
const visited = new Set;
|
|
@@ -7449,20 +8084,37 @@ function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
|
|
|
7449
8084
|
return false;
|
|
7450
8085
|
visited.add(currentId);
|
|
7451
8086
|
const block = doc.find_by_file_id(currentId);
|
|
7452
|
-
if (!block
|
|
8087
|
+
if (!block)
|
|
8088
|
+
break;
|
|
8089
|
+
if (block.class_id === 1001 || block.class_id === 310) {
|
|
8090
|
+
const parentMatch = block.raw.match(/m_TransformParent:\s*\{fileID:\s*(-?\d+)\}/);
|
|
8091
|
+
currentId = parentMatch ? parentMatch[1] : "0";
|
|
8092
|
+
} else if (block.class_id === 4 || block.class_id === 224) {
|
|
8093
|
+
const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
8094
|
+
currentId = fatherMatch ? fatherMatch[1] : "0";
|
|
8095
|
+
} else {
|
|
7453
8096
|
break;
|
|
7454
|
-
|
|
7455
|
-
currentId = fatherMatch ? fatherMatch[1] : "0";
|
|
8097
|
+
}
|
|
7456
8098
|
}
|
|
7457
8099
|
return false;
|
|
7458
8100
|
}
|
|
7459
8101
|
function resolveTransformByGameObjectId(doc, gameObjectFileId) {
|
|
7460
8102
|
const found = doc.find_by_file_id(gameObjectFileId);
|
|
7461
|
-
if (!found
|
|
7462
|
-
return { error: `
|
|
8103
|
+
if (!found) {
|
|
8104
|
+
return { error: `Block with fileID ${gameObjectFileId} not found` };
|
|
7463
8105
|
}
|
|
7464
|
-
|
|
7465
|
-
|
|
8106
|
+
if (found.class_id === 4 || found.class_id === 224) {
|
|
8107
|
+
return { id: gameObjectFileId };
|
|
8108
|
+
}
|
|
8109
|
+
if (found.class_id === 1001 || found.class_id === 310) {
|
|
8110
|
+
const strippedTransform = findStrippedTransformForPrefabInstance(doc, gameObjectFileId);
|
|
8111
|
+
return { id: strippedTransform ? strippedTransform.file_id : gameObjectFileId };
|
|
8112
|
+
}
|
|
8113
|
+
if (found.class_id !== 1) {
|
|
8114
|
+
return { error: `fileID ${gameObjectFileId} is not a GameObject, Transform, or PrefabInstance (class ${found.class_id})` };
|
|
8115
|
+
}
|
|
8116
|
+
const componentMatch = found.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(-?\d+)\}/);
|
|
8117
|
+
if (!componentMatch) {
|
|
7466
8118
|
return { error: `GameObject fileID ${gameObjectFileId} has no Transform component` };
|
|
7467
8119
|
}
|
|
7468
8120
|
const transformId = componentMatch[1];
|
|
@@ -7615,7 +8267,7 @@ function editComponentByFileId(options) {
|
|
|
7615
8267
|
return {
|
|
7616
8268
|
success: false,
|
|
7617
8269
|
file_path,
|
|
7618
|
-
error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup <
|
|
8270
|
+
error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup" first, or "setup -p <path>").`
|
|
7619
8271
|
};
|
|
7620
8272
|
}
|
|
7621
8273
|
if (resolved !== undefined) {
|
|
@@ -7652,7 +8304,7 @@ function editComponentByFileId(options) {
|
|
|
7652
8304
|
}
|
|
7653
8305
|
const targetBlock = doc.find_by_file_id(file_id);
|
|
7654
8306
|
if (!targetBlock) {
|
|
7655
|
-
const hasPrefabInstance = doc.find_by_class_id(1001).length > 0;
|
|
8307
|
+
const hasPrefabInstance = doc.find_by_class_id(1001).length > 0 || doc.find_by_class_id(310).length > 0;
|
|
7656
8308
|
if (hasPrefabInstance) {
|
|
7657
8309
|
return {
|
|
7658
8310
|
success: false,
|
|
@@ -8117,10 +8769,26 @@ function reparentGameObject(options) {
|
|
|
8117
8769
|
}
|
|
8118
8770
|
const childBlock = doc.find_by_file_id(childTransformId);
|
|
8119
8771
|
if (!childBlock) {
|
|
8120
|
-
return { success: false, file_path, error: `Transform ${childTransformId} not found` };
|
|
8772
|
+
return { success: false, file_path, error: `Transform or PrefabInstance ${childTransformId} not found` };
|
|
8773
|
+
}
|
|
8774
|
+
const childHierarchyTransformId = (() => {
|
|
8775
|
+
if (childBlock.class_id === 4 || childBlock.class_id === 224) {
|
|
8776
|
+
return childBlock.file_id;
|
|
8777
|
+
}
|
|
8778
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
8779
|
+
const stripped = findStrippedTransformForPrefabInstance(doc, childBlock.file_id);
|
|
8780
|
+
return stripped ? stripped.file_id : null;
|
|
8781
|
+
}
|
|
8782
|
+
return null;
|
|
8783
|
+
})();
|
|
8784
|
+
let oldParentTransformId = "0";
|
|
8785
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
8786
|
+
const parentMatch = childBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(-?\d+)\}/);
|
|
8787
|
+
oldParentTransformId = parentMatch ? parentMatch[1] : "0";
|
|
8788
|
+
} else {
|
|
8789
|
+
const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
8790
|
+
oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
|
|
8121
8791
|
}
|
|
8122
|
-
const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
|
|
8123
|
-
const oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
|
|
8124
8792
|
let newParentTransformId = "0";
|
|
8125
8793
|
if (new_parent.toLowerCase() !== "root") {
|
|
8126
8794
|
if (by_id || /^-?\d+$/.test(new_parent)) {
|
|
@@ -8147,11 +8815,55 @@ function reparentGameObject(options) {
|
|
|
8147
8815
|
}
|
|
8148
8816
|
}
|
|
8149
8817
|
if (oldParentTransformId !== "0") {
|
|
8150
|
-
|
|
8818
|
+
if (childHierarchyTransformId) {
|
|
8819
|
+
doc.remove_child_from_parent(oldParentTransformId, childHierarchyTransformId);
|
|
8820
|
+
} else {
|
|
8821
|
+
doc.remove_child_from_parent(oldParentTransformId, childBlock.file_id);
|
|
8822
|
+
}
|
|
8823
|
+
} else {
|
|
8824
|
+
if (childHierarchyTransformId) {
|
|
8825
|
+
doc.remove_root_from_scene_roots(childHierarchyTransformId);
|
|
8826
|
+
} else {
|
|
8827
|
+
doc.remove_root_from_scene_roots(childBlock.file_id);
|
|
8828
|
+
}
|
|
8829
|
+
}
|
|
8830
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
8831
|
+
const tpPattern = new RegExp(`(m_TransformParent:\\s*)\\{fileID:\\s*(-?\\d+)\\}`);
|
|
8832
|
+
if (tpPattern.test(childBlock.raw)) {
|
|
8833
|
+
let updatedChildRaw = childBlock.raw.replace(tpPattern, `$1{fileID: ${newParentTransformId}}`);
|
|
8834
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
8835
|
+
} else {
|
|
8836
|
+
const modPattern = /(m_Modification:\s*\n)/;
|
|
8837
|
+
const modIndentMatch = childBlock.raw.match(/^([ \t]*)m_Modification:/m);
|
|
8838
|
+
const indent = modIndentMatch ? modIndentMatch[1] : "";
|
|
8839
|
+
let updatedChildRaw = childBlock.raw.replace(modPattern, `$1${indent} m_TransformParent: {fileID: ${newParentTransformId}}
|
|
8840
|
+
`);
|
|
8841
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
8842
|
+
}
|
|
8843
|
+
} else {
|
|
8844
|
+
const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
|
|
8845
|
+
if (fatherPattern.test(childBlock.raw)) {
|
|
8846
|
+
let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
|
|
8847
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
8848
|
+
} else {
|
|
8849
|
+
const anchorPattern = /^([ \t]*(?:m_GameObject|m_CorrespondingSourceObject|m_PrefabInstance):[ \t]*\{fileID:[ \t]*(-?\d+)[^}]*\}.*)/m;
|
|
8850
|
+
const headerPattern = /(^--- !u!(?:4|224) &-?\d+ stripped\n(?:Rect)?Transform:)/m;
|
|
8851
|
+
if (anchorPattern.test(childBlock.raw)) {
|
|
8852
|
+
let updatedChildRaw = childBlock.raw.replace(anchorPattern, `$1
|
|
8853
|
+
m_Father: {fileID: ${newParentTransformId}}`);
|
|
8854
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
8855
|
+
} else if (headerPattern.test(childBlock.raw)) {
|
|
8856
|
+
let updatedChildRaw = childBlock.raw.replace(headerPattern, `$1
|
|
8857
|
+
m_Father: {fileID: ${newParentTransformId}}`);
|
|
8858
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
8859
|
+
} else {
|
|
8860
|
+
let updatedChildRaw = childBlock.raw.trimEnd() + `
|
|
8861
|
+
m_Father: {fileID: ${newParentTransformId}}
|
|
8862
|
+
`;
|
|
8863
|
+
childBlock.replace_raw(updatedChildRaw);
|
|
8864
|
+
}
|
|
8865
|
+
}
|
|
8151
8866
|
}
|
|
8152
|
-
const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
|
|
8153
|
-
let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
|
|
8154
|
-
childBlock.replace_raw(updatedChildRaw);
|
|
8155
8867
|
{
|
|
8156
8868
|
let newRootOrder;
|
|
8157
8869
|
if (newParentTransformId === "0") {
|
|
@@ -8159,10 +8871,25 @@ function reparentGameObject(options) {
|
|
|
8159
8871
|
} else {
|
|
8160
8872
|
newRootOrder = doc.calculate_root_order(newParentTransformId);
|
|
8161
8873
|
}
|
|
8162
|
-
childBlock.
|
|
8874
|
+
if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
|
|
8875
|
+
const stripped = doc.blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${childTransformId}\\}`).test(b.raw));
|
|
8876
|
+
if (stripped) {
|
|
8877
|
+
stripped.set_property("m_RootOrder", String(newRootOrder));
|
|
8878
|
+
} else {
|
|
8879
|
+
upsertPrefabRootOrderOverride(childBlock, newRootOrder);
|
|
8880
|
+
}
|
|
8881
|
+
} else {
|
|
8882
|
+
childBlock.set_property("m_RootOrder", String(newRootOrder));
|
|
8883
|
+
}
|
|
8163
8884
|
}
|
|
8164
8885
|
if (newParentTransformId !== "0") {
|
|
8165
|
-
|
|
8886
|
+
if (childHierarchyTransformId) {
|
|
8887
|
+
doc.add_child_to_parent(newParentTransformId, childHierarchyTransformId);
|
|
8888
|
+
}
|
|
8889
|
+
} else {
|
|
8890
|
+
if (childHierarchyTransformId) {
|
|
8891
|
+
doc.add_root_to_scene_roots(childHierarchyTransformId);
|
|
8892
|
+
}
|
|
8166
8893
|
}
|
|
8167
8894
|
if (!doc.validate()) {
|
|
8168
8895
|
return { success: false, file_path, error: "Validation failed after reparent" };
|
|
@@ -8181,9 +8908,9 @@ function reparentGameObject(options) {
|
|
|
8181
8908
|
}
|
|
8182
8909
|
function findPrefabInstanceBlock(doc, identifier) {
|
|
8183
8910
|
const asId = doc.find_by_file_id(identifier);
|
|
8184
|
-
if (asId && asId.class_id === 1001)
|
|
8911
|
+
if (asId && (asId.class_id === 1001 || asId.class_id === 310))
|
|
8185
8912
|
return asId;
|
|
8186
|
-
const piBlocks = doc.find_by_class_id(1001);
|
|
8913
|
+
const piBlocks = [...doc.find_by_class_id(1001), ...doc.find_by_class_id(310)];
|
|
8187
8914
|
const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8188
8915
|
const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
|
|
8189
8916
|
for (const block of piBlocks) {
|
|
@@ -8192,6 +8919,193 @@ function findPrefabInstanceBlock(doc, identifier) {
|
|
|
8192
8919
|
}
|
|
8193
8920
|
return null;
|
|
8194
8921
|
}
|
|
8922
|
+
function parsePrefabRef2(refText) {
|
|
8923
|
+
const fileIdMatch = refText.match(/fileID:[ \t]*(-?\d+)/i);
|
|
8924
|
+
if (!fileIdMatch)
|
|
8925
|
+
return null;
|
|
8926
|
+
const guidMatch = refText.match(/guid:[ \t]*([a-f0-9]{32})/i);
|
|
8927
|
+
const typeMatch = refText.match(/type:[ \t]*(-?\d+)/i);
|
|
8928
|
+
return {
|
|
8929
|
+
file_id: fileIdMatch[1],
|
|
8930
|
+
guid: guidMatch ? guidMatch[1].toLowerCase() : undefined,
|
|
8931
|
+
type: typeMatch ? typeMatch[1] : undefined
|
|
8932
|
+
};
|
|
8933
|
+
}
|
|
8934
|
+
function extractPrefabSourceGuid2(prefabInstanceRaw) {
|
|
8935
|
+
const sourceMatch = prefabInstanceRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/i);
|
|
8936
|
+
return sourceMatch ? sourceMatch[1].toLowerCase() : null;
|
|
8937
|
+
}
|
|
8938
|
+
function normalizePrefabRef2(refText, sourceGuid) {
|
|
8939
|
+
const parsed = parsePrefabRef2(refText);
|
|
8940
|
+
if (!parsed) {
|
|
8941
|
+
return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
8942
|
+
}
|
|
8943
|
+
const guid = parsed.guid ?? sourceGuid;
|
|
8944
|
+
if (!guid) {
|
|
8945
|
+
return { error: `Reference "${refText}" is missing guid and source prefab guid could not be inferred.` };
|
|
8946
|
+
}
|
|
8947
|
+
if (!/^[a-f0-9]{32}$/i.test(guid)) {
|
|
8948
|
+
return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
|
|
8949
|
+
}
|
|
8950
|
+
const type = parsed.type ?? "3";
|
|
8951
|
+
if (!/^-?\d+$/.test(type)) {
|
|
8952
|
+
return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
|
|
8953
|
+
}
|
|
8954
|
+
return {
|
|
8955
|
+
ref: {
|
|
8956
|
+
file_id: parsed.file_id,
|
|
8957
|
+
guid,
|
|
8958
|
+
type
|
|
8959
|
+
}
|
|
8960
|
+
};
|
|
8961
|
+
}
|
|
8962
|
+
function parsePrefabRefMatcher(refText) {
|
|
8963
|
+
const parsed = parsePrefabRef2(refText);
|
|
8964
|
+
if (!parsed) {
|
|
8965
|
+
return { error: `Invalid reference "${refText}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
8966
|
+
}
|
|
8967
|
+
if (parsed.guid && !/^[a-f0-9]{32}$/i.test(parsed.guid)) {
|
|
8968
|
+
return { error: `Invalid guid in reference "${refText}". Expected 32-character hex GUID.` };
|
|
8969
|
+
}
|
|
8970
|
+
if (parsed.type && !/^-?\d+$/.test(parsed.type)) {
|
|
8971
|
+
return { error: `Invalid type in reference "${refText}". Expected an integer type value.` };
|
|
8972
|
+
}
|
|
8973
|
+
return { matcher: parsed };
|
|
8974
|
+
}
|
|
8975
|
+
function prefabRefKey(ref) {
|
|
8976
|
+
return `${ref.file_id}|${ref.guid}|${ref.type}`;
|
|
8977
|
+
}
|
|
8978
|
+
function serializePrefabRef2(ref) {
|
|
8979
|
+
return `{fileID: ${ref.file_id}, guid: ${ref.guid}, type: ${ref.type}}`;
|
|
8980
|
+
}
|
|
8981
|
+
function splitPrefabArraySection2(rawText, key) {
|
|
8982
|
+
const lines = rawText.split(`
|
|
8983
|
+
`);
|
|
8984
|
+
const keyPattern = new RegExp(`^([ ]*)${key}:[ ]*(.*)$`);
|
|
8985
|
+
let keyIndex = -1;
|
|
8986
|
+
let indent = "";
|
|
8987
|
+
for (let i = 0;i < lines.length; i++) {
|
|
8988
|
+
const match = lines[i].match(keyPattern);
|
|
8989
|
+
if (match) {
|
|
8990
|
+
keyIndex = i;
|
|
8991
|
+
indent = match[1];
|
|
8992
|
+
break;
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8995
|
+
if (keyIndex === -1) {
|
|
8996
|
+
return null;
|
|
8997
|
+
}
|
|
8998
|
+
const keyIndentLen = indent.length;
|
|
8999
|
+
let sectionEnd = keyIndex + 1;
|
|
9000
|
+
for (let i = keyIndex + 1;i < lines.length; i++) {
|
|
9001
|
+
const line = lines[i];
|
|
9002
|
+
const trimmed = line.trim();
|
|
9003
|
+
if (trimmed.length === 0) {
|
|
9004
|
+
sectionEnd = i + 1;
|
|
9005
|
+
continue;
|
|
9006
|
+
}
|
|
9007
|
+
const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
9008
|
+
if (indentLen < keyIndentLen) {
|
|
9009
|
+
break;
|
|
9010
|
+
}
|
|
9011
|
+
if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
|
|
9012
|
+
break;
|
|
9013
|
+
}
|
|
9014
|
+
sectionEnd = i + 1;
|
|
9015
|
+
}
|
|
9016
|
+
return {
|
|
9017
|
+
lines,
|
|
9018
|
+
key_index: keyIndex,
|
|
9019
|
+
section_end: sectionEnd,
|
|
9020
|
+
indent
|
|
9021
|
+
};
|
|
9022
|
+
}
|
|
9023
|
+
function collectNormalizedRefsFromSection(lines, key_index, section_end, sourceGuid) {
|
|
9024
|
+
const sectionText = lines.slice(key_index, section_end).join(`
|
|
9025
|
+
`);
|
|
9026
|
+
const refMatches = sectionText.match(/\{[^}]*\}/g) || [];
|
|
9027
|
+
const dedup = new Map;
|
|
9028
|
+
for (const token of refMatches) {
|
|
9029
|
+
const parsed = parsePrefabRef2(token);
|
|
9030
|
+
if (!parsed)
|
|
9031
|
+
continue;
|
|
9032
|
+
const guid = parsed.guid ?? sourceGuid;
|
|
9033
|
+
if (!guid || !/^[a-f0-9]{32}$/i.test(guid))
|
|
9034
|
+
continue;
|
|
9035
|
+
const type = parsed.type ?? "3";
|
|
9036
|
+
if (!/^-?\d+$/.test(type))
|
|
9037
|
+
continue;
|
|
9038
|
+
const normalized = {
|
|
9039
|
+
file_id: parsed.file_id,
|
|
9040
|
+
guid: guid.toLowerCase(),
|
|
9041
|
+
type
|
|
9042
|
+
};
|
|
9043
|
+
const key = prefabRefKey(normalized);
|
|
9044
|
+
if (!dedup.has(key)) {
|
|
9045
|
+
dedup.set(key, normalized);
|
|
9046
|
+
}
|
|
9047
|
+
}
|
|
9048
|
+
return [...dedup.values()];
|
|
9049
|
+
}
|
|
9050
|
+
function mutatePrefabReferenceList(options) {
|
|
9051
|
+
const { target_block, key, ref_text, action, item_label } = options;
|
|
9052
|
+
const split = splitPrefabArraySection2(target_block.raw, key);
|
|
9053
|
+
if (!split) {
|
|
9054
|
+
return { success: false, error: `${key} property not found in PrefabInstance` };
|
|
9055
|
+
}
|
|
9056
|
+
const sourceGuid = extractPrefabSourceGuid2(target_block.raw);
|
|
9057
|
+
const existing = collectNormalizedRefsFromSection(split.lines, split.key_index, split.section_end, sourceGuid);
|
|
9058
|
+
let next = existing;
|
|
9059
|
+
if (action === "add") {
|
|
9060
|
+
const normalized = normalizePrefabRef2(ref_text, sourceGuid);
|
|
9061
|
+
if (!normalized.ref) {
|
|
9062
|
+
return { success: false, error: normalized.error };
|
|
9063
|
+
}
|
|
9064
|
+
const keyText = prefabRefKey(normalized.ref);
|
|
9065
|
+
if (existing.some((entry) => prefabRefKey(entry) === keyText)) {
|
|
9066
|
+
return { success: true, changed: false };
|
|
9067
|
+
}
|
|
9068
|
+
next = [...existing, normalized.ref];
|
|
9069
|
+
} else {
|
|
9070
|
+
const matcherResult = parsePrefabRefMatcher(ref_text);
|
|
9071
|
+
const matcher = matcherResult.matcher;
|
|
9072
|
+
if (!matcher) {
|
|
9073
|
+
return { success: false, error: matcherResult.error };
|
|
9074
|
+
}
|
|
9075
|
+
next = existing.filter((entry) => {
|
|
9076
|
+
if (entry.file_id !== matcher.file_id)
|
|
9077
|
+
return true;
|
|
9078
|
+
if (matcher.guid && entry.guid !== matcher.guid.toLowerCase())
|
|
9079
|
+
return true;
|
|
9080
|
+
if (matcher.type && entry.type !== matcher.type)
|
|
9081
|
+
return true;
|
|
9082
|
+
return false;
|
|
9083
|
+
});
|
|
9084
|
+
if (next.length === existing.length) {
|
|
9085
|
+
return {
|
|
9086
|
+
success: false,
|
|
9087
|
+
error: `${item_label} reference "${ref_text}" not found in ${key}`
|
|
9088
|
+
};
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
const replacement = [];
|
|
9092
|
+
if (next.length === 0) {
|
|
9093
|
+
replacement.push(`${split.indent}${key}: []`);
|
|
9094
|
+
} else {
|
|
9095
|
+
replacement.push(`${split.indent}${key}:`);
|
|
9096
|
+
for (const entry of next) {
|
|
9097
|
+
replacement.push(`${split.indent}- ${serializePrefabRef2(entry)}`);
|
|
9098
|
+
}
|
|
9099
|
+
}
|
|
9100
|
+
const updatedLines = [
|
|
9101
|
+
...split.lines.slice(0, split.key_index),
|
|
9102
|
+
...replacement,
|
|
9103
|
+
...split.lines.slice(split.section_end)
|
|
9104
|
+
];
|
|
9105
|
+
target_block.replace_raw(updatedLines.join(`
|
|
9106
|
+
`));
|
|
9107
|
+
return { success: true, changed: true };
|
|
9108
|
+
}
|
|
8195
9109
|
function editArray(options) {
|
|
8196
9110
|
const { file_path, file_id, array_property, action, value, index } = options;
|
|
8197
9111
|
if (!import_fs10.existsSync(file_path)) {
|
|
@@ -8402,9 +9316,6 @@ function addRemovedComponent(options) {
|
|
|
8402
9316
|
if (!import_fs10.existsSync(file_path)) {
|
|
8403
9317
|
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
8404
9318
|
}
|
|
8405
|
-
if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
|
|
8406
|
-
return { success: false, file_path, error: `Invalid component reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
8407
|
-
}
|
|
8408
9319
|
let doc;
|
|
8409
9320
|
try {
|
|
8410
9321
|
doc = UnityDocument.from_file(file_path);
|
|
@@ -8415,19 +9326,16 @@ function addRemovedComponent(options) {
|
|
|
8415
9326
|
if (!targetBlock) {
|
|
8416
9327
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
8417
9328
|
}
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
} else {
|
|
8428
|
-
return { success: false, file_path, error: "m_RemovedComponents property not found in PrefabInstance" };
|
|
9329
|
+
const mutation = mutatePrefabReferenceList({
|
|
9330
|
+
target_block: targetBlock,
|
|
9331
|
+
key: "m_RemovedComponents",
|
|
9332
|
+
ref_text: component_ref,
|
|
9333
|
+
action: "add",
|
|
9334
|
+
item_label: "Component"
|
|
9335
|
+
});
|
|
9336
|
+
if (!mutation.success) {
|
|
9337
|
+
return { success: false, file_path, error: mutation.error };
|
|
8429
9338
|
}
|
|
8430
|
-
targetBlock.replace_raw(rawText);
|
|
8431
9339
|
if (!doc.validate()) {
|
|
8432
9340
|
return { success: false, file_path, error: "Validation failed after adding removed component" };
|
|
8433
9341
|
}
|
|
@@ -8456,13 +9364,16 @@ function removeRemovedComponent(options) {
|
|
|
8456
9364
|
if (!targetBlock) {
|
|
8457
9365
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
8458
9366
|
}
|
|
8459
|
-
const
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
9367
|
+
const mutation = mutatePrefabReferenceList({
|
|
9368
|
+
target_block: targetBlock,
|
|
9369
|
+
key: "m_RemovedComponents",
|
|
9370
|
+
ref_text: component_ref,
|
|
9371
|
+
action: "remove",
|
|
9372
|
+
item_label: "Component"
|
|
9373
|
+
});
|
|
9374
|
+
if (!mutation.success) {
|
|
9375
|
+
return { success: false, file_path, error: mutation.error };
|
|
8463
9376
|
}
|
|
8464
|
-
const updatedRaw = targetBlock.raw.replace(refPattern, "");
|
|
8465
|
-
targetBlock.replace_raw(updatedRaw);
|
|
8466
9377
|
if (!doc.validate()) {
|
|
8467
9378
|
return { success: false, file_path, error: "Validation failed after removing component" };
|
|
8468
9379
|
}
|
|
@@ -8481,9 +9392,6 @@ function addRemovedGameObject(options) {
|
|
|
8481
9392
|
if (!import_fs10.existsSync(file_path)) {
|
|
8482
9393
|
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
8483
9394
|
}
|
|
8484
|
-
if (!/^\{fileID:\s*-?\d+/.test(component_ref)) {
|
|
8485
|
-
return { success: false, file_path, error: `Invalid GameObject reference "${component_ref}". Expected format: {fileID: N, guid: ..., type: T}` };
|
|
8486
|
-
}
|
|
8487
9395
|
let doc;
|
|
8488
9396
|
try {
|
|
8489
9397
|
doc = UnityDocument.from_file(file_path);
|
|
@@ -8494,19 +9402,16 @@ function addRemovedGameObject(options) {
|
|
|
8494
9402
|
if (!targetBlock) {
|
|
8495
9403
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
8496
9404
|
}
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
} else {
|
|
8507
|
-
return { success: false, file_path, error: "m_RemovedGameObjects property not found in PrefabInstance" };
|
|
9405
|
+
const mutation = mutatePrefabReferenceList({
|
|
9406
|
+
target_block: targetBlock,
|
|
9407
|
+
key: "m_RemovedGameObjects",
|
|
9408
|
+
ref_text: component_ref,
|
|
9409
|
+
action: "add",
|
|
9410
|
+
item_label: "GameObject"
|
|
9411
|
+
});
|
|
9412
|
+
if (!mutation.success) {
|
|
9413
|
+
return { success: false, file_path, error: mutation.error };
|
|
8508
9414
|
}
|
|
8509
|
-
targetBlock.replace_raw(rawText);
|
|
8510
9415
|
if (!doc.validate()) {
|
|
8511
9416
|
return { success: false, file_path, error: "Validation failed after adding removed GameObject" };
|
|
8512
9417
|
}
|
|
@@ -8535,13 +9440,16 @@ function removeRemovedGameObject(options) {
|
|
|
8535
9440
|
if (!targetBlock) {
|
|
8536
9441
|
return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
|
|
8537
9442
|
}
|
|
8538
|
-
const
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
9443
|
+
const mutation = mutatePrefabReferenceList({
|
|
9444
|
+
target_block: targetBlock,
|
|
9445
|
+
key: "m_RemovedGameObjects",
|
|
9446
|
+
ref_text: component_ref,
|
|
9447
|
+
action: "remove",
|
|
9448
|
+
item_label: "GameObject"
|
|
9449
|
+
});
|
|
9450
|
+
if (!mutation.success) {
|
|
9451
|
+
return { success: false, file_path, error: mutation.error };
|
|
8542
9452
|
}
|
|
8543
|
-
const updatedRaw = targetBlock.raw.replace(refPattern, "");
|
|
8544
|
-
targetBlock.replace_raw(updatedRaw);
|
|
8545
9453
|
if (!doc.validate()) {
|
|
8546
9454
|
return { success: false, file_path, error: "Validation failed after removing GameObject" };
|
|
8547
9455
|
}
|
|
@@ -8804,6 +9712,7 @@ ${fieldIndent} - rid: ${nextRid}`;
|
|
|
8804
9712
|
}
|
|
8805
9713
|
// src/editor/delete.ts
|
|
8806
9714
|
var import_fs13 = require("fs");
|
|
9715
|
+
var import_path6 = require("path");
|
|
8807
9716
|
|
|
8808
9717
|
// src/project-search.ts
|
|
8809
9718
|
init_scanner();
|
|
@@ -9398,6 +10307,7 @@ function removeComponent(options) {
|
|
|
9398
10307
|
return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
|
|
9399
10308
|
}
|
|
9400
10309
|
const resolved_file_id = found.file_id;
|
|
10310
|
+
let removed_added_component_overrides = 0;
|
|
9401
10311
|
const goMatch = found.raw.match(/m_GameObject:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
|
|
9402
10312
|
if (goMatch) {
|
|
9403
10313
|
const parentGoId = goMatch[1];
|
|
@@ -9408,6 +10318,19 @@ function removeComponent(options) {
|
|
|
9408
10318
|
const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
|
|
9409
10319
|
if (modifiedRaw === goBlock.raw) {}
|
|
9410
10320
|
goBlock.replace_raw(modifiedRaw);
|
|
10321
|
+
if (goBlock.is_stripped) {
|
|
10322
|
+
const piMatch = goBlock.raw.match(/m_PrefabInstance:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
|
|
10323
|
+
if (piMatch) {
|
|
10324
|
+
const piBlock = doc.find_by_file_id(piMatch[1]);
|
|
10325
|
+
if (piBlock && piBlock.class_id === 1001) {
|
|
10326
|
+
const updated = removeAddedComponentOverrideEntries(piBlock.raw, resolved_file_id);
|
|
10327
|
+
if (updated.removed_count > 0) {
|
|
10328
|
+
piBlock.replace_raw(updated.updated_raw);
|
|
10329
|
+
removed_added_component_overrides += updated.removed_count;
|
|
10330
|
+
}
|
|
10331
|
+
}
|
|
10332
|
+
}
|
|
10333
|
+
}
|
|
9411
10334
|
}
|
|
9412
10335
|
}
|
|
9413
10336
|
const dangling = [];
|
|
@@ -9431,7 +10354,7 @@ function removeComponent(options) {
|
|
|
9431
10354
|
file_path,
|
|
9432
10355
|
removed_file_id: resolved_file_id,
|
|
9433
10356
|
removed_class_id: found.class_id,
|
|
9434
|
-
warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}` : undefined
|
|
10357
|
+
warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}${removed_added_component_overrides > 0 ? ` (removed ${removed_added_component_overrides} m_AddedComponents override entr${removed_added_component_overrides === 1 ? "y" : "ies"})` : ""}` : undefined
|
|
9435
10358
|
};
|
|
9436
10359
|
}
|
|
9437
10360
|
function removeComponentBatch(options) {
|
|
@@ -9549,6 +10472,124 @@ function findPrefabInstanceBlock2(doc, identifier) {
|
|
|
9549
10472
|
}
|
|
9550
10473
|
return null;
|
|
9551
10474
|
}
|
|
10475
|
+
function splitPrefabArraySection3(rawText, key) {
|
|
10476
|
+
const lines = rawText.split(`
|
|
10477
|
+
`);
|
|
10478
|
+
const keyPattern = new RegExp(`^[ ]*${key}:[ ]*(.*)$`);
|
|
10479
|
+
let keyIndex = -1;
|
|
10480
|
+
let keyIndentLen = 0;
|
|
10481
|
+
for (let i = 0;i < lines.length; i++) {
|
|
10482
|
+
const match = lines[i].match(keyPattern);
|
|
10483
|
+
if (match) {
|
|
10484
|
+
keyIndex = i;
|
|
10485
|
+
keyIndentLen = (lines[i].match(/^[ \t]*/) || [""])[0].length;
|
|
10486
|
+
break;
|
|
10487
|
+
}
|
|
10488
|
+
}
|
|
10489
|
+
if (keyIndex === -1) {
|
|
10490
|
+
return null;
|
|
10491
|
+
}
|
|
10492
|
+
let sectionEnd = keyIndex + 1;
|
|
10493
|
+
for (let i = keyIndex + 1;i < lines.length; i++) {
|
|
10494
|
+
const line = lines[i];
|
|
10495
|
+
const trimmed = line.trim();
|
|
10496
|
+
if (trimmed.length === 0) {
|
|
10497
|
+
sectionEnd = i + 1;
|
|
10498
|
+
continue;
|
|
10499
|
+
}
|
|
10500
|
+
const indentLen = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
10501
|
+
if (indentLen < keyIndentLen) {
|
|
10502
|
+
break;
|
|
10503
|
+
}
|
|
10504
|
+
if (indentLen === keyIndentLen && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
|
|
10505
|
+
break;
|
|
10506
|
+
}
|
|
10507
|
+
sectionEnd = i + 1;
|
|
10508
|
+
}
|
|
10509
|
+
return {
|
|
10510
|
+
lines,
|
|
10511
|
+
key_index: keyIndex,
|
|
10512
|
+
section_end: sectionEnd
|
|
10513
|
+
};
|
|
10514
|
+
}
|
|
10515
|
+
function collectAddedObjectFileIdsFromArray(rawText, key) {
|
|
10516
|
+
const section = splitPrefabArraySection3(rawText, key);
|
|
10517
|
+
if (!section)
|
|
10518
|
+
return [];
|
|
10519
|
+
const keyLine = section.lines[section.key_index].trim();
|
|
10520
|
+
if (new RegExp(`^${key}:[ ]*[]$`).test(keyLine)) {
|
|
10521
|
+
return [];
|
|
10522
|
+
}
|
|
10523
|
+
const sectionText = section.lines.slice(section.key_index, section.section_end).join(`
|
|
10524
|
+
`);
|
|
10525
|
+
const fileIds = new Set;
|
|
10526
|
+
const addedObjectPattern = /addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/g;
|
|
10527
|
+
let match;
|
|
10528
|
+
while ((match = addedObjectPattern.exec(sectionText)) !== null) {
|
|
10529
|
+
fileIds.add(match[1]);
|
|
10530
|
+
}
|
|
10531
|
+
if (fileIds.size === 0) {
|
|
10532
|
+
const inlineEntryPattern = /^[ \t]*-[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/gm;
|
|
10533
|
+
while ((match = inlineEntryPattern.exec(sectionText)) !== null) {
|
|
10534
|
+
fileIds.add(match[1]);
|
|
10535
|
+
}
|
|
10536
|
+
}
|
|
10537
|
+
return [...fileIds];
|
|
10538
|
+
}
|
|
10539
|
+
function removeAddedComponentOverrideEntries(rawText, componentId) {
|
|
10540
|
+
const section = splitPrefabArraySection3(rawText, "m_AddedComponents");
|
|
10541
|
+
if (!section)
|
|
10542
|
+
return { updated_raw: rawText, removed_count: 0 };
|
|
10543
|
+
const keyLine = section.lines[section.key_index].trim();
|
|
10544
|
+
if (/^m_AddedComponents:[ \t]*\[\]$/.test(keyLine)) {
|
|
10545
|
+
return { updated_raw: rawText, removed_count: 0 };
|
|
10546
|
+
}
|
|
10547
|
+
const bodyLines = section.lines.slice(section.key_index + 1, section.section_end);
|
|
10548
|
+
const keptEntries = [];
|
|
10549
|
+
let removed = 0;
|
|
10550
|
+
let i = 0;
|
|
10551
|
+
while (i < bodyLines.length) {
|
|
10552
|
+
const line = bodyLines[i];
|
|
10553
|
+
const trimmed = line.trimStart();
|
|
10554
|
+
if (!trimmed.startsWith("-")) {
|
|
10555
|
+
i++;
|
|
10556
|
+
continue;
|
|
10557
|
+
}
|
|
10558
|
+
const entryIndent = (line.match(/^[ \t]*/) || [""])[0].length;
|
|
10559
|
+
const entryLines = [line];
|
|
10560
|
+
i++;
|
|
10561
|
+
while (i < bodyLines.length) {
|
|
10562
|
+
const nextLine = bodyLines[i];
|
|
10563
|
+
const nextTrimmed = nextLine.trim();
|
|
10564
|
+
const nextIndent = (nextLine.match(/^[ \t]*/) || [""])[0].length;
|
|
10565
|
+
if (nextTrimmed.length > 0 && nextIndent === entryIndent && nextTrimmed.startsWith("-")) {
|
|
10566
|
+
break;
|
|
10567
|
+
}
|
|
10568
|
+
entryLines.push(nextLine);
|
|
10569
|
+
i++;
|
|
10570
|
+
}
|
|
10571
|
+
const entryText = entryLines.join(`
|
|
10572
|
+
`);
|
|
10573
|
+
const addedObjectMatch = entryText.match(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)/);
|
|
10574
|
+
if (addedObjectMatch && addedObjectMatch[1] === componentId) {
|
|
10575
|
+
removed++;
|
|
10576
|
+
continue;
|
|
10577
|
+
}
|
|
10578
|
+
keptEntries.push(entryLines);
|
|
10579
|
+
}
|
|
10580
|
+
if (removed === 0) {
|
|
10581
|
+
return { updated_raw: rawText, removed_count: 0 };
|
|
10582
|
+
}
|
|
10583
|
+
const keyIndent = (section.lines[section.key_index].match(/^[ \t]*/) || [""])[0];
|
|
10584
|
+
const replacement = keptEntries.length === 0 ? [`${keyIndent}m_AddedComponents: []`] : [`${keyIndent}m_AddedComponents:`, ...keptEntries.flat()];
|
|
10585
|
+
const updatedLines = [
|
|
10586
|
+
...section.lines.slice(0, section.key_index),
|
|
10587
|
+
...replacement,
|
|
10588
|
+
...section.lines.slice(section.section_end)
|
|
10589
|
+
];
|
|
10590
|
+
return { updated_raw: updatedLines.join(`
|
|
10591
|
+
`), removed_count: removed };
|
|
10592
|
+
}
|
|
9552
10593
|
function deletePrefabInstance(options) {
|
|
9553
10594
|
const { file_path, prefab_instance } = options;
|
|
9554
10595
|
const pathError = validate_file_path(file_path, "write");
|
|
@@ -9576,36 +10617,23 @@ function deletePrefabInstance(options) {
|
|
|
9576
10617
|
allToRemove.add(block.file_id);
|
|
9577
10618
|
}
|
|
9578
10619
|
}
|
|
9579
|
-
const
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
if (compMatch) {
|
|
9591
|
-
const transformId = compMatch[1];
|
|
9592
|
-
allToRemove.add(transformId);
|
|
9593
|
-
const descendants = doc.collect_hierarchy(transformId);
|
|
9594
|
-
for (const id of descendants) {
|
|
9595
|
-
allToRemove.add(id);
|
|
9596
|
-
}
|
|
10620
|
+
for (const goId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedGameObjects")) {
|
|
10621
|
+
allToRemove.add(goId);
|
|
10622
|
+
const goBlock = doc.find_by_file_id(goId);
|
|
10623
|
+
if (goBlock) {
|
|
10624
|
+
const compMatch = goBlock.raw.match(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
|
|
10625
|
+
if (compMatch) {
|
|
10626
|
+
const transformId = compMatch[1];
|
|
10627
|
+
allToRemove.add(transformId);
|
|
10628
|
+
const descendants = doc.collect_hierarchy(transformId);
|
|
10629
|
+
for (const id of descendants) {
|
|
10630
|
+
allToRemove.add(id);
|
|
9597
10631
|
}
|
|
9598
10632
|
}
|
|
9599
10633
|
}
|
|
9600
10634
|
}
|
|
9601
|
-
const
|
|
9602
|
-
|
|
9603
|
-
if (addedCompMatch) {
|
|
9604
|
-
const addedCompSection = addedCompMatch[1];
|
|
9605
|
-
const compMatches = addedCompSection.matchAll(/fileID:[ \t]*(\d+)/g);
|
|
9606
|
-
for (const match of compMatches) {
|
|
9607
|
-
allToRemove.add(match[1]);
|
|
9608
|
-
}
|
|
10635
|
+
for (const componentId of collectAddedObjectFileIdsFromArray(piBlock.raw, "m_AddedComponents")) {
|
|
10636
|
+
allToRemove.add(componentId);
|
|
9609
10637
|
}
|
|
9610
10638
|
const parentMatch = piBlock.raw.match(/m_TransformParent:[ \t]*\{fileID:[ \t]*(\d+)\}/);
|
|
9611
10639
|
const parentTransformId = parentMatch ? parentMatch[1] : "0";
|
|
@@ -9634,6 +10662,70 @@ function deletePrefabInstance(options) {
|
|
|
9634
10662
|
deleted_count: allToRemove.size
|
|
9635
10663
|
};
|
|
9636
10664
|
}
|
|
10665
|
+
function deleteAssetFile(options) {
|
|
10666
|
+
const { file_path } = options;
|
|
10667
|
+
const pathError = validate_file_path(file_path, "write");
|
|
10668
|
+
if (pathError) {
|
|
10669
|
+
return { success: false, file_path, error: pathError };
|
|
10670
|
+
}
|
|
10671
|
+
if (!import_fs13.existsSync(file_path)) {
|
|
10672
|
+
return { success: false, file_path, error: `File not found: ${file_path}` };
|
|
10673
|
+
}
|
|
10674
|
+
const supportedExtensions = new Set([
|
|
10675
|
+
".unity",
|
|
10676
|
+
".prefab",
|
|
10677
|
+
".mat",
|
|
10678
|
+
".anim",
|
|
10679
|
+
".controller",
|
|
10680
|
+
".inputactions",
|
|
10681
|
+
".asset"
|
|
10682
|
+
]);
|
|
10683
|
+
const extension = import_path6.extname(file_path).toLowerCase();
|
|
10684
|
+
if (!supportedExtensions.has(extension)) {
|
|
10685
|
+
return {
|
|
10686
|
+
success: false,
|
|
10687
|
+
file_path,
|
|
10688
|
+
error: `Unsupported asset type "${extension}". Supported extensions: .unity, .prefab, .mat, .anim, .controller, .inputactions, .asset`
|
|
10689
|
+
};
|
|
10690
|
+
}
|
|
10691
|
+
const metaPath = `${file_path}.meta`;
|
|
10692
|
+
const hasMeta = import_fs13.existsSync(metaPath);
|
|
10693
|
+
try {
|
|
10694
|
+
import_fs13.unlinkSync(file_path);
|
|
10695
|
+
} catch (err) {
|
|
10696
|
+
return {
|
|
10697
|
+
success: false,
|
|
10698
|
+
file_path,
|
|
10699
|
+
error: `Failed to delete file: ${err instanceof Error ? err.message : String(err)}`
|
|
10700
|
+
};
|
|
10701
|
+
}
|
|
10702
|
+
if (hasMeta) {
|
|
10703
|
+
try {
|
|
10704
|
+
import_fs13.unlinkSync(metaPath);
|
|
10705
|
+
return {
|
|
10706
|
+
success: true,
|
|
10707
|
+
file_path,
|
|
10708
|
+
deleted_file: true,
|
|
10709
|
+
deleted_meta: true
|
|
10710
|
+
};
|
|
10711
|
+
} catch (err) {
|
|
10712
|
+
return {
|
|
10713
|
+
success: true,
|
|
10714
|
+
file_path,
|
|
10715
|
+
deleted_file: true,
|
|
10716
|
+
deleted_meta: false,
|
|
10717
|
+
warning: `Deleted asset file but failed to delete .meta file: ${err instanceof Error ? err.message : String(err)}`
|
|
10718
|
+
};
|
|
10719
|
+
}
|
|
10720
|
+
}
|
|
10721
|
+
return {
|
|
10722
|
+
success: true,
|
|
10723
|
+
file_path,
|
|
10724
|
+
deleted_file: true,
|
|
10725
|
+
deleted_meta: false,
|
|
10726
|
+
warning: `Asset file deleted, but no .meta file found at ${metaPath}`
|
|
10727
|
+
};
|
|
10728
|
+
}
|
|
9637
10729
|
// src/editor/duplicate.ts
|
|
9638
10730
|
var import_fs14 = require("fs");
|
|
9639
10731
|
var path6 = __toESM(require("path"));
|
|
@@ -10278,9 +11370,9 @@ function move_scene(projectPath, scenePath, newPosition) {
|
|
|
10278
11370
|
|
|
10279
11371
|
// src/packages.ts
|
|
10280
11372
|
var import_fs15 = require("fs");
|
|
10281
|
-
var
|
|
11373
|
+
var import_path7 = require("path");
|
|
10282
11374
|
function load_manifest(project_path) {
|
|
10283
|
-
const manifest_path =
|
|
11375
|
+
const manifest_path = import_path7.join(project_path, "Packages", "manifest.json");
|
|
10284
11376
|
if (!import_fs15.existsSync(manifest_path)) {
|
|
10285
11377
|
return { error: `manifest.json not found at ${manifest_path}` };
|
|
10286
11378
|
}
|
|
@@ -10474,24 +11566,381 @@ function remove_control_scheme(data, name) {
|
|
|
10474
11566
|
return data;
|
|
10475
11567
|
}
|
|
10476
11568
|
|
|
10477
|
-
// src/
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
|
|
10491
|
-
|
|
10492
|
-
|
|
11569
|
+
// src/loaded-protection.ts
|
|
11570
|
+
var import_path9 = require("path");
|
|
11571
|
+
|
|
11572
|
+
// src/editor-client.ts
|
|
11573
|
+
var import_fs17 = require("fs");
|
|
11574
|
+
var import_path8 = require("path");
|
|
11575
|
+
function read_editor_config(project_path) {
|
|
11576
|
+
const config_path = import_path8.join(project_path, ".unity-agentic", "editor.json");
|
|
11577
|
+
if (!import_fs17.existsSync(config_path)) {
|
|
11578
|
+
return { error: `Editor bridge not found at ${config_path}. Is the Unity Editor running with the bridge package installed?` };
|
|
11579
|
+
}
|
|
11580
|
+
let config;
|
|
11581
|
+
try {
|
|
11582
|
+
const raw = import_fs17.readFileSync(config_path, "utf-8");
|
|
11583
|
+
config = JSON.parse(raw);
|
|
11584
|
+
} catch (err) {
|
|
11585
|
+
return { error: `Failed to parse editor.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
11586
|
+
}
|
|
11587
|
+
if (typeof config.port !== "number" || typeof config.pid !== "number") {
|
|
11588
|
+
return { error: "Invalid editor.json: missing port or pid" };
|
|
11589
|
+
}
|
|
11590
|
+
if (!is_pid_alive(config.pid)) {
|
|
11591
|
+
return { error: `Unity Editor process (PID ${config.pid}) is not running. The editor may have been closed.` };
|
|
11592
|
+
}
|
|
11593
|
+
return config;
|
|
11594
|
+
}
|
|
11595
|
+
var RETRYABLE_CODES = new Set([-32000, -32002, -32003]);
|
|
11596
|
+
var RETRY_DELAYS = [500, 1000, 2000];
|
|
11597
|
+
async function call_editor(options) {
|
|
11598
|
+
const maxRetries = options.retries ?? 2;
|
|
11599
|
+
let lastResponse;
|
|
11600
|
+
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
11601
|
+
lastResponse = await call_editor_once(options);
|
|
11602
|
+
if (!lastResponse.error || !RETRYABLE_CODES.has(lastResponse.error.code)) {
|
|
11603
|
+
return lastResponse;
|
|
10493
11604
|
}
|
|
10494
|
-
|
|
11605
|
+
if (attempt < maxRetries) {
|
|
11606
|
+
const delay = RETRY_DELAYS[Math.min(attempt, RETRY_DELAYS.length - 1)];
|
|
11607
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
11608
|
+
}
|
|
11609
|
+
}
|
|
11610
|
+
return lastResponse;
|
|
11611
|
+
}
|
|
11612
|
+
function call_editor_once(options) {
|
|
11613
|
+
const { method, params, timeout = 1e4, no_wait } = options;
|
|
11614
|
+
const config = resolve_config(options);
|
|
11615
|
+
if ("error" in config) {
|
|
11616
|
+
return Promise.resolve({
|
|
11617
|
+
jsonrpc: "2.0",
|
|
11618
|
+
id: "0",
|
|
11619
|
+
error: { code: -32000, message: config.error }
|
|
11620
|
+
});
|
|
11621
|
+
}
|
|
11622
|
+
const url = `ws://127.0.0.1:${config.port}/unity-agentic`;
|
|
11623
|
+
const request_id = generate_id();
|
|
11624
|
+
const wire_params = { ...params };
|
|
11625
|
+
if (timeout !== 1e4)
|
|
11626
|
+
wire_params._timeout = timeout;
|
|
11627
|
+
if (no_wait)
|
|
11628
|
+
wire_params.no_wait = true;
|
|
11629
|
+
const request = {
|
|
11630
|
+
jsonrpc: "2.0",
|
|
11631
|
+
id: request_id,
|
|
11632
|
+
method,
|
|
11633
|
+
...Object.keys(wire_params).length > 0 ? { params: wire_params } : {}
|
|
11634
|
+
};
|
|
11635
|
+
return new Promise((resolve5) => {
|
|
11636
|
+
let resolved = false;
|
|
11637
|
+
let ws;
|
|
11638
|
+
const timer = setTimeout(() => {
|
|
11639
|
+
if (!resolved) {
|
|
11640
|
+
resolved = true;
|
|
11641
|
+
try {
|
|
11642
|
+
ws?.close();
|
|
11643
|
+
} catch {}
|
|
11644
|
+
resolve5({
|
|
11645
|
+
jsonrpc: "2.0",
|
|
11646
|
+
id: request_id,
|
|
11647
|
+
error: { code: -32001, message: `Timeout after ${timeout}ms waiting for response to ${method}` }
|
|
11648
|
+
});
|
|
11649
|
+
}
|
|
11650
|
+
}, timeout);
|
|
11651
|
+
try {
|
|
11652
|
+
ws = new WebSocket(url);
|
|
11653
|
+
ws.onopen = () => {
|
|
11654
|
+
ws.send(JSON.stringify(request));
|
|
11655
|
+
if (no_wait && !resolved) {
|
|
11656
|
+
resolved = true;
|
|
11657
|
+
clearTimeout(timer);
|
|
11658
|
+
setTimeout(() => {
|
|
11659
|
+
try {
|
|
11660
|
+
ws.close();
|
|
11661
|
+
} catch {}
|
|
11662
|
+
}, 200);
|
|
11663
|
+
resolve5({
|
|
11664
|
+
jsonrpc: "2.0",
|
|
11665
|
+
id: request_id,
|
|
11666
|
+
result: { queued: true }
|
|
11667
|
+
});
|
|
11668
|
+
}
|
|
11669
|
+
};
|
|
11670
|
+
ws.onmessage = (event) => {
|
|
11671
|
+
try {
|
|
11672
|
+
const data = JSON.parse(String(event.data));
|
|
11673
|
+
if (data.id === request_id) {
|
|
11674
|
+
if (!resolved) {
|
|
11675
|
+
resolved = true;
|
|
11676
|
+
clearTimeout(timer);
|
|
11677
|
+
ws.close();
|
|
11678
|
+
resolve5(data);
|
|
11679
|
+
}
|
|
11680
|
+
}
|
|
11681
|
+
} catch {}
|
|
11682
|
+
};
|
|
11683
|
+
ws.onerror = () => {
|
|
11684
|
+
if (!resolved) {
|
|
11685
|
+
resolved = true;
|
|
11686
|
+
clearTimeout(timer);
|
|
11687
|
+
resolve5({
|
|
11688
|
+
jsonrpc: "2.0",
|
|
11689
|
+
id: request_id,
|
|
11690
|
+
error: { code: -32002, message: `WebSocket connection failed to ${url}. Is the Unity Editor running?` }
|
|
11691
|
+
});
|
|
11692
|
+
}
|
|
11693
|
+
};
|
|
11694
|
+
ws.onclose = () => {
|
|
11695
|
+
if (!resolved) {
|
|
11696
|
+
resolved = true;
|
|
11697
|
+
clearTimeout(timer);
|
|
11698
|
+
resolve5({
|
|
11699
|
+
jsonrpc: "2.0",
|
|
11700
|
+
id: request_id,
|
|
11701
|
+
error: { code: -32003, message: "WebSocket connection closed before response received" }
|
|
11702
|
+
});
|
|
11703
|
+
}
|
|
11704
|
+
};
|
|
11705
|
+
} catch (err) {
|
|
11706
|
+
if (!resolved) {
|
|
11707
|
+
resolved = true;
|
|
11708
|
+
clearTimeout(timer);
|
|
11709
|
+
resolve5({
|
|
11710
|
+
jsonrpc: "2.0",
|
|
11711
|
+
id: request_id,
|
|
11712
|
+
error: { code: -32002, message: `Failed to connect: ${err instanceof Error ? err.message : String(err)}` }
|
|
11713
|
+
});
|
|
11714
|
+
}
|
|
11715
|
+
}
|
|
11716
|
+
});
|
|
11717
|
+
}
|
|
11718
|
+
async function stream_editor(options) {
|
|
11719
|
+
const { method, params, timeout = 30000, on_event } = options;
|
|
11720
|
+
const config = resolve_config(options);
|
|
11721
|
+
if ("error" in config) {
|
|
11722
|
+
throw new Error(config.error);
|
|
11723
|
+
}
|
|
11724
|
+
const MAX_RECONNECTS = 5;
|
|
11725
|
+
let reconnect_count = 0;
|
|
11726
|
+
let stopped = false;
|
|
11727
|
+
return new Promise((resolve5, reject) => {
|
|
11728
|
+
let resolved = false;
|
|
11729
|
+
function connect(url) {
|
|
11730
|
+
const ws = new WebSocket(url);
|
|
11731
|
+
const request_id = generate_id();
|
|
11732
|
+
const request = {
|
|
11733
|
+
jsonrpc: "2.0",
|
|
11734
|
+
id: request_id,
|
|
11735
|
+
method,
|
|
11736
|
+
...params ? { params } : {}
|
|
11737
|
+
};
|
|
11738
|
+
let connected = false;
|
|
11739
|
+
const timer = !resolved ? setTimeout(() => {
|
|
11740
|
+
if (!connected && !resolved) {
|
|
11741
|
+
reject(new Error(`Timeout connecting to ${url}`));
|
|
11742
|
+
}
|
|
11743
|
+
}, timeout) : null;
|
|
11744
|
+
ws.onopen = () => {
|
|
11745
|
+
connected = true;
|
|
11746
|
+
if (timer)
|
|
11747
|
+
clearTimeout(timer);
|
|
11748
|
+
reconnect_count = 0;
|
|
11749
|
+
ws.send(JSON.stringify(request));
|
|
11750
|
+
if (!resolved) {
|
|
11751
|
+
resolved = true;
|
|
11752
|
+
resolve5({ close: () => {
|
|
11753
|
+
stopped = true;
|
|
11754
|
+
ws.close();
|
|
11755
|
+
} });
|
|
11756
|
+
}
|
|
11757
|
+
};
|
|
11758
|
+
ws.onmessage = (event) => {
|
|
11759
|
+
try {
|
|
11760
|
+
const data = JSON.parse(String(event.data));
|
|
11761
|
+
if ("method" in data && !("id" in data)) {
|
|
11762
|
+
on_event(data);
|
|
11763
|
+
}
|
|
11764
|
+
} catch {}
|
|
11765
|
+
};
|
|
11766
|
+
ws.onerror = () => {
|
|
11767
|
+
if (!connected && !resolved) {
|
|
11768
|
+
if (timer)
|
|
11769
|
+
clearTimeout(timer);
|
|
11770
|
+
reject(new Error(`WebSocket connection failed to ${url}`));
|
|
11771
|
+
}
|
|
11772
|
+
};
|
|
11773
|
+
ws.onclose = () => {
|
|
11774
|
+
if (stopped)
|
|
11775
|
+
return;
|
|
11776
|
+
if (!connected && !resolved)
|
|
11777
|
+
return;
|
|
11778
|
+
if (reconnect_count >= MAX_RECONNECTS)
|
|
11779
|
+
return;
|
|
11780
|
+
reconnect_count++;
|
|
11781
|
+
const delay = Math.min(500 * reconnect_count, 3000);
|
|
11782
|
+
setTimeout(() => {
|
|
11783
|
+
if (stopped)
|
|
11784
|
+
return;
|
|
11785
|
+
const fresh_config = resolve_config(options);
|
|
11786
|
+
if ("error" in fresh_config)
|
|
11787
|
+
return;
|
|
11788
|
+
connect(`ws://127.0.0.1:${fresh_config.port}/unity-agentic`);
|
|
11789
|
+
}, delay);
|
|
11790
|
+
};
|
|
11791
|
+
}
|
|
11792
|
+
connect(`ws://127.0.0.1:${config.port}/unity-agentic`);
|
|
11793
|
+
});
|
|
11794
|
+
}
|
|
11795
|
+
async function ping_editor(port, timeout_ms = 2000) {
|
|
11796
|
+
return new Promise((resolve5) => {
|
|
11797
|
+
const timer = setTimeout(() => {
|
|
11798
|
+
resolve5({ reachable: false, error: `Timeout after ${timeout_ms}ms` });
|
|
11799
|
+
}, timeout_ms);
|
|
11800
|
+
try {
|
|
11801
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}/unity-agentic`);
|
|
11802
|
+
ws.onopen = () => {
|
|
11803
|
+
clearTimeout(timer);
|
|
11804
|
+
try {
|
|
11805
|
+
ws.close();
|
|
11806
|
+
} catch {}
|
|
11807
|
+
resolve5({ reachable: true });
|
|
11808
|
+
};
|
|
11809
|
+
ws.onerror = (err) => {
|
|
11810
|
+
clearTimeout(timer);
|
|
11811
|
+
resolve5({ reachable: false, error: String(err) });
|
|
11812
|
+
};
|
|
11813
|
+
} catch (err) {
|
|
11814
|
+
clearTimeout(timer);
|
|
11815
|
+
resolve5({ reachable: false, error: String(err) });
|
|
11816
|
+
}
|
|
11817
|
+
});
|
|
11818
|
+
}
|
|
11819
|
+
function resolve_config(options) {
|
|
11820
|
+
if (options.port) {
|
|
11821
|
+
return { port: options.port, pid: 0, version: "unknown" };
|
|
11822
|
+
}
|
|
11823
|
+
return read_editor_config(options.project_path);
|
|
11824
|
+
}
|
|
11825
|
+
function is_pid_alive(pid) {
|
|
11826
|
+
try {
|
|
11827
|
+
process.kill(pid, 0);
|
|
11828
|
+
return true;
|
|
11829
|
+
} catch {
|
|
11830
|
+
return false;
|
|
11831
|
+
}
|
|
11832
|
+
}
|
|
11833
|
+
function generate_id() {
|
|
11834
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11837
|
+
// src/loaded-protection.ts
|
|
11838
|
+
function normalize_slashes(value) {
|
|
11839
|
+
return value.replace(/\\/g, "/");
|
|
11840
|
+
}
|
|
11841
|
+
function to_project_asset_path(file_path, project_path) {
|
|
11842
|
+
const abs_file = import_path9.resolve(file_path);
|
|
11843
|
+
const abs_project = import_path9.resolve(project_path);
|
|
11844
|
+
const rel = import_path9.relative(abs_project, abs_file);
|
|
11845
|
+
if (!rel || rel.startsWith("..") || rel.includes(`..${import_path9.sep}`)) {
|
|
11846
|
+
if (/^Assets[\\/]/.test(file_path)) {
|
|
11847
|
+
return normalize_slashes(file_path);
|
|
11848
|
+
}
|
|
11849
|
+
return null;
|
|
11850
|
+
}
|
|
11851
|
+
return normalize_slashes(rel);
|
|
11852
|
+
}
|
|
11853
|
+
function normalize_loaded_paths(paths) {
|
|
11854
|
+
return new Set(paths.map((p) => normalize_slashes(p)));
|
|
11855
|
+
}
|
|
11856
|
+
async function get_loaded_state(project_path) {
|
|
11857
|
+
const config = read_editor_config(project_path);
|
|
11858
|
+
if ("error" in config)
|
|
11859
|
+
return null;
|
|
11860
|
+
const ping = await ping_editor(config.port, 1200);
|
|
11861
|
+
if (!ping.reachable)
|
|
11862
|
+
return null;
|
|
11863
|
+
const response = await call_editor({
|
|
11864
|
+
project_path,
|
|
11865
|
+
method: "editor.scene.loaded",
|
|
11866
|
+
timeout: 2500
|
|
11867
|
+
});
|
|
11868
|
+
if (response.error || !response.result || typeof response.result !== "object") {
|
|
11869
|
+
return null;
|
|
11870
|
+
}
|
|
11871
|
+
const result = response.result;
|
|
11872
|
+
const loaded = Array.isArray(result.loaded_scene_paths) ? result.loaded_scene_paths.filter((v) => typeof v === "string") : [];
|
|
11873
|
+
return {
|
|
11874
|
+
loaded_scene_paths: loaded,
|
|
11875
|
+
active_scene_path: typeof result.active_scene_path === "string" ? result.active_scene_path : undefined,
|
|
11876
|
+
prefab_stage_path: typeof result.prefab_stage_path === "string" ? result.prefab_stage_path : undefined
|
|
11877
|
+
};
|
|
11878
|
+
}
|
|
11879
|
+
async function is_editor_connected_for_project(project_path) {
|
|
11880
|
+
const resolved_project = resolve_project_path(project_path);
|
|
11881
|
+
const config = read_editor_config(resolved_project);
|
|
11882
|
+
if ("error" in config)
|
|
11883
|
+
return false;
|
|
11884
|
+
const ping = await ping_editor(config.port, 1200);
|
|
11885
|
+
return ping.reachable;
|
|
11886
|
+
}
|
|
11887
|
+
async function enforce_loaded_edit_protection(file_path, bypass, project_path) {
|
|
11888
|
+
if (bypass)
|
|
11889
|
+
return { allowed: true };
|
|
11890
|
+
const ext = file_path.toLowerCase().split(".").pop();
|
|
11891
|
+
if (!ext || ext !== "unity" && ext !== "prefab") {
|
|
11892
|
+
return { allowed: true };
|
|
11893
|
+
}
|
|
11894
|
+
const inferred_project = project_path ? resolve_project_path(project_path) : find_unity_project_root(import_path9.dirname(import_path9.resolve(file_path))) || process.cwd();
|
|
11895
|
+
const loaded_state = await get_loaded_state(inferred_project);
|
|
11896
|
+
if (!loaded_state) {
|
|
11897
|
+
return { allowed: true };
|
|
11898
|
+
}
|
|
11899
|
+
const target_asset_path = to_project_asset_path(file_path, inferred_project);
|
|
11900
|
+
if (!target_asset_path) {
|
|
11901
|
+
return { allowed: true };
|
|
11902
|
+
}
|
|
11903
|
+
const loaded_scenes = normalize_loaded_paths(loaded_state.loaded_scene_paths);
|
|
11904
|
+
const active_scene = loaded_state.active_scene_path ? normalize_slashes(loaded_state.active_scene_path) : "";
|
|
11905
|
+
const prefab_stage = loaded_state.prefab_stage_path ? normalize_slashes(loaded_state.prefab_stage_path) : "";
|
|
11906
|
+
if (target_asset_path.endsWith(".unity")) {
|
|
11907
|
+
if (loaded_scenes.has(target_asset_path)) {
|
|
11908
|
+
const reason = active_scene === target_asset_path ? "it is currently the active scene in the Unity Editor" : "it is currently loaded in the Unity Editor";
|
|
11909
|
+
return {
|
|
11910
|
+
allowed: false,
|
|
11911
|
+
error: `Refusing to edit ${target_asset_path}: ${reason}. Use --bypass-loaded-protection to force file-based edits while editor is connected.`
|
|
11912
|
+
};
|
|
11913
|
+
}
|
|
11914
|
+
}
|
|
11915
|
+
if (target_asset_path.endsWith(".prefab")) {
|
|
11916
|
+
if (prefab_stage === target_asset_path) {
|
|
11917
|
+
return {
|
|
11918
|
+
allowed: false,
|
|
11919
|
+
error: `Refusing to edit ${target_asset_path}: it is currently open in Prefab Mode. Use --bypass-loaded-protection to force file-based edits while editor is connected.`
|
|
11920
|
+
};
|
|
11921
|
+
}
|
|
11922
|
+
}
|
|
11923
|
+
return { allowed: true };
|
|
11924
|
+
}
|
|
11925
|
+
|
|
11926
|
+
// src/cmd-create.ts
|
|
11927
|
+
function build_create_command() {
|
|
11928
|
+
const cmd = new Command("create").description("Create Unity objects (GameObjects, scenes, prefabs, components)");
|
|
11929
|
+
cmd.command("gameobject <file> [name]").description("Create a new GameObject in a Unity file").option("-p, --parent <name|id>", "Parent GameObject name or Transform fileID").option("-n, --name <name>", "GameObject name (alternative to positional arg)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, name_positional, options) => {
|
|
11930
|
+
const name = name_positional || options.name;
|
|
11931
|
+
if (!name) {
|
|
11932
|
+
console.log(JSON.stringify({
|
|
11933
|
+
success: false,
|
|
11934
|
+
error: "Missing required name. Provide as positional argument or --name flag.",
|
|
11935
|
+
correct_usage: [
|
|
11936
|
+
"unity-agentic-tools create gameobject <file> <name>",
|
|
11937
|
+
"unity-agentic-tools create gameobject <file> --name <name>"
|
|
11938
|
+
]
|
|
11939
|
+
}, null, 2));
|
|
11940
|
+
process.exitCode = 1;
|
|
11941
|
+
return;
|
|
11942
|
+
}
|
|
11943
|
+
const ext = file.toLowerCase().split(".").pop();
|
|
10495
11944
|
if (!ext || !["unity", "prefab", "asset"].includes(ext)) {
|
|
10496
11945
|
console.log(JSON.stringify({ success: false, error: `Invalid file type ".${ext}". create gameobject only works with .unity, .prefab, or .asset files` }, null, 2));
|
|
10497
11946
|
process.exitCode = 1;
|
|
@@ -10502,6 +11951,12 @@ function build_create_command() {
|
|
|
10502
11951
|
const asNumber = parseInt(options.parent, 10);
|
|
10503
11952
|
parent = isNaN(asNumber) ? options.parent : asNumber;
|
|
10504
11953
|
}
|
|
11954
|
+
const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
|
|
11955
|
+
if (!guard.allowed) {
|
|
11956
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
11957
|
+
process.exitCode = 1;
|
|
11958
|
+
return;
|
|
11959
|
+
}
|
|
10505
11960
|
const result = createGameObject({
|
|
10506
11961
|
file_path: file,
|
|
10507
11962
|
name,
|
|
@@ -10530,7 +11985,7 @@ function build_create_command() {
|
|
|
10530
11985
|
if (!result.success)
|
|
10531
11986
|
process.exitCode = 1;
|
|
10532
11987
|
});
|
|
10533
|
-
cmd.command("prefab-instance <scene_file> <prefab_path>").description("Instantiate a prefab into a scene file").option("-n, --name <name>", "Instance name (defaults to prefab filename)").option("-p, --parent <name|id>", "Parent GameObject name or Transform fileID").option("--position <x,y,z>", "Local position (default: 0,0,0)").option("-j, --json", "Output as JSON").action((scene_file, prefab_path_arg, options) => {
|
|
11988
|
+
cmd.command("prefab-instance <scene_file> <prefab_path>").description("Instantiate a prefab into a scene file").option("-n, --name <name>", "Instance name (defaults to prefab filename)").option("-p, --parent <name|id>", "Parent GameObject name or Transform fileID").option("--position <x,y,z>", "Local position (default: 0,0,0)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (scene_file, prefab_path_arg, options) => {
|
|
10534
11989
|
let position;
|
|
10535
11990
|
if (options.position) {
|
|
10536
11991
|
const parts = options.position.split(",").map(Number);
|
|
@@ -10549,6 +12004,12 @@ function build_create_command() {
|
|
|
10549
12004
|
const asNumber = parseInt(options.parent, 10);
|
|
10550
12005
|
parent = isNaN(asNumber) ? options.parent : asNumber;
|
|
10551
12006
|
}
|
|
12007
|
+
const guard = await enforce_loaded_edit_protection(scene_file, options.bypassLoadedProtection);
|
|
12008
|
+
if (!guard.allowed) {
|
|
12009
|
+
console.log(JSON.stringify({ success: false, file_path: scene_file, error: guard.error }, null, 2));
|
|
12010
|
+
process.exitCode = 1;
|
|
12011
|
+
return;
|
|
12012
|
+
}
|
|
10552
12013
|
const result = createPrefabInstance({
|
|
10553
12014
|
scene_path: scene_file,
|
|
10554
12015
|
prefab_path: prefab_path_arg,
|
|
@@ -10589,12 +12050,18 @@ function build_create_command() {
|
|
|
10589
12050
|
if (!result.success)
|
|
10590
12051
|
process.exitCode = 1;
|
|
10591
12052
|
});
|
|
10592
|
-
cmd.command("component <file> <object_name> <component>").description("Add a Unity component (e.g., MeshRenderer, Animator, Rigidbody) or custom script").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("-j, --json", "Output as JSON").action((file, object_name, component, options) => {
|
|
12053
|
+
cmd.command("component <file> <object_name> <component>").description("Add a Unity component (e.g., MeshRenderer, Animator, Rigidbody) or custom script").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, object_name, component, options) => {
|
|
10593
12054
|
if (!component || component.trim() === "") {
|
|
10594
12055
|
console.log(JSON.stringify({ success: false, file_path: file, error: "Component name must not be empty" }, null, 2));
|
|
10595
12056
|
process.exitCode = 1;
|
|
10596
12057
|
return;
|
|
10597
12058
|
}
|
|
12059
|
+
const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection, options.project);
|
|
12060
|
+
if (!guard.allowed) {
|
|
12061
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
12062
|
+
process.exitCode = 1;
|
|
12063
|
+
return;
|
|
12064
|
+
}
|
|
10598
12065
|
const result = addComponent({
|
|
10599
12066
|
file_path: file,
|
|
10600
12067
|
game_object_name: object_name,
|
|
@@ -10605,7 +12072,13 @@ function build_create_command() {
|
|
|
10605
12072
|
if (!result.success)
|
|
10606
12073
|
process.exitCode = 1;
|
|
10607
12074
|
});
|
|
10608
|
-
cmd.command("component-copy <file> <source_file_id> <target_object_name>").description("Copy a component to a target GameObject").option("-j, --json", "Output as JSON").action((file, source_file_id, target_object_name,
|
|
12075
|
+
cmd.command("component-copy <file> <source_file_id> <target_object_name>").description("Copy a component to a target GameObject").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, source_file_id, target_object_name, options) => {
|
|
12076
|
+
const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
|
|
12077
|
+
if (!guard.allowed) {
|
|
12078
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
12079
|
+
process.exitCode = 1;
|
|
12080
|
+
return;
|
|
12081
|
+
}
|
|
10609
12082
|
const result = copyComponent({
|
|
10610
12083
|
file_path: file,
|
|
10611
12084
|
source_file_id,
|
|
@@ -10615,10 +12088,11 @@ function build_create_command() {
|
|
|
10615
12088
|
if (!result.success)
|
|
10616
12089
|
process.exitCode = 1;
|
|
10617
12090
|
});
|
|
10618
|
-
cmd.command("build <
|
|
12091
|
+
cmd.command("build <scene_path>").description("Add a scene to build settings").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--index <n>", "Insert at position (0-based)").option("-j, --json", "Output as JSON").action((scene_path, options) => {
|
|
10619
12092
|
try {
|
|
12093
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
10620
12094
|
const position = options.index !== undefined ? parseInt(options.index, 10) : undefined;
|
|
10621
|
-
const result = add_scene(
|
|
12095
|
+
const result = add_scene(resolvedProjectPath, scene_path, { position });
|
|
10622
12096
|
console.log(JSON.stringify(result, null, 2));
|
|
10623
12097
|
if (!result.success)
|
|
10624
12098
|
process.exitCode = 1;
|
|
@@ -10632,7 +12106,7 @@ function build_create_command() {
|
|
|
10632
12106
|
console.log(JSON.stringify({ success: false, error: "--shader <guid> is required" }, null, 2));
|
|
10633
12107
|
process.exit(1);
|
|
10634
12108
|
}
|
|
10635
|
-
if (
|
|
12109
|
+
if (import_fs18.existsSync(output_path)) {
|
|
10636
12110
|
console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
|
|
10637
12111
|
process.exit(1);
|
|
10638
12112
|
}
|
|
@@ -10705,7 +12179,7 @@ Material:
|
|
|
10705
12179
|
m_BuildTextureStacks: []
|
|
10706
12180
|
`;
|
|
10707
12181
|
ensure_parent_dir(output_path);
|
|
10708
|
-
|
|
12182
|
+
import_fs18.writeFileSync(output_path, mat_content, "utf-8");
|
|
10709
12183
|
const guid = import_crypto2.randomBytes(16).toString("hex");
|
|
10710
12184
|
const meta_content = `fileFormatVersion: 2
|
|
10711
12185
|
guid: ${guid}
|
|
@@ -10716,7 +12190,7 @@ NativeFormatImporter:
|
|
|
10716
12190
|
assetBundleName:
|
|
10717
12191
|
assetBundleVariant:
|
|
10718
12192
|
`;
|
|
10719
|
-
|
|
12193
|
+
import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
|
|
10720
12194
|
console.log(JSON.stringify({
|
|
10721
12195
|
success: true,
|
|
10722
12196
|
file: output_path,
|
|
@@ -10726,9 +12200,10 @@ NativeFormatImporter:
|
|
|
10726
12200
|
shader_guid
|
|
10727
12201
|
}, null, 2));
|
|
10728
12202
|
});
|
|
10729
|
-
cmd.command("package <
|
|
12203
|
+
cmd.command("package <name> <version>").description("Add a package to Packages/manifest.json").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((name, version, options) => {
|
|
10730
12204
|
try {
|
|
10731
|
-
const
|
|
12205
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
12206
|
+
const result = add_package(resolvedProjectPath, name, version);
|
|
10732
12207
|
if ("error" in result) {
|
|
10733
12208
|
console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
|
|
10734
12209
|
process.exitCode = 1;
|
|
@@ -10746,7 +12221,7 @@ NativeFormatImporter:
|
|
|
10746
12221
|
process.exitCode = 1;
|
|
10747
12222
|
return;
|
|
10748
12223
|
}
|
|
10749
|
-
if (
|
|
12224
|
+
if (import_fs18.existsSync(output_path)) {
|
|
10750
12225
|
console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
|
|
10751
12226
|
process.exitCode = 1;
|
|
10752
12227
|
return;
|
|
@@ -10773,7 +12248,7 @@ ScriptedImporter:
|
|
|
10773
12248
|
wrapperClassName:
|
|
10774
12249
|
wrapperCodeNamespace:
|
|
10775
12250
|
`;
|
|
10776
|
-
|
|
12251
|
+
import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
|
|
10777
12252
|
console.log(JSON.stringify({
|
|
10778
12253
|
success: true,
|
|
10779
12254
|
file: output_path,
|
|
@@ -10788,8 +12263,8 @@ ScriptedImporter:
|
|
|
10788
12263
|
process.exitCode = 1;
|
|
10789
12264
|
return;
|
|
10790
12265
|
}
|
|
10791
|
-
const name = name_arg ||
|
|
10792
|
-
if (
|
|
12266
|
+
const name = name_arg || import_path10.basename(output_path).replace(/\.anim$/i, "");
|
|
12267
|
+
if (import_fs18.existsSync(output_path)) {
|
|
10793
12268
|
console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
|
|
10794
12269
|
process.exitCode = 1;
|
|
10795
12270
|
return;
|
|
@@ -10857,7 +12332,7 @@ AnimationClip:
|
|
|
10857
12332
|
m_Events: []
|
|
10858
12333
|
`;
|
|
10859
12334
|
ensure_parent_dir(output_path);
|
|
10860
|
-
|
|
12335
|
+
import_fs18.writeFileSync(output_path, anim_content, "utf-8");
|
|
10861
12336
|
const guid = import_crypto2.randomBytes(16).toString("hex");
|
|
10862
12337
|
const meta_content = `fileFormatVersion: 2
|
|
10863
12338
|
guid: ${guid}
|
|
@@ -10868,7 +12343,7 @@ NativeFormatImporter:
|
|
|
10868
12343
|
assetBundleName:
|
|
10869
12344
|
assetBundleVariant:
|
|
10870
12345
|
`;
|
|
10871
|
-
|
|
12346
|
+
import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
|
|
10872
12347
|
console.log(JSON.stringify({
|
|
10873
12348
|
success: true,
|
|
10874
12349
|
file: output_path,
|
|
@@ -10880,13 +12355,13 @@ NativeFormatImporter:
|
|
|
10880
12355
|
}, null, 2));
|
|
10881
12356
|
});
|
|
10882
12357
|
cmd.command("animator <output_path> [name]").description("Create a blank .controller AnimatorController file (name defaults to filename without extension)").option("--layer <name>", 'Name of the initial layer (default: "Base Layer")', "Base Layer").option("-j, --json", "Output as JSON").action((output_path, name_arg, options) => {
|
|
10883
|
-
const name = name_arg ||
|
|
12358
|
+
const name = name_arg || import_path10.basename(output_path).replace(/\.controller$/i, "");
|
|
10884
12359
|
if (!output_path.toLowerCase().endsWith(".controller")) {
|
|
10885
12360
|
console.log(JSON.stringify({ success: false, error: "Output path must end with .controller" }, null, 2));
|
|
10886
12361
|
process.exitCode = 1;
|
|
10887
12362
|
return;
|
|
10888
12363
|
}
|
|
10889
|
-
if (
|
|
12364
|
+
if (import_fs18.existsSync(output_path)) {
|
|
10890
12365
|
console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
|
|
10891
12366
|
process.exitCode = 1;
|
|
10892
12367
|
return;
|
|
@@ -10937,7 +12412,7 @@ AnimatorStateMachine:
|
|
|
10937
12412
|
m_DefaultState: {fileID: 0}
|
|
10938
12413
|
`;
|
|
10939
12414
|
ensure_parent_dir(output_path);
|
|
10940
|
-
|
|
12415
|
+
import_fs18.writeFileSync(output_path, ctrl_content, "utf-8");
|
|
10941
12416
|
const guid = import_crypto2.randomBytes(16).toString("hex");
|
|
10942
12417
|
const meta_content = `fileFormatVersion: 2
|
|
10943
12418
|
guid: ${guid}
|
|
@@ -10948,7 +12423,7 @@ NativeFormatImporter:
|
|
|
10948
12423
|
assetBundleName:
|
|
10949
12424
|
assetBundleVariant:
|
|
10950
12425
|
`;
|
|
10951
|
-
|
|
12426
|
+
import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
|
|
10952
12427
|
console.log(JSON.stringify({
|
|
10953
12428
|
success: true,
|
|
10954
12429
|
file: output_path,
|
|
@@ -10959,13 +12434,13 @@ NativeFormatImporter:
|
|
|
10959
12434
|
}, null, 2));
|
|
10960
12435
|
});
|
|
10961
12436
|
cmd.command("prefab <output_path> [name]").description("Create a blank .prefab file (name defaults to filename without extension)").option("-j, --json", "Output as JSON").action((output_path, name_arg) => {
|
|
10962
|
-
const name = name_arg ||
|
|
12437
|
+
const name = name_arg || import_path10.basename(output_path).replace(/\.prefab$/i, "");
|
|
10963
12438
|
if (!output_path.toLowerCase().endsWith(".prefab")) {
|
|
10964
12439
|
console.log(JSON.stringify({ success: false, error: "Output path must end with .prefab" }, null, 2));
|
|
10965
12440
|
process.exitCode = 1;
|
|
10966
12441
|
return;
|
|
10967
12442
|
}
|
|
10968
|
-
if (
|
|
12443
|
+
if (import_fs18.existsSync(output_path)) {
|
|
10969
12444
|
console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
|
|
10970
12445
|
process.exitCode = 1;
|
|
10971
12446
|
return;
|
|
@@ -11005,7 +12480,7 @@ Transform:
|
|
|
11005
12480
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
|
11006
12481
|
`;
|
|
11007
12482
|
ensure_parent_dir(output_path);
|
|
11008
|
-
|
|
12483
|
+
import_fs18.writeFileSync(output_path, prefab_content, "utf-8");
|
|
11009
12484
|
const guid = import_crypto2.randomBytes(16).toString("hex");
|
|
11010
12485
|
const meta_content = `fileFormatVersion: 2
|
|
11011
12486
|
guid: ${guid}
|
|
@@ -11015,7 +12490,7 @@ PrefabImporter:
|
|
|
11015
12490
|
assetBundleName:
|
|
11016
12491
|
assetBundleVariant:
|
|
11017
12492
|
`;
|
|
11018
|
-
|
|
12493
|
+
import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
|
|
11019
12494
|
console.log(JSON.stringify({
|
|
11020
12495
|
success: true,
|
|
11021
12496
|
file: output_path,
|
|
@@ -11029,9 +12504,119 @@ PrefabImporter:
|
|
|
11029
12504
|
|
|
11030
12505
|
// src/cmd-read.ts
|
|
11031
12506
|
init_scanner();
|
|
11032
|
-
var
|
|
11033
|
-
var
|
|
12507
|
+
var import_fs19 = require("fs");
|
|
12508
|
+
var import_path11 = require("path");
|
|
11034
12509
|
var import_os = require("os");
|
|
12510
|
+
function parse_unity_ref(ref_text) {
|
|
12511
|
+
const file_id_match = ref_text.match(/fileID:[ \t]*(-?\d+)/i);
|
|
12512
|
+
if (!file_id_match)
|
|
12513
|
+
return null;
|
|
12514
|
+
const guid_match = ref_text.match(/guid:[ \t]*([a-f0-9]{32})/i);
|
|
12515
|
+
const type_match = ref_text.match(/type:[ \t]*(-?\d+)/i);
|
|
12516
|
+
const parsed = { file_id: file_id_match[1] };
|
|
12517
|
+
if (guid_match)
|
|
12518
|
+
parsed.guid = guid_match[1].toLowerCase();
|
|
12519
|
+
if (type_match)
|
|
12520
|
+
parsed.type = parseInt(type_match[1], 10);
|
|
12521
|
+
return parsed;
|
|
12522
|
+
}
|
|
12523
|
+
function extract_prefab_list_section(raw, key) {
|
|
12524
|
+
const lines = raw.split(`
|
|
12525
|
+
`);
|
|
12526
|
+
const key_pattern = new RegExp(`^(\\s*)${key}:[ \\t]*(.*)$`);
|
|
12527
|
+
let key_index = -1;
|
|
12528
|
+
let key_indent = 0;
|
|
12529
|
+
for (let i = 0;i < lines.length; i++) {
|
|
12530
|
+
const match = lines[i].match(key_pattern);
|
|
12531
|
+
if (match) {
|
|
12532
|
+
key_index = i;
|
|
12533
|
+
key_indent = match[1].length;
|
|
12534
|
+
break;
|
|
12535
|
+
}
|
|
12536
|
+
}
|
|
12537
|
+
if (key_index === -1)
|
|
12538
|
+
return null;
|
|
12539
|
+
const section_lines = [lines[key_index]];
|
|
12540
|
+
for (let i = key_index + 1;i < lines.length; i++) {
|
|
12541
|
+
const line = lines[i];
|
|
12542
|
+
const trimmed = line.trim();
|
|
12543
|
+
if (trimmed.length === 0) {
|
|
12544
|
+
section_lines.push(line);
|
|
12545
|
+
continue;
|
|
12546
|
+
}
|
|
12547
|
+
const indent = (line.match(/^\s*/) || [""])[0].length;
|
|
12548
|
+
if (indent < key_indent)
|
|
12549
|
+
break;
|
|
12550
|
+
if (indent === key_indent && !trimmed.startsWith("-") && /^[A-Za-z_][A-Za-z0-9_]*:/.test(trimmed)) {
|
|
12551
|
+
break;
|
|
12552
|
+
}
|
|
12553
|
+
section_lines.push(line);
|
|
12554
|
+
}
|
|
12555
|
+
return { key_indent, section_lines };
|
|
12556
|
+
}
|
|
12557
|
+
function parse_removed_list(raw, key) {
|
|
12558
|
+
const section = extract_prefab_list_section(raw, key);
|
|
12559
|
+
if (!section)
|
|
12560
|
+
return [];
|
|
12561
|
+
const key_line = section.section_lines[0].trim();
|
|
12562
|
+
if (key_line.endsWith("[]"))
|
|
12563
|
+
return [];
|
|
12564
|
+
const refs = section.section_lines.join(`
|
|
12565
|
+
`).match(/\{[^}]*fileID:[^}]*\}/g) || [];
|
|
12566
|
+
const dedup = new Map;
|
|
12567
|
+
for (const ref of refs) {
|
|
12568
|
+
const parsed = parse_unity_ref(ref);
|
|
12569
|
+
if (!parsed)
|
|
12570
|
+
continue;
|
|
12571
|
+
const key_text = `${parsed.file_id}|${parsed.guid ?? ""}|${parsed.type ?? ""}`;
|
|
12572
|
+
if (!dedup.has(key_text))
|
|
12573
|
+
dedup.set(key_text, parsed);
|
|
12574
|
+
}
|
|
12575
|
+
return [...dedup.values()];
|
|
12576
|
+
}
|
|
12577
|
+
function parse_added_list(raw, key) {
|
|
12578
|
+
const section = extract_prefab_list_section(raw, key);
|
|
12579
|
+
if (!section)
|
|
12580
|
+
return [];
|
|
12581
|
+
const key_line = section.section_lines[0].trim();
|
|
12582
|
+
if (key_line.endsWith("[]"))
|
|
12583
|
+
return [];
|
|
12584
|
+
const lines = section.section_lines.slice(1);
|
|
12585
|
+
const entries = [];
|
|
12586
|
+
let i = 0;
|
|
12587
|
+
while (i < lines.length) {
|
|
12588
|
+
const line = lines[i];
|
|
12589
|
+
const trimmed = line.trimStart();
|
|
12590
|
+
if (!trimmed.startsWith("-")) {
|
|
12591
|
+
i++;
|
|
12592
|
+
continue;
|
|
12593
|
+
}
|
|
12594
|
+
const entry_indent = (line.match(/^\s*/) || [""])[0].length;
|
|
12595
|
+
const entry_lines = [line];
|
|
12596
|
+
i++;
|
|
12597
|
+
while (i < lines.length) {
|
|
12598
|
+
const next = lines[i];
|
|
12599
|
+
const next_trimmed = next.trim();
|
|
12600
|
+
const next_indent = (next.match(/^\s*/) || [""])[0].length;
|
|
12601
|
+
if (next_trimmed.length > 0 && next_indent === entry_indent && next_trimmed.startsWith("-")) {
|
|
12602
|
+
break;
|
|
12603
|
+
}
|
|
12604
|
+
entry_lines.push(next);
|
|
12605
|
+
i++;
|
|
12606
|
+
}
|
|
12607
|
+
const entry_text = entry_lines.join(`
|
|
12608
|
+
`);
|
|
12609
|
+
const target_match = entry_text.match(/targetCorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
|
|
12610
|
+
const insert_match = entry_text.match(/insertIndex:[ \t]*(-?\d+)/);
|
|
12611
|
+
const added_match = entry_text.match(/addedObject:[ \t]*(\{[^}]+\})/);
|
|
12612
|
+
entries.push({
|
|
12613
|
+
target_corresponding_source_object: target_match ? parse_unity_ref(target_match[1]) : null,
|
|
12614
|
+
insert_index: insert_match ? parseInt(insert_match[1], 10) : null,
|
|
12615
|
+
added_object: added_match ? parse_unity_ref(added_match[1]) : null
|
|
12616
|
+
});
|
|
12617
|
+
}
|
|
12618
|
+
return entries;
|
|
12619
|
+
}
|
|
11035
12620
|
function parse_material_yaml(content) {
|
|
11036
12621
|
const lines = content.replace(/\r/g, "").split(`
|
|
11037
12622
|
`);
|
|
@@ -11230,7 +12815,7 @@ function parse_material_yaml(content) {
|
|
|
11230
12815
|
return result;
|
|
11231
12816
|
}
|
|
11232
12817
|
function categorize_asset(filePath) {
|
|
11233
|
-
const ext =
|
|
12818
|
+
const ext = import_path11.extname(filePath).toLowerCase();
|
|
11234
12819
|
const categories = {
|
|
11235
12820
|
".cs": "script",
|
|
11236
12821
|
".mat": "material",
|
|
@@ -11274,12 +12859,12 @@ function categorize_asset(filePath) {
|
|
|
11274
12859
|
return categories[ext] || "other";
|
|
11275
12860
|
}
|
|
11276
12861
|
function find_project_root_from_file(filePath) {
|
|
11277
|
-
let dir =
|
|
12862
|
+
let dir = import_path11.dirname(import_path11.resolve(filePath));
|
|
11278
12863
|
for (let i = 0;i < 20; i++) {
|
|
11279
|
-
if (
|
|
12864
|
+
if (import_fs19.existsSync(import_path11.join(dir, "Assets")) && import_fs19.existsSync(import_path11.join(dir, "ProjectSettings"))) {
|
|
11280
12865
|
return dir;
|
|
11281
12866
|
}
|
|
11282
|
-
const parent =
|
|
12867
|
+
const parent = import_path11.dirname(dir);
|
|
11283
12868
|
if (parent === dir)
|
|
11284
12869
|
break;
|
|
11285
12870
|
dir = parent;
|
|
@@ -11289,13 +12874,13 @@ function find_project_root_from_file(filePath) {
|
|
|
11289
12874
|
function get_editor_log_path() {
|
|
11290
12875
|
const p = import_os.platform();
|
|
11291
12876
|
if (p === "darwin")
|
|
11292
|
-
return
|
|
12877
|
+
return import_path11.join(import_os.homedir(), "Library", "Logs", "Unity", "Editor.log");
|
|
11293
12878
|
if (p === "win32") {
|
|
11294
|
-
const localAppData = process.env.LOCALAPPDATA ||
|
|
11295
|
-
return
|
|
12879
|
+
const localAppData = process.env.LOCALAPPDATA || import_path11.join(import_os.homedir(), "AppData", "Local");
|
|
12880
|
+
return import_path11.join(localAppData, "Unity", "Editor", "Editor.log");
|
|
11296
12881
|
}
|
|
11297
12882
|
if (p === "linux")
|
|
11298
|
-
return
|
|
12883
|
+
return import_path11.join(import_os.homedir(), ".config", "unity3d", "Editor.log");
|
|
11299
12884
|
return null;
|
|
11300
12885
|
}
|
|
11301
12886
|
function parse_log_line_timestamp(line) {
|
|
@@ -11626,19 +13211,19 @@ function walk_files(dir, extensions) {
|
|
|
11626
13211
|
const current = stack.pop();
|
|
11627
13212
|
let entries;
|
|
11628
13213
|
try {
|
|
11629
|
-
entries =
|
|
13214
|
+
entries = import_fs19.readdirSync(current);
|
|
11630
13215
|
} catch {
|
|
11631
13216
|
continue;
|
|
11632
13217
|
}
|
|
11633
13218
|
for (const entry of entries) {
|
|
11634
13219
|
if (entry.startsWith("."))
|
|
11635
13220
|
continue;
|
|
11636
|
-
const full =
|
|
13221
|
+
const full = import_path11.join(current, entry);
|
|
11637
13222
|
try {
|
|
11638
|
-
const stat =
|
|
13223
|
+
const stat = import_fs19.statSync(full);
|
|
11639
13224
|
if (stat.isDirectory()) {
|
|
11640
13225
|
stack.push(full);
|
|
11641
|
-
} else if (extensions.has(
|
|
13226
|
+
} else if (extensions.has(import_path11.extname(full).toLowerCase())) {
|
|
11642
13227
|
results.push(full);
|
|
11643
13228
|
}
|
|
11644
13229
|
} catch {
|
|
@@ -11649,7 +13234,7 @@ function walk_files(dir, extensions) {
|
|
|
11649
13234
|
return results;
|
|
11650
13235
|
}
|
|
11651
13236
|
function validate_unity_yaml(file) {
|
|
11652
|
-
if (!
|
|
13237
|
+
if (!import_fs19.existsSync(file)) {
|
|
11653
13238
|
return `File not found: ${file}`;
|
|
11654
13239
|
}
|
|
11655
13240
|
try {
|
|
@@ -11735,10 +13320,17 @@ function build_read_command(getScanner) {
|
|
|
11735
13320
|
}
|
|
11736
13321
|
if (result.total === 0 && !result.error) {
|
|
11737
13322
|
const prefabInstances = result.prefabInstances;
|
|
13323
|
+
let variantDetected = false;
|
|
13324
|
+
let variantResolvedFromSource = false;
|
|
11738
13325
|
if (prefabInstances && prefabInstances.length > 0) {
|
|
13326
|
+
variantDetected = true;
|
|
11739
13327
|
try {
|
|
11740
13328
|
const doc = UnityDocument.from_file(file);
|
|
11741
|
-
const
|
|
13329
|
+
const strippedGoCount = doc.blocks.filter((b) => b.class_id === 1 && b.is_stripped).length;
|
|
13330
|
+
if (strippedGoCount > 0) {
|
|
13331
|
+
variantDetected = true;
|
|
13332
|
+
}
|
|
13333
|
+
const projectPath = find_unity_project_root(import_path11.dirname(file));
|
|
11742
13334
|
const resolved = resolve_source_prefab(doc, file, projectPath ?? undefined);
|
|
11743
13335
|
if (resolved) {
|
|
11744
13336
|
const sourceResult = getScanner().inspect_all_paginated({
|
|
@@ -11772,6 +13364,7 @@ function build_read_command(getScanner) {
|
|
|
11772
13364
|
resultRecord.resolvedFromSource = true;
|
|
11773
13365
|
resultRecord.sourcePrefab = resolved.source_path;
|
|
11774
13366
|
resultRecord.sourceGuid = resolved.source_guid;
|
|
13367
|
+
variantResolvedFromSource = true;
|
|
11775
13368
|
const variantNote = `PrefabVariant resolved from source prefab: ${resolved.source_path}`;
|
|
11776
13369
|
result.warning = result.warning ? `${result.warning}; ${variantNote}` : variantNote;
|
|
11777
13370
|
}
|
|
@@ -11780,10 +13373,15 @@ function build_read_command(getScanner) {
|
|
|
11780
13373
|
}
|
|
11781
13374
|
if (result.total === 0) {
|
|
11782
13375
|
try {
|
|
11783
|
-
const fileSize =
|
|
13376
|
+
const fileSize = import_fs19.statSync(file).size;
|
|
11784
13377
|
if (fileSize > 100) {
|
|
11785
|
-
|
|
11786
|
-
|
|
13378
|
+
if (variantDetected && !variantResolvedFromSource) {
|
|
13379
|
+
const variantWarning = "PrefabVariant detected but source prefab hierarchy could not be resolved (missing project context or GUID cache). Use --project or run setup to resolve source hierarchy.";
|
|
13380
|
+
result.warning = result.warning ? `${result.warning}; ${variantWarning}` : variantWarning;
|
|
13381
|
+
} else {
|
|
13382
|
+
const corruptWarning = "File has valid Unity YAML header but contains no parseable GameObjects -- file may be corrupt or malformed";
|
|
13383
|
+
result.warning = result.warning ? `${result.warning}; ${corruptWarning}` : corruptWarning;
|
|
13384
|
+
}
|
|
11787
13385
|
}
|
|
11788
13386
|
} catch {}
|
|
11789
13387
|
}
|
|
@@ -11893,7 +13491,7 @@ function build_read_command(getScanner) {
|
|
|
11893
13491
|
console.log(JSON.stringify({ error: matValidationError }, null, 2));
|
|
11894
13492
|
process.exit(1);
|
|
11895
13493
|
}
|
|
11896
|
-
const content =
|
|
13494
|
+
const content = import_fs19.readFileSync(file, "utf-8");
|
|
11897
13495
|
if (!content.includes("Material:")) {
|
|
11898
13496
|
console.log(JSON.stringify({ error: `File "${file}" does not contain a Material block. Use 'read asset' for generic Unity YAML files.` }, null, 2));
|
|
11899
13497
|
process.exit(1);
|
|
@@ -11935,13 +13533,13 @@ function build_read_command(getScanner) {
|
|
|
11935
13533
|
}, null, 2));
|
|
11936
13534
|
});
|
|
11937
13535
|
cmd.command("dependencies <file>").description("List asset dependencies (GUIDs referenced by this file)").option("--project <path>", "Unity project root (for GUID resolution)").option("--unresolved", "Show only GUIDs that could not be resolved").option("--recursive [depth]", "Follow dependency chain N levels deep (default: 3)").option("-j, --json", "Output as JSON").action((file, options) => {
|
|
11938
|
-
if (!
|
|
13536
|
+
if (!import_fs19.existsSync(file)) {
|
|
11939
13537
|
console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
|
|
11940
13538
|
process.exit(1);
|
|
11941
13539
|
}
|
|
11942
13540
|
let content;
|
|
11943
13541
|
try {
|
|
11944
|
-
content =
|
|
13542
|
+
content = import_fs19.readFileSync(file, "utf-8");
|
|
11945
13543
|
} catch {
|
|
11946
13544
|
console.log(JSON.stringify({ error: `Cannot read file: ${file}` }, null, 2));
|
|
11947
13545
|
process.exit(1);
|
|
@@ -11982,7 +13580,7 @@ function build_read_command(getScanner) {
|
|
|
11982
13580
|
const result = [];
|
|
11983
13581
|
let fileContent;
|
|
11984
13582
|
try {
|
|
11985
|
-
fileContent =
|
|
13583
|
+
fileContent = import_fs19.readFileSync(filePath, "utf-8");
|
|
11986
13584
|
} catch {
|
|
11987
13585
|
return [];
|
|
11988
13586
|
}
|
|
@@ -11999,7 +13597,7 @@ function build_read_command(getScanner) {
|
|
|
11999
13597
|
const rAbsPath = cache.resolve_absolute(g);
|
|
12000
13598
|
const rType = rPath ? categorize_asset(rPath) : "unknown";
|
|
12001
13599
|
const dep = { guid: g, path: rPath, type: rType, depth };
|
|
12002
|
-
if (rAbsPath && depth < max_depth &&
|
|
13600
|
+
if (rAbsPath && depth < max_depth && import_fs19.existsSync(rAbsPath)) {
|
|
12003
13601
|
const subs = traverse_file(rAbsPath, depth + 1);
|
|
12004
13602
|
if (subs.length > 0)
|
|
12005
13603
|
dep.sub_dependencies = subs;
|
|
@@ -12028,7 +13626,7 @@ function build_read_command(getScanner) {
|
|
|
12028
13626
|
const rDep = { guid: dep.guid, path: dep.path, type: dep.type, depth: 0 };
|
|
12029
13627
|
if (dep.path) {
|
|
12030
13628
|
const absPath = cache.resolve_absolute(dep.guid);
|
|
12031
|
-
if (absPath &&
|
|
13629
|
+
if (absPath && import_fs19.existsSync(absPath)) {
|
|
12032
13630
|
const subs = traverse_file(absPath, 1);
|
|
12033
13631
|
if (subs.length > 0)
|
|
12034
13632
|
rDep.sub_dependencies = subs;
|
|
@@ -12056,38 +13654,41 @@ function build_read_command(getScanner) {
|
|
|
12056
13654
|
by_type: options.unresolved ? undefined : byType
|
|
12057
13655
|
};
|
|
12058
13656
|
if (!cache) {
|
|
12059
|
-
output._hint = "Run 'setup <
|
|
13657
|
+
output._hint = "Run 'setup' (or 'setup -p <path>') to resolve GUID paths";
|
|
12060
13658
|
}
|
|
12061
13659
|
console.log(JSON.stringify(output, null, 2));
|
|
12062
13660
|
});
|
|
12063
|
-
cmd.command("settings
|
|
13661
|
+
cmd.command("settings").description("Read Unity project settings (TagManager, DynamicsManager, QualitySettings, TimeManager, etc.)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-s, --setting <name>", "Setting name or alias (tags, physics, quality, time)", "TagManager").option("-j, --json", "Output as JSON").action((options) => {
|
|
13662
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
12064
13663
|
const result = read_settings({
|
|
12065
|
-
project_path,
|
|
13664
|
+
project_path: resolvedProjectPath,
|
|
12066
13665
|
setting: options.setting
|
|
12067
13666
|
});
|
|
12068
13667
|
console.log(JSON.stringify(result, null, 2));
|
|
12069
13668
|
if (!result.success)
|
|
12070
13669
|
process.exit(1);
|
|
12071
13670
|
});
|
|
12072
|
-
cmd.command("build
|
|
13671
|
+
cmd.command("build").description("Read build settings (scene list, build profiles)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((options) => {
|
|
13672
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
12073
13673
|
try {
|
|
12074
|
-
const result = get_build_settings(
|
|
13674
|
+
const result = get_build_settings(resolvedProjectPath);
|
|
12075
13675
|
console.log(JSON.stringify(result, null, 2));
|
|
12076
13676
|
} catch (err) {
|
|
12077
13677
|
console.log(JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }, null, 2));
|
|
12078
13678
|
process.exitCode = 1;
|
|
12079
13679
|
}
|
|
12080
13680
|
});
|
|
12081
|
-
cmd.command("scenes
|
|
13681
|
+
cmd.command("scenes").description('Read build scenes (alias for "read build")').option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((options) => {
|
|
13682
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
12082
13683
|
try {
|
|
12083
|
-
const result = get_build_settings(
|
|
13684
|
+
const result = get_build_settings(resolvedProjectPath);
|
|
12084
13685
|
console.log(JSON.stringify(result, null, 2));
|
|
12085
13686
|
} catch (err) {
|
|
12086
13687
|
console.log(JSON.stringify({ success: false, error: err instanceof Error ? err.message : String(err) }, null, 2));
|
|
12087
13688
|
process.exitCode = 1;
|
|
12088
13689
|
}
|
|
12089
13690
|
});
|
|
12090
|
-
cmd.command("overrides <file> <prefab_instance>").description("Read PrefabInstance
|
|
13691
|
+
cmd.command("overrides <file> <prefab_instance>").description("Read PrefabInstance overrides (modifications + removed/added state)").option("--flat", "Output simplified list").option("-j, --json", "Output as JSON").action((file, prefab_instance, options) => {
|
|
12091
13692
|
try {
|
|
12092
13693
|
const doc = UnityDocument.from_file(file);
|
|
12093
13694
|
let block = null;
|
|
@@ -12150,6 +13751,10 @@ function build_read_command(getScanner) {
|
|
|
12150
13751
|
i++;
|
|
12151
13752
|
}
|
|
12152
13753
|
}
|
|
13754
|
+
const removed_components = parse_removed_list(block.raw, "m_RemovedComponents");
|
|
13755
|
+
const removed_gameobjects = parse_removed_list(block.raw, "m_RemovedGameObjects");
|
|
13756
|
+
const added_gameobjects = parse_added_list(block.raw, "m_AddedGameObjects");
|
|
13757
|
+
const added_components = parse_added_list(block.raw, "m_AddedComponents");
|
|
12153
13758
|
if (options.flat) {
|
|
12154
13759
|
const flat = modifications.map((m) => ({
|
|
12155
13760
|
property_path: m.property_path,
|
|
@@ -12158,7 +13763,14 @@ function build_read_command(getScanner) {
|
|
|
12158
13763
|
}));
|
|
12159
13764
|
console.log(JSON.stringify(flat, null, 2));
|
|
12160
13765
|
} else {
|
|
12161
|
-
console.log(JSON.stringify(
|
|
13766
|
+
console.log(JSON.stringify({
|
|
13767
|
+
prefab_instance_id: block.file_id,
|
|
13768
|
+
modifications,
|
|
13769
|
+
removed_components,
|
|
13770
|
+
removed_gameobjects,
|
|
13771
|
+
added_gameobjects,
|
|
13772
|
+
added_components
|
|
13773
|
+
}, null, 2));
|
|
12162
13774
|
}
|
|
12163
13775
|
} catch (err) {
|
|
12164
13776
|
console.log(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }, null, 2));
|
|
@@ -12213,7 +13825,7 @@ function build_read_command(getScanner) {
|
|
|
12213
13825
|
});
|
|
12214
13826
|
cmd.command("target <file> <gameobject_name> [component_type]").description("Build a --target reference string for prefab override commands").option("-p, --project <path>", "Unity project path").option("-j, --json", "Output as JSON").action((file, gameobject_name, component_type, options) => {
|
|
12215
13827
|
try {
|
|
12216
|
-
if (!
|
|
13828
|
+
if (!import_fs19.existsSync(file)) {
|
|
12217
13829
|
console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
|
|
12218
13830
|
process.exit(1);
|
|
12219
13831
|
}
|
|
@@ -12274,12 +13886,12 @@ function build_read_command(getScanner) {
|
|
|
12274
13886
|
if (classId === null && compBlock.class_id === 114) {
|
|
12275
13887
|
const scriptMatch = compBlock.raw.match(/m_Script:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
|
|
12276
13888
|
if (scriptMatch) {
|
|
12277
|
-
const project = options.project || find_unity_project_root(
|
|
13889
|
+
const project = options.project || find_unity_project_root(import_path11.dirname(file));
|
|
12278
13890
|
if (project) {
|
|
12279
13891
|
const cache = load_guid_cache(project);
|
|
12280
13892
|
if (cache) {
|
|
12281
13893
|
const scriptPath = cache.resolve(scriptMatch[1]);
|
|
12282
|
-
if (scriptPath &&
|
|
13894
|
+
if (scriptPath && import_path11.basename(scriptPath, ".cs").toLowerCase() === component_type.toLowerCase().replace(/\.cs$/, "")) {
|
|
12283
13895
|
targetFileId = refId;
|
|
12284
13896
|
compFound = true;
|
|
12285
13897
|
break;
|
|
@@ -12294,7 +13906,7 @@ function build_read_command(getScanner) {
|
|
|
12294
13906
|
process.exit(1);
|
|
12295
13907
|
}
|
|
12296
13908
|
} else {
|
|
12297
|
-
const project = options.project || find_unity_project_root(
|
|
13909
|
+
const project = options.project || find_unity_project_root(import_path11.dirname(file));
|
|
12298
13910
|
const resolved = resolve_source_prefab(doc, file, project ?? undefined);
|
|
12299
13911
|
if (resolved) {
|
|
12300
13912
|
const sourceDoc = UnityDocument.from_file(resolved.source_path);
|
|
@@ -12333,7 +13945,7 @@ function build_read_command(getScanner) {
|
|
|
12333
13945
|
}
|
|
12334
13946
|
});
|
|
12335
13947
|
cmd.command("script <file>").description("Extract C# type declarations from a .cs file or .NET DLL").option("-j, --json", "Output as JSON").action((file, _options) => {
|
|
12336
|
-
if (!
|
|
13948
|
+
if (!import_fs19.existsSync(file)) {
|
|
12337
13949
|
console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
|
|
12338
13950
|
process.exit(1);
|
|
12339
13951
|
}
|
|
@@ -12369,7 +13981,7 @@ function build_read_command(getScanner) {
|
|
|
12369
13981
|
console.log(JSON.stringify({ error: "Native module not available" }, null, 2));
|
|
12370
13982
|
process.exit(1);
|
|
12371
13983
|
}
|
|
12372
|
-
const projectPath =
|
|
13984
|
+
const projectPath = import_path11.resolve(options.project);
|
|
12373
13985
|
const validSources = ["assets", "packages", "dlls", "all"];
|
|
12374
13986
|
if (!validSources.includes(options.source)) {
|
|
12375
13987
|
console.log(JSON.stringify({ error: `Invalid --source "${options.source}". Valid values: ${validSources.join(", ")}` }, null, 2));
|
|
@@ -12418,20 +14030,18 @@ function build_read_command(getScanner) {
|
|
|
12418
14030
|
types: displayed
|
|
12419
14031
|
}, null, 2));
|
|
12420
14032
|
});
|
|
12421
|
-
cmd.command("log
|
|
12422
|
-
if (projectPath && !options.project)
|
|
12423
|
-
options.project = projectPath;
|
|
14033
|
+
cmd.command("log").description("Read and filter the Unity Editor.log").option("--path <file>", "Path to Editor.log (auto-detected if omitted)").option("--project <path>", "Filter to log entries from a specific Unity project session").option("--tail <n>", "Show last N lines (default 50)", "50").option("--errors", "Show only error entries").option("--warnings", "Show only warning entries").option("--compile-errors", "Show only C# compilation errors").option("--import-errors", "Show only asset import errors").option("--since <timestamp>", "Filter entries after this timestamp (YYYY-MM-DD or HH:MM:SS)").option("--search <pattern>", "Regex filter on log content").option("-j, --json", "Output as JSON").action((options) => {
|
|
12424
14034
|
const logPath = options.path || get_editor_log_path();
|
|
12425
|
-
if (!logPath || !
|
|
14035
|
+
if (!logPath || !import_fs19.existsSync(logPath)) {
|
|
12426
14036
|
console.log(JSON.stringify({ error: `Editor.log not found${logPath ? `: ${logPath}` : ". Could not detect platform log path."}` }, null, 2));
|
|
12427
14037
|
process.exit(1);
|
|
12428
14038
|
}
|
|
12429
|
-
const content =
|
|
14039
|
+
const content = import_fs19.readFileSync(logPath, "utf-8");
|
|
12430
14040
|
let lines = content.split(/\r?\n/);
|
|
12431
14041
|
if (options.project) {
|
|
12432
|
-
const
|
|
12433
|
-
const projectName =
|
|
12434
|
-
const escapedPath =
|
|
14042
|
+
const projectPath = import_path11.resolve(options.project);
|
|
14043
|
+
const projectName = import_path11.basename(projectPath);
|
|
14044
|
+
const escapedPath = projectPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12435
14045
|
const escapedName = projectName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12436
14046
|
const session_markers = [
|
|
12437
14047
|
new RegExp(`Loading project at '${escapedPath}'`),
|
|
@@ -12551,11 +14161,11 @@ function build_read_command(getScanner) {
|
|
|
12551
14161
|
});
|
|
12552
14162
|
cmd.command("meta <file>").description("Read a Unity .meta file and show importer settings").option("--summary", "Show importer type and key settings only").option("-j, --json", "Output as JSON").action((file, options) => {
|
|
12553
14163
|
const metaPath = file.endsWith(".meta") ? file : `${file}.meta`;
|
|
12554
|
-
if (!
|
|
14164
|
+
if (!import_fs19.existsSync(metaPath)) {
|
|
12555
14165
|
console.log(JSON.stringify({ error: `Meta file not found: ${metaPath}` }, null, 2));
|
|
12556
14166
|
process.exit(1);
|
|
12557
14167
|
}
|
|
12558
|
-
const content =
|
|
14168
|
+
const content = import_fs19.readFileSync(metaPath, "utf-8");
|
|
12559
14169
|
const lines = content.split(/\r?\n/);
|
|
12560
14170
|
let guid = "";
|
|
12561
14171
|
let importer_type = "Unknown";
|
|
@@ -12643,7 +14253,7 @@ function build_read_command(getScanner) {
|
|
|
12643
14253
|
console.log(JSON.stringify({ error: animValidationError }, null, 2));
|
|
12644
14254
|
process.exit(1);
|
|
12645
14255
|
}
|
|
12646
|
-
const content =
|
|
14256
|
+
const content = import_fs19.readFileSync(file, "utf-8");
|
|
12647
14257
|
const clip = parse_animation_yaml(content, options.curves === true);
|
|
12648
14258
|
if (!clip) {
|
|
12649
14259
|
console.log(JSON.stringify({ error: `No AnimationClip found in "${file}". Is this an .anim file?` }, null, 2));
|
|
@@ -12704,7 +14314,7 @@ function build_read_command(getScanner) {
|
|
|
12704
14314
|
console.log(JSON.stringify({ error: ctrlValidationError }, null, 2));
|
|
12705
14315
|
process.exit(1);
|
|
12706
14316
|
}
|
|
12707
|
-
const content =
|
|
14317
|
+
const content = import_fs19.readFileSync(file, "utf-8");
|
|
12708
14318
|
const blocks = split_yaml_blocks(content);
|
|
12709
14319
|
const controller_block = blocks.find((b) => b.class_id === 91);
|
|
12710
14320
|
if (!controller_block) {
|
|
@@ -12876,7 +14486,7 @@ function build_read_command(getScanner) {
|
|
|
12876
14486
|
}
|
|
12877
14487
|
const states_out = { file, states_by_layer: by_layer };
|
|
12878
14488
|
if (needs_setup_hint)
|
|
12879
|
-
states_out._hint = "Run 'setup <
|
|
14489
|
+
states_out._hint = "Run 'setup' (or 'setup -p <path>') to resolve motion paths";
|
|
12880
14490
|
console.log(JSON.stringify(states_out, null, 2));
|
|
12881
14491
|
return;
|
|
12882
14492
|
}
|
|
@@ -12895,7 +14505,7 @@ function build_read_command(getScanner) {
|
|
|
12895
14505
|
if (options.summary) {
|
|
12896
14506
|
console.log(JSON.stringify({
|
|
12897
14507
|
file,
|
|
12898
|
-
name: yaml_field(controller_block.raw, "m_Name") ||
|
|
14508
|
+
name: yaml_field(controller_block.raw, "m_Name") || import_path11.basename(file),
|
|
12899
14509
|
parameter_count: params.length,
|
|
12900
14510
|
layer_count: layers.length,
|
|
12901
14511
|
state_count: states.length,
|
|
@@ -12908,7 +14518,7 @@ function build_read_command(getScanner) {
|
|
|
12908
14518
|
default_state_names[s.file_id] = s.name;
|
|
12909
14519
|
const default_out = {
|
|
12910
14520
|
file,
|
|
12911
|
-
name: yaml_field(controller_block.raw, "m_Name") ||
|
|
14521
|
+
name: yaml_field(controller_block.raw, "m_Name") || import_path11.basename(file),
|
|
12912
14522
|
parameters: params,
|
|
12913
14523
|
layers: layers.map((l) => l.name),
|
|
12914
14524
|
states: states.map((s) => ({
|
|
@@ -12925,16 +14535,17 @@ function build_read_command(getScanner) {
|
|
|
12925
14535
|
}))
|
|
12926
14536
|
};
|
|
12927
14537
|
if (needs_setup_hint)
|
|
12928
|
-
default_out._hint = "Run 'setup <
|
|
14538
|
+
default_out._hint = "Run 'setup' (or 'setup -p <path>') to resolve motion paths";
|
|
12929
14539
|
console.log(JSON.stringify(default_out, null, 2));
|
|
12930
14540
|
});
|
|
12931
|
-
cmd.command("dependents <
|
|
14541
|
+
cmd.command("dependents <guid>").description("Find which files reference a given GUID (reverse dependency lookup)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--type <type>", "Filter to specific file types (scene, prefab, mat, etc.)").option("-j, --json", "Output as JSON").action((guid, options) => {
|
|
12932
14542
|
if (!/^[0-9a-f]{32}$/i.test(guid)) {
|
|
12933
14543
|
console.log(JSON.stringify({ error: "GUID must be a 32-character hexadecimal string" }, null, 2));
|
|
12934
14544
|
process.exit(1);
|
|
12935
14545
|
}
|
|
12936
|
-
const
|
|
12937
|
-
|
|
14546
|
+
const project_path = resolve_project_path(options.project);
|
|
14547
|
+
const assetsDir = import_path11.join(import_path11.resolve(project_path), "Assets");
|
|
14548
|
+
if (!import_fs19.existsSync(assetsDir)) {
|
|
12938
14549
|
console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
|
|
12939
14550
|
process.exit(1);
|
|
12940
14551
|
}
|
|
@@ -12963,9 +14574,9 @@ function build_read_command(getScanner) {
|
|
|
12963
14574
|
const guid_pattern = `guid: ${guid}`;
|
|
12964
14575
|
for (const f of files) {
|
|
12965
14576
|
try {
|
|
12966
|
-
const fc =
|
|
14577
|
+
const fc = import_fs19.readFileSync(f, "utf-8");
|
|
12967
14578
|
if (fc.includes(guid_pattern)) {
|
|
12968
|
-
const rel =
|
|
14579
|
+
const rel = import_path11.relative(import_path11.resolve(project_path), f);
|
|
12969
14580
|
const ftype = categorize_asset(f);
|
|
12970
14581
|
referencing.push({ path: rel, type: ftype });
|
|
12971
14582
|
}
|
|
@@ -12984,17 +14595,17 @@ function build_read_command(getScanner) {
|
|
|
12984
14595
|
by_type[r.type].push(r.path);
|
|
12985
14596
|
}
|
|
12986
14597
|
console.log(JSON.stringify({
|
|
12987
|
-
project_path:
|
|
14598
|
+
project_path: import_path11.resolve(project_path),
|
|
12988
14599
|
guid,
|
|
12989
14600
|
total_references: filtered.length,
|
|
12990
14601
|
by_type
|
|
12991
14602
|
}, null, 2));
|
|
12992
14603
|
});
|
|
12993
|
-
cmd.command("unused
|
|
12994
|
-
const resolvedProject =
|
|
12995
|
-
const assetsDir =
|
|
12996
|
-
if (!
|
|
12997
|
-
console.log(JSON.stringify({ error: `Assets directory not found in "${
|
|
14604
|
+
cmd.command("unused").description("Find potentially unused assets (zero inbound GUID references)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--type <type>", "Filter to specific asset types").option("--ignore <glob>", "Exclude paths matching this pattern").option("--max <n>", "Maximum results to return (default 200)", "200").option("-j, --json", "Output as JSON").action((options) => {
|
|
14605
|
+
const resolvedProject = resolve_project_path(options.project);
|
|
14606
|
+
const assetsDir = import_path11.join(resolvedProject, "Assets");
|
|
14607
|
+
if (!import_fs19.existsSync(assetsDir)) {
|
|
14608
|
+
console.log(JSON.stringify({ error: `Assets directory not found in "${resolvedProject}"` }, null, 2));
|
|
12998
14609
|
process.exit(1);
|
|
12999
14610
|
}
|
|
13000
14611
|
const guidCacheObj = load_guid_cache(resolvedProject);
|
|
@@ -13021,7 +14632,7 @@ function build_read_command(getScanner) {
|
|
|
13021
14632
|
const guid_re = /guid:\s*([a-f0-9]{32})/g;
|
|
13022
14633
|
for (const f of files) {
|
|
13023
14634
|
try {
|
|
13024
|
-
const fc =
|
|
14635
|
+
const fc = import_fs19.readFileSync(f, "utf-8");
|
|
13025
14636
|
let gm;
|
|
13026
14637
|
while ((gm = guid_re.exec(fc)) !== null) {
|
|
13027
14638
|
referenced_guids.add(gm[1]);
|
|
@@ -13030,10 +14641,10 @@ function build_read_command(getScanner) {
|
|
|
13030
14641
|
continue;
|
|
13031
14642
|
}
|
|
13032
14643
|
}
|
|
13033
|
-
const buildSettingsPath =
|
|
14644
|
+
const buildSettingsPath = import_path11.join(resolvedProject, "ProjectSettings", "EditorBuildSettings.asset");
|
|
13034
14645
|
const build_scene_guids = new Set;
|
|
13035
|
-
if (
|
|
13036
|
-
const bsc =
|
|
14646
|
+
if (import_fs19.existsSync(buildSettingsPath)) {
|
|
14647
|
+
const bsc = import_fs19.readFileSync(buildSettingsPath, "utf-8");
|
|
13037
14648
|
let bm;
|
|
13038
14649
|
while ((bm = guid_re.exec(bsc)) !== null) {
|
|
13039
14650
|
build_scene_guids.add(bm[1]);
|
|
@@ -13054,7 +14665,7 @@ function build_read_command(getScanner) {
|
|
|
13054
14665
|
continue;
|
|
13055
14666
|
if (ignore_pattern && ignore_pattern.test(asset_path))
|
|
13056
14667
|
continue;
|
|
13057
|
-
if (
|
|
14668
|
+
if (import_path11.extname(asset_path) === "")
|
|
13058
14669
|
continue;
|
|
13059
14670
|
const asset_type = categorize_asset(asset_path);
|
|
13060
14671
|
if (options.type && asset_type !== options.type)
|
|
@@ -13085,7 +14696,8 @@ function build_read_command(getScanner) {
|
|
|
13085
14696
|
by_type
|
|
13086
14697
|
}, null, 2));
|
|
13087
14698
|
});
|
|
13088
|
-
cmd.command("manifest
|
|
14699
|
+
cmd.command("manifest").description("List packages from Packages/manifest.json").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--search <pattern>", "Filter packages by name pattern").option("-j, --json", "Output as JSON").action((options) => {
|
|
14700
|
+
const project_path = resolve_project_path(options.project);
|
|
13089
14701
|
const result = list_packages(project_path, options.search);
|
|
13090
14702
|
if ("error" in result) {
|
|
13091
14703
|
console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
|
|
@@ -13155,8 +14767,8 @@ function build_read_command(getScanner) {
|
|
|
13155
14767
|
}
|
|
13156
14768
|
|
|
13157
14769
|
// src/cmd-update.ts
|
|
13158
|
-
var
|
|
13159
|
-
var
|
|
14770
|
+
var import_fs20 = require("fs");
|
|
14771
|
+
var import_path12 = require("path");
|
|
13160
14772
|
|
|
13161
14773
|
// src/animator-utils.ts
|
|
13162
14774
|
function split_yaml_blocks2(content) {
|
|
@@ -13285,7 +14897,13 @@ function parseVector(str) {
|
|
|
13285
14897
|
}
|
|
13286
14898
|
return { x: parts[0], y: parts[1], z: parts[2] };
|
|
13287
14899
|
}
|
|
13288
|
-
function resolve_transform_id(scanner, file, identifier) {
|
|
14900
|
+
function resolve_transform_id(scanner, file, identifier, by_id = false) {
|
|
14901
|
+
if (by_id) {
|
|
14902
|
+
if (!/^-?\d+$/.test(identifier)) {
|
|
14903
|
+
return { error: `Invalid fileID: "${identifier}" \u2014 expected a numeric value when using --by-id` };
|
|
14904
|
+
}
|
|
14905
|
+
return { transform_id: identifier };
|
|
14906
|
+
}
|
|
13289
14907
|
let resolved_id = identifier;
|
|
13290
14908
|
if (!/^-?\d+$/.test(identifier)) {
|
|
13291
14909
|
const matches = scanner.find_by_name(file, identifier, false);
|
|
@@ -13310,7 +14928,17 @@ function resolve_transform_id(scanner, file, identifier) {
|
|
|
13310
14928
|
}
|
|
13311
14929
|
function build_update_command(getScanner) {
|
|
13312
14930
|
const cmd = new Command("update").description("Update Unity object properties, transforms, settings, and hierarchy");
|
|
13313
|
-
|
|
14931
|
+
async function check_loaded_edit_protection(file, bypass, project_path) {
|
|
14932
|
+
const guard = await enforce_loaded_edit_protection(file, bypass, project_path);
|
|
14933
|
+
if (guard.allowed)
|
|
14934
|
+
return true;
|
|
14935
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
14936
|
+
process.exitCode = 1;
|
|
14937
|
+
return false;
|
|
14938
|
+
}
|
|
14939
|
+
cmd.command("gameobject <file> <object_name> <property> <value>").description("Edit GameObject property value safely").option("-j, --json", "Output as JSON").option("-p, --project <path>", "Unity project path (for tag validation)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, object_name, property, value, options) => {
|
|
14940
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
|
|
14941
|
+
return;
|
|
13314
14942
|
const result = editProperty({
|
|
13315
14943
|
file_path: file,
|
|
13316
14944
|
object_name,
|
|
@@ -13322,7 +14950,7 @@ function build_update_command(getScanner) {
|
|
|
13322
14950
|
if (!result.success)
|
|
13323
14951
|
process.exitCode = 1;
|
|
13324
14952
|
});
|
|
13325
|
-
cmd.command("component <file> <file_id> <property> <value>").description("Edit any component property by file ID. Supports dotted paths (m_LocalPosition.x) and array paths (m_Materials.Array.data[0]). Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("-j, --json", "Output as JSON").option("-p, --project <path>", "Unity project path (for asset reference resolution)").action((file, file_id, property, value, options) => {
|
|
14953
|
+
cmd.command("component <file> <file_id> <property> <value>").description("Edit any component property by file ID. Supports dotted paths (m_LocalPosition.x) and array paths (m_Materials.Array.data[0]). Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("-j, --json", "Output as JSON").option("-p, --project <path>", "Unity project path (for asset reference resolution)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, file_id, property, value, options) => {
|
|
13326
14954
|
if (property === "m_RootOrder") {
|
|
13327
14955
|
const num = Number(value);
|
|
13328
14956
|
if (!Number.isInteger(num) || num < 0) {
|
|
@@ -13331,6 +14959,8 @@ function build_update_command(getScanner) {
|
|
|
13331
14959
|
return;
|
|
13332
14960
|
}
|
|
13333
14961
|
}
|
|
14962
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
|
|
14963
|
+
return;
|
|
13334
14964
|
const result = editComponentByFileId({
|
|
13335
14965
|
file_path: file,
|
|
13336
14966
|
file_id,
|
|
@@ -13342,7 +14972,7 @@ function build_update_command(getScanner) {
|
|
|
13342
14972
|
if (!result.success)
|
|
13343
14973
|
process.exitCode = 1;
|
|
13344
14974
|
});
|
|
13345
|
-
cmd.command("transform <file> <identifier>").description("Edit Transform by GameObject name or transform fileID").option("-p, --position <x,y,z>", "Set local position").option("-r, --rotation <x,y,z>", "Set local rotation (Euler angles in degrees)").option("-s, --scale <x,y,z>", "Set local scale").option("-j, --json", "Output as JSON").action((file, identifier, options) => {
|
|
14975
|
+
cmd.command("transform <file> <identifier>").description("Edit Transform by GameObject name or transform fileID. Use --by-id to force numeric fileID mode").option("-p, --position <x,y,z>", "Set local position").option("-r, --rotation <x,y,z>", "Set local rotation (Euler angles in degrees)").option("-s, --scale <x,y,z>", "Set local scale").option("--by-id", "Treat identifier as a numeric fileID instead of name lookup").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, identifier, options) => {
|
|
13346
14976
|
if (!options.position && !options.rotation && !options.scale) {
|
|
13347
14977
|
console.log(JSON.stringify({
|
|
13348
14978
|
success: false,
|
|
@@ -13352,7 +14982,9 @@ function build_update_command(getScanner) {
|
|
|
13352
14982
|
process.exitCode = 1;
|
|
13353
14983
|
return;
|
|
13354
14984
|
}
|
|
13355
|
-
|
|
14985
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
14986
|
+
return;
|
|
14987
|
+
const resolved = resolve_transform_id(getScanner(), file, identifier, options.byId);
|
|
13356
14988
|
if ("error" in resolved) {
|
|
13357
14989
|
console.log(JSON.stringify({
|
|
13358
14990
|
success: false,
|
|
@@ -13404,13 +15036,14 @@ function build_update_command(getScanner) {
|
|
|
13404
15036
|
if (!result.success)
|
|
13405
15037
|
process.exitCode = 1;
|
|
13406
15038
|
});
|
|
13407
|
-
cmd.command("settings
|
|
15039
|
+
cmd.command("settings").description("Edit a property in any ProjectSettings/*.asset file").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-s, --setting <name>", "Setting name or alias").option("--property <name>", "Property name to edit").option("--value <value>", "New value").option("-j, --json", "Output as JSON").action((options) => {
|
|
13408
15040
|
if (!options.setting || !options.property || !options.value) {
|
|
13409
15041
|
console.log(JSON.stringify({ success: false, error: "Required: --setting, --property, --value" }, null, 2));
|
|
13410
15042
|
process.exit(1);
|
|
13411
15043
|
}
|
|
15044
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
13412
15045
|
const result = edit_settings({
|
|
13413
|
-
project_path,
|
|
15046
|
+
project_path: resolvedProjectPath,
|
|
13414
15047
|
setting: options.setting,
|
|
13415
15048
|
property: options.property,
|
|
13416
15049
|
value: options.value
|
|
@@ -13419,13 +15052,14 @@ function build_update_command(getScanner) {
|
|
|
13419
15052
|
if (!result.success)
|
|
13420
15053
|
process.exitCode = 1;
|
|
13421
15054
|
});
|
|
13422
|
-
cmd.command("tag <
|
|
15055
|
+
cmd.command("tag <action> <tag>").description("Add or remove a tag in the TagManager").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((action, tag, options) => {
|
|
13423
15056
|
if (action !== "add" && action !== "remove") {
|
|
13424
15057
|
console.log(JSON.stringify({ success: false, error: 'Action must be "add" or "remove"' }, null, 2));
|
|
13425
15058
|
process.exit(1);
|
|
13426
15059
|
}
|
|
15060
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
13427
15061
|
const result = edit_tag({
|
|
13428
|
-
project_path,
|
|
15062
|
+
project_path: resolvedProjectPath,
|
|
13429
15063
|
action,
|
|
13430
15064
|
tag
|
|
13431
15065
|
});
|
|
@@ -13433,9 +15067,10 @@ function build_update_command(getScanner) {
|
|
|
13433
15067
|
if (!result.success)
|
|
13434
15068
|
process.exitCode = 1;
|
|
13435
15069
|
});
|
|
13436
|
-
cmd.command("layer <
|
|
15070
|
+
cmd.command("layer <index> <name>").description("Set a named layer at a specific index (3-31)").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((index, name, options) => {
|
|
15071
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
13437
15072
|
const result = edit_layer({
|
|
13438
|
-
project_path,
|
|
15073
|
+
project_path: resolvedProjectPath,
|
|
13439
15074
|
index: parseInt(index, 10),
|
|
13440
15075
|
name
|
|
13441
15076
|
});
|
|
@@ -13443,13 +15078,14 @@ function build_update_command(getScanner) {
|
|
|
13443
15078
|
if (!result.success)
|
|
13444
15079
|
process.exitCode = 1;
|
|
13445
15080
|
});
|
|
13446
|
-
cmd.command("sorting-layer <
|
|
15081
|
+
cmd.command("sorting-layer <action> <name>").description("Add or remove a sorting layer").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((action, name, options) => {
|
|
13447
15082
|
if (action !== "add" && action !== "remove") {
|
|
13448
15083
|
console.log(JSON.stringify({ success: false, error: 'Action must be "add" or "remove"' }, null, 2));
|
|
13449
15084
|
process.exit(1);
|
|
13450
15085
|
}
|
|
15086
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
13451
15087
|
const result = edit_sorting_layer({
|
|
13452
|
-
project_path,
|
|
15088
|
+
project_path: resolvedProjectPath,
|
|
13453
15089
|
action,
|
|
13454
15090
|
name
|
|
13455
15091
|
});
|
|
@@ -13457,7 +15093,9 @@ function build_update_command(getScanner) {
|
|
|
13457
15093
|
if (!result.success)
|
|
13458
15094
|
process.exitCode = 1;
|
|
13459
15095
|
});
|
|
13460
|
-
cmd.command("parent <file> <object_name> <new_parent>").description('Move a GameObject under a new parent. Use "root" to move to scene root. Use --by-id to specify fileIDs instead of names').option("-j, --json", "Output as JSON").option("--by-id", "Treat object_name and new_parent as numeric fileIDs instead of names").action((file, object_name, new_parent, options) => {
|
|
15096
|
+
cmd.command("parent <file> <object_name> <new_parent>").description('Move a GameObject under a new parent. Use "root" to move to scene root. Use --by-id to specify fileIDs instead of names').option("-j, --json", "Output as JSON").option("--by-id", "Treat object_name and new_parent as numeric fileIDs instead of names").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, object_name, new_parent, options) => {
|
|
15097
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15098
|
+
return;
|
|
13461
15099
|
const result = reparentGameObject({
|
|
13462
15100
|
file_path: file,
|
|
13463
15101
|
object_name,
|
|
@@ -13469,7 +15107,9 @@ function build_update_command(getScanner) {
|
|
|
13469
15107
|
process.exitCode = 1;
|
|
13470
15108
|
});
|
|
13471
15109
|
const prefab_cmd = new Command("prefab").description("Prefab operations (unpack, override, remove-override, remove/restore component/gameobject)");
|
|
13472
|
-
prefab_cmd.command("unpack <file> <prefab_instance>").description("Unpack a PrefabInstance into standalone GameObjects").option("-p, --project <path>", "Unity project path (for GUID cache lookup)").option("-j, --json", "Output as JSON").action((file, prefab_instance, options) => {
|
|
15110
|
+
prefab_cmd.command("unpack <file> <prefab_instance>").description("Unpack a PrefabInstance into standalone GameObjects").option("-p, --project <path>", "Unity project path (for GUID cache lookup)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, options) => {
|
|
15111
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
|
|
15112
|
+
return;
|
|
13473
15113
|
const result = unpackPrefab({
|
|
13474
15114
|
file_path: file,
|
|
13475
15115
|
prefab_instance,
|
|
@@ -13479,7 +15119,9 @@ function build_update_command(getScanner) {
|
|
|
13479
15119
|
if (!result.success)
|
|
13480
15120
|
process.exitCode = 1;
|
|
13481
15121
|
});
|
|
13482
|
-
prefab_cmd.command("override <file> <prefab_instance> <property_path> <value>").description("Edit or add a property override in a PrefabInstance m_Modifications list. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--object-reference <ref>", "Object reference value (default: {fileID: 0})").option("--managed-reference <id>", "Managed reference ID -- placed in value: field, forces objectReference: {fileID: 0}").option("--target <target>", 'Target reference for new entries (e.g., "{fileID: 400000, guid: abc, type: 3}")').option("-j, --json", "Output as JSON").action((file, prefab_instance, property_path, value, options) => {
|
|
15122
|
+
prefab_cmd.command("override <file> <prefab_instance> <property_path> <value>").description("Edit or add a property override in a PrefabInstance m_Modifications list. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--object-reference <ref>", "Object reference value (default: {fileID: 0})").option("--managed-reference <id>", "Managed reference ID -- placed in value: field, forces objectReference: {fileID: 0}").option("--target <target>", 'Target reference for new entries (e.g., "{fileID: 400000, guid: abc, type: 3}")').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, property_path, value, options) => {
|
|
15123
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15124
|
+
return;
|
|
13483
15125
|
if (options.objectReference && !/^\{fileID:/.test(options.objectReference)) {
|
|
13484
15126
|
console.error(`Warning: --object-reference "${options.objectReference}" does not look like a Unity object reference ({fileID: ...}). For managed reference IDs, use --managed-reference instead or pass the ID as the <value> argument.`);
|
|
13485
15127
|
}
|
|
@@ -13496,7 +15138,9 @@ function build_update_command(getScanner) {
|
|
|
13496
15138
|
if (!result.success)
|
|
13497
15139
|
process.exitCode = 1;
|
|
13498
15140
|
});
|
|
13499
|
-
prefab_cmd.command("batch-overrides <file> <prefab_instance> <edits_json>").description('Batch edit multiple property overrides in a PrefabInstance. JSON format: [{"property_path":"...","value":"...","target":"...","object_reference":"..."}]').option("-j, --json", "Output as JSON").action((file, prefab_instance, edits_json,
|
|
15141
|
+
prefab_cmd.command("batch-overrides <file> <prefab_instance> <edits_json>").description('Batch edit multiple property overrides in a PrefabInstance. JSON format: [{"property_path":"...","value":"...","target":"...","object_reference":"..."}]').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, edits_json, options) => {
|
|
15142
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15143
|
+
return;
|
|
13500
15144
|
let raw_edits;
|
|
13501
15145
|
try {
|
|
13502
15146
|
const parsed = JSON.parse(edits_json);
|
|
@@ -13535,7 +15179,9 @@ function build_update_command(getScanner) {
|
|
|
13535
15179
|
if (!result.success)
|
|
13536
15180
|
process.exitCode = 1;
|
|
13537
15181
|
});
|
|
13538
|
-
prefab_cmd.command("managed-reference <file> <prefab_instance> <field_path> <type_name>").description("Add a [SerializeReference] managed reference to a prefab override. Auto-generates rid and creates type/data/version override entries.").requiredOption("--target <ref>", 'Target reference (e.g., "{fileID: 400000, guid: ..., type: 3}")').option("--index <n>", "Array index (default: 0)", "0").option("-p, --project <path>", "Unity project path (for type registry)").option("-j, --json", "Output as JSON").action((file, prefab_instance, field_path, type_name, options) => {
|
|
15182
|
+
prefab_cmd.command("managed-reference <file> <prefab_instance> <field_path> <type_name>").description("Add a [SerializeReference] managed reference to a prefab override. Auto-generates rid and creates type/data/version override entries.").requiredOption("--target <ref>", 'Target reference (e.g., "{fileID: 400000, guid: ..., type: 3}")').option("--index <n>", "Array index (default: 0)", "0").option("-p, --project <path>", "Unity project path (for type registry)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, field_path, type_name, options) => {
|
|
15183
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
|
|
15184
|
+
return;
|
|
13539
15185
|
const result = addPrefabManagedReference({
|
|
13540
15186
|
file_path: file,
|
|
13541
15187
|
prefab_instance,
|
|
@@ -13549,20 +15195,21 @@ function build_update_command(getScanner) {
|
|
|
13549
15195
|
if (!result.success)
|
|
13550
15196
|
process.exitCode = 1;
|
|
13551
15197
|
});
|
|
13552
|
-
cmd.command("build <
|
|
15198
|
+
cmd.command("build <scene_path>").description("Enable, disable, or move a scene in build settings").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--enable", "Enable the scene").option("--disable", "Disable the scene").option("--move <index>", "Move scene to position").option("-j, --json", "Output as JSON").action((scene_path, options) => {
|
|
13553
15199
|
try {
|
|
15200
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
13554
15201
|
if (options.move !== undefined) {
|
|
13555
|
-
const result = move_scene(
|
|
15202
|
+
const result = move_scene(resolvedProjectPath, scene_path, parseInt(options.move, 10));
|
|
13556
15203
|
console.log(JSON.stringify(result, null, 2));
|
|
13557
15204
|
if (!result.success)
|
|
13558
15205
|
process.exitCode = 1;
|
|
13559
15206
|
} else if (options.enable) {
|
|
13560
|
-
const result = enable_scene(
|
|
15207
|
+
const result = enable_scene(resolvedProjectPath, scene_path);
|
|
13561
15208
|
console.log(JSON.stringify(result, null, 2));
|
|
13562
15209
|
if (!result.success)
|
|
13563
15210
|
process.exitCode = 1;
|
|
13564
15211
|
} else if (options.disable) {
|
|
13565
|
-
const result = disable_scene(
|
|
15212
|
+
const result = disable_scene(resolvedProjectPath, scene_path);
|
|
13566
15213
|
console.log(JSON.stringify(result, null, 2));
|
|
13567
15214
|
if (!result.success)
|
|
13568
15215
|
process.exitCode = 1;
|
|
@@ -13575,7 +15222,7 @@ function build_update_command(getScanner) {
|
|
|
13575
15222
|
process.exitCode = 1;
|
|
13576
15223
|
}
|
|
13577
15224
|
});
|
|
13578
|
-
cmd.command("array <file> <file_id> <array_property> <action> [args...]").description("Insert, append, or remove array elements in a component. Insert: <index> <value> or <value> --index <n>. Append: <value>. Remove: <index> or --index <n>. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--index <n>", "Index for insert/remove").option("-j, --json", "Output as JSON").action((file, file_id, array_property, action, args, options) => {
|
|
15225
|
+
cmd.command("array <file> <file_id> <array_property> <action> [args...]").description("Insert, append, or remove array elements in a component. Insert: <index> <value> or <value> --index <n>. Append: <value>. Remove: <index> or --index <n>. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--index <n>", "Index for insert/remove").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, file_id, array_property, action, args, options) => {
|
|
13579
15226
|
if (action !== "insert" && action !== "append" && action !== "remove") {
|
|
13580
15227
|
console.log(JSON.stringify({ success: false, error: 'Action must be "insert", "append", or "remove"' }, null, 2));
|
|
13581
15228
|
process.exit(1);
|
|
@@ -13616,6 +15263,8 @@ function build_update_command(getScanner) {
|
|
|
13616
15263
|
}
|
|
13617
15264
|
}
|
|
13618
15265
|
}
|
|
15266
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15267
|
+
return;
|
|
13619
15268
|
const result = editArray({
|
|
13620
15269
|
file_path: file,
|
|
13621
15270
|
file_id,
|
|
@@ -13628,7 +15277,9 @@ function build_update_command(getScanner) {
|
|
|
13628
15277
|
if (!result.success)
|
|
13629
15278
|
process.exitCode = 1;
|
|
13630
15279
|
});
|
|
13631
|
-
cmd.command("batch <file> <edits_json>").description('Batch edit multiple GameObject properties in a single file operation. JSON format: [{"object_name":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").action((file, edits_json,
|
|
15280
|
+
cmd.command("batch <file> <edits_json>").description('Batch edit multiple GameObject properties in a single file operation. JSON format: [{"object_name":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, edits_json, options) => {
|
|
15281
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15282
|
+
return;
|
|
13632
15283
|
let raw_edits;
|
|
13633
15284
|
try {
|
|
13634
15285
|
const parsed = JSON.parse(edits_json);
|
|
@@ -13669,7 +15320,9 @@ function build_update_command(getScanner) {
|
|
|
13669
15320
|
if (!result.success)
|
|
13670
15321
|
process.exitCode = 1;
|
|
13671
15322
|
});
|
|
13672
|
-
cmd.command("batch-components <file> <edits_json>").description('Batch edit multiple component properties by fileID in a single operation. JSON format: [{"file_id":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").action((file, edits_json,
|
|
15323
|
+
cmd.command("batch-components <file> <edits_json>").description('Batch edit multiple component properties by fileID in a single operation. JSON format: [{"file_id":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, edits_json, options) => {
|
|
15324
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15325
|
+
return;
|
|
13673
15326
|
let raw_edits;
|
|
13674
15327
|
try {
|
|
13675
15328
|
const parsed = JSON.parse(edits_json);
|
|
@@ -13710,7 +15363,9 @@ function build_update_command(getScanner) {
|
|
|
13710
15363
|
if (!result.success)
|
|
13711
15364
|
process.exitCode = 1;
|
|
13712
15365
|
});
|
|
13713
|
-
prefab_cmd.command("remove-override <file> <prefab_instance> <property_path>").description("Remove a property override from a PrefabInstance. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--target <ref>", "Target reference to match (for disambiguation)").option("-j, --json", "Output as JSON").action((file, prefab_instance, property_path, options) => {
|
|
15366
|
+
prefab_cmd.command("remove-override <file> <prefab_instance> <property_path>").description("Remove a property override from a PrefabInstance. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--target <ref>", "Target reference to match (for disambiguation)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, property_path, options) => {
|
|
15367
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15368
|
+
return;
|
|
13714
15369
|
const result = removePrefabOverride({
|
|
13715
15370
|
file_path: file,
|
|
13716
15371
|
prefab_instance,
|
|
@@ -13721,7 +15376,9 @@ function build_update_command(getScanner) {
|
|
|
13721
15376
|
if (!result.success)
|
|
13722
15377
|
process.exitCode = 1;
|
|
13723
15378
|
});
|
|
13724
|
-
prefab_cmd.command("remove-component <file> <prefab_instance> <component_ref>").description("Add a component to the PrefabInstance m_RemovedComponents list").option("-j, --json", "Output as JSON").action((file, prefab_instance, component_ref,
|
|
15379
|
+
prefab_cmd.command("remove-component <file> <prefab_instance> <component_ref>").description("Add a component to the PrefabInstance m_RemovedComponents list").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, component_ref, options) => {
|
|
15380
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15381
|
+
return;
|
|
13725
15382
|
const result = addRemovedComponent({
|
|
13726
15383
|
file_path: file,
|
|
13727
15384
|
prefab_instance,
|
|
@@ -13731,7 +15388,9 @@ function build_update_command(getScanner) {
|
|
|
13731
15388
|
if (!result.success)
|
|
13732
15389
|
process.exitCode = 1;
|
|
13733
15390
|
});
|
|
13734
|
-
prefab_cmd.command("restore-component <file> <prefab_instance> <component_ref>").description("Remove a component from the PrefabInstance m_RemovedComponents list (restore it)").option("-j, --json", "Output as JSON").action((file, prefab_instance, component_ref,
|
|
15391
|
+
prefab_cmd.command("restore-component <file> <prefab_instance> <component_ref>").description("Remove a component from the PrefabInstance m_RemovedComponents list (restore it)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, component_ref, options) => {
|
|
15392
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15393
|
+
return;
|
|
13735
15394
|
const result = removeRemovedComponent({
|
|
13736
15395
|
file_path: file,
|
|
13737
15396
|
prefab_instance,
|
|
@@ -13741,7 +15400,9 @@ function build_update_command(getScanner) {
|
|
|
13741
15400
|
if (!result.success)
|
|
13742
15401
|
process.exitCode = 1;
|
|
13743
15402
|
});
|
|
13744
|
-
prefab_cmd.command("remove-gameobject <file> <prefab_instance> <gameobject_ref>").description("Add a GameObject to the PrefabInstance m_RemovedGameObjects list").option("-j, --json", "Output as JSON").action((file, prefab_instance, gameobject_ref,
|
|
15403
|
+
prefab_cmd.command("remove-gameobject <file> <prefab_instance> <gameobject_ref>").description("Add a GameObject to the PrefabInstance m_RemovedGameObjects list").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, gameobject_ref, options) => {
|
|
15404
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15405
|
+
return;
|
|
13745
15406
|
const result = addRemovedGameObject({
|
|
13746
15407
|
file_path: file,
|
|
13747
15408
|
prefab_instance,
|
|
@@ -13751,7 +15412,9 @@ function build_update_command(getScanner) {
|
|
|
13751
15412
|
if (!result.success)
|
|
13752
15413
|
process.exitCode = 1;
|
|
13753
15414
|
});
|
|
13754
|
-
prefab_cmd.command("restore-gameobject <file> <prefab_instance> <gameobject_ref>").description("Remove a GameObject from the PrefabInstance m_RemovedGameObjects list (restore it)").option("-j, --json", "Output as JSON").action((file, prefab_instance, gameobject_ref,
|
|
15415
|
+
prefab_cmd.command("restore-gameobject <file> <prefab_instance> <gameobject_ref>").description("Remove a GameObject from the PrefabInstance m_RemovedGameObjects list (restore it)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, gameobject_ref, options) => {
|
|
15416
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
15417
|
+
return;
|
|
13755
15418
|
const result = removeRemovedGameObject({
|
|
13756
15419
|
file_path: file,
|
|
13757
15420
|
prefab_instance,
|
|
@@ -13771,11 +15434,11 @@ function build_update_command(getScanner) {
|
|
|
13771
15434
|
}
|
|
13772
15435
|
}
|
|
13773
15436
|
}).action((file, options) => {
|
|
13774
|
-
if (!
|
|
15437
|
+
if (!import_fs20.existsSync(file)) {
|
|
13775
15438
|
console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
|
|
13776
15439
|
process.exit(1);
|
|
13777
15440
|
}
|
|
13778
|
-
let content =
|
|
15441
|
+
let content = import_fs20.readFileSync(file, "utf-8");
|
|
13779
15442
|
const changes = [];
|
|
13780
15443
|
if (options.shader) {
|
|
13781
15444
|
const guid = options.shader;
|
|
@@ -13924,7 +15587,7 @@ ${lm[1]} - ${kw}
|
|
|
13924
15587
|
process.exitCode = 1;
|
|
13925
15588
|
return;
|
|
13926
15589
|
}
|
|
13927
|
-
|
|
15590
|
+
import_fs20.writeFileSync(file, content, "utf-8");
|
|
13928
15591
|
console.log(JSON.stringify({ success: true, file, changes }, null, 2));
|
|
13929
15592
|
});
|
|
13930
15593
|
cmd.command("meta [file]").description("Edit Unity .meta file importer settings").option("--set <key=value>", "Set an importer setting (e.g., isReadable=1)", (v, p) => [...p, v], []).option("--max-size <n>", "Set TextureImporter maxTextureSize").option("--compression <type>", "Set textureCompression (0=None, 1=LowQuality, 2=Normal, 3=HighQuality)").option("--filter-mode <mode>", "Set filterMode (0=Point, 1=Bilinear, 2=Trilinear)").option("--read-write", "Enable isReadable").option("--no-read-write", "Disable isReadable").option("--batch <glob>", "Apply to all matching files").option("--dry-run", "Preview changes without writing").option("-j, --json", "Output as JSON").action((file, options) => {
|
|
@@ -13980,14 +15643,14 @@ ${lm[1]} - ${kw}
|
|
|
13980
15643
|
const glob_pattern = options.batch;
|
|
13981
15644
|
const pattern = glob_pattern.endsWith(".meta") ? glob_pattern : `${glob_pattern}.meta`;
|
|
13982
15645
|
const first_glob = pattern.search(/[*?[\]{}]/);
|
|
13983
|
-
const dir_part = first_glob >= 0 ? pattern.slice(0, pattern.lastIndexOf("/", first_glob)) :
|
|
13984
|
-
const dir =
|
|
13985
|
-
const suffix =
|
|
15646
|
+
const dir_part = first_glob >= 0 ? pattern.slice(0, pattern.lastIndexOf("/", first_glob)) : import_path12.dirname(pattern);
|
|
15647
|
+
const dir = import_path12.resolve(dir_part);
|
|
15648
|
+
const suffix = import_path12.basename(pattern).replace(/^\*+/, "");
|
|
13986
15649
|
const meta_files = [];
|
|
13987
15650
|
const scan = (d) => {
|
|
13988
|
-
for (const entry of
|
|
13989
|
-
const full =
|
|
13990
|
-
if (
|
|
15651
|
+
for (const entry of import_fs20.readdirSync(d)) {
|
|
15652
|
+
const full = import_path12.join(d, entry);
|
|
15653
|
+
if (import_fs20.statSync(full).isDirectory()) {
|
|
13991
15654
|
if (pattern.includes("**"))
|
|
13992
15655
|
scan(full);
|
|
13993
15656
|
} else if (entry.endsWith(suffix)) {
|
|
@@ -13995,7 +15658,7 @@ ${lm[1]} - ${kw}
|
|
|
13995
15658
|
}
|
|
13996
15659
|
}
|
|
13997
15660
|
};
|
|
13998
|
-
if (
|
|
15661
|
+
if (import_fs20.existsSync(dir))
|
|
13999
15662
|
scan(dir);
|
|
14000
15663
|
if (meta_files.length === 0) {
|
|
14001
15664
|
console.log(JSON.stringify({ success: false, error: `No files matched pattern: ${glob_pattern}` }, null, 2));
|
|
@@ -14003,7 +15666,7 @@ ${lm[1]} - ${kw}
|
|
|
14003
15666
|
}
|
|
14004
15667
|
const results = [];
|
|
14005
15668
|
for (const mf of meta_files) {
|
|
14006
|
-
let mc =
|
|
15669
|
+
let mc = import_fs20.readFileSync(mf, "utf-8");
|
|
14007
15670
|
const file_changes = [];
|
|
14008
15671
|
for (const edit of edits) {
|
|
14009
15672
|
const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
|
|
@@ -14014,7 +15677,7 @@ ${lm[1]} - ${kw}
|
|
|
14014
15677
|
}
|
|
14015
15678
|
if (file_changes.length > 0) {
|
|
14016
15679
|
if (!options.dryRun)
|
|
14017
|
-
|
|
15680
|
+
import_fs20.writeFileSync(mf, mc, "utf-8");
|
|
14018
15681
|
results.push({ file: mf, changes: file_changes });
|
|
14019
15682
|
}
|
|
14020
15683
|
}
|
|
@@ -14027,11 +15690,11 @@ ${lm[1]} - ${kw}
|
|
|
14027
15690
|
}, null, 2));
|
|
14028
15691
|
return;
|
|
14029
15692
|
}
|
|
14030
|
-
if (!
|
|
15693
|
+
if (!import_fs20.existsSync(metaPath)) {
|
|
14031
15694
|
console.log(JSON.stringify({ success: false, error: `Meta file not found: ${metaPath}` }, null, 2));
|
|
14032
15695
|
process.exit(1);
|
|
14033
15696
|
}
|
|
14034
|
-
let content =
|
|
15697
|
+
let content = import_fs20.readFileSync(metaPath, "utf-8");
|
|
14035
15698
|
const changes = [];
|
|
14036
15699
|
for (const edit of edits) {
|
|
14037
15700
|
const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
|
|
@@ -14046,11 +15709,11 @@ ${lm[1]} - ${kw}
|
|
|
14046
15709
|
console.log(JSON.stringify({ success: true, dry_run: true, file: metaPath, changes }, null, 2));
|
|
14047
15710
|
return;
|
|
14048
15711
|
}
|
|
14049
|
-
|
|
15712
|
+
import_fs20.writeFileSync(metaPath, content, "utf-8");
|
|
14050
15713
|
console.log(JSON.stringify({ success: true, file: metaPath, changes }, null, 2));
|
|
14051
15714
|
});
|
|
14052
15715
|
cmd.command("animation <file>").description("Edit AnimationClip settings and events").option("--set <property=value>", "Set a clip property (e.g., wrap-mode=2 for Loop)", (v, p) => [...p, v], []).option("--add-event <time,function[,data]>", "Add an animation event (e.g., 0.5,OnFootstep,left)", (v, p) => [...p, v], []).option("--remove-event <index>", "Remove an animation event by index (0-based)").option("-j, --json", "Output as JSON").action((file, options) => {
|
|
14053
|
-
if (!
|
|
15716
|
+
if (!import_fs20.existsSync(file)) {
|
|
14054
15717
|
console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
|
|
14055
15718
|
process.exit(1);
|
|
14056
15719
|
}
|
|
@@ -14058,7 +15721,7 @@ ${lm[1]} - ${kw}
|
|
|
14058
15721
|
console.log(JSON.stringify({ success: false, error: `File is not an AnimationClip (.anim): ${file}` }, null, 2));
|
|
14059
15722
|
process.exit(1);
|
|
14060
15723
|
}
|
|
14061
|
-
let content =
|
|
15724
|
+
let content = import_fs20.readFileSync(file, "utf-8");
|
|
14062
15725
|
const changes = [];
|
|
14063
15726
|
const property_map = {
|
|
14064
15727
|
"wrap-mode": "m_WrapMode",
|
|
@@ -14219,16 +15882,16 @@ ${event_yaml}
|
|
|
14219
15882
|
process.exitCode = 1;
|
|
14220
15883
|
return;
|
|
14221
15884
|
}
|
|
14222
|
-
|
|
15885
|
+
import_fs20.writeFileSync(file, content, "utf-8");
|
|
14223
15886
|
console.log(JSON.stringify({ success: true, file, changes }, null, 2));
|
|
14224
15887
|
});
|
|
14225
15888
|
const PARAM_TYPE_MAP = { float: 1, int: 3, bool: 4, trigger: 9 };
|
|
14226
15889
|
cmd.command("animator <file>").description("Edit AnimatorController parameters").option("--add-parameter <name>", "Add a new parameter").option("--type <float|int|bool|trigger>", "Parameter type (required with --add-parameter)").option("--remove-parameter <name>", "Remove a parameter by name").option("--set-default <param=value>", "Set parameter default value (e.g., Speed=1.5)", (v, p) => [...p, v], []).option("-j, --json", "Output as JSON").action((file, options) => {
|
|
14227
|
-
if (!
|
|
15890
|
+
if (!import_fs20.existsSync(file)) {
|
|
14228
15891
|
console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
|
|
14229
15892
|
process.exit(1);
|
|
14230
15893
|
}
|
|
14231
|
-
let content =
|
|
15894
|
+
let content = import_fs20.readFileSync(file, "utf-8");
|
|
14232
15895
|
const had_crlf = content.includes(`\r
|
|
14233
15896
|
`);
|
|
14234
15897
|
if (had_crlf)
|
|
@@ -14330,17 +15993,19 @@ $1`);
|
|
|
14330
15993
|
if (had_crlf)
|
|
14331
15994
|
content = content.replace(/\n/g, `\r
|
|
14332
15995
|
`);
|
|
14333
|
-
|
|
15996
|
+
import_fs20.writeFileSync(file, content, "utf-8");
|
|
14334
15997
|
console.log(JSON.stringify({ success: true, file, changes }, null, 2));
|
|
14335
15998
|
});
|
|
14336
|
-
cmd.command("sibling-index <file> <object_name> <index>").description("Set the sibling index of a GameObject, renumbering all siblings").option("-j, --json", "Output as JSON").action((file, object_name, index_str,
|
|
15999
|
+
cmd.command("sibling-index <file> <object_name> <index>").description("Set the sibling index of a GameObject, renumbering all siblings").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, object_name, index_str, options) => {
|
|
14337
16000
|
const target_index = parseInt(index_str, 10);
|
|
14338
16001
|
if (!Number.isFinite(target_index) || target_index < 0) {
|
|
14339
16002
|
console.log(JSON.stringify({ success: false, error: "Index must be a non-negative integer" }, null, 2));
|
|
14340
16003
|
process.exitCode = 1;
|
|
14341
16004
|
return;
|
|
14342
16005
|
}
|
|
14343
|
-
if (!
|
|
16006
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
|
|
16007
|
+
return;
|
|
16008
|
+
if (!import_fs20.existsSync(file)) {
|
|
14344
16009
|
console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
|
|
14345
16010
|
process.exitCode = 1;
|
|
14346
16011
|
return;
|
|
@@ -14535,7 +16200,7 @@ $1`);
|
|
|
14535
16200
|
console.log(JSON.stringify({ success: true, file, changes }, null, 2));
|
|
14536
16201
|
});
|
|
14537
16202
|
cmd.command("animation-curves <file>").description("Add, remove, or modify animation curves in an .anim file").option("--add-curve <json>", 'Add a curve (JSON: {"type":"float","path":"Body","attribute":"m_Alpha","classID":23,"keyframes":[{"time":0,"value":1}]})').option("--remove-curve <spec>", "Remove a curve by path:attribute (e.g., Body/Mesh:m_Alpha)").option("--set-keyframes <json>", 'Replace keyframes (JSON: {"curve":"path:attribute","keyframes":[{"time":0,"value":10}]})').option("-j, --json", "Output as JSON").action((file, options) => {
|
|
14538
|
-
if (!
|
|
16203
|
+
if (!import_fs20.existsSync(file)) {
|
|
14539
16204
|
console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
|
|
14540
16205
|
process.exitCode = 1;
|
|
14541
16206
|
return;
|
|
@@ -14545,7 +16210,7 @@ $1`);
|
|
|
14545
16210
|
process.exitCode = 1;
|
|
14546
16211
|
return;
|
|
14547
16212
|
}
|
|
14548
|
-
let content =
|
|
16213
|
+
let content = import_fs20.readFileSync(file, "utf-8");
|
|
14549
16214
|
const had_crlf = content.includes(`\r
|
|
14550
16215
|
`);
|
|
14551
16216
|
if (had_crlf)
|
|
@@ -14843,16 +16508,16 @@ ${curve_yaml}`);
|
|
|
14843
16508
|
if (had_crlf)
|
|
14844
16509
|
content = content.replace(/\n/g, `\r
|
|
14845
16510
|
`);
|
|
14846
|
-
|
|
16511
|
+
import_fs20.writeFileSync(file, content, "utf-8");
|
|
14847
16512
|
console.log(JSON.stringify({ success: true, file, changes }, null, 2));
|
|
14848
16513
|
});
|
|
14849
16514
|
cmd.command("animator-state <file>").description("Add/remove states and transitions in an AnimatorController").option("--add-state <name>", "Add a new AnimatorState").option("--motion <guid-or-path>", "Motion clip GUID or file path (companion to --add-state)").option("--layer <name>", "Target layer name (default: first layer)").option("--speed <n>", "State speed (companion to --add-state, default: 1)").option("--remove-state <name>", "Remove a state and all its transitions").option("--add-transition <src:dst>", 'Add a transition (use "any" for AnyState source)').option("--condition <param,mode,threshold>", "Transition condition (repeatable)", (v, p) => [...p, v], []).option("--has-exit-time", "Enable exit time on transition").option("--exit-time <n>", "Exit time value (default: 0.75)").option("--duration <n>", "Transition duration (default: 0.25)").option("--remove-transition <src:dst>", "Remove a transition by source:destination state names").option("--set-default-state <name>", "Set the default state in the state machine").option("-j, --json", "Output as JSON").action((file, options) => {
|
|
14850
|
-
if (!
|
|
16515
|
+
if (!import_fs20.existsSync(file)) {
|
|
14851
16516
|
console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
|
|
14852
16517
|
process.exitCode = 1;
|
|
14853
16518
|
return;
|
|
14854
16519
|
}
|
|
14855
|
-
let content =
|
|
16520
|
+
let content = import_fs20.readFileSync(file, "utf-8");
|
|
14856
16521
|
const had_crlf = content.includes(`\r
|
|
14857
16522
|
`);
|
|
14858
16523
|
if (had_crlf)
|
|
@@ -15191,11 +16856,11 @@ $1`);
|
|
|
15191
16856
|
if (had_crlf)
|
|
15192
16857
|
content = content.replace(/\n/g, `\r
|
|
15193
16858
|
`);
|
|
15194
|
-
|
|
16859
|
+
import_fs20.writeFileSync(file, content, "utf-8");
|
|
15195
16860
|
console.log(JSON.stringify({ success: true, file, changes }, null, 2));
|
|
15196
16861
|
});
|
|
15197
16862
|
cmd.addCommand(prefab_cmd);
|
|
15198
|
-
cmd.command("managed-reference <file> <component_id> <field_path> <type_name>").description('Add a managed reference (SerializeReference) to a component field. Type can be "Namespace.ClassName" (registry lookup) or "Assembly Namespace.ClassName" (manual).').option("-p, --project <path>", "Unity project path (for type registry)").option("--append", "Append to array (do not update field rid)").option("--properties <json>", `JSON object of initial field values for the data block (e.g. '{"damage": "10"}')`).option("-j, --json", "Output as JSON").action((file, component_id, field_path, type_name, options) => {
|
|
16863
|
+
cmd.command("managed-reference <file> <component_id> <field_path> <type_name>").description('Add a managed reference (SerializeReference) to a component field. Type can be "Namespace.ClassName" (registry lookup) or "Assembly Namespace.ClassName" (manual).').option("-p, --project <path>", "Unity project path (for type registry)").option("--append", "Append to array (do not update field rid)").option("--properties <json>", `JSON object of initial field values for the data block (e.g. '{"damage": "10"}')`).option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, component_id, field_path, type_name, options) => {
|
|
15199
16864
|
let initial_values;
|
|
15200
16865
|
if (options.properties) {
|
|
15201
16866
|
try {
|
|
@@ -15206,6 +16871,8 @@ $1`);
|
|
|
15206
16871
|
return;
|
|
15207
16872
|
}
|
|
15208
16873
|
}
|
|
16874
|
+
if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
|
|
16875
|
+
return;
|
|
15209
16876
|
const result = editManagedReference({
|
|
15210
16877
|
file_path: file,
|
|
15211
16878
|
file_id: component_id,
|
|
@@ -15225,7 +16892,13 @@ $1`);
|
|
|
15225
16892
|
// src/cmd-delete.ts
|
|
15226
16893
|
function build_delete_command() {
|
|
15227
16894
|
const cmd = new Command("delete").description("Delete Unity objects (GameObjects, components)");
|
|
15228
|
-
cmd.command("gameobject <file> <object_name>").description("Delete a GameObject and its hierarchy from a Unity file").option("-j, --json", "Output as JSON").action((file, object_name,
|
|
16895
|
+
cmd.command("gameobject <file> <object_name>").description("Delete a GameObject and its hierarchy from a Unity file").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, object_name, options) => {
|
|
16896
|
+
const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
|
|
16897
|
+
if (!guard.allowed) {
|
|
16898
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
16899
|
+
process.exitCode = 1;
|
|
16900
|
+
return;
|
|
16901
|
+
}
|
|
15229
16902
|
const result = deleteGameObject({
|
|
15230
16903
|
file_path: file,
|
|
15231
16904
|
object_name
|
|
@@ -15234,10 +16907,22 @@ function build_delete_command() {
|
|
|
15234
16907
|
if (!result.success)
|
|
15235
16908
|
process.exitCode = 1;
|
|
15236
16909
|
});
|
|
15237
|
-
cmd.command("component <file_or_project> <component>").description('Remove a component by file ID or type name (e.g., "Rigidbody", "StandaloneInputModule")').option("--on <game_object>", "Scope to a specific GameObject (name or fileID)").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("--all", "Remove from all scenes and prefabs in the project (first arg becomes project path)").option("-j, --json", "Output as JSON").action((file_or_project, component, options) => {
|
|
16910
|
+
cmd.command("component <file_or_project> <component>").description('Remove a component by file ID or type name (e.g., "Rigidbody", "StandaloneInputModule")').option("--on <game_object>", "Scope to a specific GameObject (name or fileID)").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("--all", "Remove from all scenes and prefabs in the project (first arg becomes project path)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file_or_project, component, options) => {
|
|
15238
16911
|
if (options.all) {
|
|
16912
|
+
const resolvedProjectPath = resolve_project_path(file_or_project);
|
|
16913
|
+
if (!options.bypassLoadedProtection) {
|
|
16914
|
+
const connected = await is_editor_connected_for_project(resolvedProjectPath);
|
|
16915
|
+
if (connected) {
|
|
16916
|
+
console.log(JSON.stringify({
|
|
16917
|
+
success: false,
|
|
16918
|
+
error: "Refusing to run project-wide delete while editor is connected. Use --bypass-loaded-protection to force file-based edits."
|
|
16919
|
+
}, null, 2));
|
|
16920
|
+
process.exitCode = 1;
|
|
16921
|
+
return;
|
|
16922
|
+
}
|
|
16923
|
+
}
|
|
15239
16924
|
const result = removeComponentBatch({
|
|
15240
|
-
project_path:
|
|
16925
|
+
project_path: resolvedProjectPath,
|
|
15241
16926
|
component_type: component,
|
|
15242
16927
|
game_object: options.on
|
|
15243
16928
|
});
|
|
@@ -15245,6 +16930,12 @@ function build_delete_command() {
|
|
|
15245
16930
|
if (!result.success)
|
|
15246
16931
|
process.exitCode = 1;
|
|
15247
16932
|
} else {
|
|
16933
|
+
const guard = await enforce_loaded_edit_protection(file_or_project, options.bypassLoadedProtection, options.project);
|
|
16934
|
+
if (!guard.allowed) {
|
|
16935
|
+
console.log(JSON.stringify({ success: false, file_path: file_or_project, error: guard.error }, null, 2));
|
|
16936
|
+
process.exitCode = 1;
|
|
16937
|
+
return;
|
|
16938
|
+
}
|
|
15248
16939
|
const result = removeComponent({
|
|
15249
16940
|
file_path: file_or_project,
|
|
15250
16941
|
file_id: component,
|
|
@@ -15256,9 +16947,10 @@ function build_delete_command() {
|
|
|
15256
16947
|
process.exitCode = 1;
|
|
15257
16948
|
}
|
|
15258
16949
|
});
|
|
15259
|
-
cmd.command("build <
|
|
16950
|
+
cmd.command("build <scene_path>").description("Remove a scene from build settings").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((scene_path, options) => {
|
|
16951
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
15260
16952
|
try {
|
|
15261
|
-
const result = remove_scene(
|
|
16953
|
+
const result = remove_scene(resolvedProjectPath, scene_path);
|
|
15262
16954
|
console.log(JSON.stringify(result, null, 2));
|
|
15263
16955
|
if (!result.success)
|
|
15264
16956
|
process.exitCode = 1;
|
|
@@ -15267,7 +16959,13 @@ function build_delete_command() {
|
|
|
15267
16959
|
process.exitCode = 1;
|
|
15268
16960
|
}
|
|
15269
16961
|
});
|
|
15270
|
-
cmd.command("prefab <file> <prefab_instance>").description("Delete a PrefabInstance and all its stripped/added blocks from a Unity file").option("-j, --json", "Output as JSON").action((file, prefab_instance,
|
|
16962
|
+
cmd.command("prefab <file> <prefab_instance>").description("Delete a PrefabInstance and all its stripped/added blocks from a Unity file").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, prefab_instance, options) => {
|
|
16963
|
+
const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
|
|
16964
|
+
if (!guard.allowed) {
|
|
16965
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
16966
|
+
process.exitCode = 1;
|
|
16967
|
+
return;
|
|
16968
|
+
}
|
|
15271
16969
|
const result = deletePrefabInstance({
|
|
15272
16970
|
file_path: file,
|
|
15273
16971
|
prefab_instance
|
|
@@ -15276,9 +16974,22 @@ function build_delete_command() {
|
|
|
15276
16974
|
if (!result.success)
|
|
15277
16975
|
process.exitCode = 1;
|
|
15278
16976
|
});
|
|
15279
|
-
cmd.command("
|
|
16977
|
+
cmd.command("asset <file>").description("Delete an asset file and its .meta sidecar (missing .meta => warning)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, options) => {
|
|
16978
|
+
const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
|
|
16979
|
+
if (!guard.allowed) {
|
|
16980
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
16981
|
+
process.exitCode = 1;
|
|
16982
|
+
return;
|
|
16983
|
+
}
|
|
16984
|
+
const result = deleteAssetFile({ file_path: file });
|
|
16985
|
+
console.log(JSON.stringify(result, null, 2));
|
|
16986
|
+
if (!result.success)
|
|
16987
|
+
process.exitCode = 1;
|
|
16988
|
+
});
|
|
16989
|
+
cmd.command("package <name>").description("Remove a package from Packages/manifest.json").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((name, options) => {
|
|
15280
16990
|
try {
|
|
15281
|
-
const
|
|
16991
|
+
const resolvedProjectPath = resolve_project_path(options.project);
|
|
16992
|
+
const result = remove_package(resolvedProjectPath, name);
|
|
15282
16993
|
if ("error" in result) {
|
|
15283
16994
|
console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
|
|
15284
16995
|
process.exitCode = 1;
|
|
@@ -15294,278 +17005,11 @@ function build_delete_command() {
|
|
|
15294
17005
|
}
|
|
15295
17006
|
|
|
15296
17007
|
// src/cmd-editor.ts
|
|
15297
|
-
var
|
|
15298
|
-
|
|
15299
|
-
// src/editor-client.ts
|
|
15300
|
-
var import_fs20 = require("fs");
|
|
15301
|
-
var import_path10 = require("path");
|
|
15302
|
-
function read_editor_config(project_path) {
|
|
15303
|
-
const config_path = import_path10.join(project_path, ".unity-agentic", "editor.json");
|
|
15304
|
-
if (!import_fs20.existsSync(config_path)) {
|
|
15305
|
-
return { error: `Editor bridge not found at ${config_path}. Is the Unity Editor running with the bridge package installed?` };
|
|
15306
|
-
}
|
|
15307
|
-
let config;
|
|
15308
|
-
try {
|
|
15309
|
-
const raw = import_fs20.readFileSync(config_path, "utf-8");
|
|
15310
|
-
config = JSON.parse(raw);
|
|
15311
|
-
} catch (err) {
|
|
15312
|
-
return { error: `Failed to parse editor.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
15313
|
-
}
|
|
15314
|
-
if (typeof config.port !== "number" || typeof config.pid !== "number") {
|
|
15315
|
-
return { error: "Invalid editor.json: missing port or pid" };
|
|
15316
|
-
}
|
|
15317
|
-
if (!is_pid_alive(config.pid)) {
|
|
15318
|
-
return { error: `Unity Editor process (PID ${config.pid}) is not running. The editor may have been closed.` };
|
|
15319
|
-
}
|
|
15320
|
-
return config;
|
|
15321
|
-
}
|
|
15322
|
-
var RETRYABLE_CODES = new Set([-32000, -32002, -32003]);
|
|
15323
|
-
var RETRY_DELAYS = [500, 1000, 2000];
|
|
15324
|
-
async function call_editor(options) {
|
|
15325
|
-
const maxRetries = options.retries ?? 2;
|
|
15326
|
-
let lastResponse;
|
|
15327
|
-
for (let attempt = 0;attempt <= maxRetries; attempt++) {
|
|
15328
|
-
lastResponse = await call_editor_once(options);
|
|
15329
|
-
if (!lastResponse.error || !RETRYABLE_CODES.has(lastResponse.error.code)) {
|
|
15330
|
-
return lastResponse;
|
|
15331
|
-
}
|
|
15332
|
-
if (attempt < maxRetries) {
|
|
15333
|
-
const delay = RETRY_DELAYS[Math.min(attempt, RETRY_DELAYS.length - 1)];
|
|
15334
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
15335
|
-
}
|
|
15336
|
-
}
|
|
15337
|
-
return lastResponse;
|
|
15338
|
-
}
|
|
15339
|
-
function call_editor_once(options) {
|
|
15340
|
-
const { method, params, timeout = 1e4, no_wait } = options;
|
|
15341
|
-
const config = resolve_config(options);
|
|
15342
|
-
if ("error" in config) {
|
|
15343
|
-
return Promise.resolve({
|
|
15344
|
-
jsonrpc: "2.0",
|
|
15345
|
-
id: "0",
|
|
15346
|
-
error: { code: -32000, message: config.error }
|
|
15347
|
-
});
|
|
15348
|
-
}
|
|
15349
|
-
const url = `ws://127.0.0.1:${config.port}/unity-agentic`;
|
|
15350
|
-
const request_id = generate_id();
|
|
15351
|
-
const wire_params = { ...params };
|
|
15352
|
-
if (timeout !== 1e4)
|
|
15353
|
-
wire_params._timeout = timeout;
|
|
15354
|
-
if (no_wait)
|
|
15355
|
-
wire_params.no_wait = true;
|
|
15356
|
-
const request = {
|
|
15357
|
-
jsonrpc: "2.0",
|
|
15358
|
-
id: request_id,
|
|
15359
|
-
method,
|
|
15360
|
-
...Object.keys(wire_params).length > 0 ? { params: wire_params } : {}
|
|
15361
|
-
};
|
|
15362
|
-
return new Promise((resolve7) => {
|
|
15363
|
-
let resolved = false;
|
|
15364
|
-
let ws;
|
|
15365
|
-
const timer = setTimeout(() => {
|
|
15366
|
-
if (!resolved) {
|
|
15367
|
-
resolved = true;
|
|
15368
|
-
try {
|
|
15369
|
-
ws?.close();
|
|
15370
|
-
} catch {}
|
|
15371
|
-
resolve7({
|
|
15372
|
-
jsonrpc: "2.0",
|
|
15373
|
-
id: request_id,
|
|
15374
|
-
error: { code: -32001, message: `Timeout after ${timeout}ms waiting for response to ${method}` }
|
|
15375
|
-
});
|
|
15376
|
-
}
|
|
15377
|
-
}, timeout);
|
|
15378
|
-
try {
|
|
15379
|
-
ws = new WebSocket(url);
|
|
15380
|
-
ws.onopen = () => {
|
|
15381
|
-
ws.send(JSON.stringify(request));
|
|
15382
|
-
if (no_wait && !resolved) {
|
|
15383
|
-
resolved = true;
|
|
15384
|
-
clearTimeout(timer);
|
|
15385
|
-
setTimeout(() => {
|
|
15386
|
-
try {
|
|
15387
|
-
ws.close();
|
|
15388
|
-
} catch {}
|
|
15389
|
-
}, 200);
|
|
15390
|
-
resolve7({
|
|
15391
|
-
jsonrpc: "2.0",
|
|
15392
|
-
id: request_id,
|
|
15393
|
-
result: { queued: true }
|
|
15394
|
-
});
|
|
15395
|
-
}
|
|
15396
|
-
};
|
|
15397
|
-
ws.onmessage = (event) => {
|
|
15398
|
-
try {
|
|
15399
|
-
const data = JSON.parse(String(event.data));
|
|
15400
|
-
if (data.id === request_id) {
|
|
15401
|
-
if (!resolved) {
|
|
15402
|
-
resolved = true;
|
|
15403
|
-
clearTimeout(timer);
|
|
15404
|
-
ws.close();
|
|
15405
|
-
resolve7(data);
|
|
15406
|
-
}
|
|
15407
|
-
}
|
|
15408
|
-
} catch {}
|
|
15409
|
-
};
|
|
15410
|
-
ws.onerror = () => {
|
|
15411
|
-
if (!resolved) {
|
|
15412
|
-
resolved = true;
|
|
15413
|
-
clearTimeout(timer);
|
|
15414
|
-
resolve7({
|
|
15415
|
-
jsonrpc: "2.0",
|
|
15416
|
-
id: request_id,
|
|
15417
|
-
error: { code: -32002, message: `WebSocket connection failed to ${url}. Is the Unity Editor running?` }
|
|
15418
|
-
});
|
|
15419
|
-
}
|
|
15420
|
-
};
|
|
15421
|
-
ws.onclose = () => {
|
|
15422
|
-
if (!resolved) {
|
|
15423
|
-
resolved = true;
|
|
15424
|
-
clearTimeout(timer);
|
|
15425
|
-
resolve7({
|
|
15426
|
-
jsonrpc: "2.0",
|
|
15427
|
-
id: request_id,
|
|
15428
|
-
error: { code: -32003, message: "WebSocket connection closed before response received" }
|
|
15429
|
-
});
|
|
15430
|
-
}
|
|
15431
|
-
};
|
|
15432
|
-
} catch (err) {
|
|
15433
|
-
if (!resolved) {
|
|
15434
|
-
resolved = true;
|
|
15435
|
-
clearTimeout(timer);
|
|
15436
|
-
resolve7({
|
|
15437
|
-
jsonrpc: "2.0",
|
|
15438
|
-
id: request_id,
|
|
15439
|
-
error: { code: -32002, message: `Failed to connect: ${err instanceof Error ? err.message : String(err)}` }
|
|
15440
|
-
});
|
|
15441
|
-
}
|
|
15442
|
-
}
|
|
15443
|
-
});
|
|
15444
|
-
}
|
|
15445
|
-
async function stream_editor(options) {
|
|
15446
|
-
const { method, params, timeout = 30000, on_event } = options;
|
|
15447
|
-
const config = resolve_config(options);
|
|
15448
|
-
if ("error" in config) {
|
|
15449
|
-
throw new Error(config.error);
|
|
15450
|
-
}
|
|
15451
|
-
const MAX_RECONNECTS = 5;
|
|
15452
|
-
let reconnect_count = 0;
|
|
15453
|
-
let stopped = false;
|
|
15454
|
-
return new Promise((resolve7, reject) => {
|
|
15455
|
-
let resolved = false;
|
|
15456
|
-
function connect(url) {
|
|
15457
|
-
const ws = new WebSocket(url);
|
|
15458
|
-
const request_id = generate_id();
|
|
15459
|
-
const request = {
|
|
15460
|
-
jsonrpc: "2.0",
|
|
15461
|
-
id: request_id,
|
|
15462
|
-
method,
|
|
15463
|
-
...params ? { params } : {}
|
|
15464
|
-
};
|
|
15465
|
-
let connected = false;
|
|
15466
|
-
const timer = !resolved ? setTimeout(() => {
|
|
15467
|
-
if (!connected && !resolved) {
|
|
15468
|
-
reject(new Error(`Timeout connecting to ${url}`));
|
|
15469
|
-
}
|
|
15470
|
-
}, timeout) : null;
|
|
15471
|
-
ws.onopen = () => {
|
|
15472
|
-
connected = true;
|
|
15473
|
-
if (timer)
|
|
15474
|
-
clearTimeout(timer);
|
|
15475
|
-
reconnect_count = 0;
|
|
15476
|
-
ws.send(JSON.stringify(request));
|
|
15477
|
-
if (!resolved) {
|
|
15478
|
-
resolved = true;
|
|
15479
|
-
resolve7({ close: () => {
|
|
15480
|
-
stopped = true;
|
|
15481
|
-
ws.close();
|
|
15482
|
-
} });
|
|
15483
|
-
}
|
|
15484
|
-
};
|
|
15485
|
-
ws.onmessage = (event) => {
|
|
15486
|
-
try {
|
|
15487
|
-
const data = JSON.parse(String(event.data));
|
|
15488
|
-
if ("method" in data && !("id" in data)) {
|
|
15489
|
-
on_event(data);
|
|
15490
|
-
}
|
|
15491
|
-
} catch {}
|
|
15492
|
-
};
|
|
15493
|
-
ws.onerror = () => {
|
|
15494
|
-
if (!connected && !resolved) {
|
|
15495
|
-
if (timer)
|
|
15496
|
-
clearTimeout(timer);
|
|
15497
|
-
reject(new Error(`WebSocket connection failed to ${url}`));
|
|
15498
|
-
}
|
|
15499
|
-
};
|
|
15500
|
-
ws.onclose = () => {
|
|
15501
|
-
if (stopped)
|
|
15502
|
-
return;
|
|
15503
|
-
if (!connected && !resolved)
|
|
15504
|
-
return;
|
|
15505
|
-
if (reconnect_count >= MAX_RECONNECTS)
|
|
15506
|
-
return;
|
|
15507
|
-
reconnect_count++;
|
|
15508
|
-
const delay = Math.min(500 * reconnect_count, 3000);
|
|
15509
|
-
setTimeout(() => {
|
|
15510
|
-
if (stopped)
|
|
15511
|
-
return;
|
|
15512
|
-
const fresh_config = resolve_config(options);
|
|
15513
|
-
if ("error" in fresh_config)
|
|
15514
|
-
return;
|
|
15515
|
-
connect(`ws://127.0.0.1:${fresh_config.port}/unity-agentic`);
|
|
15516
|
-
}, delay);
|
|
15517
|
-
};
|
|
15518
|
-
}
|
|
15519
|
-
connect(`ws://127.0.0.1:${config.port}/unity-agentic`);
|
|
15520
|
-
});
|
|
15521
|
-
}
|
|
15522
|
-
async function ping_editor(port, timeout_ms = 2000) {
|
|
15523
|
-
return new Promise((resolve7) => {
|
|
15524
|
-
const timer = setTimeout(() => {
|
|
15525
|
-
resolve7({ reachable: false, error: `Timeout after ${timeout_ms}ms` });
|
|
15526
|
-
}, timeout_ms);
|
|
15527
|
-
try {
|
|
15528
|
-
const ws = new WebSocket(`ws://127.0.0.1:${port}/unity-agentic`);
|
|
15529
|
-
ws.onopen = () => {
|
|
15530
|
-
clearTimeout(timer);
|
|
15531
|
-
try {
|
|
15532
|
-
ws.close();
|
|
15533
|
-
} catch {}
|
|
15534
|
-
resolve7({ reachable: true });
|
|
15535
|
-
};
|
|
15536
|
-
ws.onerror = (err) => {
|
|
15537
|
-
clearTimeout(timer);
|
|
15538
|
-
resolve7({ reachable: false, error: String(err) });
|
|
15539
|
-
};
|
|
15540
|
-
} catch (err) {
|
|
15541
|
-
clearTimeout(timer);
|
|
15542
|
-
resolve7({ reachable: false, error: String(err) });
|
|
15543
|
-
}
|
|
15544
|
-
});
|
|
15545
|
-
}
|
|
15546
|
-
function resolve_config(options) {
|
|
15547
|
-
if (options.port) {
|
|
15548
|
-
return { port: options.port, pid: 0, version: "unknown" };
|
|
15549
|
-
}
|
|
15550
|
-
return read_editor_config(options.project_path);
|
|
15551
|
-
}
|
|
15552
|
-
function is_pid_alive(pid) {
|
|
15553
|
-
try {
|
|
15554
|
-
process.kill(pid, 0);
|
|
15555
|
-
return true;
|
|
15556
|
-
} catch {
|
|
15557
|
-
return false;
|
|
15558
|
-
}
|
|
15559
|
-
}
|
|
15560
|
-
function generate_id() {
|
|
15561
|
-
return `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
15562
|
-
}
|
|
15563
|
-
|
|
15564
|
-
// src/cmd-editor.ts
|
|
17008
|
+
var import_path13 = require("path");
|
|
15565
17009
|
var BRIDGE_PACKAGE_NAME = "com.unity-agentic-tools.editor-bridge";
|
|
15566
17010
|
var BRIDGE_PACKAGE_VERSION = "https://github.com/taconotsandwich/unity-agentic-tools.git?path=unity-package";
|
|
15567
17011
|
function get_common_options(cmd) {
|
|
15568
|
-
let current = cmd
|
|
17012
|
+
let current = cmd;
|
|
15569
17013
|
let project;
|
|
15570
17014
|
let timeout_str;
|
|
15571
17015
|
let port_str;
|
|
@@ -15579,7 +17023,7 @@ function get_common_options(cmd) {
|
|
|
15579
17023
|
port_str = opts.port;
|
|
15580
17024
|
current = current.parent;
|
|
15581
17025
|
}
|
|
15582
|
-
const project_path =
|
|
17026
|
+
const project_path = import_path13.resolve(project || process.cwd());
|
|
15583
17027
|
const timeout = parseInt(timeout_str || "10000", 10);
|
|
15584
17028
|
const port = port_str ? parseInt(port_str, 10) : undefined;
|
|
15585
17029
|
return { project_path, timeout, port };
|
|
@@ -15757,9 +17201,9 @@ function build_editor_command() {
|
|
|
15757
17201
|
params.mode = options.mode;
|
|
15758
17202
|
await handle_rpc(this, "editor.tests.run", params);
|
|
15759
17203
|
});
|
|
15760
|
-
cmd.command("install
|
|
15761
|
-
const
|
|
15762
|
-
const result = add_package(
|
|
17204
|
+
cmd.command("install").description("Install the editor bridge package into a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").action(function() {
|
|
17205
|
+
const { project_path } = get_common_options(this);
|
|
17206
|
+
const result = add_package(project_path, BRIDGE_PACKAGE_NAME, BRIDGE_PACKAGE_VERSION);
|
|
15763
17207
|
if ("error" in result) {
|
|
15764
17208
|
console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
|
|
15765
17209
|
process.exitCode = 1;
|
|
@@ -15767,9 +17211,9 @@ function build_editor_command() {
|
|
|
15767
17211
|
}
|
|
15768
17212
|
console.log(JSON.stringify(result, null, 2));
|
|
15769
17213
|
});
|
|
15770
|
-
cmd.command("uninstall
|
|
15771
|
-
const
|
|
15772
|
-
const result = remove_package(
|
|
17214
|
+
cmd.command("uninstall").description("Remove the editor bridge package from a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").action(function() {
|
|
17215
|
+
const { project_path } = get_common_options(this);
|
|
17216
|
+
const result = remove_package(project_path, BRIDGE_PACKAGE_NAME);
|
|
15773
17217
|
if ("error" in result) {
|
|
15774
17218
|
console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
|
|
15775
17219
|
process.exitCode = 1;
|
|
@@ -15994,7 +17438,13 @@ program.addCommand(build_read_command(getScanner));
|
|
|
15994
17438
|
program.addCommand(build_update_command(getScanner));
|
|
15995
17439
|
program.addCommand(build_delete_command());
|
|
15996
17440
|
program.addCommand(build_editor_command());
|
|
15997
|
-
program.command("clone <file> <object_name>").description("Duplicate a GameObject and its hierarchy").option("-n, --name <new_name>", "Name for the duplicated object").option("-j, --json", "Output as JSON").action((file, object_name, options) => {
|
|
17441
|
+
program.command("clone <file> <object_name>").description("Duplicate a GameObject and its hierarchy").option("-n, --name <new_name>", "Name for the duplicated object").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, object_name, options) => {
|
|
17442
|
+
const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
|
|
17443
|
+
if (!guard.allowed) {
|
|
17444
|
+
console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
|
|
17445
|
+
process.exitCode = 1;
|
|
17446
|
+
return;
|
|
17447
|
+
}
|
|
15998
17448
|
const result = duplicateGameObject({
|
|
15999
17449
|
file_path: file,
|
|
16000
17450
|
object_name,
|
|
@@ -16100,11 +17550,12 @@ program.command("search <path> [pattern]").description("Search for GameObjects.
|
|
|
16100
17550
|
console.log(JSON.stringify(result, null, 2));
|
|
16101
17551
|
}
|
|
16102
17552
|
});
|
|
16103
|
-
program.command("grep <
|
|
17553
|
+
program.command("grep <pattern>").description("Search for a regex pattern across project files").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("--type <type>", "File type filter: cs, yaml, unity, prefab, asset, all", "all").option("-m, --max <n>", "Max results (default: 100)", "100").option("-C, --context <n>", "Context lines around matches", "0").option("-j, --json", "Output as JSON").action((pattern, options) => {
|
|
16104
17554
|
if (!pattern || pattern.trim() === "") {
|
|
16105
17555
|
console.log(JSON.stringify({ success: false, error: "Pattern must not be empty" }, null, 2));
|
|
16106
17556
|
process.exit(1);
|
|
16107
17557
|
}
|
|
17558
|
+
let project_path = resolve_project_path(options.project);
|
|
16108
17559
|
const abs_path = path9.resolve(project_path);
|
|
16109
17560
|
const project_root = find_unity_project_root(abs_path);
|
|
16110
17561
|
if (project_root) {
|
|
@@ -16132,8 +17583,9 @@ program.command("grep <project_path> <pattern>").description("Search for a regex
|
|
|
16132
17583
|
if (!result.success)
|
|
16133
17584
|
process.exitCode = 1;
|
|
16134
17585
|
});
|
|
16135
|
-
program.command("version
|
|
17586
|
+
program.command("version").description("Read Unity project version").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((options) => {
|
|
16136
17587
|
try {
|
|
17588
|
+
const project_path = resolve_project_path(options.project);
|
|
16137
17589
|
const version = read_project_version(project_path);
|
|
16138
17590
|
console.log(JSON.stringify(version, null, 2));
|
|
16139
17591
|
} catch (err) {
|