@shoper/cli 0.7.1-1 → 0.8.1-3

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 (52) hide show
  1. package/README.md +11 -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 +0 -1
  5. package/build/cli/utilities/features/logger/logs/app_logs_constants.js +1 -2
  6. package/build/index.js +2 -2
  7. package/build/theme/class/archive/theme_archive.js +47 -1
  8. package/build/theme/{features/theme/utils/archive/theme_archive_utils_errors_factory.js → class/archive/theme_archive_errors_factory.js} +1 -1
  9. package/build/theme/class/checksums/theme_checksums.js +11 -51
  10. package/build/theme/commands/pull/theme_pull_command.js +3 -4
  11. package/build/theme/commands/push/theme_push_command.js +8 -17
  12. package/build/theme/commands/theme_commands_constants.js +1 -2
  13. package/build/theme/commands/theme_verify_command.js +3 -3
  14. package/build/theme/commands/ui/theme_error.js +6 -6
  15. package/build/theme/features/theme/actions/theme_actions_constants.js +1 -2
  16. package/build/theme/features/theme/actions/theme_actions_utils.js +17 -61
  17. package/build/theme/features/theme/fetch/service/theme_fetch_service.js +4 -2
  18. package/build/theme/features/theme/init/service/theme_init_service.js +4 -2
  19. package/build/theme/features/theme/merge/service/theme_merge_service.js +2 -2
  20. package/build/theme/features/theme/push/api/theme_push_api.js +2 -2
  21. package/build/theme/features/theme/push/service/theme_push_service.js +33 -93
  22. package/build/theme/features/theme/push/theme_push_utils.js +17 -7
  23. package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +9 -1
  24. package/build/theme/features/theme/utils/{files/theme_files_utils.js → files_structure/theme_files_structure_utils.js} +28 -36
  25. package/build/theme/features/theme/utils/meta_data/theme_meta_data_utils.js +28 -0
  26. package/build/theme/features/theme/verify/verify/theme_verify_service.js +12 -19
  27. package/build/theme/index.js +2 -6
  28. package/build/theme/utils/directory_validator/directory_validator_utils.js +11 -4
  29. package/build/theme/utils/shoperignore/shoperignore_utils.js +36 -0
  30. package/build/ui/ui_dump/ui_dump.js +4 -9
  31. package/build/utils/array_utils.js +0 -3
  32. package/build/utils/fs/fs_utils.js +1 -1
  33. package/package.json +6 -10
  34. package/build/theme/class/browser/browser.js +0 -108
  35. package/build/theme/commands/watch/theme_watch_command.js +0 -88
  36. package/build/theme/commands/watch/theme_watch_constants.js +0 -21
  37. package/build/theme/commands/watch/theme_watch_utils.js +0 -32
  38. package/build/theme/commands/watch/theme_watching_info.js +0 -14
  39. package/build/theme/commands/watch/watch.js +0 -55
  40. package/build/theme/features/theme/push/service/theme_push_service_types.js +0 -1
  41. package/build/theme/features/theme/utils/archive/theme_archive_utils.js +0 -26
  42. package/build/theme/features/theme/utils/files/them_files_constants.js +0 -1
  43. package/build/theme/features/theme/watch/api/theme_watch_api.js +0 -19
  44. package/build/theme/features/theme/watch/service/theme_watch_service.js +0 -167
  45. package/build/theme/features/theme/watch/theme_watch_constants.js +0 -4
  46. package/build/theme/features/theme/watch/theme_watch_initializer.js +0 -22
  47. package/build/ui/command_input/command_input.js +0 -25
  48. package/build/ui/logs/log_entry.js +0 -12
  49. package/build/ui/logs/logs_constants.js +0 -20
  50. package/build/ui/logs/logs_list.js +0 -18
  51. package/build/ui/logs/use_logs.js +0 -23
  52. package/build/utils/fs/fs_constants.js +0 -6
@@ -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';
5
6
  import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
6
7
  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 ThemeFilesUtils.getThemeRootDirectories(themeDir);
