@mpgd/cli 0.6.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.
Files changed (51) hide show
  1. package/dist/game-acceptance.d.ts +65 -0
  2. package/dist/game-acceptance.js +245 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +398 -32
  5. package/dist/production-target-readiness.d.ts +8 -0
  6. package/dist/production-target-readiness.js +207 -0
  7. package/package.json +15 -15
  8. package/templates/phaser-game/README.md +71 -1
  9. package/templates/phaser-game/agent/acceptance.md +12 -6
  10. package/templates/phaser-game/agent/brief.md +3 -1
  11. package/templates/phaser-game/agent/game-manifest.json +7 -0
  12. package/templates/phaser-game/apps/target-cloudflare-pages/package.json +3 -3
  13. package/templates/phaser-game/apps/target-devvit/README.md +70 -4
  14. package/templates/phaser-game/apps/target-devvit/devvit.json +8 -1
  15. package/templates/phaser-game/apps/target-devvit/package.json +15 -20
  16. package/templates/phaser-game/apps/target-devvit/src/client/game.html +13 -0
  17. package/templates/phaser-game/apps/target-devvit/src/client/game.ts +3 -0
  18. package/templates/phaser-game/apps/target-devvit/src/client/index.html +13 -0
  19. package/templates/phaser-game/apps/target-devvit/src/client/preview.ts +3 -0
  20. package/templates/phaser-game/apps/target-devvit/src/server/index.ts +228 -116
  21. package/templates/phaser-game/apps/target-devvit/src/server/postOperationStore.ts +9 -0
  22. package/templates/phaser-game/apps/target-devvit/tsconfig.json +7 -2
  23. package/templates/phaser-game/apps/target-devvit/vite.config.ts +41 -0
  24. package/templates/phaser-game/game.html +13 -0
  25. package/templates/phaser-game/gitignore +2 -0
  26. package/templates/phaser-game/index.html +2 -2
  27. package/templates/phaser-game/mpgd.game.json +8 -0
  28. package/templates/phaser-game/mpgd.targets.json +7 -0
  29. package/templates/phaser-game/package.json +10 -4
  30. package/templates/phaser-game/public/manifest.webmanifest +1 -0
  31. package/templates/phaser-game/src/entry.ts +7 -0
  32. package/templates/phaser-game/src/env.d.ts +3 -0
  33. package/templates/phaser-game/src/gameEntry.ts +3 -0
  34. package/templates/phaser-game/src/main.ts +11 -2
  35. package/templates/phaser-game/src/platform/buildGatewayModule.ts +5 -0
  36. package/templates/phaser-game/src/platform/buildGateways/ait.ts +11 -0
  37. package/templates/phaser-game/src/platform/buildGateways/aitSandbox.ts +12 -0
  38. package/templates/phaser-game/src/platform/buildGateways/browser.ts +8 -0
  39. package/templates/phaser-game/src/platform/buildGateways/capacitorAndroid.ts +12 -0
  40. package/templates/phaser-game/src/platform/buildGateways/capacitorIos.ts +12 -0
  41. package/templates/phaser-game/src/platform/buildGateways/reddit.ts +11 -0
  42. package/templates/phaser-game/src/platform/buildGateways/redditSandbox.ts +15 -0
  43. package/templates/phaser-game/src/platform/devvitEntrypoint.ts +57 -0
  44. package/templates/phaser-game/src/platform/devvitInlinePreview.css +75 -0
  45. package/templates/phaser-game/src/platform/gameServices.ts +18 -61
  46. package/templates/phaser-game/src/platform/installPlatform.ts +6 -50
  47. package/templates/phaser-game/src/platform/microsoftStorePwa.ts +99 -0
  48. package/templates/phaser-game/tools/run-game-acceptance.mjs +22 -0
  49. package/templates/phaser-game/vite.config.ts +24 -73
  50. package/templates/phaser-game/vite.shared.ts +217 -0
  51. package/templates/phaser-game/apps/target-devvit/vite.server.config.ts +0 -37
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ import completion from '@gunshi/plugin-completion';
6
6
  import i18n, { defineI18n } from '@gunshi/plugin-i18n';
