@devoplus/one-sdk 0.0.0-stage → 0.2.0-next.117

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 (139) hide show
  1. package/CHANGELOG.md +268 -0
  2. package/README.md +893 -2
  3. package/bin/accessRequest.d.mts +96 -0
  4. package/bin/accessRequest.mjs +340 -0
  5. package/bin/appOps.d.mts +97 -0
  6. package/bin/appOps.mjs +336 -0
  7. package/bin/dataCommand.mjs +131 -0
  8. package/bin/devConfig.mjs +132 -0
  9. package/bin/one.mjs +341 -0
  10. package/bin/session.mjs +239 -0
  11. package/dist/agentInput.d.ts +9 -0
  12. package/dist/agentInput.d.ts.map +1 -0
  13. package/dist/agentInput.js +102 -0
  14. package/dist/agentInput.js.map +1 -0
  15. package/dist/agentTools.d.ts +83 -0
  16. package/dist/agentTools.d.ts.map +1 -0
  17. package/dist/agentTools.js +164 -0
  18. package/dist/agentTools.js.map +1 -0
  19. package/dist/ai.d.ts +67 -0
  20. package/dist/ai.d.ts.map +1 -0
  21. package/dist/ai.js +129 -0
  22. package/dist/ai.js.map +1 -0
  23. package/dist/audit.d.ts +21 -0
  24. package/dist/audit.d.ts.map +1 -0
  25. package/dist/audit.js +58 -0
  26. package/dist/audit.js.map +1 -0
  27. package/dist/backend.d.ts +27 -0
  28. package/dist/backend.d.ts.map +1 -0
  29. package/dist/backend.js +63 -0
  30. package/dist/backend.js.map +1 -0
  31. package/dist/cache.d.ts +20 -0
  32. package/dist/cache.d.ts.map +1 -0
  33. package/dist/cache.js +84 -0
  34. package/dist/cache.js.map +1 -0
  35. package/dist/client.d.ts +147 -0
  36. package/dist/client.d.ts.map +1 -0
  37. package/dist/client.js +469 -0
  38. package/dist/client.js.map +1 -0
  39. package/dist/components.d.ts +235 -0
  40. package/dist/components.d.ts.map +1 -0
  41. package/dist/components.js +1491 -0
  42. package/dist/components.js.map +1 -0
  43. package/dist/context.d.ts +110 -0
  44. package/dist/context.d.ts.map +1 -0
  45. package/dist/context.js +126 -0
  46. package/dist/context.js.map +1 -0
  47. package/dist/data/crud.d.ts +74 -0
  48. package/dist/data/crud.d.ts.map +1 -0
  49. package/dist/data/crud.js +614 -0
  50. package/dist/data/crud.js.map +1 -0
  51. package/dist/data/ddl.d.ts +76 -0
  52. package/dist/data/ddl.d.ts.map +1 -0
  53. package/dist/data/ddl.js +362 -0
  54. package/dist/data/ddl.js.map +1 -0
  55. package/dist/data/fold.d.ts +14 -0
  56. package/dist/data/fold.d.ts.map +1 -0
  57. package/dist/data/fold.js +76 -0
  58. package/dist/data/fold.js.map +1 -0
  59. package/dist/data/model.d.ts +113 -0
  60. package/dist/data/model.d.ts.map +1 -0
  61. package/dist/data/model.js +408 -0
  62. package/dist/data/model.js.map +1 -0
  63. package/dist/data/values.d.ts +29 -0
  64. package/dist/data/values.d.ts.map +1 -0
  65. package/dist/data/values.js +152 -0
  66. package/dist/data/values.js.map +1 -0
  67. package/dist/dexa.d.ts +44 -0
  68. package/dist/dexa.d.ts.map +1 -0
  69. package/dist/dexa.js +74 -0
  70. package/dist/dexa.js.map +1 -0
  71. package/dist/dexaCommand.d.ts +116 -0
  72. package/dist/dexaCommand.d.ts.map +1 -0
  73. package/dist/dexaCommand.js +226 -0
  74. package/dist/dexaCommand.js.map +1 -0
  75. package/dist/export/columns.d.ts +62 -0
  76. package/dist/export/columns.d.ts.map +1 -0
  77. package/dist/export/columns.js +127 -0
  78. package/dist/export/columns.js.map +1 -0
  79. package/dist/export/csv.d.ts +10 -0
  80. package/dist/export/csv.d.ts.map +1 -0
  81. package/dist/export/csv.js +30 -0
  82. package/dist/export/csv.js.map +1 -0
  83. package/dist/export/report.d.ts +19 -0
  84. package/dist/export/report.d.ts.map +1 -0
  85. package/dist/export/report.js +91 -0
  86. package/dist/export/report.js.map +1 -0
  87. package/dist/export/response.d.ts +14 -0
  88. package/dist/export/response.d.ts.map +1 -0
  89. package/dist/export/response.js +70 -0
  90. package/dist/export/response.js.map +1 -0
  91. package/dist/export/xlsx.d.ts +15 -0
  92. package/dist/export/xlsx.d.ts.map +1 -0
  93. package/dist/export/xlsx.js +167 -0
  94. package/dist/export/xlsx.js.map +1 -0
  95. package/dist/export/zip.d.ts +7 -0
  96. package/dist/export/zip.d.ts.map +1 -0
  97. package/dist/export/zip.js +83 -0
  98. package/dist/export/zip.js.map +1 -0
  99. package/dist/files.d.ts +52 -0
  100. package/dist/files.d.ts.map +1 -0
  101. package/dist/files.js +137 -0
  102. package/dist/files.js.map +1 -0
  103. package/dist/index.d.ts +31 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +35 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/jobs.d.ts +57 -0
  108. package/dist/jobs.d.ts.map +1 -0
  109. package/dist/jobs.js +157 -0
  110. package/dist/jobs.js.map +1 -0
  111. package/dist/log.d.ts +22 -0
  112. package/dist/log.d.ts.map +1 -0
  113. package/dist/log.js +107 -0
  114. package/dist/log.js.map +1 -0
  115. package/dist/notify.d.ts +41 -0
  116. package/dist/notify.d.ts.map +1 -0
  117. package/dist/notify.js +11 -0
  118. package/dist/notify.js.map +1 -0
  119. package/dist/org.d.ts +184 -0
  120. package/dist/org.d.ts.map +1 -0
  121. package/dist/org.js +114 -0
  122. package/dist/org.js.map +1 -0
  123. package/dist/people.d.ts +39 -0
  124. package/dist/people.d.ts.map +1 -0
  125. package/dist/people.js +88 -0
  126. package/dist/people.js.map +1 -0
  127. package/dist/problem.d.ts +12 -0
  128. package/dist/problem.d.ts.map +1 -0
  129. package/dist/problem.js +33 -0
  130. package/dist/problem.js.map +1 -0
  131. package/dist/runtime.d.ts +92 -0
  132. package/dist/runtime.d.ts.map +1 -0
  133. package/dist/runtime.js +8 -0
  134. package/dist/runtime.js.map +1 -0
  135. package/dist/scenarios.d.ts +125 -0
  136. package/dist/scenarios.d.ts.map +1 -0
  137. package/dist/scenarios.js +251 -0
  138. package/dist/scenarios.js.map +1 -0
  139. package/package.json +55 -3
