tsoft-cli 2.5.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.
- package/CHANGELOG.md +160 -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 +209 -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,162 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import {createWriteStream} from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import AdmZip from 'adm-zip';
|
|
6
|
+
import {createApiClient} from '../api-client.js';
|
|
7
|
+
import {slugify, getActiveStore} from '../storage.js';
|
|
8
|
+
import {brandedConfirm, brandedSelect, showWarning, showInfo, showError} from '../ui/prompt-wrapper.js';
|
|
9
|
+
import {normalizeStoreDomain} from '../storage.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 themeInitCommand(fromSlug) {
|
|
15
|
+
try {
|
|
16
|
+
const apiClient = await createApiClient();
|
|
17
|
+
const response = await apiClient.get('/theme');
|
|
18
|
+
const themes = response.data || [];
|
|
19
|
+
|
|
20
|
+
let sourceTheme;
|
|
21
|
+
|
|
22
|
+
if (fromSlug && fromSlug !== true) {
|
|
23
|
+
// Slug provided: find theme by slug or uuid
|
|
24
|
+
sourceTheme = themes.find(t => slugify(t.name) === fromSlug || t.theme_folder === fromSlug);
|
|
25
|
+
|
|
26
|
+
if (!sourceTheme) {
|
|
27
|
+
if (isJsonMode()) {
|
|
28
|
+
jsonOut({ status: 'error', command: 'theme init', error: { message: t('init.theme_not_found', { slug: fromSlug }), hint: null } });
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
showError(t('init.theme_not_found', { slug: fromSlug }));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
// Interactive mode: show theme picker
|
|
36
|
+
if (isJsonMode()) {
|
|
37
|
+
jsonOut({ status: 'error', command: 'theme init', error: { message: t('init.json_error.message'), hint: t('init.json_error.hint') } });
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (themes.length === 0) {
|
|
42
|
+
showWarning(t('init.no_themes'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const themeChoices = themes.map(theme => ({
|
|
47
|
+
name: `${theme.name} ${chalk.gray('v' + theme.version)}${theme.organization_id ? chalk.dim(' [Org #' + theme.organization_id + ']') : ''}`,
|
|
48
|
+
value: theme,
|
|
49
|
+
description: theme.theme_folder
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
sourceTheme = await brandedSelect({
|
|
53
|
+
message: t('init.select_prompt'),
|
|
54
|
+
choices: themeChoices
|
|
55
|
+
}, t('init.select_title'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// JSON modda onay sormadan devam et
|
|
59
|
+
if (!isJsonMode()) {
|
|
60
|
+
const confirmed = await brandedConfirm({
|
|
61
|
+
message: t('init.confirm_prompt', { name: sourceTheme.name, version: sourceTheme.version }),
|
|
62
|
+
default: true
|
|
63
|
+
}, t('init.confirm_title'));
|
|
64
|
+
|
|
65
|
+
if (!confirmed) {
|
|
66
|
+
showInfo(t('init.cancelled'));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Fork API call
|
|
72
|
+
if (!isJsonMode()) {
|
|
73
|
+
console.log(chalk.yellow(t('init.forking')));
|
|
74
|
+
}
|
|
75
|
+
const forkResult = await apiClient.forkTheme(sourceTheme.theme_folder);
|
|
76
|
+
const forkedData = forkResult.data;
|
|
77
|
+
|
|
78
|
+
// Generate local path
|
|
79
|
+
const store = await getActiveStore();
|
|
80
|
+
const storeSlug = normalizeStoreDomain(store);
|
|
81
|
+
const forkedSlug = slugify(forkedData.name);
|
|
82
|
+
const themePath = path.join(process.cwd(), storeSlug, forkedSlug);
|
|
83
|
+
|
|
84
|
+
// Check if directory exists
|
|
85
|
+
const exists = await fs.access(themePath).then(() => true).catch(() => false);
|
|
86
|
+
if (exists) {
|
|
87
|
+
if (isJsonMode()) {
|
|
88
|
+
// JSON modda otomatik overwrite
|
|
89
|
+
await fs.rm(themePath, { recursive: true, force: true });
|
|
90
|
+
} else {
|
|
91
|
+
const overwrite = await brandedConfirm({
|
|
92
|
+
message: t('init.dir_exists_prompt', { path: themePath }),
|
|
93
|
+
default: false,
|
|
94
|
+
}, t('init.dir_exists_title'));
|
|
95
|
+
|
|
96
|
+
if (!overwrite) {
|
|
97
|
+
showWarning(t('init.dir_cancelled'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
await fs.rm(themePath, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Download and extract forked theme files
|
|
105
|
+
if (!isJsonMode()) {
|
|
106
|
+
console.log(chalk.yellow(t('init.downloading')));
|
|
107
|
+
}
|
|
108
|
+
await fs.mkdir(themePath, { recursive: true });
|
|
109
|
+
const downloadResponse = await apiClient.download(`/theme/${forkedData.uuid}/download`, {});
|
|
110
|
+
const zipPath = path.join(themePath, 'theme.zip');
|
|
111
|
+
const writer = createWriteStream(zipPath);
|
|
112
|
+
downloadResponse.data.pipe(writer);
|
|
113
|
+
await new Promise((resolve, reject) => {
|
|
114
|
+
writer.on('finish', resolve);
|
|
115
|
+
writer.on('error', reject);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const zip = new AdmZip(zipPath);
|
|
119
|
+
zip.extractAllTo(themePath, true);
|
|
120
|
+
await fs.unlink(zipPath);
|
|
121
|
+
|
|
122
|
+
// JSON mode success output
|
|
123
|
+
if (isJsonMode()) {
|
|
124
|
+
jsonOut({
|
|
125
|
+
status: 'success',
|
|
126
|
+
command: 'theme init',
|
|
127
|
+
data: {
|
|
128
|
+
uuid: forkedData.uuid || forkedData.theme_folder,
|
|
129
|
+
name: forkedData.name,
|
|
130
|
+
version: forkedData.version || '0.0.1',
|
|
131
|
+
source: {
|
|
132
|
+
uuid: sourceTheme.theme_folder,
|
|
133
|
+
name: sourceTheme.name,
|
|
134
|
+
version: sourceTheme.version,
|
|
135
|
+
},
|
|
136
|
+
path: `${storeSlug}/${forkedSlug}/`,
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Human-readable success message
|
|
143
|
+
console.log();
|
|
144
|
+
console.log(chalk.green(t('init.success')));
|
|
145
|
+
console.log();
|
|
146
|
+
console.log(chalk.cyan(' ' + t('init.theme_label') + ' ') + chalk.white(forkedData.name));
|
|
147
|
+
console.log(chalk.cyan(' ' + t('init.source_label') + ' ') + chalk.white(`${forkedData.source.name} v${forkedData.source.version}`));
|
|
148
|
+
console.log(chalk.cyan(' ' + t('init.location_label') + ' ') + chalk.white(`${storeSlug}/${forkedSlug}/`));
|
|
149
|
+
console.log();
|
|
150
|
+
console.log(chalk.gray(t('init.dev_tip', { slug: forkedSlug })));
|
|
151
|
+
console.log();
|
|
152
|
+
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (isJsonMode()) {
|
|
155
|
+
const mapped = mapError(error, { command: 'theme init' });
|
|
156
|
+
jsonOut({ status: 'error', command: 'theme init', error: { message: mapped.message, hint: mapped.hint || null } });
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
console.error(chalk.red('Hata: ') + error.message);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { themePushCommand } from './theme-push.js';
|
|
3
|
+
import { t } from '../i18n.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Tema publish komutu (deprecated) - Push'a yonlendirir
|
|
7
|
+
*/
|
|
8
|
+
export async function themePublishCommand(themeName) {
|
|
9
|
+
console.log(chalk.yellow('\n' + t('publish.deprecated')));
|
|
10
|
+
console.log('');
|
|
11
|
+
await themePushCommand(themeName);
|
|
12
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createApiClient } from '../api-client.js';
|
|
4
|
+
import { getActiveTheme } from '../storage.js';
|
|
5
|
+
import { mapPushError } from '../errors/error-mapper.js';
|
|
6
|
+
import { isJsonMode, jsonOut } from '../output-mode.js';
|
|
7
|
+
import { t } from '../i18n.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Change type'lari kategorilere grupla
|
|
11
|
+
*/
|
|
12
|
+
const CHANGE_CATEGORIES = {
|
|
13
|
+
'Sections': ['section_added', 'section_removed'],
|
|
14
|
+
'Templates': ['template_added', 'template_removed'],
|
|
15
|
+
'Meta': ['meta_object_added', 'meta_object_removed', 'meta_field_added', 'meta_field_removed'],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Diff ozeti goster (gruplu)
|
|
20
|
+
* @param {Object} data - Push response data
|
|
21
|
+
* @param {string|null} previousVersion - Onceki versiyon
|
|
22
|
+
*/
|
|
23
|
+
function displayDiffSummary(data, previousVersion) {
|
|
24
|
+
const { version, bump_type: bumpType, action, changes, partner_panel_url: partnerUrl } = data;
|
|
25
|
+
|
|
26
|
+
console.log('');
|
|
27
|
+
|
|
28
|
+
// Versiyon gecisi
|
|
29
|
+
if (action === 'created' && !previousVersion) {
|
|
30
|
+
// Ilk push
|
|
31
|
+
console.log(chalk.green(` ${version} ${t('push.first_push_note')}`));
|
|
32
|
+
console.log(chalk.gray(' ' + t('push.admin_approval')));
|
|
33
|
+
} else if (previousVersion && previousVersion !== version) {
|
|
34
|
+
console.log(chalk.green(` ${previousVersion} -> ${version} (${bumpType})`));
|
|
35
|
+
} else {
|
|
36
|
+
console.log(chalk.green(` ${version}`));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Action satiri
|
|
40
|
+
const actionText = action === 'created' ? t('push.draft_created') : t('push.draft_updated');
|
|
41
|
+
console.log(chalk.green(` ${actionText}`));
|
|
42
|
+
|
|
43
|
+
// Grouped changes
|
|
44
|
+
if (changes && changes.length > 0) {
|
|
45
|
+
console.log('');
|
|
46
|
+
|
|
47
|
+
for (const [category, types] of Object.entries(CHANGE_CATEGORIES)) {
|
|
48
|
+
const addedTypes = types.filter(t => t.endsWith('_added'));
|
|
49
|
+
const removedTypes = types.filter(t => t.endsWith('_removed'));
|
|
50
|
+
|
|
51
|
+
const addedCount = changes.filter(c => addedTypes.includes(c.type)).length;
|
|
52
|
+
const removedCount = changes.filter(c => removedTypes.includes(c.type)).length;
|
|
53
|
+
|
|
54
|
+
if (addedCount === 0 && removedCount === 0) continue;
|
|
55
|
+
|
|
56
|
+
const parts = [];
|
|
57
|
+
if (addedCount > 0) parts.push(chalk.green(t('push.changes_added', { count: addedCount })));
|
|
58
|
+
if (removedCount > 0) parts.push(chalk.red(t('push.changes_removed', { count: removedCount })));
|
|
59
|
+
|
|
60
|
+
console.log(` ${chalk.bold(category)}: ${parts.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Partner panel URL
|
|
65
|
+
if (partnerUrl) {
|
|
66
|
+
console.log('');
|
|
67
|
+
console.log(chalk.cyan(` ${partnerUrl}`));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Tema push komutu
|
|
73
|
+
* @param {string|null} themeName - Tema adi/UUID (opsiyonel)
|
|
74
|
+
* @param {Object} options - Komut opsiyonlari
|
|
75
|
+
* @param {boolean} options.verbose - Detayli hata ciktisi
|
|
76
|
+
*/
|
|
77
|
+
export async function themePushCommand(themeName, options = {}) {
|
|
78
|
+
const verbose = options.verbose || false;
|
|
79
|
+
|
|
80
|
+
// Tema belirleme
|
|
81
|
+
let themeUuid;
|
|
82
|
+
if (themeName) {
|
|
83
|
+
themeUuid = themeName;
|
|
84
|
+
} else {
|
|
85
|
+
themeUuid = await getActiveTheme();
|
|
86
|
+
if (!themeUuid) {
|
|
87
|
+
console.error(chalk.red(t('push.no_active_theme')));
|
|
88
|
+
console.log(chalk.gray(t('push.usage')));
|
|
89
|
+
console.log(chalk.gray(t('push.use_tip')));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const spinner = ora({
|
|
95
|
+
text: t('push.connecting'),
|
|
96
|
+
color: 'cyan',
|
|
97
|
+
stream: isJsonMode() ? process.stderr : process.stdout,
|
|
98
|
+
}).start();
|
|
99
|
+
|
|
100
|
+
let previousVersion = null;
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
// 1. Dev status kontrol
|
|
104
|
+
spinner.text = t('push.checking_status');
|
|
105
|
+
const apiClient = await createApiClient();
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const statusResponse = await apiClient.getThemeDevStatus(themeUuid);
|
|
109
|
+
previousVersion = statusResponse.data?.current_version || null;
|
|
110
|
+
} catch (devStatusError) {
|
|
111
|
+
// dev-status hatasi kritik degil, devam et
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 2. Push
|
|
115
|
+
spinner.text = t('push.pushing');
|
|
116
|
+
const result = await apiClient.pushTheme(themeUuid);
|
|
117
|
+
|
|
118
|
+
// 3. No-op kontrolu
|
|
119
|
+
if (result.data?.no_changes === true) {
|
|
120
|
+
// JSON mode
|
|
121
|
+
if (isJsonMode()) {
|
|
122
|
+
spinner.stop();
|
|
123
|
+
jsonOut({
|
|
124
|
+
status: 'no_op',
|
|
125
|
+
command: 'theme push',
|
|
126
|
+
data: {
|
|
127
|
+
reason: 'no_changes',
|
|
128
|
+
partner_panel_url: result.data?.partner_panel_url || null,
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Insan-okunabilir
|
|
134
|
+
spinner.info(t('push.no_changes'));
|
|
135
|
+
if (result.data?.partner_panel_url) {
|
|
136
|
+
console.log(chalk.gray(` ${result.data.partner_panel_url}`));
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 4. Basari
|
|
142
|
+
spinner.succeed(t('push.success'));
|
|
143
|
+
|
|
144
|
+
// JSON mode basari output
|
|
145
|
+
if (isJsonMode()) {
|
|
146
|
+
jsonOut({
|
|
147
|
+
status: 'success',
|
|
148
|
+
command: 'theme push',
|
|
149
|
+
data: {
|
|
150
|
+
version: result.data.version,
|
|
151
|
+
bump_type: result.data.bump_type,
|
|
152
|
+
action: result.data.action,
|
|
153
|
+
changes: result.data.changes || [],
|
|
154
|
+
partner_panel_url: result.data.partner_panel_url || null,
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 5. Diff summary (Insan-okunabilir)
|
|
161
|
+
displayDiffSummary(result.data, previousVersion);
|
|
162
|
+
|
|
163
|
+
} catch (error) {
|
|
164
|
+
spinner.stop();
|
|
165
|
+
|
|
166
|
+
// JSON mode error output
|
|
167
|
+
if (isJsonMode()) {
|
|
168
|
+
const mapped = mapPushError(error, false);
|
|
169
|
+
jsonOut({
|
|
170
|
+
status: 'error',
|
|
171
|
+
command: 'theme push',
|
|
172
|
+
error: {
|
|
173
|
+
message: mapped.message,
|
|
174
|
+
hint: mapped.hint || null,
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Push hatasi (axios error, error.response mevcut)
|
|
181
|
+
if (error.response) {
|
|
182
|
+
const mapped = mapPushError(error, verbose);
|
|
183
|
+
console.error(chalk.red(`X ${mapped.message}`));
|
|
184
|
+
if (mapped.hint) {
|
|
185
|
+
console.log(chalk.yellow(` ${mapped.hint}`));
|
|
186
|
+
}
|
|
187
|
+
if (mapped.raw) {
|
|
188
|
+
console.log(chalk.gray(JSON.stringify(mapped.raw, null, 2)));
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
// Genel hata (network, vs.)
|
|
192
|
+
console.error(chalk.red(`X ${error.message}`));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import {checkbox} 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 {getActiveTheme, getThemeUuidBySlug, normalizeStoreDomain, getActiveStore} from '../storage.js';
|
|
9
|
+
import {brandedConfirm, brandedSelect, showWarning} from '../ui/prompt-wrapper.js';
|
|
10
|
+
import { t } from '../i18n.js';
|
|
11
|
+
|
|
12
|
+
export async function themeSectionListCommand() {
|
|
13
|
+
try {
|
|
14
|
+
// Aktif temayı al
|
|
15
|
+
const activeTheme = await getActiveTheme();
|
|
16
|
+
if (!activeTheme) {
|
|
17
|
+
showWarning(t('section.list.no_active_theme'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
console.log(chalk.cyan(t('section.list.starting') + '\n'));
|
|
22
|
+
console.log(chalk.gray(t('section.list.active_theme', { theme: activeTheme }) + '\n'));
|
|
23
|
+
|
|
24
|
+
// Tema UUID'sini al
|
|
25
|
+
const themeInfo = await getThemeUuidBySlug(activeTheme);
|
|
26
|
+
|
|
27
|
+
// Section'ları çek
|
|
28
|
+
const apiClient = await createApiClient();
|
|
29
|
+
const response = await apiClient.getSections(themeInfo.uuid);
|
|
30
|
+
const blocks = response.data || [];
|
|
31
|
+
|
|
32
|
+
if (blocks.length === 0) {
|
|
33
|
+
console.log(chalk.yellow(t('section.list.no_blocks') + '\n'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(chalk.bold.cyan(t('section.list.count', { count: blocks.length }) + '\n'));
|
|
38
|
+
|
|
39
|
+
blocks.forEach((block) => {
|
|
40
|
+
// Block başlığı
|
|
41
|
+
console.log(chalk.bold.cyan(`📦 ${block.name}`));
|
|
42
|
+
console.log(chalk.gray(` ${t('section.list.id_label')} ${block.id}`));
|
|
43
|
+
console.log(chalk.gray(` ${t('section.list.folder_label')} ${block.folder}`));
|
|
44
|
+
|
|
45
|
+
// Section'lar
|
|
46
|
+
if (block.default_sections && block.default_sections.length > 0) {
|
|
47
|
+
console.log(chalk.yellow(' ' + t('section.list.sections_label', { count: block.default_sections.length })));
|
|
48
|
+
|
|
49
|
+
block.default_sections.forEach((section) => {
|
|
50
|
+
console.log(chalk.white(` • ${section.name} ${chalk.gray(`(ID: ${section.id}, v${section.version})`)}`));
|
|
51
|
+
|
|
52
|
+
// Meta bilgiler varsa göster
|
|
53
|
+
if (section.meta) {
|
|
54
|
+
if (section.meta.description) {
|
|
55
|
+
console.log(chalk.gray(` ${section.meta.description}`));
|
|
56
|
+
}
|
|
57
|
+
if (section.meta.tags && section.meta.tags.length > 0) {
|
|
58
|
+
console.log(chalk.gray(` ${t('section.list.tags_label')} ${section.meta.tags.join(', ')}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(chalk.gray(` ${t('section.list.file_label')} ${section.content}`));
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
console.log(chalk.red(' ' + t('section.list.no_sections')));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log('');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(chalk.red('\n❌ Hata: ') + error.message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function themeSectionAddCommand() {
|
|
78
|
+
try {
|
|
79
|
+
// Aktif temayı al
|
|
80
|
+
const activeTheme = await getActiveTheme();
|
|
81
|
+
if (!activeTheme) {
|
|
82
|
+
showWarning(t('section.add.no_active_theme'));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(chalk.cyan(t('section.add.starting') + '\n'));
|
|
87
|
+
console.log(chalk.gray(t('section.add.active_theme', { theme: activeTheme }) + '\n'));
|
|
88
|
+
|
|
89
|
+
// Tema UUID'sini al
|
|
90
|
+
const themeInfo = await getThemeUuidBySlug(activeTheme);
|
|
91
|
+
|
|
92
|
+
// Section'ları çek
|
|
93
|
+
const apiClient = await createApiClient();
|
|
94
|
+
const response = await apiClient.getSections(themeInfo.uuid);
|
|
95
|
+
const blocks = response.data || [];
|
|
96
|
+
|
|
97
|
+
if (blocks.length === 0) {
|
|
98
|
+
showWarning(t('section.add.no_blocks'));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sadece section'ı olan block'ları filtrele
|
|
103
|
+
const availableBlocks = blocks.filter(b => b.default_sections && b.default_sections.length > 0);
|
|
104
|
+
|
|
105
|
+
if (availableBlocks.length === 0) {
|
|
106
|
+
showWarning(t('section.add.no_sections'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Block seçimi
|
|
111
|
+
const blockChoices = availableBlocks.map(block => ({
|
|
112
|
+
name: `${block.name} ${chalk.gray(`(${block.default_sections.length} section)`)}`,
|
|
113
|
+
value: block,
|
|
114
|
+
description: `${t('section.list.folder_label')} ${block.folder}`
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
const selectedBlock = await brandedSelect({
|
|
118
|
+
message: t('section.add.block_select_prompt'),
|
|
119
|
+
choices: blockChoices,
|
|
120
|
+
}, t('section.add.block_select_title'));
|
|
121
|
+
|
|
122
|
+
console.log(chalk.cyan('\n' + t('section.add.block_selected', { name: selectedBlock.name }) + '\n'));
|
|
123
|
+
|
|
124
|
+
// Tek section varsa direkt ekle, çoklu varsa mod seçimi
|
|
125
|
+
let selectedSections = [];
|
|
126
|
+
|
|
127
|
+
if (selectedBlock.default_sections.length === 1) {
|
|
128
|
+
// Tek section - direkt ekle
|
|
129
|
+
selectedSections = [selectedBlock.default_sections[0]];
|
|
130
|
+
} else {
|
|
131
|
+
// Çoklu section - kullanıcıya sor
|
|
132
|
+
const addMode = await brandedSelect({
|
|
133
|
+
message: t('section.add.mode_prompt'),
|
|
134
|
+
choices: [
|
|
135
|
+
{name: t('section.add.mode_all'), value: 'all'},
|
|
136
|
+
{name: t('section.add.mode_selective'), value: 'selective'}
|
|
137
|
+
]
|
|
138
|
+
}, t('section.add.mode_title'));
|
|
139
|
+
|
|
140
|
+
if (addMode === 'all') {
|
|
141
|
+
selectedSections = selectedBlock.default_sections;
|
|
142
|
+
} else {
|
|
143
|
+
// Checkbox ile seçim
|
|
144
|
+
const sectionChoices = selectedBlock.default_sections.map(section => ({
|
|
145
|
+
name: `${section.name} ${chalk.gray(`v${section.version}`)}`,
|
|
146
|
+
value: section,
|
|
147
|
+
checked: false
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
selectedSections = await checkbox({
|
|
151
|
+
message: t('section.add.checkbox_prompt'),
|
|
152
|
+
choices: sectionChoices,
|
|
153
|
+
validate: (answer) => {
|
|
154
|
+
if (answer.length === 0) {
|
|
155
|
+
return t('section.add.min_one');
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Özet göster
|
|
164
|
+
console.log(chalk.yellow('\n' + t('section.add.summary', { count: selectedSections.length })));
|
|
165
|
+
selectedSections.forEach(s => {
|
|
166
|
+
console.log(chalk.gray(` • ${s.name} (v${s.version})`));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Confirmation
|
|
170
|
+
const confirm = await brandedConfirm({
|
|
171
|
+
message: t('section.add.confirm_prompt', { count: selectedSections.length, theme: themeInfo.name }),
|
|
172
|
+
default: true
|
|
173
|
+
}, t('section.add.confirm_title'));
|
|
174
|
+
|
|
175
|
+
if (!confirm) {
|
|
176
|
+
showWarning(t('section.add.cancelled'));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Section'ları ekle
|
|
181
|
+
console.log(chalk.yellow('\n' + t('section.add.adding') + '\n'));
|
|
182
|
+
|
|
183
|
+
let successCount = 0;
|
|
184
|
+
let errorCount = 0;
|
|
185
|
+
let overwriteAll = false;
|
|
186
|
+
|
|
187
|
+
for (const section of selectedSections) {
|
|
188
|
+
try {
|
|
189
|
+
let force = overwriteAll;
|
|
190
|
+
|
|
191
|
+
// İlk deneme
|
|
192
|
+
try {
|
|
193
|
+
await apiClient.addSection(
|
|
194
|
+
themeInfo.uuid,
|
|
195
|
+
selectedBlock.id,
|
|
196
|
+
section.id,
|
|
197
|
+
force
|
|
198
|
+
);
|
|
199
|
+
console.log(chalk.green(t('section.add.added', { name: section.name })));
|
|
200
|
+
successCount++;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// 422 hatası - section zaten var
|
|
203
|
+
if (error.message.startsWith('VALIDATION_ERROR:')) {
|
|
204
|
+
const errorMsg = error.message.replace('VALIDATION_ERROR:', '');
|
|
205
|
+
|
|
206
|
+
if (errorMsg.includes('already exists') || errorMsg.includes('zaten mevcut')) {
|
|
207
|
+
// Üzerine yazma sorgusu
|
|
208
|
+
console.log(chalk.yellow('\n' + t('section.add.already_exists', { name: section.name })));
|
|
209
|
+
|
|
210
|
+
let shouldOverwrite = false;
|
|
211
|
+
|
|
212
|
+
if (!overwriteAll && selectedSections.length > 1) {
|
|
213
|
+
const overwriteChoice = await brandedSelect({
|
|
214
|
+
message: t('section.add.overwrite_prompt'),
|
|
215
|
+
choices: [
|
|
216
|
+
{name: t('section.add.overwrite'), value: 'overwrite'},
|
|
217
|
+
{name: t('section.add.overwrite_all'), value: 'overwrite_all'},
|
|
218
|
+
{name: t('section.add.skip'), value: 'skip'}
|
|
219
|
+
]
|
|
220
|
+
}, t('section.add.overwrite_title'));
|
|
221
|
+
|
|
222
|
+
if (overwriteChoice === 'overwrite_all') {
|
|
223
|
+
overwriteAll = true;
|
|
224
|
+
shouldOverwrite = true;
|
|
225
|
+
} else if (overwriteChoice === 'overwrite') {
|
|
226
|
+
shouldOverwrite = true;
|
|
227
|
+
}
|
|
228
|
+
} else if (!overwriteAll) {
|
|
229
|
+
shouldOverwrite = await brandedConfirm({
|
|
230
|
+
message: t('section.add.single_overwrite_prompt', { name: section.name }),
|
|
231
|
+
default: false
|
|
232
|
+
}, t('section.add.single_overwrite_title'));
|
|
233
|
+
} else {
|
|
234
|
+
shouldOverwrite = true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (shouldOverwrite) {
|
|
238
|
+
// Force ile tekrar dene
|
|
239
|
+
await apiClient.addSection(
|
|
240
|
+
themeInfo.uuid,
|
|
241
|
+
selectedBlock.id,
|
|
242
|
+
section.id,
|
|
243
|
+
true
|
|
244
|
+
);
|
|
245
|
+
console.log(chalk.green(t('section.add.overwritten', { name: section.name })));
|
|
246
|
+
successCount++;
|
|
247
|
+
} else {
|
|
248
|
+
console.log(chalk.gray(t('section.add.skipped', { name: section.name })));
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.error(chalk.red(t('section.add.failed', { name: section.name, error: error.message })));
|
|
259
|
+
errorCount++;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Sonuç özeti
|
|
264
|
+
console.log(chalk.gray('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
265
|
+
if (successCount > 0) {
|
|
266
|
+
console.log(chalk.green(t('section.add.success_count', { count: successCount })));
|
|
267
|
+
}
|
|
268
|
+
if (errorCount > 0) {
|
|
269
|
+
console.log(chalk.red(t('section.add.error_count', { count: errorCount })));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Otomatik tema indirme
|
|
273
|
+
if (successCount > 0) {
|
|
274
|
+
console.log(chalk.yellow('\n' + t('section.add.updating_files') + '\n'));
|
|
275
|
+
|
|
276
|
+
const store = await getActiveStore();
|
|
277
|
+
const storeSlug = normalizeStoreDomain(store);
|
|
278
|
+
const themePath = path.join(process.cwd(), storeSlug, activeTheme);
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
// Temayı indir
|
|
282
|
+
const downloadResponse = await apiClient.download(`/theme/${themeInfo.uuid}/download`, {});
|
|
283
|
+
|
|
284
|
+
const zipPath = path.join(themePath, 'theme-update.zip');
|
|
285
|
+
const writer = createWriteStream(zipPath);
|
|
286
|
+
|
|
287
|
+
downloadResponse.data.pipe(writer);
|
|
288
|
+
|
|
289
|
+
await new Promise((resolve, reject) => {
|
|
290
|
+
writer.on('finish', resolve);
|
|
291
|
+
writer.on('error', reject);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
console.log(chalk.green(t('section.add.files_downloaded')));
|
|
295
|
+
console.log(chalk.yellow(t('section.add.files_updating')));
|
|
296
|
+
|
|
297
|
+
// Extract
|
|
298
|
+
const zip = new AdmZip(zipPath);
|
|
299
|
+
zip.extractAllTo(themePath, true);
|
|
300
|
+
|
|
301
|
+
await fs.unlink(zipPath);
|
|
302
|
+
|
|
303
|
+
console.log(chalk.green(t('section.add.files_updated') + '\n'));
|
|
304
|
+
console.log(chalk.bold.green(t('section.add.complete')));
|
|
305
|
+
console.log(chalk.gray(' ' + t('section.add.location', { path: `${storeSlug}/${activeTheme}/` }) + '\n'));
|
|
306
|
+
} catch (downloadError) {
|
|
307
|
+
console.error(chalk.red(t('section.add.download_error')), downloadError.message);
|
|
308
|
+
console.log(chalk.yellow('\n' + t('section.add.manual_tip')));
|
|
309
|
+
console.log(chalk.white(` ${t('section.add.manual_command')}\n`));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error(chalk.red('\n❌ Hata: ') + error.message);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
}
|