@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.
- package/README.md +11 -1
- package/build/cli/class/errors/http/http_errors_factory.js +1 -1
- package/build/cli/core/cli_setup.js +6 -6
- package/build/cli/index.js +0 -1
- package/build/cli/utilities/features/logger/logs/app_logs_constants.js +1 -2
- package/build/index.js +2 -2
- package/build/theme/class/archive/theme_archive.js +47 -1
- package/build/theme/{features/theme/utils/archive/theme_archive_utils_errors_factory.js → class/archive/theme_archive_errors_factory.js} +1 -1
- package/build/theme/class/checksums/theme_checksums.js +11 -51
- package/build/theme/commands/pull/theme_pull_command.js +3 -4
- package/build/theme/commands/push/theme_push_command.js +8 -17
- package/build/theme/commands/theme_commands_constants.js +1 -2
- package/build/theme/commands/theme_verify_command.js +3 -3
- package/build/theme/commands/ui/theme_error.js +6 -6
- package/build/theme/features/theme/actions/theme_actions_constants.js +1 -2
- package/build/theme/features/theme/actions/theme_actions_utils.js +17 -61
- package/build/theme/features/theme/fetch/service/theme_fetch_service.js +4 -2
- package/build/theme/features/theme/init/service/theme_init_service.js +4 -2
- package/build/theme/features/theme/merge/service/theme_merge_service.js +2 -2
- package/build/theme/features/theme/push/api/theme_push_api.js +2 -2
- package/build/theme/features/theme/push/service/theme_push_service.js +33 -93
- package/build/theme/features/theme/push/theme_push_utils.js +17 -7
- package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +9 -1
- package/build/theme/features/theme/utils/{files/theme_files_utils.js → files_structure/theme_files_structure_utils.js} +28 -36
- package/build/theme/features/theme/utils/meta_data/theme_meta_data_utils.js +28 -0
- package/build/theme/features/theme/verify/verify/theme_verify_service.js +12 -19
- package/build/theme/index.js +2 -6
- package/build/theme/utils/directory_validator/directory_validator_utils.js +11 -4
- package/build/theme/utils/shoperignore/shoperignore_utils.js +36 -0
- package/build/ui/ui_dump/ui_dump.js +4 -9
- package/build/utils/array_utils.js +0 -3
- package/build/utils/fs/fs_utils.js +1 -1
- package/package.json +6 -10
- package/build/theme/class/browser/browser.js +0 -108
- package/build/theme/commands/watch/theme_watch_command.js +0 -88
- package/build/theme/commands/watch/theme_watch_constants.js +0 -21
- package/build/theme/commands/watch/theme_watch_utils.js +0 -32
- package/build/theme/commands/watch/theme_watching_info.js +0 -14
- package/build/theme/commands/watch/watch.js +0 -55
- package/build/theme/features/theme/push/service/theme_push_service_types.js +0 -1
- package/build/theme/features/theme/utils/archive/theme_archive_utils.js +0 -26
- package/build/theme/features/theme/utils/files/them_files_constants.js +0 -1
- package/build/theme/features/theme/watch/api/theme_watch_api.js +0 -19
- package/build/theme/features/theme/watch/service/theme_watch_service.js +0 -167
- package/build/theme/features/theme/watch/theme_watch_constants.js +0 -4
- package/build/theme/features/theme/watch/theme_watch_initializer.js +0 -22
- package/build/ui/command_input/command_input.js +0 -25
- package/build/ui/logs/log_entry.js +0 -12
- package/build/ui/logs/logs_constants.js +0 -20
- package/build/ui/logs/logs_list.js +0 -18
- package/build/ui/logs/use_logs.js +0 -23
- 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
|
|
76
|
+
const filesStructure = await ThemeFilesStructureUtils.getThemeRootDirectories(themeDir);
|
|
77
77
|
return (await getAllDirectoriesNamesInside(themeDir, {
|
|
78
78
|
recursive: false,
|
|
79
79
|
hidden: true
|
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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(
|
|
124
|
+
async _createFilesList(themeRootDir, filesRecords, localFileNameToUploaded = {}) {
|
|
153
125
|
this.#loggerApi.debug('Creating filesList.json for the archive.');
|
|
154
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
20
|
-
suppressErrors: true,
|
|
21
|
+
const allFiles = await globs(filesToArchive, {
|
|
21
22
|
onlyFiles: true,
|
|
22
|
-
cwd: themeDir
|
|
23
|
-
|
|
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
|
}
|
package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js
CHANGED
|
@@ -1 +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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
11
|
-
export class
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 = (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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(
|
|
85
|
+
await removeFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME));
|
|
94
86
|
}
|
|
95
87
|
static async updateFilesStructure(themeDir) {
|
|
96
|
-
const fileStructure = await
|
|
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
|
|
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 {
|
|
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 {
|
|
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 =
|
|
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
|
|
49
|
+
await new ThemeArchive(themeRootDir).createFullArchive({
|
|
52
50
|
dist: themeArchivePath,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
71
|
+
await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords));
|
|
79
72
|
}
|
|
80
73
|
}
|
package/build/theme/index.js
CHANGED
|
@@ -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
|
+
}
|