@mpgd/cli 0.66.0 → 0.67.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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "kitGitSha": "9a27d7b0b8cc755238b3edab0ef7306d53597627",
2
+ "kitGitSha": "1be07c42f7d67faaae8b908ee1a56d766b4b5120",
3
3
  "kitDirty": false,
4
- "packageVersion": "0.66.0"
4
+ "packageVersion": "0.67.0"
5
5
  }
@@ -151,6 +151,13 @@ function expression(value) {
151
151
  function quoted(value) {
152
152
  return `'${value.replaceAll("'", "''")}'`;
153
153
  }
154
+ // Linux jobs pin a runner image instead of `ubuntu-latest`, as the kit's own CI does:
155
+ // the rolling label moves to a new Ubuntu image, with different preinstalled toolchains
156
+ // (for example the Go that ttsc keys its compiled plugins on), without a workflow change.
157
+ // The generated jobs cache only the pnpm store, which no image toolchain keys, so they
158
+ // need no image-keyed cache; a game that adds one should put the image in its key.
159
+ const LINUX_RUNNER = 'ubuntu-24.04';
160
+ const MACOS_RUNNER = 'macos-26';
154
161
  function renderNativeDeployWorkflow(input) {
155
162
  const options = input.targets.map((target) => ` - ${target}`);
156
163
  const lines = [
@@ -190,7 +197,7 @@ function renderNativeDeployWorkflow(input) {
190
197
  'jobs:',
191
198
  ' validate:',
192
199
  " if: github.event_name == 'pull_request'",
193
- ' runs-on: ubuntu-latest',
200
+ ` runs-on: ${LINUX_RUNNER}`,
194
201
  ' steps:',
195
202
  ' - uses: actions/checkout@v5',
196
203
  ' with:',
@@ -229,7 +236,7 @@ function renderDeployJob(input, target) {
229
236
  ` deploy_${target}:`,
230
237
  ` if: github.event_name == 'workflow_dispatch' && github.ref == `
231
238
  + `'refs/heads/${input.releaseBranch}' && inputs.target == '${target}'`,
232
- ` runs-on: ${target === 'android' ? 'ubuntu-latest' : 'macos-26'}`,
239
+ ` runs-on: ${target === 'android' ? LINUX_RUNNER : MACOS_RUNNER}`,
233
240
  ` environment: ${quoted(input.approvalEnvironment)}`,
234
241
  ' permissions:',
235
242
  ' contents: write',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/cli",
3
- "version": "0.66.0",
3
+ "version": "0.67.0",
4
4
  "description": "Gunshi CLI for mpgd-kit starter generation and target workflow orchestration.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -70,10 +70,10 @@
70
70
  "parse5": "7.3.0",
71
71
  "postcss": "8.5.28",
72
72
  "postcss-value-parser": "4.2.0",
73
- "@mpgd/catalog": "0.14.1",
73
+ "@mpgd/catalog": "0.14.2",
74
74
  "@mpgd/phaser-assets": "0.9.1",
75
- "@mpgd/platform": "0.34.0",
76
- "@mpgd/target-config": "0.24.5"
75
+ "@mpgd/target-config": "0.24.6",
76
+ "@mpgd/platform": "0.35.0"
77
77
  },
78
78
  "devDependencies": {
79
79
  "@types/node": "^24.0.0",
@@ -81,19 +81,19 @@
81
81
  "@types/yauzl": "3.4.0",
82
82
  "ttsc": "0.34.2",
83
83
  "typescript": "7.0.2",
84
- "@mpgd/adapter-ait": "0.21.7",
85
- "@mpgd/adapter-capacitor": "0.18.0",
86
- "@mpgd/adapter-browser": "0.12.3",
87
- "@mpgd/adapter-devvit": "0.14.9",
88
- "@mpgd/adapter-verse8": "0.7.18",
89
- "@mpgd/bridge": "0.18.0",
90
- "@mpgd/capacitor-play-billing": "0.6.0",
91
- "@mpgd/analytics": "0.5.7",
92
- "@mpgd/capacitor-storekit": "0.6.0",
93
- "@mpgd/game-runtime": "0.9.0",
94
- "@mpgd/game-services": "0.32.0",
95
- "@mpgd/lite-shell": "0.1.0",
96
- "@mpgd/i18n": "0.6.28"
84
+ "@mpgd/adapter-ait": "0.21.8",
85
+ "@mpgd/adapter-browser": "0.12.4",
86
+ "@mpgd/adapter-capacitor": "0.19.0",
87
+ "@mpgd/analytics": "0.5.8",
88
+ "@mpgd/adapter-verse8": "0.7.19",
89
+ "@mpgd/adapter-devvit": "0.14.10",
90
+ "@mpgd/bridge": "0.18.1",
91
+ "@mpgd/capacitor-storekit": "0.7.0",
92
+ "@mpgd/capacitor-play-billing": "0.6.1",
93
+ "@mpgd/game-runtime": "0.10.0",
94
+ "@mpgd/game-services": "0.33.0",
95
+ "@mpgd/i18n": "0.6.29",
96
+ "@mpgd/lite-shell": "0.1.0"
97
97
  },
98
98
  "main": "./dist/index.js",
99
99
  "types": "./dist/index.d.ts",
@@ -14,6 +14,9 @@ export type GameMessageKey =
14
14
  | 'reward'
15
15
  | 'rewardError'
16
16
  | 'rewardPending'
17
+ | 'rewardNotReady'
18
+ | 'rewardReconciling'
19
+ | 'rewardHistoryFull'
17
20
  | 'rewardUnavailable'
18
21
  | 'analytics'
19
22
  | 'bootError'
@@ -59,6 +62,9 @@ const messages = {
59
62
  reward: 'Rewarded ad smoke: {status}',
60
63
  rewardError: 'Rewarded ad smoke failed',
61
64
  rewardPending: 'Press R to request a rewarded ad smoke',
65
+ rewardNotReady: 'Rewarded ad smoke waits for the current purchase or ad',
66
+ rewardReconciling: 'Rewarded ad smoke waits until an earlier result is confirmed',
67
+ rewardHistoryFull: 'Rewarded ad smoke is unavailable until the game restarts',
62
68
  rewardUnavailable: 'Rewarded ads unavailable on this target',
63
69
  analytics: 'Analytics events: {count}',
64
70
  bootError: 'Failed to start game',
@@ -108,6 +114,9 @@ const messages = {
108
114
  reward: '리워드 광고 스모크: {status}',
109
115
  rewardError: '리워드 광고 스모크 실패',
110
116
  rewardPending: 'R 키로 리워드 광고 스모크 요청',
117
+ rewardNotReady: '진행 중인 구매나 광고가 끝나면 리워드 광고 스모크 요청 가능',
118
+ rewardReconciling: '이전 결과가 확인되면 리워드 광고 스모크 요청 가능',
119
+ rewardHistoryFull: '게임을 다시 시작할 때까지 리워드 광고 스모크를 사용할 수 없음',
111
120
  rewardUnavailable: '이 타깃에서는 리워드 광고를 사용할 수 없음',
112
121
  analytics: '애널리틱스 이벤트: {count}',
113
122
  bootError: '게임 시작 실패',
@@ -1,4 +1,4 @@
1
- import type { GameActionController } from '@mpgd/game-runtime/actions';
1
+ import type { GameActionAvailability, GameActionController } from '@mpgd/game-runtime/actions';
2
2
  import { bindPhaserGameScene } from '@mpgd/game-runtime/phaser';
3
3
  import Phaser from 'phaser';
4
4
 
@@ -21,6 +21,8 @@ export class PlayScene extends Phaser.Scene {
21
21
  private analyticsText!: Phaser.GameObjects.Text;
22
22
  private context!: StarterContext;
23
23
  private rewardedAction: GameActionController<'rewarded-ad'> | undefined;
24
+ private showingNotReady = false;
25
+ private requesting = false;
24
26
  private lastScore = -1;
25
27
  private lastAnalyticsCount = -1;
26
28
 
@@ -83,19 +85,67 @@ export class PlayScene extends Phaser.Scene {
83
85
  })
84
86
  .setOrigin(0.5);
85
87
 
86
- this.rewardedAction = this.context.gameRuntime.actions?.createRewardedAdController();
88
+ const actions = this.context.gameRuntime.actions;
89
+ this.rewardedAction = actions?.createRewardedAdController();
87
90
  const action = this.rewardedAction;
91
+ // Follow the runtime's availability, so the hint changes while any purchase
92
+ // (one from the shop included) or ad holds the screen, without a key press.
93
+ this.showingNotReady = false;
94
+ this.requesting = false;
95
+ const stopAvailability = actions?.subscribeAvailability((availability) => {
96
+ this.showAvailability(availability);
97
+ });
98
+ if (actions !== undefined) {
99
+ this.showAvailability(actions.getAvailability());
100
+ }
88
101
  const keyboard = this.input.keyboard;
89
102
  const onReward = () => {
90
103
  void this.requestRewardedAd();
91
104
  };
92
105
  keyboard?.on('keydown-R', onReward);
93
106
  this.events.once('shutdown', () => {
107
+ stopAvailability?.();
94
108
  action?.dispose();
95
109
  keyboard?.off('keydown-R', onReward);
96
110
  });
97
111
  }
98
112
 
113
+ private showAvailability(availability: GameActionAvailability): void {
114
+ // This scene's own request makes the actions busy too; its result text
115
+ // follows, so only someone else's purchase or ad shows the waiting text.
116
+ if (!this.rewardedAdsAvailable() || this.rewardedAction === undefined || availability === 'disposed'
117
+ || this.requesting) {
118
+ return;
119
+ }
120
+ if (availability === 'ready') {
121
+ // Restore the hint only over the waiting text, never over a result.
122
+ if (this.showingNotReady) {
123
+ this.showingNotReady = false;
124
+ this.rewardText.setText(this.rewardHint());
125
+ }
126
+ return;
127
+ }
128
+ this.showingNotReady = true;
129
+ // `history-full` lasts until the runtime is recreated; only `busy` ends
130
+ // with the other purchase or ad.
131
+ this.rewardText.setText(
132
+ t(
133
+ this.context.locale,
134
+ availability === 'history-full'
135
+ ? 'rewardHistoryFull'
136
+ : availability === 'reconciliation-required'
137
+ ? 'rewardReconciling'
138
+ : 'rewardNotReady',
139
+ ),
140
+ );
141
+ }
142
+
143
+ /** A result or error replaces the waiting text for good: `ready` must not restore the hint over it. */
144
+ private showRewardText(text: string): void {
145
+ this.showingNotReady = false;
146
+ this.rewardText.setText(text);
147
+ }
148
+
99
149
  override update(_time: number, delta: number): void {
100
150
  this.state = stepStarterRunState(this.state, delta);
101
151
 
@@ -139,13 +189,15 @@ export class PlayScene extends Phaser.Scene {
139
189
  const action = this.rewardedAction;
140
190
 
141
191
  if (!this.rewardedAdsAvailable() || action === undefined) {
142
- this.rewardText.setText(t(this.context.locale, 'rewardUnavailable'));
192
+ this.showRewardText(t(this.context.locale, 'rewardUnavailable'));
143
193
  return;
144
194
  }
145
195
 
196
+ // The tap-time guard stays: availability can change before the next frame.
146
197
  if (this.context.gameRuntime.actions?.getAvailability() !== 'ready') {
147
198
  return;
148
199
  }
200
+ this.requesting = true;
149
201
  try {
150
202
  const result = await action.execute({
151
203
  placementId: rewardedPlacementId,
@@ -155,7 +207,7 @@ export class PlayScene extends Phaser.Scene {
155
207
  if (action.isDisposed() || this.rewardedAction !== action) {
156
208
  return;
157
209
  }
158
- this.rewardText.setText(
210
+ this.showRewardText(
159
211
  result.claim?.granted === true
160
212
  ? t(this.context.locale, 'reward', { status: result.status })
161
213
  : t(this.context.locale, 'rewardError'),
@@ -169,9 +221,13 @@ export class PlayScene extends Phaser.Scene {
169
221
  },
170
222
  });
171
223
  if (!action.isDisposed() && this.rewardedAction === action) {
172
- this.rewardText.setText(t(this.context.locale, 'rewardError'));
224
+ this.showRewardText(t(this.context.locale, 'rewardError'));
173
225
  }
174
226
  console.error('[rewarded-ad]', error);
227
+ } finally {
228
+ if (this.rewardedAction === action) {
229
+ this.requesting = false;
230
+ }
175
231
  }
176
232
  }
177
233
  }