7
7
  import resources from '@gunshi/resources';
8
8
  import { cli } from 'gunshi';
9
+ import { defaultGameAcceptanceCommandTimeoutMs, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, } from './game-acceptance.js';
10
+ export { renderGameAcceptanceMarkdown, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, defaultGameAcceptanceCommandTimeoutMs, } from './game-acceptance.js';
11
+ export { assertProductionTargetReadiness, } from './production-target-readiness.js';
9
12
  const sourceDir = path.dirname(fileURLToPath(import.meta.url));
10
13
  const packageRoot = resolvePackageRoot(sourceDir);
11
14
  const detectedKitRoot = resolveDefaultKitRoot();
@@ -50,6 +53,15 @@ const supportedBuildTargets = [
50
53
  'reddit',
51
54
  ];
52
55
  const supportedVariants = ['wrapper', 'sync', 'simulator', 'archive'];
56
+ const acceptanceStepIds = {
57
+ check: 'check',
58
+ test: 'test',
59
+ build: 'build',
60
+ graph: 'graph-preflight',
61
+ playtest: 'playtest',
62
+ targetBuild: 'target-build',
63
+ targetSmoke: 'target-smoke',
64
+ };
53
65
  export async function runMpgdCli(args) {
54
66
  await cli([...args], entryCommand, {
55
67
  name: 'mpgd',
@@ -108,8 +120,6 @@ const entryCommand = defineI18n({
108
120
  ko: 'mpgd-kit 스타터와 타깃 워크플로우를 관리합니다.',
109
121
  }),
110
122
  run: () => {
111
- console.log('Use a sub-command: game, target, kit.');
112
- console.log('Run "pnpm mpgd --help" for available commands.');
113
123
  },
114
124
  });
115
125
  const gameCommand = defineI18n({
@@ -213,15 +223,397 @@ const gameCommand = defineI18n({
213
223
  });
214
224
  },
215
225
  }),
