@shoper/cli 0.5.2 → 0.6.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.
Files changed (94) hide show
  1. package/.docs/images/shoper-for-developers.svg +1 -0
  2. package/README.md +4 -2
  3. package/build/cli/auth/cli_auth_errors_factory.js +1 -1
  4. package/build/cli/auth/cli_auth_initializer.js +6 -1
  5. package/build/cli/auth/service/cli_auth_service.js +11 -2
  6. package/build/cli/auth/tokens/cli_auth_tokens_errors_factory.js +1 -1
  7. package/build/cli/auth/tokens/cli_auth_tokens_initalizer.js +6 -1
  8. package/build/cli/auth/tokens/service/cli_auth_tokens_service.js +8 -1
  9. package/build/cli/class/base_command.js +15 -1
  10. package/build/cli/class/cli_help.js +24 -0
  11. package/build/cli/class/errors/file_system_errors_factory.js +3 -3
  12. package/build/cli/class/errors/http/http_errors_factory.js +1 -1
  13. package/build/cli/commands/auth/cli_auth_add_token_command.js +5 -0
  14. package/build/cli/commands/auth/cli_auth_list_tokens_command.js +20 -11
  15. package/build/cli/commands/auth/cli_auth_logout_command.js +5 -0
  16. package/build/cli/commands/auth/cli_auth_remove_token_command.js +5 -0
  17. package/build/cli/commands/auth/cli_auth_switch_token_command.js +8 -0
  18. package/build/cli/commands/cli_update_command.js +5 -0
  19. package/build/cli/core/cli_setup.js +5 -13
  20. package/build/cli/features/execution_context/execution_context_service.js +2 -0
  21. package/build/cli/features/version/service/cli_version_service.js +2 -2
  22. package/build/cli/hooks/authorization/ensure_authorization_hook.js +9 -2
  23. package/build/cli/hooks/ensure_cli_initialized_hook.js +1 -6
  24. package/build/cli/hooks/ensure_logs_flushed_hook.js +7 -0
  25. package/build/cli/{features → utilities/features}/http_requester/http_client.js +2 -2
  26. package/build/cli/{features → utilities/features}/http_requester/http_requester_initializer.js +1 -1
  27. package/build/cli/utilities/features/logger/api/logger_api.js +28 -0
  28. package/build/cli/utilities/features/logger/logger_constants.js +7 -0
  29. package/build/cli/utilities/features/logger/logger_initializer.js +44 -0
  30. package/build/cli/utilities/features/logger/logs/app_error.js +14 -0
  31. package/build/cli/utilities/features/logger/logs/app_log.js +40 -0
  32. package/build/cli/{class/errors/app/app_error_constants.js → utilities/features/logger/logs/app_logs_constants.js} +1 -1
  33. package/build/cli/utilities/features/logger/service/logger_service.js +108 -0
  34. package/build/cli/utilities/features/logger/transports/log_object_map_transport.js +15 -0
  35. package/build/index.js +14 -2
  36. package/build/theme/class/archive/theme_archive.js +2 -1
  37. package/build/theme/class/archive/theme_archive_errors_factory.js +2 -2
  38. package/build/theme/class/checksums/theme_checksums.js +25 -5
  39. package/build/theme/class/checksums/theme_checksums_error_factory.js +3 -3
  40. package/build/theme/class/fetch_resources/fetch_resources.js +34 -4
  41. package/build/theme/class/fetch_resources/fetch_resources_errors_factory.js +1 -1
  42. package/build/theme/commands/delete/theme_delete_command.js +3 -2
  43. package/build/theme/commands/info/theme_info_command.js +3 -0
  44. package/build/theme/commands/init/theme_init_command.js +10 -1
  45. package/build/theme/commands/list/theme_list_command.js +22 -8
  46. package/build/theme/commands/publish/theme_publish_command.js +5 -1
  47. package/build/theme/commands/pull/theme_pull_command.js +17 -5
  48. package/build/theme/commands/push/theme_push_command.js +7 -1
  49. package/build/theme/commands/theme_verify_command.js +6 -1
  50. package/build/theme/commands/ui/theme_error.js +1 -1
  51. package/build/theme/features/theme/actions/service/theme_actions_service.js +42 -2
  52. package/build/theme/features/theme/actions/theme_actions_initializer.js +3 -1
  53. package/build/theme/features/theme/delete/service/theme_delete_service.js +12 -1
  54. package/build/theme/features/theme/delete/theme_delete_initalizer.js +6 -1
  55. package/build/theme/features/theme/fetch/service/theme_fetch_service.js +35 -6
  56. package/build/theme/features/theme/fetch/theme_fetch_initializer.js +3 -0
  57. package/build/theme/features/theme/init/service/theme_init_service.js +32 -6
  58. package/build/theme/features/theme/init/theme_init_initializer.js +4 -1
  59. package/build/theme/features/theme/merge/service/theme_merge_service.js +38 -4
  60. package/build/theme/features/theme/merge/theme_merge_initializer.js +3 -1
  61. package/build/theme/features/theme/push/service/theme_push_service.js +38 -10
  62. package/build/theme/features/theme/push/theme_push_errors_factory.js +2 -2
  63. package/build/theme/features/theme/push/theme_push_initializer.js +3 -1
  64. package/build/theme/features/theme/push/theme_push_utils.js +1 -1
  65. package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +2 -3
  66. package/build/theme/features/theme/utils/hidden_directory/hidden_directory_utils.js +5 -2
  67. package/build/theme/features/theme/utils/meta_data/theme_meta_data_error_factory.js +1 -1
  68. package/build/theme/features/theme/utils/resources/theme_resources_with_id_directory_utils.js +5 -2
  69. package/build/theme/features/theme/verify/theme_verify_initializer.js +4 -3
  70. package/build/theme/features/theme/verify/verify/theme_verify_service.js +20 -2
  71. package/build/theme/features/theme/watch/api/theme_watch_api.js +11 -0
  72. package/build/theme/features/theme/watch/service/theme_watch_service.js +48 -0
  73. package/build/theme/features/theme/watch/theme_watch_constants.js +2 -0
  74. package/build/theme/features/theme/watch/theme_watch_initializer.js +20 -0
  75. package/build/theme/features/themes/list/services/themes_list_service.js +36 -5
  76. package/build/theme/features/themes/list/themes_list_initializer.js +3 -1
  77. package/build/theme/hooks/ensure_theme_meta_data_untouched_hook.js +4 -2
  78. package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_hook.js +7 -2
  79. package/build/theme/hooks/themes_actions/ensure_themes_actions_hook.js +2 -3
  80. package/build/ui/gradient.js +2 -0
  81. package/build/ui/hooks/stream_hook.js +26 -0
  82. package/build/utils/download_file/download_file_errors_factory.js +1 -1
  83. package/build/utils/download_file/download_file_utils.js +19 -1
  84. package/build/utils/fs/errors/stream_read_error.js +7 -5
  85. package/build/utils/fs/errors/stream_write_error.js +7 -5
  86. package/build/utils/get_api.js +9 -0
  87. package/build/utils/use_api.js +5 -0
  88. package/build/utils/zip/create_zip_utils.js +21 -9
  89. package/build/utils/zip/errors/create_zip_error.js +7 -5
  90. package/build/utils/zip/errors/open_zip_error.js +7 -5
  91. package/build/utils/zip/extract_zip_utils.js +90 -15
  92. package/oclif.config.js +3 -1
  93. package/package.json +15 -5
  94. package/build/cli/class/errors/app/app_error.js +0 -17
