@shoper/cli 0.2.0 → 0.2.1-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/build/cli/class/base_command.js +14 -0
  2. package/build/cli/commands/auth/cli_auth_remove_token_command.js +1 -3
  3. package/build/cli/commands/auth/cli_auth_switch_token_command.js +1 -2
  4. package/build/cli/commands/commands_constants.js +5 -4
  5. package/build/cli/core/cli_setup.js +33 -18
  6. package/build/cli/hooks/authorization/ensure_authorization_hook_constants.js +0 -1
  7. package/build/theme/class/archive/theme_archive.js +44 -0
  8. package/build/theme/class/archive/theme_archive_errors_factory.js +11 -0
  9. package/build/theme/class/checksums/theme_checksums.js +3 -7
  10. package/build/theme/class/files_upload/theme_files_upload.js +61 -0
  11. package/build/theme/class/files_upload/theme_files_upload_http_api.js +23 -0
  12. package/build/theme/commands/delete/theme_delete_command.js +4 -18
  13. package/build/theme/commands/info/theme_info_command.js +3 -18
  14. package/build/theme/commands/init/theme_init_command.js +4 -17
  15. package/build/theme/commands/publish/theme_publish_command.js +4 -20
  16. package/build/theme/commands/pull/theme_pull_command.js +6 -26
  17. package/build/theme/commands/push/theme_push_command.js +17 -39
  18. package/build/theme/commands/theme_commands_constants.js +9 -9
  19. package/build/theme/commands/theme_verify_command.js +59 -18
  20. package/build/theme/commands/ui/theme_error.js +29 -0
  21. package/build/theme/features/theme/actions/theme_actions_constants.js +2 -1
  22. package/build/theme/features/theme/actions/theme_actions_utils.js +41 -1
  23. package/build/theme/features/theme/init/theme_init_initializer.js +3 -3
  24. package/build/theme/features/theme/push/service/theme_push_service.js +42 -164
  25. package/build/theme/features/theme/push/theme_push_initializer.js +1 -4
  26. package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +10 -0
  27. package/build/theme/features/theme/utils/files_structure/theme_files_structure_utils.js +38 -2
  28. package/build/theme/features/theme/utils/theme_images_utils.js +0 -18
  29. package/build/theme/features/theme/verify/api/theme_verify_api.js +13 -0
  30. package/build/theme/features/theme/verify/http/theme_verify_http_api.js +30 -0
  31. package/build/theme/features/theme/verify/theme_verify_constants.js +2 -0
  32. package/build/theme/features/theme/verify/theme_verify_initializer.js +19 -0
  33. package/build/theme/features/theme/verify/verify/theme_verify_service.js +55 -0
  34. package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js +0 -1
  35. package/build/theme/hooks/themes_actions/ensure_themes_actions_hook_constants.js +1 -2
  36. package/build/theme/index.js +23 -3
  37. package/oclif.config.js +1 -1
  38. package/package.json +2 -2
  39. package/build/cli/commands/files_diff_command.js +0 -174
  40. package/build/theme/commands/theme_show_changes_command.js +0 -61