package/bin/appOps.mjs ADDED
@@ -0,0 +1,336 @@
1
+ // Devoplus One YAYIN ve KULLANIM PANOSU istemcisi (D-86 · D-87). `one publish|promote|rollback|deployments|status`
2
+ // komutları ve Devoplus One MCP sunucusu (`one_publish` · `one_deployments` · `one_app_health`) AYNI modülü kullanır;
3
+ // ikisi aynı backend uçlarını çağırır:
4
+ //
5
+ // POST api/v1/One/apps/{slug}/deployments { portalKey?, action, toVersion? } → 202 { id, status, pipelineUrl? }
6
+ // GET api/v1/One/apps/{slug}/deployments?portalKey= → yayın istekleri
7
+ // GET api/v1/One/apps/{slug}/insights?portalKey=&days= → kullanım ve hata özeti
8
+ //
9
+ // ⛔ Yayın isteği YENİ BİR YETKİ YOLU AÇMAZ: backend yalnız uygulamanın sahibine ya da o portalın şirket yöneticisine
10
+ // izin verir, isteği kayda geçirir ve hattı (D-69) kendisi başlatır; hattın kapıları atlanmaz. Gönderenin kimliği
11
+ // OTURUMDAN (kullanıcının kendi belirteci) gelir; bu modül gövdeye kişi YAZMAZ.
12
+ //
13
+ // ⛔ Kısa ad, portal anahtarı ve sürüm ADRESE girer: biçimleri ağa çıkmadan denetlenir (yol enjeksiyonu yok). Kısa ad ve
14
+ // portal anahtarı kuralları `packages/manifest/src/naming.ts` ile AYNIDIR (eşitlik appOps.test.ts'te kilitli).
15
+
16
+ import { isSecureBase } from './session.mjs'
17
+
18
+ export class AppOpsError extends Error {
19
+ /**
20
+ * @param {string} message kullanıcıya gösterilecek Türkçe metin
21
+ * @param {{ status?: number, code?: string }} [details]
22
+ */
23
+ constructor(message, details = {}) {
24
+ super(message)
25
+ this.name = 'AppOpsError'
26
+ this.status = details.status
27
+ this.code = details.code
28
+ }
29
+ }
30
+
31
+ /** `packages/manifest/src/naming.ts` → `SLUG_RE` · `SLUG_MAX` · `PORTAL_KEY_RE` (bin derlemesiz olduğu için kopya; test eşitler). */
32
+ export const SLUG_RE = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/
33
+ export const SLUG_MAX = 24
34
+ export const PORTAL_KEY_RE = /^[a-z0-9]{2,16}$/
35
+ /** Manifest `app.version` biçimi (`packages/manifest/src/schema.ts` → semver). */
36
+ export const VERSION_RE = /^\d+\.\d+\.\d+$/
37
+
38
+ /** Pano gün aralığı: varsayılan 7 (C5). Üst sınır istemcinin makul bir sınırıdır; sunucu daha dar bir sınır uygulayabilir. */
39
+ export const DAYS_DEFAULT = 7
40
+ export const DAYS_MAX = 90
41
+
42
+ /** Yayın eylemleri: tel adı → kullanıcıya görünen ad. */
43
+ export const ACTIONS = {
44
+ deploy: 'Test ortamına yayın (önizleme)',
45
+ promote: 'Bir sonraki ortama terfi',
46
+ rollback: 'Önceki sürüme geri alma',
47
+ }
48
+
49
+ /**
50
+ * Önizleme ortamının (test) uygulama adresi tabanı — mimari §4 "Ortamlar" (D-15). `one publish` her zaman test
51
+ * ortamına yayınlar (D-86); önizleme adresi bu tabandan kurulur.
52
+ */
53
+ export const PREVIEW_HOST_BASE = 'test-one.devoplus.dev'
54
+
55
+ /** Tanınmayan durum adları olduğu gibi gösterilir (sunucunun yeni durumu uydurma bir etikete çevrilmez). */
56
+ const STATUS_LABELS = {
57
+ queued: 'Sırada',
58
+ not_configured: 'Yayın hattı yapılandırılmamış',
59
+ }
60
+
61
+ /** `not_configured` yanıtında kullanıcıya söylenecek TEK cümle — fazlası (kime iletildiği, ne zaman açılacağı) bilinmez. */
62
+ export const NOT_CONFIGURED_MESSAGE = 'Yayın hattı bu ortamda yapılandırılmamış; yayın başlatılmadı.'
63
+ export const INSIGHTS_NOT_CONFIGURED_MESSAGE = 'Kullanım ve hata panosu bu ortamda yapılandırılmamış; veri okunamadı.'
64
+
65
+ function clean(value) {
66
+ if (value === undefined || value === null) return undefined
67
+ const text = String(value).trim()
68
+ return text === '' ? undefined : text
69
+ }
70
+
71
+ /** Kısa ad: zorunlu, küçük harfe iner, naming.ts kuralı. */
72
+ export function checkSlug(value) {
73
+ const slug = clean(value)?.toLowerCase()
74
+ if (!slug) throw new AppOpsError('Uygulamanın kısa adı zorunludur (one.manifest.json dosyasındaki app.slug).')
75
+ if (slug.length > SLUG_MAX || !SLUG_RE.test(slug))
76
+ throw new AppOpsError(`Uygulamanın kısa adı geçersiz: "${slug}" (küçük harf, rakam ve tire; en çok ${SLUG_MAX} karakter).`)
77
+ return slug
78
+ }
79
+
80
+ /**
81
+ * Portal anahtarı: isteğe bağlı; verilirse naming.ts kuralı. Verilmezse `undefined` ve istekte YER ALMAZ — hangi
82
+ * kurulumun seçileceğine sunucu karar verir (C5 bunu belirtmiyor; backend tarafına öneri: oturumun portalı).
83
+ */
84
+ export function checkPortalKey(value) {
85
+ const key = clean(value)?.toLowerCase()
86
+ if (key === undefined) return undefined
87
+ if (!PORTAL_KEY_RE.test(key)) throw new AppOpsError(`Portal anahtarı geçersiz: "${key}" (2-16 küçük harf ya da rakam).`)
88
+ return key
89
+ }
90
+
91
+ function checkVersion(value) {
92
+ const version = clean(value)
93
+ if (version === undefined) return undefined
94
+ if (!VERSION_RE.test(version)) throw new AppOpsError(`Sürüm "x.y.z" biçiminde olmalı: "${version}".`)
95
+ return version
96
+ }
97
+
98
+ /** Gün sayısı: verilmezse 7; 1..DAYS_MAX tam sayı. */
99
+ export function checkDays(value) {
100
+ const text = clean(value)
101
+ if (text === undefined) return DAYS_DEFAULT
102
+ const days = Number(text)
103
+ if (!/^\d+$/.test(text) || !Number.isInteger(days) || days < 1 || days > DAYS_MAX)
104
+ throw new AppOpsError(`Gün sayısı 1 ile ${DAYS_MAX} arasında bir tam sayı olmalı: "${text}".`)
105
+ return days
106
+ }
107
+
108
+ /**
109
+ * Yayın isteği gövdesi. `toVersion` yalnız geri almada anlamlıdır; başka eylemde verilirse açık hata (sessizce
110
+ * düşürülseydi kullanıcı belirli bir sürümü yayımladığını sanırdı).
111
+ */
112
+ export function buildDeploymentRequest(fields = {}) {
113
+ const action = clean(fields.action)
114
+ if (!action || !Object.hasOwn(ACTIONS, action))
115
+ throw new AppOpsError(`Yayın eylemi tanınmadı: "${fields.action ?? ''}". Geçerli eylemler: ${Object.keys(ACTIONS).join(', ')}.`)
116
+ const portalKey = checkPortalKey(fields.portalKey)
117
+ const toVersion = checkVersion(fields.toVersion)
118
+ if (toVersion !== undefined && action !== 'rollback')
119
+ throw new AppOpsError('Sürüm (toVersion) yalnız geri almada verilir.')
120
+ const body = { action }
121
+ if (portalKey !== undefined) body.portalKey = portalKey
122
+ if (toVersion !== undefined) body.toVersion = toVersion
123
+ return body
124
+ }
125
+
126
+ /** API tabanı `https://…/` biçiminde olmalı (http yalnız yerel adreste); uç yolu ona eklenir. */
127
+ function endpoint(apiBase, path, query = {}) {
128
+ // Belirteç bu adrese gider: düz http yalnız yerel geliştirme adresinde (RFC 6750 §5.3).
129
+ if (!isSecureBase(apiBase))
130
+ throw new AppOpsError(`API tabanı "https://.../" biçiminde ve "/" ile bitmeli (http yalnız localhost): ${apiBase}`)
131
+ const url = new URL(`api/v1/One/apps/${path}`, apiBase)
132
+ for (const [name, value] of Object.entries(query)) if (value !== undefined) url.searchParams.set(name, String(value))
133
+ return url.toString()
134
+ }
135
+
136
+ /** Sunucunun ProblemDetails yanıtını kullanıcı metnine çevirir; gövde yoksa durum koduna göre sabit cümle. */
137
+ async function failure(response) {
138
+ let detail
139
+ let code
140
+ try {
141
+ const problem = await response.json()
142
+ detail = typeof problem?.detail === 'string' ? problem.detail : undefined
143
+ code = typeof problem?.code === 'string' ? problem.code : undefined
144
+ } catch {
145
+ // gövdesiz 401/403 — aşağıdaki sabit metin
146
+ }
147
+ const fallback =
148
+ response.status === 401
149
+ ? 'Oturumunuz geçersiz ya da süresi dolmuş; yeniden giriş yapın.'
150
+ : response.status === 403
151
+ ? 'Bu uygulama için bu işlemi yapma yetkiniz yok.'
152
+ : response.status === 404
153
+ ? 'Uygulama ya da kurulum bulunamadı.'
154
+ : response.status === 429
155
+ ? 'Çok fazla istek gönderdiniz; bir süre sonra yeniden deneyin.'
156
+ : `Devoplus isteği başarısız oldu (HTTP ${response.status}).`
157
+ return new AppOpsError(detail ?? fallback, { status: response.status, code })
158
+ }
159
+
160
+ /**
161
+ * @typedef {{ apiBase: string, accessToken: string, fetch?: typeof fetch }} ClientOptions
162
+ */
163
+
164
+ async function call(options, path, query, init = {}) {
165
+ const doFetch = options.fetch ?? fetch
166
+ if (!options.accessToken) throw new AppOpsError('Oturum yok; önce giriş yapın.', { status: 401 })
167
+ const url = endpoint(options.apiBase, path, query)
168
+ let response
169
+ try {
170
+ response = await doFetch(url, {
171
+ ...init,
172
+ headers: {
173
+ Accept: 'application/json',
174
+ Authorization: `Bearer ${options.accessToken}`,
175
+ ...(init.body ? { 'Content-Type': 'application/json' } : {}),
176
+ },
177
+ })
178
+ } catch {
179
+ throw new AppOpsError('Devoplus API\'ye ulaşılamadı.')
180
+ }
181
+ if (!response.ok) throw await failure(response)
182
+ try {
183
+ return await response.json()
184
+ } catch {
185
+ throw new AppOpsError('Devoplus API yanıtı okunamadı.', { status: response.status })
186
+ }
187
+ }
188
+
189
+ /** Yayın isteği bırakır (deploy · promote · rollback). Dönüş `{ id, status, pipelineUrl? }`. */
190
+ export function requestDeployment(options, slug, body) {
191
+ const app = checkSlug(slug)
192
+ const request = buildDeploymentRequest(body)
193
+ return call(options, `${encodeURIComponent(app)}/deployments`, {}, { method: 'POST', body: JSON.stringify(request) })
194
+ }
195
+
196
+ /** Uygulamanın (ve verilirse o kurulumun) yayın istekleri. */
197
+ export function listDeployments(options, slug, portalKey) {
198
+ const app = checkSlug(slug)
199
+ return call(options, `${encodeURIComponent(app)}/deployments`, { portalKey: checkPortalKey(portalKey) })
200
+ }
201
+
202
+ /** Kullanım ve hata özeti. */
203
+ export function getInsights(options, slug, { portalKey, days } = {}) {
204
+ const app = checkSlug(slug)
205
+ return call(options, `${encodeURIComponent(app)}/insights`, { portalKey: checkPortalKey(portalKey), days: checkDays(days) })
206
+ }
207
+
208
+ /** Önizleme (test ortamı) adresi; portal anahtarı yoksa kurulamaz → `undefined`. */
209
+ export function previewAddress(slug, portalKey) {
210
+ const key = checkPortalKey(portalKey)
211
+ if (key === undefined) return undefined
212
+ return `https://${checkSlug(slug)}-${key}.${PREVIEW_HOST_BASE}`
213
+ }
214
+
215
+ function statusLabel(status) {
216
+ return STATUS_LABELS[status] ?? String(status ?? '?')
217
+ }
218
+
219
+ /** Tek bir yayın isteğini tek satıra yazar (CLI listesi ve MCP yanıtı). Bilinmeyen alanlar uydurulmaz. */
220
+ export function describeDeployment(row) {
221
+ const action = ACTIONS[row?.action] ?? (row?.action ? String(row.action) : undefined)
222
+ const parts = [`${row?.id ?? '?'} ${statusLabel(row?.status)}`]
223
+ if (action) parts.push(`, ${action}`)
224
+ if (row?.portalKey) parts.push(`, portal ${row.portalKey}`)
225
+ if (row?.toVersion) parts.push(`, hedef sürüm ${row.toVersion}`)
226
+ if (typeof row?.createdAt === 'string') parts.push(`, ${row.createdAt}`)
227
+ if (typeof row?.pipelineUrl === 'string') parts.push(`\n hat: ${row.pipelineUrl}`)
228
+ return parts.join('')
229
+ }
230
+
231
+ /**
232
+ * Yayın isteği yanıtını kullanıcıya söylenecek metne çevirir. `not_configured` başka bir iddia taşımaz.
233
+ * @param {{ id?: string, status?: string, pipelineUrl?: string }} result
234
+ * @param {{ action: string, slug: string, portalKey?: string }} request
235
+ */
236
+ export function describeDeploymentResult(result, request) {
237
+ if (result?.status === 'not_configured') return NOT_CONFIGURED_MESSAGE
238
+ const lines = [`Yayın isteği alındı (${ACTIONS[request.action] ?? request.action}): ${describeDeployment({ ...result, action: request.action, portalKey: request.portalKey })}`]
239
+ if (request.action === 'deploy') {
240
+ const preview = previewAddress(request.slug, request.portalKey)
241
+ lines.push(
242
+ preview
243
+ ? `Hat bitince önizleme test ortamındadır: test Portal'ından uygulamayı açıp bakabilirsiniz (adres: ${preview}).`
244
+ : 'Hat bitince önizleme test ortamındadır: test Portal\'ından uygulamayı açıp bakabilirsiniz (adres için portal anahtarını verin).',
245
+ )
246
+ }
247
+ return lines.join('\n')
248
+ }
249
+
250
+ function percent(part, whole) {
251
+ if (!Number.isFinite(part) || !Number.isFinite(whole) || whole <= 0) return '?'
252
+ return `%${((part / whole) * 100).toLocaleString('tr-TR', { maximumFractionDigits: 1 })}`
253
+ }
254
+
255
+ function number(value) {
256
+ return Number.isFinite(value) ? Number(value).toLocaleString('tr-TR') : '?'
257
+ }
258
+
259
+ /** Kullanım özetini çok satırlı Türkçe metne çevirir. `configured: false` başka veri GÖSTERMEZ. */
260
+ export function describeInsights(insights) {
261
+ if (!insights || insights.configured === false) return INSIGHTS_NOT_CONFIGURED_MESSAGE
262
+ const totals = insights.totals ?? {}
263
+ const lines = [
264
+ `Dönem: ${insights.from ?? '?'} ile ${insights.to ?? '?'} arası`,
265
+ `İstek: ${number(totals.requests)}, hata: ${number(totals.errors)} (${percent(totals.errors, totals.requests)})`,
266
+ `İşlemci süresi (istek başına): ortanca ${number(totals.cpuP50Ms)} ms, en yavaş %1 ${number(totals.cpuP99Ms)} ms`,
267
+ ]
268
+ const failed = insights.jobs?.failed
269
+ if (Number.isFinite(failed)) lines.push(`Başarısız arka plan işi: ${number(failed)}`)
270
+ const daily = Array.isArray(insights.daily) ? insights.daily : []
271
+ if (daily.length > 0) {
272
+ lines.push('Günlük:')
273
+ for (const day of daily) lines.push(` ${day?.date ?? '?'} istek ${number(day?.requests)}, hata ${number(day?.errors)}`)
274
+ }
275
+ const errors = Array.isArray(insights.recentErrors) ? insights.recentErrors : []
276
+ if (errors.length > 0) {
277
+ lines.push('Son hatalar:')
278
+ for (const e of errors.slice(0, 20)) {
279
+ const message = String(e?.message ?? '').replace(/\s+/g, ' ').slice(0, 300)
280
+ lines.push(` ${e?.time ?? '?'}${e?.path ? ` ${e.path}` : ''} ${message}`)
281
+ }
282
+ } else lines.push('Son hata kaydı yok.')
283
+ return lines.join('\n')
284
+ }
285
+
286
+ /**
287
+ * `one <komut>` argümanlarını ayrıştırır (SAF — testlenir). `argv` komut adından SONRAKİ argümanlardır.
288
+ * Bilinmeyen seçenek açık hatadır: yazım yanlışı (`--portl`) sessizce yok sayılsaydı istek yanlış kuruluma giderdi.
289
+ * @param {'publish'|'promote'|'rollback'|'deployments'|'status'} command
290
+ * @param {string[]} argv
291
+ */
292
+ export function parseOpsArgs(command, argv) {
293
+ const allowed = {
294
+ publish: ['portal', 'app'],
295
+ promote: ['portal', 'app'],
296
+ rollback: ['portal', 'app', 'to'],
297
+ deployments: ['portal', 'app'],
298
+ status: ['portal', 'app', 'days'],
299
+ }[command]
300
+ if (!allowed) throw new AppOpsError(`bilinmeyen komut: ${command}`)
301
+ const flags = { publish: [], promote: [], rollback: ['yes'], deployments: [], status: [] }[command]
302
+ const out = { json: false, yes: false }
303
+ for (let i = 0; i < argv.length; i++) {
304
+ const arg = argv[i]
305
+ if (arg === '--json') {
306
+ out.json = true
307
+ continue
308
+ }
309
+ const name = /^--([a-z-]+)$/.exec(arg)?.[1]
310
+ if (name && flags.includes(name)) {
311
+ out[name] = true
312
+ continue
313
+ }
314
+ if (!name || !allowed.includes(name)) throw new AppOpsError(`one ${command}: tanınmayan argüman "${arg}".`)
315
+ const value = argv[i + 1]
316
+ if (value === undefined || value.startsWith('--')) throw new AppOpsError(`one ${command}: --${name} bir değer ister.`)
317
+ out[name] = value
318
+ i++
319
+ }
320
+ const portalKey = checkPortalKey(out.portal)
321
+ const result = { json: out.json, portalKey, app: out.app }
322
+ if (command === 'rollback') {
323
+ result.toVersion = checkVersion(out.to)
324
+ result.yes = out.yes
325
+ }
326
+ if (command === 'status') result.days = checkDays(out.days)
327
+ return result
328
+ }
329
+
330
+ /** Komut → yayın eylemi. */
331
+ export const COMMAND_ACTIONS = { publish: 'deploy', promote: 'promote', rollback: 'rollback' }
332
+
333
+ /** Geri almanın uyarı metni: veri geri alınırsa dönülen noktadan sonra yazılan SİLİNİR (D-43, mimari §4d). */
334
+ export const ROLLBACK_WARNING =
335
+ 'Geri alma uygulamayı önceki sürüme döndürür; veritabanı da o sürümün şemasına dönmek zorundaysa dönülen noktadan ' +
336
+ 'sonra kaydedilen veriler KAYBOLUR. Bu geri alınamaz bir işlemdir.'
@@ -0,0 +1,131 @@
1
+ // `one data generate [--check]` — tanımlayıcı veri modeli (D-84). one.data.json → yeni migration (yalnız ekleyen) +
2
+ // src/data/types.ts + migrations/.one-data-snapshot.json. Üretim kuralları SDK'nın `data/ddl.ts` modülündedir (saf,
3
+ // testlenir); bu dosya yalnız dosyaları okur ve yazar.
4
+ //
5
+ // Kural kaynağı: yayımlanmış pakette derlenmiş `dist/data/ddl.js`; çalışma alanında (kaynak varken) `src/data/ddl.ts`
6
+ // — eski bir derleme çıktısı yanlış farka yol açmasın diye kaynak önceliklidir.
7
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
8
+ import { dirname, join } from 'node:path'
9
+ import { fileURLToPath, pathToFileURL } from 'node:url'
10
+
11
+ export class DataCommandError extends Error {}
12
+
13
+ const HERE = dirname(fileURLToPath(import.meta.url))
14
+
15
+ export async function loadDataTools() {
16
+ const src = join(HERE, '../src/data/ddl.ts')
17
+ const dist = join(HERE, '../dist/data/ddl.js')
18
+ const target = existsSync(src) ? src : dist
19
+ if (!existsSync(target)) throw new DataCommandError('Devoplus One SDK eksik kurulmuş (veri modeli üreticisi bulunamadı); "pnpm install" çalıştırın.')
20
+ return import(pathToFileURL(target).href)
21
+ }
22
+
23
+ function readOptional(path) {
24
+ try {
25
+ return readFileSync(path, 'utf8')
26
+ } catch (err) {
27
+ if (err && err.code === 'ENOENT') return null
28
+ throw err
29
+ }
30
+ }
31
+
32
+ const USAGE = `kullanım: one data generate [--check]
33
+
34
+ one.data.json'daki veri modelinden şunları üretir:
35
+ migrations/NNNN_one_data.sql yalnız ekleyen değişiklik (yeni tablo, kolon, dizin); değişiklik yoksa yazılmaz
36
+ src/data/types.ts kayıt ve girdi tipleri + createCrudHandler'a verilen dataModel
37
+ migrations/.one-data-snapshot.json bir sonraki farkın alındığı model (elle düzenlenmez)
38
+ --check hiçbir şey yazmaz; dosyalar güncel değilse ya da model hatalıysa 1 ile çıkar.
39
+
40
+ Silme, yeniden adlandırma, tür değişikliği, zorunlu/isteğe bağlı değişikliği ve seçim değeri kaldırma üretilmez; komut
41
+ nedenini söyleyip durur. Seçim alanına yeni değer eklemek migration gerektirmez (yalnız tipler yenilenir).`
42
+
43
+ /**
44
+ * Komutu çalıştırır; çıkış kodu döner. `io.log` / `io.error` çıktıyı yazar (testte toplanır).
45
+ * @param {{ cwd: string, args: string[], log?: (s: string) => void, error?: (s: string) => void }} io
46
+ */
47
+ export async function runDataCommand({ cwd, args, log = console.log, error = console.error }) {
48
+ const [sub, ...rest] = args
49
+ if (!sub || sub === '--help' || sub === '-h') {
50
+ log(USAGE)
51
+ return sub ? 0 : 1
52
+ }
53
+ if (sub !== 'generate') {
54
+ error(`one data: bilinmeyen alt komut "${sub}".\n${USAGE}`)
55
+ return 1
56
+ }
57
+ const unknown = rest.filter((a) => a !== '--check')
58
+ if (unknown.length > 0) {
59
+ error(`one data generate: bilinmeyen seçenek ${unknown.join(' ')}`)
60
+ return 1
61
+ }
62
+ const check = rest.includes('--check')
63
+ const tools = await loadDataTools()
64
+
65
+ const modelRaw = readOptional(join(cwd, tools.DATA_MODEL_FILE))
66
+ if (modelRaw === null) {
67
+ error(`one data: bu dizinde ${tools.DATA_MODEL_FILE} yok. Veri modelini önce bu dosyaya yazın (belge: "Veri modeli ve kayıt işlemleri").`)
68
+ return 1
69
+ }
70
+ let model
71
+ try {
72
+ model = JSON.parse(modelRaw)
73
+ } catch (err) {
74
+ error(`one data: ${tools.DATA_MODEL_FILE} geçerli JSON değil (${err instanceof Error ? err.message : String(err)}).`)
75
+ return 1
76
+ }
77
+ const migrationsDir = join(cwd, 'migrations')
78
+ const existing = existsSync(migrationsDir)
79
+ ? readdirSync(migrationsDir)
80
+ .filter((f) => f.endsWith('.sql'))
81
+ .sort()
82
+ .map((name) => ({ name, sql: readFileSync(join(migrationsDir, name), 'utf8') }))
83
+ : []
84
+ const snapshotPath = join(migrationsDir, tools.DATA_SNAPSHOT_FILE)
85
+ let plan
86
+ try {
87
+ plan = tools.planDataGeneration({ model, snapshot: readOptional(snapshotPath), existing })
88
+ } catch (err) {
89
+ if (err instanceof Error && err.name === 'DataModelError') {
90
+ error(`one data: değişiklik üretilmedi.\n${err.message}`)
91
+ return 1
92
+ }
93
+ throw err
94
+ }
95
+
96
+ const typesPath = join(cwd, tools.DATA_TYPES_FILE)
97
+ const writes = []
98
+ if (plan.migration) writes.push({ path: join(migrationsDir, plan.migration.name), content: plan.migration.sql, label: `migrations/${plan.migration.name}` })
99
+ if (readOptional(typesPath) !== plan.types) writes.push({ path: typesPath, content: plan.types, label: tools.DATA_TYPES_FILE })
100
+ if (readOptional(snapshotPath) !== plan.snapshot) writes.push({ path: snapshotPath, content: plan.snapshot, label: `migrations/${tools.DATA_SNAPSHOT_FILE}` })
101
+
102
+ const describe = (c) =>
103
+ c.kind === 'create-table'
104
+ ? `yeni tablo: ${c.entity}`
105
+ : c.kind === 'add-column'
106
+ ? `yeni alan: ${c.entity}.${c.field}`
107
+ : c.kind === 'add-search-column'
108
+ ? `arama kolonu: ${c.entity}.${c.field}`
109
+ : `yeni dizin: ${c.entity}.${c.field}`
110
+ for (const c of plan.changes) log(` ${describe(c)}`)
111
+
112
+ if (check) {
113
+ if (writes.length === 0) {
114
+ log('one data: dosyalar güncel.')
115
+ return 0
116
+ }
117
+ log(`one data: güncel değil; "one data generate" şunları yazar:\n${writes.map((w) => ` ${w.label}`).join('\n')}`)
118
+ return 1
119
+ }
120
+ if (writes.length === 0) {
121
+ log('one data: değişiklik yok; dosyalar güncel.')
122
+ return 0
123
+ }
124
+ for (const w of writes) {
125
+ mkdirSync(dirname(w.path), { recursive: true })
126
+ writeFileSync(w.path, w.content)
127
+ log(`yazıldı: ${w.label}`)
128
+ }
129
+ if (plan.migration) log('Sıradaki adım: "one migrate" (yerel veritabanına uygular). Yayında migration platform tarafından uygulanır.')
130
+ return 0
131
+ }
@@ -0,0 +1,132 @@
1
+ // `one` komutunun SAF yardımcıları (D-72): manifest → yerel çalışma ortamı yapılandırması + komut argümanları.
2
+ // Ağ ve dosya sistemi YOK → testlenir (devConfig.test.ts). Komutun kendisi one.mjs'tedir.
3
+ //
4
+ // Yerel çalışma ortamı uygulama deposuna yapılandırma dosyası BIRAKMAZ: yapılandırma her komutta manifest'ten
5
+ // `.one/dev.jsonc`'a yeniden üretilir ve yerel veri `.one/state/` altında durur (ikisi de .gitignore'da). Gerçek
6
+ // yüklemede yapılandırmayı one-ci üretir; buradaki adlar ve kimlikler yalnız yereldir.
7
+
8
+ export const DEV_DIR = '.one'
9
+ export const DEV_CONFIG_FILE = 'dev.jsonc'
10
+ export const DEV_STATE_DIR = 'state'
11
+ /** Yerelde Devoplus servislerine giden istekler bu tabana gider (test ortamı); `--api-base` ya da ONE_API_BASE ile değişir. */
12
+ export const DEFAULT_API_BASE = 'https://test-api.devoplus.dev/'
13
+ /** Çalışma ortamı sürüm tarihi — platformun yüklemede kullandığıyla aynı tutulur. */
14
+ export const COMPATIBILITY_DATE = '2026-10-01'
15
+
16
+ const SLUG_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/
17
+ const ENTRY_RE = /^[\w./-]+\.(ts|js|mjs)$/
18
+ const RENAMED = { d1: 'database', r2: 'files', kv: 'cache' }
19
+
20
+ export class OneCliError extends Error {}
21
+
22
+ /**
23
+ * Manifest'ten yalnız yerel çalışma için gereken alanları okur. Tam doğrulama one-ci'dedir (`one-ci validate`);
24
+ * burada yalnız komutun yanlış bir şey çalıştırmasına yol açacak hatalar yakalanır.
25
+ */
26
+ export function readDevManifest(raw) {
27
+ let m
28
+ try {
29
+ m = JSON.parse(raw)
30
+ } catch {
31
+ throw new OneCliError('one.manifest.json okunamadı: geçerli JSON değil.')
32
+ }
33
+ const slug = m?.app?.slug
34
+ if (typeof slug !== 'string' || !SLUG_RE.test(slug)) throw new OneCliError('one.manifest.json: app.slug eksik ya da geçersiz.')
35
+ const entry = m?.entry
36
+ if (typeof entry !== 'string' || !ENTRY_RE.test(entry) || entry.split('/').includes('..'))
37
+ throw new OneCliError('one.manifest.json: entry eksik ya da geçersiz (ör. "src/index.ts").')
38
+ const services = m?.services ?? {}
39
+ for (const [old, next] of Object.entries(RENAMED))
40
+ if (old in services) throw new OneCliError(`one.manifest.json: "services.${old}" kaldırıldı; yerine "services.${next}" yazın.`)
41
+ return {
42
+ slug,
43
+ entry,
44
+ database: services.database !== false,
45
+ files: services.files === true,
46
+ cache: services.cache === true,
47
+ }
48
+ }
49
+
50
+ /** Yerel yapılandırma (`.one/dev.jsonc`). Göreli yollar bu dosyanın dizinine (`.one/`) göredir. */
51
+ export function devConfig(manifest, options = {}) {
52
+ const apiBase = options.apiBase || DEFAULT_API_BASE
53
+ if (!/^https?:\/\/[^\s]+\/$/.test(apiBase)) throw new OneCliError(`API tabanı "http(s)://.../" biçiminde ve "/" ile bitmeli: ${apiBase}`)
54
+ const local = `one-local-${manifest.slug}`
55
+ const config = {
56
+ name: local,
57
+ main: `../${manifest.entry}`,
58
+ compatibility_date: COMPATIBILITY_DATE,
59
+ vars: { API_BASE: apiBase },
60
+ }
61
+ if (manifest.database)
62
+ config.d1_databases = [{ binding: 'DB', database_name: local, database_id: 'local', migrations_dir: '../migrations' }]
63
+ if (manifest.files) config.r2_buckets = [{ binding: 'FILES', bucket_name: local }]
64
+ if (manifest.cache) config.kv_namespaces = [{ binding: 'CACHE', id: 'local' }]
65
+ return config
66
+ }
67
+
68
+ export const COMMANDS = ['dev', 'migrate', 'build']
69
+
70
+ /**
71
+ * Komut → çalışma ortamı aracının argümanları. `dev` ve `migrate` aynı yerel veri dizinini kullanır; aksi hâlde
72
+ * `one migrate`'in kurduğu tablolar `one dev`'de görünmezdi.
73
+ */
74
+ export function commandArgs(command, options = {}) {
75
+ const config = `${DEV_DIR}/${DEV_CONFIG_FILE}`
76
+ const state = `${DEV_DIR}/${DEV_STATE_DIR}`
77
+ switch (command) {
78
+ case 'dev': {
79
+ const args = ['dev', '-c', config, '--persist-to', state]
80
+ if (options.port !== undefined) {
81
+ const port = Number(options.port)
82
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new OneCliError(`--port geçersiz: ${options.port}`)
83
+ args.push('--port', String(port))
84
+ }
85
+ return args
86
+ }
87
+ case 'migrate':
88
+ return ['d1', 'migrations', 'apply', 'DB', '--local', '-c', config, '--persist-to', state]
89
+ case 'build':
90
+ // Çıktı dizini MUTLAK verilir: göreli yol aracın sürümüne göre yapılandırma dizinine ya da çalışma dizinine
91
+ // göre çözülüyor ve çıktı iki yere bölünüyordu (ölçüldü, 17.10.2026).
92
+ if (!options.outDir) throw new OneCliError('build için çıktı dizini verilmedi.')
93
+ return ['deploy', '--dry-run', '--outdir', options.outDir, '-c', config]
94
+ default:
95
+ throw new OneCliError(`bilinmeyen komut: ${command} (${COMMANDS.join(', ')})`)
96
+ }
97
+ }
98
+
99
+ /** `migrate` veritabanı olmayan uygulamada anlamsızdır; açık hata verilir. */
100
+ export function assertCommandFits(command, manifest) {
101
+ if (command === 'migrate' && !manifest.database)
102
+ throw new OneCliError('Bu uygulama veritabanı kullanmıyor (services.database: false); "one migrate" gerekmez.')
103
+ }
104
+
105
+ export const USAGE = `kullanım: one <komut> [seçenekler]
106
+
107
+ one dev uygulamayı yerelde çalıştırır (Devoplus One çalışma ortamı)
108
+ --port <n> dinlenecek port
109
+ --api-base <url> Devoplus API tabanı (varsayılan ${DEFAULT_API_BASE}; ONE_API_BASE ile de verilir)
110
+ one migrate migrations/ altındaki SQL dosyalarını yerel veritabanına uygular
111
+ one build yükleme provası: kodu paketler, hiçbir yere göndermez (çıktı .one/dist/)
112
+ one data generate [--check]
113
+ one.data.json veri modelinden migration + src/data/types.ts üretir (yalnız ekleyen değişiklik)
114
+
115
+ one login Devoplus hesabınızla giriş (tarayıcıda kod onayı; portal orada seçilir)
116
+ --env <test|uat|prod> ortam (varsayılan test; ONE_ENV ile de verilir)
117
+ --client-id <kimlik> sabit istemci (verilmezse bu bilgisayar kendini bir kez kaydeder)
118
+ one logout bu bilgisayardaki oturumu siler
119
+ one request <tür> ... yöneticiden eksik izni ister (kurulum, DEXA sorgusu, uygulama izni gibi); "one request" türleri listeler
120
+ one requests [kimlik] taleplerinizi ve durumlarını listeler
121
+ one context oturumdaki portalın adını ve var olan kısa adını (portal anahtarı) gösterir [--json]
122
+
123
+ one publish uygulamayı test ortamına yayınlar (paylaşılabilir önizleme) [--portal <anahtar>]
124
+ one promote sürümü bir sonraki ortama taşır (testten uat'a, sonra canlıya) [--portal <anahtar>]
125
+ one rollback önceki sürüme döner (veri kaybı olabilir; --yes ister) [--portal <anahtar>] [--to <x.y.z>] --yes
126
+ one deployments yayın isteklerinizi ve durumlarını listeler [--portal <anahtar>]
127
+ one status kullanım ve hata özeti [--portal <anahtar>] [--days 7]
128
+ Yayın komutları "one login" oturumuyla çalışır; yayını Devoplus hattı yapar, kapıları atlanmaz.
129
+ --portal verilmezse istek portal anahtarı taşımaz, kurulumu sunucu seçer; --app <kısa ad>, --json
130
+
131
+ Yerel yapılandırma ve veri .one/ altındadır; depoya girmez. Gerçek yükleme Devoplus hattıyla yapılır; bu bilgisayardan
132
+ yükleme yoktur.`