@appaflytech/zeyrek-ai-mcp 0.0.21 → 0.0.22
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/config.js +2 -0
- package/dist/knowledge/componentCatalog.js +3 -3
- package/dist/knowledge/componentEvents.js +9 -2
- package/dist/knowledge/mobileComponentCatalog.js +543 -0
- package/dist/knowledge/mobileComponentEvents.js +98 -0
- package/dist/knowledge/normalize.js +52 -11
- package/dist/knowledge/platform/lint.js +205 -0
- package/dist/knowledge/platform/mobile.js +78 -0
- package/dist/knowledge/platform/mobileRules.js +123 -0
- package/dist/knowledge/platform/mobileTabs.js +108 -0
- package/dist/knowledge/platform/profile.js +20 -0
- package/dist/knowledge/platform/resolve.js +122 -0
- package/dist/knowledge/platform/web.js +38 -0
- package/dist/knowledge/rules.js +6 -8
- package/dist/knowledge/styleResolve.js +1 -1
- package/dist/knowledge/styleResolveNative.js +410 -0
- package/dist/knowledge/themeResolve.js +3 -3
- package/dist/platform/capture/actions.js +333 -0
- package/dist/platform/capture/browser.js +1 -0
- package/dist/platform/{screenshot.js → capture/capture.js} +23 -509
- package/dist/platform/capture/index.js +10 -0
- package/dist/platform/capture/readiness.js +103 -0
- package/dist/platform/capture/summary.js +77 -0
- package/dist/platform/capture/target.js +5 -0
- package/dist/platform/capture/targets/studio-mobile.js +15 -0
- package/dist/platform/capture/targets/studio-web.js +5 -0
- package/dist/platform/domSummary.js +1 -1
- package/dist/resources/examples/mobile-app-shell-drawer.json +209 -0
- package/dist/resources/examples/mobile-app-shell-stack.json +123 -0
- package/dist/resources/examples/mobile-app-shell-tab.json +174 -0
- package/dist/resources/examples/mobile-list-detail-form.json +656 -0
- package/dist/resources/guides/component.md +6 -1
- package/dist/resources/guides/functions.md +1 -1
- package/dist/resources/guides/layout.md +11 -7
- package/dist/resources/guides/mobile.md +264 -0
- package/dist/resources/guides/page.md +55 -15
- package/dist/resources/guides/translation.md +1 -1
- package/dist/server.js +2 -0
- package/dist/tools/applications.js +60 -3
- package/dist/tools/components.js +50 -10
- package/dist/tools/designSchemaPatch.js +13 -4
- package/dist/tools/externalComponents.js +25 -4
- package/dist/tools/flows.js +9 -2
- package/dist/tools/layouts.js +41 -9
- package/dist/tools/mobile.js +203 -0
- package/dist/tools/pages.js +115 -35
- package/dist/tools/platformInfo.js +2 -1
- package/dist/tools/profileWrite.js +45 -0
- package/dist/tools/screenWrite.js +2 -2
- package/dist/tools/screens.js +9 -3
- package/package.json +1 -1
package/dist/tools/components.js
CHANGED
|
@@ -1,25 +1,40 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
-
import { normalizeComponentDefaults, componentDefaultsNote, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, withNormalization } from '../knowledge/normalize.js';
|
|
2
|
+
import { normalizeComponentDefaults, componentDefaultsNote, clampBlockSizes, blockSizeNote, normalizeHeightTypes, heightTypeNote, normalizeSizeStrings, sizeStringNote, normalizeDroppableStates, withNormalization } from '../knowledge/normalize.js';
|
|
3
3
|
import { eventContractsFor, eventPayloadNote } from '../knowledge/componentEvents.js';
|
|
4
4
|
import { componentPresetDefaults, readProjectTheme } from '../knowledge/themeSchema.js';
|
|
5
5
|
import { knowledgeFor, knowledgeNote, APPEARANCE_TOKEN_SETS, STATE_FLAG_NOTES, INTERNAL_ROLES_BY_TYPE } from '../knowledge/componentCatalog.js';
|
|
6
|
+
import { MOBILE_STATE_FLAG_NOTES, mobileComponentKnowledge } from '../knowledge/mobileComponentCatalog.js';
|
|
7
|
+
import { mobileComponentEventContracts } from '../knowledge/mobileComponentEvents.js';
|
|
6
8
|
import { componentUpdateBody, fetchHost, schemaParam, unwrap } from './screens.js';
|
|
7
9
|
import { PlatformApiError } from '../platform/errors.js';
|
|
8
10
|
import { handle } from '../platform/responses.js';
|
|
11
|
+
import { applicationTypeField, resolveProfile } from '../knowledge/platform/resolve.js';
|
|
12
|
+
import { mobileComponentNotes } from '../knowledge/platform/mobileRules.js';
|
|
9
13
|
import { z } from 'zod';
|
|
10
|
-
const typeParam = z.enum(['web', 'mobile']).optional().describe('Hedef platform;
|
|
14
|
+
const typeParam = z.enum(['web', 'mobile']).optional().describe('Hedef platform (ComponentType); verilmezse uygulamanın tipi — mobil uygulamada mobile');
|
|
11
15
|
const kindParam = z.enum(['component', 'widget']).optional()
|
|
12
16
|
.describe("component: sayfaya konan tekil eleman. widget: kendi şeması olan kapsayıcı (container/modal/drawer)");
|
|
13
17
|
const widgetTypeParam = z.enum(['container', 'modal', 'drawer']).optional()
|
|
14
18
|
.describe("kind:'widget' iken zorunlu");
|
|
19
|
+
/**
|
|
20
|
+
* Uç `type` verilmezse WEB kataloğunu döner (`GetComponentsQuery` `Type ?? Web`); mobil uygulamanın
|
|
21
|
+
* paleti, doğrulayıcısı ve widget'ları `type=mobile` satırlarıdır. Profil mobil derse sorgu tipi
|
|
22
|
+
* taşır; web'de sorgu profil öncesiyle aynıdır (G1 kilidi).
|
|
23
|
+
*/
|
|
15
24
|
async function listComponents(platform, project, environment, applicationId, scope) {
|
|
16
25
|
const path = scope === 'all' ? `/components/${applicationId}/all` : `/components/${applicationId}`;
|
|
26
|
+
const catalogType = scope === 'all' ? 'web' : (await resolveProfile(platform, applicationId)).profile.catalogType;
|
|
17
27
|
const response = await platform.tenant('GET', project, path, {
|
|
18
28
|
environment,
|
|
19
|
-
query: scope === 'all' ? undefined : { pageIndex: 0, pageLength: 999 }
|
|
29
|
+
query: scope === 'all' ? undefined : { pageIndex: 0, pageLength: 999, ...(catalogType === 'mobile' ? { type: 'mobile' } : {}) }
|
|
20
30
|
});
|
|
21
31
|
return response?.data ?? [];
|
|
22
32
|
}
|
|
33
|
+
/** Uygulamanın kayıtlı bileşen slug'ları (mobil profilin MOB-003 denetimi). */
|
|
34
|
+
export async function fetchCatalogSlugs(platform, project, environment, applicationId) {
|
|
35
|
+
const components = await listComponents(platform, project, environment, applicationId);
|
|
36
|
+
return new Set(components.map(component => String(component.slug ?? '')).filter(slug => slug.length > 0));
|
|
37
|
+
}
|
|
23
38
|
/**
|
|
24
39
|
* Yerleşim normalizasyonlarının ikisi de aynı katalog listesinden beslenir; tek okumada
|
|
25
40
|
* çıkarılır ki yazma yolları fazladan istek atmasın. Uygulamanın kendi sözlüğü okunur
|
|
@@ -38,12 +53,17 @@ export async function fetchComponentLayoutRules(platform, project, environment,
|
|
|
38
53
|
export function componentLayoutRules(components) {
|
|
39
54
|
const maxSizes = new Map();
|
|
40
55
|
const autoHeights = new Map();
|
|
56
|
+
const droppable = new Map();
|
|
41
57
|
for (const component of components) {
|
|
42
58
|
const slug = typeof component.slug === 'string' ? component.slug : undefined;
|
|
43
59
|
if (!slug)
|
|
44
60
|
continue;
|
|
45
61
|
if (typeof component.isDefaultAutoHeight === 'boolean')
|
|
46
62
|
autoHeights.set(slug, component.isDefaultAutoHeight);
|
|
63
|
+
// Kap bilgisi sözlükten gelir; düğümün `states.isDroppable`ı onu izler (Studio paleti de
|
|
64
|
+
// aynı alanı katalogdan yazar). `true` gören tip kap sayılır.
|
|
65
|
+
if (typeof component.isDroppable === 'boolean')
|
|
66
|
+
droppable.set(slug, component.isDroppable);
|
|
47
67
|
const blocks = (component.definition ?? {}).blocks;
|
|
48
68
|
if (!Array.isArray(blocks))
|
|
49
69
|
continue;
|
|
@@ -58,7 +78,7 @@ export function componentLayoutRules(components) {
|
|
|
58
78
|
});
|
|
59
79
|
}
|
|
60
80
|
}
|
|
61
|
-
return { maxSizes, autoHeights };
|
|
81
|
+
return { maxSizes, autoHeights, droppable };
|
|
62
82
|
}
|
|
63
83
|
/**
|
|
64
84
|
* Katalog okuması başarısız olsa da yazma isteği gitmeli, ama iki normalizasyonun düşüş davranışı
|
|
@@ -69,14 +89,21 @@ export function componentLayoutRules(components) {
|
|
|
69
89
|
* varsayılanını (`fixed`, `DesignComponentManagementSchema.cs:24`) kabul etmek demektir; en fazla
|
|
70
90
|
* sekiz tipte (auth-layout/html/image/layout/qrcode/table/text-editor/video) fazladan yükseklik
|
|
71
91
|
* üretir, atlamak ise her tipte içerik kırpar.
|
|
92
|
+
* - `states.isDroppable` atlanır — katalog okunamadığında hangi tipin kap olduğu bilinmez ve
|
|
93
|
+
* uydurulmaz; düğüm bugünkü hâliyle yazılır.
|
|
72
94
|
*/
|
|
73
95
|
export async function applyCatalogLayoutRules(platform, project, environment, applicationId, nodes) {
|
|
74
96
|
try {
|
|
75
97
|
const rules = await fetchComponentLayoutRules(platform, project, environment, applicationId);
|
|
76
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
clamped: clampBlockSizes(nodes, rules.maxSizes),
|
|
100
|
+
heights: normalizeHeightTypes(nodes, rules.autoHeights),
|
|
101
|
+
sizes: normalizeSizeStrings(nodes),
|
|
102
|
+
states: normalizeDroppableStates(nodes, rules.droppable)
|
|
103
|
+
};
|
|
77
104
|
}
|
|
78
105
|
catch {
|
|
79
|
-
return { clamped: [], heights: normalizeHeightTypes(nodes, new Map()), sizes: normalizeSizeStrings(nodes) };
|
|
106
|
+
return { clamped: [], heights: normalizeHeightTypes(nodes, new Map()), sizes: normalizeSizeStrings(nodes), states: [] };
|
|
80
107
|
}
|
|
81
108
|
}
|
|
82
109
|
/**
|
|
@@ -116,6 +143,7 @@ export function registerComponentTools(server, platform) {
|
|
|
116
143
|
}, handle(config, async ({ project, environment, applicationId, scope, includeHidden }) => {
|
|
117
144
|
const components = await listComponents(platform, project, environment, applicationId, scope);
|
|
118
145
|
return {
|
|
146
|
+
...applicationTypeField(await resolveProfile(platform, applicationId)),
|
|
119
147
|
components: components
|
|
120
148
|
.filter(component => includeHidden === true || component.isHide !== true)
|
|
121
149
|
.map(component => ({
|
|
@@ -161,6 +189,15 @@ export function registerComponentTools(server, platform) {
|
|
|
161
189
|
: [];
|
|
162
190
|
const componentSlug = typeof component.slug === 'string' ? component.slug : undefined;
|
|
163
191
|
const knowledge = knowledgeFor(componentSlug);
|
|
192
|
+
// Mobil uygulamada kayıt mobil kataloğun satırıdır (`listComponents` type=mobile); davranış
|
|
193
|
+
// notları web runtime'ından gelir (`knowledge` — mobil şema web sınıfını genişletir), bileşenin
|
|
194
|
+
// mobil farkları `mobileKnowledge`'ta, bütün mobil bileşenlere geçen notlar `mobileNotes`'ta (G4).
|
|
195
|
+
// Web ikizi olmayan bileşende (`app-bar`, `tooltip`…) bilgi yalnız `mobileKnowledge`'tadır; olay
|
|
196
|
+
// yükleri de önce mobil sözlükten okunur (`mobileComponentEvents.ts`).
|
|
197
|
+
const mobile = (await resolveProfile(platform, applicationId)).profile.platform === 'mobile';
|
|
198
|
+
const mobileKnowledge = mobile && componentSlug !== undefined && Object.hasOwn(mobileComponentKnowledge, componentSlug)
|
|
199
|
+
? mobileComponentKnowledge[componentSlug]
|
|
200
|
+
: undefined;
|
|
164
201
|
// S-3 — `defaults` HAM katalogdur; Studio'da bırakılan düğüm ise temanın bileşen preseti
|
|
165
202
|
// kaplanmış katalogdan kurulur (`withThemeComponentDefaults`). Tema okunamazsa (uç düşer,
|
|
166
203
|
// yetki yoksa) alan yanıta HİÇ girmez: uydurulmuş bir varsayılan yanlış bilgidir.
|
|
@@ -177,7 +214,7 @@ export function registerComponentTools(server, platform) {
|
|
|
177
214
|
isDroppable: component.isDroppable,
|
|
178
215
|
props: definition.props,
|
|
179
216
|
defaults: definition.defaults,
|
|
180
|
-
events: eventContractsFor(componentSlug, eventNames),
|
|
217
|
+
events: eventContractsFor(componentSlug, eventNames, mobile ? mobileComponentEventContracts : undefined),
|
|
181
218
|
eventPayloadNote,
|
|
182
219
|
...(knowledge
|
|
183
220
|
? {
|
|
@@ -188,7 +225,7 @@ export function registerComponentTools(server, platform) {
|
|
|
188
225
|
},
|
|
189
226
|
...(knowledge.guide ? { guide: knowledge.guide } : {})
|
|
190
227
|
}
|
|
191
|
-
: { knowledgeNote }),
|
|
228
|
+
: mobileKnowledge ? {} : { knowledgeNote }),
|
|
192
229
|
...(componentSlug && INTERNAL_ROLES_BY_TYPE[componentSlug]
|
|
193
230
|
? {
|
|
194
231
|
internalRoles: INTERNAL_ROLES_BY_TYPE[componentSlug],
|
|
@@ -196,8 +233,9 @@ export function registerComponentTools(server, platform) {
|
|
|
196
233
|
}
|
|
197
234
|
: {}),
|
|
198
235
|
...(themeDefaults ? { themeDefaults } : {}),
|
|
236
|
+
...(mobile ? { platform: 'mobile', ...(mobileKnowledge ? { mobileKnowledge } : {}), mobileNotes: mobileComponentNotes(componentSlug) } : {}),
|
|
199
237
|
appearanceValues: APPEARANCE_TOKEN_SETS,
|
|
200
|
-
stateFlags: STATE_FLAG_NOTES,
|
|
238
|
+
stateFlags: mobile ? { ...STATE_FLAG_NOTES, ...MOBILE_STATE_FLAG_NOTES } : STATE_FLAG_NOTES,
|
|
201
239
|
validations: definition.validations,
|
|
202
240
|
sizes: definition.blocks
|
|
203
241
|
};
|
|
@@ -234,11 +272,13 @@ export function registerComponentTools(server, platform) {
|
|
|
234
272
|
const catalog = (body.schema ?? []).length > 0
|
|
235
273
|
? await applyCatalogLayoutRules(platform, project, environment, applicationId, body.schema)
|
|
236
274
|
: { clamped: [], heights: [], sizes: [] };
|
|
275
|
+
// Mobil uygulamanın widget'ı mobil katalog satırıdır; tip verilmezse uygulamanın tipi.
|
|
276
|
+
const defaultType = body.type ?? (await resolveProfile(platform, applicationId)).profile.catalogType;
|
|
237
277
|
return platform.tenant('POST', project, `/components/${applicationId}`, {
|
|
238
278
|
environment,
|
|
239
279
|
body: {
|
|
240
280
|
kind: 'component',
|
|
241
|
-
type:
|
|
281
|
+
type: defaultType,
|
|
242
282
|
categories: [],
|
|
243
283
|
definition: {},
|
|
244
284
|
schema: [],
|
|
@@ -8,6 +8,8 @@ import { fetchComponentEventNames } from './components.js';
|
|
|
8
8
|
import { PlatformApiError } from '../platform/errors.js';
|
|
9
9
|
import { handle } from '../platform/responses.js';
|
|
10
10
|
import { slotWarning } from './layouts.js';
|
|
11
|
+
import { applicationTypeField, resolveProfile } from '../knowledge/platform/resolve.js';
|
|
12
|
+
import { boundFlowSteps, screenProfileWarnings } from './profileWrite.js';
|
|
11
13
|
import { z } from 'zod';
|
|
12
14
|
const HOSTS = {
|
|
13
15
|
layout: {
|
|
@@ -101,12 +103,18 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
101
103
|
const nested = simulatable ? nestedRepeaterNodes(simulated) : [];
|
|
102
104
|
if (nested.length > 0)
|
|
103
105
|
throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
|
|
106
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
104
107
|
const reshaped = simulatable || opRecords.some(entry => entry.op === 'remove_component');
|
|
108
|
+
const profileWarnings = await screenProfileWarnings(platform, resolved, project, environment, applicationId, {
|
|
109
|
+
kind: spec.kind,
|
|
110
|
+
ops: [...opRecords, ...propTypeOps],
|
|
111
|
+
steps: await boundFlowSteps(platform, resolved, project, environment, host, applicationId, id, opRecords)
|
|
112
|
+
});
|
|
105
113
|
const layoutWarnings = reshaped
|
|
106
114
|
? [
|
|
107
|
-
...verticalGapWarnings(simulated, spec.codes.gap, touched),
|
|
115
|
+
...(resolved.profile.layout.blocksMeaningful ? verticalGapWarnings(simulated, spec.codes.gap, touched) : []),
|
|
108
116
|
...stringStyleWarning(stringStyleNodes(simulated)),
|
|
109
|
-
...inertFlexPropWarning(inertFlexPropNodes(simulated)),
|
|
117
|
+
...(resolved.profile.layout.modes.has('grid') ? inertFlexPropWarning(inertFlexPropNodes(simulated)) : []),
|
|
110
118
|
...flexSizeWarnings(simulated, spec.codes.flexMismatch, spec.codes.flexInvalid),
|
|
111
119
|
...(host === 'layout' ? slotWarning(simulated) : [])
|
|
112
120
|
]
|
|
@@ -133,8 +141,9 @@ export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
|
133
141
|
response.deletedFlows = deletedFlows;
|
|
134
142
|
if (flowWarnings.length > 0)
|
|
135
143
|
response.flowWarnings = flowWarnings;
|
|
136
|
-
if (layoutWarnings.length > 0)
|
|
137
|
-
response.layoutWarnings = layoutWarnings;
|
|
144
|
+
if (layoutWarnings.length > 0 || profileWarnings.length > 0)
|
|
145
|
+
response.layoutWarnings = [...layoutWarnings, ...profileWarnings];
|
|
146
|
+
Object.assign(response, applicationTypeField(resolved));
|
|
138
147
|
const contentOps = opRecords.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
|
|
139
148
|
let opDrops = emptyWriteDiff();
|
|
140
149
|
if (contentOps.length > 0) {
|
|
@@ -7,6 +7,23 @@ import { z } from 'zod';
|
|
|
7
7
|
import { evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
|
|
8
8
|
import { policyRules } from '../knowledge/rules.js';
|
|
9
9
|
import { EXTERNAL_COMPONENT_PROTOCOL_VERSION } from '../knowledge/backendConstraints.js';
|
|
10
|
+
import { resolveProfile } from '../knowledge/platform/resolve.js';
|
|
11
|
+
import { findingLine } from '../knowledge/platform/profile.js';
|
|
12
|
+
/**
|
|
13
|
+
* MOB-006 — harici bileşen protokolü v1 tarayıcıda koşan `.mjs` custom element'tir; mobil
|
|
14
|
+
* uygulamanın native çizeri onu yükleyemez (mobil protokol v2 Faz 3). Profil kapalı derse çağrı
|
|
15
|
+
* platforma GİTMEZ: yüklenen paket hiçbir ekranda çizilmeyecekti.
|
|
16
|
+
*/
|
|
17
|
+
async function assertExternalComponentsAllowed(platform, applicationId, tool) {
|
|
18
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
19
|
+
if (resolved.profile.externalComponents)
|
|
20
|
+
return;
|
|
21
|
+
throw new PlatformApiError(`${tool}: ${findingLine({
|
|
22
|
+
code: 'MOB-006',
|
|
23
|
+
severity: 'warn',
|
|
24
|
+
message: "harici bileşen mobil uygulamada desteklenmiyor — protokol v1 tarayıcıda koşan .mjs custom element'tir, native çizer onu yükleyemez (mobil protokol v2 Faz 3). Çağrı platforma gönderilmedi; mobil ekranı katalog bileşenleriyle kurun (component_list)."
|
|
25
|
+
})}`, 400);
|
|
26
|
+
}
|
|
10
27
|
/** Platformun köprü protokolü; scaffold ve publish bu sürüme göre üretir/doğrular (backend ExternalComponentRules.CurrentProtocolVersion) */
|
|
11
28
|
const PROTOCOL_VERSION = EXTERNAL_COMPONENT_PROTOCOL_VERSION;
|
|
12
29
|
/** Backend `ExternalComponentRules.MaxBundleSizeBytes` ile aynı sınır */
|
|
@@ -199,6 +216,7 @@ export function registerExternalComponentTools(server, platform) {
|
|
|
199
216
|
},
|
|
200
217
|
annotations: writes
|
|
201
218
|
}, handle(config, async ({ project, environment, applicationId, bundlePath, manifestPath, version }) => {
|
|
219
|
+
await assertExternalComponentsAllowed(platform, applicationId, 'external_component_publish');
|
|
202
220
|
const manifestFile = resolve(manifestPath);
|
|
203
221
|
const bundleFile = resolve(bundlePath);
|
|
204
222
|
let manifest;
|
|
@@ -262,10 +280,13 @@ export function registerExternalComponentTools(server, platform) {
|
|
|
262
280
|
acknowledgeWarnings: z.boolean().optional().describe('Doğrulama uyarılarını okuyup kabul ettiğinizi bildirir; uyarısı olan sürümlerde zorunlu')
|
|
263
281
|
},
|
|
264
282
|
annotations: updates
|
|
265
|
-
}, handle(config, ({ project, environment, applicationId, slug, version, acknowledgeWarnings }) =>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
283
|
+
}, handle(config, async ({ project, environment, applicationId, slug, version, acknowledgeWarnings }) => {
|
|
284
|
+
await assertExternalComponentsAllowed(platform, applicationId, 'external_component_activate');
|
|
285
|
+
return platform.tenant('POST', project, `/components/${applicationId}/external/${slug}/activate`, {
|
|
286
|
+
environment,
|
|
287
|
+
body: { version, acknowledgeWarnings: acknowledgeWarnings === true }
|
|
288
|
+
});
|
|
289
|
+
}));
|
|
269
290
|
server.registerTool('external_component_versions', {
|
|
270
291
|
title: 'Paket sürümlerini listele',
|
|
271
292
|
description: 'Bir harici komponentin yayınlanmış paket sürümlerini, hangisinin aktif olduğunu ve her sürümün doğrulama raporunu listeler. Geri alınacak sürümü buradan seçin.',
|
package/dist/tools/flows.js
CHANGED
|
@@ -13,6 +13,7 @@ import { policyRules, flowExpressionSites } from '../knowledge/rules.js';
|
|
|
13
13
|
import { functionCallRefs } from '../knowledge/functionGuards.js';
|
|
14
14
|
import { existingMatch, ifExistsParam, platformRename, withRename } from '../platform/ifExists.js';
|
|
15
15
|
import { randomUUID } from 'node:crypto';
|
|
16
|
+
import { flowProfileWarnings } from './profileWrite.js';
|
|
16
17
|
import { z } from 'zod';
|
|
17
18
|
/**
|
|
18
19
|
* Akış yazması yanıtında gövdeyi geri döndürmüyor, bu yüzden diff için akışı bir kez okuruz.
|
|
@@ -382,6 +383,7 @@ export function registerFlowTools(server, platform) {
|
|
|
382
383
|
// Diff YALNIZ çağıranın yazdığı alanları kapsar; `id` ve barındırıcı bağı (`pageId` vb.)
|
|
383
384
|
// gövdeye gateway koyar, kullanıcının düzeltebileceği bir şey değildir.
|
|
384
385
|
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, writtenFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps }, ifExists === 'duplicate');
|
|
386
|
+
const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
|
|
385
387
|
return attachFindings(withWriteDiff(withRename(withNormalization({
|
|
386
388
|
host,
|
|
387
389
|
hostId,
|
|
@@ -393,6 +395,7 @@ export function registerFlowTools(server, platform) {
|
|
|
393
395
|
steps: steps.length
|
|
394
396
|
},
|
|
395
397
|
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
398
|
+
...(platformWarnings.length > 0 ? { platformWarnings } : {}),
|
|
396
399
|
hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]; olayda zaten akış bağlıysa ikinciyi add_event ile ekleyin (set_event bağlı akışı değiştirir, PAGE-034). Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
|
|
397
400
|
}, normalized), drops.renamed), drops), findings);
|
|
398
401
|
}));
|
|
@@ -446,7 +449,9 @@ export function registerFlowTools(server, platform) {
|
|
|
446
449
|
...(steps === undefined ? {} : { steps }),
|
|
447
450
|
...(errorSteps === undefined ? {} : { errorSteps })
|
|
448
451
|
});
|
|
449
|
-
|
|
452
|
+
const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
|
|
453
|
+
return attachFindings(withWriteDiff(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
454
|
+
...(platformWarnings.length > 0 ? { platformWarnings } : {}) }, normalized), drops), findings);
|
|
450
455
|
}));
|
|
451
456
|
server.registerTool('flow_patch', {
|
|
452
457
|
title: "Akışa patch uygula",
|
|
@@ -495,6 +500,7 @@ export function registerFlowTools(server, platform) {
|
|
|
495
500
|
});
|
|
496
501
|
const written = patchResult(saved, flow);
|
|
497
502
|
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, { steps: flow.steps, errorSteps: flow.errorSteps });
|
|
503
|
+
const platformWarnings = await flowProfileWarnings(platform, applicationId, [...(flow.steps ?? []), ...(flow.errorSteps ?? [])]);
|
|
498
504
|
return attachFindings(withWriteDiff(withNormalization({
|
|
499
505
|
host,
|
|
500
506
|
hostId,
|
|
@@ -502,7 +508,8 @@ export function registerFlowTools(server, platform) {
|
|
|
502
508
|
newVersion: written.newVersion,
|
|
503
509
|
patchTargets: resolved.targets,
|
|
504
510
|
flow: flowSummary(written.data),
|
|
505
|
-
...(parameterWarnings.length > 0 ? { parameterWarnings } : {})
|
|
511
|
+
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
512
|
+
...(platformWarnings.length > 0 ? { platformWarnings } : {})
|
|
506
513
|
}, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), drops), findings);
|
|
507
514
|
}));
|
|
508
515
|
server.registerTool('flow_delete', {
|
package/dist/tools/layouts.js
CHANGED
|
@@ -9,6 +9,8 @@ import { applyPropTypesToSchema, isSetPropTypeOp, setPropTypeErrors } from '../k
|
|
|
9
9
|
import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
|
|
10
10
|
import { PlatformApiError } from '../platform/errors.js';
|
|
11
11
|
import { handle } from '../platform/responses.js';
|
|
12
|
+
import { applicationTypeField, resolveProfile } from '../knowledge/platform/resolve.js';
|
|
13
|
+
import { screenProfileWarnings } from './profileWrite.js';
|
|
12
14
|
import { z } from 'zod';
|
|
13
15
|
const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
|
|
14
16
|
.describe("JSON Patch listesi (RFC 6902): { op, path, value }. Yol seçici alabilir — '/views/0/children[elementId=baslik]/props/content/data'.");
|
|
@@ -46,7 +48,13 @@ export function registerLayoutTools(server, platform) {
|
|
|
46
48
|
description: "Layout'u komponent ağacı (views), widget'ları, akışları ve olaylarıyla getirir.",
|
|
47
49
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
|
|
48
50
|
annotations: readOnly
|
|
49
|
-
}, handle(config, ({ project, environment, applicationId, id }) =>
|
|
51
|
+
}, handle(config, async ({ project, environment, applicationId, id }) => {
|
|
52
|
+
const response = await platform.tenant('GET', project, `/layouts/${applicationId}/${id}`, { environment });
|
|
53
|
+
const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
|
|
54
|
+
return response && typeof response === 'object' && !Array.isArray(response) && Object.keys(typeField).length > 0
|
|
55
|
+
? { ...response, ...typeField }
|
|
56
|
+
: response;
|
|
57
|
+
}));
|
|
50
58
|
server.registerTool('layout_schema_get', {
|
|
51
59
|
title: "Layout komponent ağacını oku",
|
|
52
60
|
description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Ağaçta hedefli değişiklik için layout_schema_patch (sayfa ile aynı micro-op sözlüğü, PATCH /layouts/{app}/{id}/schema). Ad/parametre/olay alanlarında ya da düz JSON Patch yolu gerektiğinde layout_patch — o araç buradaki updatedAt'i expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update.",
|
|
@@ -61,20 +69,21 @@ export function registerLayoutTools(server, platform) {
|
|
|
61
69
|
annotations: readOnly
|
|
62
70
|
}, handle(config, async ({ project, environment, applicationId, id, mode, componentId }) => {
|
|
63
71
|
const layout = await fetchHost(platform, 'layout', project, environment, applicationId, id);
|
|
72
|
+
const typeField = applicationTypeField(await resolveProfile(platform, applicationId));
|
|
64
73
|
const audit = layout.audit;
|
|
65
74
|
const updatedAt = audit?.updatedAt ?? audit?.createdAt;
|
|
66
75
|
const views = (layout.views ?? []);
|
|
67
76
|
if (mode === 'full')
|
|
68
|
-
return { updatedAt, views };
|
|
77
|
+
return { updatedAt, ...typeField, views };
|
|
69
78
|
if (mode === 'subtree') {
|
|
70
79
|
if (!componentId)
|
|
71
80
|
throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
|
|
72
81
|
const subtree = findNode(views, componentId);
|
|
73
82
|
if (!subtree)
|
|
74
83
|
throw new PlatformApiError(`Layout'ta '${componentId}' id'li bir komponent yok; layout_schema_get'i summary kipinde çağırıp hedefi doğrulayın.`);
|
|
75
|
-
return { updatedAt, views: subtree };
|
|
84
|
+
return { updatedAt, ...typeField, views: subtree };
|
|
76
85
|
}
|
|
77
|
-
return { updatedAt, views: views.map(node => summarize(node)) };
|
|
86
|
+
return { updatedAt, ...typeField, views: views.map(node => summarize(node)) };
|
|
78
87
|
}));
|
|
79
88
|
server.registerTool('layout_create', {
|
|
80
89
|
title: 'Layout oluştur',
|
|
@@ -91,18 +100,27 @@ export function registerLayoutTools(server, platform) {
|
|
|
91
100
|
annotations: writes
|
|
92
101
|
}, handle(config, async ({ project, environment, applicationId, name, views, events, parameters }) => {
|
|
93
102
|
assertScreenWriteGuards('layout', { schema: views, events });
|
|
103
|
+
const resolved = await resolveProfile(platform, applicationId);
|
|
94
104
|
const fixed = normalizeComponentDefaults(views);
|
|
95
105
|
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, views);
|
|
96
|
-
const gaps = [
|
|
106
|
+
const gaps = [
|
|
107
|
+
...(resolved.profile.layout.blocksMeaningful ? verticalGapWarnings(views ?? [], 'LAY-003 (uyarı)') : []),
|
|
108
|
+
...flexSizeWarnings(views ?? [], 'LAY-007 (uyarı)', 'LAY-008 (uyarı)'),
|
|
109
|
+
...spacingLayerWarnings(views ?? [], 'LAY-010 (uyarı)', 'LAY-011 (uyarı)'),
|
|
110
|
+
...await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'layout', nodes: views ?? [] })
|
|
111
|
+
];
|
|
97
112
|
const response = await platform.tenant('POST', project, `/layouts/${applicationId}`, {
|
|
98
113
|
environment,
|
|
99
114
|
body: { name, views: views ?? [], events: events ?? [], parameters: parameters ?? null }
|
|
100
115
|
});
|
|
101
116
|
const drops = writeDiff({ name, views: views ?? [], events: events ?? [] }, response);
|
|
102
117
|
const normalized = withWriteDiff(withNormalization(response, [...componentDefaultsNote(fixed), ...catalogNotes, ...slotWarning(views)]), drops);
|
|
103
|
-
|
|
118
|
+
const typeField = applicationTypeField(resolved);
|
|
119
|
+
if (!normalized || typeof normalized !== 'object')
|
|
104
120
|
return normalized;
|
|
105
|
-
|
|
121
|
+
if (gaps.length === 0)
|
|
122
|
+
return Object.keys(typeField).length === 0 ? normalized : { ...normalized, ...typeField };
|
|
123
|
+
return { ...normalized, ...typeField, layoutWarnings: gaps };
|
|
106
124
|
}));
|
|
107
125
|
server.registerTool('layout_update', {
|
|
108
126
|
title: 'Layout güncelle',
|
|
@@ -126,7 +144,15 @@ export function registerLayoutTools(server, platform) {
|
|
|
126
144
|
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, overrides.views);
|
|
127
145
|
const body = layoutUpdateBody({ ...current, applicationId }, overrides);
|
|
128
146
|
await platform.tenant('PUT', project, `/layouts/${applicationId}/${id}`, { environment, body });
|
|
129
|
-
const
|
|
147
|
+
const resolved = overrides.views === undefined ? undefined : await resolveProfile(platform, applicationId);
|
|
148
|
+
const gaps = overrides.views === undefined || !resolved
|
|
149
|
+
? []
|
|
150
|
+
: [
|
|
151
|
+
...(resolved.profile.layout.blocksMeaningful ? verticalGapWarnings(body.views, 'LAY-003 (uyarı)') : []),
|
|
152
|
+
...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)'),
|
|
153
|
+
...spacingLayerWarnings(body.views, 'LAY-010 (uyarı)', 'LAY-011 (uyarı)'),
|
|
154
|
+
...await screenProfileWarnings(platform, resolved, project, environment, applicationId, { kind: 'layout', nodes: body.views })
|
|
155
|
+
];
|
|
130
156
|
const summary = { id, changed: Object.keys(overrides), preserved: { viewNodes: body.views.length } };
|
|
131
157
|
if (gaps.length > 0)
|
|
132
158
|
summary.layoutWarnings = gaps;
|
|
@@ -193,7 +219,13 @@ export function registerLayoutTools(server, platform) {
|
|
|
193
219
|
body: { baseVersion: baseVersionOf(document, 'layout_get'), ops: netOps }
|
|
194
220
|
});
|
|
195
221
|
const written = patchResult(saved, finalDocument);
|
|
196
|
-
const
|
|
222
|
+
const profile = await resolveProfile(platform, applicationId);
|
|
223
|
+
const gaps = [
|
|
224
|
+
...(profile.profile.layout.blocksMeaningful ? verticalGapWarnings(body.views, 'LAY-003 (uyarı)') : []),
|
|
225
|
+
...flexSizeWarnings(body.views, 'LAY-007 (uyarı)', 'LAY-008 (uyarı)'),
|
|
226
|
+
...spacingLayerWarnings(body.views, 'LAY-010 (uyarı)', 'LAY-011 (uyarı)'),
|
|
227
|
+
...await screenProfileWarnings(platform, profile, project, environment, applicationId, { kind: 'layout', nodes: body.views })
|
|
228
|
+
];
|
|
197
229
|
const summary = {
|
|
198
230
|
id,
|
|
199
231
|
applied: patchOps.length,
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { environmentParam, guid, projectParam, writes } from './common.js';
|
|
3
|
+
import { fetchHost, pageUpdateBody, unwrap } from './screens.js';
|
|
4
|
+
import { fetchCatalogSlugs } from './components.js';
|
|
5
|
+
import { mobileTabsWarnings } from './applications.js';
|
|
6
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
7
|
+
import { handle } from '../platform/responses.js';
|
|
8
|
+
import { ensurePageContainer } from '../knowledge/normalize.js';
|
|
9
|
+
import { mobileProfile } from '../knowledge/platform/mobile.js';
|
|
10
|
+
import { MAX_MOBILE_TABS } from '../knowledge/platform/mobileTabs.js';
|
|
11
|
+
import { forgetApplication } from '../knowledge/platform/resolve.js';
|
|
12
|
+
import { readKnowledgeFile } from '../resources/knowledge.js';
|
|
13
|
+
/** Ana sayfa: açıkça verilen, yoksa ilk sekme, yoksa ilk sayfa. */
|
|
14
|
+
function homepageSlug(input) {
|
|
15
|
+
return input.homepage ?? input.tabs?.[0]?.pageSlug ?? input.pages[0].pageSlug;
|
|
16
|
+
}
|
|
17
|
+
export function mobileAppInputErrors(input) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
const slugs = input.pages.map(page => page.pageSlug);
|
|
20
|
+
const repeated = slugs.filter((slug, index) => slugs.indexOf(slug) !== index);
|
|
21
|
+
if (input.pages.length === 0)
|
|
22
|
+
errors.push('pages en az bir sayfa ister');
|
|
23
|
+
if (repeated.length > 0)
|
|
24
|
+
errors.push(`pages içinde tekrar eden slug var: ${[...new Set(repeated)].join(', ')}`);
|
|
25
|
+
if (input.tabs !== undefined) {
|
|
26
|
+
const tabSlugs = input.tabs.map(tab => tab.pageSlug);
|
|
27
|
+
if (input.tabs.length === 0)
|
|
28
|
+
errors.push("tabs verildiyse en az bir öğe taşır; sekme istemiyorsan alanı hiç gönderme (yığın)");
|
|
29
|
+
if (input.tabs.length > MAX_MOBILE_TABS)
|
|
30
|
+
errors.push(`tabs en fazla ${MAX_MOBILE_TABS} öğe taşır (${input.tabs.length} verildi) — fazlası yığına açılan sayfa olarak kalmalı`);
|
|
31
|
+
for (const slug of tabSlugs.filter(slug => !slugs.includes(slug)))
|
|
32
|
+
errors.push(`tabs '${slug}' pages içinde yok — sekme yalnız kurulan bir sayfayı gösterebilir`);
|
|
33
|
+
if (tabSlugs.some((slug, index) => tabSlugs.indexOf(slug) !== index))
|
|
34
|
+
errors.push('tabs içinde tekrar eden sayfa var');
|
|
35
|
+
}
|
|
36
|
+
if (input.homepage !== undefined && !slugs.includes(input.homepage))
|
|
37
|
+
errors.push(`homepage '${input.homepage}' pages içinde yok`);
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
40
|
+
export function buildMobileAppPlan(input) {
|
|
41
|
+
const home = homepageSlug(input);
|
|
42
|
+
const others = input.pages.filter(page => page.pageSlug !== home);
|
|
43
|
+
return [
|
|
44
|
+
{ step: 'application_create', tool: 'application_create', summary: "type:'mobile' uygulama; backend ana sayfayı kendisi açar" },
|
|
45
|
+
{ step: 'layout_create', tool: 'layout_create', summary: `kabuk: app-bar + slot${input.drawer ? ' + drawer widget (app-bar.menuTarget)' : ''}` },
|
|
46
|
+
...others.map((page, index) => ({ step: `page_create#${index + 1}`, tool: 'page_create', summary: `'${page.pageSlug}' (${page.pageName}), layoutId ile` })),
|
|
47
|
+
{ step: 'page_update#homepage', tool: 'page_update', summary: `backend'in ana sayfası → '${home}' (ad, slug, layoutId)` },
|
|
48
|
+
...(input.tabs ? [{ step: 'application_update', tool: 'application_update', summary: `properties.mobile.tabs: ${input.tabs.map(tab => tab.pageSlug).join(' · ')}` }] : [])
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
/** Kabuk ağacı örnek dosyasından okunur — örnek ile aracın kurduğu layout tek kaynaktır. */
|
|
52
|
+
function shellViews(input) {
|
|
53
|
+
const example = JSON.parse(readKnowledgeFile('examples', input.drawer ? 'mobile-app-shell-drawer.json' : 'mobile-app-shell-stack.json'));
|
|
54
|
+
const views = structuredClone(example.layoutCreate.views);
|
|
55
|
+
const appBar = views[0]?.children?.find(node => node.type === 'app-bar');
|
|
56
|
+
if (appBar && input.title)
|
|
57
|
+
appBar.props.title = { type: 'static', ref: null, data: input.title };
|
|
58
|
+
return views;
|
|
59
|
+
}
|
|
60
|
+
const SHELL_TYPES = ['app-bar', 'slot'];
|
|
61
|
+
export function registerMobileTools(server, platform) {
|
|
62
|
+
const config = platform.config;
|
|
63
|
+
server.registerTool('mobile_app_create', {
|
|
64
|
+
title: 'Mobil uygulamayı kabuğuyla kur',
|
|
65
|
+
description: "Mobil uygulamayı (type:'mobile'), kabuk layout'unu (app-bar + slot; drawer:true ise çekmece widget'ı), sayfalarını ve isteğe bağlı sekme ayarını (properties.mobile.tabs) TEK çağrıda kurar. Sıra: application_create → layout_create → page_create (sayfalar layoutId ile doğar) → backend'in açtığı ana sayfa seçilen ana sayfaya dönüştürülür (yetim sayfa kalmaz) → application_update (yalnız tabs verildiyse). tabs yoksa gezinme yığındır. dryRun:true planı döner, hiçbir şey yazmaz. Platformda işlem yoktur: hata olursa yanıt completed (kurulanlar), failed (düşen adım) ve remaining (kalanlar) taşır — kalanları tekil araçlarla tamamlayın. Kabuk bileşenleri (app-bar, slot) mobil katalogda yoksa layout atlanır ve uyarı döner. Rehber: platform_guide('mobile').",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
project: projectParam,
|
|
68
|
+
environment: environmentParam,
|
|
69
|
+
projectId: guid,
|
|
70
|
+
slug: z.string().min(1),
|
|
71
|
+
name: z.string().min(1),
|
|
72
|
+
color: z.string().optional(),
|
|
73
|
+
pages: z.array(z.object({ pageSlug: z.string().min(1), pageName: z.string().min(1) })).min(1),
|
|
74
|
+
tabs: z.array(z.object({ pageSlug: z.string().min(1), label: z.string().optional(), icon: z.string().optional() })).optional()
|
|
75
|
+
.describe(`1-${MAX_MOBILE_TABS} sekme; her pageSlug pages içinden. Verilmezse sekme çubuğu yok (yığın). label 't:<anahtar>' ile yazılırsa çeviri sözlüğünden çözülür (platform_guide('mobile') §Çok dil).`),
|
|
76
|
+
drawer: z.boolean().optional().describe("true: layout'a drawer widget'ı + app-bar.menuTarget (çekmece)"),
|
|
77
|
+
title: z.string().optional().describe('app-bar başlığı; boşsa her sayfanın adı yazılır'),
|
|
78
|
+
homepage: z.string().optional().describe('Ana sayfanın pageSlug\'ı; varsayılan ilk sekme, sekme yoksa ilk sayfa'),
|
|
79
|
+
dryRun: z.boolean().optional()
|
|
80
|
+
},
|
|
81
|
+
annotations: writes
|
|
82
|
+
}, handle(config, async ({ project, environment, projectId, slug, name, color, pages, tabs, drawer, title, homepage, dryRun }) => {
|
|
83
|
+
const input = { pages, ...(tabs ? { tabs } : {}), ...(drawer ? { drawer } : {}), ...(homepage ? { homepage } : {}), ...(title ? { title } : {}) };
|
|
84
|
+
const errors = mobileAppInputErrors(input);
|
|
85
|
+
if (errors.length > 0)
|
|
86
|
+
throw new PlatformApiError(`mobile_app_create: ${errors.join(' · ')}. Hiçbir şey yazılmadı.`, 400);
|
|
87
|
+
const plan = buildMobileAppPlan(input);
|
|
88
|
+
if (dryRun)
|
|
89
|
+
return { dryRun: true, plan };
|
|
90
|
+
const completed = [];
|
|
91
|
+
const skipped = new Set();
|
|
92
|
+
const warnings = [];
|
|
93
|
+
const home = homepageSlug(input);
|
|
94
|
+
const pageIds = new Map();
|
|
95
|
+
let applicationId;
|
|
96
|
+
let homepageId;
|
|
97
|
+
let layoutId;
|
|
98
|
+
let current = 'application_create';
|
|
99
|
+
try {
|
|
100
|
+
const created = unwrap(await platform.org('POST', `/applications/${projectId}`, {
|
|
101
|
+
environment,
|
|
102
|
+
body: { slug, name, type: 'mobile', ...(color ? { color } : {}) }
|
|
103
|
+
}));
|
|
104
|
+
applicationId = String(created.id ?? '');
|
|
105
|
+
homepageId = typeof created.homepageId === 'string'
|
|
106
|
+
? created.homepageId
|
|
107
|
+
: String(created.homepage?.id ?? '');
|
|
108
|
+
forgetApplication(platform);
|
|
109
|
+
if (!applicationId || !homepageId)
|
|
110
|
+
throw new PlatformApiError('application_create yanıtı uygulama ya da ana sayfa kimliği taşımıyor — application_get ile kontrol edin.');
|
|
111
|
+
completed.push({ step: 'application_create', id: applicationId });
|
|
112
|
+
current = 'layout_create';
|
|
113
|
+
const catalog = await fetchCatalogSlugs(platform, project, environment, applicationId);
|
|
114
|
+
const missing = SHELL_TYPES.filter(type => !catalog.has(type));
|
|
115
|
+
if (missing.length > 0) {
|
|
116
|
+
skipped.add('layout_create');
|
|
117
|
+
warnings.push(`MOB-003 (uyarı) — kabuk bileşenleri mobil katalogda yok (${missing.join(', ')}): layout KURULMADI, sayfalar layout'suz açıldı. Katalog yazıldıktan sonra kabuğu ngapps://examples/mobile-app-shell-${input.drawer ? 'drawer' : input.tabs ? 'tab' : 'stack'} ile layout_create + page_update(layoutId) olarak kurun.`);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const layout = unwrap(await platform.tenant('POST', project, `/layouts/${applicationId}`, {
|
|
121
|
+
environment,
|
|
122
|
+
body: { name: `${name} kabuğu`, views: shellViews(input), events: [], parameters: null }
|
|
123
|
+
}));
|
|
124
|
+
layoutId = String(layout.id ?? '');
|
|
125
|
+
completed.push({ step: 'layout_create', id: layoutId });
|
|
126
|
+
}
|
|
127
|
+
const others = pages.filter(page => page.pageSlug !== home);
|
|
128
|
+
for (const [index, page] of others.entries()) {
|
|
129
|
+
current = `page_create#${index + 1}`;
|
|
130
|
+
const saved = unwrap(await platform.tenant('POST', project, `/pages/${applicationId}`, {
|
|
131
|
+
environment,
|
|
132
|
+
body: {
|
|
133
|
+
name: page.pageName,
|
|
134
|
+
slug: page.pageSlug,
|
|
135
|
+
browserTitle: '',
|
|
136
|
+
browserTitleKey: '',
|
|
137
|
+
layoutId: layoutId ?? null,
|
|
138
|
+
roleIds: [],
|
|
139
|
+
middlewareIds: [],
|
|
140
|
+
events: [],
|
|
141
|
+
schema: ensurePageContainer([], mobileProfile.layout.rootContainer).schema
|
|
142
|
+
}
|
|
143
|
+
}));
|
|
144
|
+
pageIds.set(page.pageSlug, String(saved.id ?? ''));
|
|
145
|
+
completed.push({ step: current, id: String(saved.id ?? '') });
|
|
146
|
+
}
|
|
147
|
+
current = 'page_update#homepage';
|
|
148
|
+
const homePage = pages.find(page => page.pageSlug === home);
|
|
149
|
+
const existing = await fetchHost(platform, 'page', project, environment, applicationId, homepageId);
|
|
150
|
+
await platform.tenant('PUT', project, `/pages/${applicationId}/${homepageId}`, {
|
|
151
|
+
environment,
|
|
152
|
+
body: pageUpdateBody(existing, { name: homePage.pageName, slug: homePage.pageSlug, layoutId: layoutId ?? null })
|
|
153
|
+
});
|
|
154
|
+
pageIds.set(home, homepageId);
|
|
155
|
+
completed.push({ step: current, id: homepageId });
|
|
156
|
+
if (tabs) {
|
|
157
|
+
current = 'application_update';
|
|
158
|
+
const properties = {
|
|
159
|
+
mobile: {
|
|
160
|
+
tabs: {
|
|
161
|
+
enabled: true,
|
|
162
|
+
style: 'default',
|
|
163
|
+
showLabels: true,
|
|
164
|
+
items: tabs.map(tab => ({ page: pageIds.get(tab.pageSlug), ...(tab.label ? { label: tab.label } : {}), ...(tab.icon ? { icon: tab.icon } : {}) }))
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
warnings.push(...await mobileTabsWarnings(platform, applicationId, platform.resolveProject(project), homepageId, properties));
|
|
169
|
+
await platform.org('PUT', `/applications/${applicationId}`, {
|
|
170
|
+
environment,
|
|
171
|
+
body: { slug, name, homepageId, ...(color ? { color } : {}), properties }
|
|
172
|
+
});
|
|
173
|
+
forgetApplication(platform, applicationId);
|
|
174
|
+
completed.push({ step: current });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (completed.length === 0)
|
|
179
|
+
throw error;
|
|
180
|
+
const done = new Set(completed.map(entry => entry.step));
|
|
181
|
+
return {
|
|
182
|
+
applicationId,
|
|
183
|
+
...(layoutId ? { layoutId } : {}),
|
|
184
|
+
completed,
|
|
185
|
+
failed: { step: current, error: error instanceof Error ? error.message : String(error) },
|
|
186
|
+
remaining: plan.map(step => step.step).filter(step => !done.has(step) && !skipped.has(step) && step !== current),
|
|
187
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
188
|
+
note: 'Kurulum yarıda kaldı: completed kurulanları, remaining eksik adımları söyler. Kalanları tekil araçlarla tamamlayın (page_create, page_update, application_update) — mobile_app_create aynı slug ile tekrar çağrılırsa uygulamayı ikinci kez açmaya çalışır.'
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
applicationId,
|
|
193
|
+
homepageId,
|
|
194
|
+
...(layoutId ? { layoutId } : {}),
|
|
195
|
+
applicationType: 'mobile',
|
|
196
|
+
pages: pages.map(page => ({ slug: page.pageSlug, name: page.pageName, id: pageIds.get(page.pageSlug), ...(page.pageSlug === home ? { homepage: true } : {}) })),
|
|
197
|
+
...(tabs ? { tabs: tabs.map(tab => tab.pageSlug) } : {}),
|
|
198
|
+
completed,
|
|
199
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
200
|
+
next: "Ekranları page_schema_patch ile doldurun; doğrulama page_screenshot (mobilde varsayılan device: 'phone'). platform_guide('mobile')."
|
|
201
|
+
};
|
|
202
|
+
}));
|
|
203
|
+
}
|