@shoper/cli 0.6.4-3 → 0.7.1-1

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 (50) hide show
  1. package/build/cli/class/errors/http/http_errors_factory.js +1 -1
  2. package/build/cli/core/cli_setup.js +6 -6
  3. package/build/cli/index.js +1 -0
  4. package/build/cli/utilities/features/logger/logs/app_logs_constants.js +2 -1
  5. package/build/index.js +2 -2
  6. package/build/theme/class/archive/theme_archive.js +1 -45
  7. package/build/theme/class/browser/browser.js +108 -0
  8. package/build/theme/class/checksums/theme_checksums.js +50 -12
  9. package/build/theme/commands/pull/theme_pull_command.js +4 -3
  10. package/build/theme/commands/push/theme_push_command.js +17 -8
  11. package/build/theme/commands/theme_commands_constants.js +2 -1
  12. package/build/theme/commands/theme_verify_command.js +3 -3
  13. package/build/theme/commands/ui/theme_error.js +6 -6
  14. package/build/theme/commands/watch/theme_watch_command.js +88 -0
  15. package/build/theme/commands/watch/theme_watch_constants.js +21 -0
  16. package/build/theme/commands/watch/theme_watch_utils.js +32 -0
  17. package/build/theme/commands/watch/theme_watching_info.js +14 -0
  18. package/build/theme/commands/watch/watch.js +55 -0
  19. package/build/theme/features/theme/actions/theme_actions_constants.js +2 -1
  20. package/build/theme/features/theme/actions/theme_actions_utils.js +60 -5
  21. package/build/theme/features/theme/fetch/service/theme_fetch_service.js +2 -2
  22. package/build/theme/features/theme/init/service/theme_init_service.js +2 -2
  23. package/build/theme/features/theme/merge/service/theme_merge_service.js +2 -2
  24. package/build/theme/features/theme/push/api/theme_push_api.js +2 -2
  25. package/build/theme/features/theme/push/service/theme_push_service.js +93 -33
  26. package/build/theme/features/theme/push/service/theme_push_service_types.js +1 -0
  27. package/build/theme/features/theme/push/theme_push_utils.js +2 -2
  28. package/build/theme/features/theme/utils/archive/theme_archive_utils.js +26 -0
  29. package/build/theme/{class/archive/theme_archive_errors_factory.js → features/theme/utils/archive/theme_archive_utils_errors_factory.js} +1 -1
  30. package/build/theme/features/theme/utils/files/them_files_constants.js +1 -0
  31. package/build/theme/features/theme/utils/{files_structure/theme_files_structure_utils.js → files/theme_files_utils.js} +34 -23
  32. package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +1 -9
  33. package/build/theme/features/theme/utils/hidden_directory/hidden_directory_utils.js +0 -1
  34. package/build/theme/features/theme/verify/verify/theme_verify_service.js +19 -12
  35. package/build/theme/features/theme/watch/api/theme_watch_api.js +19 -0
  36. package/build/theme/features/theme/watch/service/theme_watch_service.js +167 -0
  37. package/build/theme/features/theme/watch/theme_watch_constants.js +4 -0
  38. package/build/theme/features/theme/watch/theme_watch_initializer.js +22 -0
  39. package/build/theme/index.js +6 -2
  40. package/build/ui/command_input/command_input.js +25 -0
  41. package/build/ui/logs/log_entry.js +12 -0
  42. package/build/ui/logs/logs_constants.js +20 -0
  43. package/build/ui/logs/logs_list.js +18 -0
  44. package/build/ui/logs/use_logs.js +23 -0
  45. package/build/ui/ui_dump/ui_dump.js +9 -4
  46. package/build/utils/array_utils.js +3 -0
  47. package/build/utils/checksums/checksums_utils.js +0 -4
  48. package/build/utils/fs/fs_constants.js +6 -0
  49. package/build/utils/fs/fs_utils.js +1 -1
  50. package/package.json +9 -4
