@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.
- package/CHANGELOG.md +268 -0
- package/README.md +893 -2
- package/bin/accessRequest.d.mts +96 -0
- package/bin/accessRequest.mjs +340 -0
- package/bin/appOps.d.mts +97 -0
- package/bin/appOps.mjs +336 -0
- package/bin/dataCommand.mjs +131 -0
- package/bin/devConfig.mjs +132 -0
- package/bin/one.mjs +341 -0
- package/bin/session.mjs +239 -0
- package/dist/agentInput.d.ts +9 -0
- package/dist/agentInput.d.ts.map +1 -0
- package/dist/agentInput.js +102 -0
- package/dist/agentInput.js.map +1 -0
- package/dist/agentTools.d.ts +83 -0
- package/dist/agentTools.d.ts.map +1 -0
- package/dist/agentTools.js +164 -0
- package/dist/agentTools.js.map +1 -0
- package/dist/ai.d.ts +67 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +129 -0
- package/dist/ai.js.map +1 -0
- package/dist/audit.d.ts +21 -0
- package/dist/audit.d.ts.map +1 -0
- package/dist/audit.js +58 -0
- package/dist/audit.js.map +1 -0
- package/dist/backend.d.ts +27 -0
- package/dist/backend.d.ts.map +1 -0
- package/dist/backend.js +63 -0
- package/dist/backend.js.map +1 -0
- package/dist/cache.d.ts +20 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +84 -0
- package/dist/cache.js.map +1 -0
- package/dist/client.d.ts +147 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +469 -0
- package/dist/client.js.map +1 -0
- package/dist/components.d.ts +235 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +1491 -0
- package/dist/components.js.map +1 -0
- package/dist/context.d.ts +110 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +126 -0
- package/dist/context.js.map +1 -0
- package/dist/data/crud.d.ts +74 -0
- package/dist/data/crud.d.ts.map +1 -0
- package/dist/data/crud.js +614 -0
- package/dist/data/crud.js.map +1 -0
- package/dist/data/ddl.d.ts +76 -0
- package/dist/data/ddl.d.ts.map +1 -0
- package/dist/data/ddl.js +362 -0
- package/dist/data/ddl.js.map +1 -0
- package/dist/data/fold.d.ts +14 -0
- package/dist/data/fold.d.ts.map +1 -0
- package/dist/data/fold.js +76 -0
- package/dist/data/fold.js.map +1 -0
- package/dist/data/model.d.ts +113 -0
- package/dist/data/model.d.ts.map +1 -0
- package/dist/data/model.js +408 -0
- package/dist/data/model.js.map +1 -0
- package/dist/data/values.d.ts +29 -0
- package/dist/data/values.d.ts.map +1 -0
- package/dist/data/values.js +152 -0
- package/dist/data/values.js.map +1 -0
- package/dist/dexa.d.ts +44 -0
- package/dist/dexa.d.ts.map +1 -0
- package/dist/dexa.js +74 -0
- package/dist/dexa.js.map +1 -0
- package/dist/dexaCommand.d.ts +116 -0
- package/dist/dexaCommand.d.ts.map +1 -0
- package/dist/dexaCommand.js +226 -0
- package/dist/dexaCommand.js.map +1 -0
- package/dist/export/columns.d.ts +62 -0
- package/dist/export/columns.d.ts.map +1 -0
- package/dist/export/columns.js +127 -0
- package/dist/export/columns.js.map +1 -0
- package/dist/export/csv.d.ts +10 -0
- package/dist/export/csv.d.ts.map +1 -0
- package/dist/export/csv.js +30 -0
- package/dist/export/csv.js.map +1 -0
- package/dist/export/report.d.ts +19 -0
- package/dist/export/report.d.ts.map +1 -0
- package/dist/export/report.js +91 -0
- package/dist/export/report.js.map +1 -0
- package/dist/export/response.d.ts +14 -0
- package/dist/export/response.d.ts.map +1 -0
- package/dist/export/response.js +70 -0
- package/dist/export/response.js.map +1 -0
- package/dist/export/xlsx.d.ts +15 -0
- package/dist/export/xlsx.d.ts.map +1 -0
- package/dist/export/xlsx.js +167 -0
- package/dist/export/xlsx.js.map +1 -0
- package/dist/export/zip.d.ts +7 -0
- package/dist/export/zip.d.ts.map +1 -0
- package/dist/export/zip.js +83 -0
- package/dist/export/zip.js.map +1 -0
- package/dist/files.d.ts +52 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +137 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs.d.ts +57 -0
- package/dist/jobs.d.ts.map +1 -0
- package/dist/jobs.js +157 -0
- package/dist/jobs.js.map +1 -0
- package/dist/log.d.ts +22 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +107 -0
- package/dist/log.js.map +1 -0
- package/dist/notify.d.ts +41 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +11 -0
- package/dist/notify.js.map +1 -0
- package/dist/org.d.ts +184 -0
- package/dist/org.d.ts.map +1 -0
- package/dist/org.js +114 -0
- package/dist/org.js.map +1 -0
- package/dist/people.d.ts +39 -0
- package/dist/people.d.ts.map +1 -0
- package/dist/people.js +88 -0
- package/dist/people.js.map +1 -0
- package/dist/problem.d.ts +12 -0
- package/dist/problem.d.ts.map +1 -0
- package/dist/problem.js +33 -0
- package/dist/problem.js.map +1 -0
- package/dist/runtime.d.ts +92 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +8 -0
- package/dist/runtime.js.map +1 -0
- package/dist/scenarios.d.ts +125 -0
- package/dist/scenarios.d.ts.map +1 -0
- package/dist/scenarios.js +251 -0
- package/dist/scenarios.js.map +1 -0
- package/package.json +55 -3
package/bin/one.mjs
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// `one` — Devoplus One yerel geliştirme komutu (D-72). Uygulama deposu çalışma ortamı aracını ve onun yapılandırma
|
|
3
|
+
// dosyasını görmez: yapılandırma manifest'ten `.one/dev.jsonc`'a üretilir, araç arkada çalıştırılır.
|
|
4
|
+
import { spawn } from 'node:child_process'
|
|
5
|
+
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
6
|
+
import { createRequire } from 'node:module'
|
|
7
|
+
import { homedir } from 'node:os'
|
|
8
|
+
import { dirname, join, resolve } from 'node:path'
|
|
9
|
+
import process from 'node:process'
|
|
10
|
+
import {
|
|
11
|
+
AccessRequestError,
|
|
12
|
+
KINDS,
|
|
13
|
+
buildAccessRequest,
|
|
14
|
+
describeAccessRequest,
|
|
15
|
+
describePortalContext,
|
|
16
|
+
getAccessRequest,
|
|
17
|
+
getPortalContext,
|
|
18
|
+
listMyAccessRequests,
|
|
19
|
+
parsePermissionOptions,
|
|
20
|
+
submitAccessRequest,
|
|
21
|
+
} from './accessRequest.mjs'
|
|
22
|
+
import { DataCommandError, runDataCommand } from './dataCommand.mjs'
|
|
23
|
+
import {
|
|
24
|
+
AppOpsError,
|
|
25
|
+
COMMAND_ACTIONS,
|
|
26
|
+
ROLLBACK_WARNING,
|
|
27
|
+
checkSlug,
|
|
28
|
+
describeDeployment,
|
|
29
|
+
describeDeploymentResult,
|
|
30
|
+
describeInsights,
|
|
31
|
+
getInsights,
|
|
32
|
+
listDeployments,
|
|
33
|
+
parseOpsArgs,
|
|
34
|
+
requestDeployment,
|
|
35
|
+
} from './appOps.mjs'
|
|
36
|
+
import { DEV_CONFIG_FILE, DEV_DIR, OneCliError, USAGE, assertCommandFits, commandArgs, devConfig, readDevManifest } from './devConfig.mjs'
|
|
37
|
+
import {
|
|
38
|
+
SessionError,
|
|
39
|
+
clientsFilePath,
|
|
40
|
+
deviceLogin,
|
|
41
|
+
ensureFresh,
|
|
42
|
+
registerCliClient,
|
|
43
|
+
resolveTarget,
|
|
44
|
+
revokeSession,
|
|
45
|
+
sessionFilePath,
|
|
46
|
+
} from './session.mjs'
|
|
47
|
+
|
|
48
|
+
function option(name) {
|
|
49
|
+
const i = process.argv.indexOf(`--${name}`)
|
|
50
|
+
return i >= 0 ? process.argv[i + 1] : undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Tekrar edilebilir seçeneğin bütün değerleri (`--permission a=b --permission c=d`). */
|
|
54
|
+
function optionAll(name) {
|
|
55
|
+
const values = []
|
|
56
|
+
for (let i = 0; i < process.argv.length; i++) if (process.argv[i] === `--${name}` && i + 1 < process.argv.length) values.push(process.argv[i + 1])
|
|
57
|
+
return values
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Çalışma ortamı aracı önce uygulamanın, sonra SDK'nın bağımlılıklarından çözülür. */
|
|
61
|
+
function runtimeToolBin(appDir) {
|
|
62
|
+
for (const from of [join(appDir, 'package.json'), import.meta.url]) {
|
|
63
|
+
try {
|
|
64
|
+
const pkgPath = createRequire(from).resolve('wrangler/package.json')
|
|
65
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'))
|
|
66
|
+
const bin = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.wrangler
|
|
67
|
+
if (bin) return join(dirname(pkgPath), bin)
|
|
68
|
+
} catch {
|
|
69
|
+
// sonraki konum
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new OneCliError('Devoplus One çalışma ortamı bulunamadı; "pnpm install" çalıştırın.')
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const SESSION_COMMANDS = ['login', 'logout', 'request', 'requests', 'context']
|
|
76
|
+
/** Yayın ve kullanım panosu komutları (D-86 · D-87) — `one login` oturumuyla backend'e gider. */
|
|
77
|
+
const OPS_COMMANDS = ['publish', 'promote', 'rollback', 'deployments', 'status']
|
|
78
|
+
|
|
79
|
+
function sessionFile() {
|
|
80
|
+
return sessionFilePath(process.env, homedir())
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readSession() {
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(readFileSync(sessionFile(), 'utf8'))
|
|
86
|
+
} catch {
|
|
87
|
+
return undefined
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Oturum dosyası yalnız kullanıcının okuyabileceği izinle yazılır (dizin 0700, dosya 0600). */
|
|
92
|
+
function writeSession(session) {
|
|
93
|
+
const file = sessionFile()
|
|
94
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
|
|
95
|
+
writeFileSync(file, JSON.stringify(session, null, 2) + '\n', { mode: 0o600 })
|
|
96
|
+
chmodSync(file, 0o600)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const sleep = (ms) => new Promise((done) => setTimeout(done, ms))
|
|
100
|
+
|
|
101
|
+
function readClients() {
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(readFileSync(clientsFilePath(process.env, homedir()), 'utf8'))
|
|
104
|
+
} catch {
|
|
105
|
+
return {}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function writeClients(clients) {
|
|
110
|
+
const file = clientsFilePath(process.env, homedir())
|
|
111
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 })
|
|
112
|
+
writeFileSync(file, JSON.stringify(clients, null, 2) + '\n', { mode: 0o600 })
|
|
113
|
+
chmodSync(file, 0o600)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* İstemci kimliği verilmediyse (D-78 d) bu kimlik sağlayıcı için daha önce kaydedilmiş olanı kullanır, yoksa dinamik
|
|
118
|
+
* kayıtla yenisini alıp saklar. `fresh: true` saklananı yok sayar (kayıt silinmiş/geçersizse bir kez yenilenir).
|
|
119
|
+
*/
|
|
120
|
+
async function ensureClientId(target, { fresh = false } = {}) {
|
|
121
|
+
if (target.clientId) return { clientId: target.clientId, registered: false }
|
|
122
|
+
const clients = readClients()
|
|
123
|
+
if (!fresh && typeof clients[target.issuer] === 'string') return { clientId: clients[target.issuer], registered: false }
|
|
124
|
+
const clientId = await registerCliClient(target.issuer, { fetch })
|
|
125
|
+
writeClients({ ...clients, [target.issuer]: clientId })
|
|
126
|
+
return { clientId, registered: true }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function freshSession() {
|
|
130
|
+
const { session, changed } = await ensureFresh(readSession(), { fetch })
|
|
131
|
+
if (changed) writeSession(session)
|
|
132
|
+
return session
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Uygulamanın kısa adı: önce --app, sonra bu dizindeki one.manifest.json. */
|
|
136
|
+
function appSlugFromContext(explicit = option('app')) {
|
|
137
|
+
if (explicit) return explicit
|
|
138
|
+
try {
|
|
139
|
+
return readDevManifest(readFileSync(join(process.cwd(), 'one.manifest.json'), 'utf8')).slug
|
|
140
|
+
} catch {
|
|
141
|
+
return undefined
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function sessionCommand(command) {
|
|
146
|
+
if (command === 'login') {
|
|
147
|
+
const explicit = resolveTarget(
|
|
148
|
+
{ env: option('env'), clientId: option('client-id'), issuer: option('auth'), apiBase: option('api-base') },
|
|
149
|
+
process.env,
|
|
150
|
+
)
|
|
151
|
+
const io = {
|
|
152
|
+
fetch,
|
|
153
|
+
sleep,
|
|
154
|
+
scope: option('scope'),
|
|
155
|
+
onPrompt: ({ verificationUri, verificationUriComplete, userCode, expiresIn }) => {
|
|
156
|
+
console.log(`Tarayıcıda şu adresi açın: ${verificationUriComplete ?? verificationUri}`)
|
|
157
|
+
console.log(`Kodu girin: ${userCode} (${Math.round(expiresIn / 60)} dakika geçerli)`)
|
|
158
|
+
console.log('Giriş ve portal seçimi tarayıcıda tamamlanınca burası kendiliğinden devam eder...')
|
|
159
|
+
},
|
|
160
|
+
}
|
|
161
|
+
let client = await ensureClientId(explicit)
|
|
162
|
+
let session
|
|
163
|
+
try {
|
|
164
|
+
session = await deviceLogin({ ...explicit, clientId: client.clientId }, io)
|
|
165
|
+
} catch (err) {
|
|
166
|
+
// Saklanan kayıt kimlik sağlayıcıda artık yoksa bir kez yeniden kaydolunur; açıkça verilen kimlik değiştirilmez.
|
|
167
|
+
if (!(err instanceof SessionError) || err.code !== 'invalid_client' || explicit.clientId || client.registered) throw err
|
|
168
|
+
client = await ensureClientId(explicit, { fresh: true })
|
|
169
|
+
session = await deviceLogin({ ...explicit, clientId: client.clientId }, io)
|
|
170
|
+
}
|
|
171
|
+
writeSession(session)
|
|
172
|
+
console.log(`Giriş tamamlandı (${session.env}). Oturum: ${sessionFile()}`)
|
|
173
|
+
return 0
|
|
174
|
+
}
|
|
175
|
+
if (command === 'logout') {
|
|
176
|
+
const session = readSession()
|
|
177
|
+
if (session) await revokeSession(session, { fetch })
|
|
178
|
+
rmSync(sessionFile(), { force: true })
|
|
179
|
+
console.log('Bu bilgisayardaki oturum silindi.')
|
|
180
|
+
return 0
|
|
181
|
+
}
|
|
182
|
+
if (command === 'requests') {
|
|
183
|
+
const session = await freshSession()
|
|
184
|
+
const options = { apiBase: session.apiBase, accessToken: session.accessToken }
|
|
185
|
+
const id = process.argv[3]
|
|
186
|
+
const rows = id && !id.startsWith('--') ? [await getAccessRequest(options, id)] : await listMyAccessRequests(options)
|
|
187
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(rows, null, 2))
|
|
188
|
+
else if (rows.length === 0) console.log('Talebiniz yok.')
|
|
189
|
+
else for (const row of rows) console.log(describeAccessRequest(row))
|
|
190
|
+
return 0
|
|
191
|
+
}
|
|
192
|
+
if (command === 'context') {
|
|
193
|
+
const session = await freshSession()
|
|
194
|
+
const context = await getPortalContext({ apiBase: session.apiBase, accessToken: session.accessToken })
|
|
195
|
+
console.log(process.argv.includes('--json') ? JSON.stringify(context, null, 2) : describePortalContext(context))
|
|
196
|
+
return 0
|
|
197
|
+
}
|
|
198
|
+
// request
|
|
199
|
+
const kind = process.argv[3]
|
|
200
|
+
if (!kind || kind.startsWith('--')) {
|
|
201
|
+
console.log(REQUEST_USAGE)
|
|
202
|
+
return kind ? 1 : 0
|
|
203
|
+
}
|
|
204
|
+
const sqlFile = option('sql-file')
|
|
205
|
+
const body = buildAccessRequest(kind, {
|
|
206
|
+
appSlug: appSlugFromContext(),
|
|
207
|
+
justification: option('reason'),
|
|
208
|
+
titleTr: option('title'),
|
|
209
|
+
titleEn: option('title-en'),
|
|
210
|
+
additionalPermissions: parsePermissionOptions(optionAll('permission'), optionAll('permission-en')),
|
|
211
|
+
portalKey: option('portal'),
|
|
212
|
+
queryKey: option('query'),
|
|
213
|
+
permissionCode: option('code'),
|
|
214
|
+
permissionNameTr: option('name'),
|
|
215
|
+
permissionNameEn: option('name-en'),
|
|
216
|
+
userGroupId: option('group'),
|
|
217
|
+
scope: option('scope'),
|
|
218
|
+
description: option('description'),
|
|
219
|
+
draftSql: sqlFile ? readFileSync(resolve(process.cwd(), sqlFile), 'utf8') : undefined,
|
|
220
|
+
})
|
|
221
|
+
const session = await freshSession()
|
|
222
|
+
const row = await submitAccessRequest({ apiBase: session.apiBase, accessToken: session.accessToken }, body)
|
|
223
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(row, null, 2))
|
|
224
|
+
else {
|
|
225
|
+
console.log('Talebiniz gönderildi; yönetici onaylayınca e-postayla bildirilir.')
|
|
226
|
+
console.log(describeAccessRequest(row))
|
|
227
|
+
console.log(`Durumu: one requests ${row.id}`)
|
|
228
|
+
}
|
|
229
|
+
return 0
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* `one publish|promote|rollback|deployments|status` (D-86 · D-87). Argümanlar ağa çıkmadan denetlenir; geri alma
|
|
234
|
+
* `--yes` olmadan hiçbir istek göndermez (veri kaybı olabilir).
|
|
235
|
+
*/
|
|
236
|
+
async function opsCommand(command) {
|
|
237
|
+
const args = parseOpsArgs(command, process.argv.slice(3))
|
|
238
|
+
const found = appSlugFromContext(args.app)
|
|
239
|
+
if (!found) throw new AppOpsError('Uygulamanın kısa adı bulunamadı: komutu uygulamanın kök dizininde çalıştırın ya da --app verin.')
|
|
240
|
+
// Oturuma ve ağa dokunmadan önce: kısa ad adrese girer.
|
|
241
|
+
const slug = checkSlug(found)
|
|
242
|
+
if (command === 'rollback' && !args.yes) {
|
|
243
|
+
console.error(ROLLBACK_WARNING)
|
|
244
|
+
console.error(`Onaylıyorsanız komutu --yes ile yeniden çalıştırın: one rollback${args.portalKey ? ` --portal ${args.portalKey}` : ''}${args.toVersion ? ` --to ${args.toVersion}` : ''} --yes`)
|
|
245
|
+
return 1
|
|
246
|
+
}
|
|
247
|
+
const session = await freshSession()
|
|
248
|
+
const options = { apiBase: session.apiBase, accessToken: session.accessToken }
|
|
249
|
+
if (command === 'deployments') {
|
|
250
|
+
const rows = await listDeployments(options, slug, args.portalKey)
|
|
251
|
+
if (args.json) console.log(JSON.stringify(rows, null, 2))
|
|
252
|
+
else if (!Array.isArray(rows) || rows.length === 0) console.log('Yayın isteği yok.')
|
|
253
|
+
else for (const row of rows) console.log(describeDeployment(row))
|
|
254
|
+
return 0
|
|
255
|
+
}
|
|
256
|
+
if (command === 'status') {
|
|
257
|
+
const insights = await getInsights(options, slug, { portalKey: args.portalKey, days: args.days })
|
|
258
|
+
console.log(args.json ? JSON.stringify(insights, null, 2) : describeInsights(insights))
|
|
259
|
+
return 0
|
|
260
|
+
}
|
|
261
|
+
const action = COMMAND_ACTIONS[command]
|
|
262
|
+
const result = await requestDeployment(options, slug, { action, portalKey: args.portalKey, toVersion: args.toVersion })
|
|
263
|
+
if (args.json) console.log(JSON.stringify(result, null, 2))
|
|
264
|
+
else console.log(describeDeploymentResult(result, { action, slug, portalKey: args.portalKey }))
|
|
265
|
+
return 0
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const REQUEST_USAGE = `kullanım: one request <tür> [seçenekler]
|
|
269
|
+
|
|
270
|
+
Türler:
|
|
271
|
+
${Object.entries(KINDS)
|
|
272
|
+
.map(([name, kind]) => ` ${name.padEnd(15)} ${kind.label}${kind.audience === 'platform' ? ' (platform yöneticisine)' : ''}`)
|
|
273
|
+
.join('\n')}
|
|
274
|
+
|
|
275
|
+
Ortak: --app <kısa ad> (varsayılan: one.manifest.json dosyasındaki app.slug), --reason "<gerekçe>", --json
|
|
276
|
+
registration --title "<Türkçe ad>" [--title-en "<İngilizce ad>"]
|
|
277
|
+
[--permission <One.Uygulama.Eylem>="<Türkçe ad>"]... [--permission-en <One.Uygulama.Eylem>="<İngilizce ad>"]...
|
|
278
|
+
(uygulama içi izinler kayıtla birlikte istenir, en çok 16; sonradan eklemek platform yöneticisini ister)
|
|
279
|
+
installation (portal anahtarını sunucu portalın adından belirler, elle seçilemez; --portal yok sayılır;
|
|
280
|
+
portalın adı ve var olan anahtarı: one context)
|
|
281
|
+
dexa-query --query <sorgu anahtarı>
|
|
282
|
+
dexa-command --query <işlem anahtarı>
|
|
283
|
+
app-permission --code <One.Uygulama.Eylem> [--name "<Türkçe ad>"] [--name-en "<İngilizce ad>"] [--group <grup kimliği>]
|
|
284
|
+
(kod uygulamanın kaydında tanımlı olmalı; değilse talep reddedilir)
|
|
285
|
+
scope --scope <modül.read|write|manage|execute> ya da ai.use
|
|
286
|
+
new-query --description "<ihtiyaç>" [--sql-file <taslak.sql>] (taslak hiçbir yerde ÇALIŞTIRILMAZ)
|
|
287
|
+
|
|
288
|
+
Talep yalnız uygulamanın sahibi tarafından gönderilir (kayıt talebi hariç); portal ve kimlik oturumdan gelir.
|
|
289
|
+
Kayıt, kurulum, sorgu ve izin taleplerini portalın şirket yöneticisi, yetki alanı taleplerini platform yöneticisi onaylar.`
|
|
290
|
+
|
|
291
|
+
function main() {
|
|
292
|
+
const command = process.argv[2]
|
|
293
|
+
if (!command || command === '--help' || command === '-h') {
|
|
294
|
+
console.log(USAGE)
|
|
295
|
+
return 0
|
|
296
|
+
}
|
|
297
|
+
if (SESSION_COMMANDS.includes(command)) return sessionCommand(command)
|
|
298
|
+
if (command === 'data') return runDataCommand({ cwd: process.cwd(), args: process.argv.slice(3) })
|
|
299
|
+
if (OPS_COMMANDS.includes(command)) return opsCommand(command)
|
|
300
|
+
const appDir = process.cwd()
|
|
301
|
+
let raw
|
|
302
|
+
try {
|
|
303
|
+
raw = readFileSync(join(appDir, 'one.manifest.json'), 'utf8')
|
|
304
|
+
} catch {
|
|
305
|
+
throw new OneCliError('Bu dizinde one.manifest.json yok; komutu uygulamanın kök dizininde çalıştırın.')
|
|
306
|
+
}
|
|
307
|
+
const manifest = readDevManifest(raw)
|
|
308
|
+
assertCommandFits(command, manifest)
|
|
309
|
+
const args = commandArgs(command, { port: option('port'), outDir: resolve(appDir, DEV_DIR, 'dist') })
|
|
310
|
+
const config = devConfig(manifest, { apiBase: option('api-base') ?? process.env.ONE_API_BASE })
|
|
311
|
+
mkdirSync(resolve(appDir, DEV_DIR), { recursive: true })
|
|
312
|
+
writeFileSync(
|
|
313
|
+
resolve(appDir, DEV_DIR, DEV_CONFIG_FILE),
|
|
314
|
+
'// one tarafından üretilir; elle düzenlemeyin, her komutta one.manifest.json\'dan yeniden yazılır.\n' +
|
|
315
|
+
JSON.stringify(config, null, 2) +
|
|
316
|
+
'\n',
|
|
317
|
+
)
|
|
318
|
+
const child = spawn(process.execPath, [runtimeToolBin(appDir), ...args], {
|
|
319
|
+
cwd: appDir,
|
|
320
|
+
stdio: 'inherit',
|
|
321
|
+
env: { ...process.env, WRANGLER_SEND_METRICS: 'false' },
|
|
322
|
+
})
|
|
323
|
+
child.on('exit', (code, signal) => process.exit(signal ? 1 : (code ?? 1)))
|
|
324
|
+
return undefined
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function fail(err) {
|
|
328
|
+
if (err instanceof OneCliError || err instanceof SessionError || err instanceof AccessRequestError || err instanceof DataCommandError || err instanceof AppOpsError) {
|
|
329
|
+
console.error(`one: ${err.message}`)
|
|
330
|
+
process.exit(1)
|
|
331
|
+
}
|
|
332
|
+
throw err
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
const code = main()
|
|
337
|
+
if (code instanceof Promise) code.then((c) => process.exit(c), fail)
|
|
338
|
+
else if (code !== undefined) process.exit(code)
|
|
339
|
+
} catch (err) {
|
|
340
|
+
fail(err)
|
|
341
|
+
}
|
package/bin/session.mjs
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
// `one login` oturumu (D-75): tarayıcısız giriş (cihaz kodu, RFC 8628) + belirteç yenileme + yerel saklama.
|
|
2
|
+
//
|
|
3
|
+
// Ağ ve dosya sistemi ÇAĞIRANDAN gelir (`fetch`, `sleep`, `now`) → testlenir (session.test.ts). Komutların kendisi
|
|
4
|
+
// one.mjs'tedir.
|
|
5
|
+
//
|
|
6
|
+
// ⛔ Belirteç yalnız kullanıcının kendi makinesinde, yalnız kullanıcının okuyabildiği dosyada (0600) durur ve yalnız
|
|
7
|
+
// kimlik sağlayıcıya ve Devoplus API'ye gider. Ekrana, günlüğe ya da hata metnine YAZILMAZ.
|
|
8
|
+
|
|
9
|
+
/** `https://…/` ya da yerel `http://localhost|127.0.0.1[:port]/…/` — belirteç taşıyan her taban bu kuralla denetlenir. */
|
|
10
|
+
export function isSecureBase(base) {
|
|
11
|
+
return (
|
|
12
|
+
/^https:\/\/[^\s/]+(\/[^\s]*)?\/$/.test(base ?? '') ||
|
|
13
|
+
/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/([^\s]*\/)?$/.test(base ?? '')
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class SessionError extends Error {
|
|
18
|
+
/** Kimlik sağlayıcının OAuth hata kodu (`invalid_client` …), varsa. */
|
|
19
|
+
constructor(message, code) {
|
|
20
|
+
super(message)
|
|
21
|
+
this.code = code
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Ortamlar — kimlik sağlayıcı ve API adresleri (workspace "Alan adları" tablosu). Komut satırı istemcisinin kimliği
|
|
27
|
+
* sabit DEĞİLDİR (D-78 d): `--client-id` / `ONE_CLIENT_ID` verilmezse `one login` kimlik sağlayıcıya dinamik kayıtla
|
|
28
|
+
* (RFC 7591) kendini kaydeder ve kimliği bu bilgisayarda saklar.
|
|
29
|
+
*/
|
|
30
|
+
export const ENVIRONMENTS = {
|
|
31
|
+
test: { issuer: 'https://test-auth.devoplus.dev', apiBase: 'https://test-api.devoplus.dev/' },
|
|
32
|
+
uat: { issuer: 'https://uat-auth.devoplus.dev', apiBase: 'https://uat-api.devoplus.dev/' },
|
|
33
|
+
prod: { issuer: 'https://auth.devoplus.com.tr', apiBase: 'https://api.devoplus.com.tr/' },
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const DEFAULT_ENVIRONMENT = 'test'
|
|
37
|
+
/**
|
|
38
|
+
* Dar ajan kapsamı (D-78 d): dinamik kaydın kendiliğinden etkin olmasının koşulu bu kümenin alt kümesi + `onedev.write`.
|
|
39
|
+
* Belirteç backend'de yalnız erişim talebi uçlarına ve uygulamanın yayın/pano uçlarına (D-86 · D-87, C5) geçer; portal verisine erişmez.
|
|
40
|
+
*/
|
|
41
|
+
export const AGENT_SCOPE = 'openid profile email offline_access onedev.write'
|
|
42
|
+
export const DEFAULT_SCOPE = AGENT_SCOPE
|
|
43
|
+
export const CLI_CLIENT_NAME = 'Devoplus One komut satırı'
|
|
44
|
+
const DEVICE_GRANT = 'urn:ietf:params:oauth:grant-type:device_code'
|
|
45
|
+
/** Süresi dolmadan bu kadar saniye önce yenilenir (istek yoldayken dolmasın). */
|
|
46
|
+
const REFRESH_MARGIN_SECONDS = 60
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Ortamı ve istemci kimliğini çözer. Öncelik: komut satırı → ortam değişkeni → ortam tablosu.
|
|
50
|
+
* @param {{ env?: string, clientId?: string, issuer?: string, apiBase?: string }} options
|
|
51
|
+
* @param {Record<string, string | undefined>} vars `process.env`
|
|
52
|
+
*/
|
|
53
|
+
export function resolveTarget(options = {}, vars = {}) {
|
|
54
|
+
const name = options.env ?? vars.ONE_ENV ?? DEFAULT_ENVIRONMENT
|
|
55
|
+
const preset = ENVIRONMENTS[name]
|
|
56
|
+
if (!preset) throw new SessionError(`ortam tanınmadı: ${name} (${Object.keys(ENVIRONMENTS).join(', ')})`)
|
|
57
|
+
const issuer = (options.issuer ?? vars.ONE_AUTH_ISSUER ?? preset.issuer).replace(/\/+$/, '')
|
|
58
|
+
const apiBase = options.apiBase ?? vars.ONE_API_BASE ?? preset.apiBase
|
|
59
|
+
const clientId = options.clientId ?? vars.ONE_CLIENT_ID ?? undefined
|
|
60
|
+
if (!/^https:\/\/[^\s/]+$/.test(issuer) && !/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(issuer))
|
|
61
|
+
throw new SessionError(`kimlik sağlayıcı adresi "https://..." biçiminde olmalı: ${issuer}`)
|
|
62
|
+
// Belirteç bu adrese gider: düz http yalnız yerel geliştirme adresinde kabul edilir (RFC 6750 §5.3).
|
|
63
|
+
if (!isSecureBase(apiBase)) throw new SessionError(`API tabanı "https://.../" biçiminde ve "/" ile bitmeli (http yalnız localhost): ${apiBase}`)
|
|
64
|
+
return { env: name, issuer, apiBase, clientId: clientId || undefined }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function postForm(fetchImpl, url, form) {
|
|
68
|
+
let response
|
|
69
|
+
try {
|
|
70
|
+
response = await fetchImpl(url, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
73
|
+
body: new URLSearchParams(form).toString(),
|
|
74
|
+
})
|
|
75
|
+
} catch {
|
|
76
|
+
throw new SessionError(`kimlik sağlayıcıya ulaşılamadı: ${new URL(url).origin}`)
|
|
77
|
+
}
|
|
78
|
+
let body = {}
|
|
79
|
+
try {
|
|
80
|
+
body = await response.json()
|
|
81
|
+
} catch {
|
|
82
|
+
// gövdesiz yanıt
|
|
83
|
+
}
|
|
84
|
+
return { status: response.status, ok: response.ok, body }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function tokenSession(target, body, now) {
|
|
88
|
+
if (typeof body.access_token !== 'string' || !body.access_token)
|
|
89
|
+
throw new SessionError('kimlik sağlayıcı erişim belirteci döndürmedi.')
|
|
90
|
+
const expiresIn = Number.isFinite(body.expires_in) ? body.expires_in : 0
|
|
91
|
+
return {
|
|
92
|
+
env: target.env,
|
|
93
|
+
issuer: target.issuer,
|
|
94
|
+
apiBase: target.apiBase,
|
|
95
|
+
clientId: target.clientId,
|
|
96
|
+
accessToken: body.access_token,
|
|
97
|
+
refreshToken: typeof body.refresh_token === 'string' ? body.refresh_token : undefined,
|
|
98
|
+
expiresAt: Math.floor(now() / 1000) + expiresIn,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Komut satırı istemcisini kimlik sağlayıcıya dinamik kayıtla (RFC 7591) kaydeder ve `client_id` döndürür.
|
|
104
|
+
* Public istemcidir (sır yok); yalnız cihaz kodu + yenileme grant'ı ve dar ajan kapsamını ister (D-78 d).
|
|
105
|
+
*/
|
|
106
|
+
export async function registerCliClient(issuer, io) {
|
|
107
|
+
let response
|
|
108
|
+
try {
|
|
109
|
+
response = await io.fetch(`${issuer}/register`, {
|
|
110
|
+
method: 'POST',
|
|
111
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
client_name: CLI_CLIENT_NAME,
|
|
114
|
+
grant_types: [DEVICE_GRANT, 'refresh_token'],
|
|
115
|
+
token_endpoint_auth_method: 'none',
|
|
116
|
+
scope: AGENT_SCOPE,
|
|
117
|
+
}),
|
|
118
|
+
})
|
|
119
|
+
} catch {
|
|
120
|
+
throw new SessionError(`kimlik sağlayıcıya ulaşılamadı: ${new URL(issuer).origin}`)
|
|
121
|
+
}
|
|
122
|
+
let body = {}
|
|
123
|
+
try {
|
|
124
|
+
body = await response.json()
|
|
125
|
+
} catch {
|
|
126
|
+
// gövdesiz yanıt
|
|
127
|
+
}
|
|
128
|
+
if (!response.ok || typeof body.client_id !== 'string' || !body.client_id)
|
|
129
|
+
throw new SessionError(oauthMessage(body, 'komut satırı istemcisi kaydedilemedi'), body?.error)
|
|
130
|
+
return body.client_id
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Cihaz kodu girişi. `onPrompt` kullanıcıya adresi ve kodu gösterir; ardından onay beklenir.
|
|
135
|
+
* @param {ReturnType<typeof resolveTarget>} target
|
|
136
|
+
* @param {{ fetch: typeof fetch, sleep: (ms: number) => Promise<void>, now?: () => number,
|
|
137
|
+
* onPrompt: (p: { verificationUri: string, verificationUriComplete?: string, userCode: string, expiresIn: number }) => void,
|
|
138
|
+
* scope?: string }} io
|
|
139
|
+
*/
|
|
140
|
+
export async function deviceLogin(target, io) {
|
|
141
|
+
const now = io.now ?? Date.now
|
|
142
|
+
const start = await postForm(io.fetch, `${target.issuer}/device_authorization`, {
|
|
143
|
+
client_id: target.clientId,
|
|
144
|
+
scope: io.scope ?? DEFAULT_SCOPE,
|
|
145
|
+
})
|
|
146
|
+
if (!start.ok) throw new SessionError(oauthMessage(start.body, 'giriş başlatılamadı'), start.body?.error)
|
|
147
|
+
const { device_code: deviceCode, user_code: userCode, verification_uri: verificationUri } = start.body
|
|
148
|
+
if (typeof deviceCode !== 'string' || typeof userCode !== 'string' || typeof verificationUri !== 'string')
|
|
149
|
+
throw new SessionError('kimlik sağlayıcının cihaz kodu yanıtı eksik.')
|
|
150
|
+
const expiresIn = Number(start.body.expires_in) || 900
|
|
151
|
+
let interval = Math.max(Number(start.body.interval) || 5, 1)
|
|
152
|
+
io.onPrompt({
|
|
153
|
+
verificationUri,
|
|
154
|
+
verificationUriComplete:
|
|
155
|
+
typeof start.body.verification_uri_complete === 'string' ? start.body.verification_uri_complete : undefined,
|
|
156
|
+
userCode,
|
|
157
|
+
expiresIn,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const deadline = now() + expiresIn * 1000
|
|
161
|
+
while (now() < deadline) {
|
|
162
|
+
await io.sleep(interval * 1000)
|
|
163
|
+
const poll = await postForm(io.fetch, `${target.issuer}/token`, {
|
|
164
|
+
grant_type: DEVICE_GRANT,
|
|
165
|
+
device_code: deviceCode,
|
|
166
|
+
client_id: target.clientId,
|
|
167
|
+
})
|
|
168
|
+
if (poll.ok) return tokenSession(target, poll.body, now)
|
|
169
|
+
const error = poll.body?.error
|
|
170
|
+
if (error === 'authorization_pending') continue
|
|
171
|
+
if (error === 'slow_down') {
|
|
172
|
+
interval += 5
|
|
173
|
+
continue
|
|
174
|
+
}
|
|
175
|
+
if (error === 'access_denied') throw new SessionError('giriş tarayıcıda reddedildi.')
|
|
176
|
+
if (error === 'expired_token') break
|
|
177
|
+
throw new SessionError(oauthMessage(poll.body, 'giriş tamamlanamadı'))
|
|
178
|
+
}
|
|
179
|
+
throw new SessionError('giriş kodunun süresi doldu; "one login" komutunu yeniden çalıştırın.')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Geçerli bir erişim belirteci döndürür; süresi dolmak üzereyse yenileme belirteciyle yeniler. Yenilenemezse
|
|
184
|
+
* `SessionError` (yeniden giriş gerekir). Dönüşte `changed: true` ise çağıran yeni oturumu kaydeder (yenileme
|
|
185
|
+
* belirteci her kullanımda değişir — eskisi bir daha kabul edilmez).
|
|
186
|
+
*/
|
|
187
|
+
export async function ensureFresh(session, io) {
|
|
188
|
+
const now = io.now ?? Date.now
|
|
189
|
+
if (!session?.accessToken) throw new SessionError('oturum yok; önce "one login" çalıştırın.')
|
|
190
|
+
if (session.expiresAt - REFRESH_MARGIN_SECONDS > Math.floor(now() / 1000)) return { session, changed: false }
|
|
191
|
+
if (!session.refreshToken) throw new SessionError('oturumun süresi doldu; "one login" ile yeniden giriş yapın.')
|
|
192
|
+
const result = await postForm(io.fetch, `${session.issuer}/token`, {
|
|
193
|
+
grant_type: 'refresh_token',
|
|
194
|
+
refresh_token: session.refreshToken,
|
|
195
|
+
client_id: session.clientId,
|
|
196
|
+
})
|
|
197
|
+
if (!result.ok) throw new SessionError('oturum yenilenemedi; "one login" ile yeniden giriş yapın.')
|
|
198
|
+
const next = tokenSession(session, result.body, now)
|
|
199
|
+
// Yanıt yeni yenileme belirteci taşımıyorsa eskisi korunur (RFC 6749 §6: sunucu yenisini vermeyebilir).
|
|
200
|
+
// Döndürme açık sağlayıcıda yanıt her zaman yenisini taşır; o zaman eskisi zaten geçersizdir ve kullanılmaz.
|
|
201
|
+
if (next.refreshToken === undefined && session.refreshToken) next.refreshToken = session.refreshToken
|
|
202
|
+
return { session: next, changed: true }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Yenileme belirtecini kimlik sağlayıcıda iptal eder (RFC 7009). Başarısızlık sessizdir — yerel kayıt yine silinir. */
|
|
206
|
+
export async function revokeSession(session, io) {
|
|
207
|
+
if (!session?.refreshToken) return false
|
|
208
|
+
try {
|
|
209
|
+
const result = await postForm(io.fetch, `${session.issuer}/revoke`, {
|
|
210
|
+
token: session.refreshToken,
|
|
211
|
+
token_type_hint: 'refresh_token',
|
|
212
|
+
client_id: session.clientId,
|
|
213
|
+
})
|
|
214
|
+
return result.ok
|
|
215
|
+
} catch {
|
|
216
|
+
return false
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function oauthMessage(body, prefix) {
|
|
221
|
+
const description = typeof body?.error_description === 'string' ? body.error_description : undefined
|
|
222
|
+
const code = typeof body?.error === 'string' ? body.error : undefined
|
|
223
|
+
return description ? `${prefix}: ${description}` : code ? `${prefix} (${code})` : prefix
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function configDir(vars, home) {
|
|
227
|
+
const base = vars.XDG_CONFIG_HOME && vars.XDG_CONFIG_HOME.trim() ? vars.XDG_CONFIG_HOME : `${home}/.config`
|
|
228
|
+
return `${base.replace(/\/+$/, '')}/devoplus-one`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Oturum dosyasının yolu: `$XDG_CONFIG_HOME/devoplus-one/session.json`, yoksa `~/.config/devoplus-one/session.json`. */
|
|
232
|
+
export function sessionFilePath(vars, home) {
|
|
233
|
+
return `${configDir(vars, home)}/session.json`
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Dinamik kayıtla alınmış istemci kimlikleri (kimlik sağlayıcı adresi → `client_id`); oturumdan ayrı, çıkışta silinmez. */
|
|
237
|
+
export function clientsFilePath(vars, home) {
|
|
238
|
+
return `${configDir(vars, home)}/clients.json`
|
|
239
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const AGENT_INPUT_KEYWORDS: readonly ['type', 'properties', 'required', 'enum', 'minimum', 'maximum', 'minLength', 'maxLength', 'items', 'description'];
|
|
2
|
+
/** Bir alan hatası: `path` girdi içindeki yol (`limit`, `tags.2`); kök için `(kök)`. */
|
|
3
|
+
export interface AgentInputError {
|
|
4
|
+
path: string;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/** Girdiyi şemaya göre denetler; boş dizi = geçerli. */
|
|
8
|
+
export declare function validateAgentInput(schema: unknown, input: unknown): AgentInputError[];
|
|
9
|
+
//# sourceMappingURL=agentInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentInput.d.ts","sourceRoot":"","sources":["../src/agentInput.ts"],"names":[],"mappings":"AAWA,eAAO,MAAM,oBAAoB,YAAI,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,CAAU,CAAA;AAE/J,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB;AAoBD,wDAAwD;AACxD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,eAAe,EAAE,CAIrF"}
|