@mpgd/cli 0.46.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capacitor-shell-starter.d.ts +57 -1
- package/dist/capacitor-shell-starter.js +311 -34
- package/dist/command-launcher.d.ts +13 -0
- package/dist/command-launcher.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +149 -12
- package/dist/microsoft-store-submission.js +11 -6
- package/dist/native-build-info.json +2 -2
- package/dist/native-build-target.js +2413 -345
- package/dist/windows-host/Core/BridgeCommand.cs +17 -0
- package/dist/windows-host/Core/Mpgd.MicrosoftStore.Host.csproj +9 -0
- package/dist/windows-host/Core/NativeBridgeContracts.cs +33 -0
- package/dist/windows-host/Core/NativeBridgeHost.cs +162 -0
- package/dist/windows-host/Core/NativeBridgeOrigin.cs +19 -0
- package/dist/windows-host/Core/NativeBridgeProtocol.cs +335 -0
- package/dist/windows-host/Core/NativeBridgeRequestGate.cs +76 -0
- package/dist/windows-host/Core/NativeBridgeResult.cs +65 -0
- package/dist/windows-host/README.md +118 -0
- package/dist/windows-host/WinUI/DispatcherQueueBridgeDispatcher.cs +39 -0
- package/dist/windows-host/WinUI/WebView2DocumentTransport.cs +46 -0
- package/dist/windows-host/WinUI/WebView2NativeBridge.cs +185 -0
- package/dist/windows-host/WinUI/WindowStoreContext.cs +20 -0
- package/dist/windows-native-tools.d.ts +4 -0
- package/dist/windows-native-tools.js +21 -0
- package/native-tools/windows-msix/WindowsMsixAcceptance.psm1 +333 -0
- package/package.json +24 -14
- package/templates/lite-overlay/.agents/skills/use-mpgd-kit/SKILL.md +22 -0
- package/templates/lite-overlay/AGENTS.md +27 -0
- package/templates/lite-overlay/README.md +37 -0
- package/templates/lite-overlay/agent/acceptance.md +27 -0
- package/templates/lite-overlay/agent/brief.md +17 -0
- package/templates/lite-overlay/apps/target-devvit/README.md +134 -0
- package/templates/lite-overlay/docs/MPGD_KIT_WORKFLOWS.md +37 -0
- package/templates/lite-overlay/src/lite/tapGame.ts +77 -0
- package/templates/lite-overlay/src/platform/devvitEntrypoint.ts +116 -0
- package/templates/lite-overlay/src/platform/devvitInlineMode.css +146 -0
- package/templates/lite-overlay/src/runtime/createGame.ts +57 -0
- package/templates/phaser-game/README.md +5 -3
- package/templates/phaser-game/apps/target-ait/package.json +2 -2
- package/templates/phaser-game/apps/target-ait/src/main.ts +17 -0
- package/templates/phaser-game/apps/target-ait/src/runtime-config.types.ts +2 -0
- package/templates/phaser-game/apps/target-ait/vite.config.ts +27 -0
- package/templates/phaser-game/apps/target-devvit/package.json +2 -1
- package/templates/phaser-game/apps/target-devvit/src/server/bridge.ts +137 -18
- package/templates/phaser-game/apps/target-devvit/src/server/index.ts +35 -1
- package/templates/phaser-game/package.json +3 -1
- package/templates/phaser-game/pnpm-workspace.yaml +2 -0
- package/templates/phaser-game/src/env.d.ts +2 -0
- package/templates/phaser-game/src/main.ts +8 -1
- package/templates/phaser-game/src/platform/buildGateways/ait.ts +9 -0
- package/templates/phaser-game/src/platform/buildGateways/aitSandbox.ts +13 -1
- package/templates/phaser-game/src/platform/buildGateways/capacitorAndroid.ts +51 -2
- package/templates/phaser-game/src/platform/buildGateways/capacitorIos.ts +51 -2
- package/templates/phaser-game/src/platform/buildGateways/microsoftStore.ts +59 -26
- package/templates/phaser-game/src/platform/buildGateways/reddit.ts +36 -1
- package/templates/phaser-game/src/platform/buildGateways/verse8.ts +39 -2
- package/templates/phaser-game/src/platform/commerceAccountBinding.ts +79 -0
- package/templates/phaser-game/src/platform/commerceProducts.ts +69 -0
- package/templates/phaser-game/src/platform/gameServices.ts +22 -2
- package/templates/phaser-game/src/platform/monetizationJournal.ts +286 -0
- package/templates/phaser-game/src/platform/verse8Agent8Client.ts +22 -0
- package/templates/phaser-game/vite.shared.ts +40 -1
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
# Reusable isolated Windows MSIX acceptance. Game policy belongs to the caller.
|
|
2
|
+
Set-StrictMode -Version Latest
|
|
3
|
+
|
|
4
|
+
function Get-PeMachine {
|
|
5
|
+
param([Parameter(Mandatory = $true)][string]$Path)
|
|
6
|
+
$bytes = [IO.File]::ReadAllBytes($Path)
|
|
7
|
+
if ($bytes.Length -lt 64 -or $bytes[0] -ne 0x4D -or $bytes[1] -ne 0x5A) {
|
|
8
|
+
throw "Not a PE image: $Path"
|
|
9
|
+
}
|
|
10
|
+
$offset = [BitConverter]::ToInt32($bytes, 0x3C)
|
|
11
|
+
if ($offset -lt 64 -or $offset -gt $bytes.Length - 6 -or
|
|
12
|
+
$bytes[$offset] -ne 0x50 -or $bytes[$offset + 1] -ne 0x45 -or
|
|
13
|
+
$bytes[$offset + 2] -ne 0 -or $bytes[$offset + 3] -ne 0) {
|
|
14
|
+
throw "Invalid PE header: $Path"
|
|
15
|
+
}
|
|
16
|
+
return [BitConverter]::ToUInt16($bytes, $offset + 4)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function Test-PathWithin {
|
|
20
|
+
param([string]$Path, [string]$Root)
|
|
21
|
+
$prefix = [IO.Path]::GetFullPath($Root).TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar
|
|
22
|
+
return [IO.Path]::GetFullPath($Path).StartsWith($prefix, [StringComparison]::OrdinalIgnoreCase)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function Assert-MsixPayload {
|
|
26
|
+
param([string]$Directory, [string]$Identity, [string]$Publisher,
|
|
27
|
+
[ValidateSet('x64', 'ARM64')][string]$Architecture)
|
|
28
|
+
[xml]$manifest = Get-Content -LiteralPath (Join-Path $Directory 'AppxManifest.xml') -Raw
|
|
29
|
+
$packageIdentity = $manifest.Package.Identity
|
|
30
|
+
if ($packageIdentity.Name -cne $Identity -or $packageIdentity.Publisher -cne $Publisher -or
|
|
31
|
+
$packageIdentity.ProcessorArchitecture -ine $Architecture) {
|
|
32
|
+
throw 'MSIX identity, publisher or architecture does not match the selected game.'
|
|
33
|
+
}
|
|
34
|
+
$applications = @($manifest.Package.Applications.Application)
|
|
35
|
+
if ($applications.Count -ne 1) { throw 'Expected one packaged native application.' }
|
|
36
|
+
$application = $applications[0]
|
|
37
|
+
$executable = [string]$application.Executable
|
|
38
|
+
if (-not $executable.EndsWith('.exe', [StringComparison]::OrdinalIgnoreCase) -or
|
|
39
|
+
[IO.Path]::IsPathRooted($executable) -or $executable -match '(^|[\\/])\.\.([\\/]|$)') {
|
|
40
|
+
throw 'The manifest does not name a safe relative native executable.'
|
|
41
|
+
}
|
|
42
|
+
$executablePath = Join-Path $Directory $executable
|
|
43
|
+
if (-not (Test-PathWithin $executablePath $Directory)) { throw 'Executable escapes the package.' }
|
|
44
|
+
$machine = @{ x64 = 0x8664; ARM64 = 0xAA64 }[$Architecture]
|
|
45
|
+
if ((Get-PeMachine $executablePath) -ne $machine) { throw 'Wrong native executable architecture.' }
|
|
46
|
+
$appRoot = Split-Path -Parent $executablePath
|
|
47
|
+
foreach ($name in @('hostfxr.dll', 'hostpolicy.dll', 'coreclr.dll', 'System.Private.CoreLib.dll')) {
|
|
48
|
+
if (-not (Test-Path -LiteralPath (Join-Path $appRoot $name) -PathType Leaf)) {
|
|
49
|
+
throw "Missing bundled runtime file: $name"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if ((Get-PeMachine (Join-Path $appRoot 'coreclr.dll')) -ne $machine) {
|
|
53
|
+
throw 'The package contains another architecture''s .NET runtime.'
|
|
54
|
+
}
|
|
55
|
+
$processName = [IO.Path]::GetFileNameWithoutExtension($executable)
|
|
56
|
+
$runtime = Get-Content -LiteralPath (Join-Path $appRoot "$processName.runtimeconfig.json") -Raw | ConvertFrom-Json
|
|
57
|
+
$options = $runtime.runtimeOptions
|
|
58
|
+
$included = $options.PSObject.Properties['includedFrameworks']
|
|
59
|
+
if ($null -ne $options.PSObject.Properties['framework'] -or
|
|
60
|
+
$null -ne $options.PSObject.Properties['frameworks'] -or $null -eq $included -or
|
|
61
|
+
@($included.Value | Where-Object { $_.name -eq 'Microsoft.NETCore.App' }).Count -eq 0) {
|
|
62
|
+
throw 'The native package is framework-dependent or does not include Microsoft.NETCore.App.'
|
|
63
|
+
}
|
|
64
|
+
return @{
|
|
65
|
+
Identity = [string]$packageIdentity.Name; Publisher = [string]$packageIdentity.Publisher
|
|
66
|
+
Version = [string]$packageIdentity.Version; Architecture = $Architecture
|
|
67
|
+
AppId = [string]$application.Id; Executable = $executable; ProcessName = $processName
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Get-DescendantProcesses {
|
|
72
|
+
param([int]$RootProcessId, [object[]]$Processes)
|
|
73
|
+
$ids = [Collections.Generic.HashSet[int]]::new()
|
|
74
|
+
[void]$ids.Add($RootProcessId)
|
|
75
|
+
do {
|
|
76
|
+
$changed = $false
|
|
77
|
+
foreach ($process in $Processes) {
|
|
78
|
+
if ($ids.Contains([int]$process.ParentProcessId) -and $ids.Add([int]$process.ProcessId)) {
|
|
79
|
+
$changed = $true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
} while ($changed)
|
|
83
|
+
return @($Processes | Where-Object {
|
|
84
|
+
[int]$_.ProcessId -ne $RootProcessId -and $ids.Contains([int]$_.ProcessId)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function Read-AtomicJson {
|
|
89
|
+
param([string]$Path)
|
|
90
|
+
# Permit the host's atomic rename while this sample reads the previous file.
|
|
91
|
+
$stream = [IO.File]::Open($Path, [IO.FileMode]::Open, [IO.FileAccess]::Read,
|
|
92
|
+
([IO.FileShare]::ReadWrite -bor [IO.FileShare]::Delete))
|
|
93
|
+
$reader = [IO.StreamReader]::new($stream)
|
|
94
|
+
try { return ($reader.ReadToEnd() | ConvertFrom-Json) }
|
|
95
|
+
finally { $reader.Dispose() }
|
|
96
|
+
}
|
|
97
|
+
function Assert-MsixAcceptanceSource {
|
|
98
|
+
param([Parameter(Mandatory = $true)]$Source)
|
|
99
|
+
if ($Source.GitSha -cnotmatch '^[a-f0-9]{40}$' -or [string]$Source.RunId -notmatch '^[0-9]{1,20}$' -or
|
|
100
|
+
$Source.Owner -cnotmatch '^[a-z0-9][a-z0-9-]{0,63}$' -or [string]::IsNullOrWhiteSpace($Source.Workflow)) {
|
|
101
|
+
throw 'Native acceptance requires a full source SHA, run ID, owner and workflow.'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function Copy-MsixAdditionalEvidence {
|
|
105
|
+
param($Receipt, [hashtable]$Fields, [string[]]$Reserved)
|
|
106
|
+
foreach ($key in $Fields.Keys) {
|
|
107
|
+
if ($Reserved -icontains [string]$key) {
|
|
108
|
+
throw 'Additional evidence cannot overwrite native acceptance evidence.'
|
|
109
|
+
}
|
|
110
|
+
$Receipt[$key] = $Fields[$key]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function Complete-MsixAcceptanceEvidence {
|
|
115
|
+
param($Receipt, $Installed, [string]$OutputDirectory, [hashtable]$Fields,
|
|
116
|
+
[string[]]$Reserved, [scriptblock]$Collector)
|
|
117
|
+
if ($null -ne $Collector) {
|
|
118
|
+
& $Collector $Installed $OutputDirectory $Fields | Out-Null
|
|
119
|
+
Copy-MsixAdditionalEvidence $Receipt $Fields $Reserved
|
|
120
|
+
}
|
|
121
|
+
# Evidence collection must finish before an invocation can pass.
|
|
122
|
+
$Receipt.status = 'passed'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function Invoke-MsixNativeAcceptance {
|
|
126
|
+
[CmdletBinding()]
|
|
127
|
+
param(
|
|
128
|
+
[Parameter(Mandatory = $true)][string]$PackageDirectory,
|
|
129
|
+
[Parameter(Mandatory = $true)][string]$OutputDirectory,
|
|
130
|
+
[Parameter(Mandatory = $true)][string]$Identity,
|
|
131
|
+
[Parameter(Mandatory = $true)][string]$Publisher,
|
|
132
|
+
[Parameter(Mandatory = $true)][ValidateSet('x64', 'ARM64')][string]$Architecture,
|
|
133
|
+
[Parameter(Mandatory = $true)]$Source,
|
|
134
|
+
[ValidateRange(10, 120)][int]$ObserveSeconds = 30,
|
|
135
|
+
[hashtable]$ReceiptFields = @{},
|
|
136
|
+
[scriptblock]$InspectPayload,
|
|
137
|
+
[scriptblock]$TestReadiness,
|
|
138
|
+
[scriptblock]$CollectEvidence
|
|
139
|
+
)
|
|
140
|
+
$ErrorActionPreference = 'Stop'
|
|
141
|
+
if ($env:GITHUB_ACTIONS -ne 'true' -or $env:OS -ne 'Windows_NT') {
|
|
142
|
+
throw 'Native MSIX acceptance runs on an isolated GitHub Windows runner.'
|
|
143
|
+
}
|
|
144
|
+
$cpu = @(Get-CimInstance Win32_Processor)[0].Architecture
|
|
145
|
+
$expectedCpu = @{ x64 = 9; ARM64 = 12 }[$Architecture]
|
|
146
|
+
if ($cpu -ne $expectedCpu) { throw "Native $Architecture acceptance requires a matching Windows runner." }
|
|
147
|
+
|
|
148
|
+
New-Item -ItemType Directory -Force -Path $OutputDirectory | Out-Null
|
|
149
|
+
Assert-MsixAcceptanceSource $Source
|
|
150
|
+
$receipt = [ordered]@{
|
|
151
|
+
schemaVersion = 1; game = $Source.Owner; architecture = $Architecture; status = 'failed'
|
|
152
|
+
sourceRunId = $Source.RunId; sourceGitSha = $Source.GitSha
|
|
153
|
+
sourceWorkflow = $Source.Workflow
|
|
154
|
+
acceptanceGitSha = $env:GITHUB_SHA
|
|
155
|
+
acceptanceRun = "https://github.com/$env:GITHUB_REPOSITORY/actions/runs/$env:GITHUB_RUN_ID"
|
|
156
|
+
startedAt = [DateTime]::UtcNow.ToString('o'); observeSeconds = $ObserveSeconds
|
|
157
|
+
packageIdentityVerified = $false; selfContainedRuntimeVerified = $false
|
|
158
|
+
nativeInstallationVerified = $false; nativeLaunchVerified = $false
|
|
159
|
+
packagedCoreClrVerified = $false; winUiLoaded = $false; windowObserved = $false
|
|
160
|
+
webViewRendererObserved = $false
|
|
161
|
+
gameplayAcceptance = 'not-run'; loginPurchaseAcceptance = 'not-run'; storeUploaded = $false
|
|
162
|
+
cleanupErrors = @()
|
|
163
|
+
}
|
|
164
|
+
$reserved = @($receipt.Keys) + @('packageSha256', 'packageFile', 'identity', 'packageVersion',
|
|
165
|
+
'processId', 'loadedCoreClr', 'loadedWinUi', 'failure', 'finishedAt')
|
|
166
|
+
$extraEvidence = @{}
|
|
167
|
+
foreach ($key in $ReceiptFields.Keys) { $extraEvidence[$key] = $ReceiptFields[$key] }
|
|
168
|
+
Copy-MsixAdditionalEvidence $receipt $extraEvidence $reserved
|
|
169
|
+
$temporary = Join-Path $env:RUNNER_TEMP ('native-msix-' + [Guid]::NewGuid().ToString('N'))
|
|
170
|
+
New-Item -ItemType Directory -Path $temporary | Out-Null
|
|
171
|
+
$installed = $null
|
|
172
|
+
$certificate = $null
|
|
173
|
+
$installAttempted = $false
|
|
174
|
+
$hostProcess = $null
|
|
175
|
+
$metadata = $null
|
|
176
|
+
$started = Get-Date
|
|
177
|
+
$failure = $null
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
$packages = @(Get-ChildItem -LiteralPath $PackageDirectory -Filter *.msix -File -Recurse)
|
|
181
|
+
if ($packages.Count -ne 1) { throw 'Expected exactly one native MSIX artifact for this architecture.' }
|
|
182
|
+
$original = $packages[0].FullName
|
|
183
|
+
$originalHash = (Get-FileHash -LiteralPath $original -Algorithm SHA256).Hash
|
|
184
|
+
$receipt.packageSha256 = $originalHash.ToLowerInvariant()
|
|
185
|
+
$receipt.packageFile = $packages[0].Name
|
|
186
|
+
$sdkRoot = Join-Path ([Environment]::GetEnvironmentVariable('ProgramFiles(x86)')) 'Windows Kits\10\bin'
|
|
187
|
+
$tools = @(Get-ChildItem -Path (Join-Path $sdkRoot '*\*\MakeAppx.exe') -File |
|
|
188
|
+
Where-Object { $_.Directory.Name -in @($Architecture.ToLowerInvariant(), 'x64') } |
|
|
189
|
+
Sort-Object @{ Expression = { [version]$_.Directory.Parent.Name }; Descending = $true },
|
|
190
|
+
@{ Expression = { $_.Directory.Name -eq $Architecture.ToLowerInvariant() }; Descending = $true })
|
|
191
|
+
if ($tools.Count -eq 0) { throw 'Windows SDK MakeAppx was not found.' }
|
|
192
|
+
$makeAppx = $tools[0].FullName
|
|
193
|
+
$signTool = Join-Path $tools[0].DirectoryName 'signtool.exe'
|
|
194
|
+
if (-not (Test-Path -LiteralPath $signTool -PathType Leaf)) { throw 'Windows SDK SignTool was not found.' }
|
|
195
|
+
$inspection = Join-Path $temporary 'inspection'
|
|
196
|
+
& $makeAppx unpack /p $original /d $inspection /o
|
|
197
|
+
if ($LASTEXITCODE -ne 0) { throw 'MakeAppx could not inspect the package.' }
|
|
198
|
+
$metadata = Assert-MsixPayload $inspection $Identity $Publisher $Architecture
|
|
199
|
+
if ($null -ne $InspectPayload) {
|
|
200
|
+
& $InspectPayload $inspection $extraEvidence | Out-Null
|
|
201
|
+
Copy-MsixAdditionalEvidence $receipt $extraEvidence $reserved
|
|
202
|
+
}
|
|
203
|
+
$receipt.packageIdentityVerified = $true
|
|
204
|
+
$receipt.selfContainedRuntimeVerified = $true
|
|
205
|
+
$receipt.identity = $metadata.Identity
|
|
206
|
+
$receipt.packageVersion = $metadata.Version
|
|
207
|
+
|
|
208
|
+
# Refuse to replace any pre-existing installation, including another version.
|
|
209
|
+
if (@(Get-AppxPackage -Name $metadata.Identity).Count -ne 0) {
|
|
210
|
+
throw 'The selected game is already installed; this is not a clean acceptance runner.'
|
|
211
|
+
}
|
|
212
|
+
$copy = Join-Path $temporary 'test-signed.msix'
|
|
213
|
+
Copy-Item -LiteralPath $original -Destination $copy
|
|
214
|
+
$certificate = New-SelfSignedCertificate -Type Custom -Subject $metadata.Publisher -KeyUsage DigitalSignature -CertStoreLocation 'Cert:\CurrentUser\My' -TextExtension @('2.5.29.37={text}1.3.6.1.5.5.7.3.3', '2.5.29.19={text}')
|
|
215
|
+
& $signTool sign /fd SHA256 /sha1 $certificate.Thumbprint $copy
|
|
216
|
+
if ($LASTEXITCODE -ne 0) { throw 'Could not test-sign the package copy.' }
|
|
217
|
+
$publicCertificate = Join-Path $temporary 'test-signing.cer'
|
|
218
|
+
Export-Certificate -Cert $certificate -FilePath $publicCertificate | Out-Null
|
|
219
|
+
Import-Certificate -FilePath $publicCertificate -CertStoreLocation 'Cert:\LocalMachine\TrustedPeople' | Out-Null
|
|
220
|
+
$installAttempted = $true
|
|
221
|
+
Add-AppxPackage -Path $copy
|
|
222
|
+
$matches = @(Get-AppxPackage -Name $metadata.Identity)
|
|
223
|
+
if ($matches.Count -ne 1 -or $matches[0].Version.ToString() -cne $metadata.Version) {
|
|
224
|
+
throw 'Installed package differs from the inspected artifact.'
|
|
225
|
+
}
|
|
226
|
+
$installed = $matches[0]
|
|
227
|
+
$receipt.nativeInstallationVerified = $true
|
|
228
|
+
$hostLog = Join-Path $env:LOCALAPPDATA "Packages\$($installed.PackageFamilyName)\LocalState\host-error.log"
|
|
229
|
+
$expectedExecutable = Join-Path $installed.InstallLocation $metadata.Executable
|
|
230
|
+
$started = Get-Date
|
|
231
|
+
Start-Process -FilePath "shell:AppsFolder\$($installed.PackageFamilyName)!$($metadata.AppId)" -WindowStyle Hidden
|
|
232
|
+
$clock = [Diagnostics.Stopwatch]::StartNew()
|
|
233
|
+
$readyAt = $null
|
|
234
|
+
while ($clock.Elapsed.TotalSeconds -lt (60 + $ObserveSeconds)) {
|
|
235
|
+
Start-Sleep -Seconds 1
|
|
236
|
+
if (Test-Path -LiteralPath $hostLog) { throw 'The installed host recorded a startup exception.' }
|
|
237
|
+
$candidates = @(Get-Process -Name $metadata.ProcessName -ErrorAction SilentlyContinue | Where-Object {
|
|
238
|
+
$_.StartTime -ge $started -and $_.MainModule.FileName -ieq $expectedExecutable
|
|
239
|
+
})
|
|
240
|
+
if ($candidates.Count -gt 1) { throw 'Multiple newly activated native host processes were found.' }
|
|
241
|
+
if ($candidates.Count -eq 0) {
|
|
242
|
+
if ($null -ne $readyAt -or $clock.Elapsed.TotalSeconds -gt 60) { throw 'The installed native host exited or never started.' }
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
$hostProcess = $candidates[0]
|
|
246
|
+
$modules = @($hostProcess.Modules)
|
|
247
|
+
$core = @($modules | Where-Object { $_.ModuleName -ieq 'coreclr.dll' })
|
|
248
|
+
$xaml = @($modules | Where-Object { $_.ModuleName -ieq 'Microsoft.UI.Xaml.dll' })
|
|
249
|
+
$receipt.packagedCoreClrVerified = $core.Count -eq 1 -and (Test-PathWithin $core[0].FileName $installed.InstallLocation)
|
|
250
|
+
$receipt.winUiLoaded = $xaml.Count -eq 1 -and (Test-PathWithin $xaml[0].FileName $installed.InstallLocation)
|
|
251
|
+
$receipt.windowObserved = $hostProcess.MainWindowHandle -ne [IntPtr]::Zero
|
|
252
|
+
$children = @(Get-DescendantProcesses $hostProcess.Id @(Get-CimInstance Win32_Process))
|
|
253
|
+
$renderers = @($children | Where-Object {
|
|
254
|
+
$_.Name -ieq 'msedgewebview2.exe' -and $_.CommandLine -match '(^|\s)--type=renderer(\s|$)'
|
|
255
|
+
})
|
|
256
|
+
$receipt.webViewRendererObserved = $renderers.Count -gt 0
|
|
257
|
+
$applicationReady = $true
|
|
258
|
+
if ($null -ne $TestReadiness) {
|
|
259
|
+
$applicationReady = & $TestReadiness $installed $extraEvidence
|
|
260
|
+
if ($applicationReady -isnot [bool]) { throw 'Application readiness callback must return one Boolean.' }
|
|
261
|
+
Copy-MsixAdditionalEvidence $receipt $extraEvidence $reserved
|
|
262
|
+
}
|
|
263
|
+
$ready = $receipt.packagedCoreClrVerified -and $receipt.winUiLoaded -and $receipt.windowObserved -and $receipt.webViewRendererObserved -and $applicationReady
|
|
264
|
+
if (-not $ready) {
|
|
265
|
+
if ($null -ne $readyAt -or $clock.Elapsed.TotalSeconds -gt 60) {
|
|
266
|
+
throw 'The installed host did not sustain packaged CLR, WinUI, a native window and a WebView2 renderer.'
|
|
267
|
+
}
|
|
268
|
+
continue
|
|
269
|
+
}
|
|
270
|
+
if ($null -eq $readyAt) {
|
|
271
|
+
$readyAt = $clock.Elapsed.TotalSeconds
|
|
272
|
+
$receipt.processId = $hostProcess.Id
|
|
273
|
+
$receipt.loadedCoreClr = $core[0].FileName
|
|
274
|
+
$receipt.loadedWinUi = $xaml[0].FileName
|
|
275
|
+
}
|
|
276
|
+
if ($clock.Elapsed.TotalSeconds - $readyAt -ge $ObserveSeconds) { break }
|
|
277
|
+
}
|
|
278
|
+
if ($null -eq $readyAt -or $clock.Elapsed.TotalSeconds - $readyAt -lt $ObserveSeconds) {
|
|
279
|
+
throw 'Native readiness was not observed for the required duration.'
|
|
280
|
+
}
|
|
281
|
+
if ((Get-FileHash -LiteralPath $original -Algorithm SHA256).Hash -cne $originalHash) {
|
|
282
|
+
throw 'The original unsigned Store artifact changed during acceptance.'
|
|
283
|
+
}
|
|
284
|
+
if (Test-Path -LiteralPath $hostLog) { throw 'The installed host recorded a startup exception.' }
|
|
285
|
+
$receipt.nativeLaunchVerified = $true
|
|
286
|
+
Complete-MsixAcceptanceEvidence $receipt $installed $OutputDirectory $extraEvidence $reserved $CollectEvidence
|
|
287
|
+
} catch {
|
|
288
|
+
$failure = $_.Exception.Message
|
|
289
|
+
$receipt.failure = $failure
|
|
290
|
+
if ($null -ne $installed) {
|
|
291
|
+
$log = Join-Path $env:LOCALAPPDATA "Packages\$($installed.PackageFamilyName)\LocalState\host-error.log"
|
|
292
|
+
if (Test-Path -LiteralPath $log) { Copy-Item -LiteralPath $log -Destination (Join-Path $OutputDirectory 'host-error.log') }
|
|
293
|
+
}
|
|
294
|
+
$events = @()
|
|
295
|
+
foreach ($name in @('Application', 'Microsoft-Windows-AppModel-Runtime/Admin', 'Microsoft-Windows-AppXDeploymentServer/Operational')) {
|
|
296
|
+
$events += @(Get-WinEvent -FilterHashtable @{ LogName = $name; StartTime = $started; Level = 1, 2, 3 } -ErrorAction SilentlyContinue |
|
|
297
|
+
Select-Object -First 10 TimeCreated, ProviderName, Id, LevelDisplayName, Message)
|
|
298
|
+
}
|
|
299
|
+
ConvertTo-Json -InputObject $events -Depth 6 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'windows-events.json') -Encoding UTF8
|
|
300
|
+
} finally {
|
|
301
|
+
# Clean up only this invocation's package/process and exact generated certificate.
|
|
302
|
+
if ($null -ne $hostProcess) {
|
|
303
|
+
$current = Get-Process -Id $hostProcess.Id -ErrorAction SilentlyContinue
|
|
304
|
+
if ($null -ne $current -and $current.StartTime -eq $hostProcess.StartTime) {
|
|
305
|
+
Stop-Process -Id $current.Id -ErrorAction SilentlyContinue
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if ($installAttempted -and $null -ne $metadata) {
|
|
309
|
+
try {
|
|
310
|
+
foreach ($package in @(Get-AppxPackage -Name $metadata.Identity)) {
|
|
311
|
+
if ($package.Version.ToString() -ceq $metadata.Version) {
|
|
312
|
+
Remove-AppxPackage -Package $package.PackageFullName -ErrorAction Stop
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} catch { $receipt.cleanupErrors += $_.Exception.Message }
|
|
316
|
+
}
|
|
317
|
+
if ($null -ne $certificate) {
|
|
318
|
+
try {
|
|
319
|
+
$trustPath = "Cert:\LocalMachine\TrustedPeople\$($certificate.Thumbprint)"
|
|
320
|
+
if (Test-Path -LiteralPath $trustPath) { Remove-Item -LiteralPath $trustPath -ErrorAction Stop }
|
|
321
|
+
Remove-Item -LiteralPath "Cert:\CurrentUser\My\$($certificate.Thumbprint)" -DeleteKey -ErrorAction Stop
|
|
322
|
+
} catch { $receipt.cleanupErrors += $_.Exception.Message }
|
|
323
|
+
}
|
|
324
|
+
if ($receipt.cleanupErrors.Count -gt 0) { $receipt.status = 'failed' }
|
|
325
|
+
$receipt.finishedAt = [DateTime]::UtcNow.ToString('o')
|
|
326
|
+
$receipt | ConvertTo-Json -Depth 10 | Set-Content -LiteralPath (Join-Path $OutputDirectory 'native-smoke-receipt.json') -Encoding UTF8
|
|
327
|
+
}
|
|
328
|
+
if ($receipt.status -ne 'passed') { throw "Native MSIX acceptance failed: $failure; cleanup errors: $($receipt.cleanupErrors.Count)" }
|
|
329
|
+
Write-Output "Installed $Identity ($Architecture): packaged CLR, WinUI window, WebView2 and application readiness sustained for $ObserveSeconds seconds."
|
|
330
|
+
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
Export-ModuleMember -Function Get-PeMachine, Test-PathWithin, Assert-MsixPayload, Get-DescendantProcesses, Read-AtomicJson, Assert-MsixAcceptanceSource, Invoke-MsixNativeAcceptance
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.48.0",
|
|
4
4
|
"description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"type": "module",
|
|
28
28
|
"sideEffects": false,
|
|
29
29
|
"exports": {
|
|
30
|
+
"./windows-native-tools": {
|
|
31
|
+
"types": "./dist/windows-native-tools.d.ts",
|
|
32
|
+
"default": "./dist/windows-native-tools.js"
|
|
33
|
+
},
|
|
30
34
|
".": {
|
|
31
35
|
"types": "./dist/index.d.ts",
|
|
32
36
|
"default": "./dist/index.js"
|
|
@@ -34,6 +38,7 @@
|
|
|
34
38
|
},
|
|
35
39
|
"files": [
|
|
36
40
|
"dist",
|
|
41
|
+
"native-tools/windows-msix",
|
|
37
42
|
"templates"
|
|
38
43
|
],
|
|
39
44
|
"bin": {
|
|
@@ -58,9 +63,13 @@
|
|
|
58
63
|
"undici": "^7.29.1",
|
|
59
64
|
"vite": "8.1.3",
|
|
60
65
|
"yauzl": "3.4.0",
|
|
61
|
-
"
|
|
66
|
+
"parse5": "7.3.0",
|
|
67
|
+
"postcss": "8.5.28",
|
|
68
|
+
"postcss-value-parser": "4.2.0",
|
|
69
|
+
"@mpgd/catalog": "0.10.0",
|
|
62
70
|
"@mpgd/phaser-assets": "0.9.0",
|
|
63
|
-
"@mpgd/
|
|
71
|
+
"@mpgd/platform": "0.23.0",
|
|
72
|
+
"@mpgd/target-config": "0.23.0"
|
|
64
73
|
},
|
|
65
74
|
"devDependencies": {
|
|
66
75
|
"@types/node": "^24.0.0",
|
|
@@ -68,17 +77,18 @@
|
|
|
68
77
|
"@types/yauzl": "3.4.0",
|
|
69
78
|
"ttsc": "0.30.4",
|
|
70
79
|
"typescript": "7.0.2",
|
|
71
|
-
"@mpgd/adapter-ait": "0.
|
|
72
|
-
"@mpgd/adapter-
|
|
73
|
-
"@mpgd/adapter-
|
|
74
|
-
"@mpgd/adapter-devvit": "0.
|
|
75
|
-
"@mpgd/
|
|
76
|
-
"@mpgd/
|
|
77
|
-
"@mpgd/
|
|
78
|
-
"@mpgd/
|
|
79
|
-
"@mpgd/
|
|
80
|
-
"@mpgd/
|
|
81
|
-
"@mpgd/
|
|
80
|
+
"@mpgd/adapter-ait": "0.17.0",
|
|
81
|
+
"@mpgd/adapter-browser": "0.11.0",
|
|
82
|
+
"@mpgd/adapter-capacitor": "0.10.0",
|
|
83
|
+
"@mpgd/adapter-devvit": "0.12.0",
|
|
84
|
+
"@mpgd/analytics": "0.4.9",
|
|
85
|
+
"@mpgd/adapter-verse8": "0.7.0",
|
|
86
|
+
"@mpgd/capacitor-play-billing": "0.3.0",
|
|
87
|
+
"@mpgd/bridge": "0.13.0",
|
|
88
|
+
"@mpgd/capacitor-storekit": "0.3.0",
|
|
89
|
+
"@mpgd/game-services": "0.24.0",
|
|
90
|
+
"@mpgd/game-runtime": "0.7.0",
|
|
91
|
+
"@mpgd/i18n": "0.6.14"
|
|
82
92
|
},
|
|
83
93
|
"main": "./dist/index.js",
|
|
84
94
|
"types": "./dist/index.d.ts",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-mpgd-kit
|
|
3
|
+
description: Route lite Canvas2D game development, platform integration, target builds, acceptance, and release work through this generated game's mpgd-kit contracts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Use mpgd-kit
|
|
7
|
+
|
|
8
|
+
Read `AGENTS.md`, `agent/game-manifest.json`, and `docs/MPGD_KIT_WORKFLOWS.md` before editing.
|
|
9
|
+
|
|
10
|
+
1. Identify capability ownership and configured targets from the manifest.
|
|
11
|
+
2. Keep deterministic rules in `src/game` and Canvas2D rendering/input in `src/lite` through `@mpgd/lite-shell`.
|
|
12
|
+
3. Use `PlatformGateway`; keep SDK calls in adapters or target wrappers.
|
|
13
|
+
4. Use backend ledger or verifier APIs before mutating durable purchases, rewarded grants, or verified scores.
|
|
14
|
+
5. Keep runtime, presentation, recovery, and audio ownership above renderer lifetimes; preserve durable encrypted journals and late observers.
|
|
15
|
+
6. Update the manifest, brief, and acceptance guidance when capabilities or handoff contracts change.
|
|
16
|
+
7. Run focused checks, then `pnpm check`, `pnpm build`, and `pnpm accept` for target handoffs.
|
|
17
|
+
8. Preserve generated JSON/Markdown acceptance and release evidence.
|
|
18
|
+
|
|
19
|
+
Read a target skill declared in the manifest before changing its release flow.
|
|
20
|
+
Use official target documentation or the configured MCP for SDK and submission requirements.
|
|
21
|
+
Keep the lite game free of Phaser, preserve native scrolling and history restoration, and verify Devvit inline budget.
|
|
22
|
+
Do not commit tokens, credentials, signing material, personal MCP configuration, or platform login state.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Generated Lite Game Agent Instructions
|
|
2
|
+
|
|
3
|
+
This is a Canvas2D game generated with the mpgd lite template.
|
|
4
|
+
|
|
5
|
+
## Start here
|
|
6
|
+
|
|
7
|
+
- Read `agent/game-manifest.json`, `agent/brief.md`, and `agent/acceptance.md`.
|
|
8
|
+
- Use `.agents/skills/use-mpgd-kit/SKILL.md` for platform, target, acceptance, and release work.
|
|
9
|
+
- Read `docs/MPGD_KIT_WORKFLOWS.md` for capability ownership.
|
|
10
|
+
|
|
11
|
+
## Boundaries
|
|
12
|
+
|
|
13
|
+
- Keep deterministic rules in `src/game`; rendering and input belong in `src/lite`.
|
|
14
|
+
- Use `@mpgd/lite-shell` and Canvas2D. Keep the lite template free of Phaser.
|
|
15
|
+
- Put platform SDK imports in adapters or target wrappers; use `PlatformGateway` from game code.
|
|
16
|
+
- Keep pure game packages free of DOM, network calls, and SDK imports.
|
|
17
|
+
- Purchases and rewarded-ad callbacks are evidence; backend ledger grants precede durable state changes.
|
|
18
|
+
- Create one game runtime above renderer lifetimes. Renderer disposal must not release another owner's native presentation or audio.
|
|
19
|
+
- Keep safe-area geometry reserved once and preserve native scrolling in Devvit inline mode.
|
|
20
|
+
- Do not commit secrets, signing keys, personal MCP configuration, or platform authentication state.
|
|
21
|
+
|
|
22
|
+
## Tooling and checks
|
|
23
|
+
|
|
24
|
+
- Use `ttsx`, `ttsc`, and `@ttsc/lint`; do not add `tsx`, ESLint, or Prettier.
|
|
25
|
+
- Run focused checks, then `pnpm check` and `pnpm build`.
|
|
26
|
+
- Run `pnpm accept` for a target handoff; preserve its generated evidence.
|
|
27
|
+
- Keep the manifest, brief, and acceptance guidance aligned with material changes.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# __GAME_TITLE__
|
|
2
|
+
|
|
3
|
+
Standalone Canvas2D lite game generated by `@mpgd/create-game --template lite --workspace`.
|
|
4
|
+
It uses `@mpgd/lite-shell`, `PlatformGateway`, and the shared mpgd game runtime.
|
|
5
|
+
|
|
6
|
+
## Start
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm install
|
|
10
|
+
pnpm dev
|
|
11
|
+
pnpm check
|
|
12
|
+
pnpm build
|
|
13
|
+
pnpm accept
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The lite-shell package is private until its initial npm registration. Create lite games with `--workspace` and a local kit checkout.
|
|
17
|
+
The CLI supports the canvas renderer; DOM and SVG renderers are planned.
|
|
18
|
+
|
|
19
|
+
## Ownership
|
|
20
|
+
|
|
21
|
+
- `src/game/state.ts`: deterministic state.
|
|
22
|
+
- `src/lite/tapGame.ts`: Canvas2D renderer and input.
|
|
23
|
+
- `src/runtime/createGame.ts`: mount, audio binding, and teardown.
|
|
24
|
+
- `src/platform`: gateway selection and authoritative services.
|
|
25
|
+
- `mpgd.targets.json`: configured target build and feature ownership.
|
|
26
|
+
|
|
27
|
+
Read `AGENTS.md`, `agent/brief.md`, `agent/acceptance.md`, and `docs/MPGD_KIT_WORKFLOWS.md` before material edits.
|
|
28
|
+
Use generated icon, localization, build/smoke, and acceptance commands; preserve their evidence.
|
|
29
|
+
Keep SDK calls outside game and renderer code. Backend ledger grants precede durable commerce or rewarded changes.
|
|
30
|
+
Never commit platform credentials or signing material.
|
|
31
|
+
|
|
32
|
+
## Devvit and browser behavior
|
|
33
|
+
|
|
34
|
+
Inline and expanded entries both use the lite renderer. Preserve feed scrolling and browser touch gestures.
|
|
35
|
+
Verify static imports, preloads, and CSS imports against the inline budget; defer optional engines only through explicit dynamic imports.
|
|
36
|
+
Returning through browser history must retain a persisted game. Verify resize, DPR, safe areas, lifecycle, and native presentation ownership on configured targets.
|
|
37
|
+
Target smoke verifies build artifacts; real SDK and device acceptance is required for platform release evidence.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Lite Game Acceptance
|
|
2
|
+
|
|
3
|
+
Use `agent/game-manifest.json` and `.agents/skills/use-mpgd-kit/SKILL.md` to select configured target checks.
|
|
4
|
+
Run `pnpm check`, `pnpm build`, `pnpm icons:generate`, `pnpm icons:verify`, and `pnpm accept`.
|
|
5
|
+
Preserve the generated JSON and Markdown in `artifacts/acceptance` or the target release-output directory.
|
|
6
|
+
|
|
7
|
+
- Confirm the first screen is playable and uses Canvas2D without a Phaser dependency or scene imports.
|
|
8
|
+
- Resize the game, verify DPR handling, and reserve safe-area geometry exactly once.
|
|
9
|
+
- Check pause/resume, browser history restoration, and audio ownership. A persisted `pagehide` must retain the game.
|
|
10
|
+
- Use the game-owned driver for gameplay and performance acceptance; unit tests do not certify live SDK behavior.
|
|
11
|
+
- Verify renderer teardown removes its own listeners and never updates a replacement view from an old promise.
|
|
12
|
+
- Purchases and rewarded-ad callbacks never grant locally; verify authoritative ledger grants and durable reconciliation before release.
|
|
13
|
+
- Keep unknown native outcomes and late recovery observers; closing one lease must not release another owner's presentation or audio.
|
|
14
|
+
|
|
15
|
+
## Reddit Devvit
|
|
16
|
+
|
|
17
|
+
Keep SDK calls in the adapter or `apps/target-devvit`. Verify both inline and expanded game modes, preserve feed scrolling, and run the configured inline build budget.
|
|
18
|
+
The inline first load must reject forbidden modules and include static imports, preloads, and CSS imports. Only declared dynamic imports can be deferred.
|
|
19
|
+
Run login/init/playtest for a real Devvit handoff after platform setup. Artifact smoke is not deployed-platform evidence.
|
|
20
|
+
|
|
21
|
+
## Other configured targets
|
|
22
|
+
|
|
23
|
+
Use the configured official MCP or SDK documentation before SDK or submission changes. Keep target secrets and signing material outside source control.
|
|
24
|
+
ONE play requires issued IDs, authenticated issuer routes, signed SSV/PNS, purchase verification, ledger-before-finalization, and atomic cancellation fencing.
|
|
25
|
+
Verse8 server records require authenticated senders and a game-specific completion verifier.
|
|
26
|
+
Apps in Toss and native Store builds require real-app identity, lifecycle, and SDK acceptance; generic fixtures do not certify live commerce or ads.
|
|
27
|
+
Uploading and deployment follow the target's release procedure after acceptance.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# __GAME_TITLE__ Brief
|
|
2
|
+
|
|
3
|
+
Build a Canvas2D game using `@mpgd/lite-shell` and the mpgd platform boundary.
|
|
4
|
+
Read `AGENTS.md`, the agent manifest, and the `use-mpgd-kit` router before editing.
|
|
5
|
+
|
|
6
|
+
- Deterministic rules and state belong in `src/game/state.ts`.
|
|
7
|
+
- Rendering and input belong in `src/lite/tapGame.ts`; mounting belongs in `src/runtime/createGame.ts`.
|
|
8
|
+
- Use `PlatformGateway` for identity, storage, commerce, ads, lifecycle, and host presentation.
|
|
9
|
+
- Keep SDK calls in adapters or target wrappers and use backend ledger APIs before durable grants.
|
|
10
|
+
- Create one `createGamePlatformRuntime` during bootstrap. Bind audio through `bindGameAudio` with the game-owned execution scope.
|
|
11
|
+
- Keep recovery above renderer lifetimes; reconcile existing operations on startup and resume without opening SDK UI.
|
|
12
|
+
- Rewarded v2 needs an application-owned durable encrypted journal. An in-memory store is not recovery evidence.
|
|
13
|
+
- Reserve safe-area geometry once and preserve feed scrolling and browser touch gestures.
|
|
14
|
+
|
|
15
|
+
Run `pnpm dev`, `pnpm check`, `pnpm build`, and `pnpm accept`.
|
|
16
|
+
Devvit inline and expanded entries both use the lite renderer. Keep optional engines behind explicit dynamic import boundaries and verify the inline budget.
|
|
17
|
+
Production commerce and ads remain unavailable until authenticated backend, issued IDs, durable stores, and real-platform acceptance are configured.
|