pixifact-cli 0.7.0 → 0.8.0
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 +3 -0
- package/package.json +2 -2
- package/src/pixifact-cli.ts +32 -1
- package/src/runtimeSession.ts +60 -0
- package/src/viteTarget.ts +2 -2
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ pixifact scene validate --all
|
|
|
38
38
|
```bash
|
|
39
39
|
pixifact runtime list
|
|
40
40
|
pixifact runtime tree --output .pixifact/runtime/tree.json
|
|
41
|
+
pixifact runtime screenshot
|
|
41
42
|
pixifact runtime node <pixi-uid>
|
|
42
43
|
pixifact runtime state
|
|
43
44
|
pixifact runtime logs --after <seq>
|
|
@@ -49,6 +50,8 @@ pixifact runtime input key Space
|
|
|
49
50
|
|
|
50
51
|
`runtime tree` 默认将节点树输出到终端;传入 `--output <json-path>` 时,改为保存运行时快照 JSON,终端只返回路径和采集元数据。快照包含 `schemaVersion`、`capturedAt`、当前 `runtimeId` 和 `root` 节点树,适合 Agent 使用文本搜索或 JSON 工具分析。快照中的 `runtimeId` 和 PixiJS `uid` 只对本次运行有效,不应写回项目源文件。
|
|
51
52
|
|
|
53
|
+
`runtime screenshot` 默认将当前 PixiJS Canvas 的 PNG 截图写入项目根下的 `.pixifact/runtime/frame.png`,也可用 `--output <png-path>` 指定路径。截图使用当前 `app.screen` 逻辑尺寸和 renderer 背景色,只包含 `app.stage` 的 PixiJS 渲染内容,不包含浏览器 UI、HTML/CSS 或 DOM overlay;多个页面时添加 `--runtime <runtime-id>`。
|
|
54
|
+
|
|
52
55
|
## 浏览器 Editor
|
|
53
56
|
|
|
54
57
|
在目标项目根目录运行:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Pixifact Scene 自动化 CLI。",
|
|
5
5
|
"packageManager": "bun@1.3.13",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"start": "bun src/pixifact-cli.ts"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"pixifact": "^0.
|
|
35
|
+
"pixifact": "^0.8.0",
|
|
36
36
|
"vite": "^8.0.10"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/pixifact-cli.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { CompileSceneError, compileScenes } from 'pixifact/compiler-node';
|
|
|
8
8
|
import type { SceneValidationDiagnostic } from 'pixifact/compiler';
|
|
9
9
|
import { startEditorServer } from './editorServer';
|
|
10
10
|
import { captureEditorScreenshot, queryEditorContext } from './editorSession';
|
|
11
|
-
import { queryRuntime, queryRuntimeList } from './runtimeSession';
|
|
11
|
+
import { captureRuntimeScreenshot, queryRuntime, queryRuntimeList } from './runtimeSession';
|
|
12
12
|
import type { RuntimeLogLevel, RuntimeRequest } from 'pixifact/runtime-dev';
|
|
13
13
|
import {
|
|
14
14
|
buildPixifactTarget,
|
|
@@ -23,6 +23,7 @@ type Automation = ReturnType<typeof createPixifactAutomation>;
|
|
|
23
23
|
interface CliOptions {
|
|
24
24
|
automation?: Automation;
|
|
25
25
|
captureEditorScreenshot?: typeof captureEditorScreenshot;
|
|
26
|
+
captureRuntimeScreenshot?: typeof captureRuntimeScreenshot;
|
|
26
27
|
onDevEvent?: (event: PixifactDevEvent) => void;
|
|
27
28
|
readEditorContext?: typeof queryEditorContext;
|
|
28
29
|
listRuntimes?: typeof queryRuntimeList;
|
|
@@ -46,6 +47,8 @@ interface ParsedArgs {
|
|
|
46
47
|
flags: Record<string, string | true>;
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
const runtimeScreenshotDefaultOutput = '.pixifact/runtime/frame.png';
|
|
51
|
+
|
|
49
52
|
function parseArgs(argv: string[]): ParsedArgs {
|
|
50
53
|
const positionals: string[] = [];
|
|
51
54
|
const flags: Record<string, string | true> = {};
|
|
@@ -274,6 +277,7 @@ async function executeFileCommand(
|
|
|
274
277
|
captureScreenshot: typeof captureEditorScreenshot = captureEditorScreenshot,
|
|
275
278
|
listRuntimes: typeof queryRuntimeList = queryRuntimeList,
|
|
276
279
|
sendRuntimeRequest: typeof queryRuntime = queryRuntime,
|
|
280
|
+
captureRuntime: typeof captureRuntimeScreenshot = captureRuntimeScreenshot,
|
|
277
281
|
) {
|
|
278
282
|
const [area, action] = positionals;
|
|
279
283
|
const projectRoot = projectRootFlag(flags);
|
|
@@ -282,6 +286,30 @@ async function executeFileCommand(
|
|
|
282
286
|
return listRuntimes({ projectRoot });
|
|
283
287
|
}
|
|
284
288
|
|
|
289
|
+
if (area === 'runtime' && action === 'screenshot') {
|
|
290
|
+
assertAllowedFlags(flags, ['output', 'project-root', 'runtime'], 'runtime screenshot');
|
|
291
|
+
const output = flags.output === undefined
|
|
292
|
+
? path.resolve(projectRoot, runtimeScreenshotDefaultOutput)
|
|
293
|
+
: path.resolve(requireFlag(flags, 'output'));
|
|
294
|
+
const screenshot = await captureRuntime({
|
|
295
|
+
projectRoot,
|
|
296
|
+
runtimeId: optionalFlag(flags, 'runtime'),
|
|
297
|
+
});
|
|
298
|
+
if (!screenshot.ok) return screenshot;
|
|
299
|
+
const capturedAt = new Date().toISOString();
|
|
300
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
301
|
+
fs.writeFileSync(output, screenshot.data);
|
|
302
|
+
return {
|
|
303
|
+
ok: true,
|
|
304
|
+
runtimeId: screenshot.runtimeId,
|
|
305
|
+
width: screenshot.width,
|
|
306
|
+
height: screenshot.height,
|
|
307
|
+
bytes: screenshot.data.byteLength,
|
|
308
|
+
output,
|
|
309
|
+
capturedAt,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
285
313
|
if (area === 'runtime' && action !== undefined) {
|
|
286
314
|
const runtimeId = optionalFlag(flags, 'runtime');
|
|
287
315
|
let request: RuntimeRequest;
|
|
@@ -525,6 +553,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
525
553
|
runtimeCommands: [
|
|
526
554
|
'runtime list',
|
|
527
555
|
'runtime tree [--output <json-path>] [--runtime <runtime-id>]',
|
|
556
|
+
'runtime screenshot [--output <png-path>] [--runtime <runtime-id>]',
|
|
528
557
|
'runtime node <pixi-uid> [--runtime <runtime-id>]',
|
|
529
558
|
'runtime state [--runtime <runtime-id>]',
|
|
530
559
|
'runtime logs [--after <seq>] [--level <level>] [--runtime <runtime-id>]',
|
|
@@ -544,6 +573,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
544
573
|
nodeLocatorSource: 'scene inspect --scene <scene-path> returns node locator values',
|
|
545
574
|
defaults: {
|
|
546
575
|
projectRoot: 'current working directory',
|
|
576
|
+
runtimeScreenshotOutput: runtimeScreenshotDefaultOutput,
|
|
547
577
|
},
|
|
548
578
|
}),
|
|
549
579
|
stderr: '',
|
|
@@ -561,6 +591,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
561
591
|
options.captureEditorScreenshot,
|
|
562
592
|
options.listRuntimes,
|
|
563
593
|
options.queryRuntime,
|
|
594
|
+
options.captureRuntimeScreenshot,
|
|
564
595
|
);
|
|
565
596
|
if (isFailedResult(result)) {
|
|
566
597
|
return {
|
package/src/runtimeSession.ts
CHANGED
|
@@ -4,6 +4,8 @@ import {
|
|
|
4
4
|
} from 'pixifact/compiler-node';
|
|
5
5
|
import type { RuntimePageDescriptor, RuntimeRequest } from 'pixifact/runtime-dev';
|
|
6
6
|
|
|
7
|
+
const pngSignature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
8
|
+
|
|
7
9
|
interface RuntimeSessionLookupOptions {
|
|
8
10
|
projectRoot: string;
|
|
9
11
|
sessionsRoot?: string;
|
|
@@ -131,3 +133,61 @@ export async function queryRuntime(options: RuntimeQueryOptions) {
|
|
|
131
133
|
}),
|
|
132
134
|
});
|
|
133
135
|
}
|
|
136
|
+
|
|
137
|
+
export async function captureRuntimeScreenshot(
|
|
138
|
+
options: RuntimeSessionLookupOptions & { runtimeId?: string },
|
|
139
|
+
): Promise<
|
|
140
|
+
| { ok: true; runtimeId: string; width: number; height: number; data: Uint8Array }
|
|
141
|
+
| { ok: false; error: string; [key: string]: unknown }
|
|
142
|
+
> {
|
|
143
|
+
const active = await activeDescriptor(options);
|
|
144
|
+
if (!active.ok) return active;
|
|
145
|
+
const fetcher = options.fetch ?? fetch;
|
|
146
|
+
const listed = await runtimeHostFetch(active.descriptor, 'list', fetcher);
|
|
147
|
+
if (listed.ok !== true || !Array.isArray(listed.runtimes)) return listed;
|
|
148
|
+
const selected = selectRuntime(
|
|
149
|
+
listed.runtimes as Array<RuntimePageDescriptor & { connectedAt: string }>,
|
|
150
|
+
options.runtimeId,
|
|
151
|
+
);
|
|
152
|
+
if (!selected.ok) return selected;
|
|
153
|
+
|
|
154
|
+
let response: Response;
|
|
155
|
+
try {
|
|
156
|
+
response = await fetcher(`${active.descriptor.origin}/__pixifact_runtime__/screenshot`, {
|
|
157
|
+
method: 'POST',
|
|
158
|
+
headers: {
|
|
159
|
+
authorization: `Bearer ${active.descriptor.token}`,
|
|
160
|
+
'content-type': 'application/json',
|
|
161
|
+
},
|
|
162
|
+
body: JSON.stringify({ runtimeId: selected.runtime.runtimeId }),
|
|
163
|
+
});
|
|
164
|
+
} catch {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
error: 'Pixifact Runtime Vite server is not reachable for this project.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
return await response.json() as { ok: false; error: string; [key: string]: unknown };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const runtimeId = response.headers.get('x-pixifact-runtime');
|
|
175
|
+
const width = Number(response.headers.get('x-pixifact-width'));
|
|
176
|
+
const height = Number(response.headers.get('x-pixifact-height'));
|
|
177
|
+
if (
|
|
178
|
+
response.headers.get('content-type') !== 'image/png'
|
|
179
|
+
|| runtimeId !== selected.runtime.runtimeId
|
|
180
|
+
|| !Number.isInteger(width)
|
|
181
|
+
|| width <= 0
|
|
182
|
+
|| !Number.isInteger(height)
|
|
183
|
+
|| height <= 0
|
|
184
|
+
) {
|
|
185
|
+
return { ok: false, error: 'Pixifact Runtime Host returned invalid screenshot metadata.' };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
189
|
+
if (!Buffer.from(data.subarray(0, pngSignature.length)).equals(pngSignature)) {
|
|
190
|
+
return { ok: false, error: 'Pixifact Runtime Host did not return PNG screenshot data.' };
|
|
191
|
+
}
|
|
192
|
+
return { ok: true, runtimeId, width, height, data };
|
|
193
|
+
}
|
package/src/viteTarget.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { access, readFile, readdir, stat } from 'node:fs/promises';
|
|
1
|
+
import { access, readFile, readdir, realpath, stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
build,
|
|
@@ -55,7 +55,7 @@ export class PixifactTargetError extends Error {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export async function validatePixifactTarget(projectRootInput: string, mode: string) {
|
|
58
|
-
const projectRoot = path.resolve(projectRootInput);
|
|
58
|
+
const projectRoot = await realpath(path.resolve(projectRootInput));
|
|
59
59
|
const { platform } = loadPixifactEnv(projectRoot, mode);
|
|
60
60
|
const config = parsePixifactProjectConfig(JSON.parse(await readFile(
|
|
61
61
|
path.join(projectRoot, pixifactProjectConfigFileName),
|