@mpgd/cli 0.21.0 → 0.22.1

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 (30) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.js +92 -8
  3. package/dist/microsoft-store-package-generation-contract.d.ts +5 -0
  4. package/dist/microsoft-store-package-generation-input.js +55 -0
  5. package/dist/microsoft-store-starter.d.ts +7 -0
  6. package/dist/microsoft-store-starter.js +176 -4
  7. package/dist/microsoft-store-submission.d.ts +24 -3
  8. package/dist/microsoft-store-submission.js +131 -4
  9. package/dist/offline-playtest.d.ts +31 -0
  10. package/dist/offline-playtest.js +5718 -0
  11. package/dist/production-target-readiness.js +38 -19
  12. package/package.json +15 -13
  13. package/templates/phaser-game/README.md +57 -33
  14. package/templates/phaser-game/apps/target-ait/apps-in-toss.config.ts +12 -0
  15. package/templates/phaser-game/apps/target-ait/package.json +5 -8
  16. package/templates/phaser-game/apps/target-ait/vite.config.ts +6 -15
  17. package/templates/phaser-game/apps/target-cloudflare-pages/package.json +1 -1
  18. package/templates/phaser-game/apps/target-devvit/README.md +2 -1
  19. package/templates/phaser-game/apps/target-devvit/package.json +1 -1
  20. package/templates/phaser-game/apps/target-devvit/src/server/index.ts +1 -0
  21. package/templates/phaser-game/docs/MPGD_KIT_WORKFLOWS.md +11 -0
  22. package/templates/phaser-game/mpgd.targets.json +8 -1
  23. package/templates/phaser-game/package.json +2 -5
  24. package/templates/phaser-game/pnpm-workspace.yaml +3 -2
  25. package/templates/phaser-game/src/entry.ts +10 -4
  26. package/templates/phaser-game/src/gameEntry.ts +7 -1
  27. package/templates/phaser-game/src/platform/buildGateways/microsoftStore.ts +37 -0
  28. package/templates/phaser-game/src/platform/runtimeDetector.ts +1 -0
  29. package/templates/phaser-game/src/runtime/renderEntryFailure.ts +24 -0
  30. package/templates/phaser-game/vite.shared.ts +6 -2
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  export { renderGameAcceptanceMarkdown, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, defaultGameAcceptanceCommandTimeoutMs, maximumGameplayE2EReportBytes, type GameAcceptanceCommandResult, type GameAcceptanceCommandRunner, type GameAcceptanceReport, type GameAcceptanceStatus, type GameAcceptanceStep, type GameAcceptanceStepResult, type GameAcceptanceStepStatus, type RunGameAcceptanceInput, type RunGameAcceptanceResult, } from './game-acceptance.js';
2
2
  export { createBrowserGameplayE2EDriver, type BrowserGameplayE2EInspectInput, type BrowserGameplayE2EPage, type BrowserGameplayE2EScreenshotOptions, type BrowserGameplayE2EViewport, type CreateBrowserGameplayE2EDriverInput, } from './browser-gameplay-e2e.js';
3
3
  export { collectGameplayE2EPathEvidence, defaultGameplayE2EReportFile, maximumGameplayE2EStates, parseGameplayE2EPlan, readGameplayE2EPlan, renderGameplayE2EMarkdown, resolveGameplayE2EReportFile, runGameplayE2E, type GameplayE2EAction, type GameplayE2EActionResult, type GameplayE2EDriver, type GameplayE2EHashLimits, type GameplayE2EInputAction, type GameplayE2EObservation, type GameplayE2EPauseResumeAction, type GameplayE2EPathEvidence, type GameplayE2EPlan, type GameplayE2EReport, type GameplayE2EResultStatus, type GameplayE2EState, type GameplayE2EStateResult, type GameplayE2EStatus, type RunGameplayE2EInput, type RunGameplayE2EResult, } from './gameplay-e2e.js';
4
+ export { defaultOfflinePlaytestArtifactDir, defaultOfflinePlaytestMaximumBytes, defaultOfflinePlaytestOutputDir, offlinePlaytestSchemaVersion, runOfflinePlaytestPackaging, type OfflinePlaytestEvidence, type RunOfflinePlaytestPackagingInput, type RunOfflinePlaytestPackagingResult, } from './offline-playtest.js';
4
5
  export { assertMicrosoftStorePwaCacheTransition, assertMicrosoftStorePwaPrecacheUrl, assertMicrosoftStorePwaReleaseEvidence, inspectMicrosoftStorePwaBrowserCacheTransition, microsoftStorePwaCacheSchema, readMicrosoftStorePwaBrowserReleaseEvidence, requestMicrosoftStorePwaBrowserUpdate, resolveMicrosoftStorePwaScopedCacheName, type AssertMicrosoftStorePwaCacheTransitionInput, type InspectMicrosoftStorePwaCacheTransitionInput, type MicrosoftStorePwaBrowserPage, type MicrosoftStorePwaCachedIndexObservation, type MicrosoftStorePwaCacheTransitionEvidence, type MicrosoftStorePwaCacheTransitionObservation, type MicrosoftStorePwaReleaseEvidence, } from './microsoft-store-pwa-e2e.js';
5
6
  export { createMicrosoftStorePackageAcceptanceRuntime, microsoftStorePackageAcceptanceSchemaVersion, parseMicrosoftStorePackageIdentity, parseWindowsAppCertificationResult, renderMicrosoftStorePackageAcceptanceMarkdown, runMicrosoftStorePackageAcceptance, type CreateMicrosoftStorePackageAcceptanceRuntimeInput, type MicrosoftStorePackageAcceptanceEvidence, type MicrosoftStorePackageAcceptanceRuntime, type MicrosoftStorePackageIdentity, type RunMicrosoftStorePackageAcceptanceInput, } from './microsoft-store-package-acceptance.js';
