@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
@@ -0,0 +1,28 @@
1
+ import { FeatureApi } from '@dreamcommerce/star_core';
2
+ import { LOGGER_API_NAME } from '../logger_constants.js';
3
+ export class LoggerApi extends FeatureApi {
4
+ moduleName = LOGGER_API_NAME;
5
+ #service;
6
+ constructor(loggerService) {
7
+ super();
8
+ this.#service = loggerService;
9
+ }
10
+ debug(message, props) {
11
+ this.#service.debug(message, props);
12
+ }
13
+ info(message, props) {
14
+ this.#service.info(message, props);
15
+ }
16
+ warn(message, props) {
17
+ this.#service.warn(message, props);
18
+ }
19
+ error(message, props) {
20
+ this.#service.error(message, props);
21
+ }
22
+ fatal(message, props) {
23
+ this.#service.fatal(message, props);
24
+ }
25
+ async flush() {
26
+ await this.#service.flush();
27
+ }
28
+ }
@@ -0,0 +1,7 @@
1
+ export const LOGGER_FEATURE_NAME = 'LoggerFeature';
2
+ export const LOGGER_API_NAME = 'LoggerApi';
3
+ export const LOGGER_DEBUG_MODES = {
4
+ none: 'none',
5
+ standard: 'standard',
6
+ detailed: 'detailed'
7
+ };
@@ -0,0 +1,44 @@
1
+ import { AsyncFeatureInitializer, FEATURE_CORES_TYPES } from '@dreamcommerce/star_core';
2
+ import { LoggerService } from './service/logger_service.js';
3
+ import { LoggerApi } from './api/logger_api.js';
4
+ import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../features/execution_context/execution_context_constants.js';
5
+ import { LOGGER_DEBUG_MODES, LOGGER_FEATURE_NAME } from './logger_constants.js';
6
+ import { CLI_VERSION_API_NAME } from '../../../features/version/cli_version_constants.js';
7
+ import { join } from '../../../../utils/path_utils.js';
8
+ export class LoggerInitializer extends AsyncFeatureInitializer {
9
+ static featureName = LOGGER_FEATURE_NAME;
10
+ async init() {
11
+ const executionContextApi = this.getApiSync(EXECUTION_CONTEXT_API_NAME);
12
+ const cliVersionApi = this.getApiSync(CLI_VERSION_API_NAME);
13
+ const executionContext = await executionContextApi.getExecutionContext();
14
+ const service = new LoggerService({
15
+ executionContext,
16
+ cliVersion: await cliVersionApi.getGloballyInstalledVersion(),
17
+ debugMode: this._getDebugMode(executionContext.command),
18
+ logFile: this.getLogFilePath(executionContext)
19
+ });
20
+ return {
21
+ cores: [
22
+ {
23
+ type: FEATURE_CORES_TYPES.api,
24
+ instance: new LoggerApi(service)
25
+ }
26
+ ]
27
+ };
28
+ }
29
+ _getDebugMode(command) {
30
+ if (command.includes('--vv'))
31
+ return LOGGER_DEBUG_MODES.detailed;
32
+ if (command.includes('--v'))
33
+ return LOGGER_DEBUG_MODES.standard;
34
+ return LOGGER_DEBUG_MODES.none;
35
+ }
36
+ getLogFilePath(executionContext) {
37
+ const logFileName = 'shoper_cli_debug_log.json';
38
+ if (executionContext.command.includes('--log-file')) {
39
+ return executionContext.type === EXECUTION_CONTEXTS.theme
40
+ ? join(executionContext.themeRootDir, logFileName)
41
+ : join(process.cwd(), logFileName);
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ import { AppLog } from './app_log.js';
2
+ import { APP_LOGS_TYPES } from './app_logs_constants.js';
3
+ export class AppError extends AppLog {
4
+ constructor({ details, message, level = APP_LOGS_TYPES.error, tags, error, code }) {
5
+ super({ message, level, tags, details });
6
+ this.tags = {
7
+ ...tags,
8
+ code
9
+ };
10
+ this.level = level;
11
+ if (error?.stack)
12
+ this.stacktrace = error.stack;
13
+ }
14
+ }
@@ -0,0 +1,40 @@
1
+ export class AppLog {
2
+ level;
3
+ message;
4
+ user;
5
+ /**
6
+ * Example:
7
+ * {
8
+ * userId: 123,
9
+ * shopId: 456,
10
+ * }
11
+ */
12
+ stacktrace;
13
+ // jakie wartosci dopuszczamy w value tagu?
14
+ tags;
15
+ /**
16
+ * Example:
17
+ * {
18
+ * // always added tags:
19
+ * environment: 'MacOS',
20
+ * appVersion: '1.0.0',
21
+ * nodeVersion: 'v16.0.0',
22
+ * traceId: 'unique-trace-id',
23
+ * command: 'theme init 2'
24
+ * // other tags not always added, spefic to the log, EXAMPLE ONLY added quickly:
25
+ * errorCode: 'E_THEME_NOT_FOUND',
26
+ * filePath: '/path/to/file',
27
+ * responseTimeMs: 250,
28
+ * }
29
+ */
30
+ details;
31
+ // user timestamp in ISO format or server timestamp added when proxied to elastic?
32
+ timestamp = new Date().toISOString();
33
+ constructor({ details, message, level, tags, stacktrace }) {
34
+ this.details = details;
35
+ this.message = message;
36
+ this.level = level;
37
+ this.tags = tags;
38
+ this.stacktrace = stacktrace;
39
+ }
40
+ }
@@ -1,4 +1,4 @@
1
- export const APP_ERRORS_LEVELS = {
1
+ export const APP_LOGS_TYPES = {
2
2
  fatal: 'fatal',
3
3
  error: 'error',
4
4
  warn: 'warn',
@@ -0,0 +1,108 @@
1
+ import pino from 'pino';
2
+ import os from 'node:os';
3
+ import { v4 as uuid } from 'uuid';
4
+ import { LOGGER_DEBUG_MODES } from '../logger_constants.js';
5
+ export class LoggerService {
6
+ #logger;
7
+ #transport;
8
+ #traceId = uuid();
9
+ #executionContext;
10
+ #cliVersion;
11
+ constructor({ executionContext, cliVersion, debugMode, logFile }) {
12
+ this.#executionContext = executionContext;
13
+ this.#cliVersion = cliVersion;
14
+ const { logger, transport } = this._getLoggerInstance(debugMode, logFile);
15
+ this.#logger = logger;
16
+ this.#transport = transport;
17
+ }
18
+ _getLoggerInstance(debugMode, logFile) {
19
+ const targets = [];
20
+ if (logFile) {
21
+ targets.push({
22
+ target: 'pino/file',
23
+ level: 'debug',
24
+ options: {
25
+ destination: logFile,
26
+ mkdir: true,
27
+ append: false
28
+ }
29
+ });
30
+ }
31
+ const prettyTransport = {
32
+ target: 'pino-pretty',
33
+ options: {
34
+ messageKey: 'message',
35
+ colorize: true,
36
+ translateTime: 'SYS:standard',
37
+ ignore: debugMode === LOGGER_DEBUG_MODES.standard
38
+ ? 'pid,hostname,message,traceId,cliVersion,nodeVersion,environment,command,details'
39
+ : ''
40
+ }
41
+ };
42
+ /*
43
+ * To bd potem zastąpine customowym transportem który wysyła logi do backendu
44
+ */
45
+ // const logObjectMapTransport = {
46
+ // target: '../transports/log_object_map_transport.js',
47
+ // options: {
48
+ // traceId: this.#traceId
49
+ // }
50
+ // };
51
+ // TODO docelowo logObjectMapTransport bd wysyłał logi do serwera jezeli bedzie gdzies error a apce
52
+ // targets: debugMode !== LOGGER_DEBUG_MODES.none ? [prettyTransport, logObjectMapTransport] : [logObjectMapTransport]
53
+ if (debugMode !== LOGGER_DEBUG_MODES.none) {
54
+ targets.push(prettyTransport);
55
+ }
56
+ const transport = targets.length
57
+ ? pino.transport({
58
+ targets
59
+ })
60
+ : undefined;
61
+ /**
62
+ * Przy dodawaniu logowania na backend trzeba to usunac
63
+ */
64
+ if (!transport)
65
+ return {};
66
+ const logger = pino({
67
+ level: 'debug',
68
+ base: {
69
+ pid: process.pid,
70
+ hostname: os.hostname(),
71
+ cliVersion: this.#cliVersion,
72
+ nodeVersion: process.version,
73
+ environment: os.type(),
74
+ command: this.#executionContext.command.slice(2).join(' '),
75
+ traceId: this.#traceId
76
+ },
77
+ timestamp: pino.stdTimeFunctions.isoTime,
78
+ messageKey: 'message'
79
+ }, transport);
80
+ return { logger, transport };
81
+ }
82
+ debug(message, props) {
83
+ this.#logger?.debug(props || {}, message);
84
+ }
85
+ info(message, props) {
86
+ this.#logger?.info(props || {}, message);
87
+ }
88
+ warn(message, props) {
89
+ this.#logger?.warn(props || {}, message);
90
+ }
91
+ error(message, props) {
92
+ this.#logger?.error(props || {}, message);
93
+ }
94
+ fatal(message, props) {
95
+ this.#logger?.fatal(props || {}, message);
96
+ }
97
+ async flush() {
98
+ if (!this.#transport || this.#transport?.destroyed) {
99
+ return;
100
+ }
101
+ return new Promise((resolve) => {
102
+ // The 'close' event is emitted when the stream has been fully closed.
103
+ this.#transport?.on('close', resolve);
104
+ this.#logger?.flush(); // Synchronously flush buffered logs
105
+ this.#transport?.end(); // End the stream, which will trigger 'close'
106
+ });
107
+ }
108
+ }
@@ -0,0 +1,15 @@
1
+ import { Writable } from 'node:stream';
2
+ export default async function (opts) {
3
+ return new Writable({
4
+ objectMode: true,
5
+ write(chunk, enc, cb) {
6
+ const mapped = mapLog(chunk);
7
+ cb();
8
+ return mapped;
9
+ }
10
+ });
11
+ }
12
+ function mapLog(obj) {
13
+ //TODO map for backend structure
14
+ return obj;
15
+ }
package/build/index.js CHANGED
@@ -9,6 +9,9 @@ import { CliAuthRemoveTokenCommand } from './cli/commands/auth/cli_auth_remove_t
9
9
  import { CliAuthSwitchTokenCommand } from './cli/commands/auth/cli_auth_switch_token_command.js';
10
10
  import { CliUIDumpCommand } from './cli/commands/cli_ui_dump_command.js';
11
11
  import { CliAuthLogoutCommand } from './cli/commands/auth/cli_auth_logout_command.js';
12
+ import { getApiSync } from './utils/get_api.js';
13
+ import { LOGGER_API_NAME } from './cli/utilities/features/logger/logger_constants.js';
14
+ import './cli/class/cli_help.js';
12
15
  //TODO
13
16
  //@ts-ignore
14
17
  if (typeof global.crypto !== 'object') {
@@ -24,12 +27,21 @@ function getRandomValues(array) {
24
27
  return crypto.webcrypto.getRandomValues(array);
25
28
  }
26
29
  process.on('uncaughtException', (err) => {
27
- //TODO handle error
28
30
  console.error('Uncaught Exception:', err);
31
+ const loggerApi = getApiSync(LOGGER_API_NAME);
32
+ if (loggerApi)
33
+ loggerApi.fatal('Uncaught Exception', { error: err });
29
34
  process.exit(1);
30
35
  });
31
- process.on('unhandledRejection', (reason, promise) => {
36
+ process.on('unhandledRejection', (reason) => {
32
37
  console.error('Unhandled Rejection:', reason);
38
+ const loggerApi = getApiSync(LOGGER_API_NAME);
39
+ if (loggerApi)
40
+ loggerApi.fatal('Unhandled Rejection', {
41
+ details: {
42
+ reason
43
+ }
44
+ });
33
45
  process.exit(1);
34
46
  });
35
47
  const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
@@ -11,7 +11,7 @@ export class ThemeArchive {
11
11
  constructor(themeRootDir) {
12
12
  this.#themeRootDir = themeRootDir;
13
13
  }
14
- async createFullArchive({ dist, actionValue, actionType }) {
14
+ async createFullArchive({ dist, actionValue, actionType, logger }) {
15
15
  const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(this.#themeRootDir);
16
16
  if (!filesStructure)
17
17
  throw ThemeFileStructureErrorsFactory.createNoFilesStructureError();
@@ -28,6 +28,7 @@ export class ThemeArchive {
28
28
  await this._formatJsonFiles(this.#themeRootDir, filesInThemeDirectory);
29
29
  return createZip({
30
30
  files: filesInThemeDirectory,
31
+ logger,
31
32
  baseDir: this.#themeRootDir,
32
33
  dist
33
34
  });
@@ -1,11 +1,11 @@
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 ThemeArchiveErrorsFactory {
3
3
  static createErrorWhileCreatingThemeArchive(error) {
4
4
  return new AppError({
5
5
  code: 'theme_archive.error_creating_archive',
6
6
  message: `Error while creating theme archive`,
7
7
  level: 'error',
8
- stack: error.stack
8
+ error
9
9
  });
10
10
  }
11
11
  }
@@ -12,14 +12,16 @@ import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.
12
12
  import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME, THEME_INITIAL_CHECKSUMS_FILE_NAME, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME } from '../../features/theme/theme_constants.js';
13
13
  export class ThemeChecksums {
14
14
  #themeDir;
15
+ #loggerApi;
15
16
  #currentChecksumsFilePath;
16
17
  #currentChecksumsVerificationFilePath;
17
18
  #initialChecksumsFilePath;
18
19
  #initialChecksumsVerificationFilePath;
19
20
  #initialChecksums;
20
21
  #currentChecksums;
21
- constructor(themeDir) {
22
+ constructor({ loggerApi, themeDir }) {
22
23
  this.#themeDir = themeDir;
24
+ this.#loggerApi = loggerApi;
23
25
  this.#currentChecksumsFilePath = ThemeChecksumsUtils.getCurrentThemeChecksumsFilePath(themeDir);
24
26
  this.#currentChecksumsVerificationFilePath = ThemeChecksumsUtils.getCurrentThemeChecksumsVerificationFilePath(themeDir);
25
27
  this.#initialChecksumsFilePath = ThemeChecksumsUtils.getInitialThemeChecksumsFilePath(themeDir);
@@ -83,6 +85,7 @@ export class ThemeChecksums {
83
85
  return initialChecksum === initialChecksumVerify;
84
86
  }
85
87
  async updateAllChecksums() {
88
+ this.#loggerApi?.debug('Updating all checksums.');
86
89
  const checksums = await this.computeThemeChecksums();
87
90
  return new Promise((resolve, reject) => {
88
91
  const currentChecksumFilePath = this.#currentChecksumsFilePath;
@@ -92,20 +95,24 @@ export class ThemeChecksums {
92
95
  checksumStream.end();
93
96
  checksumStream
94
97
  .on('finish', async () => {
98
+ this.#loggerApi?.debug('Finished writing initial checksums file. Creating verification files.');
95
99
  const initialChecksumVerifyFilePath = this.#initialChecksumsVerificationFilePath;
96
100
  const currentChecksumVerifyFilePath = this.#currentChecksumsVerificationFilePath;
97
101
  await this._createThemeChecksumVerifyFile(initialChecksumFilePath, initialChecksumVerifyFilePath);
98
102
  await copyFile(initialChecksumFilePath, currentChecksumFilePath);
99
103
  await copyFile(initialChecksumVerifyFilePath, currentChecksumVerifyFilePath);
104
+ this.#loggerApi?.debug('All checksums and verification files updated.');
100
105
  resolve();
101
106
  })
102
107
  .on('error', async (err) => {
108
+ this.#loggerApi?.debug('Error writing checksums file. Cleaning up.', { details: { file: initialChecksumFilePath } });
103
109
  await removeFile(initialChecksumFilePath, { force: true });
104
- reject(ThemeChecksumsErrorFactory.createThemeChecksumError(err.stack));
110
+ reject(ThemeChecksumsErrorFactory.createThemeChecksumError(err));
105
111
  });
106
112
  });
107
113
  }
108
114
  async updateCurrentChecksums() {
115
+ this.#loggerApi?.debug('Updating current checksums.');
109
116
  const checksums = await this.computeThemeChecksums();
110
117
  return new Promise((resolve, reject) => {
111
118
  const currentChecksumFilePath = this.#currentChecksumsFilePath;
@@ -114,17 +121,23 @@ export class ThemeChecksums {
114
121
  checksumStream.end();
115
122
  checksumStream
116
123
  .on('finish', async () => {
124
+ this.#loggerApi?.debug('Finished writing current checksums file. Creating verification file.');
117
125
  const currentChecksumVerifyFilePath = this.#currentChecksumsVerificationFilePath;
118
126
  await this._createThemeChecksumVerifyFile(currentChecksumFilePath, currentChecksumVerifyFilePath);
127
+ this.#loggerApi?.debug('Current checksums and verification file updated.');
119
128
  resolve();
120
129
  })
121
130
  .on('error', async (err) => {
131
+ this.#loggerApi?.debug('Error writing current checksums file. Cleaning up.', {
132
+ details: { file: currentChecksumFilePath }
133
+ });
122
134
  await removeFile(currentChecksumFilePath, { force: true });
123
- reject(ThemeChecksumsErrorFactory.createThemeChecksumError(err.stack));
135
+ reject(ThemeChecksumsErrorFactory.createThemeChecksumError(err));
124
136
  });
125
137
  });
126
138
  }
127
139
  async computeThemeChecksums() {
140
+ this.#loggerApi?.debug('Computing theme checksums.');
128
141
  const filesToIgnoreInChecksums = [
129
142
  join(SHOPER_THEME_METADATA_DIR, THEME_CURRENT_CHECKSUMS_FILE_NAME),
130
143
  join(SHOPER_THEME_METADATA_DIR, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME),
@@ -134,9 +147,12 @@ export class ThemeChecksums {
134
147
  const filesToComputeChecksums = (await ThemePushUtils.getAllFilesThatAreSendToRemote(this.#themeDir))
135
148
  .filter((path) => !filesToIgnoreInChecksums.some((ignorePath) => normalize(path) === ignorePath))
136
149
  .map((relativePath) => join(this.#themeDir, relativePath));
150
+ this.#loggerApi?.debug('Computing checksums from file structure.', { details: { count: filesToComputeChecksums.length } });
137
151
  const { filesChecksumsInDirectories, filesChecksums } = await computeChecksumsFromFilesStructure(filesToComputeChecksums, this.#themeDir);
138
152
  const directoriesChecksums = computeDirectoriesChecksums(filesChecksumsInDirectories);
139
- return { ...filesChecksums, ...directoriesChecksums };
153
+ const allChecksums = { ...filesChecksums, ...directoriesChecksums };
154
+ this.#loggerApi?.debug('Finished computing theme checksums.', { details: { count: Object.keys(allChecksums).length } });
155
+ return allChecksums;
140
156
  }
141
157
  async _getInitialChecksumsVerification() {
142
158
  return await readJSONFile(this.#initialChecksumsVerificationFilePath);
@@ -157,12 +173,16 @@ export class ThemeChecksums {
157
173
  return mapKeysPathsToWindowPlatform(checksums);
158
174
  }
159
175
  async _createThemeChecksumVerifyFile(checksumFilePath, checksumVerifyFullPath) {
176
+ this.#loggerApi?.debug('Creating checksum verification file.', { details: { path: checksumVerifyFullPath } });
160
177
  const checksumVerifyStream = createWriteStream(checksumVerifyFullPath);
161
178
  const checksumVerify = await computeFileChecksum(checksumFilePath);
162
179
  checksumVerifyStream
163
180
  .on('error', async (err) => {
181
+ this.#loggerApi?.debug('Error creating checksum verification file. Cleaning up.', {
182
+ details: { file: checksumFilePath }
183
+ });
164
184
  await removeFile(checksumFilePath, { force: true });
165
- throw ThemeChecksumsErrorFactory.createThemeChecksumError(err.stack);
185
+ throw ThemeChecksumsErrorFactory.createThemeChecksumError(err);
166
186
  })
167
187
  .write(JSON.stringify(checksumVerify));
168
188
  checksumVerifyStream.end();
@@ -1,10 +1,10 @@
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 ThemeChecksumsErrorFactory {
3
- static createThemeChecksumError(stack) {
3
+ static createThemeChecksumError(error) {
4
4
  return new AppError({
5
5
  message: 'Theme checksum error',
6
6
  code: 'theme_checksum_error',
7
- stack
7
+ error
8
8
  });
9
9
  }
10
10
  }
@@ -12,16 +12,23 @@ import { move } from 'fs-extra';
12
12
  export class FetchResources {
13
13
  #urlsAttempts = {};
14
14
  #httpApi;
15
- constructor(httpApi) {
15
+ #loggerApi;
16
+ constructor({ httpApi, loggerApi }) {
16
17
  this.#httpApi = httpApi;
18
+ this.#loggerApi = loggerApi;
17
19
  }
18
20
  async fetchResources({ dist, resourcesPart, parts, shopUrl }) {
21
+ this.#loggerApi.debug('Traversing resources part.', { details: { parts } });
19
22
  if (Array.isArray(resourcesPart)) {
20
23
  for (let i = 0; i < resourcesPart.length; i++) {
21
24
  const resource = resourcesPart[i];
22
25
  if (isResourceObject(resource)) {
26
+ const resourceUrl = isRelativeUrl(resource.url) ? `${shopUrl}${resource.url}` : resource.url;
27
+ this.#loggerApi.debug('Found resource object. Fetching resource.', {
28
+ details: { url: resourceUrl, parts: [...parts, i] }
29
+ });
23
30
  await this._fetchResource({
24
- url: isRelativeUrl(resource.url) ? `${shopUrl}${resource.url}` : resource.url,
31
+ url: resourceUrl,
25
32
  dist,
26
33
  parts: [...parts, i],
27
34
  shopUrl,
@@ -40,8 +47,12 @@ export class FetchResources {
40
47
  }
41
48
  if (typeof resourcesPart === 'object') {
42
49
  if (isResourceObject(resourcesPart)) {
50
+ const resourceUrl = isRelativeUrl(resourcesPart.url) ? `${shopUrl}${resourcesPart.url}` : resourcesPart.url;
51
+ this.#loggerApi.debug('Found resource object. Fetching resource.', {
52
+ details: { url: resourceUrl, parts }
53
+ });
43
54
  await this._fetchResource({
44
- url: isRelativeUrl(resourcesPart.url) ? `${shopUrl}${resourcesPart.url}` : resourcesPart.url,
55
+ url: resourceUrl,
45
56
  dist,
46
57
  parts,
47
58
  shopUrl,
@@ -61,6 +72,7 @@ export class FetchResources {
61
72
  return;
62
73
  }
63
74
  async _getResourcesFileContent(resourcePath) {
75
+ this.#loggerApi.debug('Reading and mapping resources file content.', { details: { resourcePath } });
64
76
  return mapResourcesToTree(await readJSONFile(resourcePath));
65
77
  }
66
78
  _getResourcesPart(resources, parts) {
@@ -68,28 +80,42 @@ export class FetchResources {
68
80
  }
69
81
  async _fetchResource({ url, shopUrl, parts, dist, isPrivate }) {
70
82
  this.#urlsAttempts[url] = (this.#urlsAttempts[url] ?? 0) + 1;
83
+ this.#loggerApi.debug('Fetching resource.', {
84
+ details: { url, attempt: this.#urlsAttempts[url], parts, isPrivate }
85
+ });
71
86
  if (this.#urlsAttempts[url] > MAX_REQUEST_FOR_RESOURCES)
72
87
  throw FetchResourcesErrorsFactory.createErrorFetchingResource(shopUrl, [
73
88
  `Failed to fetch ${url} after 3 attempts, for: ${parts.join('')}`
74
89
  ]);
75
90
  const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true, prefix: 'theme_resources_tmp' });
91
+ this.#loggerApi.debug('Created temporary directory for resource fetch.', { details: { tmpDir, url } });
76
92
  try {
93
+ this.#loggerApi.debug('Downloading resource file.', { details: { url, dist: tmpDir } });
77
94
  const { filename, ext, basename } = await downloadFile({
78
95
  dist: tmpDir,
96
+ logger: this.#loggerApi,
79
97
  request: this._getRequest({ url, isPrivate }).response
80
98
  });
99
+ this.#loggerApi.debug('Resource file downloaded.', { details: { url, filename } });
81
100
  if (ext === '.zip') {
101
+ this.#loggerApi.debug('Resource is a zip archive. Extracting.', { details: { source: filename, dist: basename } });
82
102
  await extractZip({
103
+ logger: this.#loggerApi,
83
104
  source: join(tmpDir, filename),
84
105
  dist: join(tmpDir, basename)
85
106
  });
107
+ this.#loggerApi.debug('Zip archive extracted.', { details: { filename } });
86
108
  if (!(await fileExists(getResourcesPath(join(tmpDir, basename)))))
87
109
  return;
110
+ this.#loggerApi.debug('Copying extracted files.', {
111
+ details: { from: join(tmpDir, basename), to: dist }
112
+ });
88
113
  await copyFile(join(tmpDir, basename), dist, {
89
114
  recursive: true,
90
115
  force: true
91
116
  });
92
117
  const newResources = await this._getResourcesFileContent(getResourcesPath(join(tmpDir, basename)));
118
+ this.#loggerApi.debug('Recursively fetching resources from extracted archive.', { details: { parts } });
93
119
  await this.fetchResources({
94
120
  dist,
95
121
  resourcesPart: this._getResourcesPart(newResources, parts),
@@ -98,7 +124,11 @@ export class FetchResources {
98
124
  });
99
125
  }
100
126
  else {
101
- await move(join(tmpDir, filename), join(dist, this._getFilepathFromParts(parts), filename), { overwrite: true });
127
+ const destinationPath = join(dist, this._getFilepathFromParts(parts), filename);
128
+ this.#loggerApi.debug('Moving downloaded file.', {
129
+ details: { from: join(tmpDir, filename), to: destinationPath }
130
+ });
131
+ await move(join(tmpDir, filename), destinationPath, { overwrite: true });
102
132
  }
103
133
  }
104
134
  catch (err) {
@@ -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
  export class FetchResourcesErrorsFactory {
3
3
  static createErrorFetchingResource(shopUrl, messages) {
4
4
  return new AppError({
@@ -16,6 +16,7 @@ import { ThemeDeletedSuccessfully } from './ui/theme_deleted_successfully.js';
16
16
  import { ThemeDeletionWarning } from './ui/theme_deletion_warning.js';
17
17
  import { promptConfirmation } from '../../../ui/prompts/prompt_confirmation.js';
18
18
  import { ThemeError } from '../ui/theme_error.js';
19
+ import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
19
20
  export class ThemeDeleteCommand extends BaseThemeCommand {
20
21
  static summary = 'Permanently deletes the specified theme from your store.';
21
22
  static description = 'This action cannot be undone, so make sure you really want to remove this theme.\n\nYou can run this command from a specific theme directory (ID not needed) or outside any theme directory (theme ID required).';
@@ -37,6 +38,7 @@ export class ThemeDeleteCommand extends BaseThemeCommand {
37
38
  const themeId = this.args.id;
38
39
  const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
39
40
  const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
41
+ const loggerApi = this.getApi(LOGGER_API_NAME);
40
42
  const credentials = cliAuthApi.getCredentials();
41
43
  if (!credentials) {
42
44
  renderOnce(React.createElement(MissingCredentialsError, null));
@@ -86,11 +88,10 @@ export class ThemeDeleteCommand extends BaseThemeCommand {
86
88
  if (!resp?.isSuccess)
87
89
  return;
88
90
  renderOnce(React.createElement(ThemeDeletedSuccessfully, null));
89
- return;
90
91
  }
91
92
  catch (err) {
93
+ loggerApi.error('Error executing theme delete command', { error: err });
92
94
  renderOnce(React.createElement(ThemeError, { err: err, executionContext: executionContext }));
93
- return;
94
95
  }
95
96
  }
96
97
  }
@@ -15,6 +15,7 @@ import { ThemeInfoCommandUtils } from './theme_info_command_utils.js';
15
15
  import { InvalidThemeIdError } from '../ui/invalid_theme_id.js';
16
16
  import { Box } from '../../../ui/box.js';
17
17
  import { ThemeError } from '../ui/theme_error.js';
18
+ import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
18
19
  export class ThemeInfoCommand extends BaseThemeCommand {
19
20
  static summary = 'View details about the current theme.';
20
21
  static description = 'Displays key information about the theme you’re working on — including its ID, name, description (if provided), and status.\n\nYou can run this command from a specific theme directory (ID not needed) or outside any theme directory (theme ID required).';
@@ -36,6 +37,7 @@ export class ThemeInfoCommand extends BaseThemeCommand {
36
37
  const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
37
38
  const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
38
39
  const themesListApi = this.getApi(THEMES_LIST_API_NAME);
40
+ const loggerApi = this.getApi(LOGGER_API_NAME);
39
41
  const credentials = cliAuthApi.getCredentials();
40
42
  if (!credentials) {
41
43
  renderOnce(React.createElement(MissingCredentialsError, null));
@@ -70,6 +72,7 @@ export class ThemeInfoCommand extends BaseThemeCommand {
70
72
  this._displayThemeInfo(theme);
71
73
  }
72
74
  catch (err) {
75
+ loggerApi.error('An error occurred while fetching theme info.', { error: err });
73
76
  renderOnce(React.createElement(ThemeError, { err: err, executionContext: executionContext }));
74
77
  }
75
78
  }