226
+ accept: defineI18n({
227
+ name: 'accept',
228
+ description: 'Run the reusable game handoff acceptance workflow.',
229
+ resource: commandResource({
230
+ en: 'Run check, test, build, graph, playtest, and target acceptance with handoff reports.',
231
+ ko: 'check, test, build, graph, playtest, 타깃 인수 검증과 인계 리포트를 실행합니다.',
232
+ }, {
233
+ game: {
234
+ en: 'Game root containing package.json and mpgd.targets.json.',
235
+ ko: 'package.json과 mpgd.targets.json이 있는 게임 루트.',
236
+ },
237
+ targets: {
238
+ en: 'Comma-separated target list, default, or all.',
239
+ ko: '쉼표로 구분한 타깃 목록, default 또는 all.',
240
+ },
241
+ profile: {
242
+ en: 'Target build profile. Defaults to staging.',
243
+ ko: '타깃 빌드 프로필. 기본값은 staging.',
244
+ },
245
+ 'playtest-script': {
246
+ en: 'Optional game-owned package script used for automated playtesting.',
247
+ ko: '자동 플레이테스트에 사용할 선택적 게임 소유 패키지 스크립트.',
248
+ },
249
+ 'report-dir': {
250
+ en: 'JSON and Markdown handoff report directory.',
251
+ ko: 'JSON 및 Markdown 인계 리포트 디렉터리.',
252
+ },
253
+ 'timeout-ms': {
254
+ en: 'Maximum duration for each acceptance command.',
255
+ ko: '각 인수 검증 명령의 최대 실행 시간.',
256
+ },
257
+ 'kit-path': {
258
+ en: 'Path to the mpgd-kit checkout.',
259
+ ko: 'mpgd-kit 체크아웃 경로.',
260
+ },
261
+ }),
262
+ args: {
263
+ game: {
264
+ type: 'positional',
265
+ required: true,
266
+ description: 'Game root containing package.json and mpgd.targets.json.',
267
+ },
268
+ targets: {
269
+ type: 'string',
270
+ required: false,
271
+ default: 'default',
272
+ description: 'Comma-separated target list, default, or all.',
273
+ },
274
+ profile: {
275
+ type: 'string',
276
+ required: false,
277
+ default: 'staging',
278
+ description: 'Target build profile.',
279
+ },
280
+ 'ait-variant': {
281
+ type: 'enum',
282
+ choices: supportedVariants,
283
+ required: false,
284
+ default: 'wrapper',
285
+ description: 'Variant for the ait target.',
286
+ },
287
+ 'ios-variant': {
288
+ type: 'enum',
289
+ choices: supportedVariants,
290
+ required: false,
291
+ description: 'Variant for the ios target.',
292
+ },
293
+ 'playtest-script': {
294
+ type: 'string',
295
+ required: false,
296
+ default: 'playtest',
297
+ description: 'Game-owned package script used for automated playtesting.',
298
+ },
299
+ 'report-dir': {
300
+ type: 'string',
301
+ required: false,
302
+ description: 'JSON and Markdown handoff report directory.',
303
+ },
304
+ 'timeout-ms': {
305
+ type: 'string',
306
+ required: false,
307
+ default: String(defaultGameAcceptanceCommandTimeoutMs),
308
+ description: 'Maximum duration for each acceptance command in milliseconds.',
309
+ },
310
+ 'kit-path': {
311
+ type: 'string',
312
+ required: false,
313
+ description: 'Path to the mpgd-kit checkout.',
314
+ },
315
+ 'skip-test': {
316
+ type: 'boolean',
317
+ required: false,
318
+ description: 'Skip the game-owned test package script.',
319
+ },
320
+ 'skip-graph': {
321
+ type: 'boolean',
322
+ required: false,
323
+ description: 'Skip the kit graph preflight.',
324
+ },
325
+ 'skip-playtest': {
326
+ type: 'boolean',
327
+ required: false,
328
+ description: 'Skip the game-owned playtest package script.',
329
+ },
330
+ 'skip-target-build': {
331
+ type: 'boolean',
332
+ required: false,
333
+ description: 'Skip the target build matrix.',
334
+ },
335
+ 'skip-target-smoke': {
336
+ type: 'boolean',
337
+ required: false,
338
+ description: 'Skip the target smoke matrix.',
339
+ },
340
+ },
341
+ run: (ctx) => {
342
+ const positionals = readLocalPositionals(ctx.positionals, ['game', 'accept']);
343
+ const gameRoot = path.resolve(readRequiredPositional(positionals, 0, 'game'));
344
+ const iosVariant = readOptionalString(ctx.values['ios-variant']);
345
+ const reportDir = readOptionalString(ctx.values['report-dir']);
346
+ const kitPath = readOptionalString(ctx.values['kit-path']);
347
+ const timeoutMs = readPositiveInteger(readOptionalString(ctx.values['timeout-ms'])
348
+ ?? String(defaultGameAcceptanceCommandTimeoutMs), '--timeout-ms');
349
+ acceptGame({
350
+ gameRoot,
351
+ targets: readOptionalString(ctx.values.targets) ?? 'default',
352
+ profile: readOptionalString(ctx.values.profile) ?? 'staging',
353
+ aitVariant: readOptionalString(ctx.values['ait-variant']) ?? 'wrapper',
354
+ ...(iosVariant === undefined ? {} : { iosVariant }),
355
+ playtestScript: readOptionalString(ctx.values['playtest-script']) ?? 'playtest',
356
+ ...(reportDir === undefined ? {} : { reportDir }),
357
+ ...(kitPath === undefined ? {} : { kitPath }),
358
+ timeoutMs,
359
+ skipTest: ctx.values['skip-test'] === true,
360
+ skipGraph: ctx.values['skip-graph'] === true,
361
+ skipPlaytest: ctx.values['skip-playtest'] === true,
362
+ skipTargetBuild: ctx.values['skip-target-build'] === true,
363
+ skipTargetSmoke: ctx.values['skip-target-smoke'] === true,
364
+ });
365
+ },
366
+ }),
367
+ icons: defineI18n({
368
+ name: 'icons',
369
+ description: 'Generate and verify target app icons from the game brand source.',
370
+ resource: commandResource({
371
+ en: 'Generate and verify target app icons from the game brand source.',
372
+ ko: '게임 브랜드 원본에서 타깃별 앱 아이콘을 생성하고 검증합니다.',
373
+ }),
374
+ subCommands: Object.fromEntries(['generate', 'verify', 'inspect'].map((action) => [
375
+ action,
376
+ defineI18n({
377
+ name: action,
378
+ description: `${action} target app icon assets.`,
379
+ resource: commandResource({
380
+ en: `${action} target app icon assets.`,
381
+ ko: `타깃 앱 아이콘 자산을 ${action}합니다.`,
382
+ }),
383
+ args: {
384
+ game: {
385
+ type: 'positional',
386
+ required: true,
387
+ description: 'Game root containing mpgd.game.json and mpgd.targets.json.',
388
+ },
389
+ targets: {
390
+ type: 'string',
391
+ required: false,
392
+ description: 'Comma-separated target names. Defaults to every configured target.',
393
+ },
394
+ profile: {
395
+ type: 'string',
396
+ required: false,
397
+ default: action === 'verify' ? 'production' : 'development',
398
+ description: 'Validation profile (development or production).',
399
+ },
400
+ 'kit-path': {
401
+ type: 'string',
402
+ required: false,
403
+ description: 'Path to the mpgd-kit checkout.',
404
+ },
405
+ },
406
+ run: (ctx) => {
407
+ const positionals = readLocalPositionals(ctx.positionals, ['game', 'icons', action]);
408
+ const gameRoot = path.resolve(readRequiredPositional(positionals, 0, 'game'));
409
+ const env = createTargetCommandEnv({
410
+ 'targets-file': path.join(gameRoot, 'mpgd.targets.json'),
411
+ 'kit-path': ctx.values['kit-path'],
412
+ });
413
+ const targets = readOptionalString(ctx.values.targets) ?? '';
414
+ const profile = readOptionalString(ctx.values.profile)
415
+ ?? (action === 'verify' ? 'production' : 'development');
416
+ runPnpm([`game:icons:${action}`, '--', targets, profile], env);
417
+ },
418
+ }),
419
+ ])),
420
+ run: () => {
421
+ },
422
+ }),
216
423
  },
