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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsoft-cli",
3
- "version": "3.12.1",
3
+ "version": "3.12.2",
4
4
  "description": "Command-line tool for tsoft360 theme development and management",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -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 => slugify(t.name) === themeSlug);
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(`${themeSlug} (${tempThemeInfo.name} v${tempThemeInfo.version})`));
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 response = await apiClient.get('/theme');
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 = slugify(selectedTheme.name);
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(themeSlug);
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 apiClient = await createApiClient();
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(themeSlug);
421
+ await setActiveTheme(selectedTheme.theme_folder);
446
422
  } else if (!themeInfo) {
447
423
  themeInfo = await getThemeUuid(themeSlug);
448
424
  }
449
425
 
450
- console.log(chalk.green(t('dev.active_theme_set', { slug: themeSlug })));
426
+ const themeDir = slugify(themeInfo.name);
451
427
 
452
- let themePath = path.join(getWorkspaceRoot(), storeSlug, themeSlug);
453
- console.log(chalk.green(t('dev.working_dir', { path: `${storeSlug}/${themeSlug}/` }) + '\n'));
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 apiClient.get('/theme');
459
- const currentTheme = (orgResponse.data || []).find(t => t.theme_folder === themeInfo.uuid);
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
- themeSlug = slugify(forkedData.name);
484
- themePath = path.join(getWorkspaceRoot(), storeSlug, themeSlug);
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 response = await apiClient.get('/theme');
18
- const themes = response.data || [];
18
+ const { themes } = await fetchAllThemes(apiClient);
19
19
 
20
20
  let sourceTheme;
21
21
 
@@ -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 apiClient = await createApiClient();
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 response = await apiClient.get('/theme');
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 apiClient = await createApiClient();
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 => slugify(t.name) === themeSlug);
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 { createApiClient } = await import('./api-client.js');
739
- const apiClient = await createApiClient();
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
+ }