pixifact-cli 0.3.0 → 0.5.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.
@@ -3,16 +3,23 @@ import { createPixifactAutomation } from './automation';
3
3
  import { hintForCommandError } from 'pixifact';
4
4
  import { CompileSceneError, compileScenes } from 'pixifact/compiler-node';
5
5
  import type { SceneValidationDiagnostic } from 'pixifact/compiler';
6
- import { createLiveBridgeServer } from './liveBridgeServer';
6
+ import { startEditorServer } from './editorServer';
7
+ import { queryEditorContext } from './editorSession';
8
+ import {
9
+ buildWechatTarget,
10
+ devWechatTarget,
11
+ validateWechatTarget,
12
+ type WechatDevEvent,
13
+ WechatTargetError,
14
+ } from './wechatTarget';
7
15
 
8
16
  type Automation = ReturnType<typeof createPixifactAutomation>;
9
- type LiveBridge = Pick<ReturnType<typeof createLiveBridgeServer>, 'connected' | 'callAction' | 'stop'> & {
10
- waitForConnection?: (timeoutMs?: number) => Promise<void>;
11
- };
12
17
 
13
18
  interface CliOptions {
14
19
  automation?: Automation;
15
- liveBridge?: LiveBridge;
20
+ onWechatDevEvent?: (event: WechatDevEvent) => void;
21
+ readEditorContext?: typeof queryEditorContext;
22
+ startEditor?: typeof startEditorServer;
16
23
  }
17
24
 
