@appaflytech/zeyrek-ai-mcp 0.0.36 → 0.0.40

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.
Files changed (210) hide show
  1. package/.env.example +15 -0
  2. package/README.md +5 -5
  3. package/dist/config.js +37 -0
  4. package/dist/index.js +2 -0
  5. package/dist/knowledge/access.js +802 -0
  6. package/dist/knowledge/backendConstraints.js +18 -1
  7. package/dist/knowledge/case.js +41 -0
  8. package/dist/knowledge/componentCatalog.js +524 -53
  9. package/dist/knowledge/componentEvents.js +132 -7
  10. package/dist/knowledge/connectDataBind.js +851 -0
  11. package/dist/knowledge/connectDataDefinition.js +344 -0
  12. package/dist/knowledge/connectDataExecute.js +93 -0
  13. package/dist/knowledge/connectDataRules.js +128 -0
  14. package/dist/knowledge/decision.js +121 -0
  15. package/dist/knowledge/expressionPrelude.js +60 -0
  16. package/dist/knowledge/layoutSummary.js +140 -2
  17. package/dist/knowledge/microserviceParameters.js +58 -0
  18. package/dist/knowledge/mining.js +94 -0
  19. package/dist/knowledge/mobileComponentCatalog.js +876 -0
  20. package/dist/knowledge/mobileComponentEvents.js +259 -0
  21. package/dist/knowledge/mobileStore.js +396 -0
  22. package/dist/knowledge/normalize.js +240 -72
  23. package/dist/knowledge/outlines.js +36 -0
  24. package/dist/knowledge/platform/lint.js +385 -0
  25. package/dist/knowledge/platform/mobile.js +142 -0
  26. package/dist/knowledge/platform/mobileLayoutLint.js +324 -0
  27. package/dist/knowledge/platform/mobileRules.js +231 -0
  28. package/dist/knowledge/platform/mobileSpacing.js +82 -0
  29. package/dist/knowledge/platform/mobileTabs.js +108 -0
  30. package/dist/knowledge/platform/nativeBoxModel.js +44 -0
  31. package/dist/knowledge/platform/profile.js +25 -0
  32. package/dist/knowledge/platform/resolve.js +122 -0
  33. package/dist/knowledge/platform/web.js +39 -0
  34. package/dist/knowledge/policy.js +18 -4
  35. package/dist/knowledge/process.js +510 -0
  36. package/dist/knowledge/reservedTables.js +79 -0
  37. package/dist/knowledge/rowFilter.js +43 -0
  38. package/dist/knowledge/rules.js +485 -22
  39. package/dist/knowledge/schemaGuard.js +11 -0
  40. package/dist/knowledge/styleResolve.js +64 -10
  41. package/dist/knowledge/styleResolveNative.js +529 -0
  42. package/dist/knowledge/themeResolve.js +3 -3
  43. package/dist/observability/trace.js +13 -5
  44. package/dist/platform/argumentAliases.js +128 -0
  45. package/dist/platform/bodyFile.js +103 -0
  46. package/dist/platform/capture/actions.js +398 -0
  47. package/dist/platform/capture/browser.js +1 -0
  48. package/dist/platform/capture/capture.js +683 -0
  49. package/dist/platform/capture/console-log.js +122 -0
  50. package/dist/platform/capture/index.js +11 -0
  51. package/dist/platform/capture/readiness.js +103 -0
  52. package/dist/platform/capture/summary.js +77 -0
  53. package/dist/platform/capture/target.js +5 -0
  54. package/dist/platform/capture/targets/studio-mobile.js +15 -0
  55. package/dist/platform/capture/targets/studio-web.js +5 -0
  56. package/dist/platform/diagnostics.js +10 -4
  57. package/dist/platform/domSummary.js +1 -1
  58. package/dist/platform/errors.js +163 -10
  59. package/dist/platform/http.js +29 -8
  60. package/dist/platform/impersonation.js +45 -0
  61. package/dist/platform/jsonArray.js +24 -0
  62. package/dist/platform/responses.js +39 -5
  63. package/dist/platform/schemaReadMemory.js +60 -0
  64. package/dist/platform/secrets.js +68 -2
  65. package/dist/platform/strictArguments.js +68 -12
  66. package/dist/platform/tokenVault.js +187 -4
  67. package/dist/resources/examples/bpm-gorev-kontrol.json +294 -0
  68. package/dist/resources/examples/bpm-iso-dokuman.json +228 -0
  69. package/dist/resources/examples/bpm-satinalma.json +376 -0
  70. package/dist/resources/examples/connect-data-convert-verify.json +107 -0
  71. package/dist/resources/examples/connect-data-dropdown-roles.json +66 -0
  72. package/dist/resources/examples/connect-data-edit-m2m.json +73 -0
  73. package/dist/resources/examples/connect-data-query-params.json +50 -0
  74. package/dist/resources/examples/connect-data-table-entity.json +147 -0
  75. package/dist/resources/examples/connect-data-workflow-microservice.json +65 -0
  76. package/dist/resources/examples/middleware-authenticate.json +1 -1
  77. package/dist/resources/examples/mobile-app-shell-drawer.json +244 -0
  78. package/dist/resources/examples/mobile-app-shell-stack.json +157 -0
  79. package/dist/resources/examples/mobile-app-shell-tab.json +208 -0
  80. package/dist/resources/examples/mobile-form-screen.json +365 -0
  81. package/dist/resources/examples/mobile-list-detail-form.json +703 -0
  82. package/dist/resources/examples/page-detail-profile.json +2 -1
  83. package/dist/resources/examples/page-dynamic-form.json +3 -2
  84. package/dist/resources/examples/page-flex-sections.json +1 -1
  85. package/dist/resources/examples/page-html-embed.json +1 -1
  86. package/dist/resources/examples/workflow-basic.json +8 -2
  87. package/dist/resources/guides/access.md +810 -0
  88. package/dist/resources/guides/bpm.md +691 -0
  89. package/dist/resources/guides/case.md +153 -0
  90. package/dist/resources/guides/component.md +92 -5
  91. package/dist/resources/guides/decision.md +185 -0
  92. package/dist/resources/guides/entity.md +50 -8
  93. package/dist/resources/guides/expressions.md +14 -5
  94. package/dist/resources/guides/functions.md +1 -1
  95. package/dist/resources/guides/getting-started.md +38 -1
  96. package/dist/resources/guides/layout.md +11 -7
  97. package/dist/resources/guides/merge.md +42 -7
  98. package/dist/resources/guides/microservice.md +88 -3
  99. package/dist/resources/guides/middleware.md +69 -5
  100. package/dist/resources/guides/mobile-layout.md +193 -0
  101. package/dist/resources/guides/mobile-publish.md +148 -0
  102. package/dist/resources/guides/mobile.md +565 -0
  103. package/dist/resources/guides/page.md +469 -82
  104. package/dist/resources/guides/theme.md +8 -2
  105. package/dist/resources/guides/translation.md +1 -1
  106. package/dist/resources/guides/workflow.md +58 -1
  107. package/dist/resources/read.js +41 -0
  108. package/dist/resources/rules.js +11 -5
  109. package/dist/resources/schema-fingerprint.json +7 -5
  110. package/dist/resources/schemas/AccessExplanationDto.json +144 -0
  111. package/dist/resources/schemas/AccessMatrixDto.json +170 -0
  112. package/dist/resources/schemas/AccessPermissionDto.json +57 -0
  113. package/dist/resources/schemas/AccessPolicyDto.json +223 -0
  114. package/dist/resources/schemas/AccessResourceDto.json +86 -0
  115. package/dist/resources/schemas/AppSessionDto.json +53 -0
  116. package/dist/resources/schemas/ConnectDataDescribeDto.json +344 -0
  117. package/dist/resources/schemas/ConnectDataRequestModel.json +202 -0
  118. package/dist/resources/schemas/ConnectDataResultDto.json +421 -0
  119. package/dist/resources/schemas/ConnectorCredentialDto.json +70 -0
  120. package/dist/resources/schemas/ConvertConnectDataToMicroserviceCommand.json +37 -0
  121. package/dist/resources/schemas/CreateAccessPolicyCommand.json +209 -0
  122. package/dist/resources/schemas/CreateDecisionCommand.json +221 -0
  123. package/dist/resources/schemas/CreateEntityCommand.json +118 -0
  124. package/dist/resources/schemas/CreateMicroserviceCommand.json +13 -1
  125. package/dist/resources/schemas/CreateProcessDefinitionCommand.json +1337 -0
  126. package/dist/resources/schemas/CreateQueryCommand.json +4 -0
  127. package/dist/resources/schemas/CreateWorkflowCommand.json +146 -1
  128. package/dist/resources/schemas/DataJobDto.json +6 -1
  129. package/dist/resources/schemas/DecisionDto.json +302 -0
  130. package/dist/resources/schemas/DetachConnectDataMicroserviceCommand.json +28 -0
  131. package/dist/resources/schemas/ExplainAccessQuery.json +33 -0
  132. package/dist/resources/schemas/GenerateProcessDocumentCommand.json +116 -0
  133. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +24 -0
  134. package/dist/resources/schemas/MicroserviceDto.json +15 -3
  135. package/dist/resources/schemas/OrganizationProjectDto.json +343 -0
  136. package/dist/resources/schemas/OrganizationSchemaDto.json +196 -0
  137. package/dist/resources/schemas/OrganizationSchemaVersionDto.json +207 -0
  138. package/dist/resources/schemas/PatchProcessDefinitionCommand.json +76 -0
  139. package/dist/resources/schemas/PreviewMicroserviceCommand.json +10 -1
  140. package/dist/resources/schemas/PreviewWorkflowCommand.json +25 -1
  141. package/dist/resources/schemas/ProcessDefinitionDto.json +1480 -0
  142. package/dist/resources/schemas/ProcessSimulationDto.json +96 -0
  143. package/dist/resources/schemas/SaveConnectorCredentialCommand.json +90 -0
  144. package/dist/resources/schemas/SaveProcessEventSourceCommand.json +61 -0
  145. package/dist/resources/schemas/SaveProcessMailSourceCommand.json +89 -0
  146. package/dist/resources/schemas/SignProcessTaskCommand.json +73 -0
  147. package/dist/resources/schemas/SimulateProcessDefinitionQuery.json +92 -0
  148. package/dist/resources/schemas/StartMobileBuildCommand.json +54 -0
  149. package/dist/resources/schemas/StepDto.json +25 -1
  150. package/dist/resources/schemas/UpdateAccessPolicyCommand.json +209 -0
  151. package/dist/resources/schemas/UpdateDecisionCommand.json +216 -0
  152. package/dist/resources/schemas/UpdateEntityCommand.json +118 -0
  153. package/dist/resources/schemas/UpdateMicroserviceCommand.json +13 -1
  154. package/dist/resources/schemas/UpdateProcessDefinitionCommand.json +1357 -0
  155. package/dist/resources/schemas/UpdateQueryCommand.json +4 -0
  156. package/dist/resources/schemas/UpdateViewCommand.json +0 -2
  157. package/dist/resources/schemas/UpdateWorkflowCommand.json +146 -1
  158. package/dist/resources/schemas/UpsertMobileStoreSettingsCommand.json +333 -0
  159. package/dist/resources/schemas/ValidateProcessDefinitionQuery.json +1339 -0
  160. package/dist/server.js +31 -1
  161. package/dist/tools/access.js +369 -2
  162. package/dist/tools/accessOperations.js +135 -0
  163. package/dist/tools/accessPolicies.js +170 -0
  164. package/dist/tools/accessRuntime.js +203 -0
  165. package/dist/tools/applications.js +63 -3
  166. package/dist/tools/bpm.js +343 -0
  167. package/dist/tools/bpmGovernance.js +266 -0
  168. package/dist/tools/bpmOperations.js +230 -0
  169. package/dist/tools/bpmReporting.js +154 -0
  170. package/dist/tools/bpmRouting.js +150 -0
  171. package/dist/tools/bpmRuntime.js +396 -0
  172. package/dist/tools/case.js +178 -0
  173. package/dist/tools/caseRuntime.js +89 -0
  174. package/dist/tools/common.js +19 -6
  175. package/dist/tools/components.js +118 -14
  176. package/dist/tools/connectData.js +450 -0
  177. package/dist/tools/connectors.js +193 -0
  178. package/dist/tools/datasources.js +3 -3
  179. package/dist/tools/decisions.js +206 -0
  180. package/dist/tools/dependencies.js +1 -1
  181. package/dist/tools/deployments.js +38 -3
  182. package/dist/tools/designSchemaPatch.js +34 -9
  183. package/dist/tools/dtos.js +36 -5
  184. package/dist/tools/entities.js +79 -12
  185. package/dist/tools/environments.js +1 -1
  186. package/dist/tools/externalComponents.js +26 -5
  187. package/dist/tools/flows.js +119 -13
  188. package/dist/tools/folders.js +1 -1
  189. package/dist/tools/imports.js +5 -2
  190. package/dist/tools/layouts.js +50 -17
  191. package/dist/tools/merges.js +2 -2
  192. package/dist/tools/microservices.js +77 -25
  193. package/dist/tools/mobile.js +280 -0
  194. package/dist/tools/mobileStore.js +572 -0
  195. package/dist/tools/modules.js +48 -0
  196. package/dist/tools/organizationStructure.js +137 -0
  197. package/dist/tools/pages.js +467 -100
  198. package/dist/tools/platformInfo.js +28 -8
  199. package/dist/tools/profileWrite.js +92 -0
  200. package/dist/tools/projects.js +71 -9
  201. package/dist/tools/rows.js +69 -13
  202. package/dist/tools/schemaOps.js +61 -1
  203. package/dist/tools/screenWrite.js +36 -7
  204. package/dist/tools/screens.js +95 -5
  205. package/dist/tools/templates.js +26 -1
  206. package/dist/tools/translations.js +1 -1
  207. package/dist/tools/variables.js +11 -5
  208. package/dist/tools/workflows.js +10 -5
  209. package/package.json +2 -2
  210. package/dist/platform/screenshot.js +0 -1162
