tsoft-cli 2.6.15 → 3.4.1
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 +166 -0
- package/LICENSE +21 -0
- package/README.md +86 -53
- package/locales/en.json +323 -0
- package/locales/tr.json +323 -0
- package/package.json +41 -45
- package/src/api-client.js +217 -0
- package/src/commands/login.js +94 -0
- package/src/commands/logout.js +44 -0
- package/src/commands/org-switch.js +82 -0
- package/src/commands/theme-dev.js +424 -0
- package/src/commands/theme-init.js +162 -0
- package/src/commands/theme-publish.js +12 -0
- package/src/commands/theme-push.js +197 -0
- package/src/commands/theme-section.js +317 -0
- package/src/commands/theme-submit.js +14 -0
- package/src/commands/theme.js +474 -0
- package/src/commands/whoami.js +132 -0
- package/src/config.js +19 -0
- package/src/errors/error-mapper.js +158 -0
- package/src/i18n.js +84 -0
- package/src/index.js +179 -0
- package/src/output-mode.js +45 -0
- package/src/pkce.js +34 -0
- package/src/server.js +216 -0
- package/src/storage.js +488 -0
- package/src/ui/help-renderer.js +219 -0
- package/src/ui/prompt-wrapper.js +183 -0
- package/bin/run.js +0 -5
- package/build/enums.d.ts +0 -1
- package/build/enums.js +0 -7
- package/build/enums.js.map +0 -1
- package/build/index.d.ts +0 -5
- package/build/index.js +0 -10
- package/build/index.js.map +0 -1
|
@@ -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
|
+
|