@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.
- package/build/cli/ui/validation_errors/validation_error_content.js +19 -0
- package/build/cli/ui/validation_errors/validation_errors.js +21 -0
- package/build/cli/ui/validation_errors/validation_errors_utils.js +17 -0
- package/build/index.js +0 -4
- package/build/theme/commands/delete/theme_delete_command.js +49 -37
- package/build/theme/commands/delete/ui/theme_deleted_successfully.js +15 -0
- package/build/theme/commands/delete/ui/theme_deletion_warning.js +10 -0
- package/build/theme/commands/info/theme_info_command.js +15 -18
- package/build/theme/commands/info/theme_info_command_utils.js +1 -1
- package/build/theme/commands/init/theme_init_command.js +4 -3
- package/build/theme/commands/pull/theme_pull_command.js +1 -2
- package/build/theme/commands/push/theme_push_command.js +8 -4
- package/build/theme/features/theme/actions/api/theme_actions_api.js +3 -0
- package/build/theme/features/theme/actions/service/theme_actions_service.js +11 -0
- package/build/theme/features/theme/delete/service/theme_delete_service.js +2 -8
- package/build/theme/features/theme/merge/api/theme_merge_api.js +4 -4
- package/build/theme/features/theme/merge/service/theme_merge_service.js +8 -9
- package/build/theme/features/theme/push/service/theme_push_service.js +20 -7
- package/build/theme/features/theme/push/theme_push_constants.js +1 -0
- package/build/theme/features/theme/push/theme_push_errors_factory.js +5 -3
- package/build/theme/features/theme/utils/checksums/theme_checksums_utils.js +9 -1
- package/build/ui/list/list.js +4 -1
- 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
|
@@ -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
|
-
|
|
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: "
|
|
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 (
|
|
60
|
-
|
|
61
|
-
|
|
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;
|
|
@@ -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(
|
|
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
|
|
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
|
-
|
|
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(
|
|
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));
|
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
20
|
-
return this.#service.hasFileBeenCreated(path,
|
|
19
|
+
hasFileBeenCreated(path, themeDir) {
|
|
20
|
+
return this.#service.hasFileBeenCreated(path, themeDir);
|
|
21
21
|
}
|
|
22
|
-
hasFileBeenModified(path,
|
|
23
|
-
return this.#service.hasFileBeenModified(path,
|
|
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,
|
|
50
|
-
const
|
|
51
|
-
return
|
|
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,
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
return !!
|
|
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
|
|
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:
|
|
6
|
+
code: THEME_FILES_UPLOAD_ERROR,
|
|
7
7
|
message: `Error while uploading theme files to shop "${shopUrl}"`,
|
|
8
8
|
level: 'error',
|
|
9
|
-
details:
|
|
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
|
}
|
package/build/ui/list/list.js
CHANGED
|
@@ -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((
|
|
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
|
};
|