tsoft-cli 3.12.1 → 3.12.2
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 +6 -0
- package/package.json +1 -1
- package/src/commands/theme-dev.js +20 -41
- package/src/commands/theme-init.js +2 -2
- package/src/commands/theme.js +6 -11
- package/src/storage.js +2 -4
- package/src/theme-list.js +25 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,12 @@ All notable changes to T-Soft CLI will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.12.2] - 2026-08-11
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- `theme dev` no longer reports the theme you are working on as missing. Downloading a theme records which one is active, and `theme dev` then looked that record up by the theme's name — but what gets recorded is the theme's folder, so the lookup could never match and every run ended in "theme not found". Re-downloading the theme did not help, because the failure was in the lookup rather than in the files on disk. Both spellings are now accepted, so the record written by `theme pull`, `theme use` and `theme dev` resolves the same way. `theme use` also accepts a theme folder as its argument.
|
|
12
|
+
- Theme lists no longer stop at the first page. `theme list`, `theme pull`, `theme use`, `theme init` and `theme section` asked the store for its themes and read only the first page of the answer, so on a store with many themes the rest were invisible: pickers showed a fraction of what was there and looking a theme up by name failed for anything further down. Every page is now read before the list is used.
|
|
13
|
+
|
|
8
14
|
## [3.12.1] - 2026-08-08
|
|
9
15
|
|
|
10
16
|
### Fixed
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import {normalizeStoreDomain, slugify, setActiveTheme, getActiveTheme, getActive
|
|
|
10
10
|
import {decorateThemeName} from '../ui/theme-choice.js';
|
|
11
11
|
import {brandedConfirm, brandedSelect, showWarning, showError, showInfo} from '../ui/prompt-wrapper.js';
|
|
12
12
|
import {themeSectionMenuCommand} from './theme-section.js';
|
|
13
|
+
import {fetchAllThemes} from '../theme-list.js';
|
|
13
14
|
import { t } from '../i18n.js';
|
|
14
15
|
|
|
15
16
|
function resolveSectionFolder(relativePath) {
|
|
@@ -29,32 +30,11 @@ function resolveSectionFolder(relativePath) {
|
|
|
29
30
|
return folder.includes('.') ? null : folder;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
async function fetchAllThemes() {
|
|
33
|
-
const apiClient = await createApiClient();
|
|
34
|
-
const all = [];
|
|
35
|
-
let page = 1;
|
|
36
|
-
|
|
37
|
-
// Server ignores per_page; we walk every page until last_page is reached.
|
|
38
|
-
// Hard cap at 20 pages to avoid accidental infinite loops.
|
|
39
|
-
for (let i = 0; i < 20; i++) {
|
|
40
|
-
const response = await apiClient.get(`/theme?per_page=500&page=${page}`);
|
|
41
|
-
const batch = response.data || [];
|
|
42
|
-
all.push(...batch);
|
|
43
|
-
|
|
44
|
-
// Laravel paginator puts last_page at the top level (not under meta).
|
|
45
|
-
// Some envelopes wrap it under meta — support both.
|
|
46
|
-
const lastPage = response.last_page ?? response.meta?.last_page ?? 1;
|
|
47
|
-
if (page >= lastPage || batch.length === 0) break;
|
|
48
|
-
page++;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return all;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
33
|
async function getThemeUuid(themeSlug) {
|
|
55
|
-
const themes = await fetchAllThemes();
|
|
34
|
+
const { themes } = await fetchAllThemes();
|
|
56
35
|
|
|
57
|
-
const theme = themes.find(t =>
|
|
36
|
+
const theme = themes.find(t => t.theme_folder === themeSlug)
|
|
37
|
+
?? themes.find(t => slugify(t.name) === themeSlug);
|
|
58
38
|
|
|
59
39
|
if (!theme) {
|
|
60
40
|
throw new Error(`Tema bulunamadı: ${themeSlug}`);
|
|
@@ -377,9 +357,8 @@ export async function themeDevCommand(themeName) {
|
|
|
377
357
|
if (themeSlug && !themeName) {
|
|
378
358
|
console.log(chalk.cyan(t('dev.active_site')) + ' ' + chalk.white(store));
|
|
379
359
|
|
|
380
|
-
const apiClient = await createApiClient();
|
|
381
360
|
const tempThemeInfo = await getThemeUuid(themeSlug);
|
|
382
|
-
console.log(chalk.cyan(t('dev.active_theme')) + ' ' + chalk.white(`${
|
|
361
|
+
console.log(chalk.cyan(t('dev.active_theme')) + ' ' + chalk.white(`${slugify(tempThemeInfo.name)} (${tempThemeInfo.name} v${tempThemeInfo.version})`));
|
|
383
362
|
console.log();
|
|
384
363
|
|
|
385
364
|
const continueWithActive = await brandedConfirm({
|
|
@@ -388,8 +367,7 @@ export async function themeDevCommand(themeName) {
|
|
|
388
367
|
}, t('dev.continue_title'));
|
|
389
368
|
|
|
390
369
|
if (!continueWithActive) {
|
|
391
|
-
const
|
|
392
|
-
const themes = response.data || [];
|
|
370
|
+
const { themes } = await fetchAllThemes();
|
|
393
371
|
|
|
394
372
|
if (themes.length === 0) {
|
|
395
373
|
showWarning(t('dev.no_themes'));
|
|
@@ -407,12 +385,12 @@ export async function themeDevCommand(themeName) {
|
|
|
407
385
|
message: t('dev.select_theme_prompt'), choices: themeChoices
|
|
408
386
|
}, t('dev.select_theme_title'));
|
|
409
387
|
|
|
410
|
-
themeSlug =
|
|
388
|
+
themeSlug = selectedTheme.theme_folder;
|
|
411
389
|
themeInfo = {
|
|
412
390
|
uuid: selectedTheme.theme_folder, name: selectedTheme.name, version: selectedTheme.version
|
|
413
391
|
};
|
|
414
392
|
|
|
415
|
-
await setActiveTheme(
|
|
393
|
+
await setActiveTheme(selectedTheme.theme_folder);
|
|
416
394
|
console.log();
|
|
417
395
|
} else {
|
|
418
396
|
themeInfo = tempThemeInfo;
|
|
@@ -420,9 +398,7 @@ export async function themeDevCommand(themeName) {
|
|
|
420
398
|
}
|
|
421
399
|
|
|
422
400
|
if (!themeSlug) {
|
|
423
|
-
const
|
|
424
|
-
const response = await apiClient.get('/theme');
|
|
425
|
-
const themes = response.data || [];
|
|
401
|
+
const { themes } = await fetchAllThemes();
|
|
426
402
|
|
|
427
403
|
if (themes.length === 0) {
|
|
428
404
|
showWarning(t('dev.no_themes'));
|
|
@@ -442,21 +418,23 @@ export async function themeDevCommand(themeName) {
|
|
|
442
418
|
uuid: selectedTheme.theme_folder, name: selectedTheme.name, version: selectedTheme.version
|
|
443
419
|
};
|
|
444
420
|
|
|
445
|
-
await setActiveTheme(
|
|
421
|
+
await setActiveTheme(selectedTheme.theme_folder);
|
|
446
422
|
} else if (!themeInfo) {
|
|
447
423
|
themeInfo = await getThemeUuid(themeSlug);
|
|
448
424
|
}
|
|
449
425
|
|
|
450
|
-
|
|
426
|
+
const themeDir = slugify(themeInfo.name);
|
|
451
427
|
|
|
452
|
-
|
|
453
|
-
|
|
428
|
+
console.log(chalk.green(t('dev.active_theme_set', { slug: themeDir })));
|
|
429
|
+
|
|
430
|
+
let themePath = path.join(getWorkspaceRoot(), storeSlug, themeDir);
|
|
431
|
+
console.log(chalk.green(t('dev.working_dir', { path: `${storeSlug}/${themeDir}/` }) + '\n'));
|
|
454
432
|
|
|
455
433
|
// Fork check: foreign-org themes need fork before development
|
|
456
434
|
{
|
|
457
435
|
const apiClient = await createApiClient();
|
|
458
|
-
const orgResponse = await
|
|
459
|
-
const currentTheme =
|
|
436
|
+
const { themes: orgThemes, envelope: orgResponse } = await fetchAllThemes();
|
|
437
|
+
const currentTheme = orgThemes.find(t => t.theme_folder === themeInfo.uuid);
|
|
460
438
|
const userOrg = orgResponse.user_organization;
|
|
461
439
|
|
|
462
440
|
if (currentTheme && currentTheme.organization_id != null && userOrg && !userOrg.is_system && currentTheme.organization_id !== userOrg.id) {
|
|
@@ -480,8 +458,9 @@ export async function themeDevCommand(themeName) {
|
|
|
480
458
|
const forkedData = forkResult.data;
|
|
481
459
|
|
|
482
460
|
themeInfo = { uuid: forkedData.uuid, name: forkedData.name, version: forkedData.version };
|
|
483
|
-
|
|
484
|
-
|
|
461
|
+
themePath = path.join(getWorkspaceRoot(), storeSlug, slugify(forkedData.name));
|
|
462
|
+
|
|
463
|
+
await setActiveTheme(forkedData.uuid);
|
|
485
464
|
|
|
486
465
|
console.log(chalk.green(t('dev.fork_success', { name: forkedData.name })));
|
|
487
466
|
console.log(chalk.gray(' ' + t('dev.fork_source', { source: forkedData.source.name, version: forkedData.source.version })));
|
|
@@ -6,6 +6,7 @@ import AdmZip from 'adm-zip';
|
|
|
6
6
|
import {createApiClient} from '../api-client.js';
|
|
7
7
|
import {slugify, getActiveStore, getWorkspaceRoot} from '../storage.js';
|
|
8
8
|
import {brandedConfirm, brandedSelect, showWarning, showInfo, showError} from '../ui/prompt-wrapper.js';
|
|
9
|
+
import {fetchAllThemes} from '../theme-list.js';
|
|
9
10
|
import {normalizeStoreDomain} from '../storage.js';
|
|
10
11
|
import {isJsonMode, jsonOut} from '../output-mode.js';
|
|
11
12
|
import {mapError} from '../errors/error-mapper.js';
|
|
@@ -14,8 +15,7 @@ import { t } from '../i18n.js';
|
|
|
14
15
|
export async function themeInitCommand(fromSlug) {
|
|
15
16
|
try {
|
|
16
17
|
const apiClient = await createApiClient();
|
|
17
|
-
const
|
|
18
|
-
const themes = response.data || [];
|
|
18
|
+
const { themes } = await fetchAllThemes(apiClient);
|
|
19
19
|
|
|
20
20
|
let sourceTheme;
|
|
21
21
|
|
package/src/commands/theme.js
CHANGED
|
@@ -7,6 +7,7 @@ import AdmZip from 'adm-zip';
|
|
|
7
7
|
import {createApiClient} from '../api-client.js';
|
|
8
8
|
import {normalizeStoreDomain, slugify, setActiveTheme, getActiveTheme, getActiveStore, getWorkspaceRoot, getWorkspaceContext} from '../storage.js';
|
|
9
9
|
import {brandedConfirm, brandedSelect, brandedSearch, showWarning} from '../ui/prompt-wrapper.js';
|
|
10
|
+
import {fetchAllThemes} from '../theme-list.js';
|
|
10
11
|
import {themeLabels, decorateThemeName} from '../ui/theme-choice.js';
|
|
11
12
|
import { isJsonMode, jsonOut } from '../output-mode.js';
|
|
12
13
|
import { mapError } from '../errors/error-mapper.js';
|
|
@@ -20,10 +21,7 @@ export async function themeListCommand() {
|
|
|
20
21
|
console.log(chalk.cyan(t('theme.listing') + '\n'));
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
const
|
|
24
|
-
const response = await apiClient.get('/theme');
|
|
25
|
-
|
|
26
|
-
const themes = response.data || [];
|
|
24
|
+
const { themes } = await fetchAllThemes();
|
|
27
25
|
|
|
28
26
|
if (isJsonMode()) {
|
|
29
27
|
jsonOut({
|
|
@@ -344,9 +342,7 @@ export async function themePullCommand() {
|
|
|
344
342
|
console.log(chalk.cyan(t('theme.pull.starting') + '\n'));
|
|
345
343
|
|
|
346
344
|
const apiClient = await createApiClient();
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
const themes = response.data || [];
|
|
345
|
+
const { themes } = await fetchAllThemes(apiClient);
|
|
350
346
|
|
|
351
347
|
if (themes.length === 0) {
|
|
352
348
|
showWarning(t('theme.pull.no_themes'));
|
|
@@ -445,9 +441,7 @@ export async function themeUseCommand(themeName = null) {
|
|
|
445
441
|
console.log(chalk.gray(t('theme.use.on_store', { store })) + '\n');
|
|
446
442
|
}
|
|
447
443
|
|
|
448
|
-
const
|
|
449
|
-
const response = await apiClient.get('/theme');
|
|
450
|
-
const themes = response.data || [];
|
|
444
|
+
const { themes } = await fetchAllThemes();
|
|
451
445
|
|
|
452
446
|
if (themes.length === 0) {
|
|
453
447
|
showWarning(t('theme.use.no_themes'));
|
|
@@ -458,7 +452,8 @@ export async function themeUseCommand(themeName = null) {
|
|
|
458
452
|
|
|
459
453
|
if (themeName) {
|
|
460
454
|
const themeSlug = slugify(themeName);
|
|
461
|
-
selectedTheme = themes.find(t =>
|
|
455
|
+
selectedTheme = themes.find(t => t.theme_folder === themeName)
|
|
456
|
+
?? themes.find(t => slugify(t.name) === themeSlug);
|
|
462
457
|
|
|
463
458
|
if (!selectedTheme) {
|
|
464
459
|
console.error(chalk.red('\n' + t('theme.use.not_found', { name: themeName })));
|
package/src/storage.js
CHANGED
|
@@ -735,10 +735,8 @@ export async function resolveActiveThemeUuid() {
|
|
|
735
735
|
* @returns {Promise<Object>} {uuid, name, version, active}
|
|
736
736
|
*/
|
|
737
737
|
export async function getThemeUuidBySlug(themeSlug) {
|
|
738
|
-
const {
|
|
739
|
-
const
|
|
740
|
-
const response = await apiClient.get('/theme');
|
|
741
|
-
const themes = response.data || [];
|
|
738
|
+
const { fetchAllThemes } = await import('./theme-list.js');
|
|
739
|
+
const { themes } = await fetchAllThemes();
|
|
742
740
|
|
|
743
741
|
const theme = themes.find(t => t.theme_folder === themeSlug)
|
|
744
742
|
?? themes.find(t => slugify(t.name) === themeSlug);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createApiClient } from './api-client.js';
|
|
2
|
+
|
|
3
|
+
export async function fetchAllThemes(client = null) {
|
|
4
|
+
const apiClient = client ?? await createApiClient();
|
|
5
|
+
const all = [];
|
|
6
|
+
let envelope = {};
|
|
7
|
+
let page = 1;
|
|
8
|
+
|
|
9
|
+
// Server ignores per_page; we walk every page until last_page is reached.
|
|
10
|
+
// Hard cap at 20 pages to avoid accidental infinite loops.
|
|
11
|
+
for (let i = 0; i < 20; i++) {
|
|
12
|
+
const response = await apiClient.get(`/theme?per_page=500&page=${page}`);
|
|
13
|
+
const batch = response.data || [];
|
|
14
|
+
all.push(...batch);
|
|
15
|
+
envelope = response;
|
|
16
|
+
|
|
17
|
+
// Laravel paginator puts last_page at the top level (not under meta).
|
|
18
|
+
// Some envelopes wrap it under meta — support both.
|
|
19
|
+
const lastPage = response.last_page ?? response.meta?.last_page ?? 1;
|
|
20
|
+
if (page >= lastPage || batch.length === 0) break;
|
|
21
|
+
page++;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return { themes: all, envelope };
|
|
25
|
+
}
|