@shoper/cli 0.1.0-21 → 0.1.0-22
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/features/execution_context/execution_context_service.js +5 -5
- package/build/theme/commands/info/theme_info_command.js +97 -0
- package/build/theme/commands/info/theme_info_command_utils.js +39 -0
- package/build/theme/commands/init/theme_init_command.js +4 -4
- package/build/theme/commands/pull/theme_pull_command.js +17 -2
- package/build/theme/commands/push/theme_push_command.js +32 -17
- package/build/theme/commands/theme_commands_constants.js +2 -1
- package/build/theme/commands/theme_verify_command.js +3 -3
- package/build/theme/commands/ui/invalid_theme_id.js +11 -0
- package/build/theme/commands/ui/theme_work_url_mismatch.js +17 -0
- package/build/theme/features/theme/actions/theme_actions_constants.js +2 -1
- package/build/theme/features/theme/fetch/service/theme_fetch_service.js +12 -3
- package/build/theme/features/theme/info/theme_info_utils.js +1 -0
- package/build/theme/features/theme/init/service/theme_init_service.js +2 -2
- package/build/theme/features/theme/push/theme_push_utils.js +2 -2
- package/build/theme/features/theme/utils/checksums/theme_checksums_utils.js +5 -5
- package/build/theme/features/theme/utils/files_structure/theme_files_structure_utils.js +34 -0
- package/build/theme/features/theme/utils/hidden_directory/hidden_directory_utils.js +33 -0
- package/build/theme/features/theme/utils/meta_data/theme_meta_data_constants.js +1 -0
- package/build/theme/features/theme/utils/meta_data/theme_meta_data_error_factory.js +15 -0
- package/build/theme/features/theme/utils/meta_data/theme_meta_data_utils.js +34 -0
- package/build/theme/features/themes/list/api/themes_list_api.js +3 -0
- package/build/theme/features/themes/list/services/themes_list_service.js +10 -3
- package/build/theme/hooks/ensure_theme_meta_data_untouched_hook.js +2 -2
- package/build/theme/hooks/themes_actions/ensure_themes_actions_hook.js +1 -0
- package/build/theme/index.js +3 -1
- package/package.json +1 -1
- package/build/theme/features/theme/meta_data_directory/theme_directory_utils.js +0 -71
- /package/build/theme/features/theme/utils/{directories → resources}/theme_resources_with_id_directory_utils.js +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import process from 'node:process';
|
|
2
2
|
import { EXECUTION_CONTEXTS } from './execution_context_constants.js';
|
|
3
|
-
import {
|
|
3
|
+
import { ThemeMetaDataUtils } from '../../../theme/features/theme/utils/meta_data/theme_meta_data_utils.js';
|
|
4
4
|
export class ExecutionContextService {
|
|
5
5
|
#executionContext;
|
|
6
6
|
async _obtainExecutionContext() {
|
|
7
|
-
if (await
|
|
7
|
+
if (await ThemeMetaDataUtils.closestThemeRootDirectory(process.cwd())) {
|
|
8
8
|
return await this.getThemeExecutionContext();
|
|
9
9
|
}
|
|
10
10
|
return await this._getGlobalExecutionContext();
|
|
@@ -20,14 +20,14 @@ export class ExecutionContextService {
|
|
|
20
20
|
return this.#executionContext;
|
|
21
21
|
}
|
|
22
22
|
async getThemeExecutionContext() {
|
|
23
|
-
const themeRoot = await
|
|
23
|
+
const themeRoot = await ThemeMetaDataUtils.closestThemeRootDirectory(process.cwd());
|
|
24
24
|
//TODO errors
|
|
25
25
|
if (!themeRoot)
|
|
26
26
|
throw 'Theme root directory not found';
|
|
27
|
-
const
|
|
27
|
+
const themeMetaData = await ThemeMetaDataUtils.getThemeMetadata(themeRoot);
|
|
28
28
|
return {
|
|
29
29
|
type: EXECUTION_CONTEXTS.theme,
|
|
30
|
-
themeId:
|
|
30
|
+
themeId: themeMetaData.themeId,
|
|
31
31
|
executionDir: process.cwd(),
|
|
32
32
|
themeRootDir: themeRoot
|
|
33
33
|
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { BaseThemeCommand } from '../../class/base_theme_command.js';
|
|
2
|
+
import { CLI_AUTH_API_NAME } from '../../../cli/auth/cli_auth_constants.js';
|
|
3
|
+
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
|
|
4
|
+
import { renderOnce } from '../../../ui/ui_utils.js';
|
|
5
|
+
import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_credentials_error.js';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { THEMES_LIST_API_NAME } from '../../features/themes/list/themes_list_constants.js';
|
|
8
|
+
import { Args } from '@oclif/core';
|
|
9
|
+
import { ThemeMetaDataUtils } from '../../features/theme/utils/meta_data/theme_meta_data_utils.js';
|
|
10
|
+
import { MissingThemeIdError } from '../ui/missing_theme_id_error.js';
|
|
11
|
+
import { Text } from '../../../ui/text.js';
|
|
12
|
+
import { THEME_WORK_URL_MISMATCH_ERROR } from '../../features/theme/utils/meta_data/theme_meta_data_constants.js';
|
|
13
|
+
import { Error } from '../../../ui/message_box/error.js';
|
|
14
|
+
import { ThemeWorkUrlMismatch } from '../ui/theme_work_url_mismatch.js';
|
|
15
|
+
import { Table } from '../../../ui/table/table.js';
|
|
16
|
+
import { ThemeInfoCommandUtils } from './theme_info_command_utils.js';
|
|
17
|
+
import { InvalidThemeIdError } from '../ui/invalid_theme_id.js';
|
|
18
|
+
import { Box } from '../../../ui/box.js';
|
|
19
|
+
export class ThemeInfoCommand extends BaseThemeCommand {
|
|
20
|
+
static summary = 'View details about the current theme.';
|
|
21
|
+
static description = 'Displays key information about the theme you’re working on — including its ID, name, description (if provided), and status.\n\nYou can run this command from a specific theme directory (ID not needed) or outside any theme directory (theme ID required).';
|
|
22
|
+
static examples = [
|
|
23
|
+
{
|
|
24
|
+
description: 'The command will display the theme ID, name, description, and status.',
|
|
25
|
+
command: '<%= config.bin %> <%= command.id %> 2'
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
static args = {
|
|
29
|
+
id: Args.string({
|
|
30
|
+
description: 'Theme id',
|
|
31
|
+
name: 'id',
|
|
32
|
+
type: 'string'
|
|
33
|
+
})
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const data = await this.parse(ThemeInfoCommand);
|
|
37
|
+
const { args } = data;
|
|
38
|
+
const themeId = args.id;
|
|
39
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
40
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
41
|
+
const themesListApi = this.getApi(THEMES_LIST_API_NAME);
|
|
42
|
+
const credentials = cliAuthApi.getCredentials();
|
|
43
|
+
if (!credentials) {
|
|
44
|
+
renderOnce(React.createElement(MissingCredentialsError, null));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
48
|
+
let theme;
|
|
49
|
+
let _themeId = themeId;
|
|
50
|
+
try {
|
|
51
|
+
if (executionContext.type === EXECUTION_CONTEXTS.theme) {
|
|
52
|
+
await ThemeMetaDataUtils.ensureThemeWorkUrlMatch(executionContext.themeRootDir, credentials.shopUrl);
|
|
53
|
+
_themeId = themeId ?? executionContext.themeId;
|
|
54
|
+
theme = await themesListApi.getTheme({
|
|
55
|
+
themeId: _themeId,
|
|
56
|
+
shopUrl: credentials.shopUrl
|
|
57
|
+
});
|
|
58
|
+
}
|
|
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
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (!theme) {
|
|
73
|
+
renderOnce(_themeId ? React.createElement(InvalidThemeIdError, { themeId: _themeId }) : React.createElement(Error, { header: "Error: Theme information is not available." }));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this._displayThemeInfo(theme);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
this._handleError(err);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
_handleError(err) {
|
|
83
|
+
if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
|
|
84
|
+
renderOnce(React.createElement(ThemeWorkUrlMismatch, { command: "info" }));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (err?.message) {
|
|
88
|
+
renderOnce(React.createElement(Error, null,
|
|
89
|
+
React.createElement(Text, null, err.message)));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
this.error(String(err));
|
|
93
|
+
}
|
|
94
|
+
_displayThemeInfo(theme) {
|
|
95
|
+
renderOnce(React.createElement(Table, { data: ThemeInfoCommandUtils.mapThemeMetaDataToTableData(theme) }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Text } from '../../../ui/text.js';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
export class ThemeInfoCommandUtils {
|
|
4
|
+
static mapThemeMetaDataToTableData(theme) {
|
|
5
|
+
return {
|
|
6
|
+
headers: [
|
|
7
|
+
{
|
|
8
|
+
content: 'Theme Info'
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
rows: [
|
|
12
|
+
[
|
|
13
|
+
React.createElement(Text, null,
|
|
14
|
+
React.createElement(Text, { bold: true }, "ID:"),
|
|
15
|
+
" ",
|
|
16
|
+
theme.skinId)
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
React.createElement(Text, null,
|
|
20
|
+
React.createElement(Text, { bold: true }, "Name:"),
|
|
21
|
+
" ",
|
|
22
|
+
theme.title)
|
|
23
|
+
],
|
|
24
|
+
[
|
|
25
|
+
React.createElement(Text, null,
|
|
26
|
+
React.createElement(Text, { bold: true }, "Description:"),
|
|
27
|
+
" ",
|
|
28
|
+
theme.description ? theme.description : '-')
|
|
29
|
+
],
|
|
30
|
+
[
|
|
31
|
+
React.createElement(Text, null,
|
|
32
|
+
React.createElement(Text, { bold: true }, "Status: "),
|
|
33
|
+
": $",
|
|
34
|
+
theme.isActive ? 'Active' : 'Inactive')
|
|
35
|
+
]
|
|
36
|
+
]
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -13,7 +13,7 @@ import { UnpermittedCommandError } from '../ui/unpermitted_command_error.js';
|
|
|
13
13
|
import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_credentials_error.js';
|
|
14
14
|
import { ThemeCreatedSuccess } from './ui/theme_created_success.js';
|
|
15
15
|
import { Text } from '../../../ui/text.js';
|
|
16
|
-
import {
|
|
16
|
+
import { Box } from '../../../ui/box.js';
|
|
17
17
|
export class ThemeInitCommand extends BaseThemeCommand {
|
|
18
18
|
static summary = 'Creates a copy of an existing theme by duplicating it.';
|
|
19
19
|
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.';
|
|
@@ -37,9 +37,9 @@ export class ThemeInitCommand extends BaseThemeCommand {
|
|
|
37
37
|
const themeId = args.id;
|
|
38
38
|
if (!themeId) {
|
|
39
39
|
renderOnce(React.createElement(MissingThemeIdError, null,
|
|
40
|
-
React.createElement(
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
React.createElement(Box, { flexDirection: "column", gap: 1 },
|
|
41
|
+
React.createElement(Text, null, "Usage: shoper theme init [ID]"),
|
|
42
|
+
React.createElement(Text, null, "This command creates a copy of an existing theme based on its ID.To proceed, you must provide the ID of the theme you want to duplicate."))));
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
@@ -9,7 +9,7 @@ import tmp from 'tmp-promise';
|
|
|
9
9
|
import { join } from '../../../utils/path_utils.js';
|
|
10
10
|
import { THEME_ACTION_NOT_FOUND_ERROR_CODE, THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../../features/theme/actions/theme_actions_constants.js';
|
|
11
11
|
import process from 'node:process';
|
|
12
|
-
import { ThemeResourcesWithIdDirectoryUtils } from '../../features/theme/utils/
|
|
12
|
+
import { ThemeResourcesWithIdDirectoryUtils } from '../../features/theme/utils/resources/theme_resources_with_id_directory_utils.js';
|
|
13
13
|
import { renderOnce } from '../../../ui/ui_utils.js';
|
|
14
14
|
import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_credentials_error.js';
|
|
15
15
|
import React from 'react';
|
|
@@ -22,6 +22,10 @@ import { promptConfirmation } from '../../../ui/prompts/prompt_confirmation.js';
|
|
|
22
22
|
import { ThemePulledSuccess } from './ui/theme_pulled_success.js';
|
|
23
23
|
import { Info } from '../../../ui/message_box/info.js';
|
|
24
24
|
import { directoryExists } from '../../../utils/fs/fs_utils.js';
|
|
25
|
+
import { THEME_WORK_URL_MISMATCH_ERROR } from '../../features/theme/utils/meta_data/theme_meta_data_constants.js';
|
|
26
|
+
import { ThemeWorkUrlMismatch } from '../ui/theme_work_url_mismatch.js';
|
|
27
|
+
import { Error } from '../../../ui/message_box/error.js';
|
|
28
|
+
import { ThemeMetaDataUtils } from '../../features/theme/utils/meta_data/theme_meta_data_utils.js';
|
|
25
29
|
//TODO jak jest error w pullu wowczas usuwamy docelowo pociagniety skin
|
|
26
30
|
export class ThemePullCommand extends BaseThemeCommand {
|
|
27
31
|
static summary = 'Downloads the current version of your theme from the store and overwrites your local theme files.';
|
|
@@ -99,6 +103,15 @@ export class ThemePullCommand extends BaseThemeCommand {
|
|
|
99
103
|
this._renderUnpermittedCommandError(_themeId);
|
|
100
104
|
return;
|
|
101
105
|
}
|
|
106
|
+
if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
|
|
107
|
+
renderOnce(React.createElement(ThemeWorkUrlMismatch, { command: "pull" }));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (err?.message) {
|
|
111
|
+
renderOnce(React.createElement(Error, null,
|
|
112
|
+
React.createElement(Text, null, err.message)));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
102
115
|
this.error(String(err));
|
|
103
116
|
}
|
|
104
117
|
_renderUnpermittedCommandError(themeId) {
|
|
@@ -112,7 +125,7 @@ export class ThemePullCommand extends BaseThemeCommand {
|
|
|
112
125
|
}
|
|
113
126
|
const pullAction = themeActionsApi.getThemeAction({
|
|
114
127
|
actionType: THEME_ACTIONS_TYPES.pull,
|
|
115
|
-
themeId
|
|
128
|
+
themeId,
|
|
116
129
|
credentials
|
|
117
130
|
});
|
|
118
131
|
if (!pullAction) {
|
|
@@ -132,6 +145,8 @@ export class ThemePullCommand extends BaseThemeCommand {
|
|
|
132
145
|
renderOnce(React.createElement(ThemePulledSuccess, null));
|
|
133
146
|
}
|
|
134
147
|
async _pullThemeIntoExistingOne({ themeId, themeFetchApi, themeActionsApi, fetchType, executionContext, credentials }) {
|
|
148
|
+
//TODO move these logis somvewhere else?
|
|
149
|
+
await ThemeMetaDataUtils.ensureThemeWorkUrlMatch(executionContext.themeRootDir, credentials.shopUrl);
|
|
135
150
|
const pullAction = themeActionsApi.getThemeAction({
|
|
136
151
|
actionType: THEME_ACTIONS_TYPES.pull,
|
|
137
152
|
themeId,
|
|
@@ -3,7 +3,7 @@ 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
5
|
import { THEME_ARCHIVE_UPLOAD_ERROR, THEME_PUSH_API_NAME } from '../../features/theme/push/theme_push_constants.js';
|
|
6
|
-
import {
|
|
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';
|
|
9
9
|
import { ThemeChecksumsUtils } from '../../features/theme/utils/checksums/theme_checksums_utils.js';
|
|
@@ -23,6 +23,9 @@ import { ThemeUnpermittedActionsError } from './ui/theme_unpermitted_actions_err
|
|
|
23
23
|
import { ThemeValidationErrors } from './ui/theme_validation_errors.js';
|
|
24
24
|
import { Error } from '../../../ui/message_box/error.js';
|
|
25
25
|
import { Text } from '../../../ui/text.js';
|
|
26
|
+
import { ThemeWorkUrlMismatch } from '../ui/theme_work_url_mismatch.js';
|
|
27
|
+
import { THEME_WORK_URL_MISMATCH_ERROR } from '../../features/theme/utils/meta_data/theme_meta_data_constants.js';
|
|
28
|
+
import { ThemeFilesStructureUtils } from '../../features/theme/utils/files_structure/theme_files_structure_utils.js';
|
|
26
29
|
import { ThemeInfoUtils } from '../../features/theme/info/theme_info_utils.js';
|
|
27
30
|
export class ThemePushCommand extends BaseThemeCommand {
|
|
28
31
|
static summary = 'Uploads your local theme files to the store and overwrites the current version of the theme in your store.';
|
|
@@ -50,6 +53,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
50
53
|
}
|
|
51
54
|
let spinner;
|
|
52
55
|
try {
|
|
56
|
+
await ThemeMetaDataUtils.ensureThemeWorkUrlMatch(executionContext.themeRootDir, credentials.shopUrl);
|
|
53
57
|
const pushAction = themeActionsApi.getThemeAction({
|
|
54
58
|
actionType: THEME_ACTIONS_TYPES.push,
|
|
55
59
|
themeId: executionContext.themeId,
|
|
@@ -60,7 +64,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
60
64
|
return;
|
|
61
65
|
}
|
|
62
66
|
const checksums = await ThemeChecksumsUtils.getThemeInitialChecksums(executionContext.themeRootDir);
|
|
63
|
-
const permissions = await
|
|
67
|
+
const permissions = await ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
|
|
64
68
|
if (!checksums || !permissions)
|
|
65
69
|
this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
|
|
66
70
|
const themeMergeApi = this.getApi(THEME_MERGE_API_NAME);
|
|
@@ -69,7 +73,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
69
73
|
renderOnce(React.createElement(ThemePushSkipInfo, null));
|
|
70
74
|
return;
|
|
71
75
|
}
|
|
72
|
-
const validationResult = await
|
|
76
|
+
const validationResult = await ThemeFilesStructureUtils.validateThemeDirectoryStructure({
|
|
73
77
|
checksums: mapChecksumToTree(checksums),
|
|
74
78
|
permissions: mapToPermissionsTree(permissions),
|
|
75
79
|
rootDirectory: executionContext.themeRootDir
|
|
@@ -80,7 +84,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
80
84
|
renderOnce(React.createElement(ThemeUnpermittedActionsError, { unpermittedActions: validationResult.unpermittedActions }));
|
|
81
85
|
return;
|
|
82
86
|
}
|
|
83
|
-
const filesStructure = await
|
|
87
|
+
const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(executionContext.themeRootDir);
|
|
84
88
|
if (!filesStructure) {
|
|
85
89
|
renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
|
|
86
90
|
return;
|
|
@@ -98,19 +102,30 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
98
102
|
}
|
|
99
103
|
catch (err) {
|
|
100
104
|
spinner?.stop();
|
|
101
|
-
|
|
102
|
-
renderOnce(React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "push" }));
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (err?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
|
|
106
|
-
renderOnce(React.createElement(ThemeValidationErrors, { errors: err?.details }));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (err?.message) {
|
|
110
|
-
renderOnce(React.createElement(Error, null,
|
|
111
|
-
React.createElement(Text, null, err.message)));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
105
|
+
this._handleError(err, executionContext);
|
|
114
106
|
}
|
|
115
107
|
}
|
|
108
|
+
_handleError(err, executionContext) {
|
|
109
|
+
if (err?.code === THEME_ACTION_NOT_FOUND_ERROR_CODE) {
|
|
110
|
+
renderOnce(React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "push" }));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (err?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
|
|
114
|
+
renderOnce(React.createElement(ThemeValidationErrors, { errors: err?.details }));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
|
|
118
|
+
this._renderUrlMismatchError();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (err?.message) {
|
|
122
|
+
renderOnce(React.createElement(Error, null,
|
|
123
|
+
React.createElement(Text, null, err.message)));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.error(String(err));
|
|
127
|
+
}
|
|
128
|
+
_renderUrlMismatchError() {
|
|
129
|
+
renderOnce(React.createElement(ThemeWorkUrlMismatch, { command: "push" }));
|
|
130
|
+
}
|
|
116
131
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
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
|
-
import { ThemeDirectoryUtils } from '../features/theme/meta_data_directory/theme_directory_utils.js';
|
|
5
4
|
import { mapToPermissionsTree } from '../utils/directory_validator/directory_validator_utils.js';
|
|
6
5
|
import { mapChecksumToTree } from '../../utils/checksums/checksums_utils.js';
|
|
7
6
|
import { ThemeChecksumsUtils } from '../features/theme/utils/checksums/theme_checksums_utils.js';
|
|
7
|
+
import { ThemeFilesStructureUtils } from '../features/theme/utils/files_structure/theme_files_structure_utils.js';
|
|
8
8
|
export class ThemeVerifyCommand extends BaseThemeCommand {
|
|
9
9
|
static description = 'Verify theme files structure';
|
|
10
10
|
static hidden = true;
|
|
@@ -18,11 +18,11 @@ export class ThemeVerifyCommand extends BaseThemeCommand {
|
|
|
18
18
|
if (executionContext.type !== EXECUTION_CONTEXTS.theme)
|
|
19
19
|
this.error('You cannot run this command outside theme context.');
|
|
20
20
|
const checksums = await ThemeChecksumsUtils.getThemeInitialChecksums(executionContext.themeRootDir);
|
|
21
|
-
const permissions = await
|
|
21
|
+
const permissions = await ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
|
|
22
22
|
if (!checksums || !permissions)
|
|
23
23
|
this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
|
|
24
24
|
try {
|
|
25
|
-
const validationResult = await
|
|
25
|
+
const validationResult = await ThemeFilesStructureUtils.validateThemeDirectoryStructure({
|
|
26
26
|
checksums: mapChecksumToTree(checksums),
|
|
27
27
|
permissions: mapToPermissionsTree(permissions),
|
|
28
28
|
rootDirectory: executionContext.themeRootDir
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Error } from '../../../ui/message_box/error.js';
|
|
2
|
+
import { Tip } from '../../../ui/tip.js';
|
|
3
|
+
import { Command } from '../../../ui/command.js';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
export const InvalidThemeIdError = ({ themeId }) => {
|
|
6
|
+
return (React.createElement(Error, { header: `Error: Invalid theme ID: ${themeId}` },
|
|
7
|
+
React.createElement(Tip, null,
|
|
8
|
+
"Run ",
|
|
9
|
+
React.createElement(Command, null, "shoper theme list"),
|
|
10
|
+
" to see available theme IDs.")));
|
|
11
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Error } from '../../../ui/message_box/error.js';
|
|
3
|
+
import { Command } from '../../../ui/command.js';
|
|
4
|
+
import { Tip } from '../../../ui/tip.js';
|
|
5
|
+
import { Text } from '../../../ui/text.js';
|
|
6
|
+
export const ThemeWorkUrlMismatch = ({ command }) => {
|
|
7
|
+
return (React.createElement(Error, { header: `Error: This theme belongs to a different store.` },
|
|
8
|
+
React.createElement(Text, null,
|
|
9
|
+
"Switch to the correct token to ",
|
|
10
|
+
command,
|
|
11
|
+
" this theme."),
|
|
12
|
+
React.createElement(Tip, null,
|
|
13
|
+
React.createElement(Text, null,
|
|
14
|
+
"Run ",
|
|
15
|
+
React.createElement(Command, null, "shoper theme auth token switch"),
|
|
16
|
+
" to change your token"))));
|
|
17
|
+
};
|
|
@@ -7,7 +7,6 @@ import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_conts
|
|
|
7
7
|
import { getResources, mapResourcesToTree } from '../../../../class/fetch_resources/fetch_resources_utils.js';
|
|
8
8
|
import { FetchResources } from '../../../../class/fetch_resources/fetch_resources.js';
|
|
9
9
|
import { jsonIndentTransform } from '../../../../../utils/stream_transforms/json_indent_transform.js';
|
|
10
|
-
import { ThemeDirectoryUtils } from '../../meta_data_directory/theme_directory_utils.js';
|
|
11
10
|
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';
|
|
12
11
|
import { THEME_FILES_LIST_FILE_NAME } from '../../push/theme_push_constants.js';
|
|
13
12
|
import { JSON_FILE_INDENT } from '../../../../../cli/cli_constants.js';
|
|
@@ -15,6 +14,8 @@ import { ThemeChecksumsUtils } from '../../utils/checksums/theme_checksums_utils
|
|
|
15
14
|
import { createWriteStream } from 'node:fs';
|
|
16
15
|
import { AppError } from '../../../../../cli/class/errors/app/app_error.js';
|
|
17
16
|
import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
|
|
17
|
+
import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
|
|
18
|
+
import { ThemeMetaDataUtils } from '../../utils/meta_data/theme_meta_data_utils.js';
|
|
18
19
|
export class ThemeFetchService {
|
|
19
20
|
#themeHttpApi;
|
|
20
21
|
#httpApi;
|
|
@@ -53,6 +54,7 @@ export class ThemeFetchService {
|
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
56
|
await this._createGitIgnoreFile(themeDir);
|
|
57
|
+
await this._updateMetadataFileWithWorkUrl(themeDir, credentials.shopUrl);
|
|
56
58
|
const checksums = await ThemeChecksumsUtils.computeThemeChecksums(themeDir);
|
|
57
59
|
await ThemeChecksumsUtils.createThemeChecksumsFiles(themeDir, checksums);
|
|
58
60
|
return {
|
|
@@ -60,7 +62,7 @@ export class ThemeFetchService {
|
|
|
60
62
|
};
|
|
61
63
|
}
|
|
62
64
|
async _updateFilesStructure(themeDir) {
|
|
63
|
-
const fileStructure = await
|
|
65
|
+
const fileStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDir);
|
|
64
66
|
const checksumsFiles = [
|
|
65
67
|
`${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_FILE_NAME}`,
|
|
66
68
|
`${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME}`,
|
|
@@ -93,7 +95,14 @@ export class ThemeFetchService {
|
|
|
93
95
|
}
|
|
94
96
|
};
|
|
95
97
|
});
|
|
96
|
-
await
|
|
98
|
+
await ThemeFilesStructureUtils.writeThemeFilesStructure(themeDir, fileStructure);
|
|
99
|
+
}
|
|
100
|
+
async _updateMetadataFileWithWorkUrl(themeDir, workUrl) {
|
|
101
|
+
const shopMetadata = await ThemeMetaDataUtils.getThemeMetadata(themeDir);
|
|
102
|
+
await ThemeMetaDataUtils.updateThemeMetadata(themeDir, {
|
|
103
|
+
...shopMetadata,
|
|
104
|
+
workUrl
|
|
105
|
+
});
|
|
97
106
|
}
|
|
98
107
|
async fetchResources(shopUrl, dist, resources) {
|
|
99
108
|
const resourcesTree = mapResourcesToTree(resources);
|
|
@@ -2,6 +2,7 @@ import { readJSONFile } from '../../../../utils/fs/fs_utils.js';
|
|
|
2
2
|
import { join } from '../../../../utils/path_utils.js';
|
|
3
3
|
export class ThemeInfoUtils {
|
|
4
4
|
static async getThemeName(themeDir) {
|
|
5
|
+
//TODO - wziasc z themeList a to wywalic
|
|
5
6
|
const themeDetails = await readJSONFile(join(themeDir, 'settings', 'details.json'));
|
|
6
7
|
return themeDetails.name ?? 'Storefront';
|
|
7
8
|
}
|
|
@@ -4,7 +4,7 @@ import tmp from 'tmp-promise';
|
|
|
4
4
|
import { downloadFile } from '../../../../../utils/download_file/download_file_utils.js';
|
|
5
5
|
import { extractZip } from '../../../../../utils/zip/extract_zip_utils.js';
|
|
6
6
|
import { ThemeChecksumsUtils } from '../../utils/checksums/theme_checksums_utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ThemeMetaDataUtils } from '../../utils/meta_data/theme_meta_data_utils.js';
|
|
8
8
|
import { ThemeInfoUtils } from '../../info/theme_info_utils.js';
|
|
9
9
|
export class ThemeInitService {
|
|
10
10
|
#httpApi;
|
|
@@ -25,7 +25,7 @@ export class ThemeInitService {
|
|
|
25
25
|
});
|
|
26
26
|
const checksums = await ThemeChecksumsUtils.computeThemeChecksums(distDir);
|
|
27
27
|
await ThemeChecksumsUtils.createThemeChecksumsFiles(join(process.cwd(), basename), checksums);
|
|
28
|
-
const themeMetaData = await
|
|
28
|
+
const themeMetaData = await ThemeMetaDataUtils.getThemeMetadata(distDir);
|
|
29
29
|
return {
|
|
30
30
|
themeId: themeMetaData.themeId,
|
|
31
31
|
themeName: await ThemeInfoUtils.getThemeName(distDir)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import globs from 'fast-glob';
|
|
2
|
-
import { ThemeDirectoryUtils } from '../meta_data_directory/theme_directory_utils.js';
|
|
3
2
|
import { AppError } from '../../../../cli/class/errors/app/app_error.js';
|
|
4
3
|
import { toUnixPath } from '../../../../utils/path_utils.js';
|
|
4
|
+
import { ThemeFilesStructureUtils } from '../utils/files_structure/theme_files_structure_utils.js';
|
|
5
5
|
export class ThemePushUtils {
|
|
6
6
|
static async getAllFilesThatAreSendToRemote(themeDir) {
|
|
7
|
-
const filesStructure = await
|
|
7
|
+
const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDir);
|
|
8
8
|
if (!filesStructure)
|
|
9
9
|
throw new AppError({
|
|
10
10
|
message: `Files structure not found in theme directory: ${themeDir}`,
|
|
@@ -3,25 +3,25 @@ import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_
|
|
|
3
3
|
import { createWriteStream } from 'fs';
|
|
4
4
|
import { copyFile, readJSONFile, removeFile } from '../../../../../utils/fs/fs_utils.js';
|
|
5
5
|
import { ThemeChecksumsErrorFactory } from './theme_checksums_error_factory.js';
|
|
6
|
-
import { ThemeDirectoryUtils } from '../../meta_data_directory/theme_directory_utils.js';
|
|
7
6
|
import { computeChecksumsFromFilesStructure, computeDirectoriesChecksums, computeFileChecksum } from '../../../../../utils/checksums/checksums_utils.js';
|
|
8
7
|
import { JSON_FILE_INDENT } from '../../../../../cli/cli_constants.js';
|
|
9
8
|
import { ThemePushUtils } from '../../push/theme_push_utils.js';
|
|
10
9
|
import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
|
|
11
10
|
import { isWindowsOs } from '../../../../../utils/platform_utils.js';
|
|
12
11
|
import { normalize } from 'path';
|
|
12
|
+
import { HiddenDirectoryUtils } from '../hidden_directory/hidden_directory_utils.js';
|
|
13
13
|
export class ThemeChecksumsUtils {
|
|
14
14
|
static getCurrentThemeChecksumsFilePath(themeDir) {
|
|
15
|
-
return join(
|
|
15
|
+
return join(HiddenDirectoryUtils.getThemeHiddenDirectoryPath(themeDir), THEME_CURRENT_CHECKSUMS_FILE_NAME);
|
|
16
16
|
}
|
|
17
17
|
static getInitialThemeChecksumsFilePath(themeDir) {
|
|
18
|
-
return join(
|
|
18
|
+
return join(HiddenDirectoryUtils.getThemeHiddenDirectoryPath(themeDir), THEME_INITIAL_CHECKSUMS_FILE_NAME);
|
|
19
19
|
}
|
|
20
20
|
static getCurrentThemeChecksumsVerificationFilePath(themeDir) {
|
|
21
|
-
return join(
|
|
21
|
+
return join(HiddenDirectoryUtils.getThemeHiddenDirectoryPath(themeDir), THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME);
|
|
22
22
|
}
|
|
23
23
|
static getInitialThemeChecksumsVerificationFilePath(themeDir) {
|
|
24
|
-
return join(
|
|
24
|
+
return join(HiddenDirectoryUtils.getThemeHiddenDirectoryPath(themeDir), THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME);
|
|
25
25
|
}
|
|
26
26
|
static async createThemeChecksumsFiles(themeDir, checksums) {
|
|
27
27
|
return new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readJSONFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
|
|
2
|
+
import { join, mapKeysPathsToWindowPlatform } from '../../../../../utils/path_utils.js';
|
|
3
|
+
import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
|
|
4
|
+
import { THEME_FILES_STRUCTURE_FILE_NAME } from '../../theme_constants.js';
|
|
5
|
+
import { isWindowsOs } from '../../../../../utils/platform_utils.js';
|
|
6
|
+
import { validateDirectory } from '../../../../utils/directory_validator/directory_validator_utils.js';
|
|
7
|
+
export class ThemeFilesStructureUtils {
|
|
8
|
+
static async getThemeFilesStructure(themeDirectory) {
|
|
9
|
+
const filesStructure = await readJSONFile(join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME));
|
|
10
|
+
if (!isWindowsOs())
|
|
11
|
+
return filesStructure;
|
|
12
|
+
return mapKeysPathsToWindowPlatform(filesStructure);
|
|
13
|
+
}
|
|
14
|
+
static async writeThemeFilesStructure(themeDirectory, filesStructure) {
|
|
15
|
+
const filePath = join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME);
|
|
16
|
+
await writeJSONFile(filePath, filesStructure);
|
|
17
|
+
}
|
|
18
|
+
static async getFilesPermissions(themeDirectory) {
|
|
19
|
+
const fileStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDirectory);
|
|
20
|
+
return Object.entries(fileStructure).reduce((acc, [key, value]) => {
|
|
21
|
+
return {
|
|
22
|
+
...acc,
|
|
23
|
+
[key]: value.permissions
|
|
24
|
+
};
|
|
25
|
+
}, {});
|
|
26
|
+
}
|
|
27
|
+
static async validateThemeDirectoryStructure({ checksums, permissions, rootDirectory }) {
|
|
28
|
+
return await validateDirectory({
|
|
29
|
+
permissions,
|
|
30
|
+
checksums,
|
|
31
|
+
rootDirectory
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ThemeChecksumsUtils } from '../checksums/theme_checksums_utils.js';
|
|
2
|
+
import { getAllFilesNamesInside } from '../../../../../utils/fs/fs_utils.js';
|
|
3
|
+
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';
|
|
4
|
+
import { join } from '../../../../../utils/path_utils.js';
|
|
5
|
+
import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
|
|
6
|
+
import { computeFileChecksum } from '../../../../../utils/checksums/checksums_utils.js';
|
|
7
|
+
export class HiddenDirectoryUtils {
|
|
8
|
+
static async ensureFilesInsideThemeMetaDataDirectoryUntouched(themeDirectory) {
|
|
9
|
+
const themeMetadataPath = this.getThemeHiddenDirectoryPath(themeDirectory);
|
|
10
|
+
const checksums = await ThemeChecksumsUtils.getThemeCurrentChecksums(themeDirectory);
|
|
11
|
+
if (!checksums)
|
|
12
|
+
throw new Error(`Checksums file not found in theme metadata directory: ${themeMetadataPath}`);
|
|
13
|
+
if (!(await ThemeChecksumsUtils.verifyThemeChecksums(themeDirectory)))
|
|
14
|
+
throw new Error('Theme checksum file is not valid');
|
|
15
|
+
const filesNames = (await getAllFilesNamesInside(themeMetadataPath)).filter((fileName) => fileName !== THEME_CURRENT_CHECKSUMS_FILE_NAME &&
|
|
16
|
+
fileName !== THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME &&
|
|
17
|
+
fileName !== THEME_INITIAL_CHECKSUMS_FILE_NAME &&
|
|
18
|
+
fileName !== THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME);
|
|
19
|
+
for (const fileName of filesNames) {
|
|
20
|
+
if (fileName === THEME_CURRENT_CHECKSUMS_FILE_NAME)
|
|
21
|
+
continue;
|
|
22
|
+
const fileFullPath = join(themeMetadataPath, fileName);
|
|
23
|
+
const fileRelativePath = join(SHOPER_THEME_METADATA_DIR, fileName);
|
|
24
|
+
const currentChecksum = await computeFileChecksum(fileFullPath);
|
|
25
|
+
if (currentChecksum !== checksums[fileRelativePath]) {
|
|
26
|
+
throw new Error(`File ${fileName} inside theme metadata directory (${themeMetadataPath}) has been modified. You cannot modify files inside .shoper directory`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
static getThemeHiddenDirectoryPath(themeDirectory) {
|
|
31
|
+
return join(themeDirectory, SHOPER_THEME_METADATA_DIR);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const THEME_WORK_URL_MISMATCH_ERROR = 'theme.meta_data.error_work_url_mismatch';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AppError } from '../../../../../cli/class/errors/app/app_error.js';
|
|
2
|
+
import { THEME_WORK_URL_MISMATCH_ERROR } from './theme_meta_data_constants.js';
|
|
3
|
+
export class ThemeMetaDataErrorFactory {
|
|
4
|
+
static createThemeWorkUrlMismatchError(executionUrl, themeWorkUrl) {
|
|
5
|
+
return new AppError({
|
|
6
|
+
code: THEME_WORK_URL_MISMATCH_ERROR,
|
|
7
|
+
message: `Theme work URL "${themeWorkUrl}" does not match execution URL "${executionUrl}".`,
|
|
8
|
+
level: 'error',
|
|
9
|
+
details: {
|
|
10
|
+
executionUrl,
|
|
11
|
+
themeWorkUrl
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { closestFile, readJSONFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
|
|
2
|
+
import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
|
|
3
|
+
import { join } from '../../../../../utils/path_utils.js';
|
|
4
|
+
import { HiddenDirectoryUtils } from '../hidden_directory/hidden_directory_utils.js';
|
|
5
|
+
import { ThemeMetaDataErrorFactory } from './theme_meta_data_error_factory.js';
|
|
6
|
+
export class ThemeMetaDataUtils {
|
|
7
|
+
static async closestThemeRootDirectory(path) {
|
|
8
|
+
return closestFile(SHOPER_THEME_METADATA_DIR, path);
|
|
9
|
+
}
|
|
10
|
+
static getThemeMetadataFilePath(themeDirectory) {
|
|
11
|
+
return join(HiddenDirectoryUtils.getThemeHiddenDirectoryPath(themeDirectory), 'metadata.json');
|
|
12
|
+
}
|
|
13
|
+
static async getThemeMetadata(themeDirectory) {
|
|
14
|
+
const filePath = this.getThemeMetadataFilePath(themeDirectory);
|
|
15
|
+
const metadataFileContent = await readJSONFile(filePath);
|
|
16
|
+
return {
|
|
17
|
+
themeId: String(metadataFileContent.themeId)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
static async updateThemeMetadata(themeDirectory, data) {
|
|
21
|
+
const filePath = this.getThemeMetadataFilePath(themeDirectory);
|
|
22
|
+
await writeJSONFile(filePath, data);
|
|
23
|
+
}
|
|
24
|
+
static async getThemeWorkUrl(themeDirectory) {
|
|
25
|
+
const metadataFilePath = this.getThemeMetadataFilePath(themeDirectory);
|
|
26
|
+
const metadata = await readJSONFile(metadataFilePath);
|
|
27
|
+
return metadata.workUrl || '';
|
|
28
|
+
}
|
|
29
|
+
static async ensureThemeWorkUrlMatch(themeDirectory, executionWorkUrl) {
|
|
30
|
+
const metadataWorkUrl = await this.getThemeWorkUrl(themeDirectory);
|
|
31
|
+
if (metadataWorkUrl !== executionWorkUrl)
|
|
32
|
+
throw ThemeMetaDataErrorFactory.createThemeWorkUrlMismatchError(executionWorkUrl, metadataWorkUrl);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -3,13 +3,13 @@ import { STATUS_CODES } from '@dreamcommerce/star_core';
|
|
|
3
3
|
import { HttpErrorsFactory } from '../../../../../cli/class/errors/http/http_errors_factory.js';
|
|
4
4
|
import { DownloadFileErrorsFactory } from '../../../../../utils/download_file/download_file_errors_factory.js';
|
|
5
5
|
export class ThemesListService {
|
|
6
|
-
httpApi;
|
|
6
|
+
#httpApi;
|
|
7
7
|
constructor(httpApi) {
|
|
8
|
-
this
|
|
8
|
+
this.#httpApi = httpApi;
|
|
9
9
|
}
|
|
10
10
|
async getThemes({ shopUrl }) {
|
|
11
11
|
try {
|
|
12
|
-
const { response } = this
|
|
12
|
+
const { response } = this.#httpApi.getThemes(shopUrl);
|
|
13
13
|
const { data, status } = await response;
|
|
14
14
|
if (status !== STATUS_CODES.ok)
|
|
15
15
|
return;
|
|
@@ -20,6 +20,7 @@ export class ThemesListService {
|
|
|
20
20
|
}));
|
|
21
21
|
}
|
|
22
22
|
catch (err) {
|
|
23
|
+
//TODO to basic class
|
|
23
24
|
switch (err.response?.status) {
|
|
24
25
|
case 403:
|
|
25
26
|
throw HttpErrorsFactory.createForbiddenError();
|
|
@@ -34,4 +35,10 @@ export class ThemesListService {
|
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
}
|
|
38
|
+
async getTheme({ themeId, shopUrl }) {
|
|
39
|
+
const themes = await this.getThemes({ shopUrl });
|
|
40
|
+
if (!themes)
|
|
41
|
+
return;
|
|
42
|
+
return themes.find((theme) => String(theme.skinId) === themeId);
|
|
43
|
+
}
|
|
37
44
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { useApi } from '../../cli/hooks/ensure_cli_initialized_hook.js';
|
|
2
2
|
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
|
|
3
|
-
import {
|
|
3
|
+
import { HiddenDirectoryUtils } from '../features/theme/utils/hidden_directory/hidden_directory_utils.js';
|
|
4
4
|
const ensureThemeMetaDataUntouched = async () => {
|
|
5
5
|
const executionContextApi = useApi(EXECUTION_CONTEXT_API_NAME);
|
|
6
6
|
const executionContext = await executionContextApi.getExecutionContext();
|
|
7
7
|
if (executionContext.type !== EXECUTION_CONTEXTS.theme)
|
|
8
8
|
return;
|
|
9
9
|
try {
|
|
10
|
-
await
|
|
10
|
+
await HiddenDirectoryUtils.ensureFilesInsideThemeMetaDataDirectoryUntouched(executionContext.themeRootDir);
|
|
11
11
|
}
|
|
12
12
|
catch (err) {
|
|
13
13
|
console.error(err);
|
|
@@ -31,6 +31,7 @@ const ensureThemesActionsHook = async ({ Command, argv }) => {
|
|
|
31
31
|
console.error('Unauthorized. Please authorize first.');
|
|
32
32
|
const cliAuthTokensApi = useApi(CLI_AUTH_TOKENS_API_NAME);
|
|
33
33
|
await promptForToken(cliAuthTokensApi);
|
|
34
|
+
//TODO loader
|
|
34
35
|
await themesActionsApi.ensureThemesActions({
|
|
35
36
|
credentials,
|
|
36
37
|
themeId
|
package/build/theme/index.js
CHANGED
|
@@ -5,11 +5,13 @@ import { THEME_COMMANDS_NAME } from './commands/theme_commands_constants.js';
|
|
|
5
5
|
import { ThemePushCommand } from './commands/push/theme_push_command.js';
|
|
6
6
|
import { ThemeShowChangesCommand } from './commands/theme_show_changes_command.js';
|
|
7
7
|
import { ThemeVerifyCommand } from './commands/theme_verify_command.js';
|
|
8
|
+
import { ThemeInfoCommand } from './commands/info/theme_info_command.js';
|
|
8
9
|
export const COMMANDS = {
|
|
9
10
|
[THEME_COMMANDS_NAME.list]: ThemeListCommand,
|
|
10
11
|
[THEME_COMMANDS_NAME.pull]: ThemePullCommand,
|
|
11
12
|
[THEME_COMMANDS_NAME.init]: ThemeInitCommand,
|
|
12
13
|
[THEME_COMMANDS_NAME.push]: ThemePushCommand,
|
|
13
14
|
[THEME_COMMANDS_NAME.showChanges]: ThemeShowChangesCommand,
|
|
14
|
-
[THEME_COMMANDS_NAME.verify]: ThemeVerifyCommand
|
|
15
|
+
[THEME_COMMANDS_NAME.verify]: ThemeVerifyCommand,
|
|
16
|
+
[THEME_COMMANDS_NAME.info]: ThemeInfoCommand
|
|
15
17
|
};
|
package/package.json
CHANGED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { closestFile, getAllFilesNamesInside, readJSONFile, writeJSONFile } from '../../../../utils/fs/fs_utils.js';
|
|
2
|
-
import { SHOPER_THEME_METADATA_DIR } from '../../../constants/directory_contstants.js';
|
|
3
|
-
import { join, mapKeysPathsToWindowPlatform } from '../../../../utils/path_utils.js';
|
|
4
|
-
import { validateDirectory } from '../../../utils/directory_validator/directory_validator_utils.js';
|
|
5
|
-
import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME, THEME_FILES_STRUCTURE_FILE_NAME, THEME_INITIAL_CHECKSUMS_FILE_NAME, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME } from '../theme_constants.js';
|
|
6
|
-
import { ThemeChecksumsUtils } from '../utils/checksums/theme_checksums_utils.js';
|
|
7
|
-
import { computeFileChecksum } from '../../../../utils/checksums/checksums_utils.js';
|
|
8
|
-
import { isWindowsOs } from '../../../../utils/platform_utils.js';
|
|
9
|
-
export class ThemeDirectoryUtils {
|
|
10
|
-
static async closestThemeRootDirectory(path) {
|
|
11
|
-
return closestFile(SHOPER_THEME_METADATA_DIR, path);
|
|
12
|
-
}
|
|
13
|
-
static getThemeMetaDataDirPath(themeDirectory) {
|
|
14
|
-
return join(themeDirectory, SHOPER_THEME_METADATA_DIR);
|
|
15
|
-
}
|
|
16
|
-
static async getThemeDirectoryMetadata(themeDirectory) {
|
|
17
|
-
const filePath = join(this.getThemeMetaDataDirPath(themeDirectory), 'metadata.json');
|
|
18
|
-
const metadataFileContent = await readJSONFile(filePath);
|
|
19
|
-
return {
|
|
20
|
-
themeId: String(metadataFileContent.skin_id)
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
static async getThemeFilesStructure(themeDirectory) {
|
|
24
|
-
const filesStructure = await readJSONFile(join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME));
|
|
25
|
-
if (!isWindowsOs())
|
|
26
|
-
return filesStructure;
|
|
27
|
-
return mapKeysPathsToWindowPlatform(filesStructure);
|
|
28
|
-
}
|
|
29
|
-
static async writeThemeFilesStructure(themeDirectory, filesStructure) {
|
|
30
|
-
const filePath = join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME);
|
|
31
|
-
await writeJSONFile(filePath, filesStructure);
|
|
32
|
-
}
|
|
33
|
-
static async getFilesPermissions(themeDirectory) {
|
|
34
|
-
const fileStructure = await ThemeDirectoryUtils.getThemeFilesStructure(themeDirectory);
|
|
35
|
-
return Object.entries(fileStructure).reduce((acc, [key, value]) => {
|
|
36
|
-
return {
|
|
37
|
-
...acc,
|
|
38
|
-
[key]: value.permissions
|
|
39
|
-
};
|
|
40
|
-
}, {});
|
|
41
|
-
}
|
|
42
|
-
static async validateThemeDirectoryStructure({ checksums, permissions, rootDirectory }) {
|
|
43
|
-
return await validateDirectory({
|
|
44
|
-
permissions,
|
|
45
|
-
checksums,
|
|
46
|
-
rootDirectory
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
static async ensureFilesInsideThemeMetaDataDirectoryUntouched(themeDirectory) {
|
|
50
|
-
const themeMetadataPath = this.getThemeMetaDataDirPath(themeDirectory);
|
|
51
|
-
const checksums = await ThemeChecksumsUtils.getThemeCurrentChecksums(themeDirectory);
|
|
52
|
-
if (!checksums)
|
|
53
|
-
throw new Error(`Checksums file not found in theme metadata directory: ${themeMetadataPath}`);
|
|
54
|
-
if (!(await ThemeChecksumsUtils.verifyThemeChecksums(themeDirectory)))
|
|
55
|
-
throw new Error('Theme checksum file is not valid');
|
|
56
|
-
const filesNames = (await getAllFilesNamesInside(themeMetadataPath)).filter((fileName) => fileName !== THEME_CURRENT_CHECKSUMS_FILE_NAME &&
|
|
57
|
-
fileName !== THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME &&
|
|
58
|
-
fileName !== THEME_INITIAL_CHECKSUMS_FILE_NAME &&
|
|
59
|
-
fileName !== THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME);
|
|
60
|
-
for (const fileName of filesNames) {
|
|
61
|
-
if (fileName === THEME_CURRENT_CHECKSUMS_FILE_NAME)
|
|
62
|
-
continue;
|
|
63
|
-
const fileFullPath = join(themeMetadataPath, fileName);
|
|
64
|
-
const fileRelativePath = join(SHOPER_THEME_METADATA_DIR, fileName);
|
|
65
|
-
const currentChecksum = await computeFileChecksum(fileFullPath);
|
|
66
|
-
if (currentChecksum !== checksums[fileRelativePath]) {
|
|
67
|
-
throw new Error(`File ${fileName} inside theme metadata directory (${themeMetadataPath}) has been modified. You cannot modify files inside .shoper directory`);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
File without changes
|