18
25
  interface CliResult {
@@ -88,6 +95,15 @@ function isFailedResult(value: unknown): value is CliJsonResult {
88
95
  function compileScenesFailure(error: unknown): CliJsonResult {
89
96
  const message = error instanceof Error ? error.message : String(error);
90
97
  if (error instanceof CompileSceneError) {
98
+ if (error.diagnostics) {
99
+ return {
100
+ ok: false,
101
+ scene: error.scene,
102
+ error: 'Scene compile failed.',
103
+ diagnostics: error.diagnostics,
104
+ hint: 'Fix the listed diagnostics, then run compile-scenes again.',
105
+ };
106
+ }
91
107
  const sourceDiagnostic = sourceDiagnosticFromMessage(error.source ?? '', message);
92
108
  if (sourceDiagnostic) {
93
109
  return {
@@ -205,10 +221,99 @@ function sourcePositionFromMessage(source: string, message: string) {
205
221
  };
206
222
  }
207
223
 
208
- async function executeFileCommand(positionals: string[], flags: Record<string, string | true>, automation: Automation) {
224
+ async function executeFileCommand(
225
+ positionals: string[],
226
+ flags: Record<string, string | true>,
227
+ automation: Automation,
228
+ onWechatDevEvent?: (event: WechatDevEvent) => void,
229
+ startEditor: typeof startEditorServer = startEditorServer,
230
+ readEditorContext: typeof queryEditorContext = queryEditorContext,
231
+ ) {
209
232
  const [area, action] = positionals;
210
233
  const projectRoot = projectRootFlag(flags);
211
234
 
235
+ if (area === 'editor' && action === undefined) {
236
+ const session = await startEditor({ projectRoot });
237
+ return {
238
+ ok: true,
239
+ projectRoot,
240
+ url: session.url,
241
+ };
242
+ }
243
+
244
+ if (area === 'editor' && action === 'context') {
245
+ return readEditorContext({ projectRoot });
246
+ }
247
+
248
+ if ((area === 'build' || area === 'dev' || area === 'validate') && action === undefined) {
249
+ const target = requireFlag(flags, 'target');
250
+ if (target !== 'wechat') {
251
+ throw new Error(`Unknown Pixifact target "${target}".`);
252
+ }
253
+ if (area === 'validate') {
254
+ const result = await validateWechatTarget(projectRoot);
255
+ return result.diagnostics.length > 0
256
+ ? {
257
+ ok: false,
258
+ target,
259
+ diagnostics: result.diagnostics,
260
+ hint: 'Fix the listed WeChat target diagnostics, then validate again.',
261
+ }
262
+ : { ok: true, projectRoot, target };
263
+ }
264
+ if (area === 'dev') {
265
+ if (flags.mode !== undefined) {
266
+ throw new Error('dev --target wechat always uses development mode; remove --mode.');
267
+ }
268
+ try {
269
+ const session = await devWechatTarget(projectRoot, onWechatDevEvent);
270
+ return {
271
+ ok: true,
272
+ projectRoot,
273
+ target,
274
+ mode: 'development',
275
+ watching: true,
276
+ report: session.initialReport,
277
+ };
278
+ } catch (error) {
279
+ if (error instanceof WechatTargetError) {
280
+ return {
281
+ ok: false,
282
+ target,
283
+ error: error.message,
284
+ diagnostics: error.diagnostics,
285
+ hint: 'Fix the listed WeChat target diagnostics, then start dev again.',
286
+ };
287
+ }
288
+ throw error;
289
+ }
290
+ }
291
+ const mode = flags.mode === undefined ? 'production' : requireFlag(flags, 'mode');
292
+ if (mode !== 'development' && mode !== 'production') {
293
+ throw new Error('--mode must be development or production.');
294
+ }
295
+ try {
296
+ return {
297
+ ok: true,
298
+ projectRoot,
299
+ target,
300
+ mode,
301
+ report: await buildWechatTarget(projectRoot, mode),
302
+ };
303
+ } catch (error) {
304
+ if (error instanceof WechatTargetError) {
305
+ return {
306
+ ok: false,
307
+ target,
308
+ error: error.message,
309
+ diagnostics: error.diagnostics,
310
+ hint: 'Fix the listed WeChat target diagnostics, then build again.',
311
+ };
312
+ }
313
+ throw error;
314
+ }
315
+ }
316
+
212
317
  if (area === 'compile-scenes' && action === undefined) {
213
318
  try {
214
319
  await compileScenes({ projectRoot });
@@ -268,32 +373,7 @@ async function executeFileCommand(positionals: string[], flags: Record<string, s
268
373
  throw new Error(`Unknown Pixifact CLI command "${positionals.join(' ')}".`);
269
374
  }
270
375
 
271
- async function executeLiveCommand(positionals: string[], flags: Record<string, string | true>, bridge: LiveBridge) {
272
- if (!bridge.connected && bridge.waitForConnection) {
273
- await bridge.waitForConnection();
274
- }
275
- if (!bridge.connected) {
276
- throw new Error('No live Pixifact editor is connected.');
277
- }
278
-
279
- const [area, action] = positionals;
280
- if (area === 'summary' && action === undefined) {
281
- return bridge.callAction('summary', {});
282
- }
283
- if (area === 'scene' && action === 'get') {
284
- return bridge.callAction('scene.get', {});
285
- }
286
- if (area === 'node' && action === 'inspect') {
287
- return bridge.callAction('node.inspect', {
288
- node: requireFlag(flags, 'node'),
289
- });
290
- }
291
-
292
- throw new Error(`Unknown Pixifact live command "${positionals.join(' ')}".`);
293
- }
294
-
295
376
  export async function executePixifactCli(argv: string[], options: CliOptions = {}): Promise<CliResult> {
296
- let ownedBridge: ReturnType<typeof createLiveBridgeServer> | undefined;
297
377
  try {
298
378
  const parsed = parseArgs(argv);
299
379
  if (parsed.flags.help === true) {
@@ -305,19 +385,19 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
305
385
  'scene inspect --scene <scene-path>',
306
386
  'scene validate --scene <scene-path>',
307
387
  'compile-scenes',
388
+ 'validate --target wechat',
389
+ 'build --target wechat',
390
+ 'dev --target wechat',
308
391
  ],
309
392
  aiValidationAlternatives: [
310
393
  'scene validate --all',
311
394
  ],
312
395
  auxiliaryCommands: [
396
+ 'editor',
397
+ 'editor context',
313
398
  'scene create --scene <scene-path> --name <SceneName>',
314
399
  'node inspect --scene <scene-path> --node <locator>',
315
400
  ],
316
- liveContextCommands: [
317
- 'live summary',
318
- 'live scene get',
319
- 'live node inspect --node <locator>',
320
- ],
321
401
  defaults: {
322
402
  projectRoot: 'current working directory',
323
403
  },
@@ -327,14 +407,14 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
327
407
  }
328
408
 
329
409
  const automation = options.automation ?? createPixifactAutomation();
330
- const isLive = parsed.positionals[0] === 'live';
331
- const result = isLive
332
- ? await executeLiveCommand(
333
- parsed.positionals.slice(1),
334
- parsed.flags,
335
- options.liveBridge ?? (ownedBridge = createLiveBridgeServer()),
336
- )
337
- : await executeFileCommand(parsed.positionals, parsed.flags, automation);
410
+ const result = await executeFileCommand(
411
+ parsed.positionals,
412
+ parsed.flags,
413
+ automation,
414
+ options.onWechatDevEvent,
415
+ options.startEditor,
416
+ options.readEditorContext,
417
+ );
338
418
  if (isFailedResult(result)) {
339
419
  return {
340
420
  exitCode: 1,
@@ -357,13 +437,13 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
357
437
  hint: hintForCommandError(error instanceof Error ? error.message : String(error)),
358
438
  }),
359
439
  };
360
- } finally {
361
- ownedBridge?.stop();
362
440
  }
363
441
  }
364
442
 
365
443
  if (import.meta.url === `file://${process.argv[1]}`) {
366
- const result = await executePixifactCli(process.argv.slice(2));
444
+ const result = await executePixifactCli(process.argv.slice(2), {
445
+ onWechatDevEvent: (event) => process.stdout.write(jsonLine(event)),
446
+ });
367
447
  process.stdout.write(result.stdout);
368
448
  process.stderr.write(result.stderr);
369
449
  process.exitCode = result.exitCode;