@shoper/cli 0.1.0-25 → 0.1.0-27

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 (23) hide show
  1. package/build/cli/ui/validation_errors/validation_error_content.js +19 -0
  2. package/build/cli/ui/validation_errors/validation_errors.js +21 -0
  3. package/build/cli/ui/validation_errors/validation_errors_utils.js +17 -0
  4. package/build/index.js +0 -4
  5. package/build/theme/commands/delete/theme_delete_command.js +49 -37
  6. package/build/theme/commands/delete/ui/theme_deleted_successfully.js +15 -0
  7. package/build/theme/commands/delete/ui/theme_deletion_warning.js +10 -0
  8. package/build/theme/commands/info/theme_info_command.js +15 -18
  9. package/build/theme/commands/info/theme_info_command_utils.js +1 -1
  10. package/build/theme/commands/init/theme_init_command.js +4 -3
  11. package/build/theme/commands/pull/theme_pull_command.js +1 -2
  12. package/build/theme/commands/push/theme_push_command.js +8 -4
  13. package/build/theme/features/theme/actions/api/theme_actions_api.js +3 -0
  14. package/build/theme/features/theme/actions/service/theme_actions_service.js +11 -0
  15. package/build/theme/features/theme/delete/service/theme_delete_service.js +2 -8
  16. package/build/theme/features/theme/merge/api/theme_merge_api.js +4 -4
  17. package/build/theme/features/theme/merge/service/theme_merge_service.js +8 -9
  18. package/build/theme/features/theme/push/service/theme_push_service.js +20 -7
  19. package/build/theme/features/theme/push/theme_push_constants.js +1 -0
  20. package/build/theme/features/theme/push/theme_push_errors_factory.js +5 -3
  21. package/build/theme/features/theme/utils/checksums/theme_checksums_utils.js +9 -1
  22. package/build/ui/list/list.js +4 -1
  23. package/package.json +1 -1
@@ -0,0 +1,19 @@
1
+ import { Text } from '../../../ui/text.js';
2
+ import { List } from '../../../ui/list/list.js';
3
+ import { ValidationErrorsUtils } from './validation_errors_utils.js';
4
+ import React from 'react';
5
+ export const ValidationErrorContent = ({ errors }) => {
6
+ if (typeof errors === 'string') {
7
+ return React.createElement(Text, null, errors);
8
+ }
9
+ if (Array.isArray(errors)) {
10
+ return (React.createElement(List, { items: errors.map((error) => ({
11
+ content: error
12
+ })) }));
13
+ }
14
+ return (React.createElement(List, { items: Object.entries(errors).map(([title, errorMessages]) => {
15
+ return {
16
+ content: `${title} - ${ValidationErrorsUtils.getErrorContent(errorMessages)}`
17
+ };
18
+ }) }));
19
+ };
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import { Error } from '../../../ui/message_box/error.js';
3
+ import { Text } from '../../../ui/text.js';
4
+ import { List } from '../../../ui/list/list.js';
5
+ import { ValidationErrorContent } from './validation_error_content.js';
6
+ export const ValidationErrors = ({ errors }) => {
7
+ if (typeof errors === 'string') {
8
+ return (React.createElement(Error, null,
9
+ React.createElement(Text, null, errors)));
10
+ }
11
+ if (Array.isArray(errors)) {
12
+ return (React.createElement(Error, null,
13
+ React.createElement(List, { items: errors.map((error) => ({
14
+ content: error
15
+ })) })));
16
+ }
17
+ return (React.createElement(React.Fragment, null, Object.entries(errors).map(([title, errorMessages], index) => {
18
+ return (React.createElement(Error, { key: index, header: title },
19
+ React.createElement(ValidationErrorContent, { errors: errorMessages })));
20
+ })));
21
+ };
@@ -0,0 +1,17 @@
1
+ export class ValidationErrorsUtils {
2
+ static getErrorContent(errors) {
3
+ if (typeof errors === 'string') {
4
+ return errors;
5
+ }
6
+ if (Array.isArray(errors)) {
7
+ return errors.join(', ');
8
+ }
9
+ if (errors?.errors && Array.isArray(errors?.errors)) {
10
+ return errors.errors.join(', ');
11
+ }
12
+ if (typeof errors === 'object') {
13
+ return Object.values(errors).join(', ');
14
+ }
15
+ throw new Error('Not supported validation errors', errors);
16
+ }
17
+ }
package/build/index.js CHANGED
@@ -47,7 +47,3 @@ export const COMMANDS = {
47
47
  ...THEME_COMMANDS
48
48
  };
