@apps-in-toss/devtools 3.1.0-beta.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,28 +1,352 @@
1
- //#region src/unplugin/index.d.ts
1
+ //#region src/mock/deep-merge.d.ts
2
2
  /**
3
- * @apps-in-toss/devtools unplugin
4
- *
5
- * 모든 주요 번들러를 지원하는 단일 플러그인.
6
- * @apps-in-toss/web-framework @apps-in-toss/devtools/mock 으로 alias 설정.
7
- *
8
- * Usage:
9
- * import aitDevtools from '@apps-in-toss/devtools/unplugin';
3
+ * unplugin `initialState` 옵션이 `DEFAULT_STATE`에 병합될 때 쓰는 순수
4
+ * 유틸리티. 카탈로그 선언 의미론에 맞춰 **객체는 재귀 병합, 배열은 부분
5
+ * 병합 없이 통째로 교체**한다 예를 들어 `iap.products`에 override 배열을
6
+ * 주면 기존 원소와 합쳐지는 아니라 그 배열로 완전히 대체된다(신규 SKU
7
+ * 하나를 추가하려 해도 기본 카탈로그 원소를 함께 적어야 한다는 뜻).
10
8
  *
11
- * // Vite
12
- * export default { plugins: [aitDevtools.vite()] };
13
- *
14
- * // Webpack / Next.js
15
- * config.plugins.push(aitDevtools.webpack());
16
- *
17
- * // Rspack
18
- * config.plugins.push(aitDevtools.rspack());
9
+ * `base`에 없는 키(오탈자·구버전 필드 등)는 조용히 무시한다 — 타입 체크가
10
+ * 없는 소비자(순수 JS `vite.config.js`) 잘못된 키를 넣어도 알 수 없는
11
+ * 필드가 state에 새로 생겨 patch/panel 코드가 예상 못한 shape을 보는 사고를
12
+ * 막기 위한 방어적 기본값이다.
13
+ */
14
+ /**
15
+ * `T`를 재귀적으로 부분 타입화한다. 배열 필드는 원소 단위로 partial하지
16
+ * 않고 전체 배열 타입을 그대로 유지한다 — merge 시 배열은 통째로 교체되기
17
+ * 때문에, override 쪽 배열도 완전한 원소 shape을 요구하는 게 맞다.
18
+ */
19
+ type DeepPartial<T> = T extends (infer _U)[] ? T : T extends readonly (infer _U)[] ? T : T extends object ? { [K in keyof T]?: DeepPartial<T[K]>; } : T;
20
+ //#endregion
21
+ //#region src/mock/native-error.d.ts
22
+ /**
23
+ * sdk-example#284 매트릭스에 등재된 2.x 네이티브 실패 코드 인벤토리.
24
+ * 새 코드가 매트릭스에 추가되면 여기에도 추가한다.
25
+ */
26
+ type NativeErrorCode = "APP_LOGIN" | "PLACEMENT_ID_FETCH_FAILED" | "EXECUTION_ERROR" | "NO_PERMISSION" | "INVALID_REQUEST" | "INVALID_DATA" | "FAILED_TO_GET_LOADED_AD" | "APP_BRIDGE_THROTTLED" | "1006" | "4000";
27
+ //#endregion
28
+ //#region src/mock/sdk-call.d.ts
29
+ /**
30
+ * SDK-call trace shape produced by the mock's `observe()` wrapper.
19
31
  *
20
- * // esbuild
21
- * { plugins: [aitDevtools.esbuild()] }
32
+ * These declarations used to live in `src/mcp/ait-source.ts` (that module is now
33
+ * `@ait-co/debugger`'s `packages/debugger/src/mcp/ait-source.ts`) because the MCP
34
+ * `AIT.getSdkCallHistory` tool was their only *consumer*. After the debug
35
+ * surface moved to `@ait-co/debugger` (#818) the producer — `observe()`
36
+ * writing into `aitState.sdkCallLog` — is the only side left in this package,
37
+ * so the types live next to it.
22
38
  *
23
- * // Rollup
24
- * { plugins: [aitDevtools.rollup()] }
39
+ * They stay part of the public `@apps-in-toss/devtools/mock` surface (re-exported by
40
+ * `state.ts`): a consumer reading `window.__ait.sdkCallLog` needs the shape,
41
+ * and any out-of-process reader (the MCP daemon among them) types against the
42
+ * same declarations.
43
+ */
44
+ /**
45
+ * Mock fidelity grade of the SDK call.
46
+ * - `faithful` — mock faithfully reproduces the real SDK contract (🟢).
47
+ * - `partial` — mock partially matches; edge cases may differ from real (🟡).
48
+ * - `inert` — mock accepts the call but produces no observable effect (🔴).
49
+ */
50
+ type AitSdkCallFidelity = "faithful" | "partial" | "inert";
51
+ /** One entry of the SDK-call trace. */
52
+ interface AitSdkCall {
53
+ /** SDK method name, e.g. `getOperationalEnvironment`, `saveBase64Data`. */
54
+ method: string;
55
+ /** Arguments passed to the call, serialized for transport. */
56
+ args: unknown[];
57
+ /** Milliseconds since epoch when the call was made. */
58
+ timestamp: number;
59
+ /** Outcome — resolved value, rejection error message, or still pending. */
60
+ status: "resolved" | "rejected" | "pending";
61
+ /** Serialized resolved value when `status === 'resolved'`. */
62
+ result?: unknown;
63
+ /** Error message when `status === 'rejected'`. */
64
+ error?: string;
65
+ /** Mock fidelity grade — how closely this mock reproduces the real SDK behaviour. */
66
+ fidelity: AitSdkCallFidelity;
67
+ }
68
+ //#endregion
69
+ //#region src/mock/types.d.ts
70
+ type PlatformOS = "ios" | "android";
71
+ type OperationalEnvironment = "toss" | "sandbox";
72
+ type NetworkStatus = "OFFLINE" | "WIFI" | "2G" | "3G" | "4G" | "5G" | "WWAN" | "UNKNOWN";
73
+ type PermissionStatus = "notDetermined" | "denied" | "allowed";
74
+ type PermissionName = "clipboard" | "contacts" | "photos" | "geolocation" | "camera" | "microphone";
75
+ type DeviceApiMode = "mock" | "web" | "prompt";
76
+ interface DeviceModes {
77
+ camera: DeviceApiMode;
78
+ photos: DeviceApiMode;
79
+ location: DeviceApiMode;
80
+ network: "mock" | "web";
81
+ clipboard: "mock" | "web";
82
+ }
83
+ interface MockData {
84
+ images: string[];
85
+ clipboardText: string;
86
+ }
87
+ interface LocationCoords {
88
+ latitude: number;
89
+ longitude: number;
90
+ altitude: number;
91
+ accuracy: number;
92
+ altitudeAccuracy: number;
93
+ heading: number;
94
+ }
95
+ interface MockLocation {
96
+ coords: LocationCoords;
97
+ timestamp: number;
98
+ accessLocation?: "FINE" | "COARSE";
99
+ }
100
+ interface MockContact {
101
+ name: string;
102
+ phoneNumber: string;
103
+ }
104
+ interface MockIapProduct {
105
+ sku: string;
106
+ type: "CONSUMABLE" | "NON_CONSUMABLE" | "SUBSCRIPTION";
107
+ displayName: string;
108
+ displayAmount: string;
109
+ iconUrl: string;
110
+ description: string;
111
+ renewalCycle?: "WEEKLY" | "MONTHLY" | "YEARLY";
112
+ }
113
+ type IapNextResult = "success" | "USER_CANCELED" | "INVALID_PRODUCT_ID" | "PAYMENT_PENDING" | "NETWORK_ERROR" | "ITEM_ALREADY_OWNED" | "INTERNAL_ERROR";
114
+ type NotificationAgreementResult = "newAgreement" | "alreadyAgreed" | "agreementRejected";
115
+ /**
116
+ * `getConsentedUserData`가 반환할 수 있는 동의 데이터 키. SDK 선언(`@apps-in-toss/web-bridge`
117
+ * 경유, web-framework 2.x 라인)의 `ConsentedUserDataKey`와 이름·값 동일(devtools#798).
118
+ */
119
+ type ConsentedUserDataKey = "USER_NAME" | "USER_GENDER" | "USER_NATIONALITY" | "USER_BIRTHDAY" | "USER_PHONE" | "USER_ADDRESS" | "USER_EMAIL" | "USER_CONSUMPTION_HISTORY";
120
+ /** `getConsentedUserData`의 반환 shape. SDK 선언과 동일한 `Partial<Record<..., string>>`. */
121
+ type ConsentedUserData = Partial<Record<ConsentedUserDataKey, string>>;
122
+ interface DeclaredAgeRange {
123
+ status: "SHARING" | "DECLINED_SHARING" | "UNSUPPORTED_OS" | "NOT_AVAILABLE" | "INVALID_REQUEST" | "FAILED";
124
+ lowerBound?: number;
125
+ upperBound?: number;
126
+ ageRangeDeclaration?: string;
127
+ activeParentalControls: string[];
128
+ isEligibleForAgeFeatures?: boolean;
129
+ errorCode?: string;
130
+ }
131
+ interface AnalyticsLogEntry {
132
+ timestamp: number;
133
+ type: string;
134
+ params: Record<string, unknown>;
135
+ }
136
+ interface SafeAreaInsets {
137
+ top: number;
138
+ bottom: number;
139
+ left: number;
140
+ right: number;
141
+ }
142
+ type ViewportPresetId = "none" | "iphone-se-3" | "iphone-15-pro" | "iphone-16e" | "iphone-17" | "iphone-air" | "iphone-17-pro" | "iphone-17-pro-max" | "galaxy-s26" | "galaxy-s26-plus" | "galaxy-s26-ultra" | "galaxy-z-flip7" | "galaxy-z-fold7-folded" | "galaxy-z-fold7-unfolded" | "custom";
143
+ /**
144
+ * Panel의 orientation 선택.
145
+ * - `auto` — Panel이 강제하지 않음. 앱의 SDK `setDeviceOrientation` 호출을 그대로 따름.
146
+ * 호출 값은 별도로 `viewport.appOrientation`에 기록되며, `viewport.orientation`은
147
+ * 계속 `auto`로 유지된다 — 같은 앱이 여러 번 호출해도 매번 정상 반영됨.
148
+ * - `portrait` / `landscape` — Panel이 강제. SDK 호출은 무시됨 (로그만 남김).
25
149
  */
