@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.
Files changed (132) hide show
  1. package/build/cli/auth/api/cli_auth_api.js +15 -2
  2. package/build/cli/auth/cli_auth_constants.js +6 -0
  3. package/build/cli/auth/cli_auth_errors_factory.js +10 -0
  4. package/build/cli/auth/cli_auth_initializer.js +19 -0
  5. package/build/cli/auth/cli_auth_utils.js +22 -0
  6. package/build/cli/auth/model/cli_credentials.js +10 -0
  7. package/build/cli/auth/service/cli_auth_service.js +34 -0
  8. package/build/cli/auth/tokens/api/cli_auth_tokens_api.js +43 -1
  9. package/build/cli/auth/tokens/cli_auth_tokens_constants.js +6 -0
  10. package/build/cli/auth/tokens/cli_auth_tokens_errors_factory.js +10 -0
  11. package/build/cli/auth/tokens/cli_auth_tokens_initalizer.js +35 -1
  12. package/build/cli/auth/tokens/cli_auth_tokens_utils.js +26 -0
  13. package/build/cli/auth/tokens/service/cli_auth_tokens_service.js +84 -4
  14. package/build/cli/auth/tokens/service/cli_auth_tokens_service_constants.js +2 -0
  15. package/build/cli/class/base_cli_command.js +3 -0
  16. package/build/cli/class/errors/app_error/app_error.js +17 -0
  17. package/build/cli/class/errors/app_error/app_error_constants.js +7 -0
  18. package/build/cli/class/errors/file_system_errors_factory.js +26 -0
  19. package/build/cli/class/errors/http_errors_factory.js +21 -0
  20. package/build/cli/cli_constants.js +1 -0
  21. package/build/cli/commands/auth/cli_auth_add_token_command.js +32 -0
  22. package/build/cli/commands/auth/cli_auth_list_tokens_command.js +17 -0
  23. package/build/cli/commands/auth/cli_auth_remove_token_command.js +37 -0
  24. package/build/cli/commands/auth/cli_auth_switch_token_command.js +36 -0
  25. package/build/cli/commands/cli_update_command.js +7 -3
  26. package/build/cli/commands/commands_constants.js +5 -1
  27. package/build/cli/commands/files_diff_command.js +174 -0
  28. package/build/cli/commands/utils/prompt_for_token_utils.js +25 -0
  29. package/build/cli/core/cli_setup.js +20 -7
  30. package/build/cli/features/caches/json_cache/json_cache.js +50 -0
  31. package/build/cli/features/caches/memory_cache.js +6 -0
  32. package/build/cli/features/data_directory/cli_data_directory_constants.js +0 -1
  33. package/build/cli/features/data_directory/cli_data_directory_utils.js +3 -4
  34. package/build/cli/features/data_directory/service/cli_data_directory_service.js +2 -2
  35. package/build/cli/features/execution_context/execution_context_service.js +3 -5
  36. package/build/cli/features/http_requester/http_client.js +32 -1
  37. package/build/cli/features/version/service/cli_version_service.js +2 -4
  38. package/build/cli/hooks/authorization/ensure_authorization_hook.js +11 -6
  39. package/build/cli/hooks/authorization/ensure_authorization_hook_constants.js +8 -1
  40. package/build/index.js +34 -1
  41. package/build/theme/class/fetch_resources/fetch_resources.js +127 -0
  42. package/build/theme/class/fetch_resources/fetch_resources_constants.js +2 -0
  43. package/build/theme/class/fetch_resources/fetch_resources_errors_factory.js +11 -0
  44. package/build/theme/class/fetch_resources/fetch_resources_utils.js +35 -0
  45. package/build/theme/commands/theme_commands_constants.js +8 -0
  46. package/build/theme/commands/theme_init_command.js +53 -0
  47. package/build/theme/commands/theme_list_command.js +16 -0
  48. package/build/theme/commands/theme_pull_command.js +126 -0
  49. package/build/theme/commands/theme_push_command.js +65 -0
  50. package/build/theme/commands/theme_show_changes_command.js +60 -0
  51. package/build/theme/commands/theme_verify_command.js +43 -0
  52. package/build/theme/features/theme/actions/api/theme_actions_api.js +19 -0
  53. package/build/theme/features/theme/actions/service/theme_actions_service.js +92 -0
  54. package/build/theme/features/theme/actions/service/theme_actions_service_constants.js +2 -0
  55. package/build/theme/features/theme/actions/theme_actions_constants.js +15 -0
  56. package/build/theme/features/theme/actions/theme_actions_initializer.js +27 -0
  57. package/build/theme/features/theme/actions/theme_actions_utils.js +8 -0
  58. package/build/theme/features/theme/directory/theme_directory_utils.js +61 -14
  59. package/build/theme/features/theme/fetch/api/theme_fetch_api.js +16 -0
  60. package/build/theme/features/theme/fetch/http/theme_fetch_http_api.js +18 -0
  61. package/build/theme/features/theme/fetch/service/theme_fetch_service.js +113 -0
  62. package/build/theme/features/theme/fetch/theme_fetch_constants.js +7 -0
  63. package/build/theme/features/theme/fetch/theme_fetch_initializer.js +23 -0
  64. package/build/theme/features/theme/init/api/theme_init_api.js +13 -0
  65. package/build/theme/features/theme/init/http/theme_init_http_api.js +18 -0
  66. package/build/theme/features/theme/init/service/theme_init_service.js +28 -0
  67. package/build/theme/features/theme/init/theme_init_constants.js +2 -0
  68. package/build/theme/features/theme/init/theme_init_initializer.js +22 -0
  69. package/build/theme/features/theme/merge/api/theme_merge_api.js +28 -0
  70. package/build/theme/features/theme/merge/service/theme_merge_service.js +56 -0
  71. package/build/theme/features/theme/merge/theme_merge_constants.js +9 -0
  72. package/build/theme/features/theme/merge/theme_merge_initializer.js +18 -0
  73. package/build/theme/features/theme/publish/theme_publish_constants.js +2 -0
  74. package/build/theme/features/theme/publish/theme_publish_utils.js +7 -0
  75. package/build/theme/features/theme/push/api/theme_push_api.js +13 -0
  76. package/build/theme/features/theme/push/http_api/theme_push_http_api.js +21 -0
  77. package/build/theme/features/theme/push/service/theme_push_service.js +200 -0
  78. package/build/theme/features/theme/push/service/theme_push_service_constants.js +1 -0
  79. package/build/theme/features/theme/push/theme_push_constants.js +4 -0
  80. package/build/theme/features/theme/push/theme_push_errors_factory.js +41 -0
  81. package/build/theme/features/theme/push/theme_push_initializer.js +27 -0
  82. package/build/theme/features/theme/theme_constants.js +6 -0
  83. package/build/theme/features/theme/utils/checksums/theme_checksums_error_factory.js +10 -0
  84. package/build/theme/features/theme/utils/checksums/theme_checksums_utils.js +94 -0
  85. package/build/theme/features/theme/utils/directories/theme_resources_with_id_directory_utils.js +80 -0
  86. package/build/theme/features/theme/utils/theme_images_utils.js +30 -0
  87. package/build/theme/features/themes/list/api/themes_list_api.js +13 -0
  88. package/build/theme/features/themes/{http/shoper_themes_http_api.js → list/http/themes_list_http_api.js} +3 -2
  89. package/build/theme/features/themes/{model/theme_metadata.js → list/model/theme_list_metadata.js} +3 -1
  90. package/build/theme/features/themes/list/services/themes_list_service.js +37 -0
  91. package/build/theme/features/themes/list/themes_list_constants.js +2 -0
  92. package/build/theme/features/themes/list/themes_list_initializer.js +20 -0
  93. package/build/theme/hooks/ensure_theme_meta_data_untouched.js +17 -0
  94. package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date.js +21 -0
  95. package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js +7 -0
  96. package/build/theme/hooks/themes_actions/ensure_themes_actions_hook.js +45 -0
  97. package/build/theme/hooks/themes_actions/ensure_themes_actions_hook_constants.js +7 -0
  98. package/build/theme/index.js +13 -4
  99. package/build/theme/utils/directory_validator/directory_validator_constants.js +13 -0
  100. package/build/theme/utils/directory_validator/directory_validator_utils.js +185 -0
  101. package/build/utils/checksums/checksums_utils.js +95 -0
  102. package/build/utils/checksums/checksums_utils_constants.js +1 -0
  103. package/build/utils/date_utils.js +3 -0
  104. package/build/utils/download_file/download_file_errors_factory.js +9 -0
  105. package/build/utils/download_file/download_file_utils.js +46 -0
  106. package/build/utils/fs/errors/stream_read_error.js +11 -0
  107. package/build/utils/fs/errors/stream_write_error.js +11 -0
  108. package/build/utils/fs/fs_utils.js +155 -0
  109. package/build/utils/http_utils.js +10 -0
  110. package/build/utils/path_utils.js +37 -0
  111. package/build/utils/stream_transforms/json_indent_transform.js +21 -0
  112. package/build/utils/url_utils.js +9 -0
  113. package/build/utils/zip/create_zip_utils.js +80 -0
  114. package/build/utils/zip/errors/create_zip_error.js +11 -0
  115. package/build/utils/zip/errors/open_zip_error.js +11 -0
  116. package/build/utils/zip/extract_zip_utils.js +104 -0
  117. package/oclif.config.js +4 -2
  118. package/package.json +36 -7
  119. package/build/cli/hooks/migration/migration_hook.js +0 -4
  120. package/build/theme/commands/list_command.js +0 -12
  121. package/build/theme/commands/pull_command.js +0 -24
  122. package/build/theme/features/theme/directory/theme_directories_utils.js +0 -10
  123. package/build/theme/features/theme/pull/api/shoper_theme_pull_api.js +0 -9
  124. package/build/theme/features/theme/pull/http/shoper_theme_pull_http_api.js +0 -14
  125. package/build/theme/features/theme/pull/service/shoper_theme_pull_service.js +0 -68
  126. package/build/theme/features/theme/pull/shoper_theme_pull_initializer.js +0 -22
  127. package/build/theme/features/themes/api/shoper_themes_api.js +0 -14
  128. package/build/theme/features/themes/services/shoper_themes_service.js +0 -15
  129. package/build/theme/features/themes/shoper_themes_constants.js +0 -2
  130. package/build/theme/features/themes/shoper_themes_initalizer.js +0 -20
  131. package/build/utils/fs.js +0 -44
  132. package/build/utils/path.js +0 -13