@@ -0,0 +1,259 @@
1
+ /**
2
+ * MOBİLE ÖZGÜ olay yükleri. Mobil komponentlerin ÇOĞU web şemasını genişletir ve
3
+ * olayları web sözleşmesindedir (`componentEvents.ts`) — bu dosya yalnız web'de
4
+ * karşılığı OLMAYAN komponentler içindir (`app-bar` gibi kabuk komponentleri).
5
+ *
6
+ * Okuyucu önce buraya, sonra web sözlüğüne bakar; parity testi
7
+ * (`test/mobile-component-parity.test.ts`) her mobil olayın iki sözlükten
8
+ * birinde karşılanmasını zorunlu tutar.
9
+ */
10
+ const NO_DATA = { payload: {} };
11
+ export const mobileComponentEventContracts = {
12
+ 'choice-chips': {
13
+ change: {
14
+ payload: {
15
+ value: 'string | number | boolean | Array — seçenek ÖZGÜN tipinde (değeri `34` yazılmış seçenek sayı yayar). `multiple` açıkken seçili değerlerin DİZİSİ (`[34, "a"]`); tek kipte seçili çipe yeniden dokunmak boş metin (`""`) yayar. Devre dışı seçenek olay yaymaz.'
16
+ }
17
+ }
18
+ },
19
+ // Web ikizinin yükü metindir (`e.currentTarget.value`); mobil seçeneği ÖZGÜN tipinde yayar (f-06, `optionEmitValues`).
20
+ // İki platformda aynı koşul için karşılaştırmayı metne çevirin: `String(self.value) === '34'`.
21
+ radio: {
22
+ change: {
23
+ payload: {
24
+ value: 'string | number | boolean — seçilen seçeneğin ÖZGÜN değeri (değeri `34` yazılmış seçenek sayı yayar; web aynı seçimi `"34"` metni olarak yayar)'
25
+ }
26
+ }
27
+ },
28
+ checkboxgroup: {
29
+ change: {
30
+ payload: {
31
+ value: 'Array — seçili seçeneklerin ÖZGÜN değerleri (`["su", 9]`; web metin dizisi yayar). Seçeneklerde olmayan eski değer metin kalır'
32
+ }
33
+ }
34
+ },
35
+ tooltip: {
36
+ open: NO_DATA
37
+ },
38
+ calendar: {
39
+ mount: NO_DATA,
40
+ change: {
41
+ payload: {
42
+ value: "string 'yyyy-MM-ddT00:00:00' | null — seçilen gün (aralıkta başlangıç); web datepicker'ın biçimi: yerel duvar saati, gece yarısı, dilim eki yok",
43
+ endValue: "string 'yyyy-MM-ddT00:00:00' | null — YALNIZ mode: 'range'; ilk basışta null, ikinci basışta bitiş günü"
44
+ }
45
+ }
46
+ },
47
+ stack: {
48
+ mount: NO_DATA
49
+ },
50
+ 'list-item': {
51
+ click: NO_DATA
52
+ },
53
+ fab: {
54
+ click: NO_DATA
55
+ },
56
+ 'photo-viewer': {
57
+ change: { payload: { index: 'number — gösterilen fotoğrafın sırası (0 tabanlı); makine index prop\'una yazar' } },
58
+ close: { payload: {}, reads: 'kapat düğmesi ya da geri; makine isOpen\'ı kapatır' },
59
+ share: { payload: { row: 'gösterilen satırın tamamı (images dizisindeki nesne)', index: 'number — satırın sırası' }, reads: 'Paylaş düğmesi yalnız bu olay bir akışa bağlıysa çizilir; paylaşmak akışın işidir' },
60
+ delete: { payload: { row: 'gösterilen satırın tamamı', index: 'number — satırın sırası' }, reads: 'Sil düğmesi yalnız bu olay bir akışa bağlıysa çizilir; satırı silmek ve listeyi yenilemek akışın işidir' }
61
+ },
62
+ timer: {
63
+ mount: NO_DATA,
64
+ tick: { payload: { value: 'number — gösterilen saniye (geri sayımda kalan, kronometrede geçen); makine value prop\'una yazar' } },
65
+ finish: { payload: { isRunning: 'false — geri sayım 0\'a indi; makine isRunning\'i kapatır' } }
66
+ },
67
+ audio: {
68
+ mount: NO_DATA,
69
+ play: { payload: { isPlaying: "true — çalma başladı (denetim ya da akış); makine isPlaying'i yazar" } },
70
+ pause: { payload: { isPlaying: "false — çalma durdu (denetim, akış, bitiş ya da tarayıcının otomatik oynatma reddi); makine isPlaying'i yazar" } },
71
+ ended: { payload: { isPlaying: "false — ses sona vardı (loop kapalıyken, pause'tan sonra); makine isPlaying'i kapatır" } }
72
+ },
73
+ lottie: {
74
+ mount: NO_DATA,
75
+ finish: { payload: { isPlaying: "false — döngüsüz animasyon bitti; makine isPlaying'i kapatır, yeniden true yazmak baştan oynatır" } }
76
+ },
77
+ webview: {
78
+ mount: NO_DATA,
79
+ load: { payload: {}, reads: "her tamamlanan yüklemede yayılır (izin verilen gezinmeden sonra da); cihazda yüklenemeyen sayfada yayılmaz (yerine error), web'de tarayıcı engellenen çerçevede de yayar" },
80
+ navigate: { payload: { url: "string — sayfanın gitmek istediği adres; makine url prop'una yazar (self.url). allowNavigation kapalıyken (varsayılan) gezinme engellenir; adres yalnız kullanıcının dokunduğu bağlantıda sistemde açılır" } },
81
+ message: {
82
+ payload: { data: "string — sayfanın window.ReactNativeWebView.postMessage(metin) ile yolladığı metin, olduğu gibi; makine data prop'una yazar (self.data)" },
83
+ reads: "iki yönlü köprü: sayfa window.ngApps.emit(name, data) çağırırsa yük olayın params'ında gelir — akış event.name ve event.data okur (self.data yazılmaz)"
84
+ },
85
+ error: { payload: {}, reads: "cihazda sayfa yüklenemeyince (ağ hatası) yayılır, görünüm yer tutucu çizer; web'de pratikte gelmez (iframe gibi)" }
86
+ },
87
+ html: {
88
+ mount: NO_DATA,
89
+ message: {
90
+ payload: {},
91
+ reads: "belgenin window.ngApps.emit(name, data) çağrısı; yük olayın params'ındadır — akış event.name (ileti adı) ve event.data (veri) okur. actions satırının akışı ise fireEvent(key, data) ile koşar: event.name = key, event.data = veri"
92
+ }
93
+ },
94
+ map: {
95
+ mount: NO_DATA,
96
+ markerClick: { payload: { markerId: "string — dokunulan işaretçinin `id`si (boşsa listedeki sırası); makine markerId prop'una yazar (self.markerId). Başlığı ya da açıklaması olan işaretçide balon da açılır" } },
97
+ regionChange: {
98
+ payload: {
99
+ latitude: 'number — kullanıcının bıraktığı merkezin enlemi (6 basamak)',
100
+ longitude: 'number — merkezin boylamı (6 basamak, −180…180)',
101
+ zoom: 'number — web harita ölçeği (2 basamak, 1–19)'
102
+ },
103
+ reads: "yalnız kullanıcının sürüklemesi ya da yakınlaştırması bitince yayılır; akışın yazdığı görünüm ve kutu boyu değişimi yaymaz. Makine üç prop'u da yazar (self.latitude, self.longitude, self.zoom)"
104
+ },
105
+ press: {
106
+ payload: {
107
+ latitude: 'number — yerin enlemi: tek işaretçi varsa o işaretçi, yoksa görünümün merkezi',
108
+ longitude: 'number — yerin boylamı',
109
+ mapsUrl: "string — yeri cihazın harita uygulamasında açan adres (iOS Apple Haritalar https://maps.apple.com/?ll=…&q=<ad>, Android geo:<lat>,<lng>?q=…(<ad>), web Google Haritalar araması)"
110
+ },
111
+ reads: "işaretçi dışına dokunuşta yayılır (interactive: false önizlemede bütün yüzey). Makine YALNIZ mapsUrl'i düğüme yazar (self.mapsUrl) — koordinat yazılsaydı harita kayardı; koordinat event.data'dadır. Tarif: redirect isRaw: true, path: '<harita>.mapsUrl'"
112
+ }
113
+ },
114
+ adBanner: {
115
+ mount: NO_DATA,
116
+ load: {
117
+ payload: {
118
+ width: 'number — yüklenen reklamın genişliği (dp)',
119
+ height: 'number — yüklenen reklamın yüksekliği (dp)'
120
+ },
121
+ reads: 'yalnız mağaza sürümünde, reklam yüklenip çizilince yayılır'
122
+ },
123
+ fail: {
124
+ payload: {
125
+ code: "string — SDK hata kodu (`googleMobileAds/no-fill`, `googleMobileAds/network-error`…) ya da `unavailable`: derleme reklam taşımıyor (oynatıcı, web), ya da yerleşim bu platformda yok / banner değil",
126
+ message: 'string — hatanın açıklaması'
127
+ },
128
+ reads: "reklam çizilemeyince yayılır; kutu yer kaplamaz. Reklam alanını akışla gizlemek ya da yerine içerik koymak için kullanın. Mağaza sürümünde rıza yokken ya da SDK henüz başlamamışken YAYILMAZ"
129
+ },
130
+ click: { payload: {}, reads: 'kullanıcı reklama dokununca (yalnız mağaza sürümü)' },
131
+ open: { payload: {}, reads: 'reklam ekranı kaplayan bir katman açınca' },
132
+ close: { payload: {}, reads: 'kullanıcı reklam katmanından uygulamaya dönünce' }
133
+ },
134
+ markdown: {
135
+ mount: NO_DATA,
136
+ linkClick: {
137
+ payload: {
138
+ href: "string — dokunulan bağlantının ham adresi; şema süzülmeden gelir (dışarıda yalnız http(s), mailto, tel açılır)"
139
+ }
140
+ }
141
+ },
142
+ 'app-bar': {
143
+ clickBack: NO_DATA,
144
+ clickMenu: NO_DATA,
145
+ clickAction: { payload: { key: 'string (eylemin key alanı; yoksa dizideki sırası)' } }
146
+ },
147
+ 'safe-area': {
148
+ mount: NO_DATA
149
+ },
150
+ // Web `container` yalnız `mount` yayar; `click` mobile-only (plans/mobil-plantcare-acigi f-04, K1).
151
+ container: {
152
+ click: NO_DATA
153
+ },
154
+ // Arama varyantının "Vazgeç"i (plans/mobil-plantcare-acigi f-05): değer temizlenir, klavye kapanır.
155
+ textinput: {
156
+ cancel: NO_DATA
157
+ },
158
+ 'scroll-view': {
159
+ mount: NO_DATA,
160
+ scrollEnd: NO_DATA,
161
+ refresh: NO_DATA,
162
+ scroll: {
163
+ payload: {
164
+ y: 'number — dikey kaydırma konumu (yatay kaydırıcıda x)',
165
+ contentHeight: 'number — içeriğin boyu (yatayda contentWidth)',
166
+ viewportHeight: 'number — görünen alanın boyu (yatayda viewportWidth)'
167
+ },
168
+ reads: '100 ms seyreltmeli; yalnız bu olaya akış bağlıyken yayılır. Düğüme scrollY (scrollX) de yazılır'
169
+ }
170
+ },
171
+ 'grouped-list': {
172
+ mount: NO_DATA,
173
+ change: {
174
+ payload: {
175
+ selectedIndex: 'number — dokunulan öğenin rows içindeki DÜZ indeksi (bölüm içi değil); aynı öğeye yeniden dokununca yayılmaz. Makine selectedIndex\'i yazar, akışta tıklanan satır <liste>.selectedItem'
176
+ }
177
+ },
178
+ itemPress: {
179
+ payload: {
180
+ index: 'number (öğenin rows içindeki düz indeksi, 0 tabanlı)',
181
+ row: 'object (öğenin satır verisi)'
182
+ },
183
+ reads: 'yalnız itemPressable açıkken; önce change { selectedIndex } yayılır. Öğe şablonu basılabilir bir bileşense (button, click\'i bağlı list-item) dokunuş onun click\'ine gider, itemPress yayılmaz; click\'i bağlı olmayan list-item basılamaz, dokunuş itemPress\'e gider'
184
+ },
185
+ swipe: {
186
+ payload: {
187
+ key: 'string (kaydırma eyleminin key alanı)',
188
+ index: 'number (öğenin rows içindeki düz indeksi, 0 tabanlı)',
189
+ row: 'object (öğenin satır verisi)'
190
+ }
191
+ },
192
+ sectionPress: {
193
+ payload: {
194
+ key: 'string — bölüm anahtarı (tarih değerinde yerel gün YYYY-AA-GG)',
195
+ title: 'string — bölüm başlığı (anahtar; boş alanlı satırların bölümünde otherSectionTitle)',
196
+ count: 'number — bölümdeki öğe sayısı',
197
+ items: 'object[] — bölümdeki satırların verisi'
198
+ },
199
+ reads: 'bölüm başlığına dokununca; yük başlık şablonunun Section ifadesiyle aynı nesnedir'
200
+ },
201
+ selectionChange: {
202
+ payload: {
203
+ selectedIndices: 'number[] — seçili satırların rows içindeki DÜZ indeksleri, sıralı',
204
+ count: 'number — seçili satır sayısı'
205
+ },
206
+ reads: 'yalnız selectionMode: multiple (ya da longPressSelects ile açılınca); akış event.data.selectedIndices / event.data.count okur, düğümün selectedIndices/selectedItems değeri de yazılır'
207
+ }
208
+ },
209
+ eventcalendar: {
210
+ mount: NO_DATA,
211
+ dateClick: {
212
+ payload: { date: "string 'yyyy-MM-dd'", rows: 'object[] — o güne düşen kaynak satırlar (mobil)' },
213
+ reads: "self'e yazılmaz — event.data ile oku; ay ve hafta görünümünde güne dokununca"
214
+ },
215
+ eventClick: {
216
+ payload: { id: 'string', title: 'string', row: 'kaynak satırın TAMAMI' },
217
+ reads: "self'e yazılmaz — event.data ile oku; gün çizelgesinde bloğa dokununca"
218
+ },
219
+ monthChange: {
220
+ payload: {
221
+ month: 'number (1-TABANLI)',
222
+ year: 'number',
223
+ view: "'month' | 'week' | 'day'",
224
+ start: "string 'yyyy-MM-dd' (görünen ilk gün)",
225
+ end: "string 'yyyy-MM-dd' (görünen son gün)"
226
+ },
227
+ reads: "self'e yazılmaz — event.data ile oku; oklar, yatay kaydırma ve Bugün ile her görünümde"
228
+ },
229
+ viewChange: {
230
+ payload: { view: "'month' | 'week' | 'day'", start: "string 'yyyy-MM-dd'", end: "string 'yyyy-MM-dd'" },
231
+ reads: "self'e yazılmaz — event.data ile oku; görünüm düğmesiyle"
232
+ }
233
+ },
234
+ 'floating-container': {
235
+ mount: NO_DATA
236
+ },
237
+ repeater: {
238
+ swipe: {
239
+ payload: {
240
+ key: 'string (kaydırma eyleminin key alanı)',
241
+ index: 'number (satırın sırası, 0 tabanlı)',
242
+ row: 'object (satırın verisi)'
243
+ }
244
+ },
245
+ itemPress: {
246
+ payload: {
247
+ index: 'number (satırın sırası, 0 tabanlı)',
248
+ row: 'object (satırın verisi)'
249
+ }
250
+ },
251
+ selectionChange: {
252
+ payload: {
253
+ selectedIndices: 'number[] — seçili satırların rows içindeki DÜZ indeksleri, sıralı',
254
+ count: 'number — seçili satır sayısı'
255
+ },
256
+ reads: 'yalnız selectionMode: multiple (ya da longPressSelects ile açılınca); akış event.data.selectedIndices / event.data.count okur, düğümün selectedIndices/selectedItems değeri de yazılır'
257
+ }
258
+ }
259
+ };
@@ -0,0 +1,396 @@
1
+ export const PACKAGE_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/;
2
+ export const SCHEME_PATTERN = /^[a-z][a-z0-9+.-]{2,}$/;
3
+ export const VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
4
+ export const LOCALE_PATTERN = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})?$/;
5
+ export const COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
6
+ export const PERMISSION_KEY_PATTERN = /^[a-z][A-Za-z0-9]*$/;
7
+ export const RESERVED_SCHEME = 'zeyrek';
8
+ export const MAX_BUILD_NUMBER = 2_100_000_000;
9
+ export const DISPLAY_NAME_MAX_LENGTH = 30;
10
+ export const PERMISSION_TEXT_MAX_LENGTH = 500;
11
+ export const KEYWORDS_MAX_LENGTH = 100;
12
+ export const COPYRIGHT_MAX_LENGTH = 200;
13
+ export const ORIENTATIONS = ['portrait', 'landscape', 'default'];
14
+ export const USER_INTERFACE_STYLES = ['automatic', 'light', 'dark'];
15
+ export const SPLASH_RESIZE_MODES = ['contain', 'cover'];
16
+ export const RELEASE_STATUSES = ['draft', 'completed'];
17
+ /** İznin mağaza derlemesindeki kipi (kök ADR 0010): kullanıma göre · her zaman · hiç. Anahtarları backend doğrular. */
18
+ export const PERMISSION_MODES = ['auto', 'on', 'off'];
19
+ /** Reklam ayarı (kök ADR 0012) — backend `MobileStoreSettingsValidator` / `MobileAdFormat` aynası. */
20
+ export const AD_APP_ID_PATTERN = /^ca-app-pub-\d{16}~\d{10}$/;
21
+ export const AD_UNIT_ID_PATTERN = /^ca-app-pub-\d{16}\/\d{10}$/;
22
+ export const AD_UNIT_NAME_PATTERN = /^[a-z][A-Za-z0-9]{0,39}$/;
23
+ export const AD_UNITS_MAX_COUNT = 40;
24
+ export const AD_FORMATS = ['banner', 'interstitial', 'rewarded', 'rewardedInterstitial', 'appOpen'];
25
+ /** Reklam açıkken taşınması gereken izin: iOS ATT (backend `MobileStoreSettings.AdsPermission`). */
26
+ export const ADS_PERMISSION = 'tracking';
27
+ export const LISTING_LIMITS = {
28
+ title: 30,
29
+ subtitle: 30,
30
+ shortDescription: 80,
31
+ description: 4000,
32
+ promoText: 170,
33
+ releaseNotes: 4000
34
+ };
35
+ export const URL_FIELDS = ['support', 'marketing', 'privacyPolicy', 'privacyChoices'];
36
+ /** İlk derlemenin kilitlediği alanlar (`MobileStoreSettings.FieldsLockedByFirstBuild`) */
37
+ export const LOCKABLE_FIELDS = ['bundleIdentifier', 'androidPackage', 'scheme', 'iosBuildNumber', 'androidVersionCode'];
38
+ export const JAVA_KEYWORDS = new Set([
39
+ 'abstract', 'assert', 'boolean', 'break', 'byte', 'case', 'catch', 'char', 'class', 'const', 'continue', 'default',
40
+ 'do', 'double', 'else', 'enum', 'extends', 'final', 'finally', 'float', 'for', 'goto', 'if', 'implements', 'import',
41
+ 'instanceof', 'int', 'interface', 'long', 'native', 'new', 'package', 'private', 'protected', 'public', 'return',
42
+ 'short', 'static', 'strictfp', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', 'transient', 'try',
43
+ 'void', 'volatile', 'while', 'true', 'false', 'null'
44
+ ]);
45
+ /**
46
+ * Mağaza görseli yuvası (`mobile-store/{appId}/assets`) — kaynak backend `MobileStoreAssetKind.cs` ve
47
+ * `MobileStoreAssetValidator.cs`; Studio kopyası `store-assets.validation.ts`. Parite:
48
+ * `test/mobile-store-assets.test.ts` (plans/mobil-yayin-mcp g-01).
49
+ */
50
+ export const ASSET_KINDS = ['icon', 'adaptiveForeground', 'adaptiveBackground', 'adaptiveMonochrome', 'splash', 'featureGraphic', 'screenshot', 'promo'];
51
+ /** Derlemeye giren görseller dilsizdir (`MobileStoreAssetValidator.BuildKinds`); PNG zorunlu */
52
+ export const BUILD_ASSET_KINDS = ['icon', 'adaptiveForeground', 'adaptiveBackground', 'adaptiveMonochrome', 'splash'];
53
+ export const DEVICE_CLASSES = ['iphone69', 'ipad13', 'phone', 'tablet7', 'tablet10'];
54
+ export const MAX_ASSET_BYTES = 10_485_760;
55
+ export const MAX_IMAGES_PER_SLOT = 10;
56
+ /**
57
+ * Cihaz sınıfı → `page_screenshot` `device` argümanı. Çıktı = width·scale × height·scale ve mağazanın
58
+ * kabul ettiği ölçüdür; Studio "Önizlemeden üret" aynı tabloyu kullanır (`screenshot-capture.ts`
59
+ * `CAPTURE_TARGETS`). iPhone 6.9" ve iPad 13" alfa kanalı kabul etmez: kare JPEG çekilir.
60
+ */
61
+ export const SCREENSHOT_CAPTURE_DEVICES = {
62
+ iphone69: { width: 440, height: 956, scale: 3 },
63
+ ipad13: { width: 1032, height: 1376, scale: 2 },
64
+ phone: { width: 360, height: 720, scale: 3 },
65
+ tablet7: { width: 600, height: 960, scale: 2 },
66
+ tablet10: { width: 800, height: 1280, scale: 2 }
67
+ };
68
+ /** Beyan anahtarları (`MobileStoreSettings.*Declaration`); iOS appPrivacy, ötekiler Android */
69
+ export const DECLARATION_KEYS = ['appPrivacy', 'dataSafety', 'contentRating', 'targetAudience', 'appAccess'];
70
+ /**
71
+ * Yuva kuralının istek gitmeden denetlenen kısmı (`MobileStoreAssetValidator`): derleme görseli dil
72
+ * almaz, ekran görüntüsü cihaz sınıfı ister, öteki türler cihaz sınıfı almaz. Ölçü/biçim/alfa
73
+ * denetimi dosyanın içeriğine bakar ve backend'de kalır (422 gövdesi ajana olduğu gibi döner).
74
+ */
75
+ export function assetSlotProblem(kind, locale, deviceClass) {
76
+ if (BUILD_ASSET_KINDS.includes(kind) && locale !== undefined) {
77
+ return `'${kind}' derlemeye giren görseldir ve dilsizdir — locale verilmez`;
78
+ }
79
+ if (kind === 'screenshot' && deviceClass === undefined) {
80
+ return `'screenshot' cihaz sınıfı ister — deviceClass: ${DEVICE_CLASSES.join(' | ')}`;
81
+ }
82
+ if (kind !== 'screenshot' && deviceClass !== undefined) {
83
+ return `deviceClass yalnız 'screenshot' içindir — '${kind}' için verilmez`;
84
+ }
85
+ if (locale !== undefined && !LOCALE_PATTERN.test(locale)) {
86
+ return `locale '${locale}' biçimsiz — ${LOCALE_PATTERN.source} ('tr', 'en-US')`;
87
+ }
88
+ return undefined;
89
+ }
90
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
91
+ const text = (value) => typeof value === 'string' ? value : '';
92
+ const isBlank = (value) => text(value).trim().length === 0;
93
+ /** Backend `EmailAddress()` (ASP.NET Core uyumlu): tek `@`, başta ve sonda değil */
94
+ const isEmail = (value) => {
95
+ const index = value.indexOf('@');
96
+ return index > 0 && index !== value.length - 1 && index === value.lastIndexOf('@');
97
+ };
98
+ /** .NET `Uri.TryCreate(Absolute)` + `https` + host; `https:host` (çift eğik çizgisiz) reddedilir */
99
+ const isHttpsUrl = (value) => {
100
+ if (!/^https:\/\//i.test(value.trim()))
101
+ return false;
102
+ try {
103
+ const url = new URL(value);
104
+ return url.protocol === 'https:' && url.hostname.length > 0;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ };
110
+ /** Biçim hataları: backend `PUT`'u 422 ile reddedecek her alan (alan yolu = backend `errorFields` anahtarı). */
111
+ export function storeSettingsFormatErrors(settings) {
112
+ const errors = [];
113
+ const add = (field, message) => errors.push({ field, message });
114
+ const displayName = settings.displayName;
115
+ if (isBlank(displayName))
116
+ add('displayName', 'boş olamaz');
117
+ else if (text(displayName).length > DISPLAY_NAME_MAX_LENGTH)
118
+ add('displayName', `en çok ${DISPLAY_NAME_MAX_LENGTH} karakter`);
119
+ for (const field of ['bundleIdentifier', 'androidPackage']) {
120
+ const value = text(settings[field]);
121
+ if (isBlank(value))
122
+ add(field, 'boş olamaz');
123
+ else if (!PACKAGE_NAME_PATTERN.test(value))
124
+ add(field, `ters alan adı biçiminde olmalı (ör. com.firma.uygulama): ${PACKAGE_NAME_PATTERN.source}`);
125
+ }
126
+ const keyword = text(settings.androidPackage).split('.').find(segment => JAVA_KEYWORDS.has(segment));
127
+ if (keyword)
128
+ add('androidPackage', `'${keyword}' segmenti Java anahtar sözcüğü — Android paket adında kullanılamaz`);
129
+ const scheme = text(settings.scheme);
130
+ if (isBlank(scheme))
131
+ add('scheme', 'boş olamaz');
132
+ else if (!SCHEME_PATTERN.test(scheme))
133
+ add('scheme', `küçük harfle başlar, en az 3 karakter, yalnız a-z 0-9 + . -: ${SCHEME_PATTERN.source}`);
134
+ else if (scheme === RESERVED_SCHEME)
135
+ add('scheme', `'${RESERVED_SCHEME}' oynatıcı uygulamanın şeması — mağaza derlemesi alamaz`);
136
+ const version = text(settings.version);
137
+ if (isBlank(version))
138
+ add('version', 'boş olamaz');
139
+ else if (!VERSION_PATTERN.test(version))
140
+ add('version', 'MAJOR.MINOR.PATCH biçiminde olmalı, öndeki sıfır yok (ör. 1.2.0)');
141
+ for (const field of ['iosBuildNumber', 'androidVersionCode']) {
142
+ const value = settings[field];
143
+ if (value !== undefined && value !== null && (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value > MAX_BUILD_NUMBER)) {
144
+ add(field, `0 … ${MAX_BUILD_NUMBER} arası tam sayı`);
145
+ }
146
+ }
147
+ const choices = [
148
+ ['orientation', ORIENTATIONS],
149
+ ['userInterfaceStyle', USER_INTERFACE_STYLES],
150
+ ['splashResizeMode', SPLASH_RESIZE_MODES]
151
+ ];
152
+ for (const [field, allowed] of choices) {
153
+ if (!allowed.includes(text(settings[field])))
154
+ add(field, `kabul edilen değerler: ${allowed.join(' | ')}`);
155
+ }
156
+ for (const field of ['adaptiveIconBackground', 'splashBackgroundColor']) {
157
+ if (!COLOR_PATTERN.test(text(settings[field])))
158
+ add(field, '#RRGGBB biçiminde renk olmalı');
159
+ }
160
+ for (const [key, value] of Object.entries(isObject(settings.permissions) ? settings.permissions : {})) {
161
+ if (!PERMISSION_KEY_PATTERN.test(key))
162
+ add(`permissions.${key}`, `modül anahtarı ${PERMISSION_KEY_PATTERN.source} biçiminde olmalı (ör. camera)`);
163
+ else if (value !== null && (isBlank(value) || text(value).length > PERMISSION_TEXT_MAX_LENGTH))
164
+ add(`permissions.${key}`, `null ya da 1–${PERMISSION_TEXT_MAX_LENGTH} karakter izin açıklaması`);
165
+ }
166
+ for (const [key, value] of Object.entries(isObject(settings.permissionModes) ? settings.permissionModes : {})) {
167
+ if (!PERMISSION_KEY_PATTERN.test(key))
168
+ add(`permissionModes.${key}`, `izin anahtarı ${PERMISSION_KEY_PATTERN.source} biçiminde olmalı; anahtarlar: mobile_store_get { detail: ['permissions'] }`);
169
+ else if (!PERMISSION_MODES.includes(text(value)))
170
+ add(`permissionModes.${key}`, `kabul edilen değerler: ${PERMISSION_MODES.join(' | ')}`);
171
+ }
172
+ const launchPermissions = Array.isArray(settings.launchPermissions) ? settings.launchPermissions : [];
173
+ for (const [index, key] of launchPermissions.entries()) {
174
+ if (!PERMISSION_KEY_PATTERN.test(text(key)))
175
+ add(`launchPermissions.${String(key)}`, `izin anahtarı ${PERMISSION_KEY_PATTERN.source} biçiminde olmalı; anahtarlar: mobile_store_get { detail: ['permissions'] }`);
176
+ else if (launchPermissions.indexOf(key) !== index)
177
+ add(`launchPermissions.${String(key)}`, 'anahtar iki kez yazılmış');
178
+ else if (isObject(settings.permissionModes) && settings.permissionModes[text(key)] === 'off')
179
+ add(`launchPermissions.${String(key)}`, `permissionModes.${String(key)} 'off': derlemeye dahil edilmeyen izin açılışta sorulamaz — kipi auto ya da on yapın ya da anahtarı listeden çıkarın`);
180
+ }
181
+ for (const [locale, listing] of Object.entries(isObject(settings.listing) ? settings.listing : {})) {
182
+ if (!LOCALE_PATTERN.test(locale)) {
183
+ add(`listing.${locale}`, "dil kodu geçersiz (ör. 'tr', 'en-US')");
184
+ continue;
185
+ }
186
+ const entry = isObject(listing) ? listing : {};
187
+ for (const [field, max] of Object.entries(LISTING_LIMITS)) {
188
+ if (text(entry[field]).length > max)
189
+ add(`listing.${locale}.${field}`, `en çok ${max} karakter (${text(entry[field]).length} verildi)`);
190
+ }
191
+ const keywords = Array.isArray(entry.keywords) ? entry.keywords.map(text).join(',') : '';
192
+ if (keywords.length > KEYWORDS_MAX_LENGTH)
193
+ add(`listing.${locale}.keywords`, `virgülle birleşik en çok ${KEYWORDS_MAX_LENGTH} karakter (${keywords.length} verildi)`);
194
+ }
195
+ const urls = isObject(settings.urls) ? settings.urls : {};
196
+ for (const field of URL_FIELDS) {
197
+ if (!isBlank(urls[field]) && !isHttpsUrl(text(urls[field])))
198
+ add(`urls.${field}`, 'boş ya da mutlak https:// adres olmalı');
199
+ }
200
+ const play = isObject(settings.play) ? settings.play : {};
201
+ const review = isObject(settings.review) ? settings.review : {};
202
+ const apple = isObject(settings.apple) ? settings.apple : {};
203
+ if (!isBlank(play.contactEmail) && !isEmail(text(play.contactEmail)))
204
+ add('play.contactEmail', 'geçerli e-posta olmalı');
205
+ if (play.releaseStatus !== undefined && play.releaseStatus !== null && !RELEASE_STATUSES.includes(text(play.releaseStatus)))
206
+ add('play.releaseStatus', `kabul edilen değerler: ${RELEASE_STATUSES.join(' | ')}`);
207
+ if (!isBlank(review.email) && !isEmail(text(review.email)))
208
+ add('review.email', 'geçerli e-posta olmalı');
209
+ if (text(apple.copyright).length > COPYRIGHT_MAX_LENGTH)
210
+ add('apple.copyright', `en çok ${COPYRIGHT_MAX_LENGTH} karakter`);
211
+ const ads = isObject(settings.ads) ? settings.ads : {};
212
+ const units = (Array.isArray(ads.units) ? ads.units : []).filter(isObject);
213
+ const appIdExample = 'ca-app-pub-0000000000000000~0000000000';
214
+ const unitIdExample = 'ca-app-pub-0000000000000000/0000000000';
215
+ for (const field of ['iosAppId', 'androidAppId']) {
216
+ if (!isBlank(ads[field]) && !AD_APP_ID_PATTERN.test(text(ads[field])))
217
+ add(`ads.${field}`, `AdMob App ID biçiminde olmalı (${appIdExample}; birim kimliğindeki '/' değil '~')`);
218
+ }
219
+ if (ads.enabled === true && isBlank(ads.iosAppId) && isBlank(ads.androidAppId))
220
+ add('ads.enabled', 'reklam açıkken en az bir platformun AdMob App ID\'si gerekir (ads.iosAppId / ads.androidAppId)');
221
+ if (units.length > AD_UNITS_MAX_COUNT)
222
+ add('ads.units', `en çok ${AD_UNITS_MAX_COUNT} yerleşim`);
223
+ const invalidNames = [...new Set(units.map(unit => text(unit.name)).filter(name => !AD_UNIT_NAME_PATTERN.test(name) || units.filter(unit => text(unit.name) === name).length > 1))];
224
+ for (const name of invalidNames)
225
+ add(`ads.units.${name}`, `yerleşim adı ${AD_UNIT_NAME_PATTERN.source} biçiminde ve tekil olmalı (adBanner.unit ve ad-show.value bu adla anar)`);
226
+ for (const unit of units.filter(entry => !invalidNames.includes(text(entry.name)))) {
227
+ const name = text(unit.name);
228
+ if (!AD_FORMATS.includes(text(unit.format)))
229
+ add(`ads.units.${name}.format`, `kabul edilen biçimler: ${AD_FORMATS.join(' | ')}`);
230
+ for (const platform of ['ios', 'android']) {
231
+ if (!isBlank(unit[platform]) && !AD_UNIT_ID_PATTERN.test(text(unit[platform])))
232
+ add(`ads.units.${name}.${platform}`, `AdMob reklam birimi kimliği biçiminde olmalı (${unitIdExample})`);
233
+ }
234
+ if (isBlank(unit.ios) && isBlank(unit.android))
235
+ add(`ads.units.${name}`, 'en az bir platformun reklam birimi kimliği gerekir (ios / android)');
236
+ }
237
+ if (!isBlank(ads.appOpenUnit) && text(units.find(unit => text(unit.name) === text(ads.appOpenUnit))?.format) !== 'appOpen')
238
+ add('ads.appOpenUnit', `'${text(ads.appOpenUnit)}' appOpen biçiminde tanımlı bir yerleşim değil`);
239
+ if (ads.enabled === true && isObject(settings.permissionModes) && settings.permissionModes[ADS_PERMISSION] === 'off')
240
+ add(`permissionModes.${ADS_PERMISSION}`, "reklam açıkken izleme izni (tracking) kapatılamaz — iOS'ta kişiselleştirilmiş reklam ATT ister; kipi auto ya da on yapın");
241
+ return errors;
242
+ }
243
+ /**
244
+ * Mağazaya YÜKLEME (testflight/internal) için boş kalan gizli olmayan alanlar — `build` hedefini
245
+ * etkilemez, taslak kaydı engellemez. Kesin liste ön kontroldür (P-01, varsayılan dil projeden gelir).
246
+ */
247
+ export function storeSettingsMissingForUpload(settings) {
248
+ const urls = isObject(settings.urls) ? settings.urls : {};
249
+ const apple = isObject(settings.apple) ? settings.apple : {};
250
+ const categories = isObject(apple.categories) ? apple.categories : {};
251
+ const play = isObject(settings.play) ? settings.play : {};
252
+ const listings = Object.values(isObject(settings.listing) ? settings.listing : {}).filter(isObject);
253
+ const missing = [];
254
+ if (!listings.some(listing => !isBlank(listing.title) && !isBlank(listing.description)))
255
+ missing.push('listing.<dil>.title + description');
256
+ if (isBlank(urls.privacyPolicy))
257
+ missing.push('urls.privacyPolicy');
258
+ if (isBlank(categories.primary))
259
+ missing.push('apple.categories.primary (iOS)');
260
+ if (isBlank(apple.copyright))
261
+ missing.push('apple.copyright (iOS)');
262
+ if (isBlank(play.contactEmail))
263
+ missing.push('play.contactEmail (Android)');
264
+ return missing;
265
+ }
266
+ /**
267
+ * Backend `PUT` tam satırı yazar: gönderilmeyen alan varsayılana döner (`displayName` boşalır → 422,
268
+ * `listing` silinir). Yama bu yüzden okunan kaydın üstüne birleştirilir: düz nesneler alan alan,
269
+ * dizi ve skaler değer yerine geçer. Sayaçlar yalnız yamada varsa gider — okunan sayacı geri yazmak,
270
+ * arada derleme başlamışsa kilit ihlali (422) üretirdi (`null` = değiştirme).
271
+ */
272
+ export function mergeStoreSettings(current, patch) {
273
+ const merge = (base, overlay) => {
274
+ if (!isObject(base) || !isObject(overlay))
275
+ return overlay;
276
+ return Object.fromEntries([...new Set([...Object.keys(base), ...Object.keys(overlay)])]
277
+ .map(key => [key, key in overlay ? merge(base[key], overlay[key]) : base[key]]));
278
+ };
279
+ const merged = merge(current, patch);
280
+ for (const counter of ['iosBuildNumber', 'androidVersionCode']) {
281
+ if (!(counter in patch))
282
+ delete merged[counter];
283
+ }
284
+ return merged;
285
+ }
286
+ /** Yamanın gerçekten değiştirdiği yaprak alan yolları (`listing.tr.title`, `bundleIdentifier`). */
287
+ export function changedStoreFields(current, patch, prefix = '') {
288
+ return Object.entries(patch).flatMap(([key, value]) => {
289
+ const before = current[key];
290
+ const path = `${prefix}${key}`;
291
+ if (isObject(value) && isObject(before))
292
+ return changedStoreFields(before, value, `${path}.`);
293
+ return JSON.stringify(value) === JSON.stringify(before ?? null) ? [] : [path];
294
+ });
295
+ }
296
+ const guide = "platform_guide('mobile-publish')";
297
+ /**
298
+ * MOB-023 — taşınan küme derlemede donar: kullanılan izin mağazadaki son derlemede (backend'in
299
+ * `missingFromStoreBuilds`'i) yoksa kurulu uygulama onu isteyemez. Production kullanıyorsa bugünün sorunudur;
300
+ * yalnız okunan ortam kullanıyorsa dağıtımdan sonra olacaktır. Kip `off` ise sessiz: yeni derleme de taşımaz
301
+ * (o çelişkiyi P-12 söyler). Kullanılmayan iznin derlemede olmaması budamanın işidir.
302
+ */
303
+ export function permissionStoreBuildWarnings(permissions, environment) {
304
+ return permissions.flatMap((entry) => {
305
+ const builds = (Array.isArray(entry.missingFromStoreBuilds) ? entry.missingFromStoreBuilds : []).filter(isObject);
306
+ if (builds.length === 0 || entry.mode === 'off')
307
+ return [];
308
+ const names = builds.map(build => `${text(build.platform)} ${text(build.appVersion)} (${String(build.buildNumber)})`).join(', ');
309
+ const productionUsage = isObject(entry.build) && typeof entry.build.usageCount === 'number' ? entry.build.usageCount : 0;
310
+ const usage = Array.isArray(entry.usage) ? entry.usage.length : 0;
311
+ if (productionUsage > 0)
312
+ return [`${text(entry.key)}: production kullanıyor ama mağazadaki son derleme taşımıyor (${names}) — kurulu uygulamada izin istenemez ('unavailable'); yeni bir mağaza derlemesi gerekir (MOB-023, ${guide}).`];
313
+ return usage > 0
314
+ ? [`${text(entry.key)}: ${environment} kullanıyor, mağazadaki son derleme taşımıyor (${names}) — production'a dağıtıldıktan sonra yeni bir mağaza derlemesi gerekir (MOB-023, ${guide}).`]
315
+ : [];
316
+ });
317
+ }
318
+ /**
319
+ * MOB-025 — açılışta sorma kabuğun ayrı bir yeteneğidir (`shell.carriesLaunchPermissions`). Yayındaki kabuk onu
320
+ * taşımıyorsa ayar saklanır ama derleme sormaz: liste manifeste yazılmaz. Eski backend alanı söylemez; o zaman
321
+ * iddia edilmez.
322
+ */
323
+ export function permissionLaunchShellWarnings(permissions, shell) {
324
+ const asked = permissions.filter(entry => entry.askOnLaunch === true).map(entry => text(entry.key));
325
+ if (asked.length === 0 || !isObject(shell) || shell.carriesLaunchPermissions !== false)
326
+ return [];
327
+ return [`${asked.join(', ')}: açılışta sorulacak diye ayarlı ama yayındaki kabuk paketi (${text(shell.version)}) açılışta izin sormuyor — ayar saklanır, mağaza derlemesi sormaz (ön kontrol P-12 uyarır). Daha yeni bir kabuk yayınlanana kadar izni bir akış adımından isteyin (MOB-025, ${guide}).`];
328
+ }
329
+ /**
330
+ * MOB-027 — izleme izni (kök ADR 0011): mağaza derlemesi `tracking`'i taşıyorsa App Store Connect App Privacy'de
331
+ * "Device ID — Used to Track You" ve Play Data safety'de "Advertising ID" beyanı insanın işidir; Apple izin penceresinden
332
+ * önce teşvik ya da ön pencereyi yasaklar (5.1.2). Taşınmayan izin için söylenecek bir şey yoktur.
333
+ */
334
+ export function trackingDeclarationWarnings(permissions) {
335
+ const tracking = permissions.find(entry => entry.key === ADS_PERMISSION);
336
+ return isObject(tracking?.build) && tracking.build.carried === true
337
+ ? [`${ADS_PERMISSION}: mağaza derlemesi izleme iznini (ATT) taşıyor — App Store Connect App Privacy'de "Device ID — Used to Track You", Play Console Data safety'de "Advertising ID" beyanı insanın işidir; izin penceresinden önce ödül/teşvik ya da kendi ön pencerenizi göstermeyin (App Store 5.1.2). Sonuçtaki advertisingId yalnız granted iken doludur (MOB-027, ${guide}).`]
338
+ : [];
339
+ }
340
+ /**
341
+ * MOB-026 — reklam açıkken ajanın bilmesi gereken, kayıttan okunabilen durumlar (kök ADR 0012). Satırlar `<konu>: …`
342
+ * biçimindedir. Test kipi mağaza yüklemesinde de test reklamı gösterir (ön kontrol P-13 `manual`); yerleşimsiz reklam
343
+ * SDK'yı bağlar ama hiçbir şey göstermez; App ID'si verilmiş platformda birimi olmayan yerleşim o platformda yer kaplamaz
344
+ * (`adBanner`) ya da `invalid` döner (`ad-show`). Beyan satırı her zaman gelir: App Privacy, Data safety ve AdMob rıza
345
+ * mesajı konsolda insanın işidir, gateway yazamaz.
346
+ */
347
+ export function adsStoreWarnings(settings) {
348
+ const ads = isObject(settings.ads) ? settings.ads : {};
349
+ if (ads.enabled !== true)
350
+ return [];
351
+ const units = (Array.isArray(ads.units) ? ads.units : []).filter(isObject);
352
+ const platforms = [['ios', 'iOS', ads.iosAppId], ['android', 'Android', ads.androidAppId]];
353
+ const lines = [];
354
+ if (ads.testMode !== false)
355
+ lines.push(`testMode: test kipi açık — mağaza yüklemesinde de gerçek reklam yerine Google test reklamı gösterilir; yayından önce ads.testMode: false (ön kontrol P-13, MOB-026, ${guide}).`);
356
+ if (units.length === 0)
357
+ lines.push(`yerleşim: reklam açık ama hiç yerleşim yok — SDK bağlanır, hiçbir reklam gösterilmez; ads.units'e banner ya da tam ekran yerleşim ekleyin (MOB-026, ${guide}).`);
358
+ for (const unit of units) {
359
+ const missing = platforms.filter(([field, , appId]) => !isBlank(appId) && isBlank(unit[field])).map(([, label]) => label);
360
+ if (missing.length > 0)
361
+ lines.push(`${text(unit.name)}: ${missing.join(' ve ')}'de birim yok — o platformda adBanner yer kaplamaz, ad-show 'invalid' döner (MOB-026).`);
362
+ }
363
+ lines.push(`beyan: reklam izleme sayılır — App Store Connect App Privacy'de "Device ID — Used to Track You" (ve reklam verisi), Play Console Data safety'de "Advertising ID", AdMob konsolunda rıza (GDPR/UMP) mesajı insanın işidir; gateway yazamaz. Beyanı kullanıcı onaylayınca mobile_store_update { declarations } (MOB-026, ${guide}).`);
364
+ return lines;
365
+ }
366
+ /** MOB-022 — `definition` birleşmiş kayıttır; biçim hatası backend'in 422'siyle aynı ret (block), eksik alan uyarı. */
367
+ export const mobileStorePolicyRules = [
368
+ {
369
+ id: 'MOB-022',
370
+ module: 'mobileStore',
371
+ appliesTo: ['update'],
372
+ sourceRef: 'MobileStoreSettingsValidator (zeyrek-backend/docs/mobile-builds.md §Doğrulama tablosu); yükleme eksikleri MobileStorePreflightService P-01',
373
+ check: (definition) => {
374
+ const errors = storeSettingsFormatErrors(definition);
375
+ const missing = storeSettingsMissingForUpload(definition);
376
+ return [
377
+ ...(errors.length > 0
378
+ ? [{
379
+ ruleId: 'MOB-022',
380
+ severity: 'block',
381
+ message: `Mağaza ayarı biçimsiz — backend PUT'u 422 ile reddeder, hiçbir alan yazılmaz: ${errors.map(error => `${error.field}: ${error.message}`).join(' · ')}`,
382
+ guide
383
+ }]
384
+ : []),
385
+ ...(missing.length > 0
386
+ ? [{
387
+ ruleId: 'MOB-022',
388
+ severity: 'warn',
389
+ message: `Mağazaya yükleme (testflight/internal) için eksik: ${missing.join(', ')}. Kayıt taslak olarak yazılır ve 'build' hedefini etkilemez; kesin liste mobile_build_start { dryRun: true } ön kontrolündedir. Görseller mobile_asset_create ile, beyanlar kullanıcının onayıyla mobile_store_update { declarations } ile yazılır; gizli bilgi MCP'den yazılmaz — Studio › Yayın › Hesaplar.`,
390
+ guide
391
+ }]
392
+ : [])
393
+ ];
394
+ }
395
+ }
396
+ ];