@mpgd/cli 0.58.0 → 0.61.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 +24 -0
- package/dist/capacitor-shell-starter.js +100 -8
- package/dist/crash-reporting-starter.d.ts +99 -0
- package/dist/crash-reporting-starter.js +271 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +221 -14
- package/dist/ios-ipa-inspection.js +120 -1
- package/dist/native-build-info.json +2 -2
- package/dist/native-build-target.js +1617 -1185
- package/dist/native-deploy-run.d.ts +6 -0
- package/dist/native-deploy-run.js +19 -1
- package/dist/native-deploy-submission.d.ts +20 -1
- package/dist/native-deploy-submission.js +94 -7
- package/dist/native-shell-ios-push-files.d.ts +9 -0
- package/dist/native-shell-ios-push-files.js +65 -0
- package/dist/native-shell-store-policy.d.ts +109 -0
- package/dist/native-shell-store-policy.js +553 -7
- package/dist/play-internal-submission.d.ts +40 -4
- package/dist/play-internal-submission.js +215 -48
- package/dist/play-publisher-port.d.ts +26 -1
- package/dist/play-release-options.d.ts +97 -0
- package/dist/play-release-options.js +307 -0
- package/dist/play-release-promote.d.ts +59 -0
- package/dist/play-release-promote.js +235 -0
- package/dist/play-release-rollout.d.ts +57 -0
- package/dist/play-release-rollout.js +237 -0
- package/dist/play-sdk-adapter.js +26 -3
- package/dist/release-state.d.ts +6 -0
- package/dist/release-state.js +8 -0
- package/package.json +17 -17
- package/templates/phaser-game/vite.runtime-target-config.ts +4 -0
package/dist/index.js
CHANGED
|
@@ -33,7 +33,10 @@ import { runMicrosoftStoreSubmissionPreflight } from './microsoft-store-submissi
|
|
|
33
33
|
import { defaultOfflinePlaytestArtifactDir, defaultOfflinePlaytestMaximumBytes, defaultOfflinePlaytestOutputDir, runOfflinePlaytestPackaging, } from './offline-playtest.js';
|
|
34
34
|
import { targetConfigExtensionsFileEnv, targetConfigMatrixFileEnv } from './target-config-env.js';
|
|
35
35
|
import { readStoreCredential, runNativeDeployment } from './native-deploy-run.js';
|
|
36
|
-
import { submitRecordedNativeTarget } from './native-deploy-submission.js';
|
|
36
|
+
import { isNativeDeployApproved, planRecordedAndroidSubmission, submitRecordedNativeTarget, } from './native-deploy-submission.js';
|
|
37
|
+
import { createPlayReleaseRequest, parsePlayUserFraction, readPlayReleaseNotesFile, } from './play-release-options.js';
|
|
38
|
+
import { promotePlayRelease } from './play-release-promote.js';
|
|
39
|
+
import { changePlayRollout } from './play-release-rollout.js';
|
|
37
40
|
import { readNativeReleaseStatus as readNativeReleaseStatusForCli } from './release-state.js';
|
|
38
41
|
export { renderGameAcceptanceMarkdown, resolveGameAcceptanceReleaseManifestFile, runGameAcceptance, defaultGameAcceptanceCommandTimeoutMs, maximumGameplayE2EReportBytes, } from './game-acceptance.js';
|
|
39
42
|
export { maximumPerformanceEvidenceBytes, preparePerformanceAcceptance, readPerformanceAcceptanceEvidence, validatePerformanceBudget, } from './performance-acceptance.js';
|
|
@@ -43,8 +46,11 @@ export { installPinnedReleaseDependencies, pinNativeDeploymentPlan, preparePinne
|
|
|
43
46
|
export { withAndroidUploadSigningSession, } from './android-signing-session.js';
|
|
44
47
|
export { checkpointNativeSubmission, readNativeReleaseStatus, reclaimNativeSubmission, releaseNativeSubmissionLease, recordNativeReleaseBuild, reserveNativeRelease, } from './release-state.js';
|
|
45
48
|
export { runNativeDeployment, readStoreCredential, } from './native-deploy-run.js';
|
|
46
|
-
export { submitRecordedNativeTarget, } from './native-deploy-submission.js';
|
|
47
|
-
export { PlaySubmissionUncertainError, submitVerifiedAndroidBundle, } from './play-internal-submission.js';
|
|
49
|
+
export { planRecordedAndroidSubmission, submitRecordedNativeTarget, } from './native-deploy-submission.js';
|
|
50
|
+
export { PlaySubmissionUncertainError, planVerifiedAndroidBundleSubmission, submitVerifiedAndroidBundle, } from './play-internal-submission.js';
|
|
51
|
+
export { createPlayReleaseRequest, defaultPlayReleaseRequest, maximumPlayReleaseNoteCharacters, parsePlayReleaseNotes, } from './play-release-options.js';
|
|
52
|
+
export { PlayPromoteUncertainError, promotePlayRelease, } from './play-release-promote.js';
|
|
53
|
+
export { PlayRolloutUncertainError, changePlayRollout, } from './play-release-rollout.js';
|
|
48
54
|
export { IosSubmissionUncertainError, submitVerifiedIosBuild, } from './testflight-submission.js';
|
|
49
55
|
export { createBrowserGameplayE2EDriver, } from './browser-gameplay-e2e.js';
|
|
50
56
|
export { collectGameplayE2EPathEvidence, defaultGameplayE2EReportFile, maximumGameplayE2EStates, parseGameplayE2EPlan, readGameplayE2EPlan, renderGameplayE2EMarkdown, resolveGameplayE2EReportFile, runGameplayE2E, } from './gameplay-e2e.js';
|
|
@@ -446,7 +452,35 @@ const deployCommand = defineI18n({
|
|
|
446
452
|
'game-id': { type: 'string', required: true, description: 'Explicit release ledger game ID.' },
|
|
447
453
|
release: { type: 'string', required: true, description: 'Explicit release ID.' },
|
|
448
454
|
target: { type: 'string', required: true, description: 'android or ios.' },
|
|
449
|
-
approve: { type: 'boolean', required: false, description: 'Approve
|
|
455
|
+
approve: { type: 'boolean', required: false, description: 'Approve the store submission; needed for any Play track but internal.' },
|
|
456
|
+
track: {
|
|
457
|
+
type: 'string', required: false,
|
|
458
|
+
description: 'Android: Google Play track (internal, alpha, beta, production or a closed-testing track name); defaults to internal.',
|
|
459
|
+
},
|
|
460
|
+
status: {
|
|
461
|
+
type: 'string', required: false,
|
|
462
|
+
description: 'Android: release status draft, completed (default) or inProgress (staged rollout).',
|
|
463
|
+
},
|
|
464
|
+
'user-fraction': {
|
|
465
|
+
type: 'string', required: false,
|
|
466
|
+
description: 'Android: staged rollout fraction for --status inProgress, greater than 0 and less than 1.',
|
|
467
|
+
},
|
|
468
|
+
'release-notes': {
|
|
469
|
+
type: 'string', required: false,
|
|
470
|
+
description: 'Android: release notes file, Play Console <en-US>…</en-US> blocks or JSON; 500 characters per language.',
|
|
471
|
+
},
|
|
472
|
+
'confirm-production': {
|
|
473
|
+
type: 'boolean', required: false,
|
|
474
|
+
description: 'Android: confirm a production release or staged rollout.',
|
|
475
|
+
},
|
|
476
|
+
'mapping-file': {
|
|
477
|
+
type: 'string', required: false,
|
|
478
|
+
description: 'Android: R8 mapping.txt of this exact build, uploaded as its deobfuscation file.',
|
|
479
|
+
},
|
|
480
|
+
'dry-run': {
|
|
481
|
+
type: 'boolean', required: false,
|
|
482
|
+
description: 'Android: verify the build and print the planned Play edit without any store call.',
|
|
483
|
+
},
|
|
450
484
|
},
|
|
451
485
|
run: async (ctx) => {
|
|
452
486
|
const game = readOptionalString(ctx.values.game);
|
|
@@ -455,12 +489,136 @@ const deployCommand = defineI18n({
|
|
|
455
489
|
if (target !== 'android' && target !== 'ios') {
|
|
456
490
|
throw new Error('--target must be android or ios.');
|
|
457
491
|
}
|
|
492
|
+
const androidRelease = await readPlayReleaseOptions(ctx.values, target === 'android');
|
|
493
|
+
const gameId = readRequiredCliOption(ctx.values['game-id'], '--game-id');
|
|
494
|
+
const releaseKey = readRequiredCliOption(ctx.values.release, '--release');
|
|
495
|
+
if (ctx.values['dry-run'] === true) {
|
|
496
|
+
if (target !== 'android') {
|
|
497
|
+
throw new Error('--dry-run is available for --target android.');
|
|
498
|
+
}
|
|
499
|
+
const planned = await planRecordedAndroidSubmission({
|
|
500
|
+
plan, gameId, releaseKey,
|
|
501
|
+
...(androidRelease === undefined ? {} : { androidRelease }),
|
|
502
|
+
});
|
|
503
|
+
console.info(JSON.stringify(planned, null, 2));
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
458
506
|
const result = await submitRecordedNativeTarget({
|
|
459
507
|
plan,
|
|
460
|
-
gameId
|
|
461
|
-
releaseKey
|
|
508
|
+
gameId,
|
|
509
|
+
releaseKey,
|
|
462
510
|
credential: readStoreCredential(plan, target, process.env),
|
|
463
|
-
approved:
|
|
511
|
+
approved: isNativeDeployApproved(plan, ctx.values.approve === true, androidRelease),
|
|
512
|
+
...(androidRelease === undefined ? {} : { androidRelease }),
|
|
513
|
+
...(ctx.values['confirm-production'] === true ? { confirmProduction: true } : {}),
|
|
514
|
+
onDraftAppFallback: reportPlayDraftAppFallback,
|
|
515
|
+
});
|
|
516
|
+
console.info(JSON.stringify(result, null, 2));
|
|
517
|
+
},
|
|
518
|
+
}),
|
|
519
|
+
rollout: defineI18n({
|
|
520
|
+
name: 'rollout',
|
|
521
|
+
description: 'Update, halt, resume or complete a Google Play release already on a track.',
|
|
522
|
+
resource: commandResource({
|
|
523
|
+
en: 'Update, halt, resume or complete a Google Play release already on a track.',
|
|
524
|
+
ko: '트랙에 이미 있는 Google Play 릴리스의 배포 비율을 변경·중지·재개·완료합니다.',
|
|
525
|
+
}),
|
|
526
|
+
args: {
|
|
527
|
+
plan: { type: 'string', required: true, description: 'Saved deployment plan JSON (package ID and credential).' },
|
|
528
|
+
game: { type: 'string', required: false, description: 'Current game directory if the plan moved.' },
|
|
529
|
+
track: { type: 'string', required: true, description: 'Google Play track whose release changes; no other track is touched.' },
|
|
530
|
+
action: { type: 'string', required: true, description: 'update, halt, resume or complete.' },
|
|
531
|
+
'version-code': { type: 'string', required: false, description: 'Release to change; defaults to the only release the action applies to.' },
|
|
532
|
+
'user-fraction': { type: 'string', required: false, description: 'New rollout fraction for update (or resume), greater than 0 and less than 1.' },
|
|
533
|
+
approve: { type: 'boolean', required: false, description: 'Approve the rollout change.' },
|
|
534
|
+
'confirm-production': { type: 'boolean', required: false, description: 'Confirm a production change or a running staged rollout.' },
|
|
535
|
+
'dry-run': { type: 'boolean', required: false, description: 'Read the track and print the planned update; the edit is discarded.' },
|
|
536
|
+
},
|
|
537
|
+
run: async (ctx) => {
|
|
538
|
+
const game = readOptionalString(ctx.values.game);
|
|
539
|
+
const plan = readNativeDeploymentPlan(readRequiredCliOption(ctx.values.plan, '--plan'), game === undefined ? undefined : path.resolve(game));
|
|
540
|
+
const android = plan.targets.find((entry) => entry.target === 'android');
|
|
541
|
+
if (android === undefined) {
|
|
542
|
+
throw new Error('The deployment plan has no Android target.');
|
|
543
|
+
}
|
|
544
|
+
const dryRun = ctx.values['dry-run'] === true;
|
|
545
|
+
if (!dryRun && ctx.values.approve !== true) {
|
|
546
|
+
throw new Error('Changing a Google Play rollout requires --approve.');
|
|
547
|
+
}
|
|
548
|
+
const action = readRequiredCliOption(ctx.values.action, '--action');
|
|
549
|
+
const versionCode = readOptionalString(ctx.values['version-code']);
|
|
550
|
+
const userFraction = readOptionalString(ctx.values['user-fraction']);
|
|
551
|
+
if (versionCode !== undefined && !/^[1-9]\d{0,15}$/u.test(versionCode)) {
|
|
552
|
+
throw new Error('--version-code must be a positive integer.');
|
|
553
|
+
}
|
|
554
|
+
const credential = readStoreCredential(plan, 'android', process.env);
|
|
555
|
+
if (credential.target !== 'android') {
|
|
556
|
+
throw new Error('Google Play rollout needs the Android submission credential.');
|
|
557
|
+
}
|
|
558
|
+
const result = await changePlayRollout({
|
|
559
|
+
packageName: android.appId,
|
|
560
|
+
serviceAccountFile: credential.serviceAccountFile,
|
|
561
|
+
track: readRequiredCliOption(ctx.values.track, '--track'),
|
|
562
|
+
action,
|
|
563
|
+
...(versionCode === undefined ? {} : { versionCode: Number(versionCode) }),
|
|
564
|
+
...(userFraction === undefined ? {} : { userFraction: parsePlayUserFraction(userFraction) }),
|
|
565
|
+
...(ctx.values['confirm-production'] === true ? { confirmProduction: true } : {}),
|
|
566
|
+
dryRun,
|
|
567
|
+
});
|
|
568
|
+
console.info(JSON.stringify(result, null, 2));
|
|
569
|
+
},
|
|
570
|
+
}),
|
|
571
|
+
promote: defineI18n({
|
|
572
|
+
name: 'promote',
|
|
573
|
+
description: 'Promote a Google Play release from one track to another without uploading.',
|
|
574
|
+
resource: commandResource({
|
|
575
|
+
en: 'Promote a Google Play release from one track to another without uploading.',
|
|
576
|
+
ko: '업로드 없이 Google Play 릴리스를 다른 트랙으로 승격합니다.',
|
|
577
|
+
}),
|
|
578
|
+
args: {
|
|
579
|
+
plan: { type: 'string', required: true, description: 'Saved deployment plan JSON (package ID and credential).' },
|
|
580
|
+
game: { type: 'string', required: false, description: 'Current game directory if the plan moved.' },
|
|
581
|
+
from: { type: 'string', required: true, description: 'Source Google Play track holding the tested release.' },
|
|
582
|
+
to: { type: 'string', required: true, description: 'Target Google Play track; the only track that changes.' },
|
|
583
|
+
'version-code': { type: 'string', required: false, description: "Version code to promote; defaults to the source track's only completed release." },
|
|
584
|
+
status: { type: 'string', required: false, description: 'Target release status: completed (default), draft or inProgress (staged rollout).' },
|
|
585
|
+
'user-fraction': { type: 'string', required: false, description: 'Staged rollout fraction for --status inProgress, greater than 0 and less than 1.' },
|
|
586
|
+
'release-notes': { type: 'string', required: false, description: "Release notes file; defaults to the source release's notes." },
|
|
587
|
+
approve: { type: 'boolean', required: false, description: 'Approve the promotion.' },
|
|
588
|
+
'confirm-production': { type: 'boolean', required: false, description: 'Confirm a production promotion or staged rollout.' },
|
|
589
|
+
'dry-run': { type: 'boolean', required: false, description: 'Read both tracks and print the planned update; the edit is discarded.' },
|
|
590
|
+
},
|
|
591
|
+
run: async (ctx) => {
|
|
592
|
+
const game = readOptionalString(ctx.values.game);
|
|
593
|
+
const plan = readNativeDeploymentPlan(readRequiredCliOption(ctx.values.plan, '--plan'), game === undefined ? undefined : path.resolve(game));
|
|
594
|
+
const android = plan.targets.find((entry) => entry.target === 'android');
|
|
595
|
+
if (android === undefined) {
|
|
596
|
+
throw new Error('The deployment plan has no Android target.');
|
|
597
|
+
}
|
|
598
|
+
const dryRun = ctx.values['dry-run'] === true;
|
|
599
|
+
const versionCode = readOptionalString(ctx.values['version-code']);
|
|
600
|
+
if (versionCode !== undefined && !/^[1-9]\d{0,15}$/u.test(versionCode)) {
|
|
601
|
+
throw new Error('--version-code must be a positive integer.');
|
|
602
|
+
}
|
|
603
|
+
const status = readOptionalString(ctx.values.status);
|
|
604
|
+
const userFraction = readOptionalString(ctx.values['user-fraction']);
|
|
605
|
+
const notesFile = readOptionalString(ctx.values['release-notes']);
|
|
606
|
+
const credential = readStoreCredential(plan, 'android', process.env);
|
|
607
|
+
if (credential.target !== 'android') {
|
|
608
|
+
throw new Error('Google Play promote needs the Android submission credential.');
|
|
609
|
+
}
|
|
610
|
+
const result = await promotePlayRelease({
|
|
611
|
+
packageName: android.appId,
|
|
612
|
+
serviceAccountFile: credential.serviceAccountFile,
|
|
613
|
+
fromTrack: readRequiredCliOption(ctx.values.from, '--from'),
|
|
614
|
+
toTrack: readRequiredCliOption(ctx.values.to, '--to'),
|
|
615
|
+
...(versionCode === undefined ? {} : { versionCode: Number(versionCode) }),
|
|
616
|
+
...(status === undefined ? {} : { status: status }),
|
|
617
|
+
...(userFraction === undefined ? {} : { userFraction: parsePlayUserFraction(userFraction) }),
|
|
618
|
+
...(notesFile === undefined ? {} : { releaseNotes: readPlayReleaseNotesFile(notesFile) }),
|
|
619
|
+
...(ctx.values['confirm-production'] === true ? { confirmProduction: true } : {}),
|
|
620
|
+
approved: ctx.values.approve === true,
|
|
621
|
+
dryRun,
|
|
464
622
|
});
|
|
465
623
|
console.info(JSON.stringify(result, null, 2));
|
|
466
624
|
},
|
|
@@ -482,11 +640,32 @@ const deployCommand = defineI18n({
|
|
|
482
640
|
type: 'string', required: false,
|
|
483
641
|
description: 'Initial version ledger JSON; only for the first release.',
|
|
484
642
|
},
|
|
485
|
-
approve: { type: 'boolean', required: false, description: 'Approve
|
|
643
|
+
approve: { type: 'boolean', required: false, description: 'Approve the store submission; needed for any Play track but internal.' },
|
|
644
|
+
track: {
|
|
645
|
+
type: 'string', required: false,
|
|
646
|
+
description: 'Android: Google Play track (internal, alpha, beta, production or a closed-testing track name); defaults to internal.',
|
|
647
|
+
},
|
|
648
|
+
status: {
|
|
649
|
+
type: 'string', required: false,
|
|
650
|
+
description: 'Android: release status draft, completed (default) or inProgress (staged rollout).',
|
|
651
|
+
},
|
|
652
|
+
'user-fraction': {
|
|
653
|
+
type: 'string', required: false,
|
|
654
|
+
description: 'Android: staged rollout fraction for --status inProgress, greater than 0 and less than 1.',
|
|
655
|
+
},
|
|
656
|
+
'release-notes': {
|
|
657
|
+
type: 'string', required: false,
|
|
658
|
+
description: 'Android: release notes file, Play Console <en-US>…</en-US> blocks or JSON; 500 characters per language.',
|
|
659
|
+
},
|
|
660
|
+
'confirm-production': {
|
|
661
|
+
type: 'boolean', required: false,
|
|
662
|
+
description: 'Android: confirm a production release or staged rollout.',
|
|
663
|
+
},
|
|
486
664
|
},
|
|
487
665
|
run: async (ctx) => {
|
|
488
666
|
const game = readOptionalString(ctx.values.game);
|
|
489
667
|
const plan = readNativeDeploymentPlan(readRequiredCliOption(ctx.values.plan, '--plan'), game === undefined ? undefined : path.resolve(game));
|
|
668
|
+
const androidRelease = await readPlayReleaseOptions(ctx.values, plan.targets.some((entry) => entry.target === 'android'));
|
|
490
669
|
const infoFile = path.join(packageRoot, 'dist/native-build-info.json');
|
|
491
670
|
const info = readJsonForCli(infoFile, 'native builder metadata');
|
|
492
671
|
assertJsonObject(info, 'native builder package metadata');
|
|
@@ -512,7 +691,10 @@ const deployCommand = defineI18n({
|
|
|
512
691
|
...(initialLedgerFile === undefined ? {} : {
|
|
513
692
|
initialLedger: assertPlatformVersionLedger(readJsonForCli(path.resolve(initialLedgerFile), 'initial version ledger')),
|
|
514
693
|
}),
|
|
515
|
-
approved:
|
|
694
|
+
approved: isNativeDeployApproved(plan, ctx.values.approve === true, androidRelease),
|
|
695
|
+
...(androidRelease === undefined ? {} : { androidRelease }),
|
|
696
|
+
...(ctx.values['confirm-production'] === true ? { confirmProduction: true } : {}),
|
|
697
|
+
onDraftAppFallback: reportPlayDraftAppFallback,
|
|
516
698
|
...(recordedBuildMarker === undefined ? {} : {
|
|
517
699
|
onBuildRecorded: (target) => {
|
|
518
700
|
writeFileSync(recordedBuildMarker, target, { flag: 'wx', mode: 0o600 });
|
|
@@ -1628,8 +1810,8 @@ const targetCommand = defineI18n({
|
|
|
1628
1810
|
ko: '프로덕션 HTTPS Game Services 기본 URL.',
|
|
1629
1811
|
},
|
|
1630
1812
|
providers: {
|
|
1631
|
-
en: 'Comma-separated provider IDs. play-billing (Android) and storekit (iOS) install the kit store plugin into the shell and the game, register it with cap sync, and wire an unwired template gateway; notifications installs the Firebase messaging and local notifications plugins for Android and iOS and wires a template gateway; other IDs are recorded only.',
|
|
1632
|
-
ko: '쉼표로 구분한 provider ID. play-billing(Android)과 storekit(iOS)은 키트 스토어 플러그인을 셸과 게임에 설치하고 cap sync로 등록하며, 연결되지 않은 템플릿 게이트웨이를 연결합니다. notifications는 Android·iOS용 Firebase 메시징과 로컬 알림 플러그인을 설치하고 템플릿 게이트웨이를 연결합니다. 다른 ID는 기록만 합니다.',
|
|
1813
|
+
en: 'Comma-separated provider IDs. play-billing (Android) and storekit (iOS) install the kit store plugin into the shell and the game, register it with cap sync, and wire an unwired template gateway; notifications installs the Firebase messaging and local notifications plugins for Android and iOS and wires a template gateway; crash-reporting installs the Firebase Crashlytics plugin into the shell and its Gradle plugin into a new Android project; other IDs are recorded only.',
|
|
1814
|
+
ko: '쉼표로 구분한 provider ID. play-billing(Android)과 storekit(iOS)은 키트 스토어 플러그인을 셸과 게임에 설치하고 cap sync로 등록하며, 연결되지 않은 템플릿 게이트웨이를 연결합니다. notifications는 Android·iOS용 Firebase 메시징과 로컬 알림 플러그인을 설치하고 템플릿 게이트웨이를 연결합니다. crash-reporting은 Firebase Crashlytics 플러그인을 셸에, Crashlytics Gradle 플러그인을 새 Android 프로젝트에 설치합니다. 다른 ID는 기록만 합니다.',
|
|
1633
1815
|
},
|
|
1634
1816
|
}),
|
|
1635
1817
|
args: {
|
|
@@ -1677,7 +1859,7 @@ const targetCommand = defineI18n({
|
|
|
1677
1859
|
providers: {
|
|
1678
1860
|
type: 'string',
|
|
1679
1861
|
required: false,
|
|
1680
|
-
description: 'Comma-separated provider IDs. play-billing (Android) and storekit (iOS) install the kit store plugin into the shell and the game, register it with cap sync, and wire an unwired template gateway; notifications installs the Firebase messaging and local notifications plugins for Android and iOS and wires a template gateway; other IDs are recorded only.',
|
|
1862
|
+
description: 'Comma-separated provider IDs. play-billing (Android) and storekit (iOS) install the kit store plugin into the shell and the game, register it with cap sync, and wire an unwired template gateway; notifications installs the Firebase messaging and local notifications plugins for Android and iOS and wires a template gateway; crash-reporting installs the Firebase Crashlytics plugin into the shell and its Gradle plugin into a new Android project; other IDs are recorded only.',
|
|
1681
1863
|
},
|
|
1682
1864
|
},
|
|
1683
1865
|
run: (ctx) => {
|
|
@@ -1729,6 +1911,11 @@ const targetCommand = defineI18n({
|
|
|
1729
1911
|
+ plan.notificationPlugins.map((plugin) => `${plugin.packageName}@${plugin.version}`).join(', ')
|
|
1730
1912
|
+ ` into the shell for android and ios${dryRun ? '; cap add or cap sync registers them' : ''}.`);
|
|
1731
1913
|
}
|
|
1914
|
+
if (plan.crashReportingPlugins.length > 0) {
|
|
1915
|
+
console.info(`${dryRun ? 'Would install' : 'Installed'} crash reporting plugin `
|
|
1916
|
+
+ plan.crashReportingPlugins.map((plugin) => `${plugin.packageName}@${plugin.version}`).join(', ')
|
|
1917
|
+
+ ` into the shell for android and ios${dryRun ? '; cap add or cap sync registers it' : ''}.`);
|
|
1918
|
+
}
|
|
1732
1919
|
for (const warning of plan.warnings) {
|
|
1733
1920
|
console.warn(`Warning: ${warning}`);
|
|
1734
1921
|
}
|
|
@@ -4103,8 +4290,28 @@ function renderHostedPwaVerificationMarkdown(verification) {
|
|
|
4103
4290
|
'',
|
|
4104
4291
|
].join('\n');
|
|
4105
4292
|
}
|
|
4106
|
-
|
|
4107
|
-
|
|
4293
|
+
/** Google Play release flags; undefined when none is given (the original internal path). */
|
|
4294
|
+
async function readPlayReleaseOptions(values, android) {
|
|
4295
|
+
const track = readOptionalString(values.track);
|
|
4296
|
+
const status = readOptionalString(values.status);
|
|
4297
|
+
const userFraction = readOptionalString(values['user-fraction']);
|
|
4298
|
+
const releaseNotesFile = readOptionalString(values['release-notes']);
|
|
4299
|
+
const mappingFile = readOptionalString(values['mapping-file']);
|
|
4300
|
+
if (track === undefined && status === undefined && userFraction === undefined
|
|
4301
|
+
&& releaseNotesFile === undefined && mappingFile === undefined) {
|
|
4302
|
+
return undefined;
|
|
4303
|
+
}
|
|
4304
|
+
if (!android) {
|
|
4305
|
+
throw new Error('--track, --status, --user-fraction, --release-notes and --mapping-file '
|
|
4306
|
+
+ 'apply only to an Android (Google Play) target.');
|
|
4307
|
+
}
|
|
4308
|
+
return createPlayReleaseRequest({ track, status, userFraction, releaseNotesFile, mappingFile });
|
|
4309
|
+
}
|
|
4310
|
+
function reportPlayDraftAppFallback(track) {
|
|
4311
|
+
console.warn(`Google Play accepts only draft releases until this app's first release is rolled out, `
|
|
4312
|
+
+ `so the ${track} release is committed as a draft. Open that draft release in Play Console `
|
|
4313
|
+
+ 'and roll it out once (docs/GOOGLE_PLAY_RELEASES.md#draft-apps); later submissions can use '
|
|
4314
|
+
+ 'their normal status.');
|
|
4108
4315
|
}
|
|
4109
4316
|
function readRequiredCliOption(value, label) {
|
|
4110
4317
|
const read = readOptionalString(value);
|
|
@@ -4,6 +4,84 @@ import { existsSync, mkdtempSync, readFileSync, rmSync, statSync } from "node:fs
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
|
|
7
|
+
// packages/cli/src/native-shell-store-policy.ts
|
|
8
|
+
function readPlistTopLevelValue(plist, key) {
|
|
9
|
+
const masked = plist.replace(/<!--[\s\S]*?-->/gu, (comment) => comment.replace(/[^\n]/gu, " "));
|
|
10
|
+
const marker = new RegExp(`<key>\\s*${escapeRegExp(key)}\\s*</key>\\s*`, "u").exec(masked);
|
|
11
|
+
if (marker === null) {
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
const start = marker.index + marker[0].length;
|
|
15
|
+
const open = /^<([A-Za-z]+)\s*(\/?)>/u.exec(masked.slice(start));
|
|
16
|
+
if (open === null) {
|
|
17
|
+
throw new Error(`Info.plist key ${key} has no readable value.`);
|
|
18
|
+
}
|
|
19
|
+
const tag = open[1] ?? "";
|
|
20
|
+
if (open[2] === "/") {
|
|
21
|
+
const end = start + open[0].length;
|
|
22
|
+
return { start, end, xml: plist.slice(start, end) };
|
|
23
|
+
}
|
|
24
|
+
let depth = 0;
|
|
25
|
+
const tags = new RegExp(`<(/?)${tag}\\b[^>]*?(/?)>`, "gu");
|
|
26
|
+
tags.lastIndex = start;
|
|
27
|
+
for (let match = tags.exec(masked); match !== null; match = tags.exec(masked)) {
|
|
28
|
+
if (match[2] === "/") {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
depth += match[1] === "/" ? -1 : 1;
|
|
32
|
+
if (depth === 0) {
|
|
33
|
+
const end = match.index + match[0].length;
|
|
34
|
+
return { start, end, xml: plist.slice(start, end) };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`Info.plist key ${key} has an unclosed value.`);
|
|
38
|
+
}
|
|
39
|
+
function escapeRegExp(value) {
|
|
40
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
41
|
+
}
|
|
42
|
+
var iosPortrait = "UIInterfaceOrientationPortrait";
|
|
43
|
+
var iosPortraitUpsideDown = "UIInterfaceOrientationPortraitUpsideDown";
|
|
44
|
+
var iosLandscape = [
|
|
45
|
+
"UIInterfaceOrientationLandscapeLeft",
|
|
46
|
+
"UIInterfaceOrientationLandscapeRight"
|
|
47
|
+
];
|
|
48
|
+
var iosIpadOrientations = [
|
|
49
|
+
iosPortrait,
|
|
50
|
+
iosPortraitUpsideDown,
|
|
51
|
+
...iosLandscape
|
|
52
|
+
];
|
|
53
|
+
var iosAppPrivacyManifestXml = [
|
|
54
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
55
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
56
|
+
'<plist version="1.0">',
|
|
57
|
+
"<dict>",
|
|
58
|
+
" <key>NSPrivacyTracking</key>",
|
|
59
|
+
" <false/>",
|
|
60
|
+
" <key>NSPrivacyTrackingDomains</key>",
|
|
61
|
+
" <array/>",
|
|
62
|
+
" <key>NSPrivacyCollectedDataTypes</key>",
|
|
63
|
+
" <array/>",
|
|
64
|
+
" <key>NSPrivacyAccessedAPITypes</key>",
|
|
65
|
+
" <array/>",
|
|
66
|
+
"</dict>",
|
|
67
|
+
"</plist>",
|
|
68
|
+
""
|
|
69
|
+
].join("\n");
|
|
70
|
+
var iosPushEntitlementsFile = "App.entitlements";
|
|
71
|
+
var iosPushEntitlementsSetting = `App/${iosPushEntitlementsFile}`;
|
|
72
|
+
var iosPushEntitlementsXml = [
|
|
73
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
74
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
75
|
+
'<plist version="1.0">',
|
|
76
|
+
"<dict>",
|
|
77
|
+
" <key>aps-environment</key>",
|
|
78
|
+
" <string>development</string>",
|
|
79
|
+
"</dict>",
|
|
80
|
+
"</plist>",
|
|
81
|
+
""
|
|
82
|
+
].join("\n");
|
|
83
|
+
var iosPushEntitlementStep = `add the Push Notifications capability to the App target in Xcode, or create ios/App/App/${iosPushEntitlementsFile} with <key>aps-environment</key><string>development</string> and set CODE_SIGN_ENTITLEMENTS = ${iosPushEntitlementsSetting} in the App target's Debug and Release build settings (Xcode signs the value from the provisioning profile, production for the App Store)`;
|
|
84
|
+
|
|
7
85
|
// tools/target/native-inspection-environment.ts
|
|
8
86
|
var allowedInspectionEnvironmentNames = [
|
|
9
87
|
"PATH",
|
|
@@ -89,6 +167,16 @@ ${signingInfo.stderr}`;
|
|
|
89
167
|
if (signingInfo.status !== 0 || /Signature=adhoc/iu.test(signingText) || !signingText.split(/\r?\n/u).some((line) => line.trim() === expectedTeamLine)) {
|
|
90
168
|
throw new Error("iOS app is not signed for the expected development team.");
|
|
91
169
|
}
|
|
170
|
+
if (input.expectedApsEnvironment !== void 0) {
|
|
171
|
+
const entitlements = runner.run("codesign", ["-d", "--entitlements", "-", "--xml", appPath]);
|
|
172
|
+
if (entitlements.status !== 0) {
|
|
173
|
+
throw new Error("iOS app entitlements could not be read.");
|
|
174
|
+
}
|
|
175
|
+
const problem = iosStorePushEntitlementError(readSignedApsEnvironment(entitlements.stdout));
|
|
176
|
+
if (problem !== void 0) {
|
|
177
|
+
throw new Error(problem);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
92
180
|
const fields = [
|
|
93
181
|
["bundleId", "CFBundleIdentifier", input.expectedBundleId],
|
|
94
182
|
["marketingVersion", "CFBundleShortVersionString", input.expectedMarketingVersion],
|
|
@@ -117,6 +205,35 @@ ${signingInfo.stderr}`;
|
|
|
117
205
|
}
|
|
118
206
|
return { ...observed, teamId: input.expectedTeamId, signed: true };
|
|
119
207
|
}
|
|
208
|
+
function readSignedApsEnvironment(codesignOutput) {
|
|
209
|
+
const start = codesignOutput.search(/<\?xml|<plist\b/u);
|
|
210
|
+
if (start === -1) {
|
|
211
|
+
return codesignOutput.trim() === "" ? void 0 : null;
|
|
212
|
+
}
|
|
213
|
+
let value;
|
|
214
|
+
try {
|
|
215
|
+
value = readPlistTopLevelValue(codesignOutput.slice(start), "aps-environment")?.xml;
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
if (value === void 0) {
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
return /^<string>\s*([^<]*?)\s*<\/string>$/u.exec(value)?.[1] ?? null;
|
|
223
|
+
}
|
|
224
|
+
function iosStorePushEntitlementError(apsEnvironment) {
|
|
225
|
+
if (apsEnvironment === "production") {
|
|
226
|
+
return void 0;
|
|
227
|
+
}
|
|
228
|
+
const fix = "Enable Push Notifications on the App ID in the Apple Developer account, regenerate the App Store provisioning profile (MPGD_IOS_PROVISIONING_PROFILE_FILE), and rebuild (docs/FIREBASE_NOTIFICATIONS.md#4-ios).";
|
|
229
|
+
if (apsEnvironment === void 0) {
|
|
230
|
+
return `The store IPA has no aps-environment entitlement although the shell installs @capacitor-firebase/messaging: the provisioning profile that signed it lacks the Push Notifications capability (the App ID has no push, or the profile was generated before it was enabled), so the app never gets an APNs token. ${fix}`;
|
|
231
|
+
}
|
|
232
|
+
if (apsEnvironment === "development") {
|
|
233
|
+
return `The store IPA is signed with aps-environment = development although it is an App Store build: the export used a development provisioning profile, so its APNs token belongs to the sandbox and production pushes never arrive. Export with the App Store distribution profile. ${fix}`;
|
|
234
|
+
}
|
|
235
|
+
return `The store IPA's aps-environment entitlement is ${apsEnvironment === null ? "unreadable" : apsEnvironment}, not production, so production pushes never arrive. ${fix}`;
|
|
236
|
+
}
|
|
120
237
|
function assertNoDebugServer(file) {
|
|
121
238
|
const value = JSON.parse(readFileSync(file, "utf8"));
|
|
122
239
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
@@ -147,5 +264,7 @@ function runCommand(command, args) {
|
|
|
147
264
|
export {
|
|
148
265
|
inspectSignedIosApplication,
|
|
149
266
|
inspectSignedIosArchive,
|
|
150
|
-
inspectSignedIosIpa
|
|
267
|
+
inspectSignedIosIpa,
|
|
268
|
+
iosStorePushEntitlementError,
|
|
269
|
+
readSignedApsEnvironment
|
|
151
270
|
};
|