pixifact-cli 0.1.3 → 0.1.4
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 +13 -13
- package/package.json +4 -4
- package/src/automation.ts +172 -182
- package/src/pixifact-cli.ts +1 -1
package/README.md
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
# Pixifact CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Pixifact Scene 自动化命令行工具。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
本版本 CLI 是 Bun-first 工具。
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## 安装
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
bun add -d pixifact-cli
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## 命令
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
查看 Scene:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
pixifact scene inspect --project-root . --scene src/scenes/Hud.scene
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
校验单个 Scene:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
pixifact scene validate --project-root . --scene src/scenes/Hud.scene
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
校验所有 compiler Scene:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
pixifact scene validate --project-root . --all
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
编译生成 Scene runtime 文件:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
pixifact compile-scenes --project-root .
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
## Agent
|
|
39
|
+
## Agent 工作流
|
|
40
40
|
|
|
41
|
-
Codex
|
|
41
|
+
Codex、Claude Code 和其他 coding agent 应直接编辑 `.scene` 文件,然后运行:
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
pixifact scene validate --project-root . --all
|
|
@@ -46,9 +46,9 @@ pixifact compile-scenes --project-root .
|
|
|
46
46
|
bun run build
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
不要编辑 `.pixifact/generated/**` 下的生成文件。
|
|
50
50
|
|
|
51
|
-
##
|
|
51
|
+
## 环境要求
|
|
52
52
|
|
|
53
53
|
- Bun
|
|
54
|
-
- `pixifact`
|
|
54
|
+
- 目标项目已安装 `pixifact`
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Pixifact Scene 自动化 CLI。",
|
|
5
5
|
"packageManager": "bun@1.3.13",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
|
15
|
-
"url": "
|
|
15
|
+
"url": "https://github.com/hxg2050/pixifact",
|
|
16
16
|
"directory": "packages/pixifact-cli"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
@@ -31,6 +31,6 @@
|
|
|
31
31
|
"start": "bun src/pixifact-cli.ts"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"pixifact": "^0.1.
|
|
34
|
+
"pixifact": "^0.1.4"
|
|
35
35
|
}
|
|
36
36
|
}
|
package/src/automation.ts
CHANGED
|
@@ -2,8 +2,10 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
createSceneRevision,
|
|
5
|
+
builtinSceneAssetIds,
|
|
6
|
+
builtinSceneNameFromAssetId,
|
|
5
7
|
defaultSceneSourceRoots,
|
|
6
|
-
|
|
8
|
+
extractSceneScriptInterfaces,
|
|
7
9
|
findMissingScenePartReferences,
|
|
8
10
|
inspectSceneTemplate,
|
|
9
11
|
isIgnoredSceneSourceDirectory,
|
|
@@ -12,32 +14,22 @@ import {
|
|
|
12
14
|
parseSceneTemplate,
|
|
13
15
|
resolveSceneReference,
|
|
14
16
|
sceneLocalName,
|
|
17
|
+
toPosixPath,
|
|
15
18
|
validateSceneContent,
|
|
16
19
|
} from 'pixifact/compiler';
|
|
20
|
+
import { readBuiltinSceneScriptSourcesSync } from 'pixifact/compiler-node';
|
|
17
21
|
import {
|
|
18
|
-
container,
|
|
19
|
-
scene,
|
|
20
|
-
SceneDocument,
|
|
21
22
|
parsePixifactProjectConfig,
|
|
22
23
|
pixifactProjectConfigFileName,
|
|
23
24
|
summarizePixifactProjectConfig,
|
|
24
25
|
} from 'pixifact';
|
|
25
|
-
import type {
|
|
26
|
-
import type { SceneProjectState } from 'pixifact';
|
|
27
|
-
import type { SceneTemplate, SceneTemplateInterface, SceneValidationDiagnostic } from 'pixifact/compiler';
|
|
26
|
+
import type { SceneScriptInterface, SceneTemplate, SceneTemplateInterface, SceneTemplateNode, SceneValidationDiagnostic } from 'pixifact/compiler';
|
|
28
27
|
|
|
29
28
|
interface ProjectFileSummary {
|
|
30
29
|
path: string;
|
|
31
30
|
kind: 'folder' | 'scene' | 'file';
|
|
32
31
|
}
|
|
33
32
|
|
|
34
|
-
interface EditableDocument {
|
|
35
|
-
projectRoot: string;
|
|
36
|
-
target: string;
|
|
37
|
-
document: SceneDocument;
|
|
38
|
-
isProjectFile: boolean;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
33
|
interface ToolInput {
|
|
42
34
|
projectRoot?: unknown;
|
|
43
35
|
scenePath?: unknown;
|
|
@@ -48,34 +40,13 @@ interface ToolInput {
|
|
|
48
40
|
type CompilerSceneValidationSuccess = ReturnType<typeof validateCompilerSceneFile> & { ok: true };
|
|
49
41
|
type CompilerSceneValidationFailure = ReturnType<typeof validateCompilerSceneFile> & { ok: false };
|
|
50
42
|
|
|
51
|
-
type
|
|
43
|
+
type DetailedCompilerNode = SceneTemplateNode & {
|
|
52
44
|
locator: string;
|
|
53
45
|
depth: number;
|
|
54
46
|
parent?: string;
|
|
55
47
|
childCount: number;
|
|
56
48
|
}
|
|
57
49
|
|
|
58
|
-
interface NodeSummary {
|
|
59
|
-
id?: string;
|
|
60
|
-
key?: string;
|
|
61
|
-
role?: string;
|
|
62
|
-
name?: string;
|
|
63
|
-
kind: NodeSpec['kind'];
|
|
64
|
-
locator: string;
|
|
65
|
-
depth: number;
|
|
66
|
-
transform?: NodeSpec['transform'];
|
|
67
|
-
components: Array<{
|
|
68
|
-
id?: string;
|
|
69
|
-
type: string;
|
|
70
|
-
propKeys: string[];
|
|
71
|
-
}>;
|
|
72
|
-
childCount: number;
|
|
73
|
-
children: NodeSummary[];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const projectStateType = 'pixifact.aiEditorProject';
|
|
77
|
-
const sceneType = 'scene';
|
|
78
|
-
|
|
79
50
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
80
51
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
81
52
|
}
|
|
@@ -128,58 +99,24 @@ function writeTextFile(filePath: string, value: string) {
|
|
|
128
99
|
fs.writeFileSync(filePath, value, 'utf8');
|
|
129
100
|
}
|
|
130
101
|
|
|
131
|
-
function
|
|
102
|
+
function writeNewTextFile(filePath: string, value: string, errorMessage: string) {
|
|
132
103
|
try {
|
|
133
|
-
fs.writeFileSync(filePath,
|
|
104
|
+
fs.writeFileSync(filePath, value, {
|
|
134
105
|
encoding: 'utf8',
|
|
135
106
|
flag: 'wx',
|
|
136
107
|
});
|
|
137
108
|
} catch (error) {
|
|
138
109
|
if (isRecord(error) && error.code === 'EEXIST') {
|
|
139
|
-
throw new Error(
|
|
110
|
+
throw new Error(errorMessage);
|
|
140
111
|
}
|
|
141
112
|
throw error;
|
|
142
113
|
}
|
|
143
114
|
}
|
|
144
115
|
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
&& value.type === projectStateType
|
|
149
|
-
&& isRecord(value.scene)
|
|
150
|
-
&& (value.scene as { type?: unknown }).type === sceneType;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function isScene(value: unknown): value is SceneSpec {
|
|
154
|
-
return isRecord(value)
|
|
155
|
-
&& value.version === 1
|
|
156
|
-
&& value.type === sceneType
|
|
157
|
-
&& isRecord(value.root);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function loadEditableDocument(projectRoot: unknown, scenePath: unknown): EditableDocument {
|
|
161
|
-
const { root, target } = resolveProjectPath(projectRoot, scenePath);
|
|
162
|
-
const content = readJsonFile(target);
|
|
163
|
-
if (isProjectState(content)) {
|
|
164
|
-
const document = new SceneDocument(content.scene);
|
|
165
|
-
document.loadState(content);
|
|
166
|
-
return {
|
|
167
|
-
projectRoot: root,
|
|
168
|
-
target,
|
|
169
|
-
document,
|
|
170
|
-
isProjectFile: true,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
if (!isScene(content)) {
|
|
174
|
-
throw new Error('Target file must be a Pixifact Scene or AI editor project JSON.');
|
|
116
|
+
function assertNewFile(filePath: string, errorMessage: string) {
|
|
117
|
+
if (fs.existsSync(filePath)) {
|
|
118
|
+
throw new Error(errorMessage);
|
|
175
119
|
}
|
|
176
|
-
const document = new SceneDocument(content);
|
|
177
|
-
return {
|
|
178
|
-
projectRoot: root,
|
|
179
|
-
target,
|
|
180
|
-
document,
|
|
181
|
-
isProjectFile: false,
|
|
182
|
-
};
|
|
183
120
|
}
|
|
184
121
|
|
|
185
122
|
function loadCompilerScene(projectRoot: unknown, scenePath: unknown) {
|
|
@@ -217,7 +154,12 @@ function collectAssetFiles(root: string, directory: string, assets: Set<string>)
|
|
|
217
154
|
}
|
|
218
155
|
|
|
219
156
|
function collectCompilerSceneInterfaces(root: string, skippedScene?: string) {
|
|
220
|
-
const
|
|
157
|
+
const sceneSources: {
|
|
158
|
+
scenePath: string;
|
|
159
|
+
template: SceneTemplate;
|
|
160
|
+
scriptPath: string;
|
|
161
|
+
scriptSource: string;
|
|
162
|
+
}[] = [];
|
|
221
163
|
|
|
222
164
|
for (const scenePath of collectCompilerScenePaths(root)) {
|
|
223
165
|
if (scenePath === skippedScene) {
|
|
@@ -225,11 +167,49 @@ function collectCompilerSceneInterfaces(root: string, skippedScene?: string) {
|
|
|
225
167
|
}
|
|
226
168
|
try {
|
|
227
169
|
const template = parseSceneTemplate(readTextFile(path.join(root, scenePath)));
|
|
228
|
-
const
|
|
170
|
+
const scriptPath = pairedSceneScriptPath(scenePath);
|
|
171
|
+
const absoluteScriptPath = path.join(root, scriptPath);
|
|
172
|
+
if (!fs.existsSync(absoluteScriptPath)) {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
sceneSources.push({
|
|
176
|
+
scenePath,
|
|
177
|
+
template,
|
|
178
|
+
scriptPath,
|
|
179
|
+
scriptSource: readTextFile(absoluteScriptPath),
|
|
180
|
+
});
|
|
181
|
+
} catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const builtinSources = readBuiltinSceneScriptSourcesSync();
|
|
187
|
+
const descriptors = extractSceneScriptInterfaces([
|
|
188
|
+
...builtinSceneAssetIds().map((scene) => {
|
|
189
|
+
const name = builtinSceneNameFromAssetId(scene);
|
|
190
|
+
return {
|
|
191
|
+
scene,
|
|
192
|
+
fileName: `${name}.ts`,
|
|
193
|
+
source: builtinSources[name],
|
|
194
|
+
};
|
|
195
|
+
}),
|
|
196
|
+
...sceneSources.map((source) => ({
|
|
197
|
+
scene: source.scenePath,
|
|
198
|
+
fileName: source.scriptPath,
|
|
199
|
+
source: source.scriptSource,
|
|
200
|
+
})),
|
|
201
|
+
]);
|
|
202
|
+
const interfaces: Record<string, SceneTemplateInterface> = Object.fromEntries(
|
|
203
|
+
builtinSceneAssetIds().map((scene) => [scene, descriptors[scene].interface]),
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
for (const source of sceneSources) {
|
|
207
|
+
try {
|
|
208
|
+
const pair = readCompilerScenePairContract(root, source.scenePath, source.template, descriptors[source.scenePath]);
|
|
229
209
|
if (pair.diagnostics.length > 0 || !pair.sceneInterface) {
|
|
230
210
|
continue;
|
|
231
211
|
}
|
|
232
|
-
interfaces[scenePath] = pair.sceneInterface;
|
|
212
|
+
interfaces[source.scenePath] = pair.sceneInterface;
|
|
233
213
|
} catch {
|
|
234
214
|
continue;
|
|
235
215
|
}
|
|
@@ -299,6 +279,7 @@ function readCompilerScenePairContract(
|
|
|
299
279
|
root: string,
|
|
300
280
|
scenePath: string,
|
|
301
281
|
template: SceneTemplate,
|
|
282
|
+
descriptor?: SceneScriptInterface,
|
|
302
283
|
): { diagnostics: SceneValidationDiagnostic[]; sceneInterface?: SceneTemplateInterface } {
|
|
303
284
|
const expectedName = sceneLocalName(scenePath);
|
|
304
285
|
if (template.name !== expectedName) {
|
|
@@ -315,20 +296,29 @@ function readCompilerScenePairContract(
|
|
|
315
296
|
};
|
|
316
297
|
}
|
|
317
298
|
|
|
318
|
-
let
|
|
299
|
+
let sceneDescriptor = descriptor;
|
|
319
300
|
try {
|
|
320
|
-
|
|
301
|
+
sceneDescriptor ??= extractSceneScriptInterfaces([{
|
|
302
|
+
scene: scenePath,
|
|
303
|
+
fileName: absoluteScriptPath,
|
|
304
|
+
source: readTextFile(absoluteScriptPath),
|
|
305
|
+
}])[scenePath];
|
|
321
306
|
} catch (error) {
|
|
322
307
|
return {
|
|
323
308
|
diagnostics: [compilerSceneScriptContractDiagnostic(error)],
|
|
324
309
|
};
|
|
325
310
|
}
|
|
326
|
-
if (
|
|
311
|
+
if (!sceneDescriptor) {
|
|
327
312
|
return {
|
|
328
|
-
diagnostics: [
|
|
313
|
+
diagnostics: [compilerSceneScriptContractDiagnostic(new Error('No @scene decorator found.'))],
|
|
329
314
|
};
|
|
330
315
|
}
|
|
331
|
-
|
|
316
|
+
if (sceneDescriptor.className !== template.name) {
|
|
317
|
+
return {
|
|
318
|
+
diagnostics: [compilerSceneClassDiagnostic(template.name, sceneDescriptor.className)],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
const missingParts = findMissingScenePartReferences(template, sceneDescriptor.parts);
|
|
332
322
|
if (missingParts.length > 0) {
|
|
333
323
|
return {
|
|
334
324
|
diagnostics: missingParts.map((part) => compilerScenePartDiagnostic(part.property, part.id)),
|
|
@@ -337,7 +327,7 @@ function readCompilerScenePairContract(
|
|
|
337
327
|
|
|
338
328
|
return {
|
|
339
329
|
diagnostics: [],
|
|
340
|
-
sceneInterface:
|
|
330
|
+
sceneInterface: sceneDescriptor.interface,
|
|
341
331
|
};
|
|
342
332
|
}
|
|
343
333
|
|
|
@@ -452,71 +442,6 @@ function validateAllCompilerScenes(root: string) {
|
|
|
452
442
|
};
|
|
453
443
|
}
|
|
454
444
|
|
|
455
|
-
function nodeLocator(node: NodeSpec) {
|
|
456
|
-
return node.id ?? node.key ?? node.name ?? '';
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
function summarizeNode(node: NodeSpec, depth = 0): NodeSummary {
|
|
460
|
-
const children = node.kind === 'container' ? node.children ?? [] : [];
|
|
461
|
-
return {
|
|
462
|
-
id: node.id,
|
|
463
|
-
key: node.key,
|
|
464
|
-
role: node.role,
|
|
465
|
-
name: node.name,
|
|
466
|
-
kind: node.kind,
|
|
467
|
-
locator: nodeLocator(node),
|
|
468
|
-
depth,
|
|
469
|
-
transform: node.transform,
|
|
470
|
-
components: (node.components ?? []).map((component) => ({
|
|
471
|
-
id: component.id,
|
|
472
|
-
type: component.type,
|
|
473
|
-
propKeys: Object.keys(component.props ?? {}),
|
|
474
|
-
})),
|
|
475
|
-
childCount: children.length,
|
|
476
|
-
children: children.map((child) => summarizeNode(child, depth + 1)),
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function collectNodes(node: NodeSpec, depth = 0, parent?: string): Array<{
|
|
481
|
-
id?: string;
|
|
482
|
-
key?: string;
|
|
483
|
-
role?: string;
|
|
484
|
-
name?: string;
|
|
485
|
-
kind: NodeSpec['kind'];
|
|
486
|
-
locator: string;
|
|
487
|
-
depth: number;
|
|
488
|
-
parent?: string;
|
|
489
|
-
componentCount: number;
|
|
490
|
-
childCount: number;
|
|
491
|
-
}> {
|
|
492
|
-
return [
|
|
493
|
-
{
|
|
494
|
-
id: node.id,
|
|
495
|
-
key: node.key,
|
|
496
|
-
role: node.role,
|
|
497
|
-
name: node.name,
|
|
498
|
-
kind: node.kind,
|
|
499
|
-
locator: nodeLocator(node),
|
|
500
|
-
depth,
|
|
501
|
-
parent,
|
|
502
|
-
componentCount: node.components?.length ?? 0,
|
|
503
|
-
childCount: node.kind === 'container' ? node.children?.length ?? 0 : 0,
|
|
504
|
-
},
|
|
505
|
-
...(node.kind === 'container' ? (node.children ?? []).flatMap((child) => collectNodes(child, depth + 1, nodeLocator(node))) : []),
|
|
506
|
-
];
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
function summarizeScene(scene: SceneSpec) {
|
|
510
|
-
const nodes = collectNodes(scene.root);
|
|
511
|
-
return {
|
|
512
|
-
name: scene.name,
|
|
513
|
-
version: scene.version,
|
|
514
|
-
nodeCount: nodes.length,
|
|
515
|
-
componentCount: nodes.reduce((sum, node) => sum + node.componentCount, 0),
|
|
516
|
-
root: summarizeNode(scene.root),
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
|
|
520
445
|
function walkFiles(root: string, directory: string, depth: number, maxDepth: number, results: ProjectFileSummary[]) {
|
|
521
446
|
if (depth > maxDepth) {
|
|
522
447
|
return;
|
|
@@ -548,6 +473,69 @@ function walkFiles(root: string, directory: string, depth: number, maxDepth: num
|
|
|
548
473
|
}
|
|
549
474
|
}
|
|
550
475
|
|
|
476
|
+
function compilerNodeChildren(node: SceneTemplateNode): SceneTemplateNode[] {
|
|
477
|
+
if (node.kind === 'pixi') {
|
|
478
|
+
return node.children;
|
|
479
|
+
}
|
|
480
|
+
if (node.kind === 'sceneInstance') {
|
|
481
|
+
return Object.values(node.slots).flat();
|
|
482
|
+
}
|
|
483
|
+
return [];
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function compilerNodeSegment(index: number, node: SceneTemplateNode) {
|
|
487
|
+
if (node.kind === 'slotOutlet') {
|
|
488
|
+
return `${index}:slot:${node.name}`;
|
|
489
|
+
}
|
|
490
|
+
return `${index}:${node.id ?? node.kind}`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function collectDetailedCompilerNodes(
|
|
494
|
+
nodes: readonly SceneTemplateNode[],
|
|
495
|
+
depth = 0,
|
|
496
|
+
parent?: string,
|
|
497
|
+
): DetailedCompilerNode[] {
|
|
498
|
+
return nodes.flatMap((node, index) => {
|
|
499
|
+
const locator = parent ? `${parent}/${compilerNodeSegment(index, node)}` : compilerNodeSegment(index, node);
|
|
500
|
+
const children = node.kind === 'pixi'
|
|
501
|
+
? collectDetailedCompilerNodes(node.children, depth + 1, locator)
|
|
502
|
+
: node.kind === 'sceneInstance'
|
|
503
|
+
? Object.entries(node.slots).flatMap(([slot, slotChildren]) => (
|
|
504
|
+
collectDetailedCompilerNodes(slotChildren, depth + 1, `${locator}/slot:${slot}`)
|
|
505
|
+
))
|
|
506
|
+
: [];
|
|
507
|
+
return [
|
|
508
|
+
{
|
|
509
|
+
...structuredClone(node),
|
|
510
|
+
locator,
|
|
511
|
+
depth,
|
|
512
|
+
parent,
|
|
513
|
+
childCount: compilerNodeChildren(node).length,
|
|
514
|
+
},
|
|
515
|
+
...children,
|
|
516
|
+
];
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function createBlankCompilerSceneSource(sceneName: string) {
|
|
521
|
+
return [
|
|
522
|
+
`<Scene name="${sceneName}" width="960" height="540">`,
|
|
523
|
+
'</Scene>',
|
|
524
|
+
'',
|
|
525
|
+
].join('\n');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function createBlankCompilerSceneScript(sceneName: string) {
|
|
529
|
+
return [
|
|
530
|
+
"import { Group } from 'pixifact/runtime';",
|
|
531
|
+
"import { scene } from 'pixifact/compiler';",
|
|
532
|
+
'',
|
|
533
|
+
'@scene()',
|
|
534
|
+
`export class ${sceneName} extends Group {}`,
|
|
535
|
+
'',
|
|
536
|
+
].join('\n');
|
|
537
|
+
}
|
|
538
|
+
|
|
551
539
|
export function createPixifactAutomation() {
|
|
552
540
|
return {
|
|
553
541
|
getProjectSummary(input: unknown) {
|
|
@@ -565,12 +553,15 @@ export function createPixifactAutomation() {
|
|
|
565
553
|
|
|
566
554
|
getScene(input: unknown) {
|
|
567
555
|
const args = assertRecord(input, 'input') as ToolInput;
|
|
568
|
-
const
|
|
556
|
+
const loaded = loadCompilerScene(args.projectRoot, args.scenePath);
|
|
557
|
+
const template = parseSceneTemplate(loaded.content);
|
|
569
558
|
return {
|
|
570
|
-
|
|
571
|
-
sourceType:
|
|
572
|
-
|
|
573
|
-
|
|
559
|
+
ok: true,
|
|
560
|
+
sourceType: 'compiler-scene',
|
|
561
|
+
scenePath: loaded.scenePath,
|
|
562
|
+
revision: createSceneRevision(loaded.content),
|
|
563
|
+
template,
|
|
564
|
+
summary: inspectSceneTemplate(template),
|
|
574
565
|
};
|
|
575
566
|
},
|
|
576
567
|
|
|
@@ -578,20 +569,26 @@ export function createPixifactAutomation() {
|
|
|
578
569
|
const args = assertRecord(input, 'input') as ToolInput;
|
|
579
570
|
const { root, target } = resolveProjectPath(args.projectRoot, args.scenePath);
|
|
580
571
|
const sceneName = assertString(args.name, 'name');
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
572
|
+
const scenePath = normalizeSceneAssetId(path.relative(root, target));
|
|
573
|
+
const sourceRootFailure = compilerSceneSourceRootFailure(scenePath);
|
|
574
|
+
if (sourceRootFailure) {
|
|
575
|
+
return sourceRootFailure;
|
|
576
|
+
}
|
|
577
|
+
const scriptPath = path.resolve(root, pairedSceneScriptPath(scenePath));
|
|
578
|
+
const source = createBlankCompilerSceneSource(sceneName);
|
|
579
|
+
const template = parseSceneTemplate(source);
|
|
580
|
+
|
|
581
|
+
assertNewFile(target, 'Scene file already exists.');
|
|
582
|
+
assertNewFile(scriptPath, 'Scene script already exists.');
|
|
583
|
+
writeNewTextFile(target, source, 'Scene file already exists.');
|
|
584
|
+
writeNewTextFile(scriptPath, createBlankCompilerSceneScript(sceneName), 'Scene script already exists.');
|
|
590
585
|
return {
|
|
591
586
|
ok: true,
|
|
592
|
-
scenePath
|
|
593
|
-
|
|
594
|
-
|
|
587
|
+
scenePath,
|
|
588
|
+
scriptPath: toPosixPath(path.relative(root, scriptPath)),
|
|
589
|
+
revision: createSceneRevision(source),
|
|
590
|
+
template,
|
|
591
|
+
summary: inspectSceneTemplate(template),
|
|
595
592
|
};
|
|
596
593
|
},
|
|
597
594
|
|
|
@@ -599,11 +596,16 @@ export function createPixifactAutomation() {
|
|
|
599
596
|
const args = assertRecord(input, 'input') as ToolInput;
|
|
600
597
|
const loaded = loadCompilerScene(args.projectRoot, args.scenePath);
|
|
601
598
|
const template = parseSceneTemplate(loaded.content);
|
|
599
|
+
const sceneInterface = collectCompilerSceneInterfaces(loaded.root)[loaded.scenePath];
|
|
600
|
+
if (!sceneInterface) {
|
|
601
|
+
throw new Error(`Scene "${loaded.scenePath}" requires a readable paired @scene contract.`);
|
|
602
|
+
}
|
|
602
603
|
return {
|
|
603
604
|
ok: true,
|
|
604
605
|
scenePath: loaded.scenePath,
|
|
605
606
|
revision: createSceneRevision(loaded.content),
|
|
606
607
|
summary: inspectSceneTemplate(template),
|
|
608
|
+
interface: sceneInterface,
|
|
607
609
|
};
|
|
608
610
|
},
|
|
609
611
|
|
|
@@ -621,9 +623,10 @@ export function createPixifactAutomation() {
|
|
|
621
623
|
|
|
622
624
|
inspectNode(input: unknown) {
|
|
623
625
|
const args = assertRecord(input, 'input') as ToolInput;
|
|
624
|
-
const
|
|
626
|
+
const loaded = loadCompilerScene(args.projectRoot, args.scenePath);
|
|
627
|
+
const template = parseSceneTemplate(loaded.content);
|
|
625
628
|
const nodeId = assertString(args.node, 'node');
|
|
626
|
-
const node =
|
|
629
|
+
const node = collectDetailedCompilerNodes(template.children).find((item) => item.locator === nodeId);
|
|
627
630
|
if (!node) {
|
|
628
631
|
throw new Error(`Node "${nodeId}" was not found.`);
|
|
629
632
|
}
|
|
@@ -631,16 +634,3 @@ export function createPixifactAutomation() {
|
|
|
631
634
|
},
|
|
632
635
|
};
|
|
633
636
|
}
|
|
634
|
-
|
|
635
|
-
function collectDetailedNodes(node: NodeSpec, depth = 0, parent?: string): DetailedNode[] {
|
|
636
|
-
return [
|
|
637
|
-
{
|
|
638
|
-
...structuredClone(node),
|
|
639
|
-
locator: nodeLocator(node),
|
|
640
|
-
depth,
|
|
641
|
-
parent,
|
|
642
|
-
childCount: node.kind === 'container' ? node.children?.length ?? 0 : 0,
|
|
643
|
-
},
|
|
644
|
-
...(node.kind === 'container' ? (node.children ?? []).flatMap((child) => collectDetailedNodes(child, depth + 1, nodeLocator(node))) : []),
|
|
645
|
-
];
|
|
646
|
-
}
|
package/src/pixifact-cli.ts
CHANGED
|
@@ -220,7 +220,7 @@ async function executeFileCommand(positionals: string[], flags: Record<string, s
|
|
|
220
220
|
}
|
|
221
221
|
|
|
222
222
|
if (area === 'scene' && action === 'get') {
|
|
223
|
-
return automation.
|
|
223
|
+
return automation.inspectCompilerScene({
|
|
224
224
|
projectRoot: requireFlag(flags, 'project-root'),
|
|
225
225
|
scenePath: requireFlag(flags, 'scene'),
|
|
226
226
|
});
|