217
424
  run: () => {
218
- console.log('Use "mpgd game create <directory>".');
219
425
  },
220
426
  });
221
427
  const defaultLegalDir = 'legal';
222
428
  const defaultLegalOutDir = 'artifacts/legal-site';
223
429
  const legalPageSlugs = ['privacy', 'support', 'terms'];
224
430
  const legalPageFileList = legalPageSlugs.map((slug) => `${slug}.html`).join(', ');
431
+ function acceptGame(input) {
432
+ if (input.skipTargetBuild && !input.skipTargetSmoke) {
433
+ throw new Error('Use --skip-target-smoke when --skip-target-build is set.');
434
+ }
435
+ const gameRoot = assertGameAcceptanceRoot(input.gameRoot);
436
+ const gamePackage = readPackageJsonOrUndefined(gameRoot, { strictParse: true });
437
+ if (gamePackage === undefined) {
438
+ throw new Error(`Missing game package.json: ${gameRoot}`);
439
+ }
440
+ const targetsFile = path.join(gameRoot, 'mpgd.targets.json');
441
+ if (!existsSync(targetsFile)) {
442
+ throw new Error(`Missing game target config: ${targetsFile}`);
443
+ }
444
+ const kitPath = resolveKitPathForTarget({
445
+ ...(input.kitPath === undefined ? {} : { 'kit-path': input.kitPath }),
446
+ });
447
+ const packageManager = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
448
+ const steps = [
449
+ packageScriptAcceptanceStep({
450
+ id: acceptanceStepIds.check,
451
+ label: 'Game check',
452
+ script: 'check',
453
+ gameRoot,
454
+ packageManager,
455
+ scripts: gamePackage.scripts,
456
+ required: true,
457
+ }),
458
+ packageScriptAcceptanceStep({
459
+ id: acceptanceStepIds.test,
460
+ label: 'Game tests',
461
+ script: 'test',
462
+ gameRoot,
463
+ packageManager,
464
+ scripts: gamePackage.scripts,
465
+ required: false,
466
+ skipped: input.skipTest,
467
+ }),
468
+ packageScriptAcceptanceStep({
469
+ id: acceptanceStepIds.build,
470
+ label: 'Game build',
471
+ script: 'build',
472
+ gameRoot,
473
+ packageManager,
474
+ scripts: gamePackage.scripts,
475
+ required: true,
476
+ }),
477
+ input.skipGraph
478
+ ? skippedAcceptanceStep(acceptanceStepIds.graph, 'TypeScript graph preflight', 'Disabled by --skip-graph.')
479
+ : {
480
+ id: acceptanceStepIds.graph,
481
+ label: 'TypeScript graph preflight',
482
+ command: packageManager,
483
+ args: ['graph:preflight'],
484
+ cwd: kitPath,
485
+ },
486
+ packageScriptAcceptanceStep({
487
+ id: acceptanceStepIds.playtest,
488
+ label: 'Automated playtest',
489
+ script: input.playtestScript,
490
+ gameRoot,
491
+ packageManager,
492
+ scripts: gamePackage.scripts,
493
+ required: false,
494
+ skipped: input.skipPlaytest,
495
+ }),
496
+ targetMatrixAcceptanceStep({
497
+ action: 'build-all',
498
+ id: acceptanceStepIds.targetBuild,
499
+ label: 'Target build matrix',
500
+ kitPath,
501
+ packageManager,
502
+ targetsFile,
503
+ targets: input.targets,
504
+ profile: input.profile,
505
+ aitVariant: input.aitVariant,
506
+ ...(input.iosVariant === undefined ? {} : { iosVariant: input.iosVariant }),
507
+ skipped: input.skipTargetBuild,
508
+ }),
509
+ targetMatrixAcceptanceStep({
510
+ action: 'smoke-all',
511
+ id: acceptanceStepIds.targetSmoke,
512
+ label: 'Target smoke matrix',
513
+ kitPath,
514
+ packageManager,
515
+ targetsFile,
516
+ targets: input.targets,
517
+ profile: input.profile,
518
+ aitVariant: input.aitVariant,
519
+ ...(input.iosVariant === undefined ? {} : { iosVariant: input.iosVariant }),
520
+ skipped: input.skipTargetSmoke,
521
+ }),
522
+ ];
523
+ const reportDir = path.resolve(gameRoot, input.reportDir ?? 'artifacts/acceptance');
524
+ const releaseManifestFile = resolveGameAcceptanceReleaseManifestFile(gameRoot);
525
+ const result = runGameAcceptance({
526
+ gameRoot,
527
+ reportDir,
528
+ ...(input.skipTargetBuild
529
+ ? {}
530
+ : { releaseManifestFile }),
531
+ options: {
532
+ targets: input.targets,
533
+ profile: input.profile,
534
+ aitVariant: input.aitVariant,
535
+ iosVariant: input.iosVariant ?? null,
536
+ playtestScript: input.playtestScript,
537
+ skipTest: input.skipTest,
538
+ skipGraph: input.skipGraph,
539
+ skipPlaytest: input.skipPlaytest,
540
+ skipTargetBuild: input.skipTargetBuild,
541
+ skipTargetSmoke: input.skipTargetSmoke,
542
+ timeoutMs: String(input.timeoutMs),
543
+ },
544
+ steps,
545
+ commandTimeoutMs: input.timeoutMs,
546
+ env: {
547
+ ...process.env,
548
+ MPGD_KIT_PATH: kitPath,
549
+ MPGD_RELEASE_MANIFEST_FILE: releaseManifestFile,
550
+ },
551
+ });
552
+ if (result.report.status === 'failed') {
553
+ throw new Error(`Game acceptance failed. Report: ${result.markdownFile}`);
554
+ }
555
+ }
556
+ function assertGameAcceptanceRoot(gameRoot) {
557
+ const resolved = path.resolve(gameRoot);
558
+ if (!existsSync(resolved)) {
559
+ throw new Error(`Game root does not exist: ${resolved}`);
560
+ }
561
+ return realpathSync(resolved);
562
+ }
563
+ function packageScriptAcceptanceStep(input) {
564
+ if (input.skipped === true) {
565
+ return skippedAcceptanceStep(input.id, input.label, 'Disabled by command option.');
566
+ }
567
+ if (input.scripts?.[input.script] === undefined && !input.required) {
568
+ return skippedAcceptanceStep(input.id, input.label, `Optional package script "${input.script}" is not configured.`);
569
+ }
570
+ return {
571
+ id: input.id,
572
+ label: input.label,
573
+ command: input.packageManager,
574
+ args: ['run', input.script],
575
+ cwd: input.gameRoot,
576
+ };
577
+ }
578
+ function targetMatrixAcceptanceStep(input) {
579
+ if (input.skipped) {
580
+ return skippedAcceptanceStep(input.id, input.label, 'Disabled by command option.');
581
+ }
582
+ const args = [
583
+ 'mpgd',
584
+ 'target',
585
+ input.action,
586
+ '--targets-file',
587
+ input.targetsFile,
588
+ '--kit-path',
589
+ input.kitPath,
590
+ '--targets',
591
+ input.targets,
592
+ ];
593
+ if (input.action === 'build-all') {
594
+ args.push('--profile', input.profile, '--ait-variant', input.aitVariant);
595
+ if (input.iosVariant !== undefined) {
596
+ args.push('--ios-variant', input.iosVariant);
597
+ }
598
+ }
599
+ return {
600
+ id: input.id,
601
+ label: input.label,
602
+ command: input.packageManager,
603
+ args,
604
+ cwd: input.kitPath,
605
+ };
606
+ }
607
+ function skippedAcceptanceStep(id, label, skipReason) {
608
+ return { id, label, skipReason };
609
+ }
610
+ function readPositiveInteger(value, label) {
611
+ const parsed = Number(value);
612
+ if (!Number.isInteger(parsed) || parsed <= 0) {
613
+ throw new Error(`${label} must be a positive integer.`);
614
+ }
615
+ return parsed;
616
+ }
225
617
  const legalCommand = defineI18n({
226
618
  name: 'legal',
227
619
  description: 'Build static legal/support pages for store and release evidence.',
@@ -243,7 +635,6 @@ const legalCommand = defineI18n({
243
635
  ...resolveLegalCommandOptions(ctx.values),
244
636
  check: false,
245
637
  });
246
- console.log(`Built legal site: ${result.outDir}`);
247
638
  },
248
639
  }),