@@ -6,7 +6,18 @@ import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_file
6
6
  import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
7
7
  import { join, platformSeparator } from '../../../../../utils/path_utils.js';
8
8
  export class ThemeMergeService {
9
+ #loggerApi;
10
+ constructor({ loggerApi }) {
11
+ this.#loggerApi = loggerApi;
12
+ }
9
13
  async applyChanges(fromTheme, toTheme, changes) {
14
+ this.#loggerApi.info('Applying changes between themes.', {
15
+ details: {
16
+ from: fromTheme,
17
+ to: toTheme,
18
+ changesCount: changes.length
19
+ }
20
+ });
10
21
  FSTree.applyPatch(fromTheme, toTheme, changes, {
11
22
  create(inputPath, outputPath) {
12
23
  copyFileSync(inputPath, outputPath);
@@ -15,19 +26,36 @@ export class ThemeMergeService {
15
26
  copyFileSync(inputPath, outputPath);
16
27
  }
17
28
  });
18
- new ThemeChecksums(toTheme).updateAllChecksums();
29
+ this.#loggerApi.debug('Updating checksums after applying changes.');
30
+ await new ThemeChecksums({
31
+ themeDir: toTheme,
32
+ loggerApi: this.#loggerApi
33
+ }).updateAllChecksums();
19
34
  }
20
35
  async getChangesBetweenThemes(theme1, theme2) {
36
+ this.#loggerApi.info('Calculating changes between themes.', {
37
+ details: {
38
+ theme1,
39
+ theme2
40
+ }
41
+ });
21
42
  const theme1Tree = new FSTree({
22
43
  entries: walkSync.entries(theme1)
23
44
  });
24
45
  const theme2Tree = new FSTree({
25
46
  entries: walkSync.entries(theme2)
26
47
  });
27
- const theme1Checksums = new ThemeChecksums(theme1);
28
- const theme2Checksums = new ThemeChecksums(theme2);
48
+ const theme1Checksums = new ThemeChecksums({
49
+ themeDir: theme1,
50
+ loggerApi: this.#loggerApi
51
+ });
52
+ const theme2Checksums = new ThemeChecksums({
53
+ themeDir: theme2,
54
+ loggerApi: this.#loggerApi
55
+ });
56
+ this.#loggerApi.debug('Getting user-defined root directories to exclude from changes.');
29
57
  const userDirectories = await this._getUserRootDirectories(theme2);
30
- return theme2Tree
58
+ const patch = theme2Tree
31
59
  .calculatePatch(theme1Tree, (entryA, entryB) => {
32
60
  if (entryA.isDirectory() && entryB.isDirectory())
33
61
  return true;
@@ -37,6 +65,12 @@ export class ThemeMergeService {
37
65
  .filter(([_, name]) => {
38
66
  return !name.startsWith(SHOPER_THEME_METADATA_DIR) && !userDirectories.some((userDir) => name.startsWith(userDir));
39
67
  });
68
+ this.#loggerApi.debug('Calculated patch between themes.', {
69
+ details: {
70
+ patchSize: patch.length
71
+ }
72
+ });
73
+ return patch;
40
74
  }
