@mpgd/cli 0.3.2 → 0.4.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 (32) hide show
  1. package/dist/index.js +349 -22
  2. package/package.json +14 -2
  3. package/templates/phaser-game/README.md +97 -6
  4. package/templates/phaser-game/agent/acceptance.md +7 -2
  5. package/templates/phaser-game/agent/brief.md +1 -1
  6. package/templates/phaser-game/agent/game-manifest.json +5 -1
  7. package/templates/phaser-game/apps/target-cloudflare-pages/package.json +28 -0
  8. package/templates/phaser-game/apps/target-cloudflare-pages/src/worker.ts +12 -0
  9. package/templates/phaser-game/apps/target-cloudflare-pages/tsconfig.json +8 -0
  10. package/templates/phaser-game/apps/target-cloudflare-pages/vite.config.ts +28 -0
  11. package/templates/phaser-game/apps/target-cloudflare-pages/wrangler.jsonc +28 -0
  12. package/templates/phaser-game/apps/target-devvit/README.md +1 -1
  13. package/templates/phaser-game/apps/target-devvit/package.json +1 -1
  14. package/templates/phaser-game/apps/target-devvit/src/server/index.ts +4 -4
  15. package/templates/phaser-game/index.html +2 -0
  16. package/templates/phaser-game/legal/privacy.html +78 -0
  17. package/templates/phaser-game/legal/support.html +66 -0
  18. package/templates/phaser-game/legal/terms.html +70 -0
  19. package/templates/phaser-game/mpgd.targets.json +6 -0
  20. package/templates/phaser-game/package.json +42 -12
  21. package/templates/phaser-game/pnpm-workspace.yaml +7 -0
  22. package/templates/phaser-game/public/icon.svg +12 -0
  23. package/templates/phaser-game/public/manifest.webmanifest +20 -0
  24. package/templates/phaser-game/src/assets/manifest.ts +15 -0
  25. package/templates/phaser-game/src/assets/marker.svg +12 -0
  26. package/templates/phaser-game/src/env.d.ts +1 -0
  27. package/templates/phaser-game/src/main.ts +5 -0
  28. package/templates/phaser-game/src/platform/installPlatform.ts +1 -1
  29. package/templates/phaser-game/src/platform/runtimeDetector.ts +9 -1
  30. package/templates/phaser-game/src/scenes/BootScene.ts +8 -0
  31. package/templates/phaser-game/src/scenes/PlayScene.ts +3 -2
  32. package/templates/phaser-game/vite.config.ts +2 -0
package/dist/index.js CHANGED
@@ -11,12 +11,38 @@ const packageRoot = resolvePackageRoot(sourceDir);
11
11
  const detectedKitRoot = resolveDefaultKitRoot();
12
12
  const gameTemplateDir = path.resolve(packageRoot, 'templates/phaser-game');
13
13
  const cliVersion = readPackageVersion(packageRoot);
14
- const defaultMatrixTargets = 'web,ait,reddit';
14
+ const recommendedMatrixTargets = 'web,microsoft-store,ait,reddit';
15
15
  const defaultDependencyVersion = `^${cliVersion}`;