249
640
  check: defineI18n({
@@ -259,12 +650,10 @@ const legalCommand = defineI18n({
259
650
  ...resolveLegalCommandOptions(ctx.values),
260
651
  check: true,
261
652
  });
262
- console.log(`Legal site checked: ${result.outDir}`);
263
653
  },
264
654
  }),
265
655
  },
266
656
  run: () => {
267
- console.log('Use "mpgd legal build" or "mpgd legal check".');
268
657
  },
269
658
  });
270
659
  const targetCommand = defineI18n({
@@ -473,7 +862,6 @@ const targetCommand = defineI18n({
473
862
  }),
474
863
  },
475
864
  run: () => {
476
- console.log('Use "mpgd target build <target>" or "mpgd target smoke <target>".');
477
865
  },
478
866
  });
479
867
  const kitCommand = defineI18n({
@@ -492,17 +880,10 @@ const kitCommand = defineI18n({
492
880
  ko: 'CLI, kit, template, target wrapper 상태를 출력합니다.',
493
881
  }),
494
882
  run: () => {
495
- console.log(`cli package: ${packageRoot}`);
496
- console.log(`mpgd-kit: ${detectedKitRoot ?? 'not detected'}`);
497
- console.log(`cli template: ${existsSync(gameTemplateDir) ? 'ok' : 'missing'}`);
498
- console.log(`mobile wrapper: ${kitFileStatus('apps/mobile-capacitor/package.json')}`);
499
- console.log(`ait wrapper: ${kitFileStatus('apps/target-ait/package.json')}`);
500
- console.log(`devvit wrapper: ${kitFileStatus('apps/target-devvit/package.json')}`);
501
883
  },
502
884
  }),
