@shoper/cli 0.6.4-2 → 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.
- 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 +1 -0
- package/build/cli/utilities/features/logger/logs/app_logs_constants.js +2 -1
- package/build/index.js +2 -2
- package/build/theme/class/archive/theme_archive.js +1 -45
- package/build/theme/class/browser/browser.js +108 -0
- package/build/theme/class/checksums/theme_checksums.js +50 -12
- package/build/theme/commands/pull/theme_pull_command.js +4 -3
- package/build/theme/commands/push/theme_push_command.js +17 -8
- package/build/theme/commands/theme_commands_constants.js +2 -1
- package/build/theme/commands/theme_verify_command.js +3 -3
- package/build/theme/commands/ui/theme_error.js +6 -6
- package/build/theme/commands/watch/theme_watch_command.js +88 -0
- package/build/theme/commands/watch/theme_watch_constants.js +21 -0
- package/build/theme/commands/watch/theme_watch_utils.js +32 -0
- package/build/theme/commands/watch/theme_watching_info.js +14 -0
- package/build/theme/commands/watch/watch.js +55 -0
- package/build/theme/features/theme/actions/theme_actions_constants.js +2 -1
- package/build/theme/features/theme/actions/theme_actions_utils.js +60 -5
- package/build/theme/features/theme/fetch/service/theme_fetch_service.js +2 -2
- package/build/theme/features/theme/init/service/theme_init_service.js +2 -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 +93 -33
- package/build/theme/features/theme/push/service/theme_push_service_types.js +1 -0
- package/build/theme/features/theme/push/theme_push_utils.js +2 -2
- package/build/theme/features/theme/utils/archive/theme_archive_utils.js +26 -0
- package/build/theme/{class/archive/theme_archive_errors_factory.js → features/theme/utils/archive/theme_archive_utils_errors_factory.js} +1 -1
- package/build/theme/features/theme/utils/files/them_files_constants.js +1 -0
- package/build/theme/features/theme/utils/{files_structure/theme_files_structure_utils.js → files/theme_files_utils.js} +34 -23
- package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +1 -9
- package/build/theme/features/theme/utils/hidden_directory/hidden_directory_utils.js +0 -1
- package/build/theme/features/theme/verify/verify/theme_verify_service.js +19 -12
- package/build/theme/features/theme/watch/api/theme_watch_api.js +19 -0
- package/build/theme/features/theme/watch/service/theme_watch_service.js +167 -0
- package/build/theme/features/theme/watch/theme_watch_constants.js +4 -0
- package/build/theme/features/theme/watch/theme_watch_initializer.js +22 -0
- package/build/theme/index.js +6 -2
- package/build/ui/command_input/command_input.js +25 -0
- package/build/ui/logs/log_entry.js +12 -0
- package/build/ui/logs/logs_constants.js +20 -0
- package/build/ui/logs/logs_list.js +18 -0
- package/build/ui/logs/use_logs.js +23 -0
- package/build/ui/ui_dump/ui_dump.js +9 -4
- package/build/utils/array_utils.js +3 -0
- package/build/utils/checksums/checksums_utils.js +0 -2
- package/build/utils/fs/fs_constants.js +6 -0
- package/build/utils/fs/fs_utils.js +1 -1
- 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
|
+
};
|
|
@@ -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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
76
|
+
const filesStructure = await ThemeFilesUtils.getThemeRootDirectories(themeDir);
|
|
77
77
|
return (await getAllDirectoriesNamesInside(themeDir, {
|
|
78
78
|
recursive: false,
|
|
79
79
|
hidden: true
|
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
95
|
+
action,
|
|
67
96
|
themeArchivePath,
|
|
68
97
|
credentials
|
|
69
98
|
});
|
|
70
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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(
|
|
152
|
+
async _createFilesList({ deletedFiles, localFileNameToUploaded, filesRecords, themeRootDir, allCustomModulesIds }) {
|
|
125
153
|
this.#loggerApi.debug('Creating filesList.json for the archive.');
|
|
126
|
-
|
|
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
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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 {
|
|
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
|
|
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 '
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
79
|
+
const toUnixStyleFilesList = {};
|
|
80
|
+
for (const [filePath, value] of Object.entries(filesList)) {
|
|
72
81
|
const unixPath = toUnixPath(filePath);
|
|
73
|
-
const finalPath =
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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(
|
|
93
|
+
await removeFile(this.getFilesListFilePath(themeRootDir));
|
|
83
94
|
}
|
|
84
95
|
static async updateFilesStructure(themeDir) {
|
|
85
|
-
const fileStructure = await
|
|
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
|
|
129
|
+
await ThemeFilesUtils.writeThemeFilesStructure(themeDir, fileStructure);
|
|
119
130
|
}
|
|
120
131
|
}
|
package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js
CHANGED
|
@@ -1,9 +1 @@
|
|
|
1
|
-
|
|
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 {};
|