@shoper/cli 0.6.2 → 0.6.4-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 (33) hide show
  1. package/README.md +1 -1
  2. package/build/cli/class/errors/http/http_errors_factory.js +1 -1
  3. package/build/cli/core/cli_setup.js +6 -6
  4. package/build/cli/index.js +1 -0
  5. package/build/theme/class/archive/theme_archive.js +1 -45
  6. package/build/theme/class/checksums/theme_checksums.js +47 -9
  7. package/build/theme/commands/pull/theme_pull_command.js +4 -3
  8. package/build/theme/commands/push/theme_push_command.js +17 -8
  9. package/build/theme/commands/theme_verify_command.js +3 -3
  10. package/build/theme/commands/ui/theme_error.js +2 -2
  11. package/build/theme/features/theme/actions/theme_actions_constants.js +2 -1
  12. package/build/theme/features/theme/actions/theme_actions_utils.js +31 -5
  13. package/build/theme/features/theme/fetch/service/theme_fetch_service.js +2 -2
  14. package/build/theme/features/theme/init/service/theme_init_service.js +2 -2
  15. package/build/theme/features/theme/merge/service/theme_merge_service.js +2 -2
  16. package/build/theme/features/theme/push/api/theme_push_api.js +2 -2
  17. package/build/theme/features/theme/push/service/theme_push_service.js +90 -32
  18. package/build/theme/features/theme/push/service/theme_push_service_types.js +1 -0
  19. package/build/theme/features/theme/push/theme_push_utils.js +2 -2
  20. package/build/theme/features/theme/utils/archive/theme_archive_utils.js +25 -0
  21. package/build/theme/{class/archive/theme_archive_errors_factory.js → features/theme/utils/archive/theme_archive_utils_errors_factory.js} +1 -1
  22. package/build/theme/features/theme/utils/files/them_files_constants.js +1 -0
  23. package/build/theme/features/theme/utils/{files_structure/theme_files_structure_utils.js → files/theme_files_utils.js} +34 -23
  24. package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +1 -9
  25. package/build/theme/features/theme/verify/verify/theme_verify_service.js +19 -12
  26. package/build/utils/array_utils.js +3 -0
  27. package/build/utils/fs/fs_constants.js +6 -0
  28. package/build/utils/fs/fs_utils.js +1 -1
  29. package/package.json +5 -3
  30. package/build/theme/features/theme/watch/api/theme_watch_api.js +0 -11
  31. package/build/theme/features/theme/watch/service/theme_watch_service.js +0 -48
  32. package/build/theme/features/theme/watch/theme_watch_constants.js +0 -2
  33. package/build/theme/features/theme/watch/theme_watch_initializer.js +0 -20
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![Shoper CLI](//dcsaascdn.net/shoper-cli/shoper-for-developers.svg)
1
+ ![Shoper CLI](https://dcsaascdn.net/shoper-cli/shoper-for-developers.svg)
2
2
 
3
3
  # Shoper CLI
4
4
 
@@ -15,7 +15,7 @@ export class HttpErrorsFactory {
15
15
  }
16
16
  static createNotFoundError() {
17
17
  return new AppError({
18
- message: 'Not found',
18
+ message: '404 Not found',
19
19
  code: HTTP_NOT_FOUND_ERROR_CODE
20
20
  });
21
21
  }
@@ -29,7 +29,7 @@ export const cliSetup = async () => {
29
29
  LoggerInitializer,
30
30
  CliAuthTokensInitializer,
31
31
  CliAuthInitializer,
32
- ...getCommandBaseInitializers()
32
+ ...getInitializersBasedOnCommand()
33
33
  ],
34
34
  options: {
35
35
  featuresTracking: false,
@@ -37,15 +37,15 @@ export const cliSetup = async () => {
37
37
  }
38
38
  });
39
39
  };