41
75
  async _getUserRootDirectories(themeDir) {
42
76
  const filesStructure = await ThemeFilesStructureUtils.getThemeRootDirectories(themeDir);
@@ -2,10 +2,12 @@ import { FEATURE_CORES_TYPES, SyncFeatureInitializer } from '@dreamcommerce/star
2
2
  import { ThemeMergeApi } from './api/theme_merge_api.js';
3
3
  import { ThemeMergeService } from './service/theme_merge_service.js';
4
4
  import { THEME_MERGE_FEATURE_NAME } from './theme_merge_constants.js';
5
+ import { LOGGER_API_NAME } from '../../../../cli/utilities/features/logger/logger_constants.js';
5
6
  export class ThemeMergeInitializer extends SyncFeatureInitializer {
6
7
  static featureName = THEME_MERGE_FEATURE_NAME;
7
8
  init() {
8
- const themeMergeService = new ThemeMergeService();
9
+ const loggerApi = this.getApiSync(LOGGER_API_NAME);
10
+ const themeMergeService = new ThemeMergeService({ loggerApi });
9
11
  return {
10
12
  cores: [
11
13
  {
@@ -7,7 +7,7 @@ import { THEME_MODULE_SETTINGS_FILE_NAME } from '../theme_push_constants.js';
7
7
  import { ThemePushErrorsFactory } from '../theme_push_errors_factory.js';
8
8
  import { ThemeImagesUtils } from '../../utils/theme_images_utils.js';
9
9
  import { ThemePublishUtils } from '../../skinstore/theme_publish_utils.js';
10
- import { removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
10
+ import { writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
11
11
  import { MODULES_DIRECTORY_NAME } from '../../theme_constants.js';
12
12
  import { removeOldResources } from '../../../../class/fetch_resources/fetch_resources_utils.js';
13
13
  import { ThemeArchive } from '../../../../class/archive/theme_archive.js';
@@ -16,63 +16,92 @@ import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
16
16
  import { ArrayUtils } from '@dreamcommerce/utilities';
17
17
  export class ThemePushService {
18
18
  #themeFetchApi;
19
- constructor({ themeFetchApi }) {
19
+ #loggerApi;
20
+ constructor({ themeFetchApi, loggerApi }) {
20
21
  this.#themeFetchApi = themeFetchApi;
22
+ this.#loggerApi = loggerApi;
21
23
  }
22
24
  async push({ pushAction, credentials, filesStructure, executionContext, themeChecksums, themeFilesUploadApi }) {
25
+ this.#loggerApi.info('Pushing theme changes.');
23
26
  const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
27
+ this.#loggerApi.debug('Temporary directory created.', { details: { path: tmpDir } });
24
28
  const themeRootDir = executionContext.themeRootDir;
25
29
  if (await themeChecksums.hasThemeFileBeenCreated(ThemePublishUtils.getSkinStoreSettingsFilePath(themeRootDir)))
26
30
  throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
27
31
  try {
28
- //TODO to do api?
32
+ this.#loggerApi.debug('Getting file records from action data.');
29
33
  const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
30
34
  themeRootDir,
31
35
  themeAction: pushAction,
32
36
  filesStructure
33
37
  });
38
+ this.#loggerApi.debug('Filtering for created or modified files to upload.');
34
39
  const filesToUpload = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenCreated(path)) || (await themeChecksums.hasThemeFileBeenModified(path)));
40
+ this.#loggerApi.debug('Files to upload determined.', { details: { count: filesToUpload.length } });
35
41
  if (filesToUpload.length) {
42
+ this.#loggerApi.info(`Uploading ${filesToUpload.length} theme files.`);
36
43
  const { localFileNameToUploaded } = await this._uploadThemeFiles({
37
44
  filesToUpload,
38
45
  credentials,
39
46
  themeRootDir,
40
47
  themeFilesUploadApi
41
48
  });
49
+ this.#loggerApi.info('Theme files uploaded.');
42
50
  await this._createFilesList(themeRootDir, filesRecords, localFileNameToUploaded);
43
51
  }
44
52
  else {
53
+ this.#loggerApi.info('No new or modified files to upload. Creating files list for archive.');
45
54
  await this._createFilesList(themeRootDir, filesRecords);
46
55
  }
47
56
  const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
57
+ this.#loggerApi.info('Creating theme archive.');
48
58
  await new ThemeArchive(themeRootDir).createFullArchive({
49
59
  dist: themeArchivePath,
50
60
  actionValue: THEME_WILDCARD_ACTION_NAME,
51
- actionType: THEME_ACTIONS_TYPES.push
61
+ actionType: THEME_ACTIONS_TYPES.push,
62
+ logger: this.#loggerApi
52
63
  });
64
+ this.#loggerApi.info('Theme archive created.');
53
65
  const { resources, modules } = await themeFilesUploadApi.uploadArchive({
54
66
  action: pushAction,
55
67
  themeArchivePath,
56
68
  credentials
57
69
  });
58
- if (modules)
70
+ this.#loggerApi.info('Theme archive uploaded.');
71
+ if (modules) {
72
+ this.#loggerApi.debug('Updating data for new modules.');
59
73
  await this._updateDataForNewCreatedModules({ modules, themeRootDir });
74
+ }
60
75
  if (resources) {
76
+ this.#loggerApi.debug('Removing old resources.');
61
77
  await removeOldResources(themeRootDir, resources);
78
+ this.#loggerApi.info('Fetching new resources.');
62
79
  await this.#themeFetchApi.fetchResources(credentials.shopUrl, themeRootDir, resources);
80
+ this.#loggerApi.info('New resources fetched.');
63
81
  }
82
+ this.#loggerApi.debug('Updating theme checksums.');
64
83
  await themeChecksums.updateAllChecksums();
84
+ this.#loggerApi.info('Push completed successfully.');
65
85
  }
66
86
  finally {
87
+ this.#loggerApi.debug('Cleaning up files list.');
67
88
  await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
68
89
  }
69
90
  }
70
91
  async _uploadThemeFiles({ filesToUpload, themeRootDir, credentials, themeFilesUploadApi }) {
71
92
  try {
93
+ this.#loggerApi.debug('Uploading individual files.');
72
94
  const { uploadedImageData, rejectedImageData } = await themeFilesUploadApi.uploadFiles(filesToUpload);
73
- if (uploadedImageData.length)
95
+ this.#loggerApi.debug('Individual files upload finished.', {
96
+ details: {
97
+ uploadedCount: uploadedImageData.length,
98
+ rejectedCount: rejectedImageData.length
99
+ }
100
+ });
101
+ if (uploadedImageData.length) {
102
+ this.#loggerApi.debug('Removing original files for uploaded images.');
74
103
  await ThemeImagesUtils.removeUploadedOriginalFiles(themeRootDir, uploadedImageData);
75
- // if (rejectedImageData.length) await this._removeUploadedThemeFiles(rejectedImageData, themeRootDir);
104
+ }
76
105
  return {
77
106
  localFileNameToUploaded: uploadedImageData.reduce((acc, { originalFilename, uploadedFilename }) => {
78
107
  return {
@@ -86,15 +115,14 @@ export class ThemePushService {
86
115
  throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl, err);
87
116
  }
88
117
  }
89
- async _removeUploadedThemeFiles(uploadedImageData, themeRootDir) {
90
- await Promise.all(uploadedImageData.map(({ location, originalFilename }) => removeFile(join(themeRootDir, location, originalFilename))));
91
- }
92
118
  async _updateDataForNewCreatedModules({ modules, themeRootDir }) {
93
119
  for (const [moduleDirectoryName, metaData] of Object.entries(modules)) {
120
+ this.#loggerApi.debug('Updating settings for new module.', { details: { module: moduleDirectoryName } });
94
121
  await writeJSONFile(join(themeRootDir, MODULES_DIRECTORY_NAME, moduleDirectoryName, THEME_MODULE_SETTINGS_FILE_NAME), JSON.parse(metaData[THEME_MODULE_SETTINGS_FILE_NAME]));
95
122
  }
96
123
  }
97
124
  async _createFilesList(themeRootDir, filesRecords, localFileNameToUploaded = {}) {
125
+ this.#loggerApi.debug('Creating filesList.json for the archive.');
98
126
  await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded));
99
127
  }
100
128
  }
@@ -1,4 +1,4 @@
1
- import { AppError } from '../../../../cli/class/errors/app/app_error.js';
1
+ import { AppError } from '../../../../cli/utilities/features/logger/logs/app_error.js';
2
2
  import { THEME_ARCHIVE_UPLOAD_ERROR, THEME_FILES_UPLOAD_ERROR } from './theme_push_constants.js';
3
3
  export class ThemePushErrorsFactory {
4
4
  static createErrorWhileUploadingThemeFiles(shopUrl, messages) {
@@ -16,7 +16,7 @@ export class ThemePushErrorsFactory {
16
16
  code: 'theme.push.error_creating_theme_archive',
17
17
  message: `Error while creating theme archive for shop "${shopUrl}"`,
18
18
  level: 'error',
19
- stack: error.stack
19
+ error
20
20
  });
21
21
  }
22
22
  static createErrorWhilePushingUnpublishedThemeWithSkinstoreData(shopUrl) {
@@ -3,11 +3,13 @@ import { ThemePushService } from './service/theme_push_service.js';
3
3
  import { THEME_PUSH_FEATURE_NAME } from './theme_push_constants.js';
4
4
  import { ThemePushApi } from './api/theme_push_api.js';
5
5
  import { THEME_FETCH_API_NAME } from '../fetch/theme_fetch_constants.js';
6
+ import { LOGGER_API_NAME } from '../../../../cli/utilities/features/logger/logger_constants.js';
6
7
  export class ThemePushInitializer extends SyncFeatureInitializer {
7
8
  static featureName = THEME_PUSH_FEATURE_NAME;
8
9
  init() {
9
10
  const service = new ThemePushService({
10
- themeFetchApi: this.getApiSync(THEME_FETCH_API_NAME)
11
+ themeFetchApi: this.getApiSync(THEME_FETCH_API_NAME),
12
+ loggerApi: this.getApiSync(LOGGER_API_NAME)
11
13
  });
12
14
  return {
13
15
  cores: [
@@ -1,5 +1,5 @@
1
1
  import globs from 'fast-glob';
2
- import { AppError } from '../../../../cli/class/errors/app/app_error.js';
2
+ import { AppError } from '../../../../cli/utilities/features/logger/logs/app_error.js';
3
3
  import { toUnixPath } from '../../../../utils/path_utils.js';
4
4
  import { ThemeFilesStructureUtils } from '../utils/files_structure/theme_files_structure_utils.js';
5
5
  export class ThemePushUtils {
@@ -1,10 +1,9 @@
1
- import { AppError } from '../../../../../cli/class/errors/app/app_error.js';
1
+ import { AppError } from '../../../../../cli/utilities/features/logger/logs/app_error.js';
2
2
  export class ThemeFileStructureErrorsFactory {
3
3
  static createNoFilesStructureError() {
4
4
  return new AppError({
5
5
  code: 'theme.file_structure.no_files_structure',
6
- message: 'No files structure found for the theme.',
7
- level: 'error'
6
+ message: 'No files structure found for the theme.'
8
7
  });
9
8
  }
10
9
  }
@@ -5,9 +5,12 @@ import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_conts
5
5
  import { computeFileChecksum } from '../../../../../utils/checksums/checksums_utils.js';
6
6
  import { ThemeChecksums } from '../../../../class/checksums/theme_checksums.js';
7
7
  export class HiddenDirectoryUtils {
8
- static async ensureFilesInsideThemeMetaDataDirectoryUntouched(themeDirectory) {
8
+ static async ensureFilesInsideThemeMetaDataDirectoryUntouched(themeDirectory, logger) {
9
9
  const themeMetadataPath = this.getThemeHiddenDirectoryPath(themeDirectory);
10
- const themeChecksums = new ThemeChecksums(themeDirectory);
10
+ const themeChecksums = new ThemeChecksums({
11
+ themeDir: themeDirectory,
12
+ loggerApi: logger
13
+ });
11
14
  if (!(await themeChecksums.verify()))
12
15
  throw new Error('Theme checksum file is not valid');
13
16
  const filesNames = (await getAllFilesNamesInside(themeMetadataPath)).filter((fileName) => fileName !== THEME_CURRENT_CHECKSUMS_FILE_NAME &&
@@ -1,4 +1,4 @@
1
- import { AppError } from '../../../../../cli/class/errors/app/app_error.js';
1
+ import { AppError } from '../../../../../cli/utilities/features/logger/logs/app_error.js';
2
2
  import { THEME_WORK_URL_MISMATCH_ERROR } from './theme_meta_data_constants.js';
3
3
  export class ThemeMetaDataErrorFactory {
4
4
  static createThemeWorkUrlMismatchError(executionUrl, themeWorkUrl) {
@@ -5,7 +5,7 @@ import { fileExists, readJSONFile, renameFile } from '../../../../../utils/fs/fs
5
5
  import { join } from '../../../../../utils/path_utils.js';
6
6
  import { ThemeChecksums } from '../../../../class/checksums/theme_checksums.js';
7
7
  export class ThemeResourcesWithIdDirectoryUtils {
8
- static async updateDirectoryNamesOfResourcesWithIdAccordingToLocalThemeNames(localResourcesDir, remoteResourcesDir, remoteThemeDir) {
8
+ static async updateDirectoryNamesOfResourcesWithIdAccordingToLocalThemeNames(localResourcesDir, remoteResourcesDir, remoteThemeDir, loggerApi) {
9
9
  const localThemeTree = new FSTree({
10
10
  entries: walkSync.entries(localResourcesDir, {
11
11
  globs: ['*/'],
@@ -37,7 +37,10 @@ export class ThemeResourcesWithIdDirectoryUtils {
37
37
  /**
38
38
  * If these become performance bottlenecks, we can consider computing checksums only for the changed directories.
39
39
  */
40
- await new ThemeChecksums(remoteThemeDir).updateAllChecksums();
40
+ await new ThemeChecksums({
41
+ themeDir: remoteThemeDir,
42
+ loggerApi
43
+ }).updateAllChecksums();
41
44
  }
42
45
  }
43
46
  static async _getResourceIdToFilePathMap(entries) {
@@ -1,12 +1,13 @@
1
- import { FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME, SyncFeatureInitializer } from '@dreamcommerce/star_core';
1
+ import { FEATURE_CORES_TYPES, SyncFeatureInitializer } from '@dreamcommerce/star_core';
2
2
  import { ThemeVerifyApi } from './api/theme_verify_api.js';
3
3
  import { THEME_VERIFY_FEATURE_NAME } from './theme_verify_constants.js';
4
4
  import { ThemeVerifyService } from './verify/theme_verify_service.js';
5
+ import { LOGGER_API_NAME } from '../../../../cli/utilities/features/logger/logger_constants.js';
5
6
  export class ThemeVerifyInitializer extends SyncFeatureInitializer {
6
7
  static featureName = THEME_VERIFY_FEATURE_NAME;
7
8
  init() {
8
- const httpApi = this.getApiSync(HTTP_REQUESTER_API_NAME);
9
- const service = new ThemeVerifyService();
9
+ const loggerApi = this.getApiSync(LOGGER_API_NAME);
10
+ const service = new ThemeVerifyService({ loggerApi });
10
11
  return {
11
12
  cores: [
12
13
  {
@@ -10,21 +10,33 @@ import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_file
10
10
  import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
11
11
  import { ArrayUtils } from '@dreamcommerce/utilities';
12
12
  export class ThemeVerifyService {
13
+ #loggerApi;
14
+ constructor({ loggerApi }) {
15
+ this.#loggerApi = loggerApi;
16
+ }
13
17
  async verifyTheme({ verifyAction, executionContext, credentials, themeChecksums, filesStructure, themeFilesUploadApi }) {
18
+ this.#loggerApi.info('Verifying theme.');
14
19
  const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
20
+ this.#loggerApi.debug('Temporary directory created.', { details: { path: tmpDir } });
15
21
  const themeRootDir = executionContext.themeRootDir;
16
22
  if (await themeChecksums.hasThemeFileBeenCreated(ThemePublishUtils.getSkinStoreSettingsFilePath(themeRootDir)))
17
23
  throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
18
24
  try {
19
- //TODO to do api?
25
+ this.#loggerApi.debug('Getting file records from action data.');
20
26
  const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
21
27
  themeRootDir,
22
28
  themeAction: verifyAction,
23
29
  filesStructure
24
30
  });
31
+ this.#loggerApi.debug('Filtering for modified or not created files to verify.');
25
32
  const filesToVerify = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenModified(path)) || !(await themeChecksums.hasThemeFileBeenCreated(path)));
33
+ this.#loggerApi.debug('Files to verify determined.', { details: { count: filesToVerify.length } });
26
34
  if (filesToVerify.length) {
35
+ this.#loggerApi.info(`Verifying ${filesToVerify.length} theme files.`);
27
36
  const { rejectedImageData } = await themeFilesUploadApi.uploadFiles(filesToVerify);
37
+ this.#loggerApi.debug('Individual files verification finished.', {
38
+ details: { rejectedCount: rejectedImageData.length }
39
+ });
28
40
  if (rejectedImageData.length)
29
41
  return {
30
42
  isSuccess: false,
@@ -33,23 +45,29 @@ export class ThemeVerifyService {
33
45
  }
34
46
  await this._createFilesList(themeRootDir, filesToVerify);
35
47
  const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
48
+ this.#loggerApi.info('Creating theme archive for verification.');
36
49
  await new ThemeArchive(themeRootDir).createFullArchive({
37
50
  dist: themeArchivePath,
38
51
  actionValue: THEME_WILDCARD_ACTION_NAME,
39
- actionType: THEME_ACTIONS_TYPES.push
52
+ actionType: THEME_ACTIONS_TYPES.push,
53
+ logger: this.#loggerApi
40
54
  });
55
+ this.#loggerApi.info('Theme archive created.');
41
56
  const { isSuccess, messages } = await themeFilesUploadApi.uploadArchive({
42
57
  action: verifyAction,
43
58
  themeArchivePath,
44
59
  credentials
45
60
  });
61
+ this.#loggerApi.info('Theme archive verification finished.');
46
62
  return { isSuccess, messages };
47
63
  }
48
64
  finally {
65
+ this.#loggerApi.debug('Cleaning up files list.');
49
66
  await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
50
67
  }
51
68
  }
52
69
  async _createFilesList(themeRootDir, filesRecords) {
70
+ this.#loggerApi.debug('Creating filesList.json for the archive.');
53
71
  await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords));
54
72
  }
55
73
  }
@@ -0,0 +1,11 @@
1
+ import { FeatureApi } from '@dreamcommerce/star_core';
2
+ export class ThemeWatchApi extends FeatureApi {
3
+ #service;
4
+ constructor(service) {
5
+ super();
6
+ this.#service = service;
7
+ }
8
+ async watchTheme(props) {
9
+ return this.#service.watchTheme(props);
10
+ }
11
+ }
@@ -0,0 +1,48 @@
1
+ // import chokidar from 'chokidar';
2
+ import { relative } from '../../../../../utils/path_utils.js';
3
+ export class ThemeWatchService {
4
+ #themePushApi;
5
+ constructor({ themePushApi }) {
6
+ this.#themePushApi = themePushApi;
7
+ }
8
+ async watchTheme({ themeChecksums, executionContext, onChange, credentials, themeFilesUploadApi, filesStructure, action }) {
9
+ const themePath = executionContext.themeRootDir;
10
+ console.log(`[watch] Watching for changes in: ${themePath}`);
11
+ // const watcher = chokidar.watch(themePath, {
12
+ // ignored: /(^|[\/\\])\../, // ignore dotfiles
13
+ // persistent: true,
14
+ // ignoreInitial: true // Don't trigger on initial scan
15
+ // });
16
+ const handleFileChange = async (filePath) => {
17
+ const relativePath = relative(themePath, filePath);
18
+ console.log(`[watch] Detected change in: ${relativePath}. Pushing theme...`);
19
+ try {
20
+ // Assuming pushTheme handles the logic of pushing the entire theme
21
+ return;
22
+ //@ts-ignore
23
+ await this.#themePushApi.partialPush({
24
+ credentials,
25
+ filesStructure,
26
+ action,
27
+ executionContext,
28
+ themeChecksums,
29
+ themeFilesUploadApi
30
+ });
31
+ console.log(`[watch] Theme pushed successfully.`);
32
+ }
33
+ catch (error) {
34
+ console.error(`[watch] Error pushing theme:`, error);
35
+ }
36
+ };
37
+ //
38
+ // watcher
39
+ // .on('add', handleFileChange)
40
+ // .on('change', handleFileChange)
41
+ // .on('unlink', handleFileChange)
42
+ // //@ts-ignore
43
+ // .on('error', (error) => console.error(`[watch] Watcher error: ${error}`))
44
+ // .on('ready', () => console.log('[watch] Initial scan complete. Ready for changes.'));
45
+ // Keep the process alive while watching
46
+ return new Promise(() => { });
47
+ }
48
+ }
@@ -0,0 +1,2 @@
1
+ export const THEME_WATCH_FEATURE_NAME = 'ThemeWatch';
2
+ export const THEME_WATCH_API_NAME = 'ThemeWatchApi';
@@ -0,0 +1,20 @@
1
+ import { FEATURE_CORES_TYPES, SyncFeatureInitializer } from '@dreamcommerce/star_core';
2
+ import { THEME_WATCH_FEATURE_NAME } from './theme_watch_constants.js';
3
+ import { ThemeWatchService } from './service/theme_watch_service.js';
4
+ import { THEME_PUSH_API_NAME } from '../push/theme_push_constants.js';
5
+ import { ThemeWatchApi } from './api/theme_watch_api.js';
6
+ export class ThemeWatchInitializer extends SyncFeatureInitializer {
7
+ static featureName = THEME_WATCH_FEATURE_NAME;
8
+ init() {
9
+ const themePushApi = this.getApiSync(THEME_PUSH_API_NAME);
10
+ const service = new ThemeWatchService({ themePushApi });
11
+ return {
12
+ cores: [
13
+ {
14
+ type: FEATURE_CORES_TYPES.api,
15
+ instance: new ThemeWatchApi(service)
16
+ }
17
+ ]
18
+ };
19
+ }
20
+ }
@@ -4,20 +4,38 @@ import { HttpErrorsFactory } from '../../../../../cli/class/errors/http/http_err
4
4
  import { DownloadFileErrorsFactory } from '../../../../../utils/download_file/download_file_errors_factory.js';
5
5
  export class ThemesListService {
6
6
  #httpApi;
7
- constructor(httpApi) {
7
+ #loggerApi;
8
+ constructor(httpApi, loggerApi) {
8
9
  this.#httpApi = httpApi;
10
+ this.#loggerApi = loggerApi;
9
11
  }
10
12
  async getThemes({ shopUrl }) {
13
+ this.#loggerApi.info('Fetching themes list.', {
14
+ details: {
15
+ shopUrl
16
+ }
17
+ });
11
18
  try {
12
19
  const { response: request } = this.#httpApi.getThemes(shopUrl);
13
20
  const response = await request;
14
- if (response?.status !== STATUS_CODES.ok)
21
+ if (response?.status !== STATUS_CODES.ok) {
22
+ this.#loggerApi.debug('Failed to fetch themes list.', {
23
+ details: {
24
+ status: response?.status
25
+ }
26
+ });
15
27
  return;
16
- //TODO baseHttpApi + model mapper
17
- return response?.data.map(({ _links, ...rest }) => new ThemeMetaData({
28
+ }
29
+ const themes = response?.data.map(({ _links, ...rest }) => new ThemeMetaData({
18
30
  ...rest,
19
31
  links: _links
20
32
  }));
33
+ this.#loggerApi.info('Successfully fetched themes list.', {
34
+ details: {
35
+ count: themes.length
36
+ }
37
+ });
38
+ return themes;
21
39
  }
22
40
  catch (err) {
23
41
  switch (err.response?.status) {
@@ -35,9 +53,22 @@ export class ThemesListService {
35
53
  }
36
54
  }
37
55
  async getTheme({ themeId, shopUrl }) {
56
+ this.#loggerApi.debug('Getting a specific theme from list.', {
57
+ details: {
58
+ themeId,
59
+ shopUrl
60
+ }
61
+ });
38
62
  const themes = await this.getThemes({ shopUrl });
39
63
  if (!themes)
40
64
  return;
41
- return themes.find((theme) => String(theme.skinId) === themeId);
65
+ const theme = themes.find((theme) => String(theme.skinId) === themeId);
66
+ this.#loggerApi.debug(theme ? 'Theme found.' : 'Theme not found.', {
67
+ details: {
68
+ themeId,
69
+ found: !!theme
70
+ }
71
+ });
72
+ return theme;
42
73
  }
43
74
  }
@@ -3,11 +3,13 @@ import { ThemesListApi } from './api/themes_list_api.js';
3
3
  import { FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME, SyncFeatureInitializer } from '@dreamcommerce/star_core';
4
4
  import { THEMES_LIST_FEATURE_NAME } from './themes_list_constants.js';
5
5
  import { ThemesListHttpApi } from './http/themes_list_http_api.js';
6
+ import { LOGGER_API_NAME } from '../../../../cli/utilities/features/logger/logger_constants.js';
6
7
  export class ThemesListInitializer extends SyncFeatureInitializer {
7
8
  static featureName = THEMES_LIST_FEATURE_NAME;
8
9
  init() {
9
10
  const httpApi = this.getApiSync(HTTP_REQUESTER_API_NAME);
10
- const service = new ThemesListService(new ThemesListHttpApi(httpApi));
11
+ const loggerApi = this.getApiSync(LOGGER_API_NAME);
12
+ const service = new ThemesListService(new ThemesListHttpApi(httpApi), loggerApi);
11
13
  return {
12
14
  cores: [
13
15
  {
@@ -1,13 +1,15 @@
1
- import { useApi } from '../../cli/hooks/ensure_cli_initialized_hook.js';
2
1
  import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
3
2
  import { HiddenDirectoryUtils } from '../features/theme/utils/hidden_directory/hidden_directory_utils.js';
3
+ import { LOGGER_API_NAME } from '../../cli/utilities/features/logger/logger_constants.js';
4
+ import { useApi } from '../../utils/use_api.js';
4
5
  const ensureThemeMetaDataUntouched = async () => {
5
6
  const executionContextApi = useApi(EXECUTION_CONTEXT_API_NAME);
6
7
  const executionContext = await executionContextApi.getExecutionContext();
7
8
  if (executionContext.type !== EXECUTION_CONTEXTS.theme)
8
9
  return;
10
+ const loggerApi = useApi(LOGGER_API_NAME);
9
11
  try {
10
- await HiddenDirectoryUtils.ensureFilesInsideThemeMetaDataDirectoryUntouched(executionContext.themeRootDir);
12
+ await HiddenDirectoryUtils.ensureFilesInsideThemeMetaDataDirectoryUntouched(executionContext.themeRootDir, loggerApi);
11
13
  }
12
14
  catch (err) {
13
15
  console.error(err);
@@ -1,7 +1,8 @@
1
1
  import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
2
- import { useApi } from '../../../cli/hooks/ensure_cli_initialized_hook.js';
3
2
  import { THEME_COMMANDS_THAT_REQUIRES_UP_TO_DATE_CHECKSUMS } from './ensure_theme_current_checksums_up_to_date_constants.js';
4
3
  import { ThemeChecksums } from '../../class/checksums/theme_checksums.js';
4
+ import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
5
+ import { useApi } from '../../../utils/use_api.js';
5
6
  export const ensureThemeChecksumsUpToDate = async ({ Command }) => {
6
7
  if (!THEME_COMMANDS_THAT_REQUIRES_UP_TO_DATE_CHECKSUMS.includes(Command.id))
7
8
  return;
@@ -9,10 +10,14 @@ export const ensureThemeChecksumsUpToDate = async ({ Command }) => {
9
10
  const executionContext = await executionContextApi.getExecutionContext();
10
11
  if (executionContext.type !== EXECUTION_CONTEXTS.theme)
11
12
  return;
13
+ const loggerApi = useApi(LOGGER_API_NAME);
12
14
  /**
13
15
  * Naive solution, recalculate checksums every time a command based on checksums calculation is executed;
14
16
  * If performance becomes an issue, we can implement a more sophisticated solution, eg. recalculate checksums only when files in the theme directory have changed.
15
17
  */
16
- await new ThemeChecksums(executionContext.themeRootDir).updateCurrentChecksums();
18
+ await new ThemeChecksums({
19
+ themeDir: executionContext.themeRootDir,
20
+ loggerApi
21
+ }).updateCurrentChecksums();
17
22
  };
18
23
  export default ensureThemeChecksumsUpToDate;
@@ -1,10 +1,10 @@
1
- import { useApi } from '../../../cli/hooks/ensure_cli_initialized_hook.js';
2
1
  import { THEME_ACTIONS_API_NAME } from '../../features/theme/actions/theme_actions_constants.js';
3
2
  import { CLI_AUTH_API_NAME } from '../../../cli/auth/cli_auth_constants.js';
4
3
  import { THEME_COMMANDS_THAT_REQUIRED_ACTIONS_LIST } from './ensure_themes_actions_hook_constants.js';
5
4
  import { CLI_AUTH_TOKENS_API_NAME } from '../../../cli/auth/tokens/cli_auth_tokens_constants.js';
6
5
  import { promptForToken } from '../../../cli/commands/utils/prompt_for_token_utils.js';
7
6
  import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
7
+ import { useApi } from '../../../utils/use_api.js';
8
8
  const ensureThemesActionsHook = async ({ Command, argv }) => {
9
9
  if (!THEME_COMMANDS_THAT_REQUIRED_ACTIONS_LIST.includes(Command.id))
10
10
  return;
@@ -38,8 +38,7 @@ const ensureThemesActionsHook = async ({ Command, argv }) => {
38
38
  });
39
39
  }
40
40
  else {
41
- console.error(err);
42
- process.exit(1);
41
+ throw err;
43
42
  }
44
43
  }
45
44
  };
@@ -0,0 +1,2 @@
1
+ import InkGradient from 'ink-gradient';
2
+ export const Gradient = InkGradient;