@@ -1,8 +1,8 @@
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 { THEME_ACTION_NOT_FOUND_ERROR_CODE, THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../../features/theme/actions/theme_actions_constants.js';
5
- import { THEME_ARCHIVE_UPLOAD_ERROR, THEME_FILES_UPLOAD_ERROR, THEME_PUSH_API_NAME } from '../../features/theme/push/theme_push_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
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';
@@ -13,19 +13,18 @@ import { OutsideOfThemeDirectoryContextError } from '../ui/ouside_of_theme_direc
13
13
  import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_credentials_error.js';
14
14
  import { ThemePushedSuccess } from './ui/theme_pushed_success.js';
15
15
  import ora from 'ora';
16
- import { MissingThemeFiles } from '../ui/missing_theme_files.js';
17
- import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
18
- import { THEME_FILES_STRUCTURE_FILE_NAME } from '../../features/theme/theme_constants.js';
19
16
  import { ThemePushSkipInfo } from './ui/theme_push_skip_into.js';
20
17
  import { ThemeUnpermittedActionsError } from './ui/theme_unpermitted_actions_error.js';
21
- import { Error } from '../../../ui/message_box/error.js';
22
- import { Text } from '../../../ui/text.js';
23
- import { ThemeWorkUrlMismatch } from '../ui/theme_work_url_mismatch.js';
24
- import { THEME_WORK_URL_MISMATCH_ERROR } from '../../features/theme/utils/meta_data/theme_meta_data_constants.js';
25
18
  import { ThemeFilesStructureUtils } from '../../features/theme/utils/files_structure/theme_files_structure_utils.js';
26
19
  import { ThemeInfoUtils } from '../../features/theme/info/theme_info_utils.js';
27
- import { ValidationErrors } from '../../../ui/validation_errors/validation_errors.js';
28
20
  import { ThemeChecksums } from '../../class/checksums/theme_checksums.js';
21
+ import { ThemeFilesUpload } from '../../class/files_upload/theme_files_upload.js';
22
+ import { ThemeFilesUploadHttpApi } from '../../class/files_upload/theme_files_upload_http_api.js';
23
+ import { HTTP_REQUESTER_API_NAME } from '@dreamcommerce/star_core';
24
+ import { MissingThemeFiles } from '../ui/missing_theme_files.js';
25
+ import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
26
+ import { THEME_FILES_STRUCTURE_FILE_NAME } from '../../features/theme/theme_constants.js';
27
+ import { ThemeError } from '../ui/theme_error.js';
29
28
  export class ThemePushCommand extends BaseThemeCommand {
30
29
  static summary = 'Uploads your local theme files to the store and overwrites the current version of the theme in your store.';
31
30
  static description = 'Check your local changes before pushing.\n\nYou must run this command from a specific theme directory (ID not needed).';
@@ -65,6 +64,11 @@ export class ThemePushCommand extends BaseThemeCommand {
65
64
  }
66
65
  const initialChecksums = await themeChecksums.getInitialChecksums();
67
66
  const permissions = await ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
67
+ const themeFilesUploadApi = new ThemeFilesUpload({
68
+ themeRootDir: executionContext.themeRootDir,
69
+ credentials,
70
+ themeFilesUploadHttpApi: new ThemeFilesUploadHttpApi(this.getApi(HTTP_REQUESTER_API_NAME))
71
+ });
68
72
  if (!initialChecksums || !permissions)
69
73
  this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
70
74
  if (!(await themeChecksums.hasThemeBeenModified())) {
@@ -94,41 +98,15 @@ export class ThemePushCommand extends BaseThemeCommand {
94
98
  filesStructure,
95
99
  pushAction,
96
100
  themeChecksums,
97
- executionContext
101
+ executionContext,
102
+ themeFilesUploadApi
98
103
  });
99
104
  spinner.stop();
100
105
  renderOnce(React.createElement(ThemePushedSuccess, { themeName: await ThemeInfoUtils.getThemeName(executionContext.themeRootDir) }));
101
106
  }
102
107
  catch (err) {
103
108
  spinner?.stop();
104
- this._handleError(err, executionContext);
105
- }
106
- }
107
- _handleError(err, executionContext) {
108
- if (err?.code === THEME_ACTION_NOT_FOUND_ERROR_CODE) {
109
- renderOnce(React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "push" }));
110
- return;
111
- }
112
- if (err?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
113
- renderOnce(React.createElement(ValidationErrors, { errors: err?.details }));
114
- return;
115
- }
116
- if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
117
- this._renderUrlMismatchError();
118
- return;
109
+ renderOnce(React.createElement(ThemeError, { err: err, executionContext: executionContext }));
119
110
  }
120
- if (err?.code === THEME_FILES_UPLOAD_ERROR) {
121
- renderOnce(React.createElement(Error, { header: "Uploading theme files to the shop failed.\n" },
122
- 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.")));
123
- return;
124
- }
125
- if (err?.message) {
126
- renderOnce(React.createElement(ValidationErrors, { errors: err.message }));
127
- return;
128
- }
129
- this.error(String(err));
130
- }
131
- _renderUrlMismatchError() {
132
- renderOnce(React.createElement(ThemeWorkUrlMismatch, { command: "push" }));
133
111
  }
134
112
  }