49
49
  export { runCLI } from './cli/index.js';
50
- /**
51
- * TODO:
52
- * Windows - zwłąszcza w kejsie z git grzie push i pull jest na dwóch platformach
53
- */
@@ -2,7 +2,7 @@ import { Args } from '@oclif/core';
2
2
  import { BaseThemeCommand } from '../../class/base_theme_command.js';
3
3
  import { CLI_AUTH_API_NAME } from '../../../cli/auth/cli_auth_constants.js';
4
4
  import { THEME_ACTION_NOT_FOUND_ERROR_CODE, THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../../features/theme/actions/theme_actions_constants.js';
5
- import { EXECUTION_CONTEXT_API_NAME } from '../../../cli/features/execution_context/execution_context_constants.js';
5
+ import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
6
6
  import { renderOnce } from '../../../ui/ui_utils.js';
7
7
  import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_credentials_error.js';
8
8
  import React from 'react';
@@ -10,9 +10,12 @@ import { THEME_DELETE_API_NAME } from '../../features/theme/delete/theme_delete_
10
10
  import { UnpermittedCommandError } from '../ui/unpermitted_command_error.js';
11
11
  import { Error } from '../../../ui/message_box/error.js';
12
12
  import { Text } from '../../../ui/text.js';
13
- import { promptInput } from '../../../ui/prompts/prompt_input.js';
14
- import { Warning } from '../../../ui/message_box/warning.js';
15
13
  import { Info } from '../../../ui/message_box/info.js';
14
+ import { MissingThemeIdError } from '../ui/missing_theme_id_error.js';
15
+ import { Box } from '../../../ui/box.js';
16
+ import { ThemeDeletedSuccessfully } from './ui/theme_deleted_successfully.js';
17
+ import { ThemeDeletionWarning } from './ui/theme_deletion_warning.js';
18
+ import { promptConfirmation } from '../../../ui/prompts/prompt_confirmation.js';
16
19
  export class ThemeDeleteCommand extends BaseThemeCommand {
17
20
  static summary = 'Permanently deletes the specified theme from your store.';
18
21
  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).';
@@ -42,43 +45,51 @@ export class ThemeDeleteCommand extends BaseThemeCommand {
42
45
  renderOnce(React.createElement(MissingCredentialsError, null));
43
46
  return;
44
47
  }
45
- //
46
- // if (!themeId) {
47
- // renderOnce(<MissingThemeIdError />);
48
- //
49
- // return;
50
- // }
51
- // const executionContext = await executionContextApi.getExecutionContext();
52
- //
53
- // if (executionContext.type === EXECUTION_CONTEXTS.theme) {
54
- // renderOnce(<ThemeDirectoryContextError />);
55
- //
56
- // return;
57
- // }
58
- renderOnce(React.createElement(Warning, { header: `Warning: This will permanently delete the theme {Theme_name} (ID: {theme_ID}) and cannot be undone.` }));
59
- const { input: procced } = await promptInput('Proceed?');
60
- if (!procced) {
61
- renderOnce(React.createElement(Info, { header: "Theme deletion was cancelled." }));
62
- return;
63
- }
64
- if (!themeId)
65
- return;
66
- const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
67
- const deleteAction = themeActionsApi.getThemeAction({
68
- actionType: THEME_ACTIONS_TYPES.delete,
69
- themeId,
70
- credentials
71
- });
72
- if (!deleteAction) {
73
- renderOnce(React.createElement(UnpermittedCommandError, { themeId: themeId, commandName: "delete" }));
74
- return;
75
- }
76
- const themeDeleteApi = this.getApi(THEME_DELETE_API_NAME);
48
+ const executionContext = await executionContextApi.getExecutionContext();
77
49
  try {
78
- await themeDeleteApi.deleteTheme({
50
+ let _themeId = themeId;
51
+ if (executionContext.type !== EXECUTION_CONTEXTS.theme && !_themeId) {
52
+ renderOnce(React.createElement(MissingThemeIdError, null,
53
+ React.createElement(Box, { flexDirection: "column", gap: 1 },
54
+ React.createElement(Text, null, "Usage: shoper theme delete [ID]"),
55
+ React.createElement(Text, null, "Please run this command inside a theme directory or provide a theme ID."))));
56
+ return;
57
+ }
58
+ if (executionContext.type === EXECUTION_CONTEXTS.theme)
59
+ _themeId = _themeId ?? executionContext.themeId;
60
+ if (!_themeId) {
61
+ renderOnce(React.createElement(MissingThemeIdError, null));
62
+ return;
63
+ }
64
+ const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
65
+ const deleteAction = themeActionsApi.getThemeAction({
66
+ actionType: THEME_ACTIONS_TYPES.delete,
67
+ themeId: _themeId,
68
+ credentials
69
+ });
70
+ if (!deleteAction) {
71
+ renderOnce(React.createElement(UnpermittedCommandError, { themeId: _themeId, commandName: "delete" }));
72
+ return;
73
+ }
74
+ renderOnce(React.createElement(ThemeDeletionWarning, { themeId: _themeId }));
75
+ const { proceed } = await promptConfirmation('Proceed?');
76
+ if (!proceed) {
77
+ renderOnce(React.createElement(Info, { header: "Theme deletion was cancelled." }));
78
+ return;
79
+ }
80
+ const themeDeleteApi = this.getApi(THEME_DELETE_API_NAME);
81
+ const { isSuccess } = await themeDeleteApi.deleteTheme({
79
82
  actionData: deleteAction.data,
80
83
  credentials
81
84
  });
85
+ themeActionsApi.removeThemeActions({
86
+ themeId: _themeId,
87
+ credentials
88
+ });
89
+ if (!isSuccess)
90
+ return;
91
+ renderOnce(React.createElement(ThemeDeletedSuccessfully, null));
92
+ return;
82
93
  }
83
94
  catch (err) {
84
95
  this._handleError(err, themeId);
@@ -87,7 +98,8 @@ export class ThemeDeleteCommand extends BaseThemeCommand {
87
98
  }
88
99
  _handleError(err, themeId) {
89
100
  if (err?.code === THEME_ACTION_NOT_FOUND_ERROR_CODE && themeId) {
90
- renderOnce(React.createElement(UnpermittedCommandError, { themeId: themeId, commandName: "init" }));
101
+ renderOnce(React.createElement(UnpermittedCommandError, { themeId: themeId, commandName: "delete" }));
102
+ return;
91
103
  }
92
104
  if (err?.message) {
93
105
  renderOnce(React.createElement(Error, null,
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import { Text } from '../../../../ui/text.js';
3
+ import { List } from '../../../../ui/list/list.js';
4
+ import { Tip } from '../../../../ui/tip.js';
5
+ import { Success } from '../../../../ui/message_box/success.js';
6
+ export const ThemeDeletedSuccessfully = () => {
7
+ return (React.createElement(Success, { header: "Success: Theme was deleted from the store." },
8
+ React.createElement(Text, null, "The theme is no longer available in your store panel, but its files remain locally."),
9
+ React.createElement(Tip, null, "If you want to reuse this theme:"),
10
+ React.createElement(List, { items: [
11
+ 'create a new theme using shoper theme init [id]',
12
+ 'copy the local files into the new theme folder',
13
+ 'and push it using shoper theme push'
14
+ ] })));
15
+ };
@@ -0,0 +1,10 @@
1
+ import { Warning } from '../../../../ui/message_box/warning.js';
2
+ import { Text } from '../../../../ui/text.js';
3
+ import React from 'react';
4
+ export const ThemeDeletionWarning = ({ themeId }) => {
5
+ return (React.createElement(Warning, { header: `Warning: This will permanently delete the theme (ID: ${themeId}) from your store and cannot be undone.` },
6
+ React.createElement(Text, null,
7
+ "Your local files ",
8
+ React.createElement(Text, { bold: true }, "will not be removed"),
9
+ ". You\u2019ll still be able to access and modify them.")));
10
+ };
@@ -45,30 +45,27 @@ export class ThemeInfoCommand extends BaseThemeCommand {
45
45
  return;
46
46
  }
47
47
  const executionContext = await executionContextApi.getExecutionContext();
48
- let theme;
49
- let _themeId = themeId;
50
48
  try {
49
+ let _themeId = themeId;
50
+ if (executionContext.type !== EXECUTION_CONTEXTS.theme && !_themeId) {
51
+ renderOnce(React.createElement(MissingThemeIdError, null,
52
+ React.createElement(Box, { flexDirection: "column", gap: 1 },
53
+ React.createElement(Text, null, "Usage: shoper theme info [ID]"),
54
+ React.createElement(Text, null, "Please run this command inside a theme directory or provide a theme ID."))));
55
+ return;
56
+ }
51
57
  if (executionContext.type === EXECUTION_CONTEXTS.theme) {
52
58
  await ThemeMetaDataUtils.ensureThemeWorkUrlMatch(executionContext.themeRootDir, credentials.shopUrl);
53
59
  _themeId = themeId ?? executionContext.themeId;
54
- theme = await themesListApi.getTheme({
55
- themeId: _themeId,
56
- shopUrl: credentials.shopUrl
57
- });
58
60
  }
59
- if (executionContext.type !== EXECUTION_CONTEXTS.theme) {
60
- if (_themeId === undefined) {
61
- renderOnce(React.createElement(MissingThemeIdError, null,
62
- React.createElement(Box, { flexDirection: "column", gap: 1 },
63
- React.createElement(Text, null, "Usage: shoper theme info [ID]"),
64
- React.createElement(Text, null, "Please run this command inside a theme directory or provide a theme ID."))));
65
- return;
66
- }
67
- theme = await themesListApi.getTheme({
68
- themeId: _themeId,
69
- shopUrl: credentials.shopUrl
70
- });
61
+ if (!_themeId) {
62
+ renderOnce(React.createElement(MissingThemeIdError, null));
63
+ return;
71
64
  }
65
+ const theme = await themesListApi.getTheme({
66
+ themeId: _themeId,
67
+ shopUrl: credentials.shopUrl
68
+ });
72
69
  if (!theme) {
73
70
  renderOnce(_themeId ? React.createElement(InvalidThemeIdError, { themeId: _themeId }) : React.createElement(Error, { header: "Error: Theme information is not available." }));
74
71
  return;
@@ -30,7 +30,7 @@ export class ThemeInfoCommandUtils {
30
30
  [
31
31
  React.createElement(Text, null,
32
32
  React.createElement(Text, { bold: true }, "Status: "),
33
- ": $",
33
+ ": ",
34
34
  theme.isActive ? 'Active' : 'Inactive')
35
35
  ]
36
36
  ]
@@ -12,9 +12,9 @@ import { UnpermittedCommandError } from '../ui/unpermitted_command_error.js';
12
12
  import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_credentials_error.js';
13
13
  import { ThemeCreatedSuccess } from './ui/theme_created_success.js';
14
14
  import { Text } from '../../../ui/text.js';
15
- import { Error } from '../../../ui/message_box/error.js';
16
15
  import { MissingThemeIdError } from '../ui/missing_theme_id_error.js';
17
16
  import { Box } from '../../../ui/box.js';
17
+ import { ValidationErrors } from '../../../cli/ui/validation_errors/validation_errors.js';
18
18
  export class ThemeInitCommand extends BaseThemeCommand {
19
19
  static summary = 'Creates a copy of an existing theme by duplicating it.';
20
20
  static description = 'The new theme will be named after the source theme with a "Copy" suffix and an incremental number.\n\nYou must run this command from the root directory (not inside any theme folder) and provide the ID of the theme you want to duplicate.';
@@ -73,6 +73,8 @@ export class ThemeInitCommand extends BaseThemeCommand {
73
73
  catch (err) {
74
74
  spinner?.stop();
75
75
  this._handleError(err, themeId);
76
+ //TODO analyze why on nodejs v22 this command does not exit properly, even all promises are resolved
77
+ this.exit();
76
78
  }
77
79
  }
78
80
  _handleError(err, themeId) {
@@ -81,8 +83,7 @@ export class ThemeInitCommand extends BaseThemeCommand {
81
83
  return;
82
84
  }
83
85
  if (err?.message) {
84
- renderOnce(React.createElement(Error, null,
85
- React.createElement(Text, null, err.message)));
86
+ renderOnce(React.createElement(ValidationErrors, { errors: err.message }));
86
87
  return;
87
88
  }
88
89
  this.error(String(err));
@@ -94,7 +94,6 @@ export class ThemePullCommand extends BaseThemeCommand {
94
94
  }
95
95
  catch (err) {
96
96
  this.#spinner?.stop();
97
- console.log('err', err);
98
97
  this._handleError(err, _themeId);
99
98
  }
100
99
  }
@@ -145,7 +144,7 @@ export class ThemePullCommand extends BaseThemeCommand {
145
144
  renderOnce(React.createElement(ThemePulledSuccess, null));
146
145
  }
147
146
  async _pullThemeIntoExistingOne({ themeId, themeFetchApi, themeActionsApi, fetchType, executionContext, credentials }) {
148
- //TODO move these logis somvewhere else?
147
+ //TODO move these logi somvewhere else?
149
148
  await ThemeMetaDataUtils.ensureThemeWorkUrlMatch(executionContext.themeRootDir, credentials.shopUrl);
150
149
  const pullAction = themeActionsApi.getThemeAction({
151
150
  actionType: THEME_ACTIONS_TYPES.pull,
@@ -2,7 +2,7 @@ import { BaseThemeCommand } from '../../class/base_theme_command.js';
2
2
  import { CLI_AUTH_API_NAME } from '../../../cli/auth/cli_auth_constants.js';
3
3
  import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
4
4
  import { THEME_ACTION_NOT_FOUND_ERROR_CODE, THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../../features/theme/actions/theme_actions_constants.js';
5
- import { THEME_ARCHIVE_UPLOAD_ERROR, THEME_PUSH_API_NAME } from '../../features/theme/push/theme_push_constants.js';
5
+ import { THEME_ARCHIVE_UPLOAD_ERROR, THEME_FILES_UPLOAD_ERROR, THEME_PUSH_API_NAME } from '../../features/theme/push/theme_push_constants.js';
6
6
  import { ThemeMetaDataUtils } from '../../features/theme/utils/meta_data/theme_meta_data_utils.js';
7
7
  import { mapChecksumToTree } from '../../../utils/checksums/checksums_utils.js';
8
8
  import { mapToPermissionsTree } from '../../utils/directory_validator/directory_validator_utils.js';
@@ -26,6 +26,7 @@ import { ThemeWorkUrlMismatch } from '../ui/theme_work_url_mismatch.js';
26
26
  import { THEME_WORK_URL_MISMATCH_ERROR } from '../../features/theme/utils/meta_data/theme_meta_data_constants.js';
27
27
  import { ThemeFilesStructureUtils } from '../../features/theme/utils/files_structure/theme_files_structure_utils.js';
28
28
  import { ThemeInfoUtils } from '../../features/theme/info/theme_info_utils.js';
29
+ import { ValidationErrors } from '../../../cli/ui/validation_errors/validation_errors.js';
29
30
  export class ThemePushCommand extends BaseThemeCommand {
30
31
  static summary = 'Uploads your local theme files to the store and overwrites the current version of the theme in your store.';
31
32
  static description = 'Check your local changes before pushing.\n\nYou must run this command from a specific theme directory (ID not needed).';
@@ -110,16 +111,19 @@ export class ThemePushCommand extends BaseThemeCommand {
110
111
  return;
111
112
  }
112
113
  if (err?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
113
- // renderOnce(<ThemeValidationErrors errors={err?.details} />);
114
+ renderOnce(React.createElement(ValidationErrors, { errors: err?.details }));
114
115
  return;
115
116
  }
116
117
  if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
117
118
  this._renderUrlMismatchError();
118
119
  return;
119
120
  }
121
+ if (err?.code === THEME_FILES_UPLOAD_ERROR) {
122
+ return renderOnce(React.createElement(Error, { header: "Uploading theme files to the shop failed.\n" },
123
+ React.createElement(Text, null, "The rejected files have been removed locally. Please ensure that the files are not corrupted and that the file extensions are supported.")));
124
+ }
120
125
  if (err?.message) {
121
- renderOnce(React.createElement(Error, null,
122
- React.createElement(Text, null, err.message)));
126
+ renderOnce(React.createElement(ValidationErrors, { errors: err.message }));
123
127
  return;
124
128
  }
125
129
  this.error(String(err));
@@ -16,4 +16,7 @@ export class ThemeActionsApi extends FeatureApi {
16
16
  clearThemesActions(props) {
17
17
  return this.#service.clearThemesActions(props);
18
18
  }
19
+ removeThemeActions(props) {
20
+ return this.#service.removeThemeActions(props);
21
+ }
19
22
  }
@@ -25,6 +25,17 @@ export class ThemeActionsService {
25
25
  });
26
26
  return actions[actionType];
27
27
  }
28
+ removeThemeActions({ themeId, credentials }) {
29
+ const storeActions = this.#themesActionsStore.get(this._getKey(credentials.shopUrl));
30
+ if (!storeActions || !storeActions.actions[themeId]) {
31
+ throw new AppError({
32
+ code: THEME_ACTION_NOTS_FOUND_ERROR_CODE,
33
+ message: `Theme actions for store ${credentials.shopUrl} not found.`
34
+ });
35
+ }
36
+ delete storeActions.actions[themeId];
37
+ this.#themesActionsStore.set(this._getKey(credentials.shopUrl), storeActions);
38
+ }
28
39
  async ensureThemesActions({ credentials, themeId }) {
29
40
  if (!themeId)
30
41
  return;
@@ -1,4 +1,3 @@
1
- import { STATUS_CODES } from '@dreamcommerce/star_core';
2
1
  import { HttpErrorsFactory } from '../../../../../cli/class/errors/http/http_errors_factory.js';
3
2
  import { DownloadFileErrorsFactory } from '../../../../../utils/download_file/download_file_errors_factory.js';
4
3
  export class ThemeDeleteService {
@@ -9,10 +8,7 @@ export class ThemeDeleteService {
9
8
  async deleteTheme({ actionData, credentials }) {
10
9
  try {
11
10
  const { response } = this.#httpApi.deleteTheme({ actionData, shopUrl: credentials.shopUrl });
12
- const { data, status } = await response;
13
- if (status !== STATUS_CODES.ok)
14
- return;
15
- //TODO if is success remove theme directory
11
+ const { data } = await response;
16
12
  return data;
17
13
  }
18
14
  catch (err) {
@@ -25,9 +21,7 @@ export class ThemeDeleteService {
25
21
  case 404:
26
22
  throw HttpErrorsFactory.createNotFoundError();
27
23
  default:
28
- if (err.response?.status !== 200) {
29
- throw DownloadFileErrorsFactory.downloadError(err.response.status);
30
- }
24
+ throw DownloadFileErrorsFactory.downloadError(err.response.status);
31
25
  }
32
26
  }
33
27
  }
@@ -16,10 +16,10 @@ export class ThemeMergeApi extends FeatureApi {
16
16
  applyChanges(fromTheme, toTheme, changes) {
17
17
  return this.#service.applyChanges(fromTheme, toTheme, changes);
18
18
  }
19
- hasFileBeenCreated(path, executionContext) {
20
- return this.#service.hasFileBeenCreated(path, executionContext);
19
+ hasFileBeenCreated(path, themeDir) {
20
+ return this.#service.hasFileBeenCreated(path, themeDir);
21
21
  }
22
- hasFileBeenModified(path, executionContext) {
23
- return this.#service.hasFileBeenModified(path, executionContext);
22
+ hasFileBeenModified(path, themeDir) {
23
+ return this.#service.hasFileBeenModified(path, themeDir);
24
24
  }
25
25
  }
@@ -1,10 +1,9 @@
1
1
  import FSTree from 'fs-tree-diff';
2
- import { join } from '../../../../../utils/path_utils.js';
3
- import { computeFileChecksum } from '../../../../../utils/checksums/checksums_utils.js';
4
2
  import { copyFileSync, fileExists, getAllDirectoriesNamesInside } from '../../../../../utils/fs/fs_utils.js';
5
3
  import { ThemeChecksumsUtils } from '../../utils/checksums/theme_checksums_utils.js';
6
4
  import walkSync from 'walk-sync';
7
5
  import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
6
+ import { join } from '../../../../../utils/path_utils.js';
8
7
  export class ThemeMergeService {
9
8
  async applyChanges(fromTheme, toTheme, changes) {
10
9
  FSTree.applyPatch(fromTheme, toTheme, changes, {
@@ -46,13 +45,13 @@ export class ThemeMergeService {
46
45
  const currentChecksums = await ThemeChecksumsUtils.getThemeCurrentChecksumsVerification(themeRootDir);
47
46
  return initialChecksums !== currentChecksums;
48
47
  }
49
- async hasFileBeenCreated(path, executionContext) {
50
- const checksums = await ThemeChecksumsUtils.getThemeInitialChecksums(executionContext.themeRootDir);
51
- return checksums[path] === undefined && (await fileExists(join(executionContext.themeRootDir, path)));
48
+ async hasFileBeenCreated(path, themeDir) {
49
+ const initialChecksum = await ThemeChecksumsUtils.getThemeInitialChecksums(themeDir);
50
+ return !initialChecksum && (await fileExists(join(themeDir, path)));
52
51
  }
53
- async hasFileBeenModified(path, executionContext) {
54
- const checksums = await ThemeChecksumsUtils.getThemeInitialChecksums(executionContext.themeRootDir);
55
- const currentChecksum = await computeFileChecksum(join(executionContext.themeRootDir, path));
56
- return !!checksums[path] && checksums[path] !== currentChecksum;
52
+ async hasFileBeenModified(path, themeDir) {
53
+ const currentChecksum = await ThemeChecksumsUtils.getCurrentChecksumFromPath(path, themeDir);
54
+ const initialChecksum = await ThemeChecksumsUtils.getInitialChecksumFromPath(path, themeDir);
55
+ return !!currentChecksum && !!initialChecksum && currentChecksum !== initialChecksum;
57
56
  }
58
57
  }
@@ -27,7 +27,7 @@ export class ThemePushService {
27
27
  }
28
28
  async push({ pushAction, filesStructure, credentials, executionContext }) {
29
29
  const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
30
- if (await this.#themeMergeApi.hasFileBeenCreated(ThemePublishUtils.getSkinStoreSettingsFilePath(executionContext.themeRootDir), executionContext))
30
+ if (await this.#themeMergeApi.hasFileBeenCreated(ThemePublishUtils.getSkinStoreSettingsFilePath(executionContext.themeRootDir), executionContext.themeRootDir))
31
31
  throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
32
32
  const { uploadData, localFiles } = await this._getActionDataForFilesToUpload({
33
33
  pushAction,
@@ -105,18 +105,24 @@ export class ThemePushService {
105
105
  }
106
106
  async _uploadThemeFiles({ filesToUpload, themeRootDir, localFiles, credentials }) {
107
107
  try {
108
- const uploadedImageData = await this._uploadFiles(filesToUpload, credentials);
108
+ const { uploadedImageData, rejectedImageData } = await this._uploadFiles(filesToUpload, credentials);
109
109
  const newFilesList = ThemeImagesUtils.updateOriginalFilenameToUploadedFilename(localFiles, uploadedImageData);
110
110
  if (uploadedImageData.length)
111
111
  await ThemeImagesUtils.removeUploadedOriginalFiles(themeRootDir, uploadedImageData);
112
+ if (rejectedImageData.length)
113
+ await this._removeUploadedThemeFiles(rejectedImageData, themeRootDir);
112
114
  await this._createAFilesListFile(themeRootDir, newFilesList);
113
115
  }
114
116
  catch (err) {
115
117
  throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl, err);
116
118
  }
117
119
  }
120
+ async _removeUploadedThemeFiles(uploadedImageData, themeRootDir) {
121
+ await Promise.all(uploadedImageData.map(({ location, originalFilename }) => removeFile(join(themeRootDir, location, originalFilename))));
122
+ }
118
123
  async _uploadFiles(uploadData, credentials) {
119
124
  const uploadedImageData = [];
125
+ const rejectedImageData = [];
120
126
  await Promise.all(uploadData.map(({ actionData, path }) => this.#themePushHttpApi
121
127
  .pushThemeData({
122
128
  actionData,
@@ -124,15 +130,22 @@ export class ThemePushService {
124
130
  shopUrl: credentials.shopUrl
125
131
  })
126
132
  .response.then((response) => {
127
- if (response.status !== 200 || !response.data.isSuccess)
128
- throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl, response.data?.messages ?? []);
129
133
  uploadedImageData.push({
130
134
  location: dirname(path),
131
135
  originalFilename: basename(path),
132
136
  uploadedFilename: response.data.imageId
133
137
  });
138
+ })
139
+ .catch(() => {
140
+ rejectedImageData.push({
141
+ location: dirname(path),
142
+ originalFilename: basename(path)
143
+ });
134
144
  })));
135
- return uploadedImageData;
145
+ return {
146
+ uploadedImageData,
147
+ rejectedImageData
148
+ };
136
149
  }
137
150
  async _getActionDataForFilesToUpload({ pushAction, filesStructure, executionContext }) {
138
151
  const uploadData = [];
@@ -156,8 +169,8 @@ export class ThemePushService {
156
169
  localFiles[fileGlob] = files.length ? basename(files[0]) : null;
157
170
  }
158
171
  for (const filePath of files) {
159
- if ((await this.#themeMergeApi.hasFileBeenCreated(filePath, executionContext)) ||
160
- (await this.#themeMergeApi.hasFileBeenModified(filePath, executionContext))) {
172
+ if ((await this.#themeMergeApi.hasFileBeenCreated(filePath, executionContext.themeRootDir)) ||
173
+ (await this.#themeMergeApi.hasFileBeenModified(filePath, executionContext.themeRootDir))) {
161
174
  uploadData.push({
162
175
  actionData,
163
176
  actionKey,
@@ -3,3 +3,4 @@ export const THEME_PUSH_API_NAME = 'ThemePushApi';
3
3
  export const THEME_FILES_LIST_FILE_NAME = 'filesList.json';
4
4
  export const THEME_MODULE_SETTINGS_FILE_NAME = 'settings.json';
5
5
  export const THEME_ARCHIVE_UPLOAD_ERROR = 'theme.push.error_uploading_theme_archive';
6
+ export const THEME_FILES_UPLOAD_ERROR = 'theme.push.error_uploading_theme_files';
@@ -1,12 +1,14 @@
1
1
  import { AppError } from '../../../../cli/class/errors/app/app_error.js';
2
- import { THEME_ARCHIVE_UPLOAD_ERROR } from './theme_push_constants.js';
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) {
5
5
  return new AppError({
6
- code: 'theme.push.error_uploading_theme_files',
6
+ code: THEME_FILES_UPLOAD_ERROR,
7
7
  message: `Error while uploading theme files to shop "${shopUrl}"`,
8
8
  level: 'error',
9
- details: messages
9
+ details: {
10
+ messages
11
+ }
10
12
  });
11
13
  }
12
14
  static createErrorWhileCreatingThemeArchive(shopUrl, error) {
@@ -1,4 +1,4 @@
1
- import { join, mapKeysPathsToWindowPlatform } from '../../../../../utils/path_utils.js';
1
+ import { join, mapKeysPathsToWindowPlatform, toUnixPath, toWinowsPath } from '../../../../../utils/path_utils.js';
2
2
  import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME, THEME_INITIAL_CHECKSUMS_FILE_NAME, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME } from '../../theme_constants.js';
3
3
  import { createWriteStream } from 'fs';
4
4
  import { copyFile, readJSONFile, removeFile } from '../../../../../utils/fs/fs_utils.js';
@@ -126,4 +126,12 @@ export class ThemeChecksumsUtils {
126
126
  const directoriesChecksums = computeDirectoriesChecksums(filesChecksumsInDirectories);
127
127
  return { ...filesChecksums, ...directoriesChecksums };
128
128
  }
129
+ static async getCurrentChecksumFromPath(path, themeDir) {
130
+ const checksums = await this.getThemeCurrentChecksums(themeDir);
131
+ return checksums[isWindowsOs() ? toWinowsPath(path) : toUnixPath(path)];
132
+ }
133
+ static async getInitialChecksumFromPath(path, themeDir) {
134
+ const checksums = await this.getThemeInitialChecksums(themeDir);
135
+ return checksums[isWindowsOs() ? toWinowsPath(path) : toUnixPath(path)];
136
+ }
129
137
  }
@@ -3,5 +3,8 @@ import { Box } from '../box.js';
3
3
  import React from 'react';
4
4
  import { ListItem } from './list_item.js';
5
5
  export const List = ({ type = LIST_TYPES.unordered, items }) => {
6
- return (React.createElement(Box, { flexDirection: "column", gap: 1, width: "100%", marginLeft: 2 }, items.map(({ content }, index) => (React.createElement(ListItem, { key: index, index: index, type: type }, content)))));
6
+ return (React.createElement(Box, { flexDirection: "column", gap: 1, width: "100%", marginLeft: 2 }, items.map((item, index) => {
7
+ const itemContent = typeof item === 'string' ? item : item.content;
8
+ return (React.createElement(ListItem, { key: index, index: index, type: type }, itemContent));
9
+ })));
7
10
  };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@shoper/cli",
3
3
  "packageManager": "yarn@3.2.0",
4
4
  "sideEffects": false,
5
- "version": "0.1.0-25",
5
+ "version": "0.1.0-27",
6
6
  "description": "CLI tool for Shoper",
7
7
  "author": "Joanna Firek",
8
8
  "license": "MIT",