150
+ type ViewportOrientation = "auto" | "portrait" | "landscape";
151
+ /**
152
+ * `setDeviceOrientation`이 한 번도 호출되지 않은 초기 상태는 `null`.
153
+ * SDK가 받을 수 있는 값과 일치하므로 portrait/landscape만 허용.
154
+ */
155
+ type AppOrientation = "portrait" | "landscape" | null;
156
+ /**
157
+ * Apps in Toss host nav bar 변형. SDK `webViewProps.type`과 의미 일치.
158
+ * - `partner` (기본): 흰 배경, 뒤로가기 + 앱 아이콘/이름 + ⋯ + ×.
159
+ * - `game`: 투명 배경, 게임 캔버스를 가리지 않도록 ⋯ + ×만 표시.
160
+ */
161
+ type AitNavBarType = "partner" | "game";
162
+ interface ViewportState {
163
+ preset: ViewportPresetId;
164
+ /** User-controlled orientation. `auto`이면 `appOrientation`을 따른다. */
165
+ orientation: ViewportOrientation;
166
+ /**
167
+ * SDK가 마지막으로 요청한 orientation. `setDeviceOrientation` 호출 시 갱신.
168
+ * `orientation === 'auto'`일 때 실제 화면 방향 결정에 쓰인다. 초기값 `null`.
169
+ */
170
+ appOrientation: AppOrientation;
171
+ customWidth: number;
172
+ customHeight: number;
173
+ frame: boolean;
174
+ /** Render the Apps in Toss host nav bar (back / app name / ··· / close) inside the frame. */
175
+ aitNavBar: boolean;
176
+ /** Nav bar 변형. `partner` = 기본(흰 배경 + 아이콘/이름), `game` = 투명 배경 + ⋯/× 만. */
177
+ aitNavBarType: AitNavBarType;
178
+ }
179
+ //#endregion
180
+ //#region src/mock/state.d.ts
181
+ interface AitDevtoolsState {
182
+ platform: PlatformOS;
183
+ environment: OperationalEnvironment;
184
+ appVersion: string;
185
+ locale: string;
186
+ schemeUri: string;
187
+ groupId: string;
188
+ deploymentId: string;
189
+ deviceId: string;
190
+ brand: {
191
+ displayName: string;
192
+ icon: string;
193
+ primaryColor: string;
194
+ };
195
+ networkStatus: NetworkStatus;
196
+ navigation: {
197
+ iosSwipeGestureEnabled: boolean | null;
198
+ };
199
+ permissions: Record<PermissionName, PermissionStatus>;
200
+ location: MockLocation;
201
+ safeAreaInsets: SafeAreaInsets;
202
+ contacts: MockContact[];
203
+ iap: {
204
+ products: MockIapProduct[];
205
+ nextResult: IapNextResult;
206
+ pendingOrders: Array<{
207
+ orderId: string;
208
+ sku: string;
209
+ paymentCompletedDate: string;
210
+ }>;
211
+ completedOrders: Array<{
212
+ orderId: string;
213
+ sku: string;
214
+ status: "COMPLETED" | "REFUNDED";
215
+ date: string;
216
+ }>;
217
+ };
218
+ payment: {
219
+ nextResult: "success" | "fail";
220
+ failReason: string;
221
+ };
222
+ auth: {
223
+ isLoggedIn: boolean;
224
+ isTossLoginIntegrated: boolean;
225
+ userKeyHash: string;
226
+ anonymousKeyHash: string;
227
+ /**
228
+ * `getConsentedUserData`가 resolve할 최소 plausible 객체(devtools#798). 실제로는
229
+ * 콘솔에 등록된 동의문/데이터 묶음(`consentedUserDataKey`)에 따라 채워지는 키가
230
+ * 달라지지만 그 매핑은 서버 쪽 설정이라 mock이 알 수 없다 — 호출 파라미터와
231
+ * 무관하게 이 상태값을 그대로 반환한다.
232
+ */
233
+ consentedUserData: ConsentedUserData;
234
+ /** `getDeclaredAgeRange`/`User.getDeclaredAgeRange`가 반환할 테스트 값. */
235
+ declaredAgeRange: DeclaredAgeRange;
236
+ };
237
+ notification: {
238
+ nextResult: NotificationAgreementResult;
239
+ };
240
+ ads: {
241
+ isLoaded: boolean;
242
+ nextEvent: "loaded" | "clicked" | "dismissed" | "failedToShow" | "impression" | "userEarnedReward";
243
+ forceNoFill: boolean;
244
+ lastEvent: {
245
+ type: string;
246
+ timestamp: number;
247
+ } | null;
248
+ /** AdMob reward 단위 타입 (기본: 'coins') */
249
+ rewardUnitType: string;
250
+ /** AdMob reward 단위 수량 (기본: 10) */
251
+ rewardAmount: number;
252
+ };
253
+ game: {
254
+ profile: {
255
+ nickname: string;
256
+ profileImageUri: string;
257
+ } | null;
258
+ leaderboardScores: Array<{
259
+ score: string;
260
+ timestamp: number;
261
+ }>;
262
+ };
263
+ analyticsLog: AnalyticsLogEntry[];
264
+ sdkCallLog: AitSdkCall[];
265
+ deviceModes: DeviceModes;
266
+ mockData: MockData;
267
+ panelEditable: boolean;
268
+ viewport: ViewportState;
269
+ failureModes: FailureModes;
270
+ }
271
+ /**
272
+ * per-API 실패 코드 다이얼. 값이 설정된 API만 mock이 reject하고, 나머지는
273
+ * 기존 낙관적 resolve를 유지한다. `sdkLine`은 envelope shape 분기 축 —
274
+ * `'2.x'`(기본)는 native envelope(`{name,code,userInfo,moduleName,__isError}`),
275
+ * `'3.x'`는 맨 Error로 평탄화(sdk-example#284 매트릭스 "패턴 ① envelope 평탄화").
276
+ */
277
+ interface FailureModes {
278
+ /** 네이티브 실패 envelope의 라인 축. 기본 '2.x'. */
279
+ sdkLine: "2.x" | "3.x";
280
+ /** appLogin 실패 코드 (예: 'APP_LOGIN'). 미설정 시 기존처럼 항상 resolve. */
281
+ appLogin?: NativeErrorCode;
282
+ /** GoogleAdMob.loadAppsInTossAdMob 실패 코드 (예: 'PLACEMENT_ID_FETCH_FAILED'). */
283
+ loadAdMob?: NativeErrorCode;
284
+ /** loadFullScreenAd 실패 코드 (예: 'EXECUTION_ERROR'). */
285
+ loadFullScreenAd?: NativeErrorCode;
286
+ /**
287
+ * getIsTossLoginIntegratedService 실패 코드 (devtools#783 실측: env3 run11,
288
+ * 2.x/iOS `A1-awaited-is-boolean` 시나리오 — rejected/`Error`/`EXECUTION_ERROR`).
289
+ */
290
+ getIsTossLoginIntegratedService?: NativeErrorCode;
291
+ /**
292
+ * requestNotificationAgreement 실패 코드 (devtools#783 실측: env3 run11,
293
+ * 2.x/iOS `happy-force-*`/`A1-empty-templateCode` 시나리오 전부 —
294
+ * rejected/`Error`/`4000`).
295
+ */
296
+ requestNotificationAgreement?: NativeErrorCode;
297
+ /**
298
+ * 권한 이름별 실패 코드 (devtools#783 실측: env3 run11, 2.x/iOS
299
+ * `permissions.ait.test.ts` PERMISSION_NAMES 순회 — `geolocation`/`camera`/
300
+ * `microphone`만 rejected/`Error`/`NO_PERMISSION`, `clipboard`/`contacts`/
301
+ * `photos`는 resolved. 31146의 `granite.config.ts`가 `permissions: []`라
302
+ * 선언 안 된 권한만 거부되는 그림과 정합 — 전역 on/off가 아니라 이름 단위 맵.
303
+ * 설정된 이름만 reject, 나머지는 기존대로 resolve.
304
+ */
305
+ getPermission?: Partial<Record<PermissionName, NativeErrorCode>>;
306
+ /**
307
+ * THROTTLED 다이얼 (#834) — 실기기 네이티브 브리지의 per-method rate limit을
308
+ * 재현한다. `methods`에 적힌 메서드를 `intervalMs` 안에 재호출하면
309
+ * `APP_BRIDGE_THROTTLED`로 거부된다(2.x envelope / 3.x 맨 Error는 `sdkLine`을 따름).
310
+ * 미설정이 기본 = zero behavior change.
311
+ *
312
+ * 훅이 실제로 삽입된 메서드는 `throttle.ts`의 `THROTTLE_INSTRUMENTED_METHODS`가
313
+ * 정본이다 — 목록 밖 이름을 넣어도 효과가 없다. 거부된 호출은 창을 갱신하지 않는다.
314
+ */
315
+ throttled?: {
316
+ methods: string[];
317
+ intervalMs: number;
318
+ };
319
+ /**
320
+ * soft-resolve 다이얼 (#789) — reject가 아니라 "다른 shape로 resolve"하는 env3
321
+ * 프로비저닝-의존 실패를 재현한다. 켠 API만 실측 대체 shape로 resolve하고, 미설정
322
+ * API는 선언 타입대로 성공 shape를 유지한다(다이얼 미사용 시 zero behavior change).
323
+ * shape는 API별 고정(env3 run11 2.x/iOS 실측 — valueKeys만 실측, 문자열 내용은 예시):
324
+ * grantPromotionReward/grantPromotionRewardForGame → { errorCode, message }
325
+ * getSubscriptionInfo → {}
326
+ * checkoutPayment/requestTossPayPaysBilling → { false, reason } (valueKeys=['false','reason'])
327
+ * payment shape의 리터럴 `false` 키가 하네스 artifact가 아니라 실기기 WebView 관측값임은
328
+ * 코드로 확정됐다(sdk-example#303: capture는 relay 개입 전 WebView 안에서 계산 — 아래
329
+ * checkoutPayment 항목 주석). #303/#789.
330
+ */
331
+ softResolve?: {
332
+ grantPromotionReward?: boolean;
333
+ grantPromotionRewardForGame?: boolean;
334
+ getSubscriptionInfo?: boolean;
335
+ /**
336
+ * checkoutPayment/requestTossPayPaysBilling → `{ false: …, reason: … }`
337
+ * (valueKeys=['false','reason'], booleanValues=null). env3 run11 실측 shape로,
338
+ * 리터럴 `false` 키는 실기기 WebView가 실제로 관측한 형태다 — capture는 WebView
339
+ * 안에서 `Object.keys(value)`로 계산돼 console 문자열로 나오므로(devtools#696
340
+ * capture.ts) 우리 CDP relay가 개입하기 전이다. 즉 relay 역직렬화 artifact가 아니라
341
+ * 모든 WebView 소비자가 보는 shape다(sdk-example#303 진단 결론). 성공 분기 기본값
342
+ * ({ success: true })은 그대로 두고, 미프로비저닝 실패 재현만 다이얼에 붙인다.
343
+ */
344
+ checkoutPayment?: boolean;
345
+ requestTossPayPaysBilling?: boolean;
346
+ };
347
+ }
348
+ //#endregion
349
+ //#region src/unplugin/index.d.ts
26
350
  type SdkVersionSelection = "auto" | "2" | "3";