6
7
  export { createMicrosoftStorePackageGenerationRuntime, microsoftStorePackageGeneratorEndpoint, microsoftStorePackageGeneratorSourceRevision, microsoftStorePackageGenerationSchemaVersion, renderMicrosoftStorePackageGenerationMarkdown, runMicrosoftStorePackageGeneration, type CreateMicrosoftStorePackageGenerationRuntimeInput, type MicrosoftStoreAddressResolver, type MicrosoftStorePackageGenerationEvidence, type MicrosoftStorePackageGenerationRuntime, type MicrosoftStoreResolvedAddress, type RunMicrosoftStorePackageGenerationInput, } from './microsoft-store-package-generation.js';
7
- export { microsoftStoreSubmissionSchemaVersion, parseMicrosoftStoreSubmissionConfig, renderMicrosoftStoreSubmissionMarkdown, runMicrosoftStoreSubmissionPreflight, type MicrosoftStoreSubmissionConfig, type MicrosoftStoreSubmissionEvidence, type RunMicrosoftStoreSubmissionPreflightInput, } from './microsoft-store-submission.js';
8
+ export { microsoftStoreSubmissionSchemaVersion, parseMicrosoftStoreSubmissionConfig, renderMicrosoftStoreSubmissionMarkdown, runMicrosoftStoreSubmissionPreflight, type MicrosoftStoreSubmissionConfig, type MicrosoftStoreSubmissionCommerce, type MicrosoftStoreSubmissionCommerceProduct, type MicrosoftStoreSubmissionEvidence, type RunMicrosoftStoreSubmissionPreflightInput, } from './microsoft-store-submission.js';
8
9
  export { assertProductionTargetReadiness, type ProductionTargetReadinessInput, } from './production-target-readiness.js';
9
10
  export declare function runMpgdCli(args: readonly string[]): Promise<void>;
10
11
  export declare function runCreateGameCli(args: readonly string[]): Promise<void>;
package/dist/index.js CHANGED
@@ -11,12 +11,14 @@ import { defaultGameAcceptanceCommandTimeoutMs, resolveGameAcceptanceReleaseMani
11
11
  import { resolveGameplayE2EReportFile } from './gameplay-e2e.js';
12
12
  import { createMicrosoftStorePackageAcceptanceRuntime, runMicrosoftStorePackageAcceptance, } from './microsoft-store-package-acceptance.js';
13
13
  import { runMicrosoftStorePackageGeneration } from './microsoft-store-package-generation.js';
14
- import { defaultMpgdKitPath, initializeMicrosoftStoreStarter, listMicrosoftStoreInitializerTemplateFiles, prepareBaseGameTemplateFile, } from './microsoft-store-starter.js';
14
+ import { defaultMpgdKitPath, initializeMicrosoftStoreStarter, listMicrosoftStoreInitializerTemplateFiles, prepareBaseGameTemplateFile, resolveMicrosoftStoreAdapterDependencyVersion, } from './microsoft-store-starter.js';
15
15
  import { runMicrosoftStoreSubmissionPreflight } from './microsoft-store-submission.js';
16
+ import { defaultOfflinePlaytestArtifactDir, defaultOfflinePlaytestMaximumBytes, defaultOfflinePlaytestOutputDir, runOfflinePlaytestPackaging, } from './offline-playtest.js';
16
17
  import { targetConfigExtensionsFileEnv } from './target-config-env.js';
17
18
  export { renderGameAcceptanceMarkdown, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, defaultGameAcceptanceCommandTimeoutMs, maximumGameplayE2EReportBytes, } from './game-acceptance.js';
18
19
  export { createBrowserGameplayE2EDriver, } from './browser-gameplay-e2e.js';
19
20
  export { collectGameplayE2EPathEvidence, defaultGameplayE2EReportFile, maximumGameplayE2EStates, parseGameplayE2EPlan, readGameplayE2EPlan, renderGameplayE2EMarkdown, resolveGameplayE2EReportFile, runGameplayE2E, } from './gameplay-e2e.js';
21
+ export { defaultOfflinePlaytestArtifactDir, defaultOfflinePlaytestMaximumBytes, defaultOfflinePlaytestOutputDir, offlinePlaytestSchemaVersion, runOfflinePlaytestPackaging, } from './offline-playtest.js';
20
22
  export { assertMicrosoftStorePwaCacheTransition, assertMicrosoftStorePwaPrecacheUrl, assertMicrosoftStorePwaReleaseEvidence, inspectMicrosoftStorePwaBrowserCacheTransition, microsoftStorePwaCacheSchema, readMicrosoftStorePwaBrowserReleaseEvidence, requestMicrosoftStorePwaBrowserUpdate, resolveMicrosoftStorePwaScopedCacheName, } from './microsoft-store-pwa-e2e.js';
21
23
  export { createMicrosoftStorePackageAcceptanceRuntime, microsoftStorePackageAcceptanceSchemaVersion, parseMicrosoftStorePackageIdentity, parseWindowsAppCertificationResult, renderMicrosoftStorePackageAcceptanceMarkdown, runMicrosoftStorePackageAcceptance, } from './microsoft-store-package-acceptance.js';
22
24
  export { createMicrosoftStorePackageGenerationRuntime, microsoftStorePackageGeneratorEndpoint, microsoftStorePackageGeneratorSourceRevision, microsoftStorePackageGenerationSchemaVersion, renderMicrosoftStorePackageGenerationMarkdown, runMicrosoftStorePackageGeneration, } from './microsoft-store-package-generation.js';
@@ -409,6 +411,72 @@ const gameCommand = defineI18n({
409
411
  });
410
412
  },
411
413
  }),