503
885
  },
504
886
  run: () => {
505
- console.log('Use "mpgd kit doctor".');
506
887
  },
507
888
  });
508
889
  function commandResource(description, args = {}) {
@@ -717,9 +1098,7 @@ function createGameApp(input) {
717
1098
  });
718
1099
  const files = collectTemplateFiles(gameTemplateDir);
719
1100
  if (input.dryRun) {
720
- console.log(`Would create ${path.relative(process.cwd(), appDir) || appDir}:`);
721
1101
  for (const file of files) {
722
- console.log(`- ${path.join(path.relative(process.cwd(), appDir), file.relativePath)}`);
723
1102
  }
724
1103
  return;
725
1104
  }
@@ -728,19 +1107,6 @@ function createGameApp(input) {
728
1107
  mkdirSync(path.dirname(outputFile), { recursive: true });
729
1108
  writeFileSync(outputFile, renderTemplate(file.content, context));
730
1109
  }
731
- console.log(`Created ${appDir}`);
732
- console.log('Next steps:');
733
- console.log(` cd ${appDir}`);
734
- console.log(' pnpm install -w');
735
- console.log(' pnpm dev');
736
- console.log('Target builds require an mpgd-kit checkout:');
737
- console.log([
738
- ' mpgd target build-all',
739
- `--targets-file ${path.join(appDir, 'mpgd.targets.json')}`,
740
- `--targets ${recommendedMatrixTargets}`,
741
- '--ait-variant wrapper',
742
- '--kit-path <path-to-mpgd-kit>',
743
- ].join(' '));
744
1110
  }