40
- const getCommandBaseInitializers = () => {
41
- if (isCommandsTopic(process.argv[2]))
42
- return getCommandWithTopicBaseInitializers();
40
+ const getInitializersBasedOnCommand = () => {
41
+ if (isCommandWithTopic(process.argv[2]))
42
+ return getInitializersBasedOnCommandWithTopic();
43
43
  return [];
44
44
  };
45
- const isCommandsTopic = (arg) => {
45
+ const isCommandWithTopic = (arg) => {
46
46
  return arg === THEME_TOPIC_NAME || arg === CLI_AUTH_TOPIC_NAME;
47
47
  };
48
- const getCommandWithTopicBaseInitializers = () => {
48
+ const getInitializersBasedOnCommandWithTopic = () => {
49
49
  const topic = process.argv[2];
50
50
  const command = process.argv[3];
51
51
  switch (topic) {
@@ -6,6 +6,7 @@ export const runCLI = async () => {
6
6
  await execute({ dir: import.meta.url });
7
7
  }
8
8
  catch (error) {
9
+ console.log('Error executing CLI command:', error);
9
10
  // TODO handle error
10
11
  }
11
12
  };
@@ -1,45 +1 @@
1
- import globs from 'fast-glob';
2
- import { createZip } from '../../../utils/zip/create_zip_utils.js';
3
- import { ThemeFilesStructureUtils } from '../../features/theme/utils/files_structure/theme_files_structure_utils.js';
4
- import { extname, join } from '../../../utils/path_utils.js';
5
- import { formatJSONFile } from '../../../utils/fs/fs_utils.js';
6
- import { ThemeArchiveErrorsFactory } from './theme_archive_errors_factory.js';
7
- import { ThemeFileStructureErrorsFactory } from '../../features/theme/utils/files_structure/theme_file_structure_errors_factory.js';
8
- import { ThemeActionsUtils } from '../../features/theme/actions/theme_actions_utils.js';
9
- export class ThemeArchive {
10
- #themeRootDir;
11
- constructor(themeRootDir) {
12
- this.#themeRootDir = themeRootDir;
13
- }
14
- async createFullArchive({ dist, actionValue, actionType, logger }) {
15
- const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(this.#themeRootDir);
16
- if (!filesStructure)
17
- throw ThemeFileStructureErrorsFactory.createNoFilesStructureError();
18
- try {
19
- const filesInThemeDirectory = await globs(ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
20
- actionType,
21
- actionValue,
22
- filesStructure
23
- }), {
24
- suppressErrors: true,
25
- onlyFiles: true,
26
- cwd: this.#themeRootDir
27
- });
28
- await this._formatJsonFiles(this.#themeRootDir, filesInThemeDirectory);
29
- return createZip({
30
- files: filesInThemeDirectory,
31
- logger,
32
- baseDir: this.#themeRootDir,
33
- dist
34
- });
35
- }
36
- catch (err) {
37
- throw ThemeArchiveErrorsFactory.createErrorWhileCreatingThemeArchive(err);
38
- }
39
- }
40
- async _formatJsonFiles(themeRootDir, filesToUpload) {
41
- await Promise.all(filesToUpload
42
- .filter((path) => extname(path).toLowerCase() === '.json')
43
- .map((jsonFile) => formatJSONFile(join(themeRootDir, jsonFile))));
44
- }
45
- }
1
+ export {};
@@ -10,6 +10,7 @@ import { normalize } from 'node:path';
10
10
  import { ThemePushUtils } from '../../features/theme/push/theme_push_utils.js';
11
11
  import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
12
12
  import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME, THEME_INITIAL_CHECKSUMS_FILE_NAME, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME } from '../../features/theme/theme_constants.js';
13
+ import { FILE_STATES } from '../../../utils/fs/fs_constants.js';
13
14
  export class ThemeChecksums {
14
15
  #themeDir;
15
16
  #loggerApi;
@@ -33,12 +34,6 @@ export class ThemeChecksums {
33
34
  this.#initialChecksums = await this._getChecksums(this.#initialChecksumsFilePath);
34
35
  return this.#initialChecksums;
35
36
  }
36
- getInitialChecksumsSync() {
37
- if (this.#initialChecksums)
38
- return this.#initialChecksums;
39
- this.#initialChecksums = this._getChecksumsSync(this.#initialChecksumsFilePath);
40
- return this.#initialChecksums;
41
- }
42
37
  async getCurrentChecksums() {
43
38
  if (this.#currentChecksums)
44
39
  return this.#currentChecksums;
@@ -72,10 +67,22 @@ export class ThemeChecksums {
72
67
  const initialChecksum = await this.getInitialChecksumFromPath(path);
73
68
  return !initialChecksum && (await fileExists(join(this.#themeDir, path)));
74
69
  }
75
- async hasThemeFileBeenModified(path) {
76
- const currentChecksum = await this.getCurrentChecksumFromPath(path);
70
+ async getFileState(path) {
77
71
  const initialChecksum = await this.getInitialChecksumFromPath(path);
78
- return !!currentChecksum && !!initialChecksum && currentChecksum !== initialChecksum;
72
+ const currentChecksum = await this.getCurrentChecksumFromPath(path);
73
+ const fileExistsInFs = await fileExists(join(this.#themeDir, path));
74
+ if (!initialChecksum && fileExistsInFs) {
75
+ return FILE_STATES.created;
76
+ }
77
+ else if (initialChecksum && !fileExistsInFs) {
78
+ return FILE_STATES.deleted;
79
+ }
80
+ else if (initialChecksum && currentChecksum && initialChecksum !== currentChecksum) {
81
+ return FILE_STATES.modified;
82
+ }
83
+ else {
84
+ return FILE_STATES.unchanged;
85
+ }
79
86
  }
80
87
  async verify() {
81
88
  const initialChecksumFilePath = this.#initialChecksumsFilePath;
@@ -154,6 +161,37 @@ export class ThemeChecksums {
154
161
  this.#loggerApi?.debug('Finished computing theme checksums.', { details: { count: Object.keys(allChecksums).length } });
155
162
  return allChecksums;
156
163
  }
164
+ async groupFilesByStatus(files) {
165
+ const initialChecksums = await this.getInitialChecksums();
166
+ const currentChecksums = await this.getCurrentChecksums();
167
+ return files.reduce((acc, filePath) => {
168
+ const initialChecksum = initialChecksums[toCurrentPlatformPath(filePath)];
169
+ const currentChecksum = currentChecksums[toCurrentPlatformPath(filePath)];
170
+ if (!initialChecksum) {
171
+ acc.created.push(filePath);
172
+ }
173
+ else if (currentChecksum && initialChecksum !== currentChecksum) {
174
+ acc.modified.push(filePath);
175
+ }
176
+ else if (initialChecksum && !currentChecksum) {
177
+ acc.deleted.push(filePath);
178
+ }
179
+ else {
180
+ acc.unchanged.push(filePath);
181
+ }
182
+ return acc;
183
+ }, {
184
+ created: [],
185
+ modified: [],
186
+ unchanged: [],
187
+ deleted: []
188
+ });
189
+ }
190
+ async getDeletedFiles() {
191
+ const initialChecksums = await this.getInitialChecksums();
192
+ const currentChecksums = await this.getCurrentChecksums();
193
+ return Object.keys(initialChecksums).filter((filePath) => !currentChecksums[filePath]);
194
+ }
157
195
  async _getInitialChecksumsVerification() {
158
196
  return await readJSONFile(this.#initialChecksumsVerificationFilePath);
159
197
  }
@@ -24,8 +24,9 @@ import { Info } from '../../../ui/message_box/info.js';
24
24
  import { directoryExists } from '../../../utils/fs/fs_utils.js';
25
25
  import { ThemeMetaDataUtils } from '../../features/theme/utils/meta_data/theme_meta_data_utils.js';
26
26
  import { ThemeChecksums } from '../../class/checksums/theme_checksums.js';
27
- import { ThemeError } from '../ui/theme_error.js';
28
27
  import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
28
+ import { ThemeError } from '../ui/theme_error.js';
29
+ import { MODULES_DIRECTORY_NAME } from '../../features/theme/theme_constants.js';
29
30
  //TODO jak jest error w pullu wowczas usuwamy docelowo pociagniety skin
30
31
  export class ThemePullCommand extends BaseThemeCommand {
31
32
  static summary = 'Downloads the current version of your theme from the store and overwrites your local theme files.';
@@ -160,9 +161,9 @@ export class ThemePullCommand extends BaseThemeCommand {
160
161
  dist: tmpDir
161
162
  }
162
163
  });
163
- const localModulesPath = join(executionContext.themeRootDir, 'modules');
164
+ const localModulesPath = join(executionContext.themeRootDir, MODULES_DIRECTORY_NAME);
164
165
  const fetchedThemePath = join(tmpDir, name);
165
- const fetchedModulesPath = join(fetchedThemePath, 'modules');
166
+ const fetchedModulesPath = join(fetchedThemePath, MODULES_DIRECTORY_NAME);
166
167
  if ((await directoryExists(localModulesPath)) && (await directoryExists(fetchedModulesPath)))
167
168
  await ThemeResourcesWithIdDirectoryUtils.updateDirectoryNamesOfResourcesWithIdAccordingToLocalThemeNames(localModulesPath, fetchedModulesPath, fetchedThemePath, loggerApi);
168
169
  this.#spinner.stop();
@@ -4,8 +4,6 @@ import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/fea
4
4
  import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../../features/theme/actions/theme_actions_constants.js';
5
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
- import { mapChecksumToTree } from '../../../utils/checksums/checksums_utils.js';
8
- import { mapToPermissionsTree } from '../../utils/directory_validator/directory_validator_utils.js';
9
7
  import { renderOnce } from '../../../ui/ui_utils.js';
10
8
  import { UnpermittedCommandError } from '../ui/unpermitted_command_error.js';
11
9
  import React from 'react';
@@ -14,7 +12,7 @@ import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_c
14
12
  import { ThemePushedSuccess } from './ui/theme_pushed_success.js';
15
13
  import ora from 'ora';
16
14
  import { ThemePushSkipInfo } from './ui/theme_push_skip_into.js';
17
- import { ThemeFilesStructureUtils } from '../../features/theme/utils/files_structure/theme_files_structure_utils.js';
15
+ import { ThemeFilesUtils } from '../../features/theme/utils/files/theme_files_utils.js';
18
16
  import { ThemeInfoUtils } from '../../features/theme/info/theme_info_utils.js';
19
17
  import { ThemeChecksums } from '../../class/checksums/theme_checksums.js';
20
18
  import { ThemeFilesUpload } from '../../class/files_upload/theme_files_upload.js';
@@ -24,6 +22,8 @@ import { MissingThemeFiles } from '../ui/missing_theme_files.js';
24
22
  import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
25
23
  import { THEME_FILES_STRUCTURE_FILE_NAME } from '../../features/theme/theme_constants.js';
26
24
  import { ThemeError } from '../ui/theme_error.js';
25
+ import { mapToPermissionsTree } from '../../utils/directory_validator/directory_validator_utils.js';
26
+ import { mapChecksumToTree } from '../../../utils/checksums/checksums_utils.js';
27
27
  import { ThemeUnpermittedActionsError } from './ui/theme_unpermitted_actions_error.js';
28
28
  import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
29
29
  export class ThemePushCommand extends BaseThemeCommand {
@@ -68,7 +68,7 @@ export class ThemePushCommand extends BaseThemeCommand {
68
68
  return;
69
69
  }
70
70
  const initialChecksums = await themeChecksums.getInitialChecksums();
71
- const permissions = await ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
71
+ const permissions = await ThemeFilesUtils.getFilesPermissions(executionContext.themeRootDir);
72
72
  const themeFilesUploadApi = new ThemeFilesUpload({
73
73
  themeRootDir: executionContext.themeRootDir,
74
74
  credentials,
@@ -80,7 +80,7 @@ export class ThemePushCommand extends BaseThemeCommand {
80
80
  renderOnce(React.createElement(ThemePushSkipInfo, null));
81
81
  return;
82
82
  }
83
- const validationResult = await ThemeFilesStructureUtils.validateThemeDirectoryStructure({
83
+ const validationResult = await ThemeFilesUtils.validateThemeDirectoryStructure({
84
84
  //TDO przeniesc do theme checksums
85
85
  checksums: mapChecksumToTree(initialChecksums),
86
86
  permissions: mapToPermissionsTree(permissions),
@@ -92,16 +92,25 @@ export class ThemePushCommand extends BaseThemeCommand {
92
92
  renderOnce(React.createElement(ThemeUnpermittedActionsError, { unpermittedActions: validationResult.unpermittedActions }));
93
93
  return;
94
94
  }
95
- const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(executionContext.themeRootDir);
95
+ const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(executionContext.themeRootDir);
96
96
  if (!filesStructure) {
97
97
  renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
98
98
  return;
99
99
  }
100
100
  spinner = ora('Pushing theme...').start();
101
- await themePushApi.push({
101
+ const partialPushAction = themeActionsApi.getThemeAction({
102
+ actionType: THEME_ACTIONS_TYPES.partialPush,
103
+ themeId: executionContext.themeId,
104
+ credentials
105
+ });
106
+ if (!pushAction) {
107
+ renderOnce(React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "push" }));
108
+ return;
109
+ }
110
+ await themePushApi.partialPush({
102
111
  credentials,
103
112
  filesStructure,
104
- pushAction,
113
+ action: partialPushAction,
105
114
  themeChecksums,
106
115
  executionContext,
107
116
  themeFilesUploadApi
@@ -1,7 +1,6 @@
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 { ThemeFilesStructureUtils } from '../features/theme/utils/files_structure/theme_files_structure_utils.js';
5
4
  import { ThemeChecksums } from '../class/checksums/theme_checksums.js';
6
5
  import { THEME_VERIFY_API_NAME } from '../features/theme/verify/theme_verify_constants.js';
7
6
  import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
@@ -18,6 +17,7 @@ import { MissingCredentialsError } from '../../cli/commands/auth/ui/missing_cred
18
17
  import { OutsideOfThemeDirectoryContextError } from './ui/ouside_of_theme_directory_context_error.js';
19
18
  import ora from 'ora';
20
19
  import { ThemeError } from './ui/theme_error.js';
20
+ import { ThemeFilesUtils } from '../features/theme/utils/files/theme_files_utils.js';
21
21
  import { LOGGER_API_NAME } from '../../cli/utilities/features/logger/logger_constants.js';
22
22
  export class ThemeVerifyCommand extends BaseThemeCommand {
23
23
  static description = 'Verify theme files structure';
@@ -39,7 +39,7 @@ export class ThemeVerifyCommand extends BaseThemeCommand {
39
39
  themeDir: executionContext.themeRootDir,
40
40
  loggerApi
41
41
  });
42
- const permissions = await ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
42
+ const permissions = await ThemeFilesUtils.getFilesPermissions(executionContext.themeRootDir);
43
43
  if (!themeChecksums || !permissions)
44
44
  this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
45
45
  let spinner;
@@ -56,7 +56,7 @@ export class ThemeVerifyCommand extends BaseThemeCommand {
56
56
  themeId: executionContext.themeId,
57
57
  credentials
58
58
  });
59
- const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(executionContext.themeRootDir);
59
+ const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(executionContext.themeRootDir);
60
60
  if (!filesStructure) {
61
61
  renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
62
62
  return;
@@ -19,8 +19,8 @@ export const ThemeError = ({ err, executionContext }) => {
19
19
  return React.createElement(ThemeWorkUrlMismatch, { command: "verify" });
20
20
  }
21
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.")));
22
+ return (React.createElement(Error, { header: "Uploading theme files to the shop failed." },
23
+ React.createElement(Text, null, "Please ensure that the files are not corrupted and that the file extensions are supported or file size is not too large.")));
24
24
  }
25
25
  if (err?.message) {
26
26
  return React.createElement(ValidationErrors, { errors: err.message });
@@ -8,7 +8,8 @@ export const THEME_ACTIONS_TYPES = {
8
8
  delete: 'delete',
9
9
  publishForm: 'publish_form',
10
10
  publish: 'publish',
11
- verify: 'verify'
11
+ verify: 'verify',
12
+ partialPush: 'partial_push'
12
13
  };
13
14
  export const THEME_ACTION_DATA_TYPE = {
14
15
  file: 'file',
@@ -3,8 +3,10 @@ import { basename, looksLikeDirectory, toUnixPath } from '../../../../utils/path
3
3
  import { THEME_ACTION_DATA_TYPE } from './theme_actions_constants.js';
4
4
  import globs from 'fast-glob';
5
5
  import { THEME_PUSH_WILDCARD_GLOBS_FOR_FILES } from '../push/service/theme_push_service_constants.js';
6
+ import micromatch from 'micromatch';
7
+ import difference from 'lodash/difference.js';
6
8
  export class ThemeActionsUtils {
7
- static getFilesGlobsThatMatchesActionName({ filesStructure, actionValue, actionType }) {
9
+ static getFilesGlobsThatMatchesAction({ filesStructure, actionValue, actionType }) {
8
10
  return Object.entries(filesStructure).reduce((acc, [filePath, fileStructureItem]) => {
9
11
  if (fileStructureItem._links?.[actionType] && this._doesActionValueMatch(fileStructureItem._links[actionType], actionValue)) {
10
12
  return [...acc, toUnixPath(filePath)];
@@ -12,11 +14,32 @@ export class ThemeActionsUtils {
12
14
  return acc;
13
15
  }, []);
14
16
  }
15
- static async getFilesRecordsFromActionData({ themeRootDir, themeAction, filesStructure }) {
17
+ static async getFilesThatMatchesAction({ actionType, actionValue, filesStructure, rootDir }) {
18
+ return await globs(ThemeActionsUtils.getFilesGlobsThatMatchesAction({
19
+ actionType,
20
+ actionValue,
21
+ filesStructure
22
+ }), {
23
+ suppressErrors: true,
24
+ onlyFiles: true,
25
+ cwd: rootDir
26
+ });
27
+ }
28
+ static async getDeletedFilesThatMatchesAction({ actionType, actionValue, themeChecksums, filesStructure }) {
29
+ const deletedFiles = await themeChecksums.getDeletedFiles();
30
+ const globs = ThemeActionsUtils.getFilesGlobsThatMatchesAction({
31
+ actionType,
32
+ actionValue,
33
+ filesStructure
34
+ });
35
+ return deletedFiles.filter((path) => !looksLikeDirectory(path) && micromatch.isMatch(path, globs));
36
+ }
37
+ static async getFilesRecordsFromActionData({ themeRootDir, themeAction, filesStructure, themeChecksums }) {
16
38
  const filesRecords = [];
39
+ const initialChecksums = await themeChecksums.getInitialChecksums();
17
40
  for (const [actionKey, actionData] of Object.entries(themeAction.data)) {
18
41
  if (actionData.type === THEME_ACTION_DATA_TYPE.file) {
19
- const filesGlobs = ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
42
+ const filesGlobs = ThemeActionsUtils.getFilesGlobsThatMatchesAction({
20
43
  //TODO remove when backend fixed
21
44
  actionType: 'push',
22
45
  // actionType: themeAction.actionType,
@@ -24,6 +47,7 @@ export class ThemeActionsUtils {
24
47
  filesStructure
25
48
  });
26
49
  for (const fileGlob of filesGlobs) {
50
+ const checksumMatchedFiles = micromatch(Object.keys(initialChecksums), [fileGlob]);
27
51
  const files = await globs(fileGlob, {
28
52
  suppressErrors: true,
29
53
  onlyFiles: true,
@@ -35,13 +59,15 @@ export class ThemeActionsUtils {
35
59
  ? fileGlob.slice(0, fileGlob.length - 2)
36
60
  : fileGlob;
37
61
  }
38
- for (const filePath of files) {
62
+ const deletedFiles = difference(checksumMatchedFiles, files);
63
+ for (const filePath of [...files, ...deletedFiles]) {
39
64
  filesRecords.push({
40
65
  actionData,
41
66
  actionKey,
42
67
  path: filePath,
43
68
  fileName: basename(filePath),
44
- fileGlob: processedFileGlob
69
+ fileGlob: processedFileGlob,
70
+ state: await themeChecksums.getFileState(filePath)
45
71
  });
46
72
  }
47
73
  }
@@ -8,9 +8,9 @@ import { FetchResources } from '../../../../class/fetch_resources/fetch_resource
8
8
  import { jsonIndentTransform } from '../../../../../utils/stream_transforms/json_indent_transform.js';
9
9
  import { JSON_FILE_INDENT } from '../../../../../cli/cli_constants.js';
10
10
  import { AppError } from '../../../../../cli/utilities/features/logger/logs/app_error.js';
11
- import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
12
11
  import { ThemeChecksums } from '../../../../class/checksums/theme_checksums.js';
13
12
  import { ThemeMetaDataUtils } from '../../utils/meta_data/theme_meta_data_utils.js';
13
+ import { ThemeFilesUtils } from '../../utils/files/theme_files_utils.js';
14
14
  export class ThemeFetchService {
15
15
  #themeHttpApi;
16
16
  #httpApi;
@@ -55,7 +55,7 @@ export class ThemeFetchService {
55
55
  }
56
56
  try {
57
57
  this.#loggerApi.debug('Updating theme files structure.');
58
- await ThemeFilesStructureUtils.updateFilesStructure(themeDir);
58
+ await ThemeFilesUtils.updateFilesStructure(themeDir);
59
59
  }
60
60
  catch (error) {
61
61
  throw new AppError({
@@ -6,8 +6,8 @@ import { extractZip } from '../../../../../utils/zip/extract_zip_utils.js';
6
6
  import { ThemeMetaDataUtils } from '../../utils/meta_data/theme_meta_data_utils.js';
7
7
  import { ThemeInfoUtils } from '../../info/theme_info_utils.js';
8
8
  import { ThemeChecksums } from '../../../../class/checksums/theme_checksums.js';
9
- import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
10
9
  import { AppError } from '../../../../../cli/utilities/features/logger/logs/app_error.js';
10
+ import { ThemeFilesUtils } from '../../utils/files/theme_files_utils.js';
11
11
  export class ThemeInitService {
12
12
  #httpApi;
13
13
  #loggerApi;
@@ -41,7 +41,7 @@ export class ThemeInitService {
41
41
  this.#loggerApi.info('Theme archive extracted.');
42
42
  try {
43
43
  this.#loggerApi.debug('Updating theme files structure.');
44
- await ThemeFilesStructureUtils.updateFilesStructure(themeDir);
44
+ await ThemeFilesUtils.updateFilesStructure(themeDir);
45
45
  }
46
46
  catch (err) {
47
47
  throw new AppError({
@@ -2,9 +2,9 @@ import FSTree from 'fs-tree-diff';
2
2
  import { copyFileSync, getAllDirectoriesNamesInside } from '../../../../../utils/fs/fs_utils.js';
3
3
  import walkSync from 'walk-sync';
4
4
  import { ThemeChecksums } from '../../../../class/checksums/theme_checksums.js';
5
- import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
6
5
  import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
7
6
  import { join, platformSeparator } from '../../../../../utils/path_utils.js';
7
+ import { ThemeFilesUtils } from '../../utils/files/theme_files_utils.js';
8
8
  export class ThemeMergeService {
9
9
  #loggerApi;
10
10
  constructor({ loggerApi }) {
@@ -73,7 +73,7 @@ export class ThemeMergeService {
73
73
  return patch;
74
74
  }
75
75
  async _getUserRootDirectories(themeDir) {
76
- const filesStructure = await ThemeFilesStructureUtils.getThemeRootDirectories(themeDir);
76
+ const filesStructure = await ThemeFilesUtils.getThemeRootDirectories(themeDir);
77
77
  return (await getAllDirectoriesNamesInside(themeDir, {
78
78
  recursive: false,
79
79
  hidden: true
@@ -7,7 +7,7 @@ export class ThemePushApi extends FeatureApi {
7
7
  super();
8
8
  this.#service = service;
9
9
  }
10
- async push(props) {
11
- return this.#service.push(props);
10
+ async partialPush(props) {
11
+ return this.#service.partialPush(props);
12
12
  }
13
13
  }
@@ -1,19 +1,21 @@
1
1
  import tmp from 'tmp-promise';
2
2
  import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
3
- import { join } from '../../../../../utils/path_utils.js';
3
+ import { dirname, join } from '../../../../../utils/path_utils.js';
4
4
  import { v4 as uuid } from 'uuid';
5
5
  import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
6
- import { THEME_MODULE_SETTINGS_FILE_NAME } from '../theme_push_constants.js';
6
+ import { THEME_FILES_LIST_FILE_NAME, THEME_MODULE_SETTINGS_FILE_NAME } from '../theme_push_constants.js';
7
7
  import { ThemePushErrorsFactory } from '../theme_push_errors_factory.js';
8
8
  import { ThemeImagesUtils } from '../../utils/theme_images_utils.js';
9
9
  import { ThemePublishUtils } from '../../skinstore/theme_publish_utils.js';
10
- import { writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
10
+ import { fileExists, getAllDirectoriesNamesInside, readJSONFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
11
11
  import { MODULES_DIRECTORY_NAME } from '../../theme_constants.js';
12
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';
13
+ import { ThemeFilesUtils } from '../../utils/files/theme_files_utils.js';
15
14
  import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
16
- import { ArrayUtils } from '@dreamcommerce/utilities';
15
+ import { ThemeArchiveUtils } from '../../utils/archive/theme_archive_utils.js';
16
+ import { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
17
+ import uniq from 'lodash/uniq.js';
18
+ import { FILES_LIST_CUSTOM_MODULES_TO_KEEP_IDS } from '../../utils/files/them_files_constants.js';
17
19
  export class ThemePushService {
18
20
  #themeFetchApi;
19
21
  #loggerApi;
@@ -21,8 +23,7 @@ export class ThemePushService {
21
23
  this.#themeFetchApi = themeFetchApi;
22
24
  this.#loggerApi = loggerApi;
23
25
  }
24
- async push({ pushAction, credentials, filesStructure, executionContext, themeChecksums, themeFilesUploadApi }) {
25
- this.#loggerApi.info('Pushing theme changes.');
26
+ async partialPush({ action, filesStructure, executionContext, themeChecksums, themeFilesUploadApi, credentials }) {
26
27
  const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
27
28
  this.#loggerApi.debug('Temporary directory created.', { details: { path: tmpDir } });
28
29
  const themeRootDir = executionContext.themeRootDir;
@@ -30,48 +31,72 @@ export class ThemePushService {
30
31
  throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
31
32
  try {
32
33
  this.#loggerApi.debug('Getting file records from action data.');
34
+ //TODO to do api?
33
35
  const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
34
36
  themeRootDir,
35
- themeAction: pushAction,
36
- filesStructure
37
+ themeAction: action,
38
+ filesStructure,
39
+ themeChecksums
37
40
  });
38
41
  this.#loggerApi.debug('Filtering for created or modified files to upload.');
39
- const filesToUpload = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenCreated(path)) || (await themeChecksums.hasThemeFileBeenModified(path)));
42
+ const filesToUpload = filesRecords.filter(({ state }) => [FILE_STATES.modified, FILE_STATES.created].includes(state));
43
+ let localFileNameToUploaded;
40
44
  this.#loggerApi.debug('Files to upload determined.', { details: { count: filesToUpload.length } });
41
45
  if (filesToUpload.length) {
42
46
  this.#loggerApi.info(`Uploading ${filesToUpload.length} theme files.`);
43
- const { localFileNameToUploaded } = await this._uploadThemeFiles({
47
+ const uploadData = await this._uploadThemeFiles({
44
48
  filesToUpload,
45
49
  credentials,
46
50
  themeRootDir,
47
51
  themeFilesUploadApi
48
52
  });
53
+ if (!uploadData.isSuccess)
54
+ throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl);
49
55
  this.#loggerApi.info('Theme files uploaded.');
50
- await this._createFilesList(themeRootDir, filesRecords, localFileNameToUploaded);
51
- }
52
- else {
53
- this.#loggerApi.info('No new or modified files to upload. Creating files list for archive.');
54
- await this._createFilesList(themeRootDir, filesRecords);
56
+ localFileNameToUploaded = uploadData.localFileNameToUploadedMap;
55
57
  }
56
58
  const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
57
- this.#loggerApi.info('Creating theme archive.');
58
- await new ThemeArchive(themeRootDir).createFullArchive({
59
- dist: themeArchivePath,
59
+ const filesInArchive = await ThemeActionsUtils.getFilesThatMatchesAction({
60
60
  actionValue: THEME_WILDCARD_ACTION_NAME,
61
61
  actionType: THEME_ACTIONS_TYPES.push,
62
- logger: this.#loggerApi
62
+ filesStructure,
63
+ rootDir: themeRootDir
64
+ });
65
+ const groupedFiles = await themeChecksums.groupFilesByStatus(filesInArchive);
66
+ const deletedFiles = await ThemeActionsUtils.getDeletedFilesThatMatchesAction({
67
+ actionType: THEME_ACTIONS_TYPES.push,
68
+ actionValue: THEME_WILDCARD_ACTION_NAME,
69
+ themeChecksums,
70
+ filesStructure,
71
+ rootDir: themeRootDir
72
+ });
73
+ await this._createFilesList({
74
+ themeRootDir,
75
+ filesRecords,
76
+ localFileNameToUploaded,
77
+ deletedFiles,
78
+ allCustomModulesIds: await this._getAllModulesIds(themeRootDir)
79
+ });
80
+ this.#loggerApi.info('Creating theme archive.');
81
+ await ThemeArchiveUtils.create({
82
+ dist: themeArchivePath,
83
+ rootDir: themeRootDir,
84
+ logger: this.#loggerApi,
85
+ files: [
86
+ ...(await this._appendSettingsIfModifiedFileFromFolderThatContainsOne(groupedFiles.modified)),
87
+ ...groupedFiles.created,
88
+ (await fileExists(ThemeFilesUtils.getFilesListFilePath(themeRootDir))) ? THEME_FILES_LIST_FILE_NAME : undefined
89
+ ].filter(Boolean)
63
90
  });
64
91
  this.#loggerApi.info('Theme archive created.');
92
+ await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
65
93
  const { resources, modules } = await themeFilesUploadApi.uploadArchive({
66
- action: pushAction,
94
+ action,
67
95
  themeArchivePath,
68
96
  credentials
69
97
  });
70
- this.#loggerApi.info('Theme archive uploaded.');
71
- if (modules) {
72
- this.#loggerApi.debug('Updating data for new modules.');
98
+ if (modules)
73
99
  await this._updateDataForNewCreatedModules({ modules, themeRootDir });
74
- }
75
100
  if (resources) {
76
101
  this.#loggerApi.debug('Removing old resources.');
77
102
  await removeOldResources(themeRootDir, resources);
@@ -80,12 +105,13 @@ export class ThemePushService {
80
105
  this.#loggerApi.info('New resources fetched.');
81
106
  }
82
107
  this.#loggerApi.debug('Updating theme checksums.');
108
+ //TODO only changed files checksum
83
109
  await themeChecksums.updateAllChecksums();
84
110
  this.#loggerApi.info('Push completed successfully.');
85
111
  }
86
- finally {
87
- this.#loggerApi.debug('Cleaning up files list.');
88
- await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
112
+ catch (err) {
113
+ await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
114
+ throw err;
89
115
  }
90
116
  }
91
117
  async _uploadThemeFiles({ filesToUpload, themeRootDir, credentials, themeFilesUploadApi }) {
@@ -103,7 +129,9 @@ export class ThemePushService {
103
129
  await ThemeImagesUtils.removeUploadedOriginalFiles(themeRootDir, uploadedImageData);
104
130
  }
105
131
  return {
106
- localFileNameToUploaded: uploadedImageData.reduce((acc, { originalFilename, uploadedFilename }) => {
132
+ isSuccess: rejectedImageData.length === 0,
133
+ rejectedImageData,
134
+ localFileNameToUploadedMap: uploadedImageData.reduce((acc, { originalFilename, uploadedFilename }) => {
107
135
  return {
108
136
  ...acc,
109
137
  [originalFilename]: uploadedFilename ?? originalFilename
@@ -121,8 +149,38 @@ export class ThemePushService {
121
149
  await writeJSONFile(join(themeRootDir, MODULES_DIRECTORY_NAME, moduleDirectoryName, THEME_MODULE_SETTINGS_FILE_NAME), JSON.parse(metaData[THEME_MODULE_SETTINGS_FILE_NAME]));
122
150
  }
123
151
  }
124
- async _createFilesList(themeRootDir, filesRecords, localFileNameToUploaded = {}) {
152
+ async _createFilesList({ deletedFiles, localFileNameToUploaded, filesRecords, themeRootDir, allCustomModulesIds }) {
125
153
  this.#loggerApi.debug('Creating filesList.json for the archive.');
126
- await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded));
154
+ const filesListContent = ThemeFilesUtils.mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded);
155
+ if (deletedFiles?.length)
156
+ filesListContent.removed = deletedFiles;
157
+ if (allCustomModulesIds?.length)
158
+ filesListContent[FILES_LIST_CUSTOM_MODULES_TO_KEEP_IDS] = allCustomModulesIds;
159
+ await ThemeFilesUtils.createAFilesListFile(themeRootDir, filesListContent);
160
+ }
161
+ async _appendSettingsIfModifiedFileFromFolderThatContainsOne(modifiedFiles) {
162
+ const withSettingsFiles = [...modifiedFiles];
163
+ for (const filePath of modifiedFiles) {
164
+ if (await fileExists(join(dirname(filePath), 'settings.json'))) {
165
+ withSettingsFiles.push(join(dirname(filePath), 'settings.json'));
166
+ }
167
+ }
168
+ return uniq(withSettingsFiles);
169
+ }
170
+ async _getAllModulesIds(themeRootDir) {
171
+ const modulesPath = join(themeRootDir, MODULES_DIRECTORY_NAME);
172
+ if (!(await fileExists(modulesPath)))
173
+ return [];
174
+ const moduleDirs = await getAllDirectoriesNamesInside(modulesPath);
175
+ const modulesIds = [];
176
+ for (const moduleDir of moduleDirs) {
177
+ const moduleSettingsPath = join(themeRootDir, MODULES_DIRECTORY_NAME, moduleDir, THEME_MODULE_SETTINGS_FILE_NAME);
178
+ if (await fileExists(moduleSettingsPath)) {
179
+ const settingsContent = await readJSONFile(moduleSettingsPath);
180
+ if (settingsContent.id && Number.isInteger(settingsContent.id))
181
+ modulesIds.push(settingsContent.id);
182
+ }
183
+ }
184
+ return modulesIds;
127
185
  }
128
186
  }
@@ -1,10 +1,10 @@
1
1
  import globs from 'fast-glob';
2
2
  import { AppError } from '../../../../cli/utilities/features/logger/logs/app_error.js';
3
3
  import { toUnixPath } from '../../../../utils/path_utils.js';
4
- import { ThemeFilesStructureUtils } from '../utils/files_structure/theme_files_structure_utils.js';
4
+ import { ThemeFilesUtils } from '../utils/files/theme_files_utils.js';
5
5
  export class ThemePushUtils {
6
6
  static async getAllFilesThatAreSendToRemote(themeDir) {
7
- const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDir);
7
+ const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDir);
8
8
  if (!filesStructure)
9
9
  throw new AppError({
10
10
  message: `Files structure not found in theme directory: ${themeDir}`,
@@ -0,0 +1,25 @@
1
+ import { createZip } from '../../../../../utils/zip/create_zip_utils.js';
2
+ import { extname, join } from '../../../../../utils/path_utils.js';
3
+ import { formatJSONFile } from '../../../../../utils/fs/fs_utils.js';
4
+ import { ThemeArchiveErrorsFactory } from './theme_archive_utils_errors_factory.js';
5
+ export class ThemeArchiveUtils {
6
+ static async create({ dist, files, rootDir, logger }) {
7
+ try {
8
+ await this._formatJsonFiles(rootDir, files);
9
+ return createZip({
10
+ files,
11
+ baseDir: rootDir,
12
+ logger,
13
+ dist
14
+ });
15
+ }
16
+ catch (err) {
17
+ throw ThemeArchiveErrorsFactory.createErrorWhileCreatingThemeArchive(err);
18
+ }
19
+ }
20
+ static async _formatJsonFiles(themeRootDir, filesToUpload) {
21
+ await Promise.all(filesToUpload
22
+ .filter((path) => extname(path).toLowerCase() === '.json')
23
+ .map((jsonFile) => formatJSONFile(join(themeRootDir, jsonFile))));
24
+ }
25
+ }
@@ -1,4 +1,4 @@
1
- import { AppError } from '../../../cli/utilities/features/logger/logs/app_error.js';
1
+ import { AppError } from '../../../../../cli/utilities/features/logger/logs/app_error.js';
2
2
  export class ThemeArchiveErrorsFactory {
3
3
  static createErrorWhileCreatingThemeArchive(error) {
4
4
  return new AppError({
@@ -0,0 +1 @@
1
+ export const FILES_LIST_CUSTOM_MODULES_TO_KEEP_IDS = 'customModulesIdsToKeep';
@@ -1,4 +1,4 @@
1
- import { readJSONFile, removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
1
+ import { fileExists, isDirectory, readJSONFile, removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
2
2
  import { join, looksLikeDirectory, mapKeysPathsToWindowPlatform, toUnixPath } from '../../../../../utils/path_utils.js';
3
3
  import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
4
4
  import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME, THEME_FILES_STRUCTURE_FILE_NAME, THEME_INITIAL_CHECKSUMS_FILE_NAME, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME } from '../../theme_constants.js';
@@ -7,7 +7,8 @@ import { validateDirectory } from '../../../../utils/directory_validator/directo
7
7
  import path from 'node:path';
8
8
  import { THEME_FILES_LIST_FILE_NAME } from '../../push/theme_push_constants.js';
9
9
  import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
10
- export class ThemeFilesStructureUtils {
10
+ import { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
11
+ export class ThemeFilesUtils {
11
12
  static async getThemeFilesStructure(themeDirectory) {
12
13
  const filesStructure = await readJSONFile(join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME));
13
14
  if (!isWindowsOs())
@@ -19,7 +20,7 @@ export class ThemeFilesStructureUtils {
19
20
  await writeJSONFile(filePath, filesStructure);
20
21
  }
21
22
  static async getFilesPermissions(themeDirectory) {
22
- const fileStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDirectory);
23
+ const fileStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDirectory);
23
24
  return Object.entries(fileStructure).reduce((acc, [key, value]) => {
24
25
  return {
25
26
  ...acc,
@@ -35,14 +36,25 @@ export class ThemeFilesStructureUtils {
35
36
  });
36
37
  }
37
38
  static async getThemeRootDirectories(themeDirectory) {
38
- const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDirectory);
39
+ const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDirectory);
39
40
  return Object.keys(filesStructure).filter((path) => {
40
41
  return looksLikeDirectory(path);
41
42
  });
42
43
  }
43
44
  static mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded = {}) {
44
- const mappedFilesRecords = filesRecords.reduce((acc, { fileGlob, fileName }) => {
45
+ let areAllSkinstoreFilesUnchanged = true;
46
+ const filesList = filesRecords.reduce((acc, { fileGlob, fileName, state, actionKey }) => {
45
47
  const name = localFileNameToUploaded[fileName] ?? fileName;
48
+ if (state === FILE_STATES.deleted && actionKey === 'thumbnail') {
49
+ return {
50
+ ...acc,
51
+ [fileGlob]: null
52
+ };
53
+ }
54
+ if (actionKey === 'skinstore_files' && state !== FILE_STATES.unchanged)
55
+ areAllSkinstoreFilesUnchanged = false;
56
+ if (state === FILE_STATES.deleted || (state === FILE_STATES.unchanged && actionKey !== 'skinstore_files'))
57
+ return acc;
46
58
  if (looksLikeDirectory(fileGlob)) {
47
59
  const existingFiles = acc[fileGlob] || [];
48
60
  return {
@@ -57,32 +69,31 @@ export class ThemeFilesStructureUtils {
57
69
  };
58
70
  }
59
71
  }, {});
60
- /*
61
- * Brzydki fix, poprawione w partial push na ładniej
62
- */
63
- if (!mappedFilesRecords['settings/thumbnail.jpg']) {
64
- mappedFilesRecords['settings/thumbnail.jpg'] = null;
65
- }
66
- return mappedFilesRecords;
72
+ if (areAllSkinstoreFilesUnchanged)
73
+ delete filesList['skinstore/files'];
74
+ return filesList;
67
75
  }
68
76
  static async createAFilesListFile(themeRootDir, filesList) {
69
77
  if (!filesList || !Object.keys(filesList).length)
70
78
  return;
71
- const toUnixStyleFilesList = Object.entries(filesList).reduce((acc, [filePath, value]) => {
79
+ const toUnixStyleFilesList = {};
80
+ for (const [filePath, value] of Object.entries(filesList)) {
72
81
  const unixPath = toUnixPath(filePath);
73
- const finalPath = looksLikeDirectory(unixPath) ? `${unixPath}${path.posix.sep}` : unixPath;
74
- return {
75
- ...acc,
76
- [finalPath]: value
77
- };
78
- }, {});
79
- await writeJSONFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME), toUnixStyleFilesList);
82
+ const finalPath = (await fileExists(join(themeRootDir, filePath))) && (await isDirectory(join(themeRootDir, filePath)))
83
+ ? `${unixPath}${path.posix.sep}`
84
+ : unixPath;
85
+ toUnixStyleFilesList[finalPath] = value;
86
+ }
87
+ await writeJSONFile(this.getFilesListFilePath(themeRootDir), toUnixStyleFilesList);
88
+ }
89
+ static getFilesListFilePath(themeRootDir) {
90
+ return join(themeRootDir, THEME_FILES_LIST_FILE_NAME);
80
91
  }
81
92
  static async removeAFilesListFile(themeRootDir) {
82
- await removeFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME));
93
+ await removeFile(this.getFilesListFilePath(themeRootDir));
83
94
  }
84
95
  static async updateFilesStructure(themeDir) {
85
- const fileStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDir);
96
+ const fileStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDir);
86
97
  const checksumsFiles = [
87
98
  `${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_FILE_NAME}`,
88
99
  `${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME}`,
@@ -115,6 +126,6 @@ export class ThemeFilesStructureUtils {
115
126
  }
116
127
  };
117
128
  });
118
- await ThemeFilesStructureUtils.writeThemeFilesStructure(themeDir, fileStructure);
129
+ await ThemeFilesUtils.writeThemeFilesStructure(themeDir, fileStructure);
119
130
  }
120
131
  }
@@ -1,9 +1 @@
1
- import { AppError } from '../../../../../cli/utilities/features/logger/logs/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
- });
8
- }
9
- }
1
+ export {};
@@ -3,12 +3,12 @@ import { ThemePublishUtils } from '../../skinstore/theme_publish_utils.js';
3
3
  import { ThemePushErrorsFactory } from '../../push/theme_push_errors_factory.js';
4
4
  import { join } from '../../../../../utils/path_utils.js';
5
5
  import { v4 as uuid } from 'uuid';
6
- import { ThemeArchive } from '../../../../class/archive/theme_archive.js';
6
+ import { ThemeArchiveUtils } from '../../utils/archive/theme_archive_utils.js';
7
+ import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
8
+ import { ThemeFilesUtils } from '../../utils/files/theme_files_utils.js';
7
9
  import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
8
10
  import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
9
- import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
10
- import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
11
- import { ArrayUtils } from '@dreamcommerce/utilities';
11
+ import { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
12
12
  export class ThemeVerifyService {
13
13
  #loggerApi;
14
14
  constructor({ loggerApi }) {
@@ -23,13 +23,15 @@ export class ThemeVerifyService {
23
23
  throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
24
24
  try {
25
25
  this.#loggerApi.debug('Getting file records from action data.');
26
+ //TODO to do api?
26
27
  const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
27
28
  themeRootDir,
28
29
  themeAction: verifyAction,
29
- filesStructure
30
+ filesStructure,
31
+ themeChecksums
30
32
  });
31
33
  this.#loggerApi.debug('Filtering for modified or not created files to verify.');
32
- const filesToVerify = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenModified(path)) || !(await themeChecksums.hasThemeFileBeenCreated(path)));
34
+ const filesToVerify = filesRecords.filter(({ state }) => [FILE_STATES.modified, FILE_STATES.created].includes(state));
33
35
  this.#loggerApi.debug('Files to verify determined.', { details: { count: filesToVerify.length } });
34
36
  if (filesToVerify.length) {
35
37
  this.#loggerApi.info(`Verifying ${filesToVerify.length} theme files.`);
@@ -46,11 +48,16 @@ export class ThemeVerifyService {
46
48
  await this._createFilesList(themeRootDir, filesToVerify);
47
49
  const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
48
50
  this.#loggerApi.info('Creating theme archive for verification.');
49
- await new ThemeArchive(themeRootDir).createFullArchive({
51
+ await ThemeArchiveUtils.create({
50
52
  dist: themeArchivePath,
51
- actionValue: THEME_WILDCARD_ACTION_NAME,
52
- actionType: THEME_ACTIONS_TYPES.push,
53
- logger: this.#loggerApi
53
+ rootDir: themeRootDir,
54
+ logger: this.#loggerApi,
55
+ files: await ThemeActionsUtils.getFilesThatMatchesAction({
56
+ actionValue: THEME_WILDCARD_ACTION_NAME,
57
+ actionType: THEME_ACTIONS_TYPES.push,
58
+ filesStructure,
59
+ rootDir: themeRootDir
60
+ })
54
61
  });
55
62
  this.#loggerApi.info('Theme archive created.');
56
63
  const { isSuccess, messages } = await themeFilesUploadApi.uploadArchive({
@@ -63,11 +70,11 @@ export class ThemeVerifyService {
63
70
  }
64
71
  finally {
65
72
  this.#loggerApi.debug('Cleaning up files list.');
66
- await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
73
+ await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
67
74
  }
68
75
  }
69
76
  async _createFilesList(themeRootDir, filesRecords) {
70
77
  this.#loggerApi.debug('Creating filesList.json for the archive.');
71
- await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords));
78
+ await ThemeFilesUtils.createAFilesListFile(themeRootDir, ThemeFilesUtils.mapFilesRecordsToFilesList(filesRecords));
72
79
  }
73
80
  }
@@ -4,3 +4,6 @@ export const asyncFilter = async (array, callback) => {
4
4
  return array.filter((_v, index) => results[index]);
5
5
  });
6
6
  };
7
+ export const asyncMap = async (array, callback) => {
8
+ return Promise.all(array.map(callback));
9
+ };
@@ -0,0 +1,6 @@
1
+ export const FILE_STATES = {
2
+ created: 'created',
3
+ modified: 'modified',
4
+ deleted: 'deleted',
5
+ unchanged: 'unchanged'
6
+ };
@@ -100,7 +100,7 @@ export const getAllFilesAndDirectoriesInside = async (path, options) => {
100
100
  withFileTypes
101
101
  });
102
102
  };
103
- export const getAllDirectoriesNamesInside = async (path, options) => {
103
+ export const getAllDirectoriesNamesInside = async (path, options = { recursive: false, hidden: false }) => {
104
104
  const { recursive = true, hidden = true } = options;
105
105
  const files = await fsPromises.readdir(path, {
106
106
  recursive,
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@shoper/cli",
3
3
  "packageManager": "yarn@3.2.0",
4
4
  "sideEffects": false,
5
- "version": "0.6.2",
5
+ "version": "0.6.4-0",
6
6
  "description": "CLI tool for Shoper",
7
7
  "author": "Joanna Firek",
8
8
  "license": "MIT",
@@ -72,6 +72,7 @@
72
72
  "strip-ansi": "7.1.0",
73
73
  "tmp-promise": "3.0.3",
74
74
  "uuid": "11.1.0",
75
+ "micromatch": "4.0.8",
75
76
  "walk-sync": "3.0.0",
76
77
  "yauzl": "3.2.0",
77
78
  "yazl": "3.3.1",
@@ -86,14 +87,15 @@
86
87
  "@types/fs-extra": "11.0.4",
87
88
  "@types/jest": "29.5.14",
88
89
  "@types/jsonwebtoken": "9.0.9",
89
- "@types/klaw": "3.0.7",
90
- "@types/lodash": "4.17.17",
91
90
  "@types/node": "18.19.84",
92
91
  "@types/react": "19.1.8",
93
92
  "@types/semver": "7.7.0",
94
93
  "@types/tmp": "0.2.6",
95
94
  "@types/yauzl": "2.10.3",
96
95
  "@types/yazl": "2.4.6",
96
+ "@types/klaw": "3.0.7",
97
+ "@types/lodash": "4.17.17",
98
+ "@types/micromatch": "4.0.9",
97
99
  "@typescript-eslint/eslint-plugin": "8.29.1",
98
100
  "babel-jest": "29.7.0",
99
101
  "eslint": "9.39.1",
@@ -1,11 +0,0 @@
1
- import { FeatureApi } from '@dreamcommerce/star_core';
2
- export class ThemeWatchApi extends FeatureApi {
3
- #service;
4
- constructor(service) {
5
- super();
6
- this.#service = service;
7
- }
8
- async watchTheme(props) {
9
- return this.#service.watchTheme(props);
10
- }
11
- }
@@ -1,48 +0,0 @@
1
- // import chokidar from 'chokidar';
2
- import { relative } from '../../../../../utils/path_utils.js';
3
- export class ThemeWatchService {
4
- #themePushApi;
5
- constructor({ themePushApi }) {
6
- this.#themePushApi = themePushApi;
7
- }
8
- async watchTheme({ themeChecksums, executionContext, onChange, credentials, themeFilesUploadApi, filesStructure, action }) {
9
- const themePath = executionContext.themeRootDir;
10
- console.log(`[watch] Watching for changes in: ${themePath}`);
11
- // const watcher = chokidar.watch(themePath, {
12
- // ignored: /(^|[\/\\])\../, // ignore dotfiles
13
- // persistent: true,
14
- // ignoreInitial: true // Don't trigger on initial scan
15
- // });
16
- const handleFileChange = async (filePath) => {
17
- const relativePath = relative(themePath, filePath);
18
- console.log(`[watch] Detected change in: ${relativePath}. Pushing theme...`);
19
- try {
20
- // Assuming pushTheme handles the logic of pushing the entire theme
21
- return;
22
- //@ts-ignore
23
- await this.#themePushApi.partialPush({
24
- credentials,
25
- filesStructure,
26
- action,
27
- executionContext,
28
- themeChecksums,
29
- themeFilesUploadApi
30
- });
31
- console.log(`[watch] Theme pushed successfully.`);
32
- }
33
- catch (error) {
34
- console.error(`[watch] Error pushing theme:`, error);
35
- }
36
- };
37
- //
38
- // watcher
39
- // .on('add', handleFileChange)
40
- // .on('change', handleFileChange)
41
- // .on('unlink', handleFileChange)
42
- // //@ts-ignore
43
- // .on('error', (error) => console.error(`[watch] Watcher error: ${error}`))
44
- // .on('ready', () => console.log('[watch] Initial scan complete. Ready for changes.'));
45
- // Keep the process alive while watching
46
- return new Promise(() => { });
47
- }
48
- }
@@ -1,2 +0,0 @@
1
- export const THEME_WATCH_FEATURE_NAME = 'ThemeWatch';
2
- export const THEME_WATCH_API_NAME = 'ThemeWatchApi';
@@ -1,20 +0,0 @@
1
- import { FEATURE_CORES_TYPES, SyncFeatureInitializer } from '@dreamcommerce/star_core';
2
- import { THEME_WATCH_FEATURE_NAME } from './theme_watch_constants.js';
3
- import { ThemeWatchService } from './service/theme_watch_service.js';
4
- import { THEME_PUSH_API_NAME } from '../push/theme_push_constants.js';
5
- import { ThemeWatchApi } from './api/theme_watch_api.js';
6
- export class ThemeWatchInitializer extends SyncFeatureInitializer {
7
- static featureName = THEME_WATCH_FEATURE_NAME;
8
- init() {
9
- const themePushApi = this.getApiSync(THEME_PUSH_API_NAME);
10
- const service = new ThemeWatchService({ themePushApi });
11
- return {
12
- cores: [
13
- {
14
- type: FEATURE_CORES_TYPES.api,
15
- instance: new ThemeWatchApi(service)
16
- }
17
- ]
18
- };
19
- }
20
- }