76
+ const filesStructure = await ThemeFilesStructureUtils.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 partialPush(props) {
11
- return this.#service.partialPush(props);
10
+ async push(props) {
11
+ return this.#service.push(props);
12
12
  }
13
13
  }
@@ -1,21 +1,19 @@
1
1
  import tmp from 'tmp-promise';
2
2
  import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
3
- import { dirname, join } from '../../../../../utils/path_utils.js';
3
+ import { 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_FILES_LIST_FILE_NAME, THEME_MODULE_SETTINGS_FILE_NAME } from '../theme_push_constants.js';
6
+ import { 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 { fileExists, getAllDirectoriesNamesInside, readJSONFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
10
+ import { 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 { ThemeFilesUtils } from '../../utils/files/theme_files_utils.js';
13
+ import { ThemeArchive } from '../../../../class/archive/theme_archive.js';
14
+ import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
14
15
  import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
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';
16
+ import { ArrayUtils } from '@dreamcommerce/utilities';
19
17
  export class ThemePushService {
20
18
  #themeFetchApi;
21
19
  #loggerApi;
@@ -23,7 +21,8 @@ export class ThemePushService {
23
21
  this.#themeFetchApi = themeFetchApi;
24
22
  this.#loggerApi = loggerApi;
25
23
  }
26
- async partialPush({ action, filesStructure, executionContext, themeChecksums, themeFilesUploadApi, credentials }) {
24
+ async push({ pushAction, credentials, filesStructure, executionContext, themeChecksums, themeFilesUploadApi }) {
25
+ this.#loggerApi.info('Pushing theme changes.');
27
26
  const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
28
27
  this.#loggerApi.debug('Temporary directory created.', { details: { path: tmpDir } });
29
28
  const themeRootDir = executionContext.themeRootDir;
@@ -31,73 +30,48 @@ export class ThemePushService {
31
30
  throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
32
31
  try {
33
32
  this.#loggerApi.debug('Getting file records from action data.');
34
- //TODO to do api?
35
33
  const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
36
34
  themeRootDir,
37
- themeAction: action,
38
- filesStructure,
39
- themeChecksums
35
+ themeAction: pushAction,
36
+ filesStructure
40
37
  });
41
38
  this.#loggerApi.debug('Filtering for created or modified files to upload.');
42
- const filesToUpload = filesRecords.filter(({ state }) => [FILE_STATES.modified, FILE_STATES.created].includes(state));
43
- let localFileNameToUploaded;
39
+ const filesToUpload = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenCreated(path)) || (await themeChecksums.hasThemeFileBeenModified(path)));
44
40
  this.#loggerApi.debug('Files to upload determined.', { details: { count: filesToUpload.length } });
45
41
  if (filesToUpload.length) {
46
42
  this.#loggerApi.info(`Uploading ${filesToUpload.length} theme files.`);
47
- const uploadData = await this._uploadThemeFiles({
43
+ const { localFileNameToUploaded } = await this._uploadThemeFiles({
48
44
  filesToUpload,
49
45
  credentials,
50
46
  themeRootDir,
51
47
  themeFilesUploadApi
52
48
  });
53
- if (!uploadData.isSuccess)
54
- throw ThemePushErrorsFactory.createErrorWhileUploadingThemeFiles(credentials.shopUrl);
55
49
  this.#loggerApi.info('Theme files uploaded.');
56
- localFileNameToUploaded = uploadData.localFileNameToUploadedMap;
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);
57
55
  }
58
56
  const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
59
- const filesInArchive = await ThemeActionsUtils.getFilesThatMatchesAction({
60
- actionValue: THEME_WILDCARD_ACTION_NAME,
61
- actionType: THEME_ACTIONS_TYPES.push,
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
57
  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({
58
+ await new ThemeArchive(themeRootDir).createFullArchive({
87
59
  dist: themeArchivePath,
88
- rootDir: themeRootDir,
89
- logger: this.#loggerApi,
90
- files: filestToArchive
60
+ actionValue: THEME_WILDCARD_ACTION_NAME,
61
+ actionType: THEME_ACTIONS_TYPES.push,
62
+ logger: this.#loggerApi
91
63
  });
92
64
  this.#loggerApi.info('Theme archive created.');
93
- await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
94
65
  const { resources, modules } = await themeFilesUploadApi.uploadArchive({
95
- action,
66
+ action: pushAction,
96
67
  themeArchivePath,
97
68
  credentials
98
69
  });
99
- if (modules)
70
+ this.#loggerApi.info('Theme archive uploaded.');
71
+ if (modules) {
72
+ this.#loggerApi.debug('Updating data for new modules.');
100
73
  await this._updateDataForNewCreatedModules({ modules, themeRootDir });
74
+ }
101
75
  if (resources) {
102
76
  this.#loggerApi.debug('Removing old resources.');
103
77
  await removeOldResources(themeRootDir, resources);
@@ -106,12 +80,12 @@ export class ThemePushService {
106
80
  this.#loggerApi.info('New resources fetched.');
107
81
  }
108
82
  this.#loggerApi.debug('Updating theme checksums.');
109
- //TODO only changed files checksum
110
83
  await themeChecksums.updateAllChecksums();
84
+ this.#loggerApi.info('Push completed successfully.');
111
85
  }
112
- catch (err) {
113
- await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
114
- throw err;
86
+ finally {
87
+ this.#loggerApi.debug('Cleaning up files list.');
88
+ await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
115
89
  }
116
90
  }
117
91
  async _uploadThemeFiles({ filesToUpload, themeRootDir, credentials, themeFilesUploadApi }) {
@@ -129,9 +103,7 @@ export class ThemePushService {
129
103
  await ThemeImagesUtils.removeUploadedOriginalFiles(themeRootDir, uploadedImageData);
130
104
  }
131
105
  return {
132
- isSuccess: rejectedImageData.length === 0,
133
- rejectedImageData,
134
- localFileNameToUploadedMap: uploadedImageData.reduce((acc, { originalFilename, uploadedFilename }) => {
106
+ localFileNameToUploaded: uploadedImageData.reduce((acc, { originalFilename, uploadedFilename }) => {
135
107
  return {
136
108
  ...acc,
137
109
  [originalFilename]: uploadedFilename ?? originalFilename
@@ -149,40 +121,8 @@ export class ThemePushService {
149
121
  await writeJSONFile(join(themeRootDir, MODULES_DIRECTORY_NAME, moduleDirectoryName, THEME_MODULE_SETTINGS_FILE_NAME), JSON.parse(metaData[THEME_MODULE_SETTINGS_FILE_NAME]));
150
122
  }
151
123
  }
152
- async _createFilesList({ deletedFiles, localFileNameToUploaded, filesRecords, themeRootDir, allCustomModulesIds }) {
124
+ async _createFilesList(themeRootDir, filesRecords, localFileNameToUploaded = {}) {
153
125
  this.#loggerApi.debug('Creating filesList.json for the archive.');
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;
126
+ await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded));
187
127
  }
188
128
  }
@@ -1,10 +1,12 @@
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 { ThemeFilesUtils } from '../utils/files/theme_files_utils.js';
4
+ import { ThemeFilesStructureUtils } from '../utils/files_structure/theme_files_structure_utils.js';
5
+ import { filterFiles } from '../../../utils/shoperignore/shoperignore_utils.js';
6
+ import { THEME_PUSH_WILDCARD_GLOBS_FOR_FILES } from './service/theme_push_service_constants.js';
5
7
  export class ThemePushUtils {
6
8
  static async getAllFilesThatAreSendToRemote(themeDir) {
7
- const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDir);
9
+ const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDir);
8
10
  if (!filesStructure)
9
11
  throw new AppError({
10
12
  message: `Files structure not found in theme directory: ${themeDir}`,
@@ -12,14 +14,22 @@ export class ThemePushUtils {
12
14
  });
13
15
  //need unix styles globs
14
16
  const filesToArchive = Object.keys(filesStructure)
15
- .map((path) => toUnixPath(path))
17
+ .map((path) => ThemePushUtils._normalizeFileGlob(toUnixPath(path)))
16
18
  .filter((path) => path !== '*');
17
19
  //need unix styles globs
18
20
  filesToArchive.push('styles/src/**/*');
19
- return await globs(filesToArchive, {
20
- suppressErrors: true,
21
+ const allFiles = await globs(filesToArchive, {
21
22
  onlyFiles: true,
22
- cwd: themeDir
23
- }).then((files) => files.sort());
23
+ cwd: themeDir,
24
+ suppressErrors: true
25
+ });
26
+ const filteredFiles = await filterFiles(allFiles, themeDir);
27
+ return filteredFiles.sort();
28
+ }
29
+ static _normalizeFileGlob(fileGlob) {
30
+ if (fileGlob.endsWith('/')) {
31
+ return `${fileGlob}${THEME_PUSH_WILDCARD_GLOBS_FOR_FILES}`;
32
+ }
33
+ return fileGlob;
24
34
  }
25
35
  }
@@ -1 +1,9 @@
1
- export {};
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,4 +1,4 @@
1
- import { fileExists, isDirectory, readJSONFile, removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
1
+ import { 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,8 +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
- import { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
11
- export class ThemeFilesUtils {
10
+ import { loadShoperIgnore } from '../../../../utils/shoperignore/shoperignore_utils.js';
11
+ export class ThemeFilesStructureUtils {
12
12
  static async getThemeFilesStructure(themeDirectory) {
13
13
  const filesStructure = await readJSONFile(join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME));
14
14
  if (!isWindowsOs())
@@ -20,7 +20,7 @@ export class ThemeFilesUtils {
20
20
  await writeJSONFile(filePath, filesStructure);
21
21
  }
22
22
  static async getFilesPermissions(themeDirectory) {
23
- const fileStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDirectory);
23
+ const fileStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDirectory);
24
24
  return Object.entries(fileStructure).reduce((acc, [key, value]) => {
25
25
  return {
26
26
  ...acc,
@@ -29,32 +29,23 @@ export class ThemeFilesUtils {
29
29
  }, {});
30
30
  }
31
31
  static async validateThemeDirectoryStructure({ checksums, permissions, rootDirectory }) {
32
+ const ignoreInstance = await loadShoperIgnore(rootDirectory);
32
33
  return await validateDirectory({
33
34
  permissions,
34
35
  checksums,
35
- rootDirectory
36
+ rootDirectory,
37
+ ignoreInstance
36
38
  });
37
39
  }
38
40
  static async getThemeRootDirectories(themeDirectory) {
39
- const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDirectory);
41
+ const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDirectory);
40
42
  return Object.keys(filesStructure).filter((path) => {
41
43
  return looksLikeDirectory(path);
42
44
  });
43
45
  }
44
46
  static mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded = {}) {
45
- let areAllSkinstoreFilesUnchanged = true;
46
- const filesList = filesRecords.reduce((acc, { fileGlob, fileName, state, actionKey }) => {
47
+ const mappedFilesRecords = filesRecords.reduce((acc, { fileGlob, fileName }) => {
47
48
  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;
58
49
  if (looksLikeDirectory(fileGlob)) {
59
50
  const existingFiles = acc[fileGlob] || [];
60
51
  return {
@@ -69,31 +60,32 @@ export class ThemeFilesUtils {
69
60
  };
70
61
  }
71
62
  }, {});
72
- if (areAllSkinstoreFilesUnchanged)
73
- delete filesList['skinstore/files'];
74
- return filesList;
63
+ /*
64
+ * Brzydki fix, poprawione w partial push na ładniej
65
+ */
66
+ if (!mappedFilesRecords['settings/thumbnail.jpg']) {
67
+ mappedFilesRecords['settings/thumbnail.jpg'] = null;
68
+ }
69
+ return mappedFilesRecords;
75
70
  }
76
71
  static async createAFilesListFile(themeRootDir, filesList) {
77
72
  if (!filesList || !Object.keys(filesList).length)
78
73
  return;
79
- const toUnixStyleFilesList = {};
80
- for (const [filePath, value] of Object.entries(filesList)) {
74
+ const toUnixStyleFilesList = Object.entries(filesList).reduce((acc, [filePath, value]) => {
81
75
  const unixPath = toUnixPath(filePath);
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);
76
+ const finalPath = looksLikeDirectory(unixPath) ? `${unixPath}${path.posix.sep}` : unixPath;
77
+ return {
78
+ ...acc,
79
+ [finalPath]: value
80
+ };
81
+ }, {});
82
+ await writeJSONFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME), toUnixStyleFilesList);
91
83
  }
92
84
  static async removeAFilesListFile(themeRootDir) {
93
- await removeFile(this.getFilesListFilePath(themeRootDir));
85
+ await removeFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME));
94
86
  }
95
87
  static async updateFilesStructure(themeDir) {
96
- const fileStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDir);
88
+ const fileStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDir);
97
89
  const checksumsFiles = [
98
90
  `${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_FILE_NAME}`,
99
91
  `${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME}`,
@@ -117,7 +109,7 @@ export class ThemeFilesUtils {
117
109
  },
118
110
  _links: { [THEME_ACTIONS_TYPES.push]: '*' }
119
111
  };
120
- ['.gitignore'].forEach((fileName) => {
112
+ ['.gitignore', '.shoperignore'].forEach((fileName) => {
121
113
  fileStructure[fileName] = {
122
114
  permissions: {
123
115
  canAdd: true,
@@ -126,6 +118,6 @@ export class ThemeFilesUtils {
126
118
  }
127
119
  };
128
120
  });
129
- await ThemeFilesUtils.writeThemeFilesStructure(themeDir, fileStructure);
121
+ await ThemeFilesStructureUtils.writeThemeFilesStructure(themeDir, fileStructure);
130
122
  }
131
123
  }
@@ -58,4 +58,32 @@ export class ThemeMetaDataUtils {
58
58
  });
59
59
  });
60
60
  }
61
+ static async createShoperIgnoreFile(themeDir) {
62
+ return new Promise((resolve, reject) => {
63
+ const shoperIgnoreContent = `# .shoperignore
64
+ # Pliki i katalogi wymienione tutaj będą wykluczane z:
65
+ # - pakowania do archiwum podczas publikacji
66
+ # - uploadowania na serwer
67
+ # - weryfikacji checksum
68
+
69
+ # Przykłady:
70
+ # node_modules/
71
+ # .env
72
+ # .env.*
73
+ # *.log
74
+ # temp/
75
+ # .vscode/
76
+ # .idea/
77
+ `;
78
+ const writeStream = createWriteStream(join(themeDir, '.shoperignore'));
79
+ writeStream.write(shoperIgnoreContent);
80
+ writeStream.end();
81
+ writeStream.on('error', (err) => {
82
+ reject(err);
83
+ });
84
+ writeStream.on('finish', () => {
85
+ resolve();
86
+ });
87
+ });
88
+ }
61
89
  }
@@ -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 { 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';
6
+ import { ThemeArchive } from '../../../../class/archive/theme_archive.js';
9
7
  import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
10
8
  import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
11
- import { FILE_STATES } from '../../../../../utils/fs/fs_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';
12
12
  export class ThemeVerifyService {
13
13
  #loggerApi;
14
14
  constructor({ loggerApi }) {
@@ -23,15 +23,13 @@ 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?
27
26
  const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
28
27
  themeRootDir,
29
28
  themeAction: verifyAction,
30
- filesStructure,
31
- themeChecksums
29
+ filesStructure
32
30
  });
33
31
  this.#loggerApi.debug('Filtering for modified or not created files to verify.');
34
- const filesToVerify = filesRecords.filter(({ state }) => [FILE_STATES.modified, FILE_STATES.created].includes(state));
32
+ const filesToVerify = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenModified(path)) || !(await themeChecksums.hasThemeFileBeenCreated(path)));
35
33
  this.#loggerApi.debug('Files to verify determined.', { details: { count: filesToVerify.length } });
36
34
  if (filesToVerify.length) {
37
35
  this.#loggerApi.info(`Verifying ${filesToVerify.length} theme files.`);
@@ -48,16 +46,11 @@ export class ThemeVerifyService {
48
46
  await this._createFilesList(themeRootDir, filesToVerify);
49
47
  const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
50
48
  this.#loggerApi.info('Creating theme archive for verification.');
51
- await ThemeArchiveUtils.create({
49
+ await new ThemeArchive(themeRootDir).createFullArchive({
52
50
  dist: themeArchivePath,
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
- })
51
+ actionValue: THEME_WILDCARD_ACTION_NAME,
52
+ actionType: THEME_ACTIONS_TYPES.push,
53
+ logger: this.#loggerApi
61
54
  });
62
55
  this.#loggerApi.info('Theme archive created.');
63
56
  const { isSuccess, messages } = await themeFilesUploadApi.uploadArchive({
@@ -70,11 +63,11 @@ export class ThemeVerifyService {
70
63
  }
71
64
  finally {
72
65
  this.#loggerApi.debug('Cleaning up files list.');
73
- await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
66
+ await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
74
67
  }
75
68
  }
76
69
  async _createFilesList(themeRootDir, filesRecords) {
77
70
  this.#loggerApi.debug('Creating filesList.json for the archive.');
78
- await ThemeFilesUtils.createAFilesListFile(themeRootDir, ThemeFilesUtils.mapFilesRecordsToFilesList(filesRecords));
71
+ await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords));
79
72
  }
80
73
  }
@@ -15,8 +15,6 @@ import { ThemePushInitializer } from './features/theme/push/theme_push_initializ
15
15
  import { ThemeVerifyInitializer } from './features/theme/verify/theme_verify_initializer.js';
16
16
  import { ThemeDeleteInitializer } from './features/theme/delete/theme_delete_initalizer.js';
17
17
  import { ThemeActionsInitializer } from './features/theme/actions/theme_actions_initializer.js';
18
- import { ThemeWatchCommand } from './commands/watch/theme_watch_command.js';
19
- import { ThemeWatchInitializer } from './features/theme/watch/theme_watch_initializer.js';
20
18
  export const COMMANDS = {
21
19
  [THEME_COMMANDS_NAME.list]: ThemeListCommand,
22
20
  [THEME_COMMANDS_NAME.pull]: ThemePullCommand,
@@ -25,16 +23,14 @@ export const COMMANDS = {
25
23
  [THEME_COMMANDS_NAME.verify]: ThemeVerifyCommand,
26
24
  [THEME_COMMANDS_NAME.info]: ThemeInfoCommand,
27
25
  [THEME_COMMANDS_NAME.delete]: ThemeDeleteCommand,
28
- [THEME_COMMANDS_NAME.publish]: ThemePublishCommand,
29
- [THEME_COMMANDS_NAME.watch]: ThemeWatchCommand
26
+ [THEME_COMMANDS_NAME.publish]: ThemePublishCommand
30
27
  };
31
28
  export const COMMAND_TO_FEATURES_MAP = {
32
29
  [THEME_COMMANDS_NAME.pull]: [ThemeMergeInitializer, ThemeFetchInitializer],
33
30
  [THEME_COMMANDS_NAME.init]: ThemeInitInitializer,
34
31
  [THEME_COMMANDS_NAME.push]: [ThemeFetchInitializer, ThemePushInitializer],
35
32
  [THEME_COMMANDS_NAME.verify]: ThemeVerifyInitializer,
36
- [THEME_COMMANDS_NAME.delete]: ThemeDeleteInitializer,
37
- [THEME_COMMANDS_NAME.watch]: [ThemeFetchInitializer, ThemePushInitializer, ThemeWatchInitializer]
33
+ [THEME_COMMANDS_NAME.delete]: ThemeDeleteInitializer
38
34
  };
39
35
  export const getThemeInitializersForCommand = (commandName) => {
40
36
  if (!commandName)
@@ -5,7 +5,7 @@ import { computeFileChecksum } from '../../../utils/checksums/checksums_utils.js
5
5
  import { DEFAULT_PERMISSION_FOR_ALL_FILES_KEY, DEFAULT_PERMISSION_FOR_DIRECTORY_KEY, FILES_MODIFICATION_TYPES, PERMISSION_KEY } from './directory_validator_constants.js';
6
6
  import { CHECKSUM_KEY } from '../../../utils/checksums/checksums_utils_constants.js';
7
7
  import _ from 'lodash';
8
- export const validateDirectory = async ({ rootDirectory, permissions, checksums }) => {
8
+ export const validateDirectory = async ({ rootDirectory, permissions, checksums, ignoreInstance }) => {
9
9
  const unpermittedActions = [];
10
10
  await _checkPermissions({
11
11
  permissions,
@@ -14,14 +14,15 @@ export const validateDirectory = async ({ rootDirectory, permissions, checksums
14
14
  parts: [],
15
15
  checksumsPart: checksums,
16
16
  rootDirectory,
17
- unpermittedActions
17
+ unpermittedActions,
18
+ ignoreInstance
18
19
  });
19
20
  return {
20
21
  isValid: !unpermittedActions.length,
21
22
  unpermittedActions
22
23
  };
23
24
  };
24
- const _checkPermissions = async ({ permissionPart, permissionParts, parts = [], checksumsPart, rootDirectory, unpermittedActions, permissions }) => {
25
+ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [], checksumsPart, rootDirectory, unpermittedActions, permissions, ignoreInstance }) => {
25
26
  if (!permissionPart)
26
27
  return;
27
28
  //TODO required files in a custom module directory
@@ -40,6 +41,11 @@ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [],
40
41
  filesInsideCurrentDirectoryObject[path] = true;
41
42
  });
42
43
  for (const file of files) {
44
+ const relativePath = join(parentPath, file);
45
+ // Skip files that are in .shoperignore
46
+ if (ignoreInstance && ignoreInstance.ignores(relativePath)) {
47
+ continue;
48
+ }
43
49
  const { permission, permissionKey } = _getPermission({
44
50
  path: file,
45
51
  fullPath: join(fullPath, file),
@@ -65,7 +71,8 @@ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [],
65
71
  checksumsPart: checksumsPart?.[file] ?? {},
66
72
  rootDirectory,
67
73
  unpermittedActions,
68
- permissions
74
+ permissions,
75
+ ignoreInstance
69
76
  });
70
77
  }
71
78
  }
@@ -0,0 +1,36 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from '../../../utils/path_utils.js';
3
+ import { fileExists } from '../../../utils/fs/fs_utils.js';
4
+ import ignore from 'ignore';
5
+ export const SHOPER_IGNORE_FILE_NAME = '.shoperignore';
6
+ export async function loadShoperIgnore(themeRootDir) {
7
+ const shoperIgnorePath = join(themeRootDir, SHOPER_IGNORE_FILE_NAME);
8
+ if (!(await fileExists(shoperIgnorePath))) {
9
+ return null;
10
+ }
11
+ try {
12
+ const content = await readFile(shoperIgnorePath, 'utf-8');
13
+ const ig = ignore();
14
+ ig.add('.shoperignore');
15
+ ig.add('.gitignore');
16
+ ig.add(content);
17
+ return ig;
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ export async function filterFiles(files, themeRootDir, ignoreInstance) {
24
+ const ig = ignoreInstance !== undefined ? ignoreInstance : await loadShoperIgnore(themeRootDir);
25
+ if (!ig) {
26
+ return files;
27
+ }
28
+ return files.filter((file) => !ig.ignores(file));
29
+ }
30
+ export async function isIgnored(filePath, themeRootDir, ignoreInstance) {
31
+ const ig = ignoreInstance !== undefined ? ignoreInstance : await loadShoperIgnore(themeRootDir);
32
+ if (!ig) {
33
+ return false;
34
+ }
35
+ return ig.ignores(filePath);
36
+ }