745
1111
  function assertValidGameName(name) {
746
1112
  if (!/^[a-z][a-z0-9-]*$/.test(name)) {
@@ -1010,7 +1376,7 @@ function readConfiguredPath(value) {
1010
1376
  return normalized === undefined || normalized.length === 0 ? undefined : normalized;
1011
1377
  }
1012
1378
  function readSourceGitSha(gameRoot) {
1013
- const result = spawnSync('git', ['-C', gameRoot, 'rev-parse', '--short', 'HEAD'], {
1379
+ const result = spawnSync('git', ['-C', gameRoot, 'rev-parse', 'HEAD'], {
1014
1380
  encoding: 'utf8',
1015
1381
  stdio: ['ignore', 'pipe', 'ignore'],
1016
1382
  });
@@ -1088,10 +1454,10 @@ function replaceTargetTokens(value, context) {
1088
1454
  function runTargetCommand(input) {
1089
1455
  const target = normalizeBuildTarget(input.target);
1090
1456
  const env = withTargetVariantEnv(target, input.variant, input.env);
1457
+ const profile = input.profile ?? 'production';
1091
1458
  const args = input.action === 'build'
1092
- ? ['build:target', target, input.profile ?? 'production']
1459
+ ? ['build:target', target, profile]
1093
1460
  : ['smoke:target', target];
1094
- console.log(`[mpgd] ${input.action} ${target}`);
1095
1461
  runPnpm(args, env);
1096
1462
  }
1097
1463
  function runTargetMatrix(input) {
@@ -0,0 +1,8 @@
1
+ export interface ProductionTargetReadinessInput {
2
+ readonly target: string;
3
+ readonly profile: string;
4
+ readonly targetsFile: string;
5
+ readonly gameRoot: string;
6
+ readonly gameServicesUrl?: string;
7
+ }
8
+ export declare function assertProductionTargetReadiness(input: ProductionTargetReadinessInput): void;