@@ -1,11 +1,11 @@
1
+ export const THEME_TOPIC_NAME = 'theme';
1
2
  export const THEME_COMMANDS_NAME = {
2
- list: 'theme:list',
3
- pull: 'theme:pull',
4
- init: 'theme:init',
5
- push: 'theme:push',
6
- showChanges: 'theme:show-changes',
7
- verify: 'theme:verify',
8
- info: 'theme:info',
9
- delete: 'theme:delete',
10
- publish: 'theme:publish'
3
+ list: `${THEME_TOPIC_NAME}:list`,
4
+ pull: `${THEME_TOPIC_NAME}:pull`,
5
+ init: `${THEME_TOPIC_NAME}:init`,
6
+ push: `${THEME_TOPIC_NAME}:push`,
7
+ verify: `${THEME_TOPIC_NAME}:verify`,
8
+ info: `${THEME_TOPIC_NAME}:info`,
9
+ delete: `${THEME_TOPIC_NAME}:delete`,
10
+ publish: `${THEME_TOPIC_NAME}:publish`
11
11
  };
@@ -1,44 +1,85 @@
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 { mapToPermissionsTree } from '../utils/directory_validator/directory_validator_utils.js';
5
- import { mapChecksumToTree } from '../../utils/checksums/checksums_utils.js';
6
4
  import { ThemeFilesStructureUtils } from '../features/theme/utils/files_structure/theme_files_structure_utils.js';
7
5
  import { ThemeChecksums } from '../class/checksums/theme_checksums.js';
