@shoper/cli 0.8.1-3 → 0.8.1-7
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 +1 -11
- 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 -47
- package/build/theme/class/browser/browser.js +109 -0
- package/build/theme/class/checksums/theme_checksums.js +51 -11
- 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 +89 -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 +66 -15
- package/build/theme/features/theme/fetch/service/theme_fetch_service.js +2 -4
- package/build/theme/features/theme/init/service/theme_init_service.js +2 -4
- 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 +6 -9
- 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} +36 -28
- package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +1 -9
- package/build/theme/features/theme/utils/meta_data/theme_meta_data_utils.js +0 -28
- 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/theme/utils/directory_validator/directory_validator_utils.js +4 -11
- 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/fs/fs_constants.js +6 -0
- package/build/utils/fs/fs_utils.js +1 -1
- package/package.json +11 -6
- package/build/theme/utils/shoperignore/shoperignore_utils.js +0 -36
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { COMMAND_PREFIX, WATCH_COMMANDS_NAMES } from './theme_watch_constants.js';
|
|
2
|
+
import { APP_LOGS_TYPES } from '../../../cli/utilities/features/logger/logs/app_logs_constants.js';
|
|
3
|
+
import { ThemeError } from '../ui/theme_error.js';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { CSS_TEXT_COLORS } from '../../../ui/color_constants.js';
|
|
7
|
+
export class ThemeWatchUtils {
|
|
8
|
+
static mapAppLogToUiMessage(log) {
|
|
9
|
+
switch (log.level) {
|
|
10
|
+
case APP_LOGS_TYPES.success:
|
|
11
|
+
return this._mapSuccessLogToUiMessage(log);
|
|
12
|
+
case APP_LOGS_TYPES.info:
|
|
13
|
+
return this._mapInfoLogToUiMessage(log);
|
|
14
|
+
case APP_LOGS_TYPES.error:
|
|
15
|
+
return this._mapErrorLogToUiMessage(log);
|
|
16
|
+
default:
|
|
17
|
+
return log.message;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
static _mapInfoLogToUiMessage(log) {
|
|
21
|
+
if (log.message === 'Browser closed') {
|
|
22
|
+
return `Browser closed. If you want to open it again, use the ${COMMAND_PREFIX}${WATCH_COMMANDS_NAMES.open} command or execute again "shoper theme watch --open".`;
|
|
23
|
+
}
|
|
24
|
+
return log.message;
|
|
25
|
+
}
|
|
26
|
+
static _mapErrorLogToUiMessage(log) {
|
|
27
|
+
return React.createElement(ThemeError, { err: log });
|
|
28
|
+
}
|
|
29
|
+
static _mapSuccessLogToUiMessage(log) {
|
|
30
|
+
return chalk.hex(CSS_TEXT_COLORS.success)(log.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -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,9 +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
|
|
6
|
+
import micromatch from 'micromatch';
|
|
7
|
+
import difference from 'lodash/difference.js';
|
|
7
8
|
export class ThemeActionsUtils {
|
|
8
|
-
static
|
|
9
|
+
static getFilesGlobsThatMatchesAction({ filesStructure, actionValue, actionType }) {
|
|
9
10
|
return Object.entries(filesStructure).reduce((acc, [filePath, fileStructureItem]) => {
|
|
10
11
|
if (fileStructureItem._links?.[actionType] && this._doesActionValueMatch(fileStructureItem._links[actionType], actionValue)) {
|
|
11
12
|
const normalizedPath = this._normalizeFileGlob(toUnixPath(filePath));
|
|
@@ -14,13 +15,32 @@ export class ThemeActionsUtils {
|
|
|
14
15
|
return acc;
|
|
15
16
|
}, []);
|
|
16
17
|
}
|
|
17
|
-
static async
|
|
18
|
+
static async getFilesThatMatchesAction({ actionType, actionValue, filesStructure, rootDir }) {
|
|
19
|
+
return await globs(ThemeActionsUtils.getFilesGlobsThatMatchesAction({
|
|
20
|
+
actionType,
|
|
21
|
+
actionValue,
|
|
22
|
+
filesStructure
|
|
23
|
+
}), {
|
|
24
|
+
suppressErrors: true,
|
|
25
|
+
onlyFiles: true,
|
|
26
|
+
cwd: rootDir
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
static async getDeletedFilesThatMatchesAction({ actionType, actionValue, themeChecksums, filesStructure }) {
|
|
30
|
+
const deletedFiles = await themeChecksums.getDeletedFiles();
|
|
31
|
+
const globs = ThemeActionsUtils.getFilesGlobsThatMatchesAction({
|
|
32
|
+
actionType,
|
|
33
|
+
actionValue,
|
|
34
|
+
filesStructure
|
|
35
|
+
});
|
|
36
|
+
return deletedFiles.filter((path) => !looksLikeDirectory(path) && micromatch.isMatch(path, globs));
|
|
37
|
+
}
|
|
38
|
+
static async getFilesRecordsFromActionData({ themeRootDir, themeAction, filesStructure, themeChecksums }) {
|
|
18
39
|
const filesRecords = [];
|
|
19
|
-
|
|
20
|
-
const ignoreInstance = await loadShoperIgnore(themeRootDir);
|
|
40
|
+
const initialChecksums = await themeChecksums.getInitialChecksums();
|
|
21
41
|
for (const [actionKey, actionData] of Object.entries(themeAction.data)) {
|
|
22
42
|
if (actionData.type === THEME_ACTION_DATA_TYPE.file) {
|
|
23
|
-
const filesGlobs = ThemeActionsUtils.
|
|
43
|
+
const filesGlobs = ThemeActionsUtils.getFilesGlobsThatMatchesAction({
|
|
24
44
|
//TODO remove when backend fixed
|
|
25
45
|
actionType: 'push',
|
|
26
46
|
// actionType: themeAction.actionType,
|
|
@@ -28,25 +48,27 @@ export class ThemeActionsUtils {
|
|
|
28
48
|
filesStructure
|
|
29
49
|
});
|
|
30
50
|
for (const fileGlob of filesGlobs) {
|
|
51
|
+
const checksumMatchedFiles = micromatch(Object.keys(initialChecksums), [fileGlob]);
|
|
31
52
|
const files = await globs(fileGlob, {
|
|
32
53
|
suppressErrors: true,
|
|
33
54
|
onlyFiles: true,
|
|
34
55
|
cwd: themeRootDir
|
|
35
56
|
});
|
|
36
|
-
const filteredFiles = await filterFiles(files, themeRootDir, ignoreInstance);
|
|
37
57
|
let processedFileGlob = fileGlob;
|
|
38
58
|
if (looksLikeDirectory(fileGlob)) {
|
|
39
59
|
processedFileGlob = fileGlob.endsWith(THEME_PUSH_WILDCARD_GLOBS_FOR_FILES)
|
|
40
60
|
? fileGlob.slice(0, fileGlob.length - 2)
|
|
41
61
|
: fileGlob;
|
|
42
62
|
}
|
|
43
|
-
|
|
63
|
+
const deletedFiles = difference(checksumMatchedFiles, files);
|
|
64
|
+
for (const filePath of [...files, ...deletedFiles]) {
|
|
44
65
|
filesRecords.push({
|
|
45
66
|
actionData,
|
|
46
67
|
actionKey,
|
|
47
68
|
path: filePath,
|
|
48
69
|
fileName: basename(filePath),
|
|
49
|
-
fileGlob: processedFileGlob
|
|
70
|
+
fileGlob: processedFileGlob,
|
|
71
|
+
state: await themeChecksums.getFileState(filePath)
|
|
50
72
|
});
|
|
51
73
|
}
|
|
52
74
|
}
|
|
@@ -54,6 +76,41 @@ export class ThemeActionsUtils {
|
|
|
54
76
|
}
|
|
55
77
|
return filesRecords;
|
|
56
78
|
}
|
|
79
|
+
// static async getFilesRecordsFromFiles({
|
|
80
|
+
// files,
|
|
81
|
+
// themeRootDir,
|
|
82
|
+
// themeAction,
|
|
83
|
+
// filesStructure,
|
|
84
|
+
// themeChecksums
|
|
85
|
+
// }: {
|
|
86
|
+
// files: string[];
|
|
87
|
+
// themeRootDir: string;
|
|
88
|
+
// themeAction: TThemeAction;
|
|
89
|
+
// filesStructure: TThemeFilesStructure;
|
|
90
|
+
// themeChecksums: ThemeChecksums;
|
|
91
|
+
// }): Promise<TFileRecord[]> {
|
|
92
|
+
// const fileRecords: TFileRecord[] = [];
|
|
93
|
+
//
|
|
94
|
+
// for (const file of files) {
|
|
95
|
+
// const state = await themeChecksums.getFileState(file);
|
|
96
|
+
//
|
|
97
|
+
// if (state) {
|
|
98
|
+
// fileRecords.push({
|
|
99
|
+
// path: file,
|
|
100
|
+
// state,
|
|
101
|
+
// action: themeAction
|
|
102
|
+
// });
|
|
103
|
+
// }
|
|
104
|
+
// }
|
|
105
|
+
//
|
|
106
|
+
// return fileRecords;
|
|
107
|
+
// }
|
|
108
|
+
static _normalizeFileGlob(fileGlob) {
|
|
109
|
+
if (fileGlob.endsWith('/')) {
|
|
110
|
+
return `${fileGlob}${THEME_PUSH_WILDCARD_GLOBS_FOR_FILES}`;
|
|
111
|
+
}
|
|
112
|
+
return fileGlob;
|
|
113
|
+
}
|
|
57
114
|
static _doesActionValueMatch(currentActionValue, valuesToMatch) {
|
|
58
115
|
if (typeof valuesToMatch === 'string') {
|
|
59
116
|
return currentActionValue === valuesToMatch || valuesToMatch === THEME_ALL_ACTIONS_NAME;
|
|
@@ -63,10 +120,4 @@ export class ThemeActionsUtils {
|
|
|
63
120
|
}
|
|
64
121
|
return false;
|
|
65
122
|
}
|
|
66
|
-
static _normalizeFileGlob(fileGlob) {
|
|
67
|
-
if (fileGlob.endsWith('/')) {
|
|
68
|
-
return `${fileGlob}${THEME_PUSH_WILDCARD_GLOBS_FOR_FILES}`;
|
|
69
|
-
}
|
|
70
|
-
return fileGlob;
|
|
71
|
-
}
|
|
72
123
|
}
|
|
@@ -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({
|
|
@@ -66,8 +66,6 @@ export class ThemeFetchService {
|
|
|
66
66
|
}
|
|
67
67
|
this.#loggerApi.debug('Creating .gitignore file.');
|
|
68
68
|
await ThemeMetaDataUtils.createGitIgnoreFile(themeDir);
|
|
69
|
-
this.#loggerApi.debug('Creating .shoperignore file.');
|
|
70
|
-
await ThemeMetaDataUtils.createShoperIgnoreFile(themeDir);
|
|
71
69
|
this.#loggerApi.debug('Updating metadata file.');
|
|
72
70
|
await ThemeMetaDataUtils.updateMetadataFileWithWorkUrl(themeDir, credentials.shopUrl);
|
|
73
71
|
this.#loggerApi.debug('Updating theme checksums');
|
|
@@ -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({
|
|
@@ -52,8 +52,6 @@ export class ThemeInitService {
|
|
|
52
52
|
}
|
|
53
53
|
this.#loggerApi.debug('Creating .gitignore file.');
|
|
54
54
|
await ThemeMetaDataUtils.createGitIgnoreFile(themeDir);
|
|
55
|
-
this.#loggerApi.debug('Creating .shoperignore file.');
|
|
56
|
-
await ThemeMetaDataUtils.createShoperIgnoreFile(themeDir);
|
|
57
55
|
this.#loggerApi.debug('Updating metadata file.');
|
|
58
56
|
await ThemeMetaDataUtils.updateMetadataFileWithWorkUrl(themeDir, credentials.shopUrl);
|
|
59
57
|
this.#loggerApi.debug('Updating theme checksums.');
|
|
@@ -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,12 +1,11 @@
|
|
|
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 {
|
|
5
|
-
import { filterFiles } from '../../../utils/shoperignore/shoperignore_utils.js';
|
|
4
|
+
import { ThemeFilesUtils } from '../utils/files/theme_files_utils.js';
|
|
6
5
|
import { THEME_PUSH_WILDCARD_GLOBS_FOR_FILES } from './service/theme_push_service_constants.js';
|
|
7
6
|
export class ThemePushUtils {
|
|
8
7
|
static async getAllFilesThatAreSendToRemote(themeDir) {
|
|
9
|
-
const filesStructure = await
|
|
8
|
+
const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDir);
|
|
10
9
|
if (!filesStructure)
|
|
11
10
|
throw new AppError({
|
|
12
11
|
message: `Files structure not found in theme directory: ${themeDir}`,
|
|
@@ -18,13 +17,11 @@ export class ThemePushUtils {
|
|
|
18
17
|
.filter((path) => path !== '*');
|
|
19
18
|
//need unix styles globs
|
|
20
19
|
filesToArchive.push('styles/src/**/*');
|
|
21
|
-
|
|
20
|
+
return await globs(filesToArchive, {
|
|
21
|
+
suppressErrors: true,
|
|
22
22
|
onlyFiles: true,
|
|
23
|
-
cwd: themeDir
|
|
24
|
-
|
|
25
|
-
});
|
|
26
|
-
const filteredFiles = await filterFiles(allFiles, themeDir);
|
|
27
|
-
return filteredFiles.sort();
|
|
23
|
+
cwd: themeDir
|
|
24
|
+
}).then((files) => files.sort());
|
|
28
25
|
}
|
|
29
26
|
static _normalizeFileGlob(fileGlob) {
|
|
30
27
|
if (fileGlob.endsWith('/')) {
|
|
@@ -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';
|