@@ -0,0 +1,200 @@
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';
6
+ import { v4 as uuid } from 'uuid';
7
+ import globs from 'fast-glob';
8
+ import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
9
+ import { getFilesGlobsThatMatchesActionName } 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';
12
+ import { ThemePushErrorsFactory } from '../theme_push_errors_factory.js';
13
+ import { ThemeImagesUtils } from '../../utils/theme_images_utils.js';
14
+ import { ThemePublishUtils } from '../../publish/theme_publish_utils.js';
15
+ import { formatJSONFile, removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
16
+ import { computeChecksumsFromSource } from '../../../../../utils/checksums/checksums_utils.js';
17
+ import { ThemeChecksumsUtils } from '../../utils/checksums/theme_checksums_utils.js';
18
+ import { MODULES_DIRECTORY_NAME } from '../../theme_constants.js';
19
+ export class ThemePushService {
20
+ #themePushHttpApi;
21
+ #themeMergeApi;
22
+ #themeFetchApi;
23
+ constructor({ themePushHttpApi, themeMergeApi, themeFetchApi }) {
24
+ this.#themePushHttpApi = themePushHttpApi;
25
+ this.#themeMergeApi = themeMergeApi;
26
+ this.#themeFetchApi = themeFetchApi;
27
+ }
28
+ async push({ pushAction, filesStructure, credentials, executionContext }) {
29
+ const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
30
+ if (await this.#themeMergeApi.hasFileBeenCreated(ThemePublishUtils.getSkinStoreSettingsFilePath(executionContext.themeRootDir), executionContext))
31
+ throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
32
+ const { uploadData, localFiles } = await this._getActionDataForFilesToUpload({
33
+ pushAction,
34
+ filesStructure,
35
+ executionContext
36
+ });
37
+ if (uploadData.length) {
38
+ await this._uploadThemeFiles({
39
+ filesToUpload: uploadData,
40
+ actionData: pushAction.data[THEME_WILDCARD_ACTION_NAME],
41
+ credentials,
42
+ localFiles,
43
+ themeRootDir: executionContext.themeRootDir
44
+ });
45
+ }
46
+ else {
47
+ await this._createAFilesListFile(executionContext.themeRootDir, localFiles);
48
+ }
49
+ const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
50
+ try {
51
+ await this._createThemeArchive({
52
+ themeRootDir: executionContext.themeRootDir,
53
+ filesToArchive: [
54
+ ...getFilesGlobsThatMatchesActionName(THEME_ACTIONS_TYPES.push, THEME_WILDCARD_ACTION_NAME, filesStructure),
55
+ THEME_FILES_LIST_FILE_NAME
56
+ ],
57
+ dist: themeArchivePath
58
+ });
59
+ }
60
+ catch (err) {
61
+ throw ThemePushErrorsFactory.createErrorWhileCreatingThemeArchive(credentials.shopUrl, err);
62
+ }
63
+ const { resources, modules } = await this._uploadThemeArchive({
64
+ themeArchivePath,
65
+ credentials,
66
+ pushAction
67
+ });
68
+ if (modules)
69
+ await this._updateDataForNewCreatedModules({ modules, themeRootDir: executionContext.themeRootDir });
70
+ console.log(JSON.stringify(resources));
71
+ if (resources)
72
+ await this.#themeFetchApi.fetchResources(credentials.shopUrl, executionContext.themeRootDir, resources);
73
+ await removeFile(join(executionContext.themeRootDir, THEME_FILES_LIST_FILE_NAME));
74
+ const checksums = await computeChecksumsFromSource(executionContext.themeRootDir);
75
+ await ThemeChecksumsUtils.createThemeChecksumsFiles(executionContext.themeRootDir, checksums);
76
+ }
77
+ async _createThemeArchive({ themeRootDir, filesToArchive, dist }) {
78
+ const filesInThemeDirectory = await globs(filesToArchive, {
79
+ suppressErrors: true,
80
+ onlyFiles: true,
81
+ cwd: themeRootDir
82
+ });
83
+ await this._formatJsonFiles(themeRootDir, filesInThemeDirectory);
84
+ return createZip({
85
+ files: filesInThemeDirectory,
86
+ baseDir: themeRootDir,
87
+ dist
88
+ });
89
+ }
90
+ async _uploadThemeArchive({ themeArchivePath, credentials, pushAction }) {
91
+ try {
92
+ const request = this.#themePushHttpApi.pushThemeData({
93
+ actionData: pushAction.data[THEME_WILDCARD_ACTION_NAME],
94
+ stream: createReadStream(themeArchivePath),
95
+ shopUrl: credentials.shopUrl
96
+ });
97
+ const response = await request.response;
98
+ if (response.status !== 200 || !response.data.isSuccess)
99
+ throw response;
100
+ return response.data;
101
+ }
102
+ catch (err) {
103
+ console.log('error', err.response.data.messages);
104
+ throw ThemePushErrorsFactory.createErrorWhileUploadingTheme(credentials.shopUrl, err.response.data.messages);
105
+ }
106
+ }
107
+ async _formatJsonFiles(themeRootDir, filesToUpload) {
108
+ await Promise.all(filesToUpload
109
+ .filter((path) => extname(path).toLowerCase() === '.json')
110
+ .map((jsonFile) => formatJSONFile(join(themeRootDir, jsonFile))));
111
+ }
112
+ async _uploadThemeFiles({ filesToUpload, themeRootDir, localFiles, credentials }) {
113
+ try {
114
+ const uploadedImageData = await this._uploadFiles(filesToUpload, credentials);
115
+ const newFilesList = ThemeImagesUtils.updateOriginalFilenameToUploadedFilename(localFiles, uploadedImageData);
116
+ if (uploadedImageData.length)
117
+ await ThemeImagesUtils.removeUploadedOriginalFiles(themeRootDir, uploadedImageData);
118
+ await this._createAFilesListFile(themeRootDir, newFilesList);
119
+ }
120
+ catch (err) {
121
+ throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl, err);
122
+ }
123
+ }
124
+ async _uploadFiles(uploadData, credentials) {
125
+ const uploadedImageData = [];
126
+ await Promise.all(uploadData.map(({ actionData, path }) => this.#themePushHttpApi
127
+ .pushThemeData({
128
+ actionData,
129
+ stream: createReadStream(path),
130
+ shopUrl: credentials.shopUrl
131
+ })
132
+ .response.then((response) => {
133
+ if (response.status !== 200 || !response.data.isSuccess)
134
+ throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl, response.data?.messages ?? []);
135
+ uploadedImageData.push({
136
+ location: dirname(path),
137
+ originalFilename: basename(path),
138
+ uploadedFilename: response.data.imageId
139
+ });
140
+ })));
141
+ return uploadedImageData;
142
+ }
143
+ async _getActionDataForFilesToUpload({ pushAction, filesStructure, executionContext }) {
144
+ const uploadData = [];
145
+ const localFiles = {};
146
+ for (const [actionKey, actionData] of Object.entries(pushAction.data)) {
147
+ if (actionData.type === THEME_ACTION_DATA_TYPE.file) {
148
+ const filesGlobs = getFilesGlobsThatMatchesActionName(THEME_ACTIONS_TYPES.push, actionKey, filesStructure);
149
+ for (const fileGlob of filesGlobs) {
150
+ const files = await globs(fileGlob, {
151
+ suppressErrors: true,
152
+ onlyFiles: true,
153
+ cwd: executionContext.themeRootDir
154
+ });
155
+ if (looksLikeDirectory(fileGlob)) {
156
+ const processedFileGlob = fileGlob.endsWith(THEME_PUSH_WILDCARD_GLOBS_FOR_FILES)
157
+ ? fileGlob.slice(0, fileGlob.length - 2)
158
+ : fileGlob;
159
+ localFiles[processedFileGlob] = files.length ? files.map((filePath) => basename(filePath)) : [];
160
+ }
161
+ else {
162
+ localFiles[fileGlob] = files.length ? basename(files[0]) : null;
163
+ }
164
+ for (const filePath of files) {
165
+ if ((await this.#themeMergeApi.hasFileBeenCreated(filePath, executionContext)) ||
166
+ (await this.#themeMergeApi.hasFileBeenModified(filePath, executionContext))) {
167
+ uploadData.push({
168
+ actionData,
169
+ actionKey,
170
+ path: filePath
171
+ });
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
177
+ return {
178
+ uploadData,
179
+ localFiles
180
+ };
181
+ }
182
+ async _updateDataForNewCreatedModules({ modules, themeRootDir }) {
183
+ for (const [moduleDirectoryName, metaData] of Object.entries(modules)) {
184
+ await writeJSONFile(join(themeRootDir, MODULES_DIRECTORY_NAME, moduleDirectoryName, THEME_MODULE_SETTINGS_FILE_NAME), JSON.parse(metaData[THEME_MODULE_SETTINGS_FILE_NAME]));
185
+ }
186
+ }
187
+ async _createAFilesListFile(themeRootDir, filesList) {
188
+ if (!filesList || !Object.keys(filesList).length)
189
+ return;
190
+ const toUnixStyleFilesList = Object.entries(filesList).reduce((acc, [path, value]) => {
191
+ const unixPath = toUnixPath(path);
192
+ const finalPath = looksLikeDirectory(path) ? `${unixPath}/` : unixPath;
193
+ return {
194
+ ...acc,
195
+ [finalPath]: value
196
+ };
197
+ }, {});
198
+ await writeJSONFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME), toUnixStyleFilesList);
199
+ }
200
+ }
@@ -0,0 +1 @@
1
+ export const THEME_PUSH_WILDCARD_GLOBS_FOR_FILES = '*';
@@ -0,0 +1,4 @@
1
+ export const THEME_PUSH_FEATURE_NAME = 'ThemePush';
2
+ export const THEME_PUSH_API_NAME = 'ThemePushApi';
3
+ export const THEME_FILES_LIST_FILE_NAME = 'filesList.json';
4
+ export const THEME_MODULE_SETTINGS_FILE_NAME = 'settings.json';
@@ -0,0 +1,41 @@
1
+ import { AppError } from '../../../../cli/class/errors/app_error/app_error.js';
2
+ export class ThemePushErrorsFactory {
3
+ static createErrorWhileUploadingThemeFiles(shopUrl, messages) {
4
+ return new AppError({
5
+ code: 'theme.push.error_uploading_theme_files',
6
+ message: `Error while uploading theme files to shop "${shopUrl}"`,
7
+ level: 'error',
8
+ details: messages
9
+ });
10
+ }
11
+ static createErrorWhileCreatingThemeArchive(shopUrl, error) {
12
+ return new AppError({
13
+ code: 'theme.push.error_creating_theme_archive',
14
+ message: `Error while creating theme archive for shop "${shopUrl}"`,
15
+ level: 'error',
16
+ stack: error.stack
17
+ });
18
+ }
19
+ static createErrorWhilePushingUnpublishedThemeWithSkinstoreData(shopUrl) {
20
+ return new AppError({
21
+ code: 'theme.push.error_pushing_unpublished_theme_with_skinstore_data',
22
+ message: `Error while pushing unpublished theme with skinstore data to shop "${shopUrl}"`,
23
+ level: 'error'
24
+ });
25
+ }
26
+ static createGeneralError() {
27
+ return new AppError({
28
+ code: 'theme.push.general_error',
29
+ message: 'Something went wrong while pushing theme, try again later. ',
30
+ level: 'error'
31
+ });
32
+ }
33
+ static createErrorWhileUploadingTheme(shopUrl, messages) {
34
+ return new AppError({
35
+ code: 'theme.push.error_uploading_theme',
36
+ message: `Error while uploading theme to shop "${shopUrl}"`,
37
+ level: 'error',
38
+ details: messages
39
+ });
40
+ }
41
+ }
@@ -0,0 +1,27 @@
1
+ import { FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME, SyncFeatureInitializer } from '@dreamcommerce/star_core';
2
+ import { ThemePushService } from './service/theme_push_service.js';
3
+ import { THEME_PUSH_FEATURE_NAME } from './theme_push_constants.js';
4
+ import { ThemePushApi } from './api/theme_push_api.js';
5
+ import { ThemePushHttpApi } from './http_api/theme_push_http_api.js';
6
+ import { THEME_MERGE_API_NAME } from '../merge/theme_merge_constants.js';
7
+ import { THEME_FETCH_API_NAME } from '../fetch/theme_fetch_constants.js';
8
+ export class ThemePushInitializer extends SyncFeatureInitializer {
9
+ static featureName = THEME_PUSH_FEATURE_NAME;
10
+ init() {
11
+ const httpApi = this.getApiSync(HTTP_REQUESTER_API_NAME);
12
+ const themeMergeApi = this.getApiSync(THEME_MERGE_API_NAME);
13
+ const service = new ThemePushService({
14
+ themePushHttpApi: new ThemePushHttpApi(httpApi),
15
+ themeMergeApi,
16
+ themeFetchApi: this.getApiSync(THEME_FETCH_API_NAME)
17
+ });
18
+ return {
19
+ cores: [
20
+ {
21
+ type: FEATURE_CORES_TYPES.api,
22
+ instance: new ThemePushApi(service)
23
+ }
24
+ ]
25
+ };
26
+ }
27
+ }
@@ -0,0 +1,6 @@
1
+ export const THEME_CURRENT_CHECKSUMS_FILE_NAME = 'current_checksum.json';
2
+ export const THEME_INITIAL_CHECKSUMS_FILE_NAME = 'initial_checksum.json';
3
+ export const THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME = 'current_checksums_verify.json';
4
+ export const THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME = 'initial_checksums_verify.json';
5
+ export const THEME_FILES_STRUCTURE_FILE_NAME = 'filesStructure.json';
6
+ export const MODULES_DIRECTORY_NAME = 'modules';
@@ -0,0 +1,10 @@
1
+ import { AppError } from '../../../../../cli/class/errors/app_error/app_error.js';
2
+ export class ThemeChecksumsErrorFactory {
3
+ static createThemeChecksumError(stack) {
4
+ return new AppError({
5
+ message: 'Theme checksum error',
6
+ code: 'theme_checksum_error',
7
+ stack
8
+ });
9
+ }
10
+ }
@@ -0,0 +1,94 @@
1
+ import { join } from '../../../../../utils/path_utils.js';
2
+ import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME, THEME_INITIAL_CHECKSUMS_FILE_NAME, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME } from '../../theme_constants.js';
3
+ import { createWriteStream } from 'fs';
4
+ import { copyFile, readJSONFile, removeFile } from '../../../../../utils/fs/fs_utils.js';
5
+ import { ThemeChecksumsErrorFactory } from './theme_checksums_error_factory.js';
6
+ import { ThemeDirectoryUtils } from '../../directory/theme_directory_utils.js';
7
+ import { computeFileChecksum } from '../../../../../utils/checksums/checksums_utils.js';
8
+ import { JSON_FILE_INDENT } from '../../../../../cli/cli_constants.js';
9
+ export class ThemeChecksumsUtils {
10
+ static getCurrentThemeChecksumsFilePath(themeDir) {
11
+ return join(ThemeDirectoryUtils.getThemeMetaDataDirPath(themeDir), THEME_CURRENT_CHECKSUMS_FILE_NAME);
12
+ }
13
+ static getInitialThemeChecksumsFilePath(themeDir) {
14
+ return join(ThemeDirectoryUtils.getThemeMetaDataDirPath(themeDir), THEME_INITIAL_CHECKSUMS_FILE_NAME);
15
+ }
16
+ static getCurrentThemeChecksumsVerificationFilePath(themeDir) {
17
+ return join(ThemeDirectoryUtils.getThemeMetaDataDirPath(themeDir), THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME);
18
+ }
19
+ static getInitialThemeChecksumsVerificationFilePath(themeDir) {
20
+ return join(ThemeDirectoryUtils.getThemeMetaDataDirPath(themeDir), THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME);
21
+ }
22
+ static async createThemeChecksumsFiles(themeDir, checksums) {
23
+ return new Promise((resolve, reject) => {
24
+ const currentChecksumFilePath = this.getCurrentThemeChecksumsFilePath(themeDir);
25
+ const initialChecksumFilePath = this.getInitialThemeChecksumsFilePath(themeDir);
26
+ const checksumStream = createWriteStream(initialChecksumFilePath);
27
+ checksumStream.write(JSON.stringify(checksums, null, JSON_FILE_INDENT));
28
+ checksumStream.end();
29
+ checksumStream
30
+ .on('finish', async () => {
31
+ const initialChecksumVerifyFilePath = this.getInitialThemeChecksumsVerificationFilePath(themeDir);
32
+ const currentChecksumVerifyFilePath = this.getCurrentThemeChecksumsVerificationFilePath(themeDir);
33
+ await this.createThemeChecksumVerifyFile(initialChecksumFilePath, initialChecksumVerifyFilePath);
34
+ await copyFile(initialChecksumFilePath, currentChecksumFilePath);
35
+ await copyFile(initialChecksumVerifyFilePath, currentChecksumVerifyFilePath);
36
+ resolve();
37
+ })
38
+ .on('error', async (err) => {
39
+ await removeFile(initialChecksumFilePath, { force: true });
40
+ reject(ThemeChecksumsErrorFactory.createThemeChecksumError(err.stack));
41
+ });
42
+ });
43
+ }
44
+ static async createThemeCurrentChecksumsFile(themeDir, checksums) {
45
+ return new Promise((resolve, reject) => {
46
+ const currentChecksumFilePath = this.getCurrentThemeChecksumsFilePath(themeDir);
47
+ const checksumStream = createWriteStream(currentChecksumFilePath);
48
+ checksumStream.write(JSON.stringify(checksums, null, JSON_FILE_INDENT));
49
+ checksumStream.end();
50
+ checksumStream
51
+ .on('finish', async () => {
52
+ const currentChecksumVerifyFilePath = this.getCurrentThemeChecksumsVerificationFilePath(themeDir);
53
+ await this.createThemeChecksumVerifyFile(currentChecksumFilePath, currentChecksumVerifyFilePath);
54
+ resolve();
55
+ })
56
+ .on('error', async (err) => {
57
+ await removeFile(currentChecksumFilePath, { force: true });
58
+ reject(ThemeChecksumsErrorFactory.createThemeChecksumError(err.stack));
59
+ });
60
+ });
61
+ }
62
+ static async createThemeChecksumVerifyFile(checksumFilePath, checksumVerifyFullPath) {
63
+ const checksumVerifyStream = createWriteStream(checksumVerifyFullPath);
64
+ const checksumVerify = await computeFileChecksum(checksumFilePath);
65
+ checksumVerifyStream
66
+ .on('error', async (err) => {
67
+ await removeFile(checksumFilePath, { force: true });
68
+ throw ThemeChecksumsErrorFactory.createThemeChecksumError(err.stack);
69
+ })
70
+ .write(JSON.stringify(checksumVerify));
71
+ checksumVerifyStream.end();
72
+ }
73
+ static async getThemeCurrentChecksums(themeDir) {
74
+ const checksumFilePath = this.getCurrentThemeChecksumsFilePath(themeDir);
75
+ return await readJSONFile(checksumFilePath);
76
+ }
77
+ static async getThemeInitialChecksums(themeDir) {
78
+ const checksumFilePath = this.getInitialThemeChecksumsFilePath(themeDir);
79
+ return await readJSONFile(checksumFilePath);
80
+ }
81
+ static async verifyThemeChecksums(themeDir) {
82
+ const initialChecksumFilePath = this.getInitialThemeChecksumsFilePath(themeDir);
83
+ const initialChecksumVerifyFilePath = this.getInitialThemeChecksumsVerificationFilePath(themeDir);
84
+ const initialChecksum = await computeFileChecksum(initialChecksumFilePath);
85
+ const initialChecksumVerify = await readJSONFile(initialChecksumVerifyFilePath);
86
+ return initialChecksum === initialChecksumVerify;
87
+ }
88
+ static async getThemeCurrentChecksumsVerification(themeDir) {
89
+ return await readJSONFile(this.getCurrentThemeChecksumsVerificationFilePath(themeDir));
90
+ }
91
+ static async getInitialChecksumsVerification(themeDir) {
92
+ return await readJSONFile(this.getInitialThemeChecksumsVerificationFilePath(themeDir));
93
+ }
94
+ }
@@ -0,0 +1,80 @@
1
+ import FSTree from 'fs-tree-diff';
2
+ import walkSync from 'walk-sync';
3
+ import { THEME_FILES_OPERATIONS } from '../../merge/theme_merge_constants.js';
4
+ import { fileExists, readJSONFile, renameFile } from '../../../../../utils/fs/fs_utils.js';
5
+ import { join } from '../../../../../utils/path_utils.js';
6
+ import fs from 'node:fs';
7
+ import fsPromises from 'node:fs/promises';
8
+ import { computeChecksumsFromSource } from '../../../../../utils/checksums/checksums_utils.js';
9
+ import { ThemeChecksumsUtils } from '../checksums/theme_checksums_utils.js';
10
+ export class ThemeResourcesWithIdDirectoryUtils {
11
+ static async updateDirectoryNamesOfResourcesWithIdAccordingToLocalThemeNames(localResourcesDir, localThemeDir, remoteResourcesDir, remoteThemeDir) {
12
+ const localThemeTree = new FSTree({
13
+ entries: walkSync.entries(localResourcesDir, {
14
+ globs: ['*/'],
15
+ ignore: ['*/**']
16
+ })
17
+ });
18
+ const remoteThemeTree = new FSTree({
19
+ entries: walkSync.entries(remoteResourcesDir, {
20
+ globs: ['*/'],
21
+ ignore: ['*/**']
22
+ })
23
+ });
24
+ const localResourceIdToPathMap = await this._getFilePathToResourceIdMap(localThemeTree.entries);
25
+ const remoteResourceIdToPathMap = await this._getResourceIdToFilePathMap(remoteThemeTree.entries);
26
+ const directoriesCreatedLocally = localThemeTree
27
+ .calculatePatch(remoteThemeTree)
28
+ .filter(([operation]) => operation === THEME_FILES_OPERATIONS.rmdir);
29
+ for (const operation of directoriesCreatedLocally) {
30
+ const resourceDirectoryRelativePath = operation[1];
31
+ if (!resourceDirectoryRelativePath)
32
+ continue;
33
+ const resourceId = localResourceIdToPathMap.get(resourceDirectoryRelativePath);
34
+ if (!resourceId)
35
+ continue;
36
+ const remoteResourceDirectoryRelativePath = remoteResourceIdToPathMap.get(resourceId);
37
+ if (!remoteResourceDirectoryRelativePath)
38
+ continue;
39
+ await renameFile(join(remoteResourcesDir, remoteResourceDirectoryRelativePath), join(remoteResourcesDir, resourceDirectoryRelativePath));
40
+ const resultPath = join(remoteResourcesDir, resourceDirectoryRelativePath);
41
+ const stat = fs.lstatSync(resultPath);
42
+ console.log('stat', stat.isDirectory());
43
+ if (stat.isSymbolicLink()) {
44
+ console.warn(`[WARN] Renamed path became a symlink: ${resultPath}`);
45
+ console.warn(`Points to: ${await fsPromises.readlink(resultPath)}`);
46
+ }
47
+ /**
48
+ * If these become performance bottlenecks, we can consider computing checksums only for the changed directories.
49
+ */
50
+ const checksums = await computeChecksumsFromSource(remoteThemeDir);
51
+ await ThemeChecksumsUtils.createThemeChecksumsFiles(remoteThemeDir, checksums);
52
+ }
53
+ }
54
+ static async _getResourceIdToFilePathMap(entries) {
55
+ const map = new Map();
56
+ for (const entry of entries) {
57
+ const settingsPaths = join(entry.basePath, entry.relativePath, 'settings.json');
58
+ if (!(await fileExists(settingsPaths)))
59
+ continue;
60
+ const settings = await readJSONFile(settingsPaths);
61
+ if (settings.id === undefined)
62
+ continue;
63
+ map.set(settings.id, entry.relativePath);
64
+ }
65
+ return map;
66
+ }
67
+ static async _getFilePathToResourceIdMap(entries) {
68
+ const map = new Map();
69
+ for (const entry of entries) {
70
+ const settingsPaths = join(entry.basePath, entry.relativePath, 'settings.json');
71
+ if (!(await fileExists(settingsPaths)))
72
+ continue;
73
+ const settings = await readJSONFile(settingsPaths);
74
+ if (settings.id === undefined)
75
+ continue;
76
+ map.set(entry.relativePath, settings.id);
77
+ }
78
+ return map;
79
+ }
80
+ }
@@ -0,0 +1,30 @@
1
+ import { join } from '../../../../utils/path_utils.js';
2
+ import { removeFile } from '../../../../utils/fs/fs_utils.js';
3
+ export class ThemeImagesUtils {
4
+ static updateOriginalFilenameToUploadedFilename(filesList, uploadedImageData) {
5
+ const newFilesList = { ...filesList };
6
+ uploadedImageData
7
+ .filter(({ uploadedFilename }) => Boolean(uploadedFilename))
8
+ .forEach(({ originalFilename, uploadedFilename, location }) => {
9
+ if (typeof newFilesList[location] === 'string' && uploadedFilename) {
10
+ newFilesList[location] = uploadedFilename;
11
+ return;
12
+ }
13
+ if (Array.isArray(newFilesList[location]) && uploadedFilename) {
14
+ const indexOfOriginalFilename = newFilesList[location].indexOf(originalFilename);
15
+ if (indexOfOriginalFilename === -1 && !uploadedFilename)
16
+ return;
17
+ newFilesList[location].splice(indexOfOriginalFilename, 1, uploadedFilename);
18
+ }
19
+ });
20
+ return newFilesList;
21
+ }
22
+ static async removeUploadedOriginalFiles(themeRootDir, uploadedImagesData) {
23
+ await Promise.all(uploadedImagesData
24
+ .filter(({ originalFilename }) => Boolean(originalFilename))
25
+ .map(({ originalFilename, location }) => {
26
+ const originalFilePath = join(themeRootDir, location, originalFilename);
27
+ return removeFile(originalFilePath);
28
+ }));
29
+ }
30
+ }
@@ -0,0 +1,13 @@
1
+ import { FeatureApi } from '@dreamcommerce/star_core';
2
+ import { THEMES_LIST_API_NAME } from '../themes_list_constants.js';
3
+ export class ThemesListApi extends FeatureApi {
4
+ moduleName = THEMES_LIST_API_NAME;
5
+ #service;
6
+ constructor(service) {
7
+ super();
8
+ this.#service = service;
9
+ }
10
+ async getThemes(credentials) {
11
+ return this.#service.getThemes(credentials);
12
+ }
13
+ }
@@ -1,5 +1,5 @@
1
1
  import { REQUEST_TYPES } from '@dreamcommerce/star_core';
2
- export class ShoperThemesHttpApi {
2
+ export class ThemesListHttpApi {
3
3
  #httpApi;
4
4
  constructor(httpApi) {
5
5
  this.#httpApi = httpApi;
@@ -10,7 +10,8 @@ export class ShoperThemesHttpApi {
10
10
  method: REQUEST_TYPES.get,
11
11
  sanitizeOptions: {
12
12
  disable: true
13
- }
13
+ },
14
+ isPrivate: true
14
15
  });
15
16
  }
16
17
  }
@@ -4,11 +4,13 @@ export class ThemeMetaData {
4
4
  description;
5
5
  isActive;
6
6
  canEdit;
7
- constructor({ canEdit, skinId, description, isActive, title }) {
7
+ links;
8
+ constructor({ canEdit, skinId, description, isActive, title, links }) {
8
9
  this.skinId = skinId;
9
10
  this.title = title;
10
11
  this.description = description;
11
12
  this.isActive = isActive;
12
13
  this.canEdit = canEdit;
14
+ this.links = links;
13
15
  }
14
16
  }
@@ -0,0 +1,37 @@
1
+ import { ThemeMetaData } from '../model/theme_list_metadata.js';
2
+ import { STATUS_CODES } from '@dreamcommerce/star_core';
3
+ import { HttpErrorsFactory } from '../../../../../cli/class/errors/http_errors_factory.js';
4
+ import { DownloadFileErrorsFactory } from '../../../../../utils/download_file/download_file_errors_factory.js';
5
+ export class ThemesListService {
6
+ httpApi;
7
+ constructor(httpApi) {
8
+ this.httpApi = httpApi;
9
+ }
10
+ async getThemes({ shopUrl }) {
11
+ try {
12
+ const { response } = this.httpApi.getThemes(shopUrl);
13
+ const { data, status } = await response;
14
+ if (status !== STATUS_CODES.ok)
15
+ return;
16
+ //TODO baseHttpApi + model mapper
17
+ return data.map(({ _links, ...rest }) => new ThemeMetaData({
18
+ ...rest,
19
+ links: _links
20
+ }));
21
+ }
22
+ catch (err) {
23
+ switch (err.response?.status) {
24
+ case 403:
25
+ throw HttpErrorsFactory.createForbiddenError();
26
+ case 401:
27
+ throw HttpErrorsFactory.createUnauthorizedError();
28
+ case 404:
29
+ throw HttpErrorsFactory.createNotFoundError();
30
+ default:
31
+ if (err.response?.status !== 200) {
32
+ throw DownloadFileErrorsFactory.downloadError(err.response.status);
33
+ }
34
+ }
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,2 @@
1
+ export const THEMES_LIST_FEATURE_NAME = 'ThemesList';
2
+ export const THEMES_LIST_API_NAME = 'ThemesListApi';
@@ -0,0 +1,20 @@
1
+ import { ThemesListService } from './services/themes_list_service.js';
2
+ import { ThemesListApi } from './api/themes_list_api.js';
3
+ import { FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME, SyncFeatureInitializer } from '@dreamcommerce/star_core';
4
+ import { THEMES_LIST_FEATURE_NAME } from './themes_list_constants.js';
5
+ import { ThemesListHttpApi } from './http/themes_list_http_api.js';
6
+ export class ThemesListInitializer extends SyncFeatureInitializer {
7
+ static featureName = THEMES_LIST_FEATURE_NAME;
8
+ init() {
9
+ const httpApi = this.getApiSync(HTTP_REQUESTER_API_NAME);
10
+ const service = new ThemesListService(new ThemesListHttpApi(httpApi));
11
+ return {
12
+ cores: [
13
+ {
14
+ type: FEATURE_CORES_TYPES.api,
15
+ instance: new ThemesListApi(service)
16
+ }
17
+ ]
18
+ };
19
+ }
20
+ }
@@ -0,0 +1,17 @@
1
+ import { useApi } from '../../cli/hooks/ensure_cli_initialized_hook.js';
2
+ import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
3
+ import { ThemeDirectoryUtils } from '../features/theme/directory/theme_directory_utils.js';
4
+ const ensureThemeMetaDataUntouched = async () => {
5
+ const executionContextApi = useApi(EXECUTION_CONTEXT_API_NAME);
6
+ const executionContext = await executionContextApi.getExecutionContext();
7
+ if (executionContext.type !== EXECUTION_CONTEXTS.theme)
8
+ return;
9
+ try {
10
+ await ThemeDirectoryUtils.ensureFilesInsideThemeMetaDataDirectoryUntouched(executionContext.themeRootDir);
11
+ }
12
+ catch (err) {
13
+ console.error(err);
14
+ process.exit(1);
15
+ }
16
+ };
17
+ export default ensureThemeMetaDataUntouched;
@@ -0,0 +1,21 @@
1
+ import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
2
+ import { useApi } from '../../../cli/hooks/ensure_cli_initialized_hook.js';
3
+ import { ThemeChecksumsUtils } from '../../features/theme/utils/checksums/theme_checksums_utils.js';
4
+ import { computeChecksumsFromSource } from '../../../utils/checksums/checksums_utils.js';
5
+ import { THEME_COMMANDS_THAT_REQUIRES_UP_TO_DATE_CHECKSUMS } from './ensure_theme_current_checksums_up_to_date_constants.js';
6
+ export const ensureThemeChecksumsUpToDate = async ({ Command }) => {
7
+ console.log('Command', Command.id);
8
+ if (!THEME_COMMANDS_THAT_REQUIRES_UP_TO_DATE_CHECKSUMS.includes(Command.id))
9
+ return;
10
+ const executionContextApi = useApi(EXECUTION_CONTEXT_API_NAME);
11
+ const executionContext = await executionContextApi.getExecutionContext();
12
+ if (executionContext.type !== EXECUTION_CONTEXTS.theme)
13
+ return;
14
+ /**
15
+ * Naive solution, recalculate checksums every time a command based on checksums calculation is executed;
16
+ * If performance becomes an issue, we can implement a more sophisticated solution, eg. recalculate checksums only when files in the theme directory have changed.
17
+ */
18
+ const checksums = await computeChecksumsFromSource(executionContext.themeRootDir);
19
+ await ThemeChecksumsUtils.createThemeCurrentChecksumsFile(executionContext.themeRootDir, checksums);
20
+ };
21
+ export default ensureThemeChecksumsUpToDate;