@@ -0,0 +1,14 @@
1
+ import { Info } from '../../../ui/message_box/info.js';
2
+ import React from 'react';
3
+ import { Text } from '../../../ui/text.js';
4
+ import { CSS_COLOR_TOKENS_VALUES } from '../../../ui/color_constants.js';
5
+ import { COMMAND_PREFIX, WATCH_COMMANDS_NAMES } from './theme_watch_constants.js';
6
+ export const ThemeWatchingInfo = ({ themeId, withPreview }) => {
7
+ return (React.createElement(Info, { header: `Watching theme (ID: ${themeId}) files for changes... ` },
8
+ React.createElement(Text, null, "Any modifications will be reflected in real-time."),
9
+ withPreview ? React.createElement(Text, null, "Live preview opened in your browser.") : null,
10
+ React.createElement(Text, { color: CSS_COLOR_TOKENS_VALUES.theme4 },
11
+ "(Press Ctrl+C or type ",
12
+ `${COMMAND_PREFIX}${WATCH_COMMANDS_NAMES.quit}`,
13
+ " to stop watching)")));
14
+ };
@@ -0,0 +1,55 @@
1
+ import { ThemeWatchingInfo } from './theme_watching_info.js';
2
+ import { Box } from '../../../ui/box.js';
3
+ import React, { useEffect } from 'react';
4
+ import { CommandInput } from '../../../ui/command_input/command_input.js';
5
+ import { useApp } from 'ink';
6
+ import { COMMAND_PREFIX, MAX_WATCH_LOGS, WATCH_COMMANDS, WATCH_COMMANDS_NAMES } from './theme_watch_constants.js';
7
+ import { useLogs } from '../../../ui/logs/use_logs.js';
8
+ import { LogsList } from '../../../ui/logs/logs_list.js';
9
+ import { ThemeWatchUtils } from './theme_watch_utils.js';
10
+ export const Watch = ({ executionContext, credentials, openBrowser, themeChecksums, themeFilesUploadApi, filesStructure, action, themeWatchApi }) => {
11
+ const { exit } = useApp();
12
+ const { logs, addLog, clearLogs } = useLogs(MAX_WATCH_LOGS);
13
+ useEffect(() => {
14
+ themeWatchApi.watchTheme({
15
+ themeChecksums,
16
+ executionContext,
17
+ credentials,
18
+ themeFilesUploadApi,
19
+ filesStructure,
20
+ action,
21
+ openBrowser,
22
+ onMessage: (log) => {
23
+ addLog(ThemeWatchUtils.mapAppLogToUiMessage(log));
24
+ }
25
+ });
26
+ return () => {
27
+ themeWatchApi.stopWatching();
28
+ };
29
+ }, []);
30
+ const handleCommandSubmit = async (value) => {
31
+ const command = value.trim();
32
+ addLog(`> ${command}`);
33
+ switch (command) {
34
+ case `${COMMAND_PREFIX}${WATCH_COMMANDS_NAMES.open}`: {
35
+ await themeWatchApi.ensureBrowserTabOpen();
36
+ break;
37
+ }
38
+ case `${COMMAND_PREFIX}${WATCH_COMMANDS_NAMES.quit}`: {
39
+ await themeWatchApi.stopWatching();
40
+ exit();
41
+ break;
42
+ }
43
+ case `${COMMAND_PREFIX}${WATCH_COMMANDS_NAMES.clear}`: {
44
+ clearLogs();
45
+ break;
46
+ }
47
+ default:
48
+ addLog(`Unknown command: ${command}`);
49
+ }
50
+ };
51
+ return (React.createElement(Box, { flexDirection: "column", padding: 1 },
52
+ React.createElement(ThemeWatchingInfo, { withPreview: openBrowser, themeId: executionContext.themeId }),
53
+ React.createElement(LogsList, { logs: logs }),
54
+ React.createElement(CommandInput, { commands: WATCH_COMMANDS, onSubmit: handleCommandSubmit })));
55
+ };
@@ -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
  }
@@ -49,6 +75,35 @@ export class ThemeActionsUtils {
49
75
  }
50
76
  return filesRecords;
51
77
  }