27
351
  /** Resolve the consumer project's installed SDK major without importing it. */
28
352
  declare function detectInstalledSdkMajor(cwd?: string): "2" | "3" | null;
@@ -32,12 +356,74 @@ interface AitDevtoolsOptions {
32
356
  * true이면 진입점에 floating panel import를 자동 추가한다.
33
357
  */
34
358
  panel?: boolean;
359
+ /**
360
+ * 패널 자동 주입 대상 진입점 파일 패턴 (default:
361
+ * `/\/(main|index|entry|app)\.[tj]sx?$/i`).
362
+ *
363
+ * 기본 패턴은 파일명이 `main`/`index`/`entry`/`app` 중 하나일 때만
364
+ * 매칭한다 — 이 이름을 쓰지 않는 진입점(예: 커스텀 브리지 파일)은
365
+ * `panel: true`여도 자동 주입 대상에서 빠진다. 그런 프로젝트는 이
366
+ * 옵션으로 자체 진입점 패턴을 지정하면 된다.
367
+ *
368
+ * ```ts
369
+ * aitDevtools.vite({ entryPattern: /\/my-bridge\.[tj]sx?$/i });
370
+ * ```
371
+ *
372
+ * `node_modules` 제외, `.tsx?`/`.jsx?` 확장자 제한은 이 옵션과 무관하게
373
+ * 항상 적용된다 — 이 옵션은 파일명 매칭 부분만 대체한다.
374
+ *
375
+ * `g`(global)/`y`(sticky) 플래그는 붙이지 마세요 — 매 진입점 후보 파일마다
376
+ * 같은 `RegExp` 인스턴스로 `.test()`를 반복 호출하는데, 두 플래그는
377
+ * `lastIndex`를 전진시켜 두 번째 파일부터 매칭이 간헐적으로 실패한다.
378
+ */
379
+ entryPattern?: RegExp;
35
380
  /** web-framework facade selection. Auto-detects the consumer SDK by default. */
36
381
  sdkVersion?: SdkVersionSelection;
37
382
  /**
38
- * mock alias 활성화 여부. default: true (development), false (production)
383
+ * production 빌드에서도 devtools를 활성화한다 (default: false).
384
+ *
385
+ * 기본적으로 devtools는 `NODE_ENV === 'production'`이면 mock alias와
386
+ * 패널 주입을 모두 건너뛴다(`Production 빌드` 섹션 참고). 스테이징
387
+ * 환경처럼 production 빌드에서도 패널을 띄우고 싶을 때 `true`로 설정한다.
388
+ * `mock`은 이 옵션과 독립적으로 결정된다 — `mock` 옵션을 명시하지 않으면
389
+ * production에서는 여전히 OFF(모니터링 전용), `panel`은 이 옵션이
390
+ * `true`면 production에서도 ON이다.
391
+ *
392
+ * ```ts
393
+ * aitDevtools.vite({ forceEnable: true }); // panel ON, mock OFF
394
+ * aitDevtools.vite({ forceEnable: true, mock: true }); // panel + mock 모두 ON
395
+ * ```
396
+ */
397
+ forceEnable?: boolean;
398
+ /**
399
+ * mock alias 활성화 여부. default: true (development), false (production,
400
+ * `forceEnable: true`인 production 빌드 포함)
39
401
  */
40
402
  mock?: boolean;
403
+ /**
404
+ * mock 상태의 초기값 커스터마이즈 — `DEFAULT_STATE`에 재귀 병합된다
405
+ * (객체는 병합, **배열은 통째로 교체** — 예: `iap.products`를 지정하면
406
+ * 그 배열이 기본 카탈로그를 완전히 대체한다). `vite.config.ts`에서
407
+ * 선언적으로 mock 카탈로그를 구성할 때 쓴다.
408
+ *
409
+ * ```ts
410
+ * aitDevtools.vite({
411
+ * initialState: {
412
+ * iap: {
413
+ * products: [
414
+ * { sku: "my-sku", type: "CONSUMABLE", displayName: "…", displayAmount: "…", iconUrl: "", description: "…" },
415
+ * ],
416
+ * },
417
+ * },
418
+ * });
419
+ * ```
420
+ *
421
+ * **Vite 전용** (webpack/rspack/esbuild/rollup에서는 무시된다) — Vite의
422
+ * `define`으로 build-time에 JSON 직렬화해 주입하므로, 값은 JSON으로
423
+ * 직렬화 가능해야 한다(함수 등 비직렬화 값은 조용히 사라진다). `base`
424
+ * (=`DEFAULT_STATE`)에 없는 키는 무시된다.
425
+ */
426
+ initialState?: DeepPartial<AitDevtoolsState>;
41
427
  /**
42
428
  * Vite dev server에 MCP state endpoint를 추가할지 여부 (default: false).
43
429
  *
@@ -50,63 +436,7 @@ interface AitDevtoolsOptions {
50
436
  * Vite 전용: webpack/rspack/esbuild/rollup 환경에서는 무시된다.
51
437
  */
52
438
  mcp?: boolean;
53
- /**
54
- * 미니앱의 webViewType (`granite.config.ts`의 `webViewProps.type`)을 빌드 상수
55
- * `__WEB_VIEW_TYPE__`로 주입한다 (#580). **Vite 전용** (다른 번들러는 무시).
56
- *
57
- * 미지정 시 `'partner'`(web-framework `webViewProps.type`의 `@default`)로 주입한다.
58
- * `game`이면 게임 모드로 자동 진입한다. (granite.config.ts를 config 시점에
59
- * 자동으로 읽는 것은 TS 모듈 로더가 필요해 보류 — 명시 옵션으로 신뢰성 확보, #580.)
60
- */
61
- webViewType?: "partner" | "game";
62
- /**
63
- * 미니앱의 `granite.config.ts` `navigationBar.transparentBackground` 값
64
- * (SDK `@apps-in-toss/plugins@2.8.0` 신규 필드, #587). `true`이면 env-2 launcher
65
- * deep-link에 `&navBarTransparent=1`을 주입해 launcher partner bar가 투명 배경으로
66
- * 렌더된다. granite.config를 직접 읽지 않는다(version-agnostic, #580 원칙) —
67
- * 소비자 vite.config가 `graniteConfig.navigationBar?.transparentBackground`를
68
- * import해 이 옵션으로 넘긴다. 미지정 시 주입 안 함(URL 청정, back-compat).
69
- */
70
- navBarTransparent?: boolean;
71
- /**
72
- * 미니앱의 `granite.config.ts` `navigationBar.theme` 값
73
- * (SDK `@apps-in-toss/plugins@2.8.0` 신규 필드, #587). `'light'` 또는 `'dark'`이면
74
- * env-2 launcher deep-link에 `&navBarTheme=<v>`를 주입해 launcher partner bar가
75
- * 해당 테마 글자/아이콘 색으로 렌더된다. granite.config를 직접 읽지 않는다
76
- * (version-agnostic, #580 원칙). 미지정 시 주입 안 함(URL 청정, back-compat).
77
- */
78
- navBarTheme?: "light" | "dark";
79
- /**
80
- * Vite dev 서버를 Cloudflare quick tunnel(`*.trycloudflare.com`, 계정 불필요)로
81
- * 외부 노출해 실제 폰에서 미리보기. **Vite dev 모드 전용** — production에서는
82
- * 터널을 띄우지 않는다 (의도치 않은 노출 방지). 다른 번들러는
83
- * 무시. `true`면 기본 동작, 객체로 세부 설정 가능.
84
- */
85
- tunnel?: boolean | {
86
- /** 노출할 포트 (미지정 시 dev 서버가 실제 listen한 포트 자동 감지). */
87
- port?: number;
88
- /** 터미널 ASCII QR 출력 (default: true). */
89
- qr?: boolean;
90
- };
91
439
  }