16
+ const mpgdTemplateDependencyPackages = [
17
+ { name: '@mpgd/adapter-ait', packageDir: 'adapters/ait' },
18
+ { name: '@mpgd/adapter-browser', packageDir: 'adapters/browser' },
19
+ { name: '@mpgd/adapter-capacitor', packageDir: 'adapters/capacitor' },
20
+ { name: '@mpgd/adapter-devvit', packageDir: 'adapters/devvit' },
21
+ { name: '@mpgd/analytics', packageDir: 'packages/analytics' },
22
+ { name: '@mpgd/bridge', packageDir: 'packages/bridge' },
23
+ { name: '@mpgd/catalog', packageDir: 'packages/catalog' },
24
+ { name: '@mpgd/cli', packageDir: 'packages/cli' },
25
+ { name: '@mpgd/game-services', packageDir: 'packages/game-services' },
26
+ { name: '@mpgd/i18n', packageDir: 'packages/i18n' },
27
+ { name: '@mpgd/phaser-assets', packageDir: 'packages/phaser-assets' },
28
+ { name: '@mpgd/platform', packageDir: 'packages/platform' },
29
+ { name: '@mpgd/target-config', packageDir: 'packages/target-config' },
30
+ ];
31
+ const recommendedMatrixTargetOrder = ['web-preview', 'microsoft-store', 'ait', 'reddit'];
32
+ const allMatrixTargetOrder = [
33
+ 'web-preview',
34
+ 'microsoft-store',
35
+ 'android',
36
+ 'ios',
37
+ 'ait',
38
+ 'reddit',
39
+ ];
16
40
  const supportedBuildTargets = [
17
41
  'browser',
18
42
  'web',
19
43
  'web-preview',
44
+ 'microsoft-store',
45
+ 'msstore',
20
46
  'android',
21
47
  'ios',
22
48
  'ait',
@@ -30,6 +56,7 @@ export async function runMpgdCli(args) {
30
56
  version: cliVersion,
31
57
  subCommands: {
32
58
  game: gameCommand,
59
+ legal: legalCommand,
33
60
  target: targetCommand,
34
61
  kit: kitCommand,
35
62
  },
@@ -148,8 +175,7 @@ const gameCommand = defineI18n({
148
175
  'dependency-version': {
149
176
  type: 'string',
150
177
  required: false,
151
- default: defaultDependencyVersion,
152
- description: 'Version range for @mpgd packages.',
178
+ description: 'Override version range for every @mpgd package.',
153
179
  },
154
180
  workspace: {
155
181
  type: 'boolean',
@@ -174,13 +200,13 @@ const gameCommand = defineI18n({
174
200
  const packageName = readOptionalString(ctx.values['package-name']);
175
201
  const dependencyVersion = ctx.values.workspace === true
176
202
  ? 'workspace:*'
177
- : (readOptionalString(ctx.values['dependency-version']) ?? defaultDependencyVersion);
203
+ : readOptionalString(ctx.values['dependency-version']);
178
204
  const kitPath = readGameCreateKitPath(ctx.values, ctx.values.workspace === true);
179
205
  createGameApp({
180
206
  directory,
181
207
  ...(title === undefined ? {} : { title }),
182
208
  ...(packageName === undefined ? {} : { packageName }),
183
- dependencyVersion,
209
+ ...(dependencyVersion === undefined ? {} : { dependencyVersion }),
184
210
  workspace: ctx.values.workspace === true,
185
211
  ...(kitPath === undefined ? {} : { kitPath }),
186
212
  dryRun: ctx.values['dry-run'] === true,
@@ -192,6 +218,55 @@ const gameCommand = defineI18n({
192
218
  console.log('Use "mpgd game create <directory>".');
193
219
  },
194
220
  });
221
+ const defaultLegalDir = 'legal';
222
+ const defaultLegalOutDir = 'artifacts/legal-site';
223
+ const legalPageSlugs = ['privacy', 'support', 'terms'];
224
+ const legalPageFileList = legalPageSlugs.map((slug) => `${slug}.html`).join(', ');
225
+ const legalCommand = defineI18n({
226
+ name: 'legal',
227
+ description: 'Build static legal/support pages for store and release evidence.',
228
+ resource: commandResource({
229
+ en: 'Build static legal/support pages for store and release evidence.',
230
+ ko: '스토어와 출시 증빙용 정적 약관/지원 페이지를 빌드합니다.',
231
+ }),
232
+ subCommands: {
233
+ build: defineI18n({
234
+ name: 'build',
235
+ description: 'Build privacy, support, and terms HTML pages.',
236
+ resource: commandResource({
237
+ en: 'Build privacy, support, and terms HTML pages.',
238
+ ko: 'privacy, support, terms HTML 페이지를 빌드합니다.',
239
+ }, legalArgResources()),
240
+ args: legalArgs(),
241
+ run: (ctx) => {
242
+ const result = buildLegalSite({
243
+ ...resolveLegalCommandOptions(ctx.values),
244
+ check: false,
245
+ });
246
+ console.log(`Built legal site: ${result.outDir}`);
247
+ },
248
+ }),
249
+ check: defineI18n({
250
+ name: 'check',
251
+ description: 'Check generated legal pages are current.',
252
+ resource: commandResource({
253
+ en: 'Check generated legal pages are current.',
254
+ ko: '생성된 legal 페이지가 최신인지 확인합니다.',
255
+ }, legalArgResources()),
256
+ args: legalArgs(),
257
+ run: (ctx) => {
258
+ const result = buildLegalSite({
259
+ ...resolveLegalCommandOptions(ctx.values),
260
+ check: true,
261
+ });
262
+ console.log(`Legal site checked: ${result.outDir}`);
263
+ },
264
+ }),
265
+ },
266
+ run: () => {
267
+ console.log('Use "mpgd legal build" or "mpgd legal check".');
268
+ },
269
+ });
195
270
  const targetCommand = defineI18n({
196
271
  name: 'target',
197
272
  description: 'Build and smoke target artifacts with generated game target config.',
@@ -311,7 +386,6 @@ const targetCommand = defineI18n({
311
386
  targets: {
312
387
  type: 'string',
313
388
  required: false,
314
- default: defaultMatrixTargets,
315
389
  description: 'Comma-separated target list, or all.',
316
390
  },
317
391
  profile: {
@@ -337,11 +411,12 @@ const targetCommand = defineI18n({
337
411
  run: (ctx) => {
338
412
  const aitVariant = readOptionalString(ctx.values['ait-variant']);
339
413
  const iosVariant = readOptionalString(ctx.values['ios-variant']);
414
+ const env = createTargetCommandEnv(ctx.values);
340
415
  runTargetMatrix({
341
416
  action: 'build',
342
- targets: parseTargetList(readOptionalString(ctx.values.targets)),
417
+ targets: parseTargetList(readOptionalString(ctx.values.targets), env),
343
418
  profile: readOptionalString(ctx.values.profile) ?? 'production',
344
- env: createTargetCommandEnv(ctx.values),
419
+ env,
345
420
  ...(aitVariant === undefined ? {} : { aitVariant }),
346
421
  ...(iosVariant === undefined ? {} : { iosVariant }),
347
422
  });
@@ -358,16 +433,41 @@ const targetCommand = defineI18n({
358
433
  targets: {
359
434
  type: 'string',
360
435
  required: false,
361
- default: defaultMatrixTargets,
362
436
  description: 'Comma-separated target list, or all.',
363
437
  },
364
438
  ...targetConfigArgs(),
365
439
  },
366
440
  run: (ctx) => {
441
+ const env = createTargetCommandEnv(ctx.values);
367
442
  runTargetMatrix({
368
443
  action: 'smoke',
369
- targets: parseTargetList(readOptionalString(ctx.values.targets)),
370
- env: createTargetCommandEnv(ctx.values),
444
+ targets: parseTargetList(readOptionalString(ctx.values.targets), env),
445
+ env,
446
+ });
447
+ },
448
+ }),
449
+ doctor: defineI18n({
450
+ name: 'doctor',
451
+ description: 'Verify existing target artifacts and game-owned output paths.',
452
+ resource: commandResource({
453
+ en: 'Verify existing target artifacts and game-owned output paths.',
454
+ ko: '기존 타깃 산출물과 게임 소유 출력 경로를 검증합니다.',
455
+ }, matrixArgResources()),
456
+ args: {
457
+ targets: {
458
+ type: 'string',
459
+ required: false,
460
+ default: 'all',
461
+ description: 'Comma-separated target list, or all.',
462
+ },
463
+ ...targetConfigArgs(),
464
+ },
465
+ run: (ctx) => {
466
+ const env = createTargetCommandEnv(ctx.values);
467
+ runTargetMatrix({
468
+ action: 'smoke',
469
+ targets: parseTargetList(readOptionalString(ctx.values.targets) ?? 'all', env),
470
+ env,
371
471
  });
372
472
  },
373
473
  }),
@@ -470,20 +570,146 @@ function matrixArgResources() {
470
570
  },
471
571
  };
472
572
  }
573
+ function legalArgs() {
574
+ return {
575
+ 'legal-dir': {
576
+ type: 'string',
577
+ required: false,
578
+ default: defaultLegalDir,
579
+ description: `Directory containing ${legalPageFileList}.`,
580
+ },
581
+ 'out-dir': {
582
+ type: 'string',
583
+ required: false,
584
+ default: defaultLegalOutDir,
585
+ description: 'Output directory for the static legal site.',
586
+ },
587
+ };
588
+ }
589
+ function legalArgResources() {
590
+ return {
591
+ 'legal-dir': {
592
+ en: `Directory containing ${legalPageFileList}.`,
593
+ ko: `${legalPageFileList} 이 있는 디렉터리.`,
594
+ },
595
+ 'out-dir': {
596
+ en: 'Output directory for the static legal site.',
597
+ ko: '정적 legal 사이트 출력 디렉터리.',
598
+ },
599
+ };
600
+ }
601
+ function resolveLegalCommandOptions(values) {
602
+ return {
603
+ legalDir: readOptionalString(values['legal-dir']) ?? defaultLegalDir,
604
+ outDir: readOptionalString(values['out-dir']) ?? defaultLegalOutDir,
605
+ };
606
+ }
607
+ function buildLegalSite(input) {
608
+ const legalDir = path.resolve(process.cwd(), input.legalDir);
609
+ const outDir = path.resolve(process.cwd(), input.outDir);
610
+ const outputs = createLegalSiteOutputs(legalDir, outDir);
611
+ if (input.check) {
612
+ const stale = outputs.filter((output) => {
613
+ if (!existsSync(output.file)) {
614
+ return true;
615
+ }
616
+ return readFileSync(output.file, 'utf8') !== output.content;
617
+ });
618
+ if (stale.length > 0) {
619
+ throw new Error([
620
+ 'Legal site output is stale. Run "mpgd legal build".',
621
+ ...stale.map((output) => `- ${path.relative(process.cwd(), output.file)}`),
622
+ ].join('\n'));
623
+ }
624
+ return {
625
+ outDir,
626
+ };
627
+ }
628
+ for (const output of outputs) {
629
+ mkdirSync(path.dirname(output.file), { recursive: true });
630
+ writeFileSync(output.file, output.content);
631
+ }
632
+ return {
633
+ outDir,
634
+ };
635
+ }
636
+ function createLegalSiteOutputs(legalDir, outDir) {
637
+ const relativeLegalDir = path.relative(process.cwd(), legalDir);
638
+ const relativeOutDir = path.relative(process.cwd(), outDir);
639
+ if (relativeLegalDir.startsWith('..') || path.isAbsolute(relativeLegalDir)) {
640
+ throw new Error(`legal-dir must be inside the project root: ${legalDir}`);
641
+ }
642
+ if (relativeOutDir.startsWith('..') || path.isAbsolute(relativeOutDir)) {
643
+ throw new Error(`out-dir must be inside the project root: ${outDir}`);
644
+ }
645
+ const sourceRoot = toTemplatePath(relativeLegalDir || '.');
646
+ const outputs = legalPageSlugs.map((slug) => {
647
+ const sourceFile = path.join(legalDir, `${slug}.html`);
648
+ if (!existsSync(sourceFile)) {
649
+ throw new Error(`Missing legal page source: ${sourceFile}`);
650
+ }
651
+ return {
652
+ file: path.join(outDir, slug, 'index.html'),
653
+ content: normalizeLegalHtml(readFileSync(sourceFile, 'utf8'), sourceFile),
654
+ };
655
+ });
656
+ return [
657
+ ...outputs,
658
+ {
659
+ file: path.join(outDir, '_headers'),
660
+ content: [
661
+ '/*',
662
+ ' X-Content-Type-Options: nosniff',
663
+ ' Referrer-Policy: strict-origin-when-cross-origin',
664
+ ' Cache-Control: public, max-age=300',
665
+ '',
666
+ ].join('\n'),
667
+ },
668
+ {
669
+ file: path.join(outDir, '_redirects'),
670
+ content: [
671
+ '/ /support/ 302',
672
+ '/privacy /privacy/ 301',
673
+ '/support /support/ 301',
674
+ '/terms /terms/ 301',
675
+ '',
676
+ ].join('\n'),
677
+ },
678
+ {
679
+ file: path.join(outDir, 'legal-site.json'),
680
+ content: `${JSON.stringify({
681
+ version: 1,
682
+ pages: legalPageSlugs.map((slug) => ({
683
+ slug,
684
+ path: `/${slug}/`,
685
+ source: `${sourceRoot}/${slug}.html`,
686
+ })),
687
+ }, null, 2)}\n`,
688
+ },
689
+ ];
690
+ }
691
+ function normalizeLegalHtml(content, sourceFile) {
692
+ if (!/^<!doctype html>/iu.test(content) || !/<html[\s>]/iu.test(content)) {
693
+ throw new Error(`${sourceFile} must be a complete HTML document.`);
694
+ }
695
+ return content.endsWith('\n') ? content : `${content}\n`;
696
+ }
473
697
  function createGameApp(input) {
474
698
  const appDir = resolveAppDirectory(input.directory);
475
699
  const gameName = path.basename(appDir);
476
700
  assertValidGameName(gameName);
477
701
  const packageName = input.packageName ?? gameName;
478
702
  assertValidPackageName(packageName);
479
- assertValidDependencyVersion(input.dependencyVersion);
703
+ if (input.dependencyVersion !== undefined) {
704
+ assertValidDependencyVersion(input.dependencyVersion);
705
+ }
480
706
  if (existsSync(appDir)) {
481
707
  throw new Error(`Game directory already exists: ${appDir}`);
482
708
  }
483
709
  const context = createTemplateContext({
484
710
  gameName,
485
711
  packageName,
486
- dependencyVersion: input.dependencyVersion,
712
+ ...(input.dependencyVersion === undefined ? {} : { dependencyVersion: input.dependencyVersion }),
487
713
  appDir,
488
714
  workspace: input.workspace,
489
715
  ...(input.kitPath === undefined ? {} : { kitPath: input.kitPath }),
@@ -505,13 +731,13 @@ function createGameApp(input) {
505
731
  console.log(`Created ${appDir}`);
506
732
  console.log('Next steps:');
507
733
  console.log(` cd ${appDir}`);
508
- console.log(' pnpm install --filter . --filter ./apps/target-devvit');
734
+ console.log(' pnpm install -w');
509
735
  console.log(' pnpm dev');
510
736
  console.log('Target builds require an mpgd-kit checkout:');
511
737
  console.log([
512
738
  ' mpgd target build-all',
513
739
  `--targets-file ${path.join(appDir, 'mpgd.targets.json')}`,
514
- `--targets ${defaultMatrixTargets}`,
740
+ `--targets ${recommendedMatrixTargets}`,
515
741
  '--ait-variant wrapper',
516
742
  '--kit-path <path-to-mpgd-kit>',
517
743
  ].join(' '));
@@ -535,10 +761,13 @@ function assertValidPackageName(name) {
535
761
  }
536
762
  }
537
763
  function assertValidDependencyVersion(version) {
538
- if (!/^(?:workspace:\*|[~^]?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.test(version)) {
764
+ if (!isValidDependencyVersion(version)) {
539
765
  throw new Error('Dependency version must be workspace:* or a semver version/range such as ^0.1.0.');
540
766
  }
541
767
  }
768
+ function isValidDependencyVersion(version) {
769
+ return /^(?:workspace:\*|[~^]?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/.test(version);
770
+ }
542
771
  function createTemplateContext(input) {
543
772
  const title = input.title === undefined || input.title.length === 0 ? input.gameName : input.title;
544
773
  assertValidGameTitle(title);
@@ -553,8 +782,16 @@ function createTemplateContext(input) {
553
782
  devvitAppName: toDevvitAppName(input.gameName),
554
783
  gameTitle: title,
555
784
  gameTitleTsLiteral: toJavaScriptStringLiteral(title),
785
+ legalLastUpdated: new Date().toISOString().slice(0, 10),
556
786
  packageName: input.packageName,
557
- dependencyVersion: input.dependencyVersion,
787
+ defaultDependencyVersion: input.dependencyVersion ?? defaultDependencyVersion,
788
+ mpgdDependencyVersionReplacements: resolveMpgdDependencyVersionReplacements({
789
+ ...(input.dependencyVersion === undefined
790
+ ? {}
791
+ : { overrideVersion: input.dependencyVersion }),
792
+ workspace: input.workspace,
793
+ ...(kitPath === undefined ? {} : { kitPath }),
794
+ }),
558
795
  tsconfigExtendsLine: input.workspace
559
796
  ? ` "extends": "${workspacePrefix}/tsconfig.base.json",`
560
797
  : '',
@@ -589,6 +826,43 @@ function createTemplateContext(input) {
589
826
  camelName: toCamelCase(input.gameName),
590
827
  };
591
828
  }
829
+ function resolveMpgdDependencyVersionReplacements(input) {
830
+ const packageJson = readPackageJsonOrUndefined(packageRoot, { strictParse: true });
831
+ const kitRoot = input.kitPath ?? detectedKitRoot;
832
+ return mpgdTemplateDependencyPackages.map((dependency) => {
833
+ const version = input.overrideVersion
834
+ ?? (input.workspace ? 'workspace:*' : undefined)
835
+ ?? resolvePublishedTemplateDependencyVersion(dependency.name, packageJson)
836
+ ?? resolveWorkspaceTemplateDependencyVersion(kitRoot, dependency.packageDir)
837
+ ?? defaultDependencyVersion;
838
+ return {
839
+ placeholder: mpgdDependencyVersionPlaceholder(dependency.name),
840
+ version,
841
+ };
842
+ });
843
+ }
844
+ function resolvePublishedTemplateDependencyVersion(packageName, packageJson) {
845
+ if (packageName === '@mpgd/cli') {
846
+ return `^${cliVersion}`;
847
+ }
848
+ const version = packageJson?.devDependencies?.[packageName];
849
+ return version !== undefined && version !== 'workspace:*' && isValidDependencyVersion(version)
850
+ ? version
851
+ : undefined;
852
+ }
853
+ function resolveWorkspaceTemplateDependencyVersion(kitRoot, packageDir) {
854
+ if (kitRoot === undefined) {
855
+ return undefined;
856
+ }
857
+ const version = readPackageVersion(path.join(kitRoot, packageDir));
858
+ return version === '0.0.0' ? undefined : `^${version}`;
859
+ }
860
+ function mpgdDependencyVersionPlaceholder(packageName) {
861
+ return `__MPGD_DEPENDENCY_VERSION_${packageName
862
+ .replace(/^@mpgd\//, '')
863
+ .replaceAll('-', '_')
864
+ .toUpperCase()}__`;
865
+ }
592
866
  function assertValidGameTitle(title) {
593
867
  if (title.trim().length === 0) {
594
868
  throw new Error('Game title cannot be empty.');
@@ -617,13 +891,14 @@ function walkTemplateDir(templateDir, relativeDir, files) {
617
891
  }
618
892
  }
619
893
  function renderTemplate(content, context) {
620
- return content
894
+ let rendered = content
621
895
  .replaceAll('__GAME_NAME__', context.gameName)
622
896
  .replaceAll('__DEVVIT_APP_NAME__', context.devvitAppName)
623
897
  .replaceAll('__GAME_TITLE_TS_LITERAL__', context.gameTitleTsLiteral)
624
898
  .replaceAll('__GAME_TITLE__', context.gameTitle)
899
+ .replaceAll('__LEGAL_LAST_UPDATED__', context.legalLastUpdated)
625
900
  .replaceAll('__PACKAGE_NAME__', context.packageName)
626
- .replaceAll('__MPGD_DEPENDENCY_VERSION__', context.dependencyVersion)
901
+ .replaceAll('__MPGD_DEPENDENCY_VERSION__', context.defaultDependencyVersion)
627
902
  .replaceAll('__TSCONFIG_EXTENDS_LINE__', context.tsconfigExtendsLine)
628
903
  .replaceAll('__TSCONFIG_WORKSPACE_INCLUDES__', context.tsconfigWorkspaceIncludes)
629
904
  .replaceAll('__TSCONFIG_WORKSPACE_EXCLUDES__', context.tsconfigWorkspaceExcludes)
@@ -632,6 +907,10 @@ function renderTemplate(content, context) {
632
907
  .replaceAll('__PNPM_WORKSPACE_KIT_PACKAGES__', context.pnpmWorkspaceKitPackages)
633
908
  .replaceAll('__PASCAL_NAME__', context.pascalName)
634
909
  .replaceAll('__CAMEL_NAME__', context.camelName);
910
+ for (const replacement of context.mpgdDependencyVersionReplacements) {
911
+ rendered = rendered.replaceAll(replacement.placeholder, replacement.version);
912
+ }
913
+ return rendered;
635
914
  }
636
915
  function templateOutputPath(relativePath) {
637
916
  return path.basename(relativePath) === 'gitignore'
@@ -792,10 +1071,13 @@ function withTargetVariantEnv(target, variant, env) {
792
1071
  }
793
1072
  return env;
794
1073
  }
795
- function parseTargetList(value) {
796
- const raw = value ?? defaultMatrixTargets;
1074
+ function parseTargetList(value, env) {
1075
+ const raw = (value ?? 'default').trim();
797
1076
  if (raw === 'all') {
798
- return ['web-preview', 'android', 'ios', 'ait', 'reddit'];
1077
+ return configuredTargetsForOrder(env, allMatrixTargetOrder, 'all');
1078
+ }
1079
+ if (raw === 'default') {
1080
+ return configuredTargetsForOrder(env, recommendedMatrixTargetOrder, 'default');
799
1081
  }
800
1082
  const targets = raw
801
1083
  .split(',')
@@ -807,10 +1089,50 @@ function parseTargetList(value) {
807
1089
  }
808
1090
  return [...new Set(targets)];
809
1091
  }
1092
+ function configuredTargetsForOrder(env, order, label) {
1093
+ const configuredTargets = readConfiguredBuildTargets(env);
1094
+ const targets = order.filter((target) => configuredTargets.has(target));
1095
+ if (targets.length === 0) {
1096
+ throw new Error(`No ${label} targets are configured in ${readConfiguredTargetsFile(env)}.`);
1097
+ }
1098
+ return targets;
1099
+ }
1100
+ function readConfiguredBuildTargets(env) {
1101
+ const file = readConfiguredTargetsFile(env);
1102
+ const parsed = readJsonForCli(file);
1103
+ assertJsonObject(parsed, `targets file ${file}`);
1104
+ assertJsonObject(parsed.targets, `targets in ${file}`);
1105
+ const targets = new Set();
1106
+ for (const target of Object.keys(parsed.targets)) {
1107
+ const normalized = normalizeConfiguredBuildTarget(target);
1108
+ if (normalized !== undefined) {
1109
+ targets.add(normalized);
1110
+ }
1111
+ }
1112
+ return targets;
1113
+ }
1114
+ function readConfiguredTargetsFile(env) {
1115
+ const file = env.MPGD_PLATFORM_TARGETS_FILE;
1116
+ if (file === undefined || file.length === 0) {
1117
+ throw new Error('Missing MPGD_PLATFORM_TARGETS_FILE.');
1118
+ }
1119
+ return file;
1120
+ }
1121
+ function normalizeConfiguredBuildTarget(target) {
1122
+ try {
1123
+ return normalizeBuildTarget(target);
1124
+ }
1125
+ catch {
1126
+ return undefined;
1127
+ }
1128
+ }
810
1129
  function normalizeBuildTarget(target) {
811
1130
  if (target === 'browser' || target === 'web') {
812
1131
  return 'web-preview';
813
1132
  }
1133
+ if (target === 'msstore') {
1134
+ return 'microsoft-store';
1135
+ }
814
1136
  if (target === 'devvit') {
815
1137
  return 'reddit';
816
1138
  }
@@ -819,6 +1141,11 @@ function normalizeBuildTarget(target) {
819
1141
  }
820
1142
  return target;
821
1143
  }
1144
+ function assertJsonObject(input, label) {
1145
+ if (typeof input !== 'object' || input === null || Array.isArray(input)) {
1146
+ throw new Error(`${label} must be an object.`);
1147
+ }
1148
+ }
822
1149
  function runPnpm(args, commandEnv) {
823
1150
  const kitPath = commandEnv.MPGD_KIT_PATH;
824
1151
  if (kitPath === undefined || kitPath.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,7 +48,19 @@
48
48
  "devDependencies": {
49
49
  "@types/node": "^24.0.0",
50
50
  "ttsc": "0.16.9",
51
- "typescript": "7.0.1-rc"
51
+ "typescript": "7.0.1-rc",
52
+ "@mpgd/adapter-ait": "0.3.3",
53
+ "@mpgd/adapter-browser": "0.3.2",
54
+ "@mpgd/adapter-devvit": "0.3.3",
55
+ "@mpgd/analytics": "0.3.2",
56
+ "@mpgd/adapter-capacitor": "0.3.3",
57
+ "@mpgd/bridge": "0.4.0",
58
+ "@mpgd/game-services": "0.3.3",
59
+ "@mpgd/catalog": "0.3.2",
60
+ "@mpgd/i18n": "0.3.2",
61
+ "@mpgd/phaser-assets": "0.4.0",
62
+ "@mpgd/target-config": "0.3.3",
63
+ "@mpgd/platform": "0.3.2"
52
64
  },
53
65
  "main": "./dist/index.js",
54
66
  "types": "./dist/index.d.ts",