78
+ // static async getFilesRecordsFromFiles({
79
+ // files,
80
+ // themeRootDir,
81
+ // themeAction,
82
+ // filesStructure,
83
+ // themeChecksums
84
+ // }: {
85
+ // files: string[];
86
+ // themeRootDir: string;
87
+ // themeAction: TThemeAction;
88
+ // filesStructure: TThemeFilesStructure;
89
+ // themeChecksums: ThemeChecksums;
90
+ // }): Promise<TFileRecord[]> {
91
+ // const fileRecords: TFileRecord[] = [];
92
+ //
93
+ // for (const file of files) {
94
+ // const state = await themeChecksums.getFileState(file);
95
+ //
96
+ // if (state) {
97
+ // fileRecords.push({
98
+ // path: file,
99
+ // state,
100
+ // action: themeAction
101
+ // });
102
+ // }
103
+ // }
104
+ //
105
+ // return fileRecords;
106
+ // }
52
107
  static _doesActionValueMatch(currentActionValue, valuesToMatch) {
53
108
  if (typeof valuesToMatch === 'string') {
54
109
  return currentActionValue === valuesToMatch || valuesToMatch === THEME_ALL_ACTIONS_NAME;
@@ -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,73 @@ 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
+ //TODO catch error on settings failure
79
+ allCustomModulesIds: await this._getAllModulesIds(themeRootDir)
80
+ });
81
+ this.#loggerApi.info('Creating theme archive.');
82
+ const filestToArchive = [
83
+ ...(await this._appendSettingsIfNeeded([...groupedFiles.modified, ...groupedFiles.created])),
84
+ (await fileExists(ThemeFilesUtils.getFilesListFilePath(themeRootDir))) ? THEME_FILES_LIST_FILE_NAME : undefined
85
+ ].filter(Boolean);
86
+ await ThemeArchiveUtils.create({
87
+ dist: themeArchivePath,
88
+ rootDir: themeRootDir,
89
+ logger: this.#loggerApi,
90
+ files: filestToArchive
63
91
  });
64
92
  this.#loggerApi.info('Theme archive created.');
93
+ await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
65
94
  const { resources, modules } = await themeFilesUploadApi.uploadArchive({
66
- action: pushAction,
95
+ action,
67
96
  themeArchivePath,
68
97
  credentials
69
98
  });
70
- this.#loggerApi.info('Theme archive uploaded.');
71
- if (modules) {
72
- this.#loggerApi.debug('Updating data for new modules.');
99
+ if (modules)
73
100
  await this._updateDataForNewCreatedModules({ modules, themeRootDir });
74
- }
75
101
  if (resources) {
76
102
  this.#loggerApi.debug('Removing old resources.');
77
103
  await removeOldResources(themeRootDir, resources);
@@ -80,12 +106,12 @@ export class ThemePushService {
80
106
  this.#loggerApi.info('New resources fetched.');
81
107
  }
82
108
  this.#loggerApi.debug('Updating theme checksums.');
109
+ //TODO only changed files checksum
83
110
  await themeChecksums.updateAllChecksums();
84
- 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,40 @@ 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 _appendSettingsIfNeeded(files) {
162
+ const withSettingsFiles = [...files];
163
+ for (const filePath of files) {
164
+ const settingsFilePath = join(dirname(filePath), 'settings.json');
165
+ const settingsExist = await fileExists(settingsFilePath);
166
+ if (settingsExist && !!(await readJSONFile(settingsFilePath))?.id) {
167
+ withSettingsFiles.push(join(dirname(filePath), 'settings.json'));
168
+ }
169
+ }
170
+ return uniq(withSettingsFiles);
171
+ }
172
+ async _getAllModulesIds(themeRootDir) {
173
+ const modulesPath = join(themeRootDir, MODULES_DIRECTORY_NAME);
174
+ if (!(await fileExists(modulesPath)))
175
+ return [];
176
+ const moduleDirs = await getAllDirectoriesNamesInside(modulesPath);
177
+ const modulesIds = [];
178
+ for (const moduleDir of moduleDirs) {
179
+ const moduleSettingsPath = join(themeRootDir, MODULES_DIRECTORY_NAME, moduleDir, THEME_MODULE_SETTINGS_FILE_NAME);
180
+ if (await fileExists(moduleSettingsPath)) {
181
+ const settingsContent = await readJSONFile(moduleSettingsPath);
182
+ if (settingsContent.id && Number.isInteger(settingsContent.id))
183
+ modulesIds.push(settingsContent.id);
184
+ }
185
+ }
186
+ return modulesIds;
127
187
  }
128
188
  }
@@ -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,26 @@
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
+ // TODO spradzić czy potrzebne, ew po formatowanu sprawdzic czy plik sie zmienił jak nie to nie podmieniac contentu zformatowanego
9
+ // await this._formatJsonFiles(rootDir, files);
10
+ return createZip({
11
+ files,
12
+ baseDir: rootDir,
13
+ logger,
14
+ dist
15
+ });
16
+ }
17
+ catch (err) {
18
+ throw ThemeArchiveErrorsFactory.createErrorWhileCreatingThemeArchive(err);
19
+ }
20
+ }
21
+ static async _formatJsonFiles(themeRootDir, filesToUpload) {
22
+ await Promise.all(filesToUpload
23
+ .filter((path) => extname(path).toLowerCase() === '.json')
24
+ .map((jsonFile) => formatJSONFile(join(themeRootDir, jsonFile))));
25
+ }
26
+ }
@@ -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 {};