@mpgd/cli 0.34.0 → 0.36.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/android-bundle-signer.d.ts +2 -0
- package/dist/android-bundle-signer.js +38 -0
- package/dist/android-signing-session.d.ts +22 -0
- package/dist/android-signing-session.js +198 -0
- package/dist/artifact-inspection-environment.d.ts +2 -0
- package/dist/artifact-inspection-environment.js +31 -0
- package/dist/asc-pin.json +26 -0
- package/dist/capacitor-shell-starter.d.ts +1 -0
- package/dist/capacitor-shell-starter.js +9 -6
- package/dist/deploy-planning.d.ts +64 -0
- package/dist/deploy-planning.js +471 -0
- package/dist/deploy-process.d.ts +28 -0
- package/dist/deploy-process.js +197 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +483 -13
- package/dist/ios-ipa-inspection.js +151 -0
- package/dist/ios-keychain-import.swift +92 -0
- package/dist/ios-plist.d.ts +2 -0
- package/dist/ios-plist.js +75 -0
- package/dist/ios-signing-profile.d.ts +12 -0
- package/dist/ios-signing-profile.js +60 -0
- package/dist/ios-signing-session.d.ts +22 -0
- package/dist/ios-signing-session.js +256 -0
- package/dist/kit-upgrade-target-readiness.d.ts +8 -0
- package/dist/kit-upgrade-target-readiness.js +119 -0
- package/dist/kit-upgrade.d.ts +41 -0
- package/dist/kit-upgrade.js +489 -0
- package/dist/native-build-info.json +5 -0
- package/dist/native-build-target.js +12032 -0
- package/dist/native-deploy-run.d.ts +35 -0
- package/dist/native-deploy-run.js +462 -0
- package/dist/native-deploy-submission.d.ts +40 -0
- package/dist/native-deploy-submission.js +244 -0
- package/dist/native-deploy-workflow.d.ts +11 -0
- package/dist/native-deploy-workflow.js +328 -0
- package/dist/play-internal-submission.d.ts +32 -0
- package/dist/play-internal-submission.js +247 -0
- package/dist/play-publisher-port.d.ts +30 -0
- package/dist/play-publisher-port.js +1 -0
- package/dist/play-sdk-adapter.js +78 -0
- package/dist/release-state.d.ts +140 -0
- package/dist/release-state.js +1046 -0
- package/dist/release-workspace.d.ts +61 -0
- package/dist/release-workspace.js +504 -0
- package/dist/testflight-submission.d.ts +49 -0
- package/dist/testflight-submission.js +462 -0
- package/package.json +20 -13
- package/templates/phaser-game/README.md +23 -8
- package/templates/phaser-game/src/main.ts +34 -33
package/dist/index.js
CHANGED
|
@@ -6,9 +6,12 @@ import completion from '@gunshi/plugin-completion';
|
|
|
6
6
|
import i18n, { defineI18n } from '@gunshi/plugin-i18n';
|
|
7
7
|
import resources from '@gunshi/resources';
|
|
8
8
|
import { cli } from 'gunshi';
|
|
9
|
+
import { assertPlatformVersionLedger } from '@mpgd/target-config';
|
|
9
10
|
import { buildAssetPacks } from './asset-pack-build.js';
|
|
10
11
|
import { verifyAssetPackDelivery } from './asset-pack-verify.js';
|
|
11
12
|
import { materializeCapacitorShellStarter, planCapacitorShellStarter, } from './capacitor-shell-starter.js';
|
|
13
|
+
import { doctorNativeDeployment, initializeDeployConfig, parseDeployTargets, planNativeDeployment, readNativeDeploymentPlan, writeNativeDeploymentPlan, } from './deploy-planning.js';
|
|
14
|
+
import { initializeNativeDeployWorkflow } from './native-deploy-workflow.js';
|
|
12
15
|
import { normalizeConfiguredBuildTargets, normalizeBuildTarget as normalizeConfiguredTargetName, supportedBuildTargets, } from './build-targets.js';
|
|
13
16
|
import { escapeMarkdownInline as escapeEvidenceMarkdownInline, writeEvidenceReportFiles, } from './evidence-io.js';
|
|
14
17
|
import { defaultGameAcceptanceCommandTimeoutMs, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, } from './game-acceptance.js';
|
|
@@ -17,11 +20,23 @@ import { cloudflarePagesDeploymentProfiles, hostedPwaDeploymentHosts, verifyHost
|
|
|
17
20
|
import { createMicrosoftStorePackageAcceptanceRuntime, runMicrosoftStorePackageAcceptance, } from './microsoft-store-package-acceptance.js';
|
|
18
21
|
import { runMicrosoftStorePackageGeneration } from './microsoft-store-package-generation.js';
|
|
19
22
|
import { defaultMpgdKitPath, initializeMicrosoftStoreStarter, listMicrosoftStoreInitializerTemplateFiles, prepareBaseGameTemplateFile, resolveMicrosoftStoreAdapterDependencyVersion, } from './microsoft-store-starter.js';
|
|
23
|
+
import { applyKitUpgrade, planKitUpgrade } from './kit-upgrade.js';
|
|
20
24
|
import { runMicrosoftStoreSubmissionPreflight } from './microsoft-store-submission.js';
|
|
21
25
|
import { defaultOfflinePlaytestArtifactDir, defaultOfflinePlaytestMaximumBytes, defaultOfflinePlaytestOutputDir, runOfflinePlaytestPackaging, } from './offline-playtest.js';
|
|
22
|
-
import { targetConfigExtensionsFileEnv } from './target-config-env.js';
|
|
26
|
+
import { targetConfigExtensionsFileEnv, targetConfigMatrixFileEnv } from './target-config-env.js';
|
|
27
|
+
import { readStoreCredential, runNativeDeployment } from './native-deploy-run.js';
|
|
28
|
+
import { submitRecordedNativeTarget } from './native-deploy-submission.js';
|
|
29
|
+
import { readNativeReleaseStatus as readNativeReleaseStatusForCli } from './release-state.js';
|
|
23
30
|
export { renderGameAcceptanceMarkdown, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, defaultGameAcceptanceCommandTimeoutMs, maximumGameplayE2EReportBytes, } from './game-acceptance.js';
|
|
24
31
|
export { applyCapacitorShellStarter, materializeCapacitorShellStarter, planCapacitorShellStarter, } from './capacitor-shell-starter.js';
|
|
32
|
+
export { ReleaseProcessError, runReleaseProcess, } from './deploy-process.js';
|
|
33
|
+
export { installPinnedReleaseDependencies, pinNativeDeploymentPlan, preparePinnedReleaseWorkspace, runPinnedNativeBuild, withPinnedReleaseWorkspace, } from './release-workspace.js';
|
|
34
|
+
export { withAndroidUploadSigningSession, } from './android-signing-session.js';
|
|
35
|
+
export { checkpointNativeSubmission, readNativeReleaseStatus, reclaimNativeSubmission, releaseNativeSubmissionLease, recordNativeReleaseBuild, reserveNativeRelease, } from './release-state.js';
|
|
36
|
+
export { runNativeDeployment, readStoreCredential, } from './native-deploy-run.js';
|
|
37
|
+
export { submitRecordedNativeTarget, } from './native-deploy-submission.js';
|
|
38
|
+
export { PlaySubmissionUncertainError, submitVerifiedAndroidBundle, } from './play-internal-submission.js';
|
|
39
|
+
export { IosSubmissionUncertainError, submitVerifiedIosBuild, } from './testflight-submission.js';
|
|
25
40
|
export { createBrowserGameplayE2EDriver, } from './browser-gameplay-e2e.js';
|
|
26
41
|
export { collectGameplayE2EPathEvidence, defaultGameplayE2EReportFile, maximumGameplayE2EStates, parseGameplayE2EPlan, readGameplayE2EPlan, renderGameplayE2EMarkdown, resolveGameplayE2EReportFile, runGameplayE2E, } from './gameplay-e2e.js';
|
|
27
42
|
export { cloudflarePagesDeploymentProfiles, hostedPwaDeploymentHosts, isSupportedHostedPwaProfile, verifyHostedPwaDeployment, } from './hosted-pwa-deployment.js';
|
|
@@ -135,7 +150,7 @@ export async function runMpgdCli(args) {
|
|
|
135
150
|
// partial document. Every other command keeps the default rendering.
|
|
136
151
|
renderHeader: async (ctx) => {
|
|
137
152
|
const values = ctx.values;
|
|
138
|
-
if (ctx.name === 'verify-delivery' && values.json === true) {
|
|
153
|
+
if ((ctx.name === 'verify-delivery' || ctx.name === 'upgrade') && values.json === true) {
|
|
139
154
|
return '';
|
|
140
155
|
}
|
|
141
156
|
const title = ctx.env.description || ctx.env.name || '';
|
|
@@ -146,7 +161,7 @@ export async function runMpgdCli(args) {
|
|
|
146
161
|
renderValidationErrors: async (ctx, error) => {
|
|
147
162
|
const messages = error.errors.map((entry) => String(entry.message)).join('\n');
|
|
148
163
|
const values = ctx.values;
|
|
149
|
-
if (ctx.name === 'verify-delivery' && values.json === true) {
|
|
164
|
+
if ((ctx.name === 'verify-delivery' || ctx.name === 'upgrade') && values.json === true) {
|
|
150
165
|
process.stderr.write(`${messages}\n`);
|
|
151
166
|
return '';
|
|
152
167
|
}
|
|
@@ -155,6 +170,7 @@ export async function runMpgdCli(args) {
|
|
|
155
170
|
subCommands: {
|
|
156
171
|
assets: assetsCommand,
|
|
157
172
|
game: gameCommand,
|
|
173
|
+
deploy: deployCommand,
|
|
158
174
|
legal: legalCommand,
|
|
159
175
|
target: targetCommand,
|
|
160
176
|
kit: kitCommand,
|
|
@@ -207,10 +223,275 @@ const entryCommand = defineI18n({
|
|
|
207
223
|
ko: 'mpgd-kit 스타터와 타깃 워크플로우를 관리합니다.',
|
|
208
224
|
}),
|
|
209
225
|
run: () => {
|
|
210
|
-
console.info('Use a sub-command: assets, game, legal, target, kit.');
|
|
226
|
+
console.info('Use a sub-command: assets, game, deploy, legal, target, kit.');
|
|
211
227
|
console.info('Run "pnpm mpgd --help" for available commands.');
|
|
212
228
|
},
|
|
213
229
|
});
|
|
230
|
+
const deployCommand = defineI18n({
|
|
231
|
+
name: 'deploy',
|
|
232
|
+
description: 'Configure and inspect game-owned native test deployments.',
|
|
233
|
+
resource: commandResource({
|
|
234
|
+
en: 'Configure and inspect game-owned native test deployments.',
|
|
235
|
+
ko: '게임 소유 네이티브 테스트 배포를 설정하고 점검합니다.',
|
|
236
|
+
}),
|
|
237
|
+
subCommands: {
|
|
238
|
+
init: defineI18n({
|
|
239
|
+
name: 'init',
|
|
240
|
+
description: 'Create game-owned deployment profile references.',
|
|
241
|
+
resource: commandResource({
|
|
242
|
+
en: 'Create game-owned deployment profile references.',
|
|
243
|
+
ko: '게임 소유 배포 프로필 참조를 생성합니다.',
|
|
244
|
+
}),
|
|
245
|
+
args: {
|
|
246
|
+
game: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
required: false,
|
|
249
|
+
description: 'Game project directory containing mpgd.targets.json.',
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
run: (ctx) => {
|
|
253
|
+
const game = readOptionalString(ctx.values.game) ?? '.';
|
|
254
|
+
console.info(`Created deployment config: ${initializeDeployConfig(game)}`);
|
|
255
|
+
},
|
|
256
|
+
}),
|
|
257
|
+
workflow: defineI18n({
|
|
258
|
+
name: 'workflow',
|
|
259
|
+
description: 'Scaffold game-owned native deployment CI.',
|
|
260
|
+
resource: commandResource({
|
|
261
|
+
en: 'Scaffold game-owned native deployment CI.',
|
|
262
|
+
ko: '게임 소유 네이티브 배포 CI를 생성합니다.',
|
|
263
|
+
}),
|
|
264
|
+
subCommands: {
|
|
265
|
+
init: defineI18n({
|
|
266
|
+
name: 'init',
|
|
267
|
+
description: 'Create a protected-branch native deployment workflow.',
|
|
268
|
+
resource: commandResource({
|
|
269
|
+
en: 'Create a protected-branch native deployment workflow.',
|
|
270
|
+
ko: '보호 브랜치용 네이티브 배포 워크플로우를 생성합니다.',
|
|
271
|
+
}),
|
|
272
|
+
args: {
|
|
273
|
+
game: { type: 'string', required: false, description: 'Game project directory.' },
|
|
274
|
+
profile: { type: 'string', required: false, description: 'Deployment profile name.' },
|
|
275
|
+
targets: { type: 'string', required: false, description: 'android,ios selection.' },
|
|
276
|
+
'release-branch': {
|
|
277
|
+
type: 'string', required: true,
|
|
278
|
+
description: 'Protected branch allowed to dispatch signed deployments.',
|
|
279
|
+
},
|
|
280
|
+
'game-id': { type: 'string', required: false, description: 'Release ledger game ID.' },
|
|
281
|
+
'approval-environment': {
|
|
282
|
+
type: 'string', required: false,
|
|
283
|
+
description: 'Protected GitHub Environment name.',
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
run: (ctx) => {
|
|
287
|
+
const targets = parseDeployTargets(readOptionalString(ctx.values.targets));
|
|
288
|
+
const gameId = readOptionalString(ctx.values['game-id']);
|
|
289
|
+
const approvalEnvironment = readOptionalString(ctx.values['approval-environment']);
|
|
290
|
+
const file = initializeNativeDeployWorkflow({
|
|
291
|
+
game: readOptionalString(ctx.values.game) ?? '.',
|
|
292
|
+
profile: readOptionalString(ctx.values.profile) ?? 'beta',
|
|
293
|
+
releaseBranch: readRequiredCliOption(ctx.values['release-branch'], '--release-branch'),
|
|
294
|
+
...(targets === undefined ? {} : { targets }),
|
|
295
|
+
...(gameId === undefined ? {} : { gameId }),
|
|
296
|
+
...(approvalEnvironment === undefined ? {} : { approvalEnvironment }),
|
|
297
|
+
});
|
|
298
|
+
console.info(`Created native deployment workflow: ${file}`);
|
|
299
|
+
},
|
|
300
|
+
}),
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
303
|
+
doctor: defineI18n({
|
|
304
|
+
name: 'doctor',
|
|
305
|
+
description: 'Check local native deployment configuration and toolchains.',
|
|
306
|
+
resource: commandResource({
|
|
307
|
+
en: 'Check local native deployment configuration and toolchains.',
|
|
308
|
+
ko: '로컬 네이티브 배포 설정과 도구 체인을 점검합니다.',
|
|
309
|
+
}),
|
|
310
|
+
args: {
|
|
311
|
+
game: {
|
|
312
|
+
type: 'string',
|
|
313
|
+
required: false,
|
|
314
|
+
description: 'Game project directory.',
|
|
315
|
+
},
|
|
316
|
+
profile: {
|
|
317
|
+
type: 'string',
|
|
318
|
+
required: false,
|
|
319
|
+
description: 'Deployment profile name; defaults to beta.',
|
|
320
|
+
},
|
|
321
|
+
targets: {
|
|
322
|
+
type: 'string',
|
|
323
|
+
required: false,
|
|
324
|
+
description: 'Comma-separated android,ios target selection.',
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
run: (ctx) => {
|
|
328
|
+
const targets = parseDeployTargets(readOptionalString(ctx.values.targets));
|
|
329
|
+
const result = doctorNativeDeployment({
|
|
330
|
+
game: readOptionalString(ctx.values.game) ?? '.',
|
|
331
|
+
profile: readOptionalString(ctx.values.profile) ?? 'beta',
|
|
332
|
+
...(targets === undefined ? {} : { targets }),
|
|
333
|
+
});
|
|
334
|
+
for (const check of result.checks) {
|
|
335
|
+
console.info(`${check.status}: ${check.name} — ${check.detail}`);
|
|
336
|
+
}
|
|
337
|
+
if (!result.healthy) {
|
|
338
|
+
throw new Error('Native deployment doctor found missing requirements.');
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
}),
|
|
342
|
+
plan: defineI18n({
|
|
343
|
+
name: 'plan',
|
|
344
|
+
description: 'Write a read-only native deployment plan without reserving versions.',
|
|
345
|
+
resource: commandResource({
|
|
346
|
+
en: 'Write a read-only native deployment plan without reserving versions.',
|
|
347
|
+
ko: '버전 예약 없이 읽기 전용 네이티브 배포 계획을 작성합니다.',
|
|
348
|
+
}),
|
|
349
|
+
args: {
|
|
350
|
+
game: {
|
|
351
|
+
type: 'string',
|
|
352
|
+
required: false,
|
|
353
|
+
description: 'Game project directory.',
|
|
354
|
+
},
|
|
355
|
+
profile: {
|
|
356
|
+
type: 'string',
|
|
357
|
+
required: false,
|
|
358
|
+
description: 'Deployment profile name; defaults to beta.',
|
|
359
|
+
},
|
|
360
|
+
targets: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
required: false,
|
|
363
|
+
description: 'Comma-separated android,ios target selection.',
|
|
364
|
+
},
|
|
365
|
+
out: {
|
|
366
|
+
type: 'string',
|
|
367
|
+
required: true,
|
|
368
|
+
description: 'New plan JSON path; existing files are not overwritten.',
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
run: (ctx) => {
|
|
372
|
+
const targets = parseDeployTargets(readOptionalString(ctx.values.targets));
|
|
373
|
+
const plan = planNativeDeployment({
|
|
374
|
+
game: readOptionalString(ctx.values.game) ?? '.',
|
|
375
|
+
profile: readOptionalString(ctx.values.profile) ?? 'beta',
|
|
376
|
+
...(targets === undefined ? {} : { targets }),
|
|
377
|
+
});
|
|
378
|
+
const out = readRequiredCliOption(ctx.values.out, '--out');
|
|
379
|
+
writeNativeDeploymentPlan(out, plan);
|
|
380
|
+
console.info(`Wrote read-only deployment plan: ${path.resolve(out)}`);
|
|
381
|
+
},
|
|
382
|
+
}),
|
|
383
|
+
status: defineI18n({
|
|
384
|
+
name: 'status',
|
|
385
|
+
description: 'Read one game-owned native release and its store checkpoints.',
|
|
386
|
+
resource: commandResource({
|
|
387
|
+
en: 'Read one game-owned native release and its store checkpoints.',
|
|
388
|
+
ko: '게임 소유 네이티브 릴리스와 스토어 체크포인트를 조회합니다.',
|
|
389
|
+
}),
|
|
390
|
+
args: {
|
|
391
|
+
game: { type: 'string', required: false, description: 'Game project directory.' },
|
|
392
|
+
'game-id': { type: 'string', required: true, description: 'Explicit release ledger game ID.' },
|
|
393
|
+
release: { type: 'string', required: true, description: 'Explicit release ID.' },
|
|
394
|
+
},
|
|
395
|
+
run: async (ctx) => {
|
|
396
|
+
const status = await readNativeReleaseStatusForCli({
|
|
397
|
+
gameRoot: path.resolve(readOptionalString(ctx.values.game) ?? '.'),
|
|
398
|
+
gameId: readRequiredCliOption(ctx.values['game-id'], '--game-id'),
|
|
399
|
+
releaseKey: readRequiredCliOption(ctx.values.release, '--release'),
|
|
400
|
+
});
|
|
401
|
+
console.info(JSON.stringify(status, null, 2));
|
|
402
|
+
},
|
|
403
|
+
}),
|
|
404
|
+
submit: defineI18n({
|
|
405
|
+
name: 'submit',
|
|
406
|
+
description: 'Resume store submission of one already verified native build.',
|
|
407
|
+
resource: commandResource({
|
|
408
|
+
en: 'Resume store submission of one already verified native build.',
|
|
409
|
+
ko: '이미 검증한 네이티브 빌드의 스토어 제출을 재개합니다.',
|
|
410
|
+
}),
|
|
411
|
+
args: {
|
|
412
|
+
plan: { type: 'string', required: true, description: 'Saved deployment plan JSON.' },
|
|
413
|
+
game: { type: 'string', required: false, description: 'Current game directory if the plan moved.' },
|
|
414
|
+
'game-id': { type: 'string', required: true, description: 'Explicit release ledger game ID.' },
|
|
415
|
+
release: { type: 'string', required: true, description: 'Explicit release ID.' },
|
|
416
|
+
target: { type: 'string', required: true, description: 'android or ios.' },
|
|
417
|
+
approve: { type: 'boolean', required: false, description: 'Approve internal test submission.' },
|
|
418
|
+
},
|
|
419
|
+
run: async (ctx) => {
|
|
420
|
+
const game = readOptionalString(ctx.values.game);
|
|
421
|
+
const plan = readNativeDeploymentPlan(readRequiredCliOption(ctx.values.plan, '--plan'), game === undefined ? undefined : path.resolve(game));
|
|
422
|
+
const target = readRequiredCliOption(ctx.values.target, '--target');
|
|
423
|
+
if (target !== 'android' && target !== 'ios') {
|
|
424
|
+
throw new Error('--target must be android or ios.');
|
|
425
|
+
}
|
|
426
|
+
const result = await submitRecordedNativeTarget({
|
|
427
|
+
plan,
|
|
428
|
+
gameId: readRequiredCliOption(ctx.values['game-id'], '--game-id'),
|
|
429
|
+
releaseKey: readRequiredCliOption(ctx.values.release, '--release'),
|
|
430
|
+
credential: readStoreCredential(plan, target, process.env),
|
|
431
|
+
approved: isDeployApproved(plan, ctx.values.approve === true),
|
|
432
|
+
});
|
|
433
|
+
console.info(JSON.stringify(result, null, 2));
|
|
434
|
+
},
|
|
435
|
+
}),
|
|
436
|
+
run: defineI18n({
|
|
437
|
+
name: 'run',
|
|
438
|
+
description: 'Reserve, build, verify and submit native internal-test releases.',
|
|
439
|
+
resource: commandResource({
|
|
440
|
+
en: 'Reserve, build, verify and submit native internal-test releases.',
|
|
441
|
+
ko: '네이티브 내부 테스트 릴리스를 예약·빌드·검증·제출합니다.',
|
|
442
|
+
}),
|
|
443
|
+
args: {
|
|
444
|
+
plan: { type: 'string', required: true, description: 'Saved deployment plan JSON.' },
|
|
445
|
+
game: { type: 'string', required: false, description: 'Current game directory if the plan moved.' },
|
|
446
|
+
'game-id': { type: 'string', required: true, description: 'Explicit release ledger game ID.' },
|
|
447
|
+
'game-version': { type: 'string', required: true, description: 'Game SemVer version.' },
|
|
448
|
+
release: { type: 'string', required: true, description: 'Explicit release ID.' },
|
|
449
|
+
'initial-ledger': {
|
|
450
|
+
type: 'string', required: false,
|
|
451
|
+
description: 'Initial version ledger JSON; only for the first release.',
|
|
452
|
+
},
|
|
453
|
+
approve: { type: 'boolean', required: false, description: 'Approve internal test submission.' },
|
|
454
|
+
},
|
|
455
|
+
run: async (ctx) => {
|
|
456
|
+
const game = readOptionalString(ctx.values.game);
|
|
457
|
+
const plan = readNativeDeploymentPlan(readRequiredCliOption(ctx.values.plan, '--plan'), game === undefined ? undefined : path.resolve(game));
|
|
458
|
+
const infoFile = path.join(packageRoot, 'dist/native-build-info.json');
|
|
459
|
+
const info = readJsonForCli(infoFile, 'native builder metadata');
|
|
460
|
+
assertJsonObject(info, 'native builder package metadata');
|
|
461
|
+
if (info.packageVersion !== cliVersion || typeof info.kitGitSha !== 'string'
|
|
462
|
+
|| !/^[0-9a-f]{40}$/u.test(info.kitGitSha) || info.kitDirty !== false) {
|
|
463
|
+
throw new Error('Installed CLI has no clean, matching native Kit build identity.');
|
|
464
|
+
}
|
|
465
|
+
const initialLedgerFile = readOptionalString(ctx.values['initial-ledger']);
|
|
466
|
+
const recordedBuildMarker = process.env.MPGD_RECORDED_BUILD_MARKER;
|
|
467
|
+
if (recordedBuildMarker !== undefined
|
|
468
|
+
&& (process.env.RUNNER_TEMP === undefined
|
|
469
|
+
|| !path.isAbsolute(process.env.RUNNER_TEMP)
|
|
470
|
+
|| !path.isAbsolute(recordedBuildMarker)
|
|
471
|
+
|| path.dirname(recordedBuildMarker) !== path.resolve(process.env.RUNNER_TEMP))) {
|
|
472
|
+
throw new Error('The recorded-build marker must be directly inside RUNNER_TEMP.');
|
|
473
|
+
}
|
|
474
|
+
const status = await runNativeDeployment({
|
|
475
|
+
plan,
|
|
476
|
+
gameId: readRequiredCliOption(ctx.values['game-id'], '--game-id'),
|
|
477
|
+
gameVersion: readRequiredCliOption(ctx.values['game-version'], '--game-version'),
|
|
478
|
+
releaseKey: readRequiredCliOption(ctx.values.release, '--release'),
|
|
479
|
+
kit: { packageVersion: cliVersion, gitSha: info.kitGitSha },
|
|
480
|
+
...(initialLedgerFile === undefined ? {} : {
|
|
481
|
+
initialLedger: assertPlatformVersionLedger(readJsonForCli(path.resolve(initialLedgerFile), 'initial version ledger')),
|
|
482
|
+
}),
|
|
483
|
+
approved: isDeployApproved(plan, ctx.values.approve === true),
|
|
484
|
+
...(recordedBuildMarker === undefined ? {} : {
|
|
485
|
+
onBuildRecorded: (target) => {
|
|
486
|
+
writeFileSync(recordedBuildMarker, target, { flag: 'wx', mode: 0o600 });
|
|
487
|
+
},
|
|
488
|
+
}),
|
|
489
|
+
});
|
|
490
|
+
console.info(JSON.stringify(status, null, 2));
|
|
491
|
+
},
|
|
492
|
+
}),
|
|
493
|
+
},
|
|
494
|
+
});
|
|
214
495
|
const gameCommand = defineI18n({
|
|
215
496
|
name: 'game',
|
|
216
497
|
description: 'Create standalone Phaser game starters.',
|
|
@@ -1339,7 +1620,7 @@ const targetCommand = defineI18n({
|
|
|
1339
1620
|
action: 'build',
|
|
1340
1621
|
target,
|
|
1341
1622
|
profile,
|
|
1342
|
-
env: createTargetCommandEnv(ctx.values),
|
|
1623
|
+
env: createTargetCommandEnv(ctx.values, { nativeBuildTarget: target }),
|
|
1343
1624
|
...(variant === undefined ? {} : { variant }),
|
|
1344
1625
|
});
|
|
1345
1626
|
},
|
|
@@ -2042,6 +2323,79 @@ const kitCommand = defineI18n({
|
|
|
2042
2323
|
ko: '현재 mpgd-kit 체크아웃을 점검합니다.',
|
|
2043
2324
|
}),
|
|
2044
2325
|
subCommands: {
|
|
2326
|
+
upgrade: defineI18n({
|
|
2327
|
+
name: 'upgrade',
|
|
2328
|
+
description: 'Plan or apply the latest published Kit package versions for a game.',
|
|
2329
|
+
resource: commandResource({
|
|
2330
|
+
base: {
|
|
2331
|
+
en: 'Plan or apply the latest published Kit package versions for a game.',
|
|
2332
|
+
ko: '게임에 배포된 최신 Kit 패키지 버전 적용을 계획하거나 실행합니다.',
|
|
2333
|
+
},
|
|
2334
|
+
args: {
|
|
2335
|
+
game: {
|
|
2336
|
+
en: 'Game directory containing package.json and optional mpgd.targets.json.',
|
|
2337
|
+
ko: 'package.json과 선택적 mpgd.targets.json이 있는 게임 디렉터리.',
|
|
2338
|
+
},
|
|
2339
|
+
'targets-file': {
|
|
2340
|
+
en: 'Optional game-owned target configuration file.',
|
|
2341
|
+
ko: '선택적 게임 소유 타깃 구성 파일.',
|
|
2342
|
+
},
|
|
2343
|
+
apply: {
|
|
2344
|
+
en: 'Write the planned manifests and pnpm lockfiles.',
|
|
2345
|
+
ko: '계획된 의존성 선언과 pnpm 락파일을 갱신합니다.',
|
|
2346
|
+
},
|
|
2347
|
+
json: {
|
|
2348
|
+
en: 'Write one machine-readable JSON document to stdout.',
|
|
2349
|
+
ko: '표준 출력에 기계 판독용 JSON 문서 하나를 출력합니다.',
|
|
2350
|
+
},
|
|
2351
|
+
},
|
|
2352
|
+
}),
|
|
2353
|
+
args: {
|
|
2354
|
+
game: { type: 'string', required: false, default: '.', description: 'Game directory.' },
|
|
2355
|
+
'targets-file': { type: 'string', required: false, description: 'Target config file.' },
|
|
2356
|
+
apply: { type: 'boolean', required: false, description: 'Apply the upgrade plan.' },
|
|
2357
|
+
json: { type: 'boolean', required: false, description: 'Print JSON.' },
|
|
2358
|
+
},
|
|
2359
|
+
run: async (ctx) => {
|
|
2360
|
+
const game = readOptionalString(ctx.values.game) ?? '.';
|
|
2361
|
+
const plan = await planKitUpgrade(game, undefined, readOptionalString(ctx.values['targets-file']));
|
|
2362
|
+
const result = ctx.values.apply === true && plan.blockers.length === 0
|
|
2363
|
+
? applyKitUpgrade(plan)
|
|
2364
|
+
: undefined;
|
|
2365
|
+
if (ctx.values.json === true) {
|
|
2366
|
+
process.stdout.write(JSON.stringify({ plan, result: result ?? null }, null, 2) + '\n');
|
|
2367
|
+
}
|
|
2368
|
+
else {
|
|
2369
|
+
console.info('Kit upgrade plan for ' + plan.gameRoot);
|
|
2370
|
+
for (const update of plan.updates) {
|
|
2371
|
+
console.info(' ' + path.relative(plan.gameRoot, update.manifest)
|
|
2372
|
+
+ ': ' + update.packageName + ' ' + update.current + ' -> ' + update.next);
|
|
2373
|
+
}
|
|
2374
|
+
if (plan.updates.length === 0) {
|
|
2375
|
+
console.info(' Already current for published npm latest tags.');
|
|
2376
|
+
}
|
|
2377
|
+
for (const note of plan.notes) {
|
|
2378
|
+
console.info('Note: ' + note);
|
|
2379
|
+
}
|
|
2380
|
+
for (const advisory of plan.targetAdvisories) {
|
|
2381
|
+
console.info('Target advisory [' + advisory.target + ']: ' + advisory.message);
|
|
2382
|
+
}
|
|
2383
|
+
for (const blocker of plan.blockers) {
|
|
2384
|
+
console.error('Blocked: ' + blocker);
|
|
2385
|
+
}
|
|
2386
|
+
if (result !== undefined) {
|
|
2387
|
+
console.info('Updated ' + result.manifests.length + ' manifest(s) and '
|
|
2388
|
+
+ result.lockfiles.length + ' lockfile(s). Run target checks before release.');
|
|
2389
|
+
}
|
|
2390
|
+
else if (plan.updates.length > 0 && plan.blockers.length === 0) {
|
|
2391
|
+
console.info('Run again with --apply to update manifests and pnpm lockfiles.');
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
if (plan.blockers.length > 0) {
|
|
2395
|
+
process.exitCode = 1;
|
|
2396
|
+
}
|
|
2397
|
+
},
|
|
2398
|
+
}),
|
|
2045
2399
|
doctor: defineI18n({
|
|
2046
2400
|
name: 'doctor',
|
|
2047
2401
|
description: 'Print CLI, kit, template, and target-wrapper status.',
|
|
@@ -2060,7 +2414,7 @@ const kitCommand = defineI18n({
|
|
|
2060
2414
|
}),
|
|
2061
2415
|
},
|
|
2062
2416
|
run: () => {
|
|
2063
|
-
console.info('Use "mpgd kit doctor".');
|
|
2417
|
+
console.info('Use "mpgd kit doctor" or "mpgd kit upgrade --game <directory> [--apply]".');
|
|
2064
2418
|
},
|
|
2065
2419
|
});
|
|
2066
2420
|
function commandResource(description, args = {}) {
|
|
@@ -2573,10 +2927,11 @@ function toJavaScriptStringLiteral(value) {
|
|
|
2573
2927
|
function toTemplatePath(value) {
|
|
2574
2928
|
return value.split(path.sep).join('/');
|
|
2575
2929
|
}
|
|
2576
|
-
function createTargetCommandEnv(values) {
|
|
2930
|
+
function createTargetCommandEnv(values, options = {}) {
|
|
2577
2931
|
const targetsFile = path.resolve(readOptionalString(values['targets-file']) ?? 'mpgd.targets.json');
|
|
2578
2932
|
const gameRoot = path.dirname(targetsFile);
|
|
2579
|
-
const
|
|
2933
|
+
const standaloneNative = resolveStandaloneNativeBuild(targetsFile, options.nativeBuildTarget, values);
|
|
2934
|
+
const kitPath = standaloneNative === undefined ? resolveKitPathForTarget(values) : gameRoot;
|
|
2580
2935
|
const resolvedTargetsFile = readOptionalString(values['resolved-targets-file']);
|
|
2581
2936
|
const targetConfigExtensionsFile = resolveTargetConfigExtensionsFile(gameRoot, readOptionalString(values['target-config-extensions-file']));
|
|
2582
2937
|
const preparedTargetsFile = preparePlatformTargetsFile({
|
|
@@ -2586,15 +2941,101 @@ function createTargetCommandEnv(values) {
|
|
|
2586
2941
|
? {}
|
|
2587
2942
|
: { outputFile: path.resolve(resolvedTargetsFile) }),
|
|
2588
2943
|
});
|
|
2589
|
-
|
|
2944
|
+
const env = {
|
|
2590
2945
|
...process.env,
|
|
2591
2946
|
...createGameOwnedReleaseEnv(gameRoot, process.env),
|
|
2592
|
-
MPGD_KIT_PATH: kitPath,
|
|
2593
2947
|
MPGD_PLATFORM_TARGETS_FILE: preparedTargetsFile,
|
|
2594
2948
|
...(targetConfigExtensionsFile === undefined
|
|
2595
2949
|
? {}
|
|
2596
2950
|
: { [targetConfigExtensionsFileEnv]: targetConfigExtensionsFile }),
|
|
2597
2951
|
};
|
|
2952
|
+
if (standaloneNative === undefined) {
|
|
2953
|
+
env.MPGD_KIT_PATH = kitPath;
|
|
2954
|
+
delete env.MPGD_NATIVE_PACKAGED_BUILD;
|
|
2955
|
+
delete env.MPGD_PACKAGED_KIT_GIT_SHA;
|
|
2956
|
+
}
|
|
2957
|
+
else {
|
|
2958
|
+
delete env.MPGD_KIT_PATH;
|
|
2959
|
+
env.MPGD_NATIVE_PACKAGED_BUILD = '1';
|
|
2960
|
+
env.MPGD_PACKAGED_KIT_GIT_SHA = standaloneNative.kitGitSha;
|
|
2961
|
+
env.MPGD_SKIP_BUILD_TARGET_PREFLIGHT = '1';
|
|
2962
|
+
env[targetConfigMatrixFileEnv] = standaloneNative.targetMatrixFile;
|
|
2963
|
+
}
|
|
2964
|
+
return env;
|
|
2965
|
+
}
|
|
2966
|
+
function resolveStandaloneNativeBuild(targetsFile, requestedTarget, values) {
|
|
2967
|
+
if ((requestedTarget !== 'android' && requestedTarget !== 'ios')
|
|
2968
|
+
|| readOptionalString(values['kit-path']) !== undefined
|
|
2969
|
+
|| readOptionalString(process.env.MPGD_KIT_PATH) !== undefined) {
|
|
2970
|
+
return undefined;
|
|
2971
|
+
}
|
|
2972
|
+
const raw = readJsonForCli(targetsFile);
|
|
2973
|
+
assertJsonObject(raw, `targets file ${targetsFile}`);
|
|
2974
|
+
assertJsonObject(raw.targets, `targets in ${targetsFile}`);
|
|
2975
|
+
const selected = raw.targets[requestedTarget];
|
|
2976
|
+
const expectedKind = requestedTarget === 'android' ? 'capacitor-android' : 'capacitor-ios';
|
|
2977
|
+
if (typeof selected !== 'object' || selected === null || Array.isArray(selected)
|
|
2978
|
+
|| selected.kind !== expectedKind) {
|
|
2979
|
+
return undefined;
|
|
2980
|
+
}
|
|
2981
|
+
if (JSON.stringify(selected).includes('${MPGD_KIT_PATH}')) {
|
|
2982
|
+
if (detectedKitRoot !== undefined) {
|
|
2983
|
+
return undefined;
|
|
2984
|
+
}
|
|
2985
|
+
throw new Error(`Native target ${requestedTarget} still references the Kit checkout. Run mpgd target init capacitor to create a game-owned shell.`);
|
|
2986
|
+
}
|
|
2987
|
+
const gameRoot = path.dirname(targetsFile);
|
|
2988
|
+
const gameRootReal = realpathSync(gameRoot);
|
|
2989
|
+
for (const field of ['shellApp', 'webDir']) {
|
|
2990
|
+
const configuredPath = selected[field];
|
|
2991
|
+
if (typeof configuredPath !== 'string') {
|
|
2992
|
+
return undefined;
|
|
2993
|
+
}
|
|
2994
|
+
const candidate = path.resolve(gameRoot, configuredPath
|
|
2995
|
+
.replaceAll('${MPGD_GAME_ROOT}', gameRoot)
|
|
2996
|
+
.replaceAll('${MPGD_GAME_APP_ROOT}', gameRoot));
|
|
2997
|
+
let existingAncestor = candidate;
|
|
2998
|
+
while (!existsSync(existingAncestor)) {
|
|
2999
|
+
const parent = path.dirname(existingAncestor);
|
|
3000
|
+
if (parent === existingAncestor) {
|
|
3001
|
+
throw new Error(`Cannot resolve native target ${requestedTarget} ${field}: ${configuredPath}`);
|
|
3002
|
+
}
|
|
3003
|
+
existingAncestor = parent;
|
|
3004
|
+
}
|
|
3005
|
+
const canonicalCandidate = path.resolve(realpathSync(existingAncestor), path.relative(existingAncestor, candidate));
|
|
3006
|
+
const relativeToGame = path.relative(gameRootReal, canonicalCandidate);
|
|
3007
|
+
if (relativeToGame === '' || relativeToGame === '..'
|
|
3008
|
+
|| relativeToGame.startsWith(`..${path.sep}`)
|
|
3009
|
+
|| path.isAbsolute(relativeToGame)) {
|
|
3010
|
+
if (detectedKitRoot !== undefined) {
|
|
3011
|
+
return undefined;
|
|
3012
|
+
}
|
|
3013
|
+
throw new Error(`Native target ${requestedTarget} ${field} must stay inside its game project. Run mpgd target init capacitor to create a game-owned shell.`);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
const script = path.join(packageRoot, 'dist/native-build-target.js');
|
|
3017
|
+
const infoFile = path.join(packageRoot, 'dist/native-build-info.json');
|
|
3018
|
+
if (!existsSync(script) || !existsSync(infoFile)) {
|
|
3019
|
+
if (detectedKitRoot !== undefined) {
|
|
3020
|
+
return undefined;
|
|
3021
|
+
}
|
|
3022
|
+
throw new Error('The installed @mpgd/cli package is missing its native builder artifacts.');
|
|
3023
|
+
}
|
|
3024
|
+
const info = readJsonForCli(infoFile);
|
|
3025
|
+
assertJsonObject(info, 'native builder package metadata');
|
|
3026
|
+
if (info.packageVersion !== cliVersion
|
|
3027
|
+
|| typeof info.kitGitSha !== 'string'
|
|
3028
|
+
|| !/^[0-9a-f]{40}$/u.test(info.kitGitSha)
|
|
3029
|
+
|| typeof info.kitDirty !== 'boolean') {
|
|
3030
|
+
throw new Error('The installed native builder metadata does not match @mpgd/cli.');
|
|
3031
|
+
}
|
|
3032
|
+
if (info.kitDirty) {
|
|
3033
|
+
throw new Error('The installed native builder was packaged from a dirty Kit worktree and cannot provide reliable kitGitSha provenance.');
|
|
3034
|
+
}
|
|
3035
|
+
return {
|
|
3036
|
+
kitGitSha: info.kitGitSha,
|
|
3037
|
+
targetMatrixFile: fileURLToPath(import.meta.resolve('@mpgd/target-config/targets.json')),
|
|
3038
|
+
};
|
|
2598
3039
|
}
|
|
2599
3040
|
function resolveTargetConfigExtensionsFile(gameRoot, configuredFile) {
|
|
2600
3041
|
const inheritedFile = readConfiguredPath(process.env[targetConfigExtensionsFileEnv]);
|
|
@@ -2684,19 +3125,19 @@ function preparePlatformTargetsFile(input) {
|
|
|
2684
3125
|
writeFileSync(outputFile, `${JSON.stringify(rendered, null, 2)}\n`);
|
|
2685
3126
|
return outputFile;
|
|
2686
3127
|
}
|
|
2687
|
-
function readJsonForCli(file) {
|
|
3128
|
+
function readJsonForCli(file, label = 'targets file') {
|
|
2688
3129
|
let raw;
|
|
2689
3130
|
try {
|
|
2690
3131
|
raw = readFileSync(file, 'utf8');
|
|
2691
3132
|
}
|
|
2692
3133
|
catch (error) {
|
|
2693
|
-
throw new Error(`Failed to read
|
|
3134
|
+
throw new Error(`Failed to read ${label} ${file}: ${formatError(error)}`);
|
|
2694
3135
|
}
|
|
2695
3136
|
try {
|
|
2696
3137
|
return JSON.parse(raw);
|
|
2697
3138
|
}
|
|
2698
3139
|
catch (error) {
|
|
2699
|
-
throw new Error(`Failed to parse
|
|
3140
|
+
throw new Error(`Failed to parse ${label} ${file}: ${formatError(error)}`);
|
|
2700
3141
|
}
|
|
2701
3142
|
}
|
|
2702
3143
|
function formatError(error) {
|
|
@@ -2728,12 +3169,38 @@ function runTargetCommand(input) {
|
|
|
2728
3169
|
const target = normalizeBuildTarget(input.target, input.env);
|
|
2729
3170
|
const env = withTargetVariantEnv(target, input.variant, input.env);
|
|
2730
3171
|
const profile = input.profile ?? 'production';
|
|
3172
|
+
if (input.action === 'build' && env.MPGD_NATIVE_PACKAGED_BUILD === '1') {
|
|
3173
|
+
if (target !== 'android' && target !== 'ios') {
|
|
3174
|
+
throw new Error(`Packaged native builder cannot build target ${target}.`);
|
|
3175
|
+
}
|
|
3176
|
+
console.info(`[mpgd] build ${target} from installed CLI`);
|
|
3177
|
+
runPackagedNativeBuild(target, profile, env);
|
|
3178
|
+
return;
|
|
3179
|
+
}
|
|
2731
3180
|
const args = input.action === 'build'
|
|
2732
3181
|
? ['build:target', target, profile]
|
|
2733
3182
|
: ['smoke:target', target];
|
|
2734
3183
|
console.info(`[mpgd] ${input.action} ${target}`);
|
|
2735
3184
|
runPnpm(args, env);
|
|
2736
3185
|
}
|
|
3186
|
+
function runPackagedNativeBuild(target, profile, env) {
|
|
3187
|
+
const targetsFile = env.MPGD_PLATFORM_TARGETS_FILE;
|
|
3188
|
+
if (targetsFile === undefined) {
|
|
3189
|
+
throw new Error('Packaged native build is missing its target config file.');
|
|
3190
|
+
}
|
|
3191
|
+
const script = path.join(packageRoot, 'dist/native-build-target.js');
|
|
3192
|
+
const result = spawnSync(process.execPath, [script, target, profile], {
|
|
3193
|
+
cwd: path.dirname(targetsFile),
|
|
3194
|
+
env,
|
|
3195
|
+
stdio: 'inherit',
|
|
3196
|
+
});
|
|
3197
|
+
if (result.error !== undefined) {
|
|
3198
|
+
throw result.error;
|
|
3199
|
+
}
|
|
3200
|
+
if (result.status !== 0) {
|
|
3201
|
+
throw new Error(`Packaged ${target} build failed with exit code ${result.status}.`);
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
2737
3204
|
function runTargetMatrix(input) {
|
|
2738
3205
|
for (const target of input.targets) {
|
|
2739
3206
|
const variant = variantForTarget(target, input);
|
|
@@ -3019,6 +3486,9 @@ function renderHostedPwaVerificationMarkdown(verification) {
|
|
|
3019
3486
|
'',
|
|
3020
3487
|
].join('\n');
|
|
3021
3488
|
}
|
|
3489
|
+
function isDeployApproved(plan, approveFlag) {
|
|
3490
|
+
return plan.approval === 'preapproved-internal-test' || approveFlag;
|
|
3491
|
+
}
|
|
3022
3492
|
function readRequiredCliOption(value, label) {
|
|
3023
3493
|
const read = readOptionalString(value);
|
|
3024
3494
|
if (read === undefined) {
|