pixifact-cli 0.7.0 → 0.9.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 +10 -1
- package/editor/assets/browserAll-CG--e3R8.js +1 -0
- package/editor/assets/index-BUZ_WtnT.js +1130 -0
- package/editor/assets/init-CzHI7RKj.js +1 -0
- package/editor/assets/webworkerAll-D-3iZ4fC.js +1 -0
- package/editor/index.html +1 -1
- package/package.json +2 -2
- package/src/automation.ts +1 -2
- package/src/editorServer.ts +47 -0
- package/src/pixifact-cli.ts +42 -4
- package/src/runtimeSession.ts +60 -0
- package/src/viteTarget.ts +2 -2
- package/editor/assets/browserAll-DePxC4RK.js +0 -1
- package/editor/assets/index-DoPhYWF1.js +0 -1130
- package/editor/assets/init-CuKNsO_k.js +0 -1
- package/editor/assets/webworkerAll-DM9kdU0t.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,r as t,t as n,u as r}from"./index-BUZ_WtnT.js";r.add(n,t),r.add(e);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./index-BUZ_WtnT.js";import"./init-CzHI7RKj.js";
|
package/editor/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Pixifact Editor</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-BUZ_WtnT.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-D3t5XxiB.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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.9.0",
|
|
36
36
|
"vite": "^8.0.10"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/automation.ts
CHANGED
|
@@ -33,7 +33,6 @@ interface ToolInput {
|
|
|
33
33
|
projectRoot?: unknown;
|
|
34
34
|
scenePath?: unknown;
|
|
35
35
|
node?: unknown;
|
|
36
|
-
name?: unknown;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
type CompilerSceneValidationSuccess = ReturnType<typeof validateCompilerSceneFile> & { ok: true };
|
|
@@ -576,8 +575,8 @@ export function createPixifactAutomation() {
|
|
|
576
575
|
createScene(input: unknown) {
|
|
577
576
|
const args = assertRecord(input, 'input') as ToolInput;
|
|
578
577
|
const { root, target } = resolveProjectPath(args.projectRoot, args.scenePath);
|
|
579
|
-
const sceneName = assertString(args.name, 'name');
|
|
580
578
|
const scenePath = normalizeSceneAssetId(path.relative(root, target));
|
|
579
|
+
const sceneName = sceneLocalName(scenePath);
|
|
581
580
|
const sourceRootFailure = compilerSceneSourceRootFailure(scenePath);
|
|
582
581
|
if (sourceRootFailure) {
|
|
583
582
|
return sourceRootFailure;
|
package/src/editorServer.ts
CHANGED
|
@@ -36,6 +36,10 @@ interface EditorProjectFile {
|
|
|
36
36
|
path: string;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
interface EditorUiState {
|
|
40
|
+
assetTreeExpandedDirectories?: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
interface BunServer {
|
|
40
44
|
hostname: string;
|
|
41
45
|
port: number;
|
|
@@ -163,6 +167,35 @@ function requestPath(url: URL) {
|
|
|
163
167
|
return url.searchParams.get('path') ?? '';
|
|
164
168
|
}
|
|
165
169
|
|
|
170
|
+
function editorUiStatePath(projectRoot: string) {
|
|
171
|
+
return path.join(projectRoot, '.pixifact', 'editor', 'ui-state.json');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function parseEditorUiState(value: unknown): EditorUiState {
|
|
175
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
176
|
+
throw new Error('Editor UI state must be a JSON object.');
|
|
177
|
+
}
|
|
178
|
+
const directories = (value as Record<string, unknown>).assetTreeExpandedDirectories;
|
|
179
|
+
if (directories === undefined) return {};
|
|
180
|
+
if (!Array.isArray(directories) || directories.some((directory) => typeof directory !== 'string')) {
|
|
181
|
+
throw new Error('Editor UI state assetTreeExpandedDirectories must be a string array.');
|
|
182
|
+
}
|
|
183
|
+
return { assetTreeExpandedDirectories: directories };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function readEditorUiState(projectRoot: string): EditorUiState {
|
|
187
|
+
const statePath = editorUiStatePath(projectRoot);
|
|
188
|
+
if (!fs.existsSync(statePath)) return {};
|
|
189
|
+
return parseEditorUiState(JSON.parse(fs.readFileSync(statePath, 'utf8')));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function writeEditorUiState(projectRoot: string, state: EditorUiState) {
|
|
193
|
+
const statePath = editorUiStatePath(projectRoot);
|
|
194
|
+
fs.mkdirSync(path.dirname(statePath), { recursive: true });
|
|
195
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n', 'utf8');
|
|
196
|
+
return state;
|
|
197
|
+
}
|
|
198
|
+
|
|
166
199
|
async function readJsonBody(request: Request) {
|
|
167
200
|
const value = await request.json();
|
|
168
201
|
if (!value || typeof value !== 'object') {
|
|
@@ -205,6 +238,17 @@ export function createEditorProjectService(options: EditorProjectServiceOptions)
|
|
|
205
238
|
images: files.filter((file) => file.kind === 'image').map((file) => file.path),
|
|
206
239
|
});
|
|
207
240
|
}
|
|
241
|
+
if (url.pathname === '/api/editor-ui-state' && request.method === 'GET') {
|
|
242
|
+
return jsonResponse(readEditorUiState(projectRoot));
|
|
243
|
+
}
|
|
244
|
+
if (url.pathname === '/api/editor-ui-state' && request.method === 'PUT') {
|
|
245
|
+
const body = await readJsonBody(request);
|
|
246
|
+
const state = parseEditorUiState(body);
|
|
247
|
+
if (!state.assetTreeExpandedDirectories) {
|
|
248
|
+
throw new EditorRequestError(400, 'Editor UI state requires assetTreeExpandedDirectories.');
|
|
249
|
+
}
|
|
250
|
+
return jsonResponse(writeEditorUiState(projectRoot, state));
|
|
251
|
+
}
|
|
208
252
|
if (url.pathname === '/api/scene' && request.method === 'GET') {
|
|
209
253
|
const scenePath = requestPath(url);
|
|
210
254
|
if (path.extname(scenePath).toLowerCase() !== '.scene') {
|
|
@@ -275,6 +319,9 @@ export function watchEditorProject(projectRoot: string, onProjectFileChanged: (p
|
|
|
275
319
|
const watcher = fs.watch(projectRoot, { recursive: true }, (_event, fileName) => {
|
|
276
320
|
if (!fileName) return;
|
|
277
321
|
const filePath = String(fileName);
|
|
322
|
+
if (filePath.split(/[\\/]/).some((segment) => ignoredDirectories.has(segment))) {
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
278
325
|
const absolutePath = path.resolve(projectRoot, filePath);
|
|
279
326
|
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory()) {
|
|
280
327
|
return;
|
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;
|
|
@@ -463,10 +491,17 @@ async function executeFileCommand(
|
|
|
463
491
|
}
|
|
464
492
|
|
|
465
493
|
if (area === 'scene' && action === 'create') {
|
|
494
|
+
assertAllowedFlags(flags, ['project-root'], 'scene create');
|
|
495
|
+
const scenePath = positionals[2];
|
|
496
|
+
if (!scenePath) {
|
|
497
|
+
throw new Error('Scene create requires a project-relative .scene path.');
|
|
498
|
+
}
|
|
499
|
+
if (positionals[3] !== undefined) {
|
|
500
|
+
throw new Error('Scene create accepts exactly one scene path.');
|
|
501
|
+
}
|
|
466
502
|
return automation.createScene({
|
|
467
503
|
projectRoot,
|
|
468
|
-
scenePath
|
|
469
|
-
name: requireFlag(flags, 'name'),
|
|
504
|
+
scenePath,
|
|
470
505
|
});
|
|
471
506
|
}
|
|
472
507
|
|
|
@@ -525,6 +560,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
525
560
|
runtimeCommands: [
|
|
526
561
|
'runtime list',
|
|
527
562
|
'runtime tree [--output <json-path>] [--runtime <runtime-id>]',
|
|
563
|
+
'runtime screenshot [--output <png-path>] [--runtime <runtime-id>]',
|
|
528
564
|
'runtime node <pixi-uid> [--runtime <runtime-id>]',
|
|
529
565
|
'runtime state [--runtime <runtime-id>]',
|
|
530
566
|
'runtime logs [--after <seq>] [--level <level>] [--runtime <runtime-id>]',
|
|
@@ -538,12 +574,13 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
538
574
|
'editor',
|
|
539
575
|
'editor context',
|
|
540
576
|
'editor screenshot --output <png-path>',
|
|
541
|
-
'scene create
|
|
577
|
+
'scene create <scene-path>',
|
|
542
578
|
'node inspect --scene <scene-path> --node <locator>',
|
|
543
579
|
],
|
|
544
580
|
nodeLocatorSource: 'scene inspect --scene <scene-path> returns node locator values',
|
|
545
581
|
defaults: {
|
|
546
582
|
projectRoot: 'current working directory',
|
|
583
|
+
runtimeScreenshotOutput: runtimeScreenshotDefaultOutput,
|
|
547
584
|
},
|
|
548
585
|
}),
|
|
549
586
|
stderr: '',
|
|
@@ -561,6 +598,7 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
561
598
|
options.captureEditorScreenshot,
|
|
562
599
|
options.listRuntimes,
|
|
563
600
|
options.queryRuntime,
|
|
601
|
+
options.captureRuntimeScreenshot,
|
|
564
602
|
);
|
|
565
603
|
if (isFailedResult(result)) {
|
|
566
604
|
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),
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,c as t,i as n,l as r,o as i,s as a,u as o}from"./index-DoPhYWF1.js";import"./init-CuKNsO_k.js";o.add(t),o.mixin(r,a),o.add(i),o.add(e),o.mixin(r,n);
|