tsoft-cli 2.6.15 → 3.4.0

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.
@@ -0,0 +1,14 @@
1
+ import chalk from 'chalk';
2
+ import { t } from '../i18n.js';
3
+
4
+ /**
5
+ * Tema submit komutu (deprecated) - Partner panel rehberi gosterir
6
+ */
7
+ export async function themeSubmitCommand(themeName) {
8
+ console.log(chalk.yellow('\n' + t('submit.deprecated')));
9
+ console.log('');
10
+ console.log(chalk.gray(' ' + t('submit.step1')));
11
+ console.log(chalk.gray(' ' + t('submit.step2')));
12
+ console.log(chalk.gray(' ' + t('submit.step3')));
13
+ process.exit(0);
14
+ }
@@ -0,0 +1,474 @@
1
+ import chalk from 'chalk';
2
+ import {input} from '@inquirer/prompts';
3
+ import fs from 'fs/promises';
4
+ import {createWriteStream} from 'fs';
5
+ import path from 'path';
6
+ import AdmZip from 'adm-zip';
7
+ import {createApiClient} from '../api-client.js';
8
+ import {normalizeStoreDomain, slugify, setActiveTheme, getActiveStore} from '../storage.js';
9
+ import {brandedConfirm, brandedSelect, showWarning} from '../ui/prompt-wrapper.js';
10
+ import { isJsonMode, jsonOut } from '../output-mode.js';
11
+ import { mapError } from '../errors/error-mapper.js';
12
+ import { t } from '../i18n.js';
13
+
14
+ export async function themeListCommand() {
15
+ try {
16
+ if (!isJsonMode()) {
17
+ console.log(chalk.cyan(t('theme.listing') + '\n'));
18
+ }
19
+
20
+ const apiClient = await createApiClient();
21
+ const response = await apiClient.get('/theme');
22
+
23
+ const themes = response.data || [];
24
+
25
+ if (isJsonMode()) {
26
+ jsonOut({
27
+ status: 'success',
28
+ command: 'theme list',
29
+ data: {
30
+ themes: themes.map(t => ({
31
+ name: t.name,
32
+ version: t.version,
33
+ theme_folder: t.theme_folder || t.alias,
34
+ active: Boolean(t.active),
35
+ description: t.description || null,
36
+ }))
37
+ }
38
+ });
39
+ return;
40
+ }
41
+
42
+ if (themes.length === 0) {
43
+ console.log(chalk.yellow(t('theme.no_themes') + '\n'));
44
+ return;
45
+ }
46
+
47
+ console.log(chalk.bold.cyan(t('theme.count', { count: themes.length }) + '\n'));
48
+
49
+ themes.forEach((theme) => {
50
+ const activeIndicator = theme.active ? chalk.green('●') : chalk.gray('○');
51
+ const name = theme.active ? chalk.bold.green(theme.name) : chalk.white(theme.name);
52
+ const version = chalk.gray(`v${theme.version}`);
53
+ const description = theme.description ? chalk.gray(` - ${theme.description}`) : '';
54
+
55
+ console.log(`${activeIndicator} ${name} ${version}${description}`);
56
+ console.log(chalk.gray(` ${t('theme.folder_label')} ${theme.theme_folder || theme.alias}`));
57
+ console.log();
58
+ });
59
+
60
+ } catch (error) {
61
+ if (isJsonMode()) {
62
+ const mapped = mapError(error, { command: 'theme list' });
63
+ jsonOut({ status: 'error', command: 'theme list', error: { message: mapped.message, hint: mapped.hint } });
64
+ process.exit(1);
65
+ }
66
+ console.error(chalk.red('\n❌ Hata: ') + error.message);
67
+ if (error.response?.status === 401) {
68
+ console.log(chalk.yellow(' ' + t('whoami.relogin_tip')));
69
+ }
70
+ process.exit(1);
71
+ }
72
+ }
73
+
74
+ export async function themeCreateCommand() {
75
+ try {
76
+ console.log(chalk.cyan(t('theme.creating') + '\n'));
77
+
78
+ const name = await input({
79
+ message: t('theme.name_prompt'), validate: (value) => {
80
+ if (!value.trim()) {
81
+ return t('theme.name_required');
82
+ }
83
+ return true;
84
+ }
85
+ });
86
+
87
+ const description = await input({
88
+ message: t('theme.description_prompt'), default: '',
89
+ });
90
+
91
+ const author = await input({
92
+ message: t('theme.author_prompt'), default: '',
93
+ });
94
+
95
+
96
+ const minimal = await brandedConfirm({
97
+ message: t('theme.minimal_prompt'), default: false,
98
+ }, t('theme.minimal_title'));
99
+
100
+ const themeSlug = slugify(name);
101
+ const store = await getActiveStore();
102
+ const storeSlug = normalizeStoreDomain(store);
103
+ const themePath = path.join(process.cwd(), storeSlug, themeSlug);
104
+
105
+ const exists = await fs.access(themePath).then(() => true).catch(() => false);
106
+ if (exists) {
107
+ const overwrite = await brandedConfirm({
108
+ message: t('theme.dir_exists_prompt', { path: themePath }), default: false,
109
+ }, t('theme.dir_exists_title'));
110
+
111
+ if (!overwrite) {
112
+ showWarning(t('theme.cancelled'));
113
+ return;
114
+ }
115
+
116
+ await fs.rm(themePath, {recursive: true, force: true});
117
+ }
118
+
119
+ console.log(chalk.yellow('\n' + t('theme.updating')));
120
+
121
+ const apiClient = await createApiClient();
122
+
123
+ const themeData = {
124
+ name: name.trim(),
125
+ author: author.trim() || null,
126
+ theme_folder: themeSlug,
127
+ description: description.trim() || null,
128
+ active: 0,
129
+ hidden: 0,
130
+ forceUpdate: false
131
+ };
132
+
133
+ console.log(chalk.yellow(t('theme.saving_db')));
134
+
135
+ let createResult;
136
+
137
+ try {
138
+ createResult = await apiClient.post('/theme', themeData);
139
+ } catch (error) {
140
+ // Network/Connection hatası kontrolü
141
+ if (error.message.includes('bağlanılamadı') || error.message.includes('İnternet')) {
142
+ console.error(chalk.red('\n❌ ' + t('theme.connection_error')));
143
+ console.log(chalk.yellow('\nDefault tema güncellik kontrolü yapılamadı.'));
144
+ console.log(chalk.gray(t('theme.connection_check') + '\n'));
145
+ process.exit(1);
146
+ }
147
+
148
+ // VALIDATION_ERROR kontrolü - requiresForceUpdate durumu
149
+ if (error.message.startsWith('VALIDATION_ERROR:')) {
150
+ const errorMsg = error.message.replace('VALIDATION_ERROR:', '').trim();
151
+
152
+ // Backend'den gelen response'u parse et
153
+ let errorData;
154
+ try {
155
+ errorData = JSON.parse(errorMsg);
156
+ } catch {
157
+ errorData = {
158
+ requiresForceUpdate: true,
159
+ reason: 'default_theme_outdated',
160
+ message: errorMsg
161
+ };
162
+ }
163
+
164
+ // requiresForceUpdate kontrolü
165
+ if (errorData.requiresForceUpdate === true) {
166
+ // Reason'a göre özel mesajlar
167
+ const reasonDetails = {
168
+ 'no_version_installed': {
169
+ title: '📦 Default Tema Versiyonu Yok',
170
+ description: 'Sisteminizde default tema versiyonu yüklenmemiş.',
171
+ impact: 'Yeni tema güvenli bir şekilde oluşturulamaz.'
172
+ },
173
+ 'default_theme_not_installed': {
174
+ title: '⚠️ Default Tema Yüklenmemiş',
175
+ description: 'Sisteminizde default tema bulunamadı.',
176
+ impact: 'Default tema olmadan yeni tema geliştirilemez.'
177
+ },
178
+ 'default_theme_outdated': {
179
+ title: '🔄 Default Tema Güncel Değil',
180
+ description: 'Sisteminizde yüklü olan default tema güncel değil.',
181
+ impact: 'Güncel özellikler ve düzeltmeler yeni temanızda eksik olacak.'
182
+ },
183
+ 'check_failed': {
184
+ title: '⚠️ Default Tema Kontrolü Başarısız',
185
+ description: 'Default tema güncellik kontrolü yapılamadı.',
186
+ impact: 'Güvenlik nedeniyle default tema güncellenmelidir.'
187
+ },
188
+ 'update_failed': {
189
+ title: '❌ Güncelleme Başarısız',
190
+ description: 'Default tema güncellenirken bir hata oluştu.',
191
+ impact: 'Sistem yöneticinizle iletişime geçin.'
192
+ }
193
+ };
194
+
195
+ const reasonInfo = reasonDetails[errorData.reason] || {
196
+ title: '⚠️ Default Tema Güncelleme Gerekli',
197
+ description: errorData.reason || 'Bilinmeyen bir durum tespit edildi.',
198
+ impact: 'Devam etmek için default tema güncellenmelidir.'
199
+ };
200
+
201
+ console.log(chalk.yellow(`\n${reasonInfo.title}\n`));
202
+ console.log(chalk.gray(`📋 Durum: ${reasonInfo.description}`));
203
+ console.log(chalk.gray(`💡 Etki: ${reasonInfo.impact}`));
204
+
205
+ if (errorData.currentVersion) {
206
+ console.log(chalk.gray(`📌 Mevcut Version: ${errorData.currentVersion}`));
207
+ }
208
+
209
+ console.log(); // Boş satır
210
+
211
+ // update_failed durumunda işlemi durdur
212
+ if (errorData.reason === 'update_failed') {
213
+ console.error(chalk.red('❌ İşlem durdu: ' + (errorData.message || 'Güncelleme başarısız oldu')));
214
+ console.log(chalk.yellow('\n💡 ' + t('theme.update_failed_contact') + '\n'));
215
+ process.exit(1);
216
+ }
217
+
218
+ // Kullanıcıya onay sor
219
+ const shouldUpdate = await brandedConfirm({
220
+ message: t('theme.force_update_confirm'),
221
+ default: true
222
+ }, t('theme.force_update_title'));
223
+
224
+ if (!shouldUpdate) {
225
+ showWarning(t('theme.cancelled'));
226
+ return;
227
+ }
228
+
229
+ // Loading mesajı göster
230
+ console.log(chalk.cyan('\n' + t('theme.force_updating')));
231
+ console.log(chalk.gray('━'.repeat(60)));
232
+ console.log(chalk.gray('🔹 Default tema son versiyonuna yükseltiliyor...'));
233
+ console.log(chalk.gray('🔹 Layout ve section\'lar import ediliyor...'));
234
+ console.log(chalk.gray('🔹 Yeni tema bootstrap ediliyor...'));
235
+ console.log(chalk.gray('━'.repeat(60)));
236
+ console.log(chalk.yellow('\n⏱️ Lütfen bekleyin, bu işlem birkaç dakika sürebilir...\n'));
237
+
238
+ // forceUpdate: true ile tekrar dene
239
+ themeData.forceUpdate = true;
240
+
241
+ try {
242
+ createResult = await apiClient.post('/theme', themeData);
243
+ console.log(chalk.green('\n' + t('theme.force_update_success_1')));
244
+ console.log(chalk.green(t('theme.force_update_success_2') + '\n'));
245
+ } catch (retryError) {
246
+ if (retryError.message.startsWith('VALIDATION_ERROR:')) {
247
+ const retryErrorMsg = retryError.message.replace('VALIDATION_ERROR:', '');
248
+ let retryErrorData;
249
+
250
+ try {
251
+ retryErrorData = JSON.parse(retryErrorMsg);
252
+ console.error(chalk.red('\n❌ Hata: ' + (retryErrorData.message || 'İşlem başarısız oldu')));
253
+ } catch {
254
+ console.error(chalk.red('\n❌ Hata: ' + retryErrorMsg));
255
+ }
256
+ } else {
257
+ console.error(chalk.red('\n❌ Hata: ' + retryError.message));
258
+ }
259
+
260
+ console.log(chalk.yellow('\n💡 ' + t('theme.update_failed_contact') + '\n'));
261
+ process.exit(1);
262
+ }
263
+ } else {
264
+ // requiresForceUpdate olmayan başka bir validation hatası
265
+ throw error;
266
+ }
267
+ } else {
268
+ // VALIDATION_ERROR değil, başka bir hata
269
+ throw error;
270
+ }
271
+ }
272
+
273
+ console.log(chalk.green(t('theme.saved_db')));
274
+
275
+ const downloadParams = {
276
+ minimal: minimal,
277
+ name: name.trim(),
278
+ description: description.trim() || undefined,
279
+ author: author.trim() || undefined,
280
+ };
281
+
282
+ console.log(chalk.yellow(t('theme.downloading_files')));
283
+
284
+ const response = await apiClient.download('/theme/default/download', downloadParams);
285
+
286
+ await fs.mkdir(themePath, {recursive: true});
287
+
288
+ const zipPath = path.join(themePath, 'theme.zip');
289
+ const writer = createWriteStream(zipPath);
290
+
291
+ response.data.pipe(writer);
292
+
293
+ writer.on('finish', async () => {
294
+ console.log(chalk.green(t('theme.files_downloaded')));
295
+ console.log(chalk.yellow(t('theme.extracting')));
296
+
297
+ const zip = new AdmZip(zipPath);
298
+ zip.extractAllTo(themePath, true);
299
+
300
+ await fs.unlink(zipPath);
301
+
302
+ console.log(chalk.green(t('theme.extracted', { path: `${storeSlug}/${themeSlug}/` })));
303
+
304
+ await setActiveTheme(themeSlug);
305
+ console.log(chalk.green(t('theme.active_set') + '\n'));
306
+
307
+ console.log(chalk.bold.green(t('theme.create_success')));
308
+ console.log(chalk.gray(' ' + t('theme.create_location', { path: `${storeSlug}/${themeSlug}/` }) + '\n'));
309
+ console.log(chalk.cyan(t('theme.dev_start_tip')));
310
+ console.log(chalk.white(` cd ${storeSlug}/${themeSlug}\n`));
311
+ });
312
+
313
+ writer.on('error', (error) => {
314
+ console.error(chalk.red('❌ Hata: ' + t('theme.file_write_error')), error.message);
315
+ process.exit(1);
316
+ });
317
+
318
+ } catch (error) {
319
+ console.error(chalk.red('\n❌ Hata: ') + error.message);
320
+
321
+ if (error.message.includes('VALIDATION_ERROR')) {
322
+ console.log(chalk.yellow('\n💡 ' + t('theme.validation_tip')));
323
+ }
324
+
325
+ if (error.message.includes('Token bulunamadı')) {
326
+ console.log(chalk.yellow('\n💡 ' + t('theme.token_tip')));
327
+ }
328
+
329
+ console.log();
330
+ process.exit(1);
331
+ }
332
+ }
333
+
334
+ export async function themePullCommand() {
335
+ try {
336
+ console.log(chalk.cyan(t('theme.pull.starting') + '\n'));
337
+
338
+ const apiClient = await createApiClient();
339
+ const response = await apiClient.get('/theme');
340
+
341
+ const themes = response.data || [];
342
+
343
+ if (themes.length === 0) {
344
+ showWarning(t('theme.pull.no_themes'));
345
+ return;
346
+ }
347
+
348
+ const themeChoices = themes.map(theme => ({
349
+ name: `${theme.name} ${chalk.gray(`v${theme.version}`)} ${theme.active ? chalk.green('(Aktif)') : ''}`,
350
+ value: theme,
351
+ description: theme.description || `${t('theme.folder_label')} ${theme.theme_folder}`
352
+ }));
353
+
354
+ const selectedTheme = await brandedSelect({
355
+ message: t('theme.pull.select_prompt'), choices: themeChoices,
356
+ }, t('theme.pull.select_title'));
357
+
358
+ console.log(chalk.cyan('\n' + t('theme.pull.downloading', { name: selectedTheme.name }) + '\n'));
359
+
360
+ const themeSlug = slugify(selectedTheme.name);
361
+ const themeUuid = selectedTheme.theme_folder;
362
+ const store = await getActiveStore();
363
+ const storeSlug = normalizeStoreDomain(store);
364
+ const themePath = path.join(process.cwd(), storeSlug, themeSlug);
365
+
366
+ const exists = await fs.access(themePath).then(() => true).catch(() => false);
367
+ if (exists) {
368
+ const overwrite = await brandedConfirm({
369
+ message: t('theme.dir_overwrite_prompt', { path: themePath }), default: false,
370
+ }, t('theme.dir_exists_title'));
371
+
372
+ if (!overwrite) {
373
+ showWarning(t('theme.cancelled'));
374
+ return;
375
+ }
376
+
377
+ await fs.rm(themePath, {recursive: true, force: true});
378
+ }
379
+
380
+ console.log(chalk.yellow(t('theme.downloading_files')));
381
+
382
+ const downloadResponse = await apiClient.download(`/theme/${themeUuid}/download`, {});
383
+
384
+ await fs.mkdir(themePath, {recursive: true});
385
+
386
+ const zipPath = path.join(themePath, 'theme.zip');
387
+ const writer = createWriteStream(zipPath);
388
+
389
+ downloadResponse.data.pipe(writer);
390
+
391
+ await new Promise((resolve, reject) => {
392
+ writer.on('finish', resolve);
393
+ writer.on('error', reject);
394
+ });
395
+
396
+ console.log(chalk.green(t('theme.files_downloaded')));
397
+ console.log(chalk.yellow(t('theme.extracting')));
398
+
399
+ const zip = new AdmZip(zipPath);
400
+ zip.extractAllTo(themePath, true);
401
+
402
+ await fs.unlink(zipPath);
403
+
404
+ console.log(chalk.green(t('theme.extracted', { path: `${storeSlug}/${themeSlug}/` })));
405
+
406
+ await setActiveTheme(themeSlug);
407
+ console.log(chalk.green(t('theme.active_set') + '\n'));
408
+
409
+ console.log(chalk.bold.green(t('theme.pull.success')));
410
+ console.log(chalk.gray(' ' + t('theme.create_location', { path: `${storeSlug}/${themeSlug}/` }) + '\n'));
411
+ } catch (error) {
412
+ console.error(chalk.red('\n❌ Hata: ') + error.message);
413
+ if (error.response?.status === 401) {
414
+ console.log(chalk.yellow(' ' + t('whoami.relogin_tip')));
415
+ }
416
+ process.exit(1);
417
+ }
418
+ }
419
+
420
+ export async function themeUseCommand(themeName = null) {
421
+ try {
422
+ console.log(chalk.cyan(t('theme.use.starting') + '\n'));
423
+
424
+ const apiClient = await createApiClient();
425
+ const response = await apiClient.get('/theme');
426
+ const themes = response.data || [];
427
+
428
+ if (themes.length === 0) {
429
+ showWarning(t('theme.use.no_themes'));
430
+ return;
431
+ }
432
+
433
+ let selectedTheme;
434
+
435
+ if (themeName) {
436
+ const themeSlug = slugify(themeName);
437
+ selectedTheme = themes.find(t => slugify(t.name) === themeSlug);
438
+
439
+ if (!selectedTheme) {
440
+ console.error(chalk.red('\n' + t('theme.use.not_found', { name: themeName })));
441
+ console.log(chalk.yellow('\n' + t('theme.use.available')));
442
+ themes.forEach(t => {
443
+ console.log(chalk.gray(` • ${t.name}`));
444
+ });
445
+ process.exit(1);
446
+ }
447
+ } else {
448
+ const themeChoices = themes.map(theme => ({
449
+ name: `${theme.name} ${chalk.gray(`v${theme.version}`)} ${theme.active ? chalk.green('(Şu an aktif)') : ''}`,
450
+ value: theme,
451
+ description: theme.description || `${t('theme.folder_label')} ${theme.theme_folder}`
452
+ }));
453
+
454
+ selectedTheme = await brandedSelect({
455
+ message: t('theme.use.select_prompt'),
456
+ choices: themeChoices,
457
+ }, t('theme.use.select_title'));
458
+ }
459
+
460
+ const themeSlug = slugify(selectedTheme.name);
461
+
462
+ await setActiveTheme(themeSlug);
463
+
464
+ console.log(chalk.green('\n' + t('theme.use.set', { name: chalk.bold(selectedTheme.name) })));
465
+ console.log(chalk.gray(' ' + t('theme.use.folder', { folder: themeSlug }) + '\n'));
466
+
467
+ } catch (error) {
468
+ console.error(chalk.red('\n❌ Hata: ') + error.message);
469
+ if (error.response?.status === 401) {
470
+ console.log(chalk.yellow(' ' + t('whoami.relogin_tip')));
471
+ }
472
+ process.exit(1);
473
+ }
474
+ }
@@ -0,0 +1,132 @@
1
+ import chalk from 'chalk';
2
+ import { loadTokens, ensureValidToken, getTokenExpiry, isTokenValid, isRefreshTokenValid } from '../storage.js';
3
+ import { loginCommand } from './login.js';
4
+ import { isJsonMode, jsonOut } from '../output-mode.js';
5
+ import { mapError } from '../errors/error-mapper.js';
6
+ import { t } from '../i18n.js';
7
+
8
+ /**
9
+ * Whoami komutu - Mevcut kullanıcı ve token bilgilerini gösterir
10
+ */
11
+ export async function whoamiCommand() {
12
+ try {
13
+ let tokens = await loadTokens();
14
+
15
+ if (!tokens) {
16
+ if (isJsonMode()) {
17
+ jsonOut({ status: 'error', command: 'whoami', error: { message: t('whoami.not_logged_in.json.message'), hint: t('whoami.not_logged_in.json.hint') } });
18
+ process.exit(1);
19
+ }
20
+ console.log(chalk.yellow(t('whoami.not_logged_in')));
21
+ console.log(chalk.gray(' ' + t('whoami.not_logged_in_tip') + '\n'));
22
+ return;
23
+ }
24
+
25
+ // Token geçerliliğini kontrol et ve gerekirse refresh et
26
+ try {
27
+ tokens = await ensureValidToken(tokens);
28
+ if (!isTokenValid(tokens)) {
29
+ // Token refresh edildi, bilgilendirme mesajı
30
+ console.log(chalk.yellow(t('whoami.token_refreshed') + '\n'));
31
+ }
32
+ } catch (error) {
33
+ if (error.message === 'RELOGIN_REQUIRED') {
34
+ console.log(chalk.yellow(t('whoami.session_expired') + '\n'));
35
+ await loginCommand();
36
+ tokens = await loadTokens();
37
+ if (!tokens) {
38
+ return;
39
+ }
40
+ } else {
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ if (isJsonMode()) {
46
+ jsonOut({
47
+ status: 'success',
48
+ command: 'whoami',
49
+ data: {
50
+ user: tokens.user ? {
51
+ name: tokens.user.name,
52
+ surname: tokens.user.surname,
53
+ email: tokens.user.email,
54
+ } : null,
55
+ store: tokens.store,
56
+ access_token_valid: isTokenValid(tokens),
57
+ refresh_token_valid: isRefreshTokenValid(tokens),
58
+ updated_at: tokens.updatedAt || null,
59
+ }
60
+ });
61
+ return;
62
+ }
63
+
64
+ console.log(chalk.bold.cyan(t('whoami.title') + '\n'));
65
+
66
+ // User bilgisi
67
+ if (tokens.user) {
68
+ console.log(chalk.cyan(t('whoami.user_label')));
69
+ console.log(chalk.white(` ${tokens.user.name} ${tokens.user.surname}`));
70
+ console.log(chalk.gray(` ${tokens.user.email}`));
71
+ console.log(chalk.gray(` Versiyon: ${tokens.user.version}\n`));
72
+ }
73
+
74
+ // Store bilgisi
75
+ console.log(chalk.cyan(t('whoami.store_label')));
76
+ console.log(chalk.white(` ${tokens.store}\n`));
77
+
78
+ // Token durumu
79
+ const accessTokenValid = isTokenValid(tokens);
80
+ const refreshTokenValid = isRefreshTokenValid(tokens);
81
+ const accessExpiry = getTokenExpiry(tokens);
82
+
83
+ console.log(chalk.cyan(t('whoami.access_token_label')));
84
+ if (accessTokenValid) {
85
+ console.log(chalk.green(' ' + t('whoami.token_valid', { expiry: accessExpiry })));
86
+ } else {
87
+ console.log(chalk.red(' ' + t('whoami.token_expired')));
88
+ }
89
+
90
+ console.log(chalk.cyan('\n' + t('whoami.refresh_token_label')));
91
+ if (refreshTokenValid) {
92
+ const refreshExpiry = new Date(tokens.refreshTokenExpiresAt);
93
+ const now = new Date();
94
+ const daysLeft = Math.floor((refreshExpiry - now) / (1000 * 60 * 60 * 24));
95
+ console.log(chalk.green(' ' + t('whoami.refresh_valid', { days: daysLeft })));
96
+ } else {
97
+ console.log(chalk.red(' ' + t('whoami.token_expired')));
98
+ }
99
+
100
+ console.log();
101
+
102
+ // Config dizini
103
+ console.log(chalk.cyan(t('whoami.config_dir_label')));
104
+ console.log(chalk.gray(` .tsoft/${tokens.store.replace(/\./g, '-').toLowerCase()}/\n`));
105
+
106
+ // Son güncelleme
107
+ if (tokens.updatedAt) {
108
+ const updatedAt = new Date(tokens.updatedAt);
109
+ console.log(chalk.cyan(t('whoami.last_update_label')));
110
+ console.log(chalk.white(` ${updatedAt.toLocaleString('tr-TR')}\n`));
111
+ }
112
+
113
+ // Access Token (ilk 20 karakter)
114
+ if (tokens.accessToken) {
115
+ const preview = tokens.accessToken.substring(0, 20) + '...';
116
+ console.log(chalk.cyan(t('whoami.access_token_label')));
117
+ console.log(chalk.gray(` ${preview}\n`));
118
+ }
119
+
120
+ } catch (error) {
121
+ if (isJsonMode()) {
122
+ const mapped = mapError(error, { command: 'whoami' });
123
+ jsonOut({ status: 'error', command: 'whoami', error: { message: mapped.message, hint: mapped.hint } });
124
+ process.exit(1);
125
+ }
126
+ console.error(chalk.red('\n❌ Hata: ') + error.message);
127
+ if (error.response?.status === 401 || error.message?.includes('Token')) {
128
+ console.log(chalk.yellow(' ' + t('whoami.relogin_tip')));
129
+ }
130
+ process.exit(1);
131
+ }
132
+ }
package/src/config.js ADDED
@@ -0,0 +1,19 @@
1
+ export const CLIENT_ID = process.env.TSOFT_CLIENT_ID ?? 'a08b4122-7f91-4f00-a1b7-799a60d25d92';
2
+ export const CALLBACK_PORT = parseInt(process.env.TSOFT_CALLBACK_PORT ?? '8046', 10);
3
+ export const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/callback`;
4
+ export const SCOPE = 'online-store-2:manage';
5
+ export const IDP_DOMAIN = process.env.TSOFT_IDP_DOMAIN ?? 'login.tsoft360.com';
6
+
7
+ // Laravel Passport endpoints
8
+ export const getAuthorizationUrl = () =>
9
+ `https://${IDP_DOMAIN}/oauth/authorize`;
10
+
11
+ export const getTokenUrl = (store) =>
12
+ `https://${store}/api/v3/admin/auth/oauth/callback`;
13
+
14
+ export const getRefreshUrl = (store) =>
15
+ `https://${store}/api/v3/admin/auth/oauth/refresh`;
16
+
17
+ export const getRevokeUrl = (store) =>
18
+ `https://${store}/oauth/revoke`;
19
+