92
- /**
93
- * Resolves the effective tunnel option (#425).
94
- *
95
- * An explicit `tunnel` value (including `false`) always takes priority over
96
- * env vars — the `??` operator means `undefined` (= omitted) falls through,
97
- * but `false` / `true` / an object are preserved as-is (non-breaking).
98
- *
99
- * When the option is omitted:
100
- * - `AIT_TUNNEL=1` enables the screen-preview tunnel.
101
- * - Not set → `false` (disabled).
102
- *
103
- * Extracted as a pure function so it can be unit-tested without standing up
104
- * a full Vite dev server.
105
- *
106
- * @param explicit - The `tunnel` option as passed by the consumer (or `undefined` when omitted).
107
- * @param env - The process environment (injectable for testing).
108
- */
109
- declare function resolveTunnelOption(explicit: AitDevtoolsOptions["tunnel"], env: Record<string, string | undefined>): AitDevtoolsOptions["tunnel"];
110
440
  declare const aitDevtoolsPlugin: import("unplugin").UnpluginInstance<AitDevtoolsOptions | undefined, boolean>;
111
441
  /**
112
442
  * 번들러 플러그인 팩토리의 반환 타입을 의도적으로 any로 풀어놓아요.
@@ -137,4 +467,4 @@ declare const esbuild: BundlerPluginFactory;
137
467
  declare const rspack: BundlerPluginFactory;
138
468
  declare const aitDevtools: AitDevtoolsUnplugin;
139
469
  //#endregion
140
- export { AitDevtoolsOptions, SdkVersionSelection, aitDevtools as default, detectInstalledSdkMajor, esbuild, resolveTunnelOption, rollup, rspack, vite, webpack };
470
+ export { AitDevtoolsOptions, SdkVersionSelection, aitDevtools as default, detectInstalledSdkMajor, esbuild, rollup, rspack, vite, webpack };