@shoper/cli 0.1.0-7 → 0.1.0-9
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/auth/api/cli_auth_api.js +15 -2
- package/build/cli/auth/cli_auth_constants.js +6 -0
- package/build/cli/auth/cli_auth_errors_factory.js +10 -0
- package/build/cli/auth/cli_auth_initializer.js +19 -0
- package/build/cli/auth/cli_auth_utils.js +22 -0
- package/build/cli/auth/model/cli_credentials.js +10 -0
- package/build/cli/auth/service/cli_auth_service.js +34 -0
- package/build/cli/auth/tokens/api/cli_auth_tokens_api.js +43 -1
- package/build/cli/auth/tokens/cli_auth_tokens_constants.js +6 -0
- package/build/cli/auth/tokens/cli_auth_tokens_errors_factory.js +10 -0
- package/build/cli/auth/tokens/cli_auth_tokens_initalizer.js +35 -1
- package/build/cli/auth/tokens/cli_auth_tokens_utils.js +26 -0
- package/build/cli/auth/tokens/service/cli_auth_tokens_service.js +84 -4
- package/build/cli/auth/tokens/service/cli_auth_tokens_service_constants.js +2 -0
- package/build/cli/class/base_cli_command.js +3 -0
- package/build/cli/class/errors/app_error/app_error.js +17 -0
- package/build/cli/class/errors/app_error/app_error_constants.js +7 -0
- package/build/cli/class/errors/file_system_errors_factory.js +26 -0
- package/build/cli/class/errors/http_errors_factory.js +21 -0
- package/build/cli/cli_constants.js +1 -0
- package/build/cli/commands/auth/cli_auth_add_token_command.js +32 -0
- package/build/cli/commands/auth/cli_auth_list_tokens_command.js +17 -0
- package/build/cli/commands/auth/cli_auth_remove_token_command.js +37 -0
- package/build/cli/commands/auth/cli_auth_switch_token_command.js +36 -0
- package/build/cli/commands/cli_update_command.js +7 -3
- package/build/cli/commands/commands_constants.js +5 -1
- package/build/cli/commands/files_diff_command.js +174 -0
- package/build/cli/commands/utils/prompt_for_token_utils.js +25 -0
- package/build/cli/core/cli_setup.js +20 -7
- package/build/cli/features/caches/json_cache/json_cache.js +50 -0
- package/build/cli/features/caches/memory_cache.js +6 -0
- package/build/cli/features/data_directory/cli_data_directory_constants.js +0 -1
- package/build/cli/features/data_directory/cli_data_directory_utils.js +3 -4
- package/build/cli/features/data_directory/service/cli_data_directory_service.js +2 -2
- package/build/cli/features/execution_context/execution_context_service.js +3 -5
- package/build/cli/features/http_requester/http_client.js +32 -1
- package/build/cli/features/version/service/cli_version_service.js +2 -4
- package/build/cli/hooks/authorization/ensure_authorization_hook.js +11 -6
- package/build/cli/hooks/authorization/ensure_authorization_hook_constants.js +8 -1
- package/build/index.js +34 -1
- package/build/theme/class/fetch_resources/fetch_resources.js +127 -0
- package/build/theme/class/fetch_resources/fetch_resources_constants.js +2 -0
- package/build/theme/class/fetch_resources/fetch_resources_errors_factory.js +11 -0
- package/build/theme/class/fetch_resources/fetch_resources_utils.js +35 -0
- package/build/theme/commands/theme_commands_constants.js +8 -0
- package/build/theme/commands/theme_init_command.js +53 -0
- package/build/theme/commands/theme_list_command.js +16 -0
- package/build/theme/commands/theme_pull_command.js +126 -0
- package/build/theme/commands/theme_push_command.js +65 -0
- package/build/theme/commands/theme_show_changes_command.js +60 -0
- package/build/theme/commands/theme_verify_command.js +43 -0
- package/build/theme/features/theme/actions/api/theme_actions_api.js +19 -0
- package/build/theme/features/theme/actions/service/theme_actions_service.js +92 -0
- package/build/theme/features/theme/actions/service/theme_actions_service_constants.js +2 -0
- package/build/theme/features/theme/actions/theme_actions_constants.js +15 -0
- package/build/theme/features/theme/actions/theme_actions_initializer.js +27 -0
- package/build/theme/features/theme/actions/theme_actions_utils.js +8 -0
- package/build/theme/features/theme/directory/theme_directory_utils.js +61 -14
- package/build/theme/features/theme/fetch/api/theme_fetch_api.js +16 -0
- package/build/theme/features/theme/fetch/http/theme_fetch_http_api.js +18 -0
- package/build/theme/features/theme/fetch/service/theme_fetch_service.js +113 -0
- package/build/theme/features/theme/fetch/theme_fetch_constants.js +7 -0
- package/build/theme/features/theme/fetch/theme_fetch_initializer.js +23 -0
- package/build/theme/features/theme/init/api/theme_init_api.js +13 -0
- package/build/theme/features/theme/init/http/theme_init_http_api.js +18 -0
- package/build/theme/features/theme/init/service/theme_init_service.js +28 -0
- package/build/theme/features/theme/init/theme_init_constants.js +2 -0
- package/build/theme/features/theme/init/theme_init_initializer.js +22 -0
- package/build/theme/features/theme/merge/api/theme_merge_api.js +28 -0
- package/build/theme/features/theme/merge/service/theme_merge_service.js +56 -0
- package/build/theme/features/theme/merge/theme_merge_constants.js +9 -0
- package/build/theme/features/theme/merge/theme_merge_initializer.js +18 -0
- package/build/theme/features/theme/publish/theme_publish_constants.js +2 -0
- package/build/theme/features/theme/publish/theme_publish_utils.js +7 -0
- package/build/theme/features/theme/push/api/theme_push_api.js +13 -0
- package/build/theme/features/theme/push/http_api/theme_push_http_api.js +21 -0
- package/build/theme/features/theme/push/service/theme_push_service.js +200 -0
- package/build/theme/features/theme/push/service/theme_push_service_constants.js +1 -0
- package/build/theme/features/theme/push/theme_push_constants.js +4 -0
- package/build/theme/features/theme/push/theme_push_errors_factory.js +41 -0
- package/build/theme/features/theme/push/theme_push_initializer.js +27 -0
- package/build/theme/features/theme/theme_constants.js +6 -0
- package/build/theme/features/theme/utils/checksums/theme_checksums_error_factory.js +10 -0
- package/build/theme/features/theme/utils/checksums/theme_checksums_utils.js +94 -0
- package/build/theme/features/theme/utils/directories/theme_resources_with_id_directory_utils.js +80 -0
- package/build/theme/features/theme/utils/theme_images_utils.js +30 -0
- package/build/theme/features/themes/list/api/themes_list_api.js +13 -0
- package/build/theme/features/themes/{http/shoper_themes_http_api.js → list/http/themes_list_http_api.js} +3 -2
- package/build/theme/features/themes/{model/theme_metadata.js → list/model/theme_list_metadata.js} +3 -1
- package/build/theme/features/themes/list/services/themes_list_service.js +37 -0
- package/build/theme/features/themes/list/themes_list_constants.js +2 -0
- package/build/theme/features/themes/list/themes_list_initializer.js +20 -0
- package/build/theme/hooks/ensure_theme_meta_data_untouched.js +17 -0
- package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date.js +21 -0
- package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js +7 -0
- package/build/theme/hooks/themes_actions/ensure_themes_actions_hook.js +45 -0
- package/build/theme/hooks/themes_actions/ensure_themes_actions_hook_constants.js +7 -0
- package/build/theme/index.js +13 -4
- package/build/theme/utils/directory_validator/directory_validator_constants.js +13 -0
- package/build/theme/utils/directory_validator/directory_validator_utils.js +185 -0
- package/build/utils/checksums/checksums_utils.js +95 -0
- package/build/utils/checksums/checksums_utils_constants.js +1 -0
- package/build/utils/date_utils.js +3 -0
- package/build/utils/download_file/download_file_errors_factory.js +9 -0
- package/build/utils/download_file/download_file_utils.js +46 -0
- package/build/utils/fs/errors/stream_read_error.js +11 -0
- package/build/utils/fs/errors/stream_write_error.js +11 -0
- package/build/utils/fs/fs_utils.js +155 -0
- package/build/utils/http_utils.js +10 -0
- package/build/utils/path_utils.js +37 -0
- package/build/utils/stream_transforms/json_indent_transform.js +21 -0
- package/build/utils/url_utils.js +9 -0
- package/build/utils/zip/create_zip_utils.js +80 -0
- package/build/utils/zip/errors/create_zip_error.js +11 -0
- package/build/utils/zip/errors/open_zip_error.js +11 -0
- package/build/utils/zip/extract_zip_utils.js +104 -0
- package/oclif.config.js +4 -2
- package/package.json +36 -7
- package/build/cli/hooks/migration/migration_hook.js +0 -4
- package/build/theme/commands/list_command.js +0 -12
- package/build/theme/commands/pull_command.js +0 -24
- package/build/theme/features/theme/directory/theme_directories_utils.js +0 -10
- package/build/theme/features/theme/pull/api/shoper_theme_pull_api.js +0 -9
- package/build/theme/features/theme/pull/http/shoper_theme_pull_http_api.js +0 -14
- package/build/theme/features/theme/pull/service/shoper_theme_pull_service.js +0 -68
- package/build/theme/features/theme/pull/shoper_theme_pull_initializer.js +0 -22
- package/build/theme/features/themes/api/shoper_themes_api.js +0 -14
- package/build/theme/features/themes/services/shoper_themes_service.js +0 -15
- package/build/theme/features/themes/shoper_themes_constants.js +0 -2
- package/build/theme/features/themes/shoper_themes_initalizer.js +0 -20
- package/build/utils/fs.js +0 -44
- package/build/utils/path.js +0 -13
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { join } from '../../../utils/path_utils.js';
|
|
2
|
+
import { RESOURCES_FILE_NAME } from './fetch_resources_constants.js';
|
|
3
|
+
import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
|
|
4
|
+
import { fileExists, readJSONFile } from '../../../utils/fs/fs_utils.js';
|
|
5
|
+
export const isResourceObject = (resource) => {
|
|
6
|
+
return typeof resource === 'object' && resource !== null && 'url' in resource && typeof resource.url === 'string';
|
|
7
|
+
};
|
|
8
|
+
export const getResourcesPath = (root) => {
|
|
9
|
+
return join(root, SHOPER_THEME_METADATA_DIR, RESOURCES_FILE_NAME);
|
|
10
|
+
};
|
|
11
|
+
export const getResources = async (root) => {
|
|
12
|
+
const resourcesPath = getResourcesPath(root);
|
|
13
|
+
if (!(await fileExists(resourcesPath))) {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
return await readJSONFile(resourcesPath);
|
|
17
|
+
};
|
|
18
|
+
export const mapResourcesToTree = (resources, separator = '/') => {
|
|
19
|
+
const tree = {};
|
|
20
|
+
Object.entries(resources).forEach(([path, value]) => {
|
|
21
|
+
const parts = path.split(separator).filter(Boolean);
|
|
22
|
+
let currentLevel = tree;
|
|
23
|
+
parts.filter(Boolean).forEach((part, index) => {
|
|
24
|
+
if (index === parts.length - 1) {
|
|
25
|
+
currentLevel[part] = value;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
if (!currentLevel[part])
|
|
29
|
+
currentLevel[part] = {};
|
|
30
|
+
currentLevel = currentLevel[part];
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
return tree;
|
|
35
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { BaseThemeCommand } from '../class/base_theme_command.js';
|
|
2
|
+
import { Args } from '@oclif/core';
|
|
3
|
+
import { THEME_INIT_API_NAME } from '../features/theme/init/theme_init_constants.js';
|
|
4
|
+
import { CLI_AUTH_API_NAME } from '../../cli/auth/cli_auth_constants.js';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
|
|
7
|
+
import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
|
|
8
|
+
export class ThemeInitCommand extends BaseThemeCommand {
|
|
9
|
+
static description = 'Clone a theme with provided ID from the shop';
|
|
10
|
+
static args = {
|
|
11
|
+
id: Args.string({
|
|
12
|
+
description: 'Theme id',
|
|
13
|
+
name: 'id',
|
|
14
|
+
required: true,
|
|
15
|
+
type: 'string'
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const data = await this.parse(ThemeInitCommand);
|
|
20
|
+
const { args } = data;
|
|
21
|
+
const themeId = args.id;
|
|
22
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
23
|
+
const themeInitApi = this.getApi(THEME_INIT_API_NAME);
|
|
24
|
+
const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
|
|
25
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
26
|
+
const credentials = cliAuthApi.getCredentials();
|
|
27
|
+
if (!credentials)
|
|
28
|
+
this.error('Credentials not found. Please authorize first.');
|
|
29
|
+
const copyAction = themeActionsApi.getThemeAction({
|
|
30
|
+
actionType: THEME_ACTIONS_TYPES.copy,
|
|
31
|
+
themeId,
|
|
32
|
+
credentials
|
|
33
|
+
});
|
|
34
|
+
if (!copyAction)
|
|
35
|
+
this.error(`You cannot init theme with id ${themeId}`);
|
|
36
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
37
|
+
if (executionContext.type === EXECUTION_CONTEXTS.theme)
|
|
38
|
+
this.error('You cannot run this command in the theme execution context');
|
|
39
|
+
const spinner = ora('Creating theme...').start();
|
|
40
|
+
try {
|
|
41
|
+
await themeInitApi.initTheme({ action: copyAction, credentials });
|
|
42
|
+
spinner.stopAndPersist({
|
|
43
|
+
symbol: '\u2713',
|
|
44
|
+
text: 'Theme successfully created!'
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
// TODO handle error
|
|
49
|
+
spinner.clear();
|
|
50
|
+
this.error(String(err));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseThemeCommand } from '../class/base_theme_command.js';
|
|
2
|
+
import { THEMES_LIST_API_NAME } from '../features/themes/list/themes_list_constants.js';
|
|
3
|
+
import { CLI_AUTH_API_NAME } from '../../cli/auth/cli_auth_constants.js';
|
|
4
|
+
export class ThemeListCommand extends BaseThemeCommand {
|
|
5
|
+
static description = 'List all shop themes';
|
|
6
|
+
async run() {
|
|
7
|
+
const ThemesListApi = this.getApi(THEMES_LIST_API_NAME);
|
|
8
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
9
|
+
const credentials = cliAuthApi.getCredentials();
|
|
10
|
+
if (!credentials)
|
|
11
|
+
this.error('Credentials not found. Please authorize first.');
|
|
12
|
+
const themes = await ThemesListApi.getThemes(credentials);
|
|
13
|
+
//TODO UI
|
|
14
|
+
this.log('themes', themes);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { BaseThemeCommand } from '../class/base_theme_command.js';
|
|
3
|
+
import { THEME_FETCH_API_NAME, THEME_FETCH_TYPES } from '../features/theme/fetch/theme_fetch_constants.js';
|
|
4
|
+
import { CLI_AUTH_API_NAME } from '../../cli/auth/cli_auth_constants.js';
|
|
5
|
+
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import ora from 'ora';
|
|
8
|
+
import { THEME_MERGE_API_NAME } from '../features/theme/merge/theme_merge_constants.js';
|
|
9
|
+
import tmp from 'tmp-promise';
|
|
10
|
+
import { join } from '../../utils/path_utils.js';
|
|
11
|
+
import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
|
|
12
|
+
import process from 'node:process';
|
|
13
|
+
import { ThemeResourcesWithIdDirectoryUtils } from '../features/theme/utils/directories/theme_resources_with_id_directory_utils.js';
|
|
14
|
+
export class ThemePullCommand extends BaseThemeCommand {
|
|
15
|
+
static description = 'Pull theme from shop';
|
|
16
|
+
static args = {
|
|
17
|
+
id: Args.string({
|
|
18
|
+
description: 'Theme id',
|
|
19
|
+
name: 'id',
|
|
20
|
+
type: 'string'
|
|
21
|
+
})
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
type: Flags.string({
|
|
25
|
+
default: THEME_FETCH_TYPES.full,
|
|
26
|
+
description: 'Type of theme to pull',
|
|
27
|
+
options: Object.values(THEME_FETCH_TYPES)
|
|
28
|
+
})
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const data = await this.parse(ThemePullCommand);
|
|
32
|
+
const { args, flags } = data;
|
|
33
|
+
const themeId = args.id;
|
|
34
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
35
|
+
const themeFetchApi = this.getApi(THEME_FETCH_API_NAME);
|
|
36
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
37
|
+
const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
|
|
38
|
+
const credentials = cliAuthApi.getCredentials();
|
|
39
|
+
if (!credentials)
|
|
40
|
+
this.error('Credentials not found. Please authorize first.');
|
|
41
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
42
|
+
const spinner = ora('Pulling theme...').start();
|
|
43
|
+
try {
|
|
44
|
+
if (executionContext.type !== EXECUTION_CONTEXTS.theme) {
|
|
45
|
+
if (themeId === undefined)
|
|
46
|
+
this.error('To run this command outside theme context you must provide theme id.');
|
|
47
|
+
const pullAction = themeActionsApi.getThemeAction({
|
|
48
|
+
actionType: THEME_ACTIONS_TYPES.pull,
|
|
49
|
+
themeId: themeId,
|
|
50
|
+
credentials
|
|
51
|
+
});
|
|
52
|
+
await themeFetchApi.fetchTheme({
|
|
53
|
+
credentials,
|
|
54
|
+
action: pullAction,
|
|
55
|
+
config: {
|
|
56
|
+
dist: process.cwd(),
|
|
57
|
+
fetchType: flags.type
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
spinner.stopAndPersist({
|
|
61
|
+
symbol: '\u2713',
|
|
62
|
+
text: 'Theme successfully pulled!'
|
|
63
|
+
});
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const _themeId = themeId ? themeId : executionContext.themeId;
|
|
67
|
+
const pullAction = themeActionsApi.getThemeAction({
|
|
68
|
+
actionType: THEME_ACTIONS_TYPES.pull,
|
|
69
|
+
themeId: _themeId,
|
|
70
|
+
credentials
|
|
71
|
+
});
|
|
72
|
+
if (executionContext.themeId !== _themeId) {
|
|
73
|
+
this.error('You cannot pull a theme in the theme execution context that is not the current theme.');
|
|
74
|
+
}
|
|
75
|
+
const themeMergeApi = this.getApi(THEME_MERGE_API_NAME);
|
|
76
|
+
const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
|
|
77
|
+
const { name } = await themeFetchApi.fetchTheme({
|
|
78
|
+
credentials,
|
|
79
|
+
action: pullAction,
|
|
80
|
+
config: {
|
|
81
|
+
fetchType: flags.type,
|
|
82
|
+
dist: tmpDir
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
await ThemeResourcesWithIdDirectoryUtils.updateDirectoryNamesOfResourcesWithIdAccordingToLocalThemeNames(join(executionContext.themeRootDir, 'modules'), executionContext.themeRootDir, join(tmpDir, name, 'modules'), join(tmpDir, name));
|
|
86
|
+
spinner.stopAndPersist({
|
|
87
|
+
symbol: '\u2713',
|
|
88
|
+
text: 'Theme successfully pulled!'
|
|
89
|
+
});
|
|
90
|
+
this.log('\n');
|
|
91
|
+
const changes = await themeMergeApi.getChangesBetweenThemes(join(tmpDir, name), executionContext.themeRootDir);
|
|
92
|
+
console.log('changes', changes);
|
|
93
|
+
console.log('is modified', await themeMergeApi.hasThemeBeenModified(executionContext.themeRootDir));
|
|
94
|
+
if (await themeMergeApi.hasThemeBeenModified(executionContext.themeRootDir)) {
|
|
95
|
+
console.log('changes', changes);
|
|
96
|
+
this.log('You have unpublished changes in your theme...\n');
|
|
97
|
+
this.log('The following files will be changed:\n');
|
|
98
|
+
changes.forEach(([action, name]) => {
|
|
99
|
+
console.log(name, ' - ', action);
|
|
100
|
+
});
|
|
101
|
+
console.log('\n');
|
|
102
|
+
const { proceed } = await inquirer.prompt([
|
|
103
|
+
{
|
|
104
|
+
type: 'confirm',
|
|
105
|
+
name: 'proceed',
|
|
106
|
+
message: 'Do you want to continue and overwrite local changes?',
|
|
107
|
+
default: true
|
|
108
|
+
}
|
|
109
|
+
]);
|
|
110
|
+
if (!proceed)
|
|
111
|
+
this.log('Pull operation cancelled. Your local changes are safe.');
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
await themeMergeApi.applyChanges(join(tmpDir, name), executionContext.themeRootDir, changes);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.log('err', err);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
// TODO handle error
|
|
122
|
+
spinner.clear();
|
|
123
|
+
this.error(String(err));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
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 { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
|
|
5
|
+
import { THEME_PUSH_API_NAME } from '../features/theme/push/theme_push_constants.js';
|
|
6
|
+
import { ThemeDirectoryUtils } from '../features/theme/directory/theme_directory_utils.js';
|
|
7
|
+
import { mapChecksumToTree } from '../../utils/checksums/checksums_utils.js';
|
|
8
|
+
import { mapToPermissionsTree } from '../utils/directory_validator/directory_validator_utils.js';
|
|
9
|
+
import { ThemeChecksumsUtils } from '../features/theme/utils/checksums/theme_checksums_utils.js';
|
|
10
|
+
export class ThemePushCommand extends BaseThemeCommand {
|
|
11
|
+
static description = 'Upload theme files to the shop';
|
|
12
|
+
async run() {
|
|
13
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
14
|
+
const themePushApi = this.getApi(THEME_PUSH_API_NAME);
|
|
15
|
+
const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
|
|
16
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
17
|
+
const credentials = cliAuthApi.getCredentials();
|
|
18
|
+
if (!credentials)
|
|
19
|
+
this.error('Credentials not found. Please authorize first.');
|
|
20
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
21
|
+
if (executionContext.type !== EXECUTION_CONTEXTS.theme)
|
|
22
|
+
this.error('You cannot run this command outside theme context.');
|
|
23
|
+
const pushAction = themeActionsApi.getThemeAction({
|
|
24
|
+
actionType: THEME_ACTIONS_TYPES.push,
|
|
25
|
+
themeId: executionContext.themeId,
|
|
26
|
+
credentials
|
|
27
|
+
});
|
|
28
|
+
if (!pushAction)
|
|
29
|
+
this.error(`You cannot push theme with id ${executionContext.themeId}`);
|
|
30
|
+
const checksums = await ThemeChecksumsUtils.getThemeCurrentChecksums(executionContext.themeRootDir);
|
|
31
|
+
const permissions = await ThemeDirectoryUtils.getFilesPermissions(executionContext.themeRootDir);
|
|
32
|
+
if (!checksums || !permissions)
|
|
33
|
+
this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
|
|
34
|
+
try {
|
|
35
|
+
const validationResult = await ThemeDirectoryUtils.validateThemeDirectoryStructure({
|
|
36
|
+
checksums: mapChecksumToTree(checksums),
|
|
37
|
+
permissions: mapToPermissionsTree(permissions),
|
|
38
|
+
rootDirectory: executionContext.themeRootDir
|
|
39
|
+
});
|
|
40
|
+
//TODO jak wysyla folder z nazwa posiadajaco \ na windows, wychodzimy
|
|
41
|
+
//TODO validacja folderów przed pushem
|
|
42
|
+
if (!validationResult.isValid) {
|
|
43
|
+
validationResult.unpermittedActions.forEach((unpermittedAction) => {
|
|
44
|
+
console.log('Unpermitted action:', unpermittedAction.type, unpermittedAction.path);
|
|
45
|
+
});
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const filesStructure = await ThemeDirectoryUtils.getThemeFilesStructure(executionContext.themeRootDir);
|
|
49
|
+
if (!filesStructure)
|
|
50
|
+
this.error('Theme files structure not found. Please ensure you are in the correct theme directory.');
|
|
51
|
+
await themePushApi.push({
|
|
52
|
+
credentials,
|
|
53
|
+
checksums,
|
|
54
|
+
filesStructure,
|
|
55
|
+
pushAction,
|
|
56
|
+
executionContext
|
|
57
|
+
});
|
|
58
|
+
console.log('Theme pushed successfully!');
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.log(err);
|
|
62
|
+
this.error(`Failed to push theme: ${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { BaseThemeCommand } from '../class/base_theme_command.js';
|
|
2
|
+
import { CLI_AUTH_API_NAME } from '../../cli/auth/cli_auth_constants.js';
|
|
3
|
+
import { THEME_MERGE_API_NAME } from '../features/theme/merge/theme_merge_constants.js';
|
|
4
|
+
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
|
|
5
|
+
import { THEME_FETCH_API_NAME, THEME_FETCH_TYPES } from '../features/theme/fetch/theme_fetch_constants.js';
|
|
6
|
+
import tmp from 'tmp-promise';
|
|
7
|
+
import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
|
|
8
|
+
import { join } from '../../utils/path_utils.js';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
export class ThemeShowChangesCommand extends BaseThemeCommand {
|
|
11
|
+
static description = 'Show local theme changes';
|
|
12
|
+
async run() {
|
|
13
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
14
|
+
const credentials = cliAuthApi.getCredentials();
|
|
15
|
+
if (!credentials)
|
|
16
|
+
this.error('Credentials not found. Please authorize first.');
|
|
17
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
18
|
+
const themeMergeApi = this.getApi(THEME_MERGE_API_NAME);
|
|
19
|
+
const themeFetchApi = this.getApi(THEME_FETCH_API_NAME);
|
|
20
|
+
const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
|
|
21
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
22
|
+
if (executionContext.type !== EXECUTION_CONTEXTS.theme)
|
|
23
|
+
this.error('You cannot run this command outside theme context.');
|
|
24
|
+
const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
|
|
25
|
+
const pullAction = themeActionsApi.getThemeAction({
|
|
26
|
+
actionType: THEME_ACTIONS_TYPES.pull,
|
|
27
|
+
themeId: executionContext.themeId,
|
|
28
|
+
credentials
|
|
29
|
+
});
|
|
30
|
+
const spinner = ora('Pulling theme...').start();
|
|
31
|
+
try {
|
|
32
|
+
spinner.stopAndPersist({
|
|
33
|
+
symbol: '\u2713',
|
|
34
|
+
text: 'Calculating theme changes...'
|
|
35
|
+
});
|
|
36
|
+
const { name } = await themeFetchApi.fetchTheme({
|
|
37
|
+
credentials,
|
|
38
|
+
action: pullAction,
|
|
39
|
+
config: {
|
|
40
|
+
fetchType: THEME_FETCH_TYPES.full,
|
|
41
|
+
dist: tmpDir
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
const changes = await themeMergeApi.getChangesBetweenThemes(executionContext.themeRootDir, join(tmpDir, name));
|
|
45
|
+
spinner.clear();
|
|
46
|
+
if (!changes.length) {
|
|
47
|
+
this.log('No changes found in your theme.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.log('Changes:\n');
|
|
51
|
+
changes.forEach(([action, name]) => {
|
|
52
|
+
console.log(name, ' - ', action);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
//TODO
|
|
57
|
+
this.error(`Failed to fetch theme changes: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { ThemeDirectoryUtils } from '../features/theme/directory/theme_directory_utils.js';
|
|
5
|
+
import { mapToPermissionsTree } from '../utils/directory_validator/directory_validator_utils.js';
|
|
6
|
+
import { mapChecksumToTree } from '../../utils/checksums/checksums_utils.js';
|
|
7
|
+
import { ThemeChecksumsUtils } from '../features/theme/utils/checksums/theme_checksums_utils.js';
|
|
8
|
+
export class ThemeVerifyCommand extends BaseThemeCommand {
|
|
9
|
+
static description = 'Verify theme files structure';
|
|
10
|
+
async run() {
|
|
11
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
12
|
+
const credentials = cliAuthApi.getCredentials();
|
|
13
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
14
|
+
if (!credentials)
|
|
15
|
+
this.error('Credentials not found. Please authorize first.');
|
|
16
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
17
|
+
if (executionContext.type !== EXECUTION_CONTEXTS.theme)
|
|
18
|
+
this.error('You cannot run this command outside theme context.');
|
|
19
|
+
const checksums = await ThemeChecksumsUtils.getThemeInitialChecksums(executionContext.themeRootDir);
|
|
20
|
+
const permissions = await ThemeDirectoryUtils.getFilesPermissions(executionContext.themeRootDir);
|
|
21
|
+
if (!checksums || !permissions)
|
|
22
|
+
this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
|
|
23
|
+
try {
|
|
24
|
+
const validationResult = await ThemeDirectoryUtils.validateThemeDirectoryStructure({
|
|
25
|
+
checksums: mapChecksumToTree(checksums),
|
|
26
|
+
permissions: mapToPermissionsTree(permissions),
|
|
27
|
+
rootDirectory: executionContext.themeRootDir
|
|
28
|
+
});
|
|
29
|
+
if (validationResult.isValid) {
|
|
30
|
+
this.log('Theme structure is valid.');
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
this.log('Verification failed.\n');
|
|
34
|
+
validationResult.unpermittedActions.forEach((action) => {
|
|
35
|
+
this.log(`Unpermitted action: ${action.type} path: ${action.path}`);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
this.error(`Error during theme verification: ${err instanceof Error ? err.message : String(err)}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FeatureApi } from '@dreamcommerce/star_core';
|
|
2
|
+
import { THEME_ACTIONS_API_NAME } from '../theme_actions_constants.js';
|
|
3
|
+
export class ThemeActionsApi extends FeatureApi {
|
|
4
|
+
moduleName = THEME_ACTIONS_API_NAME;
|
|
5
|
+
#service;
|
|
6
|
+
constructor(service) {
|
|
7
|
+
super();
|
|
8
|
+
this.#service = service;
|
|
9
|
+
}
|
|
10
|
+
getThemeAction(props) {
|
|
11
|
+
return this.#service.getThemeAction(props);
|
|
12
|
+
}
|
|
13
|
+
async ensureThemesActions(props) {
|
|
14
|
+
return this.#service.ensureThemesActions(props);
|
|
15
|
+
}
|
|
16
|
+
clearThemesActions(props) {
|
|
17
|
+
return this.#service.clearThemesActions(props);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { STORE_THEMES_ACTIONS_TTL } from './theme_actions_service_constants.js';
|
|
2
|
+
import { AppError } from '../../../../../cli/class/errors/app_error/app_error.js';
|
|
3
|
+
import { getNMinutesFromNow } from '../../../../../utils/date_utils.js';
|
|
4
|
+
import { computeChecksum } from '../../../../../utils/checksums/checksums_utils.js';
|
|
5
|
+
export class ThemeActionsService {
|
|
6
|
+
#themesListApi;
|
|
7
|
+
#themesActionsStore;
|
|
8
|
+
constructor({ themesListApi, themesActionsStore }) {
|
|
9
|
+
this.#themesListApi = themesListApi;
|
|
10
|
+
this.#themesActionsStore = themesActionsStore;
|
|
11
|
+
}
|
|
12
|
+
getThemeAction({ actionType, themeId, credentials }) {
|
|
13
|
+
const storeActions = this.#themesActionsStore.get(this._getKey(credentials.shopUrl));
|
|
14
|
+
if (!storeActions)
|
|
15
|
+
throw new AppError({
|
|
16
|
+
code: 'theme_actions_not_found',
|
|
17
|
+
message: `Theme actions for store ${credentials.shopUrl} not found.`
|
|
18
|
+
});
|
|
19
|
+
const actions = storeActions.actions[themeId];
|
|
20
|
+
if (!actions)
|
|
21
|
+
throw new AppError({
|
|
22
|
+
code: 'theme_action_not_found',
|
|
23
|
+
message: `Theme action for theme ${themeId} not found.`
|
|
24
|
+
});
|
|
25
|
+
return actions[actionType];
|
|
26
|
+
}
|
|
27
|
+
async ensureThemesActions({ credentials, themeId }) {
|
|
28
|
+
if (!themeId)
|
|
29
|
+
return;
|
|
30
|
+
const actionsData = this.#themesActionsStore.get(this._getKey(credentials.shopUrl));
|
|
31
|
+
if (actionsData &&
|
|
32
|
+
this._isValidThemesActions(actionsData) &&
|
|
33
|
+
actionsData.actions[themeId] &&
|
|
34
|
+
!this._hasThemesActionsExpired(this._getKey(credentials.shopUrl))) {
|
|
35
|
+
return Promise.resolve();
|
|
36
|
+
}
|
|
37
|
+
this._cleanExpiredActions();
|
|
38
|
+
const actions = await this._fetchStoreThemesActions(credentials);
|
|
39
|
+
this.#themesActionsStore.set(this._getKey(credentials.shopUrl), {
|
|
40
|
+
actions,
|
|
41
|
+
expires: getNMinutesFromNow(STORE_THEMES_ACTIONS_TTL)
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
_hasThemesActionsExpired(key) {
|
|
45
|
+
const expires = this.#themesActionsStore.get(key)?.expires;
|
|
46
|
+
if (typeof expires !== 'number')
|
|
47
|
+
return true;
|
|
48
|
+
return expires - Date.now() < 0;
|
|
49
|
+
}
|
|
50
|
+
_isValidThemesActions(actions) {
|
|
51
|
+
//TODO schema
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
async _fetchStoreThemesActions(credentials) {
|
|
55
|
+
const themes = await this.#themesListApi.getThemes(credentials);
|
|
56
|
+
if (!Array.isArray(themes))
|
|
57
|
+
throw new AppError({
|
|
58
|
+
code: 'invalid_themes_list',
|
|
59
|
+
message: 'Error while fetching themes list.'
|
|
60
|
+
});
|
|
61
|
+
return themes.reduce((acc, theme) => {
|
|
62
|
+
return {
|
|
63
|
+
...acc,
|
|
64
|
+
[theme.skinId]: Object.entries(theme.links).reduce((acc, [actionType, data]) => {
|
|
65
|
+
return {
|
|
66
|
+
...acc,
|
|
67
|
+
[actionType]: {
|
|
68
|
+
data: data,
|
|
69
|
+
actionType
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, {})
|
|
73
|
+
};
|
|
74
|
+
}, {});
|
|
75
|
+
}
|
|
76
|
+
clearThemesActions({ credentials }) {
|
|
77
|
+
const key = this._getKey(credentials.shopUrl);
|
|
78
|
+
this.#themesActionsStore.remove(key);
|
|
79
|
+
}
|
|
80
|
+
_getKey(shopUrl) {
|
|
81
|
+
return computeChecksum(shopUrl);
|
|
82
|
+
}
|
|
83
|
+
_cleanExpiredActions() {
|
|
84
|
+
const keys = this.#themesActionsStore.getKeys();
|
|
85
|
+
keys.forEach((key) => {
|
|
86
|
+
const actionsData = this.#themesActionsStore.get(key);
|
|
87
|
+
if (!actionsData || this._hasThemesActionsExpired(key)) {
|
|
88
|
+
this.#themesActionsStore.remove(key);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const THEMES_ACTIONS_FILE_NAME = 'themes_actions';
|
|
2
|
+
export const THEME_ACTIONS_FEATURE_NAME = 'ThemeActions';
|
|
3
|
+
export const THEME_ACTIONS_API_NAME = 'ThemeActionsApi';
|
|
4
|
+
export const THEME_ACTIONS_TYPES = {
|
|
5
|
+
copy: 'copy',
|
|
6
|
+
push: 'push',
|
|
7
|
+
pull: 'pull',
|
|
8
|
+
delete: 'delete',
|
|
9
|
+
publish_form: 'publish_form',
|
|
10
|
+
publish: 'publish'
|
|
11
|
+
};
|
|
12
|
+
export const THEME_ACTION_DATA_TYPE = {
|
|
13
|
+
file: 'file',
|
|
14
|
+
zip: 'zip'
|
|
15
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AsyncFeatureInitializer, FEATURE_CORES_TYPES } from '@dreamcommerce/star_core';
|
|
2
|
+
import { THEMES_LIST_API_NAME } from '../../themes/list/themes_list_constants.js';
|
|
3
|
+
import { JsonCache } from '../../../../cli/features/caches/json_cache/json_cache.js';
|
|
4
|
+
import { CLI_DATA_DIRECTORY_API_NAME } from '../../../../cli/features/data_directory/cli_data_directory_constants.js';
|
|
5
|
+
import { THEME_ACTIONS_FEATURE_NAME, THEMES_ACTIONS_FILE_NAME } from './theme_actions_constants.js';
|
|
6
|
+
import { ThemeActionsApi } from './api/theme_actions_api.js';
|
|
7
|
+
import { ThemeActionsService } from './service/theme_actions_service.js';
|
|
8
|
+
export class ThemeActionsInitializer extends AsyncFeatureInitializer {
|
|
9
|
+
static featureName = THEME_ACTIONS_FEATURE_NAME;
|
|
10
|
+
async init() {
|
|
11
|
+
const themesListApi = this.getApiSync(THEMES_LIST_API_NAME);
|
|
12
|
+
const cliDataDirectoryApi = this.getApiSync(CLI_DATA_DIRECTORY_API_NAME);
|
|
13
|
+
const themesActionsStore = new JsonCache({
|
|
14
|
+
path: cliDataDirectoryApi.getDataDirectoryFullPath(),
|
|
15
|
+
configFileMode: 0o600,
|
|
16
|
+
name: THEMES_ACTIONS_FILE_NAME
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
cores: [
|
|
20
|
+
{
|
|
21
|
+
type: FEATURE_CORES_TYPES.api,
|
|
22
|
+
instance: new ThemeActionsApi(new ThemeActionsService({ themesListApi, themesActionsStore }))
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const getFilesGlobsThatMatchesActionName = (actionType, actionValue, filesStructure) => {
|
|
2
|
+
return Object.entries(filesStructure).reduce((acc, [filePath, fileStructureItem]) => {
|
|
3
|
+
if (fileStructureItem._links?.[actionType] === actionValue) {
|
|
4
|
+
return [...acc, filePath];
|
|
5
|
+
}
|
|
6
|
+
return acc;
|
|
7
|
+
}, []);
|
|
8
|
+
};
|