Патчи к code mode и доки по его настройке

This commit is contained in:
ftc
2026-09-13 10:03:08 +03:00
parent 55959b6e7f
commit 7e3498bd7b
6 changed files with 1402 additions and 3 deletions
+102
View File
@@ -0,0 +1,102 @@
# Code Mode setup
The `cocos-code-mode` extension (in [extensions/cocos-code-mode](../extensions/cocos-code-mode))
runs an HTTP server inside the Cocos Creator editor and exposes scene, asset and
inspector operations as [UTCP](https://www.utcp.io/) tools. That is how the editor
is driven from outside the UI — see [CODE_MODE_TOOLS.md](CODE_MODE_TOOLS.md) for
the tool reference.
## Prerequisites
- Cocos Creator 3.8.8 must be **running with this project open**. The tool server
lives inside the editor process; if the editor is closed, there are no tools.
- The extension registers itself in `~/.utcp_config.json` on startup.
## The port changes on every editor restart
The server binds to `127.0.0.1` on an OS-assigned port, and rewrites
`~/.utcp_config.json` each time. Always read the config file rather than
remembering a port:
```bash
cat ~/.utcp_config.json
```
## Running a tool chain
Pass the config path via `UTCP_CONFIG_FILE`. The script body runs as an async
function; tool calls look synchronous and `return` gives back the result.
```bash
UTCP_CONFIG_FILE="$USERPROFILE/.utcp_config.json" npx -y @utcp/code-mode-cli run <<'EOF'
const tree = CocosEditor.nodeGetTree({});
return { name: tree.name, children: (tree.children || []).map(c => c.name) };
EOF
```
Discover tools by task instead of guessing names:
```bash
UTCP_CONFIG_FILE="$USERPROFILE/.utcp_config.json" npx -y @utcp/code-mode-cli search "add a sprite to a node"
```
## Working discipline
The tools are built around **discover, then act**:
1. `nodeGetTree` / `assetGetTree` — find the thing.
2. `inspectorGetInstanceDefinition` — get its real TypeScript class.
3. `inspectorSetInstanceProperties` — set properties by confirmed name.
`inspectorSetInstanceProperties` states it outright: if a property path or type
has not been confirmed via an `inspectorGet*` call, do not call a setter. Property
names differ from the engine docs often enough that guessing corrupts scenes.
Batch reads and writes into one `run` script — each `npx` invocation re-registers
the manual, which costs a few seconds.
## Regenerating the tool reference
After updating the extension:
```bash
curl -s "$(node -p "require(require('os').homedir()+'/.utcp_config.json').manual_call_templates[0].url")" -o manual.json
node docs/scripts/gen-tool-reference.js manual.json docs/CODE_MODE_TOOLS.md
```
## Known issue: `sharp` / libvips conflict on Windows
**Symptom.** The editor logs `The plug-in main process failed to load` with either
`Could not load the "sharp" module using the win32-x64 runtime` or
`TypeError: Cannot read properties of undefined (reading 'output')` at
`node_modules/sharp/lib/utility.js`. No `~/.utcp_config.json` appears and no tools
are reachable.
**Cause.** Two things stacked:
1. The released extension zip was packaged on macOS, so its bundled `sharp` ships
only `darwin-arm64` binaries.
2. More fundamentally, Cocos Creator already has its own `sharp` **0.32.6**
(libvips 8.14) resident in the same process. On Windows a second libvips cannot
be loaded beside the first, so the extension's `sharp` 0.34.x binds against the
editor's older DLL. That DLL has no JPEG 2000 support, `format.jp2k` comes back
undefined, and the module throws while initialising — taking the whole plugin
with it.
**Fix.** [asset-tools.js](../extensions/cocos-code-mode/dist/utcp/tools/asset-tools.js)
now loads `sharp` lazily, prefers the editor's own resident build under
`process.resourcesPath/app.asar.unpacked/node_modules/sharp`, and tolerates it
being unavailable. `sharp` is only a fast path for `assetGetPreview`, which
already had a renderer-based fallback, so a missing image library no longer stops
the tool server from starting.
That file is tracked by git, so the fix survives — but **re-importing the extension
from a release zip will overwrite it.** If the extension is ever re-imported,
re-apply the patch and, on Windows, also install the matching native binaries:
```bash
cd extensions/cocos-code-mode && npm install --no-save --os=win32 --cpu=x64 --include=optional sharp@0.34.5
```
(`extensions/cocos-code-mode/node_modules` is gitignored, so that install does not
survive a clean checkout either.)
+485
View File
@@ -0,0 +1,485 @@
# Code Mode tool reference (`CocosEditor`)
Generated from the live UTCP manual served by the `cocos-code-mode` extension
(UTCP 1.0.1, 24 tools). Regenerate with the command in
[CODE_MODE_SETUP.md](CODE_MODE_SETUP.md) whenever the extension is updated.
All tools are called as `CocosEditor.<name>({ ... })` inside a `utcp run` script.
Calls look synchronous — the host bridges the async work, so no `await` is needed.
## Conventions
Nodes, components and assets are passed around as UUID handles:
```ts
interface IInstanceReference { id: string; type?: string; } // e.g. { id: "a1b2…", type: "cc.Sprite" }
```
Work **discover, then act**: get the tree, get the type definition, then set properties by name.
Never guess property names — `inspectorGetInstanceDefinition` returns the real TypeScript class.
## Scene graph
### `nodeGetTree`
Get the hierarchy tree of specific node or scene root if no reference is provided. Children have recursive structure.
```ts
CocosEditor.nodeGetTree(args: {
reference?: {
id: string;
type?: string;
};
}): {
path?: string;
reference: {
id: string;
type?: string;
};
name: string;
active: boolean;
components: {
id: string;
type?: string;
}[];
children: Record<string, unknown>[];
}
```
### `nodeGetAtPath`
Get nodes at specific path in the scene hierarchy. Usually returns one node, but can return multiple nodes with the same name.
```ts
CocosEditor.nodeGetAtPath(args: {
hierarchyPath: string; // Path to the node in the scene hierarchy"
}): {
references?: {
id: string;
type?: string;
}[];
}
```
### `nodeCreate`
Create a new node in the scene. If no parent is specified, root node is used. Returns reference to the new node.
```ts
CocosEditor.nodeCreate(args: {
name: string;
parentReference?: {
id: string;
type?: string;
};
assetReference?: {
id: string;
type?: string;
};
unwrapPrefab?: boolean; // default: false
}): {
reference: {
id: string;
type?: string;
};
}
```
### `nodeCreatePrimitive`
Create a new node with predefined primitive geometry MeshRenderer. If no parent is specified, root node is used. Returns reference to the new node.
```ts
CocosEditor.nodeCreatePrimitive(args: {
name: string;
primitiveType: "Capsule" | "Cone" | "Cube" | "Cylinder" | "Plane" | "Quad" | "Sphere" | "Torus";
parentReference?: {
id: string;
type?: string;
};
}): {
reference: {
id: string;
type?: string;
};
}
```
### `nodeOperate`
Perform operation on referenced node, including prefab operations.
```ts
CocosEditor.nodeOperate(args: {
operation: "move" | "copy" | "delete" | "create_prefab" | "revert_prefab" | "apply_prefab" | "unwrap_prefab" | "unwrap_prefab_completely" | "open_prefab";
reference: {
id: string;
type?: string;
};
newParentReference?: {
id: string;
type?: string;
};
newPrefabPath?: string; // For create_prefab: target db:// path
siblingIndex?: integer; // For move/copy: target index in parent children array
}): {
success?: boolean;
createdPrefabAssetReference?: {
id: string;
type?: string;
};
updatedNodeReference?: {
id: string;
type?: string;
};
copiedNodeReference?: {
id: string;
type?: string;
};
}
```
## Components
### `nodeComponentsGet`
Get components of specific type on a node. If componentType is not provided, returns all components on the node.
```ts
CocosEditor.nodeComponentsGet(args: {
reference: {
id: string;
type?: string;
};
componentType?: string;
}): {
references: {
id: string;
type?: string;
}[];
}
```
### `nodeComponentAdd`
Add a component to a referenced node, returns reference to the new component
```ts
CocosEditor.nodeComponentAdd(args: {
reference: {
id: string;
type?: string;
};
componentType: string;
}): {
reference: {
id: string;
type?: string;
};
}
```
### `nodeComponentRemove`
Remove referenced component from node it is attached to.
```ts
CocosEditor.nodeComponentRemove(args: {
reference: {
id: string;
type?: string;
};
}): {
success: boolean;
error?: string;
}
```
### `nodeGetAvailableComponentTypes`
Get list of globally available component types (class names) at the moment.
```ts
CocosEditor.nodeGetAvailableComponentTypes(args: {
includeInternal: boolean; // Whether to include internal engine components. — default: false
filter?: string; // Optional filter string to match component types or categories (case-insensitive substring match).
}): {
componentTypes: string[];
}
```
## Inspector (types & properties)
### `inspectorGetInstanceDefinition`
Generates TypeScript definition based on properties and descriptions of instance (Node, Component, Asset).
```ts
CocosEditor.inspectorGetInstanceDefinition(args: {
reference: {
id: string;
type?: string;
};
}): {
definition: string;
}
```
### `inspectorGetSettingsDefinition`
Generates TypeScript definition for specific settings.
```ts
CocosEditor.inspectorGetSettingsDefinition(args: {
settingsType: "CommonTypes" | "CurrentSceneGlobals" | "ProjectSettings";
}): {
definition: string;
}
```
### `inspectorGetInstanceProperties`
Gets plain object of properties, with no serialization info for any instance (scene node, component, asset).
```ts
CocosEditor.inspectorGetInstanceProperties(args: {
reference: {
id: string;
type?: string;
};
}): {
dump: Record<string, unknown>;
}
```
### `inspectorGetSettingsProperties`
Gets plain object of properties for the specific settings.
```ts
CocosEditor.inspectorGetSettingsProperties(args: {
settingsType: "CurrentSceneGlobals" | "ProjectSettings";
}): {
dump: Record<string, unknown>;
}
```
### `inspectorSetInstanceProperties`
Sets a property on instance of Node, Component or Asset. If a property path or type is not confirmed via inspectorGet* tools, you MUST NOT call any setter.
```ts
CocosEditor.inspectorSetInstanceProperties(args: {
reference: {
id: string;
type?: string;
};
propertyPaths: string[]; // Plain paths to the properties (e.g., ['position.x', 'rotation.y']). Don't support code execution. Arrays are reached by indexes. (e.g. 'sharedMaterials.0')
values: array,object,string,number,boolean,null[];
}): {
success: boolean;
error?: string;
}
```
### `inspectorSetSettingsProperties`
Sets a property on the specific settings. If a property path or type is not confirmed via inspectorGet* tools, you MUST NOT call any setter.
```ts
CocosEditor.inspectorSetSettingsProperties(args: {
settingsType: "CurrentSceneGlobals" | "ProjectSettings";
propertyPath: string; // Plain path to the property (e.g., 'ambient.skyLightingColor.r'). Don't support code execution.
value: array,object,string,number,boolean,null;
}): {
success: boolean;
error?: string;
}
```
## Assets
### `assetGetTree`
Get the asset and subAsset hierarchy tree. Children have recursive structure.
```ts
CocosEditor.assetGetTree(args: {
reference?: {
id: string;
type?: string;
};
assetPath?: string; // Root path to start from
}): {
filesystemPath?: string;
reference: {
id: string;
type?: string;
};
name: string;
children: Record<string, unknown>[];
}
```
### `assetGetAtPath`
Get asset reference by given local path and name, including extension. Can be used for subassets too. Returns reference to the asset.
```ts
CocosEditor.assetGetAtPath(args: {
assetPath: string;
}): {
reference: {
id: string;
type?: string;
};
}
```
### `assetCreate`
Create empty asset or folder of given type. Automatically handles folders creation along the path. Returns reference to the new asset.
```ts
CocosEditor.assetCreate(args: {
assetPath: string;
preset: "folder" | "material" | "effect" | "scene" | "prefab" | "typescript" | "animation-clip" | "render-texture" | "physics-material" | "animation-graph" | "animation-graph-variant" | "animation-mask" | "auto-atlas" | "effect-header" | "label-atlas" | "terrain"; // Preset type for the new asset
options?: {
overwrite?: boolean;
rename?: boolean;
}; // Additional options for the operation
}): {
reference: {
id: string;
type?: string;
};
}
```
### `assetImport`
Import an external file as an asset into the project. Path must end with the extension. Returns reference to the new asset.
```ts
CocosEditor.assetImport(args: {
sourceFilesystemPath: string; // Source filesystem path of the file to import
targetAssetPath: string; // Target path in the asset database
imageType?: "raw" | "texture" | "normal-map" | "sprite-frame" | "texture-cube"; // For image files, specify how to import them
options?: {
overwrite?: boolean;
rename?: boolean;
}; // Additional options for the operation
}): {
reference: {
id: string;
type?: string;
};
}
```
### `assetOperate`
Perform operations on assets (move, copy, delete, open). Returns reference to the affected asset (for delete/open returns the source asset reference).
```ts
CocosEditor.assetOperate(args: {
operation: "move" | "copy" | "delete" | "open" | "refresh" | "reimport";
reference: {
id: string;
type?: string;
};
targetAssetPath?: string; // Target path (for move/copy/import)
options?: {
overwrite?: boolean;
rename?: boolean;
}; // Additional options for the operation
}): {
reference: {
id: string;
type?: string;
};
}
```
### `assetGetPreview`
Returns preview image of the asset (Prefab, Image, Model or Material is supported). IMPORTANT: To visualize the image, you must return the result of this function DIRECTLY as the final value of your code, do NOT wrap it in an object.
```ts
CocosEditor.assetGetPreview(args: {
reference: {
id: string;
type?: string;
};
imageSize?: number; // Size of the preview image (square) — default: 512
jpegQuality?: integer; // JPEG Quality of the preview image — default: 80
transparentColor?: {
r: integer;
g: integer;
b: integer;
}; // Background color for transparent images in RGB format
}): {
type: string;
data: string;
mimeType: string;
}
```
## Editor
### `editorOperate`
Common editor operations for scene and prefab view, game preview controls and asset database refresh
```ts
CocosEditor.editorOperate(args: {
operation: "save_scene_or_prefab" | "close_scene_or_prefab" | "play_preview" | "pause" | "step" | "stop" | "refresh";
}): {
success: boolean;
error?: string;
}
```
### `editorGetLogs`
Get last N editor log entries
```ts
CocosEditor.editorGetLogs(args: {
count: number; // Number of log entries to retrieve — default: 10
showStack?: boolean; // Return full stack trace for each log entry
order: "newest-to-oldest" | "oldest-to-newest"; // Order of logs — default: "newest-to-oldest"
}): {
logLines: string[];
}
```
### `editorGetScenePreview`
Returns preview image of scene view. IMPORTANT: To visualize the image, you must return the result of this function DIRECTLY as the final value of your code, do NOT wrap it in an object.
```ts
CocosEditor.editorGetScenePreview(args: {
imageSize?: {
width?: number; // default: 512
height?: number; // default: 512
};
jpegQuality?: integer; // default: 80
cameraPosition: {
x: number;
y: number;
z: number;
}; // Camera world position
targetPosition: {
x: number;
y: number;
z: number;
}; // Point the camera looks at
orthographic?: boolean; // Whether to use orthographic projection — default: false
orthographicSize?: number; // Orthographic size (only applies if orthographic is true) — default: 10
}): {
type: string;
data: string;
mimeType: string;
}
```
+101
View File
@@ -0,0 +1,101 @@
const fs = require('fs');
const manual = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
function render(schema, indent) {
if (!schema) return 'unknown';
const pad = ' '.repeat(indent);
const padIn = ' '.repeat(indent + 1);
if (schema.enum) return schema.enum.map(v => JSON.stringify(v)).join(' | ');
switch (schema.type) {
case 'array':
return render(schema.items, indent) + '[]';
case 'object': {
if (!schema.properties) return 'Record<string, unknown>';
const req = schema.required || [];
const lines = Object.entries(schema.properties).map(([k, v]) => {
const opt = req.includes(k) ? '' : '?';
let line = `${padIn}${k}${opt}: ${render(v, indent + 1)};`;
const notes = [];
if (v.description) notes.push(v.description);
if (v.default !== undefined) notes.push(`default: ${JSON.stringify(v.default)}`);
if (notes.length) line += ` // ${notes.join(' — ')}`;
return line;
});
return `{\n${lines.join('\n')}\n${pad}}`;
}
case undefined:
return 'unknown';
default:
return schema.type;
}
}
const groups = [
['Scene graph', ['nodeGetTree', 'nodeGetAtPath', 'nodeCreate', 'nodeCreatePrimitive', 'nodeOperate']],
['Components', ['nodeComponentsGet', 'nodeComponentAdd', 'nodeComponentRemove', 'nodeGetAvailableComponentTypes']],
['Inspector (types & properties)', ['inspectorGetInstanceDefinition', 'inspectorGetSettingsDefinition', 'inspectorGetInstanceProperties', 'inspectorGetSettingsProperties', 'inspectorSetInstanceProperties', 'inspectorSetSettingsProperties']],
['Assets', ['assetGetTree', 'assetGetAtPath', 'assetCreate', 'assetImport', 'assetOperate', 'assetGetPreview']],
['Editor', ['editorOperate', 'editorGetLogs', 'editorGetScenePreview']],
];
const byName = new Map(manual.tools.map(t => [t.name, t]));
const out = [];
out.push('# Code Mode tool reference (`CocosEditor`)');
out.push('');
out.push('Generated from the live UTCP manual served by the `cocos-code-mode` extension');
out.push(`(UTCP ${manual.utcp_version}, ${manual.tools.length} tools). Regenerate with the command in`);
out.push('[CODE_MODE_SETUP.md](CODE_MODE_SETUP.md) whenever the extension is updated.');
out.push('');
out.push('All tools are called as `CocosEditor.<name>({ ... })` inside a `utcp run` script.');
out.push('Calls look synchronous — the host bridges the async work, so no `await` is needed.');
out.push('');
out.push('## Conventions');
out.push('');
out.push('Nodes, components and assets are passed around as UUID handles:');
out.push('');
out.push('```ts');
out.push('interface IInstanceReference { id: string; type?: string; } // e.g. { id: "a1b2…", type: "cc.Sprite" }');
out.push('```');
out.push('');
out.push('Work **discover, then act**: get the tree, get the type definition, then set properties by name.');
out.push('Never guess property names — `inspectorGetInstanceDefinition` returns the real TypeScript class.');
out.push('');
const seen = new Set();
for (const [title, names] of groups) {
out.push(`## ${title}`);
out.push('');
for (const name of names) {
const t = byName.get(name);
if (!t) continue;
seen.add(name);
out.push(`### \`${name}\``);
out.push('');
out.push(t.description);
out.push('');
out.push('```ts');
out.push(`CocosEditor.${name}(args: ${render(t.inputs, 0)}): ${render(t.outputs, 0)}`);
out.push('```');
out.push('');
}
}
const rest = manual.tools.filter(t => !seen.has(t.name));
if (rest.length) {
out.push('## Other');
out.push('');
for (const t of rest) {
out.push(`### \`${t.name}\``);
out.push('');
out.push(t.description);
out.push('');
out.push('```ts');
out.push(`CocosEditor.${t.name}(args: ${render(t.inputs, 0)}): ${render(t.outputs, 0)}`);
out.push('```');
out.push('');
}
}
fs.writeFileSync(process.argv[3], out.join('\n'));
console.log('wrote', process.argv[3], out.length, 'lines');