6
+ import { THEME_VERIFY_API_NAME } from '../features/theme/verify/theme_verify_constants.js';
7
+ import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
8
+ import { ThemeFilesUpload } from '../class/files_upload/theme_files_upload.js';
9
+ import { ThemeFilesUploadHttpApi } from '../class/files_upload/theme_files_upload_http_api.js';
10
+ import { HTTP_REQUESTER_API_NAME } from '@dreamcommerce/star_core';
11
+ import { renderOnce } from '../../ui/ui_utils.js';
12
+ import { MissingThemeFiles } from './ui/missing_theme_files.js';
13
+ import { SHOPER_THEME_METADATA_DIR } from '../constants/directory_contstants.js';
14
+ import { THEME_FILES_STRUCTURE_FILE_NAME } from '../features/theme/theme_constants.js';
15
+ import React from 'react';
16
+ import { Success } from '../../ui/message_box/success.js';
17
+ import { MissingCredentialsError } from '../../cli/commands/auth/ui/missing_credentials_error.js';
18
+ import { OutsideOfThemeDirectoryContextError } from './ui/ouside_of_theme_directory_context_error.js';
19
+ import ora from 'ora';
20
+ import { ThemeError } from './ui/theme_error.js';
8
21
  export class ThemeVerifyCommand extends BaseThemeCommand {
9
22
  static description = 'Verify theme files structure';
10
- static hidden = true;
11
23
  async run() {
12
24
  const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
13
25
  const credentials = cliAuthApi.getCredentials();
14
26
  const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
15
- if (!credentials)
16
- this.error('Credentials not found. Please authorize first.');
27
+ if (!credentials) {
28
+ renderOnce(React.createElement(MissingCredentialsError, null));
29
+ return;
30
+ }
17
31
  const executionContext = await executionContextApi.getExecutionContext();
18
- if (executionContext.type !== EXECUTION_CONTEXTS.theme)
19
- this.error('You cannot run this command outside theme context.');
32
+ if (executionContext.type !== EXECUTION_CONTEXTS.theme) {
33
+ renderOnce(React.createElement(OutsideOfThemeDirectoryContextError, null));
34
+ return;
35
+ }
20
36
  const themeChecksums = new ThemeChecksums(executionContext.themeRootDir);
21
37
  const permissions = await ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
22
38
  if (!themeChecksums || !permissions)
23
39
  this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
40
+ let spinner;
24
41
  try {
25
- const validationResult = await ThemeFilesStructureUtils.validateThemeDirectoryStructure({
26
- checksums: mapChecksumToTree(await themeChecksums.getInitialChecksums()),
27
- permissions: mapToPermissionsTree(permissions),
28
- rootDirectory: executionContext.themeRootDir
42
+ const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
43
+ const themeVerifyApi = this.getApi(THEME_VERIFY_API_NAME);
44
+ const themeFilesUploadApi = new ThemeFilesUpload({
45
+ themeRootDir: executionContext.themeRootDir,
46
+ credentials,
47
+ themeFilesUploadHttpApi: new ThemeFilesUploadHttpApi(this.getApi(HTTP_REQUESTER_API_NAME))
48
+ });
49
+ const verifyAction = themeActionsApi.getThemeAction({
50
+ actionType: THEME_ACTIONS_TYPES.verify,
51
+ themeId: executionContext.themeId,
52
+ credentials
53
+ });
54
+ const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(executionContext.themeRootDir);
55
+ if (!filesStructure) {
56
+ renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
57
+ return;
58
+ }
59
+ spinner = ora('Verifying theme...').start();
60
+ const { isSuccess, messages } = await themeVerifyApi.verifyTheme({
61
+ verifyAction,
62
+ filesStructure,
63
+ executionContext,
64
+ credentials,
65
+ themeChecksums,
66
+ themeFilesUploadApi
29
67
  });
30
- if (validationResult.isValid) {
31
- this.log('Theme structure is valid.');
68
+ spinner.stop();
69
+ if (isSuccess) {
70
+ renderOnce(React.createElement(Success, { header: `Success: Theme verified!` }));
32
71
  }
33
72
  else {
34
- this.log('Verification failed.\n');
35
- validationResult.unpermittedActions.forEach((action) => {
36
- this.log(`Unpermitted action: ${action.type} path: ${action.path}`);
37
- });
73
+ renderOnce(React.createElement(ThemeError, { err: {
74
+ massages: messages
75
+ }, executionContext: executionContext }));
38
76
  }
77
+ // await promptConfirmation('jolo');
39
78
  }
40
79
  catch (err) {
41
- this.error(`Error during theme verification: ${err instanceof Error ? err.message : String(err)}`);
80
+ spinner?.stop();
81
+ // await promptConfirmation('jolo');
82
+ renderOnce(React.createElement(ThemeError, { err: err, executionContext: executionContext }));
42
83
  }
43
84
  }
44
85
  }
@@ -0,0 +1,29 @@
1
+ import { THEME_ACTION_NOT_FOUND_ERROR_CODE } from '../../features/theme/actions/theme_actions_constants.js';
2
+ import { UnpermittedCommandError } from './unpermitted_command_error.js';
3
+ import { THEME_ARCHIVE_UPLOAD_ERROR, THEME_FILES_UPLOAD_ERROR } from '../../features/theme/push/theme_push_constants.js';
4
+ import { ValidationErrors } from '../../../ui/validation_errors/validation_errors.js';
5
+ import { THEME_WORK_URL_MISMATCH_ERROR } from '../../features/theme/utils/meta_data/theme_meta_data_constants.js';
6
+ import { Error } from '../../../ui/message_box/error.js';
7
+ import { Text } from '../../../ui/text.js';
8
+ import React from 'react';
9
+ import { ThemeWorkUrlMismatch } from './theme_work_url_mismatch.js';
10
+ import { EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
11
+ export const ThemeError = ({ err, executionContext }) => {
12
+ if (err?.code === THEME_ACTION_NOT_FOUND_ERROR_CODE && executionContext.type === EXECUTION_CONTEXTS.theme) {
13
+ return (React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "verify" }));
14
+ }
15
+ if (err?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
16
+ return React.createElement(ValidationErrors, { errors: err?.details });
17
+ }
18
+ if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
19
+ return React.createElement(ThemeWorkUrlMismatch, { command: "verify" });
20
+ }
21
+ if (err?.code === THEME_FILES_UPLOAD_ERROR) {
22
+ return (React.createElement(Error, { header: "Uploading theme files to the shop failed.\n" },
23
+ 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.")));
24
+ }
25
+ if (err?.message) {
26
+ return React.createElement(ValidationErrors, { errors: err.message });
27
+ }
28
+ return String(err);
29
+ };
@@ -7,7 +7,8 @@ export const THEME_ACTIONS_TYPES = {
7
7
  pull: 'pull',
8
8
  delete: 'delete',
9
9
  publishForm: 'publish_form',
10
- publish: 'publish'
10
+ publish: 'publish',
11
+ verify: 'verify'
11
12
  };
12
13
  export const THEME_ACTION_DATA_TYPE = {
13
14
  file: 'file',
@@ -1,5 +1,8 @@
1
1
  import { THEME_ALL_ACTIONS_NAME } from './service/theme_actions_service_constants.js';
2
- import { toUnixPath } from '../../../../utils/path_utils.js';
2
+ import { basename, looksLikeDirectory, toUnixPath } from '../../../../utils/path_utils.js';
3
+ import { THEME_ACTION_DATA_TYPE } from './theme_actions_constants.js';
4
+ import globs from 'fast-glob';
5
+ import { THEME_PUSH_WILDCARD_GLOBS_FOR_FILES } from '../push/service/theme_push_service_constants.js';
3
6
  export class ThemeActionsUtils {
4
7
  static getFilesGlobsThatMatchesActionName({ filesStructure, actionValue, actionType }) {
5
8
  return Object.entries(filesStructure).reduce((acc, [filePath, fileStructureItem]) => {
@@ -9,6 +12,43 @@ export class ThemeActionsUtils {
9
12
  return acc;
10
13
  }, []);
11
14
  }
15
+ static async getFilesRecordsFromActionData({ themeRootDir, themeAction, filesStructure }) {
16
+ const filesRecords = [];
17
+ for (const [actionKey, actionData] of Object.entries(themeAction.data)) {
18
+ if (actionData.type === THEME_ACTION_DATA_TYPE.file) {
19
+ const filesGlobs = ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
20
+ //TODO remove when backend fixed
21
+ actionType: 'push',
22
+ // actionType: themeAction.actionType,
23
+ actionValue: actionKey,
24
+ filesStructure
25
+ });
26
+ for (const fileGlob of filesGlobs) {
27
+ const files = await globs(fileGlob, {
28
+ suppressErrors: true,
29
+ onlyFiles: true,
30
+ cwd: themeRootDir
31
+ });
32
+ let processedFileGlob = fileGlob;
33
+ if (looksLikeDirectory(fileGlob)) {
34
+ processedFileGlob = fileGlob.endsWith(THEME_PUSH_WILDCARD_GLOBS_FOR_FILES)
35
+ ? fileGlob.slice(0, fileGlob.length - 2)
36
+ : fileGlob;
37
+ }
38
+ for (const filePath of files) {
39
+ filesRecords.push({
40
+ actionData,
41
+ actionKey,
42
+ path: filePath,
43
+ fileName: basename(filePath),
44
+ fileGlob: processedFileGlob
45
+ });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return filesRecords;
51
+ }
12
52
  static _doesActionValueMatch(currentActionValue, valuesToMatch) {
13
53
  if (typeof valuesToMatch === 'string') {
14
54
  return currentActionValue === valuesToMatch || valuesToMatch === THEME_ALL_ACTIONS_NAME;
@@ -1,11 +1,11 @@
1
- import { AsyncFeatureInitializer, FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME } from '@dreamcommerce/star_core';
1
+ import { FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME, SyncFeatureInitializer } from '@dreamcommerce/star_core';
2
2
  import { ThemeInitHttpApi } from './http/theme_init_http_api.js';
3
3
  import { ThemeInitApi } from './api/theme_init_api.js';
4
4
  import { ThemeInitService } from './service/theme_init_service.js';
5
5
  import { THEME_INIT_FEATURE_NAME } from './theme_init_constants.js';
6
- export class ThemeInitInitializer extends AsyncFeatureInitializer {
6
+ export class ThemeInitInitializer extends SyncFeatureInitializer {
7
7
  static featureName = THEME_INIT_FEATURE_NAME;
8
- async init() {
8
+ init() {
9
9
  const httpApi = this.getApiSync(HTTP_REQUESTER_API_NAME);
10
10
  const service = new ThemeInitService({
11
11
  httpApi: new ThemeInitHttpApi(httpApi)
@@ -1,67 +1,59 @@
1
1
  import tmp from 'tmp-promise';
2
- import { THEME_ACTION_DATA_TYPE, THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
3
- import { createZip } from '../../../../../utils/zip/create_zip_utils.js';
4
- import { basename, dirname, extname, join, looksLikeDirectory, toUnixPath } from '../../../../../utils/path_utils.js';
5
- import { createReadStream } from 'node:fs';
2
+ import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
3
+ import { join } from '../../../../../utils/path_utils.js';
6
4
  import { v4 as uuid } from 'uuid';
7
- import globs from 'fast-glob';
8
5
  import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
9
- import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
10
- import { THEME_FILES_LIST_FILE_NAME, THEME_MODULE_SETTINGS_FILE_NAME } from '../theme_push_constants.js';
11
- import { THEME_PUSH_WILDCARD_GLOBS_FOR_FILES } from './theme_push_service_constants.js';
6
+ import { THEME_MODULE_SETTINGS_FILE_NAME } from '../theme_push_constants.js';
12
7
  import { ThemePushErrorsFactory } from '../theme_push_errors_factory.js';
13
8
  import { ThemeImagesUtils } from '../../utils/theme_images_utils.js';
14
9
  import { ThemePublishUtils } from '../../skinstore/theme_publish_utils.js';
15
- import { formatJSONFile, removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
10
+ import { removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
16
11
  import { MODULES_DIRECTORY_NAME } from '../../theme_constants.js';
17
- import path from 'node:path';
18
12
  import { removeOldResources } from '../../../../class/fetch_resources/fetch_resources_utils.js';
13
+ import { ThemeArchive } from '../../../../class/archive/theme_archive.js';
14
+ import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
15
+ import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
16
+ import { ArrayUtils } from '@dreamcommerce/utilities';
19
17
  export class ThemePushService {
20
- #themePushHttpApi;
21
18
  #themeFetchApi;
22
- constructor({ themePushHttpApi, themeFetchApi }) {
23
- this.#themePushHttpApi = themePushHttpApi;
19
+ constructor({ themeFetchApi }) {
24
20
  this.#themeFetchApi = themeFetchApi;
25
21
  }
26
- async push({ pushAction, filesStructure, credentials, executionContext, themeChecksums }) {
22
+ async push({ pushAction, credentials, filesStructure, executionContext, themeChecksums, themeFilesUploadApi }) {
27
23
  const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
28
24
  const themeRootDir = executionContext.themeRootDir;
29
25
  if (await themeChecksums.hasThemeFileBeenCreated(ThemePublishUtils.getSkinStoreSettingsFilePath(themeRootDir)))
30
26
  throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
31
27
  try {
32
- const { uploadData, localFiles } = await this._getActionDataForFilesToUpload({
33
- pushAction,
34
- filesStructure,
35
- executionContext,
36
- themeChecksums
28
+ //TODO to do api?
29
+ const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
30
+ themeRootDir,
31
+ themeAction: pushAction,
32
+ filesStructure
37
33
  });
38
- if (uploadData.length) {
39
- const { newFiles } = await this._uploadThemeFiles({
40
- filesToUpload: uploadData,
34
+ const filesToUpload = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenCreated(path)) || (await themeChecksums.hasThemeFileBeenModified(path)));
35
+ if (filesToUpload.length) {
36
+ const { localFileNameToUploaded } = await this._uploadThemeFiles({
37
+ filesToUpload,
41
38
  credentials,
42
- localFiles,
43
- themeRootDir
39
+ themeRootDir,
40
+ themeFilesUploadApi
44
41
  });
45
- await this._createAFilesListFile(themeRootDir, newFiles);
42
+ await this._createFilesList(themeRootDir, filesRecords, localFileNameToUploaded);
46
43
  }
47
44
  else {
48
- await this._createAFilesListFile(themeRootDir, localFiles);
45
+ await this._createFilesList(themeRootDir, filesRecords);
49
46
  }
50
47
  const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
51
- await this._createThemeArchive({
52
- themeRootDir: themeRootDir,
53
- filesToArchive: ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
54
- actionType: THEME_ACTIONS_TYPES.push,
55
- actionValue: THEME_WILDCARD_ACTION_NAME,
56
- filesStructure
57
- }),
48
+ await new ThemeArchive(themeRootDir).createFullArchive({
58
49
  dist: themeArchivePath,
59
- credentials
50
+ actionValue: THEME_WILDCARD_ACTION_NAME,
51
+ actionType: THEME_ACTIONS_TYPES.push
60
52
  });
61
- const { resources, modules } = await this._uploadThemeArchive({
53
+ const { resources, modules } = await themeFilesUploadApi.uploadArchive({
54
+ action: pushAction,
62
55
  themeArchivePath,
63
- credentials,
64
- pushAction
56
+ credentials
65
57
  });
66
58
  if (modules)
67
59
  await this._updateDataForNewCreatedModules({ modules, themeRootDir });
@@ -72,58 +64,24 @@ export class ThemePushService {
72
64
  await themeChecksums.updateAllChecksums();
73
65
  }
74
66
  finally {
75
- await removeFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME));
67
+ await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
76
68
  }
77
69
  }
78
- async _createThemeArchive({ themeRootDir, filesToArchive, dist, credentials }) {
70
+ async _uploadThemeFiles({ filesToUpload, themeRootDir, credentials, themeFilesUploadApi }) {
79
71
  try {
80
- const filesInThemeDirectory = await globs(filesToArchive, {
81
- suppressErrors: true,
82
- onlyFiles: true,
83
- cwd: themeRootDir
84
- });
85
- await this._formatJsonFiles(themeRootDir, filesInThemeDirectory);
86
- return createZip({
87
- files: filesInThemeDirectory,
88
- baseDir: themeRootDir,
89
- dist
90
- });
91
- }
92
- catch (err) {
93
- throw ThemePushErrorsFactory.createErrorWhileCreatingThemeArchive(credentials.shopUrl, err);
94
- }
95
- }
96
- async _uploadThemeArchive({ themeArchivePath, credentials, pushAction }) {
97
- try {
98
- const request = this.#themePushHttpApi.pushThemeData({
99
- actionData: pushAction.data[THEME_WILDCARD_ACTION_NAME],
100
- stream: createReadStream(themeArchivePath),
101
- shopUrl: credentials.shopUrl
102
- });
103
- const response = await request.response;
104
- if (response.status !== 200 || !response.data.isSuccess)
105
- throw response;
106
- return response.data;
107
- }
108
- catch (err) {
109
- throw ThemePushErrorsFactory.createErrorWhileUploadingTheme(credentials.shopUrl, err.response.data.messages);
110
- }
111
- }
112
- async _formatJsonFiles(themeRootDir, filesToUpload) {
113
- await Promise.all(filesToUpload
114
- .filter((path) => extname(path).toLowerCase() === '.json')
115
- .map((jsonFile) => formatJSONFile(join(themeRootDir, jsonFile))));
116
- }
117
- //TODO osobna klasa do uploadu
118
- async _uploadThemeFiles({ filesToUpload, themeRootDir, localFiles, credentials }) {
119
- try {
120
- const { uploadedImageData, rejectedImageData } = await this._uploadFiles(filesToUpload, credentials);
121
- const newFilesList = ThemeImagesUtils.updateOriginalFilenameToUploadedFilename(localFiles, uploadedImageData);
72
+ const { uploadedImageData, rejectedImageData } = await themeFilesUploadApi.uploadFiles(filesToUpload);
122
73
  if (uploadedImageData.length)
123
74
  await ThemeImagesUtils.removeUploadedOriginalFiles(themeRootDir, uploadedImageData);
124
75
  if (rejectedImageData.length)
125
76
  await this._removeUploadedThemeFiles(rejectedImageData, themeRootDir);
126
- return { newFiles: newFilesList };
77
+ return {
78
+ localFileNameToUploaded: uploadedImageData.reduce((acc, { originalFilename, uploadedFilename }) => {
79
+ return {
80
+ ...acc,
81
+ [originalFilename]: uploadedFilename ?? originalFilename
82
+ };
83
+ }, {})
84
+ };
127
85
  }
128
86
  catch (err) {
129
87
  throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl, err);
@@ -132,92 +90,12 @@ export class ThemePushService {
132
90
  async _removeUploadedThemeFiles(uploadedImageData, themeRootDir) {
133
91
  await Promise.all(uploadedImageData.map(({ location, originalFilename }) => removeFile(join(themeRootDir, location, originalFilename))));
134
92
  }
135
- async _uploadFiles(uploadData, credentials) {
136
- const uploadedImageData = [];
137
- const rejectedImageData = [];
138
- await Promise.all(uploadData.map(({ actionData, path }) => this.#themePushHttpApi
139
- .pushThemeData({
140
- actionData,
141
- stream: createReadStream(path),
142
- shopUrl: credentials.shopUrl
143
- })
144
- .response.then((response) => {
145
- uploadedImageData.push({
146
- location: dirname(path),
147
- originalFilename: basename(path),
148
- uploadedFilename: response.data.imageId
149
- });
150
- })
151
- .catch(() => {
152
- rejectedImageData.push({
153
- location: dirname(path),
154
- originalFilename: basename(path)
155
- });
156
- })));
157
- return {
158
- uploadedImageData,
159
- rejectedImageData
160
- };
161
- }
162
- async _getActionDataForFilesToUpload({ pushAction, filesStructure, executionContext, themeChecksums }) {
163
- const uploadData = [];
164
- const localFiles = {};
165
- for (const [actionKey, actionData] of Object.entries(pushAction.data)) {
166
- if (actionData.type === THEME_ACTION_DATA_TYPE.file) {
167
- const filesGlobs = ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
168
- actionType: THEME_ACTIONS_TYPES.push,
169
- actionValue: actionKey,
170
- filesStructure
171
- });
172
- for (const fileGlob of filesGlobs) {
173
- const files = await globs(fileGlob, {
174
- suppressErrors: true,
175
- onlyFiles: true,
176
- cwd: executionContext.themeRootDir
177
- });
178
- if (looksLikeDirectory(fileGlob)) {
179
- const processedFileGlob = fileGlob.endsWith(THEME_PUSH_WILDCARD_GLOBS_FOR_FILES)
180
- ? fileGlob.slice(0, fileGlob.length - 2)
181
- : fileGlob;
182
- localFiles[processedFileGlob] = files.length ? files.map((filePath) => basename(filePath)) : [];
183
- }
184
- else {
185
- localFiles[fileGlob] = files.length ? basename(files[0]) : null;
186
- }
187
- for (const filePath of files) {
188
- if ((await themeChecksums.hasThemeFileBeenCreated(filePath)) ||
189
- (await themeChecksums.hasThemeFileBeenModified(filePath))) {
190
- uploadData.push({
191
- actionData,
192
- actionKey,
193
- path: filePath
194
- });
195
- }
196
- }
197
- }
198
- }
199
- }
200
- return {
201
- uploadData,
202
- localFiles
203
- };
204
- }
205
93
  async _updateDataForNewCreatedModules({ modules, themeRootDir }) {
206
94
  for (const [moduleDirectoryName, metaData] of Object.entries(modules)) {
207
95
  await writeJSONFile(join(themeRootDir, MODULES_DIRECTORY_NAME, moduleDirectoryName, THEME_MODULE_SETTINGS_FILE_NAME), JSON.parse(metaData[THEME_MODULE_SETTINGS_FILE_NAME]));
208
96
  }
209
97
  }
210
- async _createAFilesListFile(themeRootDir, filesList) {
211
- if (!filesList || !Object.keys(filesList).length)
212
- return;
213
- const toUnixStyleFilesList = Object.entries(filesList).reduce((acc, [filePath, value]) => {
214
- const unixPath = toUnixPath(filePath);
215
- const finalPath = looksLikeDirectory(unixPath) ? `${unixPath}${path.posix.sep}` : unixPath;
216
- return {
217
- ...acc,
218
- [finalPath]: value
219
- };
220
- }, {});
221
- await writeJSONFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME), toUnixStyleFilesList);
98
+ async _createFilesList(themeRootDir, filesRecords, localFileNameToUploaded = {}) {
99
+ await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded));
222
100
  }
223
101
  }
@@ -1,15 +1,12 @@
1
- import { FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME, SyncFeatureInitializer } from '@dreamcommerce/star_core';
1
+ import { FEATURE_CORES_TYPES, SyncFeatureInitializer } from '@dreamcommerce/star_core';
2
2
  import { ThemePushService } from './service/theme_push_service.js';
3
3
  import { THEME_PUSH_FEATURE_NAME } from './theme_push_constants.js';
4
4
  import { ThemePushApi } from './api/theme_push_api.js';
5
- import { ThemePushHttpApi } from './http_api/theme_push_http_api.js';
6
5
  import { THEME_FETCH_API_NAME } from '../fetch/theme_fetch_constants.js';
7
6
  export class ThemePushInitializer extends SyncFeatureInitializer {
8
7
  static featureName = THEME_PUSH_FEATURE_NAME;
9
8
  init() {
10
- const httpApi = this.getApiSync(HTTP_REQUESTER_API_NAME);
11
9
  const service = new ThemePushService({
12
- themePushHttpApi: new ThemePushHttpApi(httpApi),
13
10
  themeFetchApi: this.getApiSync(THEME_FETCH_API_NAME)
14
11
  });
15
12
  return {
@@ -0,0 +1,10 @@
1
+ import { AppError } from '../../../../../cli/class/errors/app/app_error.js';
2
+ export class ThemeFileStructureErrorsFactory {
3
+ static createNoFilesStructureError() {
4
+ return new AppError({
5
+ code: 'theme.file_structure.no_files_structure',
6
+ message: 'No files structure found for the theme.',
7
+ level: 'error'
8
+ });
9
+ }
10
+ }