@shoper/cli 0.1.0-26 → 0.1.0-28
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 +2 -2
- package/build/theme/commands/pull/theme_pull_command.js +1 -2
- package/build/theme/commands/push/theme_push_command.js +3 -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 +28 -22
- 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
- package/build/cli/ui/error_item.js +0 -7
- package/build/cli/ui/errors_list.js +0 -15
|
@@ -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;
|
|
@@ -14,7 +14,7 @@ import { ThemeCreatedSuccess } from './ui/theme_created_success.js';
|
|
|
14
14
|
import { Text } from '../../../ui/text.js';
|
|
15
15
|
import { MissingThemeIdError } from '../ui/missing_theme_id_error.js';
|
|
16
16
|
import { Box } from '../../../ui/box.js';
|
|
17
|
-
import {
|
|
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.';
|
|
@@ -83,7 +83,7 @@ export class ThemeInitCommand extends BaseThemeCommand {
|
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
85
|
if (err?.message) {
|
|
86
|
-
renderOnce(React.createElement(
|
|
86
|
+
renderOnce(React.createElement(ValidationErrors, { errors: err.message }));
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
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,
|
|
@@ -26,7 +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 {
|
|
29
|
+
import { ValidationErrors } from '../../../cli/ui/validation_errors/validation_errors.js';
|
|
30
30
|
export class ThemePushCommand extends BaseThemeCommand {
|
|
31
31
|
static summary = 'Uploads your local theme files to the store and overwrites the current version of the theme in your store.';
|
|
32
32
|
static description = 'Check your local changes before pushing.\n\nYou must run this command from a specific theme directory (ID not needed).';
|
|
@@ -111,7 +111,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
111
111
|
return;
|
|
112
112
|
}
|
|
113
113
|
if (err?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
|
|
114
|
-
renderOnce(React.createElement(
|
|
114
|
+
renderOnce(React.createElement(ValidationErrors, { errors: err?.details }));
|
|
115
115
|
return;
|
|
116
116
|
}
|
|
117
117
|
if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
|
|
@@ -123,8 +123,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
123
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
124
|
}
|
|
125
125
|
if (err?.message) {
|
|
126
|
-
|
|
127
|
-
renderOnce(React.createElement(ErrorsList, { errors: err.message }));
|
|
126
|
+
renderOnce(React.createElement(ValidationErrors, { errors: err.message }));
|
|
128
127
|
return;
|
|
129
128
|
}
|
|
130
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.getInitialChecksumFromPath(path, 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
|
}
|
|
@@ -2,7 +2,7 @@ import tmp from 'tmp-promise';
|
|
|
2
2
|
import { THEME_ACTION_DATA_TYPE, THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
|
|
3
3
|
import { createZip } from '../../../../../utils/zip/create_zip_utils.js';
|
|
4
4
|
import { basename, dirname, extname, join, looksLikeDirectory, toUnixPath } from '../../../../../utils/path_utils.js';
|
|
5
|
-
import { createReadStream } from 'node:fs';
|
|
5
|
+
import fs, { createReadStream } from 'node:fs';
|
|
6
6
|
import { v4 as uuid } from 'uuid';
|
|
7
7
|
import globs from 'fast-glob';
|
|
8
8
|
import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
|
|
@@ -27,13 +27,14 @@ 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,
|
|
34
34
|
filesStructure,
|
|
35
35
|
executionContext
|
|
36
36
|
});
|
|
37
|
+
console.log('uploadData', uploadData);
|
|
37
38
|
if (uploadData.length) {
|
|
38
39
|
await this._uploadThemeFiles({
|
|
39
40
|
filesToUpload: uploadData,
|
|
@@ -123,25 +124,30 @@ export class ThemePushService {
|
|
|
123
124
|
async _uploadFiles(uploadData, credentials) {
|
|
124
125
|
const uploadedImageData = [];
|
|
125
126
|
const rejectedImageData = [];
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
127
|
+
console.log('uploadData', uploadData);
|
|
128
|
+
await Promise.all(uploadData.map(({ actionData, path }) => {
|
|
129
|
+
const fstat = fs.lstatSync(path);
|
|
130
|
+
console.log('fstat', fstat);
|
|
131
|
+
return this.#themePushHttpApi
|
|
132
|
+
.pushThemeData({
|
|
133
|
+
actionData,
|
|
134
|
+
stream: createReadStream(path),
|
|
135
|
+
shopUrl: credentials.shopUrl
|
|
136
|
+
})
|
|
137
|
+
.response.then((response) => {
|
|
138
|
+
uploadedImageData.push({
|
|
139
|
+
location: dirname(path),
|
|
140
|
+
originalFilename: basename(path),
|
|
141
|
+
uploadedFilename: response.data.imageId
|
|
142
|
+
});
|
|
143
|
+
})
|
|
144
|
+
.catch(() => {
|
|
145
|
+
rejectedImageData.push({
|
|
146
|
+
location: dirname(path),
|
|
147
|
+
originalFilename: basename(path)
|
|
148
|
+
});
|
|
143
149
|
});
|
|
144
|
-
}))
|
|
150
|
+
}));
|
|
145
151
|
return {
|
|
146
152
|
uploadedImageData,
|
|
147
153
|
rejectedImageData
|
|
@@ -169,8 +175,8 @@ export class ThemePushService {
|
|
|
169
175
|
localFiles[fileGlob] = files.length ? basename(files[0]) : null;
|
|
170
176
|
}
|
|
171
177
|
for (const filePath of files) {
|
|
172
|
-
if ((await this.#themeMergeApi.hasFileBeenCreated(filePath, executionContext)) ||
|
|
173
|
-
(await this.#themeMergeApi.hasFileBeenModified(filePath, executionContext))) {
|
|
178
|
+
if ((await this.#themeMergeApi.hasFileBeenCreated(filePath, executionContext.themeRootDir)) ||
|
|
179
|
+
(await this.#themeMergeApi.hasFileBeenModified(filePath, executionContext.themeRootDir))) {
|
|
174
180
|
uploadData.push({
|
|
175
181
|
actionData,
|
|
176
182
|
actionKey,
|
|
@@ -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
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Error } from '../../ui/message_box/error.js';
|
|
2
|
-
import { Text } from '../../ui/text.js';
|
|
3
|
-
import React from 'react';
|
|
4
|
-
export const ErrorItem = ({ error, title }) => {
|
|
5
|
-
return (React.createElement(Error, { header: title },
|
|
6
|
-
React.createElement(Text, null, error)));
|
|
7
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { ErrorItem } from './error_item.js';
|
|
3
|
-
import { Error } from '../../ui/message_box/error.js';
|
|
4
|
-
import { Text } from '../../ui/text.js';
|
|
5
|
-
import { Box } from '../../ui/box.js';
|
|
6
|
-
export const ErrorsList = ({ errors }) => {
|
|
7
|
-
if (typeof errors === 'string') {
|
|
8
|
-
return React.createElement(ErrorItem, { error: errors });
|
|
9
|
-
}
|
|
10
|
-
if (Array.isArray(errors)) {
|
|
11
|
-
return errors.map((error, index) => (React.createElement(ErrorItem, { key: index, error: error })));
|
|
12
|
-
}
|
|
13
|
-
return (React.createElement(React.Fragment, null, Object.entries(errors).map(([title, errorMessages], index) => (React.createElement(Error, { key: index, header: title },
|
|
14
|
-
React.createElement(Box, { flexDirection: "column" }, errorMessages.map((message, index) => (React.createElement(Text, { key: index }, message)))))))));
|
|
15
|
-
};
|