414
+ 'offline-playtest': defineI18n({
415
+ name: 'offline-playtest',
416
+ description: 'Package a web preview as a test-only, direct-file offline playtest.',
417
+ resource: commandResource({
418
+ en: 'Package an existing web-preview artifact as a network-blocked, direct-file test bundle.',
419
+ ko: '기존 web-preview 산출물을 네트워크가 차단된 직접 실행 테스트 묶음으로 패키징합니다.',
420
+ }, {
421
+ game: {
422
+ en: 'Game root containing the source artifact.',
423
+ ko: '원본 산출물이 있는 게임 루트.',
424
+ },
425
+ 'artifact-dir': {
426
+ en: 'Existing web-preview artifact directory, relative to the game root.',
427
+ ko: '게임 루트 기준 기존 web-preview 산출물 디렉터리.',
428
+ },
429
+ 'output-dir': {
430
+ en: 'Test-only output directory under the game artifacts directory.',
431
+ ko: '게임 artifacts 디렉터리 아래의 테스트 전용 출력 디렉터리.',
432
+ },
433
+ 'maximum-bytes': {
434
+ en: 'Maximum size of the generated single index.html file.',
435
+ ko: '생성되는 단일 index.html 파일의 최대 바이트 수.',
436
+ },
437
+ }),
438
+ args: {
439
+ game: {
440
+ type: 'positional',
441
+ required: true,
442
+ description: 'Game root containing the source artifact.',
443
+ },
444
+ 'artifact-dir': {
445
+ type: 'string',
446
+ required: false,
447
+ default: defaultOfflinePlaytestArtifactDir,
448
+ description: 'Existing web-preview artifact directory.',
449
+ },
450
+ 'output-dir': {
451
+ type: 'string',
452
+ required: false,
453
+ default: defaultOfflinePlaytestOutputDir,
454
+ description: 'Test-only output directory under game artifacts.',
455
+ },
456
+ 'maximum-bytes': {
457
+ type: 'string',
458
+ required: false,
459
+ default: String(defaultOfflinePlaytestMaximumBytes),
460
+ description: 'Maximum generated index.html size in bytes.',
461
+ },
462
+ },
463
+ run: async (ctx) => {
464
+ const positionals = readLocalPositionals(ctx.positionals, ['game', 'offline-playtest']);
465
+ const gameRoot = path.resolve(readRequiredPositional(positionals, 0, 'game'));
466
+ const artifactDir = readOptionalString(ctx.values['artifact-dir'])
467
+ ?? defaultOfflinePlaytestArtifactDir;
468
+ const outputDir = readOptionalString(ctx.values['output-dir'])
469
+ ?? defaultOfflinePlaytestOutputDir;
470
+ const maximumBytes = readPositiveInteger(readOptionalString(ctx.values['maximum-bytes'])
471
+ ?? String(defaultOfflinePlaytestMaximumBytes), '--maximum-bytes');
472
+ const result = await runOfflinePlaytestPackaging({
473
+ gameRoot,
474
+ artifactDir,
475
+ outputDir,
476
+ maximumBytes,
477
+ });
478
+ },
479
+ }),
412
480
  icons: defineI18n({
413
481
  name: 'icons',
414
482
  description: 'Generate and verify target app icons from the game brand source.',
@@ -806,10 +874,15 @@ const targetCommand = defineI18n({
806
874
  ? defaultMpgdKitPath
807
875
  : toTemplatePath(path.relative(gameRoot, resolvedKitPath) || '.');
808
876
  const dryRun = ctx.values['dry-run'] === true;
877
+ const adapterDependencyVersion = resolveMicrosoftStoreAdapterDependencyVersion(requireMpgdDependencyVersion(resolveMpgdDependencyVersionReplacements({
878
+ workspace: false,
879
+ ...(resolvedKitPath === undefined ? {} : { kitPath: resolvedKitPath }),
880
+ }), '@mpgd/adapter-browser'));
809
881
  const result = initializeMicrosoftStoreStarter({
810
882
  gameRoot,
811
883
  templateRoot: gameTemplateDir,
812
884
  defaultKitPath,
885
+ adapterDependencyVersion,
813
886
  dryRun,
814
887
  });
815
888
  for (const file of result.changedFiles) {
@@ -1599,6 +1672,7 @@ function createGameApp(input) {
1599
1672
  gameRoot: appDir,
1600
1673
  templateRoot: gameTemplateDir,
1601
1674
  defaultKitPath: context.defaultKitPath,
1675
+ adapterDependencyVersion: context.microsoftStoreAdapterDependencyVersion,
1602
1676
  dryRun: false,
1603
1677
  });
1604
1678
  }
@@ -1650,6 +1724,13 @@ function createTemplateContext(input) {
1650
1724
  }
1651
1725
  const workspaceRoot = toTemplatePath(path.relative(input.appDir, kitPath ?? input.appDir) || '.');
1652
1726
  const workspacePrefix = workspaceRoot;
1727
+ const mpgdDependencyVersionReplacements = resolveMpgdDependencyVersionReplacements({
1728
+ ...(input.dependencyVersion === undefined
1729
+ ? {}
1730
+ : { overrideVersion: input.dependencyVersion }),
1731
+ workspace: input.workspace,
1732
+ ...(kitPath === undefined ? {} : { kitPath }),
1733
+ });
1653
1734
  return {
1654
1735
  gameName: input.gameName,
1655
1736
  devvitAppName: toDevvitAppName(input.gameName),
@@ -1658,13 +1739,8 @@ function createTemplateContext(input) {
1658
1739
  legalLastUpdated: new Date().toISOString().slice(0, 10),
1659
1740
  packageName: input.packageName,
1660
1741
  defaultDependencyVersion: input.dependencyVersion ?? defaultDependencyVersion,
1661
- mpgdDependencyVersionReplacements: resolveMpgdDependencyVersionReplacements({
1662
- ...(input.dependencyVersion === undefined
1663
- ? {}
1664
- : { overrideVersion: input.dependencyVersion }),
1665
- workspace: input.workspace,
1666
- ...(kitPath === undefined ? {} : { kitPath }),
1667
- }),
1742
+ mpgdDependencyVersionReplacements,
1743
+ microsoftStoreAdapterDependencyVersion: resolveMicrosoftStoreAdapterDependencyVersion(requireMpgdDependencyVersion(mpgdDependencyVersionReplacements, '@mpgd/adapter-browser')),
1668
1744
  tsconfigExtendsLine: input.workspace
1669
1745
  ? ` "extends": "${workspacePrefix}/tsconfig.base.json",`
1670
1746
  : '',
@@ -1740,6 +1816,14 @@ function mpgdDependencyVersionPlaceholder(packageName) {
1740
1816
  .replaceAll('-', '_')
1741
1817
  .toUpperCase()}__`;
1742
1818
  }
1819
+ function requireMpgdDependencyVersion(replacements, packageName) {
1820
+ const placeholder = mpgdDependencyVersionPlaceholder(packageName);
1821
+ const replacement = replacements.find((candidate) => candidate.placeholder === placeholder);
1822
+ if (replacement === undefined) {
1823
+ throw new Error(`Missing template dependency version for ${packageName}.`);
1824
+ }
1825
+ return replacement.version;
1826
+ }
1743
1827
  function assertValidGameTitle(title) {
1744
1828
  if (title.trim().length === 0) {
1745
1829
  throw new Error('Game title cannot be empty.');
@@ -80,6 +80,11 @@ export interface MicrosoftStoreSubmissionEvidenceInput {
80
80
  readonly manifestSha256: string;
81
81
  readonly manifest: Readonly<Record<string, unknown>>;
82
82
  readonly manifestIcons: readonly MicrosoftStoreManifestIconInput[];
83
+ readonly effectiveTarget: {
84
+ readonly file: string;
85
+ readonly snapshot: MicrosoftStoreFileSnapshot;
86
+ };
87
+ /** Comma-separated Store languages accepted by the pinned PWABuilder generator contract. */
83
88
  readonly resourceLanguage: string;
84
89
  }
85
90
  export interface MicrosoftStoreManifestIconInput {
@@ -5,7 +5,9 @@ import { TextDecoder } from 'node:util';
5
5
  import { formatError } from './evidence-io.js';
6
6
  import { assertMicrosoftStoreSnapshotUnchanged, hashMicrosoftStoreBytes, hashMicrosoftStoreFileSnapshot, readBoundedMicrosoftStoreFileBytes, } from './microsoft-store-package-generation-integrity.js';
7
7
  import { assertMicrosoftStorePwaUrlInsideManifestScope, readHashVerifiedMicrosoftStoreManifest, } from './microsoft-store-package-generation-manifest.js';
8
+ import { assertMicrosoftStoreEffectiveTarget, parseMicrosoftStoreSubmissionCommerce, } from './microsoft-store-submission.js';
8
9
  const maximumSubmissionEvidenceBytes = 4 * 1024 * 1024;
10
+ const maximumEffectiveTargetBytes = 4 * 1024 * 1024;
9
11
  const maximumManifestIcons = 32;
10
12
  const maximumManifestIconBytes = 2 * 1024 * 1024;
11
13
  export function prepareMicrosoftStorePackageGenerationInput(input) {
@@ -40,6 +42,10 @@ export function prepareMicrosoftStorePackageGenerationInput(input) {
40
42
  { file: markdownFile, label: 'package generation Markdown' },
41
43
  { file: submissionEvidenceFile, label: 'submission evidence' },
42
44
  { file: submission.manifestFile, label: 'web app manifest' },
45
+ {
46
+ file: submission.effectiveTarget.file,
47
+ label: 'Microsoft Store effective target config',
48
+ },
43
49
  ];
44
50
  assertDistinctFiles(filesThatMustAlwaysBeDistinct);
45
51
  for (const [index, icon] of submission.manifestIcons.entries()) {
@@ -66,6 +72,7 @@ export function prepareMicrosoftStorePackageGenerationInput(input) {
66
72
  export function assertMicrosoftStorePackageGenerationInputUnchanged(input) {
67
73
  assertMicrosoftStoreSnapshotUnchanged(input.submissionEvidenceFile, input.submissionBefore, 'Microsoft Store submission evidence changed during package generation');
68
74
  assertMicrosoftStoreSnapshotUnchanged(input.submission.manifestFile, input.manifestBefore, 'Microsoft Store web app manifest changed during package generation');
75
+ assertMicrosoftStoreSnapshotUnchanged(input.submission.effectiveTarget.file, input.submission.effectiveTarget.snapshot, 'Microsoft Store effective target config changed during package generation');
69
76
  for (const [index, icon] of input.submission.manifestIcons.entries()) {
70
77
  assertMicrosoftStoreSnapshotUnchanged(icon.file, icon.snapshot, `Microsoft Store web app manifest icon[${index}] changed during package generation`);
71
78
  }
@@ -112,6 +119,19 @@ function readSubmissionEvidence(file, gameRoot, expected) {
112
119
  || manifestIconCount !== manifestIcons.length) {
113
120
  throw new Error(`Microsoft Store manifest icon evidence must contain 1-${maximumManifestIcons} entries and match iconCount.`);
114
121
  }
122
+ if (root.commerce === undefined) {
123
+ throw new Error('Microsoft Store commerce evidence is required for package generation.');
124
+ }
125
+ const commerce = requireRecord(root.commerce, 'Microsoft Store commerce evidence');
126
+ const commerceMode = commerce.mode;
127
+ if (commerceMode !== 'disabled' && commerceMode !== 'microsoft-store') {
128
+ throw new Error('Microsoft Store commerce evidence mode must be disabled or microsoft-store.');
129
+ }
130
+ const parsedCommerce = parseMicrosoftStoreSubmissionCommerce(commerce);
131
+ if (root.effectiveTarget === undefined) {
132
+ throw new Error('Microsoft Store effective target evidence is required for package generation.');
133
+ }
134
+ const effectiveTarget = readEffectiveTargetEvidence(root.effectiveTarget, gameRoot, parsedCommerce);
115
135
  return {
116
136
  identity: {
117
137
  packageId: requireNonEmptyString(identity.packageId, 'Microsoft Store package ID'),
@@ -140,9 +160,44 @@ function readSubmissionEvidence(file, gameRoot, expected) {
140
160
  height: requirePositiveInteger(record.height, `Microsoft Store manifest icon evidence[${index}].height`),
141
161
  };
142
162
  }),
163
+ effectiveTarget,
164
+ // The pinned PWABuilder source accepts multiple Store languages as one comma-separated field.
143
165
  resourceLanguage: resourceLanguages.join(','),
144
166
  };
145
167
  }
168
+ function readEffectiveTargetEvidence(input, gameRoot, commerce) {
169
+ const evidence = requireRecord(input, 'Microsoft Store effective target evidence for package generation');
170
+ const file = readCanonicalFileInside(gameRoot, path.resolve(gameRoot, requireNonEmptyString(evidence.file, 'Microsoft Store effective target evidence file')), 'Microsoft Store effective target config');
171
+ const snapshot = hashMicrosoftStoreFileSnapshot(file, 'Microsoft Store effective target config');
172
+ const expectedSha256 = requireSha256(evidence.sha256, 'Microsoft Store effective target evidence SHA-256');
173
+ if (snapshot.sha256 !== expectedSha256) {
174
+ throw new Error(`Microsoft Store effective target config SHA-256 must match submission evidence: expected ${expectedSha256}, received ${snapshot.sha256}.`);
175
+ }
176
+ const bytes = readBoundedMicrosoftStoreFileBytes(file, 'Microsoft Store effective target config', maximumEffectiveTargetBytes);
177
+ if (bytes === null) {
178
+ throw new Error('Microsoft Store effective target config is too large.');
179
+ }
180
+ if (bytes.length !== snapshot.sizeBytes || hashMicrosoftStoreBytes(bytes) !== snapshot.sha256) {
181
+ throw new Error(`Microsoft Store effective target config changed while it was read: ${file}`);
182
+ }
183
+ let source;
184
+ try {
185
+ source = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
186
+ }
187
+ catch (error) {
188
+ throw new Error(`Microsoft Store effective target config must use valid UTF-8: ${formatError(error)}`);
189
+ }
190
+ let parsed;
191
+ try {
192
+ parsed = JSON.parse(source);
193
+ }
194
+ catch (error) {
195
+ throw new Error(`Failed to parse Microsoft Store effective target config: ${formatError(error)}`);
196
+ }
197
+ assertMicrosoftStoreEffectiveTarget(parsed, commerce);
198
+ assertMicrosoftStoreSnapshotUnchanged(file, snapshot, 'Microsoft Store effective target config changed while it was parsed');
199
+ return { file, snapshot };
200
+ }
146
201
  function prepareManifestIcons(gameRoot, manifestUrl, manifest, evidenceIcons) {
147
202
  const manifestIcons = requireArray(manifest.icons, 'Microsoft Store web app manifest icons');
148
203
  if (manifestIcons.length !== evidenceIcons.length) {
@@ -6,10 +6,12 @@ export declare const microsoftStoreDocumentationAnchors: {
6
6
  readonly 'agent/acceptance.md': '## Verse8 Agent8 Structured Server';
7
7
  readonly 'agent/brief.md': undefined;
8
8
  };
9
+ export declare const microsoftStoreAdapterMinimumDependencyVersion = "^0.6.0";
9
10
  export interface InitializeMicrosoftStoreStarterInput {
10
11
  readonly gameRoot: string;
11
12
  readonly templateRoot: string;
12
13
  readonly defaultKitPath: string;
14
+ readonly adapterDependencyVersion: string;
13
15
  readonly dryRun: boolean;
14
16
  }
15
17
  export interface InitializeMicrosoftStoreStarterResult {
@@ -25,4 +27,9 @@ export declare function prepareBaseGameTemplateFile(input: {
25
27
  }): string | undefined;
26
28
  export declare function listMicrosoftStoreInitializerTemplateFiles(): readonly string[];
27
29
  export declare function initializeMicrosoftStoreStarter(input: InitializeMicrosoftStoreStarterInput, runtime?: MicrosoftStoreStarterApplyRuntime): InitializeMicrosoftStoreStarterResult;
30
+ /**
31
+ * Floors registry-backed browser adapter versions at the first release that exports the
32
+ * Microsoft Store PWA adapter. Local workspace/link/file specs remain intentionally untouched.
33
+ */
34
+ export declare function resolveMicrosoftStoreAdapterDependencyVersion(candidate: string): string;
28
35
  export {};
@@ -23,6 +23,7 @@ const microsoftStoreOnlyTemplateFiles = new Set([
23
23
  '.agents/skills/release-microsoft-store/SKILL.md',
24
24
  '.agents/skills/release-microsoft-store/agents/openai.yaml',
25
25
  'mpgd.microsoft-store.json',
26
+ 'src/platform/buildGateways/microsoftStore.ts',
26
27
  'src/platform/microsoftStorePwa.ts',
27
28
  ]);
28
29
  const microsoftStoreManagedTemplateFiles = new Set([
@@ -33,10 +34,21 @@ const microsoftStoreManagedTemplateFiles = new Set([
33
34
  const microsoftStoreTarget = {
34
35
  kind: 'web',
35
36
  gameApp: '.',
36
- adapter: 'browser',
37
+ adapter: 'microsoft-store',
38
+ authoritativeGameServices: false,
37
39
  icon: { profile: 'microsoft-pwa' },
38
40
  output: 'artifacts/microsoft-store',
39
41
  };
42
+ const microsoftStoreAdapterBasePackage = '@mpgd/adapter-browser';
43
+ export const microsoftStoreAdapterMinimumDependencyVersion = '^0.6.0';
44
+ const microsoftStoreGatewayResolver = {
45
+ anchor: " default:\n return 'src/platform/buildGateways/browser.ts';",
46
+ block: " case 'microsoft-store':\n return 'src/platform/buildGateways/microsoftStore.ts';",
47
+ };
48
+ const microsoftStoreRuntimeTarget = {
49
+ anchor: " 'ios',\n 'ait',",
50
+ line: " 'microsoft-store',",
51
+ };
40
52
  export function prepareBaseGameTemplateFile(input) {
41
53
  const relativePath = toTemplatePath(input.relativePath);
42
54
  if (microsoftStoreOnlyTemplateFiles.has(relativePath)) {
@@ -68,6 +80,10 @@ export function prepareBaseGameTemplateFile(input) {
68
80
  });
69
81
  case 'src/main.ts':
70
82
  return removeMicrosoftStoreBootstrap(withoutBlocks);
83
+ case 'src/platform/runtimeDetector.ts':
84
+ return removeMicrosoftStoreRuntimeTarget(withoutBlocks);
85
+ case 'vite.shared.ts':
86
+ return removeMicrosoftStoreGatewayResolver(withoutBlocks);
71
87
  default:
72
88
  return withoutBlocks;
73
89
  }
@@ -97,6 +113,7 @@ export function initializeMicrosoftStoreStarter(input, runtime = {}) {
97
113
  const packageSource = readRequiredRegularFile(gameRoot, packageFile, 'package.json');
98
114
  const packageJson = parseJsonObject(packageSource, 'package.json');
99
115
  const scripts = requireJsonObject(packageJson.scripts, 'package.json scripts');
116
+ const dependencies = requireJsonObject(packageJson.dependencies, 'package.json dependencies');
100
117
  const legacyScripts = legacyMicrosoftStoreScripts(input.defaultKitPath);
101
118
  for (const [name, command] of Object.entries(microsoftStoreScripts(input.defaultKitPath))) {
102
119
  const existing = scripts[name];
@@ -106,6 +123,7 @@ export function initializeMicrosoftStoreStarter(input, runtime = {}) {
106
123
  }
107
124
  scripts[name] = command;
108
125
  }
126
+ dependencies[microsoftStoreAdapterBasePackage] = resolveAdapterDependency(dependencies[microsoftStoreAdapterBasePackage], input.adapterDependencyVersion);
109
127
  plan('package.json', formatJson(packageJson));
110
128
  const targetsFile = resolveGameFile(gameRoot, 'mpgd.targets.json');
111
129
  const targetsSource = readRequiredRegularFile(gameRoot, targetsFile, 'mpgd.targets.json');
@@ -116,12 +134,23 @@ export function initializeMicrosoftStoreStarter(input, runtime = {}) {
116
134
  targets['microsoft-store'] = microsoftStoreTarget;
117
135
  }
118
136
  else {
119
- assertCompatibleMicrosoftStoreTarget(existingTarget);
137
+ const compatibleTarget = assertCompatibleMicrosoftStoreTarget(existingTarget);
138
+ targets['microsoft-store'] = {
139
+ ...compatibleTarget,
140
+ ...microsoftStoreTarget,
141
+ ...(compatibleTarget.authoritativeGameServices === undefined
142
+ ? {}
143
+ : { authoritativeGameServices: compatibleTarget.authoritativeGameServices }),
144
+ };
120
145
  }
121
146
  plan('mpgd.targets.json', formatJson(targetsJson));
122
147
  const mainFile = resolveGameFile(gameRoot, 'src/main.ts');
123
148
  const mainSource = readRequiredRegularFile(gameRoot, mainFile, 'src/main.ts');
124
149
  plan('src/main.ts', addMicrosoftStoreBootstrap(mainSource));
150
+ const runtimeSource = readRequiredMicrosoftStoreMigrationFile(gameRoot, 'src/platform/runtimeDetector.ts');
151
+ plan('src/platform/runtimeDetector.ts', addMicrosoftStoreRuntimeTarget(runtimeSource));
152
+ const viteSource = readRequiredMicrosoftStoreMigrationFile(gameRoot, 'vite.shared.ts');
153
+ plan('vite.shared.ts', addMicrosoftStoreGatewayResolver(viteSource));
125
154
  planGitignore(gameRoot, plan);
126
155
  planAgentManifest(gameRoot, plan);
127
156
  planManagedDocumentation(gameRoot, templateRoot, plan);
@@ -133,6 +162,7 @@ export function initializeMicrosoftStoreStarter(input, runtime = {}) {
133
162
  '.agents/skills/release-microsoft-store/agents/openai.yaml',
134
163
  'docs/MPGD_KIT_WORKFLOWS.md',
135
164
  'mpgd.microsoft-store.json',
165
+ 'src/platform/buildGateways/microsoftStore.ts',
136
166
  'src/platform/microsoftStorePwa.ts',
137
167
  ]) {
138
168
  const destination = resolveGameFile(gameRoot, relativePath);
@@ -150,6 +180,135 @@ export function initializeMicrosoftStoreStarter(input, runtime = {}) {
150
180
  }
151
181
  return { changedFiles: writes.map((write) => write.relativePath).sort() };
152
182
  }
183
+ /**
184
+ * Floors registry-backed browser adapter versions at the first release that exports the
185
+ * Microsoft Store PWA adapter. Local workspace/link/file specs remain intentionally untouched.
186
+ */
187
+ export function resolveMicrosoftStoreAdapterDependencyVersion(candidate) {
188
+ return resolveAdapterDependency(candidate, microsoftStoreAdapterMinimumDependencyVersion);
189
+ }
190
+ function dependencyVersion(value) {
191
+ const normalized = value.trim();
192
+ if (normalized.length === 0
193
+ || !/^(?:workspace:\*|[~^]?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/u.test(normalized)) {
194
+ throw new Error('Microsoft Store adapter dependency version must be workspace:* or a SemVer version/range.');
195
+ }
196
+ return normalized;
197
+ }
198
+ function resolveAdapterDependency(existing, required) {
199
+ const requiredDependency = dependencyVersion(required);
200
+ if (existing === undefined) {
201
+ return requiredDependency;
202
+ }
203
+ if (typeof existing !== 'string' || existing.trim().length === 0) {
204
+ throw new Error(`package.json dependency ${microsoftStoreAdapterBasePackage} must be a string.`);
205
+ }
206
+ const normalized = existing.trim();
207
+ if (/^(?:workspace|link|file):/u.test(normalized)) {
208
+ return normalized;
209
+ }
210
+ const currentRange = parseSimpleSemverRange(normalized);
211
+ const requiredRange = parseSimpleSemverRange(requiredDependency);
212
+ if (currentRange === undefined) {
213
+ // A non-simple registry constraint cannot prove that installing it will expose the
214
+ // Microsoft Store entry point. Fail closed instead of retaining ranges such as
215
+ // `0.5.x` or `>=0.5.1 <0.6.0` that may resolve below the required release.
216
+ return requiredDependency;
217
+ }
218
+ if (requiredRange !== undefined
219
+ && compareSemver(currentRange, requiredRange) < 0) {
220
+ return requiredDependency;
221
+ }
222
+ // Preserve simple exact, tilde, and caret ranges already at or above the feature floor.
223
+ return normalized;
224
+ }
225
+ function parseSimpleSemverRange(value) {
226
+ const match = /^([~^]?)(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/u.exec(value);
227
+ if (match === null) {
228
+ return undefined;
229
+ }
230
+ return {
231
+ major: Number(match[2]),
232
+ minor: Number(match[3]),
233
+ patch: Number(match[4]),
234
+ ...(match[5] === undefined ? {} : { prerelease: match[5].split('.') }),
235
+ };
236
+ }
237
+ function compareSemver(left, right) {
238
+ const releaseComparison = left.major - right.major
239
+ || left.minor - right.minor
240
+ || left.patch - right.patch;
241
+ if (releaseComparison !== 0) {
242
+ return releaseComparison;
243
+ }
244
+ if (left.prerelease === undefined) {
245
+ return right.prerelease === undefined ? 0 : 1;
246
+ }
247
+ if (right.prerelease === undefined) {
248
+ return -1;
249
+ }
250
+ const identifierCount = Math.max(left.prerelease.length, right.prerelease.length);
251
+ for (let index = 0; index < identifierCount; index += 1) {
252
+ const comparison = comparePrereleaseIdentifier(left.prerelease[index], right.prerelease[index]);
253
+ if (comparison !== 0) {
254
+ return comparison;
255
+ }
256
+ }
257
+ return 0;
258
+ }
259
+ function comparePrereleaseIdentifier(left, right) {
260
+ if (left === undefined) {
261
+ return right === undefined ? 0 : -1;
262
+ }
263
+ if (right === undefined) {
264
+ return 1;
265
+ }
266
+ const leftNumeric = /^\d+$/u.test(left);
267
+ const rightNumeric = /^\d+$/u.test(right);
268
+ if (leftNumeric && rightNumeric) {
269
+ const leftNumber = BigInt(left);
270
+ const rightNumber = BigInt(right);
271
+ if (leftNumber !== rightNumber) {
272
+ return leftNumber < rightNumber ? -1 : 1;
273
+ }
274
+ return 0;
275
+ }
276
+ if (leftNumeric !== rightNumeric) {
277
+ return leftNumeric ? -1 : 1;
278
+ }
279
+ if (left === right) {
280
+ return 0;
281
+ }
282
+ return left < right ? -1 : 1;
283
+ }
284
+ function addMicrosoftStoreGatewayResolver(source) {
285
+ if (source.includes(microsoftStoreGatewayResolver.block)) {
286
+ return source;
287
+ }
288
+ return insertBefore(source, microsoftStoreGatewayResolver.anchor, microsoftStoreGatewayResolver.block, 'vite.shared.ts Microsoft Store gateway resolver');
289
+ }
290
+ function addMicrosoftStoreRuntimeTarget(source) {
291
+ if (source.includes(microsoftStoreRuntimeTarget.line)) {
292
+ return source;
293
+ }
294
+ return insertAfter(source, microsoftStoreRuntimeTarget.anchor, microsoftStoreRuntimeTarget.line, 'src/platform/runtimeDetector.ts target list');
295
+ }
296
+ function removeMicrosoftStoreRuntimeTarget(source) {
297
+ const managedLine = `${microsoftStoreRuntimeTarget.line}\n`;
298
+ const occurrences = source.split(managedLine).length - 1;
299
+ if (occurrences !== 1) {
300
+ throw new Error('Template src/platform/runtimeDetector.ts must contain exactly one Microsoft Store target.');
301
+ }
302
+ return source.replace(managedLine, '');
303
+ }
304
+ function removeMicrosoftStoreGatewayResolver(source) {
305
+ const managedBlock = `${microsoftStoreGatewayResolver.block}\n`;
306
+ const occurrences = source.split(managedBlock).length - 1;
307
+ if (occurrences !== 1) {
308
+ throw new Error('Template vite.shared.ts must contain exactly one Microsoft Store gateway resolver block.');
309
+ }
310
+ return source.replace(managedBlock, '');
311
+ }
153
312
  function microsoftStoreScripts(defaultKitPath) {
154
313
  const safeKitPath = requireSafeShellParameterDefaultPath(defaultKitPath);
155
314
  const kitPath = `"\${MPGD_KIT_PATH:-${safeKitPath}}"`;
@@ -180,13 +339,18 @@ function requireSafeShellParameterDefaultPath(value) {
180
339
  function assertCompatibleMicrosoftStoreTarget(value) {
181
340
  const target = requireJsonObject(value, 'microsoft-store target');
182
341
  const icon = isJsonObject(target.icon) ? target.icon : undefined;
342
+ if (target.authoritativeGameServices !== undefined
343
+ && typeof target.authoritativeGameServices !== 'boolean') {
344
+ throw new Error('Existing microsoft-store target authoritativeGameServices must be a boolean when present.');
345
+ }
183
346
  if (target.kind !== 'web'
184
- || target.adapter !== 'browser'
347
+ || (target.adapter !== 'browser' && target.adapter !== 'microsoft-store')
185
348
  || target.gameApp !== microsoftStoreTarget.gameApp
186
349
  || target.output !== microsoftStoreTarget.output
187
350
  || icon?.profile !== microsoftStoreTarget.icon.profile) {
188
- throw new Error('Existing microsoft-store target must use the game root, Store artifact directory, browser adapter, and microsoft-pwa icon profile.');
351
+ throw new Error('Existing microsoft-store target must use the game root, Store artifact directory, a supported Store adapter, and microsoft-pwa icon profile.');
189
352
  }
353
+ return target;
190
354
  }
191
355
  function addMicrosoftStoreBootstrap(source) {
192
356
  let output = source;
@@ -401,6 +565,14 @@ function readOptionalRegularFile(gameRoot, file, relativePath) {
401
565
  }
402
566
  return readRequiredRegularFile(gameRoot, file, relativePath);
403
567
  }
568
+ function readRequiredMicrosoftStoreMigrationFile(gameRoot, relativePath) {
569
+ const source = readOptionalRegularFile(gameRoot, resolveGameFile(gameRoot, relativePath), relativePath);
570
+ if (source === undefined) {
571
+ throw new Error('target init microsoft-store requires existing generated starter files: '
572
+ + 'src/platform/runtimeDetector.ts and vite.shared.ts.');
573
+ }
574
+ return source;
575
+ }
404
576
  function lstatIfExists(file) {
405
577
  try {
406
578
  return lstatSync(file);
@@ -1,4 +1,20 @@
1
1
  export declare const microsoftStoreSubmissionSchemaVersion: 1;
2
+ export interface MicrosoftStoreSubmissionCommerceProduct {
3
+ readonly logicalProductId: string;
4
+ /** Add-on Product ID used by the PWA Digital Goods API. */
5
+ readonly inAppOfferToken: string;
6
+ /** Partner Center Store ID used by Microsoft collections query and consume APIs. */
7
+ readonly storeId: string;
8
+ }
9
+ export type MicrosoftStoreSubmissionCommerce = {
10
+ readonly mode: 'disabled';
11
+ } | {
12
+ readonly mode: 'microsoft-store';
13
+ readonly productType: 'developer-managed-consumable';
14
+ readonly fulfillment: 'authoritative-server';
15
+ readonly authoritativeGameServices: true;
16
+ readonly products: readonly MicrosoftStoreSubmissionCommerceProduct[];
17
+ };
2
18
  export interface MicrosoftStoreSubmissionConfig {
3
19
  readonly schemaVersion: 1;
4
20
  readonly productIdentity: {
@@ -23,9 +39,7 @@ export interface MicrosoftStoreSubmissionConfig {
23
39
  readonly questionnaireCompleted: true;
24
40
  readonly iarcId?: string;
25
41
  };
26
- readonly commerce: {
27
- readonly mode: 'disabled';
28
- };
42
+ readonly commerce: MicrosoftStoreSubmissionCommerce;
29
43
  }
30
44
  export interface MicrosoftStoreSubmissionEvidence {
31
45
  readonly schemaVersion: 1;
@@ -49,6 +63,11 @@ export interface MicrosoftStoreSubmissionEvidence {
49
63
  readonly height: number;
50
64
  }[];
51
65
  };
66
+ /** Binds preflight evidence to the exact built target contract in every commerce mode. */
67
+ readonly effectiveTarget: {
68
+ readonly file: string;
69
+ readonly sha256: string;
70
+ };
52
71
  readonly listing: {
53
72
  readonly category: 'Games';
54
73
  readonly supportUrl: string;
@@ -75,5 +94,7 @@ export interface RunMicrosoftStoreSubmissionPreflightInput {
75
94
  readonly markdownFile: string;
76
95
  }
77
96
  export declare function runMicrosoftStoreSubmissionPreflight(input: RunMicrosoftStoreSubmissionPreflightInput): MicrosoftStoreSubmissionEvidence;
97
+ export declare function assertMicrosoftStoreEffectiveTarget(input: unknown, commerce: MicrosoftStoreSubmissionCommerce): void;
78
98
  export declare function parseMicrosoftStoreSubmissionConfig(input: unknown): MicrosoftStoreSubmissionConfig;
99
+ export declare function parseMicrosoftStoreSubmissionCommerce(input: Record<string, unknown>): MicrosoftStoreSubmissionCommerce;
79
100
  export declare function renderMicrosoftStoreSubmissionMarkdown(evidence: MicrosoftStoreSubmissionEvidence): string;