@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
|
@@ -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,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 { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
|
|
11
|
+
export class ThemeFilesUtils {
|
|
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 ThemeFilesStructureUtils {
|
|
|
20
20
|
await writeJSONFile(filePath, filesStructure);
|
|
21
21
|
}
|
|
22
22
|
static async getFilesPermissions(themeDirectory) {
|
|
23
|
-
const fileStructure = await
|
|
23
|
+
const fileStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDirectory);
|
|
24
24
|
return Object.entries(fileStructure).reduce((acc, [key, value]) => {
|
|
25
25
|
return {
|
|
26
26
|
...acc,
|
|
@@ -29,23 +29,32 @@ export class ThemeFilesStructureUtils {
|
|
|
29
29
|
}, {});
|
|
30
30
|
}
|
|
31
31
|
static async validateThemeDirectoryStructure({ checksums, permissions, rootDirectory }) {
|
|
32
|
-
const ignoreInstance = await loadShoperIgnore(rootDirectory);
|
|
33
32
|
return await validateDirectory({
|
|
34
33
|
permissions,
|
|
35
34
|
checksums,
|
|
36
|
-
rootDirectory
|
|
37
|
-
ignoreInstance
|
|
35
|
+
rootDirectory
|
|
38
36
|
});
|
|
39
37
|
}
|
|
40
38
|
static async getThemeRootDirectories(themeDirectory) {
|
|
41
|
-
const filesStructure = await
|
|
39
|
+
const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDirectory);
|
|
42
40
|
return Object.keys(filesStructure).filter((path) => {
|
|
43
41
|
return looksLikeDirectory(path);
|
|
44
42
|
});
|
|
45
43
|
}
|
|
46
44
|
static mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded = {}) {
|
|
47
|
-
|
|
45
|
+
let areAllSkinstoreFilesUnchanged = true;
|
|
46
|
+
const filesList = filesRecords.reduce((acc, { fileGlob, fileName, state, actionKey }) => {
|
|
48
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;
|
|
49
58
|
if (looksLikeDirectory(fileGlob)) {
|
|
50
59
|
const existingFiles = acc[fileGlob] || [];
|
|
51
60
|
return {
|
|
@@ -60,32 +69,31 @@ export class ThemeFilesStructureUtils {
|
|
|
60
69
|
};
|
|
61
70
|
}
|
|
62
71
|
}, {});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (!mappedFilesRecords['settings/thumbnail.jpg']) {
|
|
67
|
-
mappedFilesRecords['settings/thumbnail.jpg'] = null;
|
|
68
|
-
}
|
|
69
|
-
return mappedFilesRecords;
|
|
72
|
+
if (areAllSkinstoreFilesUnchanged)
|
|
73
|
+
delete filesList['skinstore/files'];
|
|
74
|
+
return filesList;
|
|
70
75
|
}
|
|
71
76
|
static async createAFilesListFile(themeRootDir, filesList) {
|
|
72
77
|
if (!filesList || !Object.keys(filesList).length)
|
|
73
78
|
return;
|
|
74
|
-
const toUnixStyleFilesList =
|
|
79
|
+
const toUnixStyleFilesList = {};
|
|
80
|
+
for (const [filePath, value] of Object.entries(filesList)) {
|
|
75
81
|
const unixPath = toUnixPath(filePath);
|
|
76
|
-
const finalPath =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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);
|
|
83
91
|
}
|
|
84
92
|
static async removeAFilesListFile(themeRootDir) {
|
|
85
|
-
await removeFile(
|
|
93
|
+
await removeFile(this.getFilesListFilePath(themeRootDir));
|
|
86
94
|
}
|
|
87
95
|
static async updateFilesStructure(themeDir) {
|
|
88
|
-
const fileStructure = await
|
|
96
|
+
const fileStructure = await ThemeFilesUtils.getThemeFilesStructure(themeDir);
|
|
89
97
|
const checksumsFiles = [
|
|
90
98
|
`${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_FILE_NAME}`,
|
|
91
99
|
`${SHOPER_THEME_METADATA_DIR}/${THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME}`,
|
|
@@ -109,7 +117,7 @@ export class ThemeFilesStructureUtils {
|
|
|
109
117
|
},
|
|
110
118
|
_links: { [THEME_ACTIONS_TYPES.push]: '*' }
|
|
111
119
|
};
|
|
112
|
-
['.gitignore'
|
|
120
|
+
['.gitignore'].forEach((fileName) => {
|
|
113
121
|
fileStructure[fileName] = {
|
|
114
122
|
permissions: {
|
|
115
123
|
canAdd: true,
|
|
@@ -118,6 +126,6 @@ export class ThemeFilesStructureUtils {
|
|
|
118
126
|
}
|
|
119
127
|
};
|
|
120
128
|
});
|
|
121
|
-
await
|
|
129
|
+
await ThemeFilesUtils.writeThemeFilesStructure(themeDir, fileStructure);
|
|
122
130
|
}
|
|
123
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 {};
|
|
@@ -58,32 +58,4 @@ 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
|
-
}
|
|
89
61
|
}
|
|
@@ -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 {
|
|
6
|
+
import { ThemeArchiveUtils } from '../../utils/archive/theme_archive_utils.js';
|
|
7
|
+
import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
|
|
8
|
+
import { ThemeFilesUtils } from '../../utils/files/theme_files_utils.js';
|
|
7
9
|
import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
|
|
8
10
|
import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
|
|
9
|
-
import {
|
|
10
|
-
import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
|
|
11
|
-
import { ArrayUtils } from '@dreamcommerce/utilities';
|
|
11
|
+
import { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
|
|
12
12
|
export class ThemeVerifyService {
|
|
13
13
|
#loggerApi;
|
|
14
14
|
constructor({ loggerApi }) {
|
|
@@ -23,13 +23,15 @@ 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?
|
|
26
27
|
const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
|
|
27
28
|
themeRootDir,
|
|
28
29
|
themeAction: verifyAction,
|
|
29
|
-
filesStructure
|
|
30
|
+
filesStructure,
|
|
31
|
+
themeChecksums
|
|
30
32
|
});
|
|
31
33
|
this.#loggerApi.debug('Filtering for modified or not created files to verify.');
|
|
32
|
-
const filesToVerify =
|
|
34
|
+
const filesToVerify = filesRecords.filter(({ state }) => [FILE_STATES.modified, FILE_STATES.created].includes(state));
|
|
33
35
|
this.#loggerApi.debug('Files to verify determined.', { details: { count: filesToVerify.length } });
|
|
34
36
|
if (filesToVerify.length) {
|
|
35
37
|
this.#loggerApi.info(`Verifying ${filesToVerify.length} theme files.`);
|
|
@@ -46,11 +48,16 @@ export class ThemeVerifyService {
|
|
|
46
48
|
await this._createFilesList(themeRootDir, filesToVerify);
|
|
47
49
|
const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
|
|
48
50
|
this.#loggerApi.info('Creating theme archive for verification.');
|
|
49
|
-
await
|
|
51
|
+
await ThemeArchiveUtils.create({
|
|
50
52
|
dist: themeArchivePath,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
rootDir: themeRootDir,
|
|
54
|
+
logger: this.#loggerApi,
|
|
55
|
+
files: await ThemeActionsUtils.getFilesThatMatchesAction({
|
|
56
|
+
actionValue: THEME_WILDCARD_ACTION_NAME,
|
|
57
|
+
actionType: THEME_ACTIONS_TYPES.push,
|
|
58
|
+
filesStructure,
|
|
59
|
+
rootDir: themeRootDir
|
|
60
|
+
})
|
|
54
61
|
});
|
|
55
62
|
this.#loggerApi.info('Theme archive created.');
|
|
56
63
|
const { isSuccess, messages } = await themeFilesUploadApi.uploadArchive({
|
|
@@ -63,11 +70,11 @@ export class ThemeVerifyService {
|
|
|
63
70
|
}
|
|
64
71
|
finally {
|
|
65
72
|
this.#loggerApi.debug('Cleaning up files list.');
|
|
66
|
-
await
|
|
73
|
+
await ThemeFilesUtils.removeAFilesListFile(themeRootDir);
|
|
67
74
|
}
|
|
68
75
|
}
|
|
69
76
|
async _createFilesList(themeRootDir, filesRecords) {
|
|
70
77
|
this.#loggerApi.debug('Creating filesList.json for the archive.');
|
|
71
|
-
await
|
|
78
|
+
await ThemeFilesUtils.createAFilesListFile(themeRootDir, ThemeFilesUtils.mapFilesRecordsToFilesList(filesRecords));
|
|
72
79
|
}
|
|
73
80
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FeatureApi } from '@dreamcommerce/star_core';
|
|
2
|
+
import { THEME_WATCH_API_NAME } from '../theme_watch_constants.js';
|
|
3
|
+
export class ThemeWatchApi extends FeatureApi {
|
|
4
|
+
moduleName = THEME_WATCH_API_NAME;
|
|
5
|
+
#service;
|
|
6
|
+
constructor(service) {
|
|
7
|
+
super();
|
|
8
|
+
this.#service = service;
|
|
9
|
+
}
|
|
10
|
+
async watchTheme(props) {
|
|
11
|
+
return this.#service.watchTheme(props);
|
|
12
|
+
}
|
|
13
|
+
stopWatching() {
|
|
14
|
+
return this.#service.stopWatching();
|
|
15
|
+
}
|
|
16
|
+
ensureBrowserTabOpen() {
|
|
17
|
+
return this.#service.ensureBrowserTabOpen();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { AppLog } from '../../../../../cli/utilities/features/logger/logs/app_log.js';
|
|
2
|
+
import chokidar from 'chokidar';
|
|
3
|
+
import { APP_LOGS_TYPES } from '../../../../../cli/utilities/features/logger/logs/app_logs_constants.js';
|
|
4
|
+
import { relative } from '../../../../../utils/path_utils.js';
|
|
5
|
+
import { SHOP_BROWSER_TAB_ID, THEME_BATCH_DELAY_MS } from '../theme_watch_constants.js';
|
|
6
|
+
import { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
|
|
7
|
+
export class ThemeWatchService {
|
|
8
|
+
#themePushApi;
|
|
9
|
+
#isPushing = false;
|
|
10
|
+
#pendingPush = false;
|
|
11
|
+
#browserApi;
|
|
12
|
+
#batchTimeout = null;
|
|
13
|
+
#onMessage = null;
|
|
14
|
+
#changedFiles = new Set();
|
|
15
|
+
#watcher = null;
|
|
16
|
+
#urlForWatchedTheme = null;
|
|
17
|
+
constructor({ themePushApi, browserClient }) {
|
|
18
|
+
this.#themePushApi = themePushApi;
|
|
19
|
+
this.#browserApi = new browserClient({
|
|
20
|
+
onDisconnected: () => {
|
|
21
|
+
this._onInfoMessage('Browser closed');
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async watchTheme({ themeChecksums, executionContext, credentials, themeFilesUploadApi, filesStructure, action, openBrowser = false, onMessage }) {
|
|
26
|
+
this.#onMessage = onMessage || null;
|
|
27
|
+
const themePath = executionContext.themeRootDir;
|
|
28
|
+
this.#urlForWatchedTheme = `${credentials.shopUrl}/admin/configSkins/skin-preview/id/${executionContext.themeId}`;
|
|
29
|
+
if (openBrowser) {
|
|
30
|
+
await this.#browserApi.launch();
|
|
31
|
+
await this.#browserApi.openTab(this.#urlForWatchedTheme, SHOP_BROWSER_TAB_ID);
|
|
32
|
+
}
|
|
33
|
+
this._onInfoMessage(`Watching for changes in: ${themePath}`);
|
|
34
|
+
this.#watcher = chokidar.watch(themePath, {
|
|
35
|
+
ignored: /^(?:.*[/\\])?\..*$/,
|
|
36
|
+
persistent: true,
|
|
37
|
+
ignoreInitial: true,
|
|
38
|
+
awaitWriteFinish: {
|
|
39
|
+
stabilityThreshold: 100,
|
|
40
|
+
pollInterval: 50
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const handleFileChange = async (filePath) => {
|
|
44
|
+
const relativePath = relative(themePath, filePath);
|
|
45
|
+
await themeChecksums.updateCurrentChecksums();
|
|
46
|
+
const fileState = await themeChecksums.getFileState(relativePath);
|
|
47
|
+
if (fileState == FILE_STATES.unchanged)
|
|
48
|
+
return;
|
|
49
|
+
this._onInfoMessage(`Detected change in: ${relativePath}`);
|
|
50
|
+
this.#changedFiles.add(relativePath);
|
|
51
|
+
if (this.#batchTimeout)
|
|
52
|
+
this._clearQueuedBatch();
|
|
53
|
+
this.#batchTimeout = setTimeout(async () => {
|
|
54
|
+
const fileCount = this.#changedFiles.size;
|
|
55
|
+
this._onInfoMessage(`Processing batch of ${fileCount} file(s)`);
|
|
56
|
+
this.#batchTimeout = null;
|
|
57
|
+
if (this.#isPushing) {
|
|
58
|
+
this._onInfoMessage(`Push in progress, queuing another push...`);
|
|
59
|
+
this.#pendingPush = true;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
await this._executePush({
|
|
64
|
+
credentials,
|
|
65
|
+
filesStructure,
|
|
66
|
+
action,
|
|
67
|
+
executionContext,
|
|
68
|
+
themeChecksums,
|
|
69
|
+
themeFilesUploadApi
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
this.#onMessage?.(err);
|
|
74
|
+
}
|
|
75
|
+
}, THEME_BATCH_DELAY_MS);
|
|
76
|
+
};
|
|
77
|
+
this.#watcher
|
|
78
|
+
.on('add', handleFileChange)
|
|
79
|
+
.on('change', handleFileChange)
|
|
80
|
+
.on('unlink', handleFileChange)
|
|
81
|
+
.on('error', (error) => this._onErrorMessage(`Watcher error:`, error))
|
|
82
|
+
.on('ready', () => this._onInfoMessage('Initial scan complete. Ready for changes.'));
|
|
83
|
+
}
|
|
84
|
+
async ensureBrowserTabOpen() {
|
|
85
|
+
if (!this.#urlForWatchedTheme)
|
|
86
|
+
return;
|
|
87
|
+
if (!this.#browserApi.isBrowserOpen()) {
|
|
88
|
+
this._onInfoMessage(`Browser is not open, launching...`);
|
|
89
|
+
await this.#browserApi.launch();
|
|
90
|
+
await this.#browserApi.openTab(this.#urlForWatchedTheme, SHOP_BROWSER_TAB_ID);
|
|
91
|
+
this._onInfoMessage(`Browser opened with new tab`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (!this.#browserApi.isTabOpen(SHOP_BROWSER_TAB_ID)) {
|
|
95
|
+
this._onInfoMessage(`Tab was closed, reopening...`);
|
|
96
|
+
await this.#browserApi.openTab(this.#urlForWatchedTheme, SHOP_BROWSER_TAB_ID);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this._onInfoMessage(`Browser and tab already open`);
|
|
100
|
+
}
|
|
101
|
+
async stopWatching() {
|
|
102
|
+
this._onInfoMessage('Shutting down...');
|
|
103
|
+
if (this.#batchTimeout)
|
|
104
|
+
this._clearQueuedBatch();
|
|
105
|
+
await this.#watcher?.close();
|
|
106
|
+
await this.#browserApi.close();
|
|
107
|
+
this.#urlForWatchedTheme = null;
|
|
108
|
+
}
|
|
109
|
+
_clearQueuedBatch() {
|
|
110
|
+
if (this.#batchTimeout) {
|
|
111
|
+
clearTimeout(this.#batchTimeout);
|
|
112
|
+
this.#batchTimeout = null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async _refreshBrowserTab() {
|
|
116
|
+
if (!SHOP_BROWSER_TAB_ID || !this.#browserApi.isTabOpen(SHOP_BROWSER_TAB_ID))
|
|
117
|
+
return;
|
|
118
|
+
try {
|
|
119
|
+
this._onInfoMessage(`Refreshing browser tab...`);
|
|
120
|
+
await this.#browserApi.refresh(SHOP_BROWSER_TAB_ID);
|
|
121
|
+
this._onInfoMessage(`Browser tab refreshed`);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this._onErrorMessage(`Error refreshing browser:`, error);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async _executePush(params) {
|
|
128
|
+
this.#isPushing = true;
|
|
129
|
+
this.#pendingPush = false;
|
|
130
|
+
try {
|
|
131
|
+
this.#changedFiles.clear();
|
|
132
|
+
this._onInfoMessage(`Pushing theme...`);
|
|
133
|
+
await this.#themePushApi.partialPush(params);
|
|
134
|
+
this._onSuccessMessage(`Theme pushed successfully`);
|
|
135
|
+
await this._refreshBrowserTab();
|
|
136
|
+
if (this.#pendingPush) {
|
|
137
|
+
this._onInfoMessage(`Executing queued push...`);
|
|
138
|
+
this.#isPushing = false;
|
|
139
|
+
await this._executePush(params);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
this.#isPushing = false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
_onInfoMessage(message, details) {
|
|
147
|
+
this.#onMessage?.(new AppLog({
|
|
148
|
+
level: APP_LOGS_TYPES.info,
|
|
149
|
+
message,
|
|
150
|
+
details
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
_onErrorMessage(message, error) {
|
|
154
|
+
this.#onMessage?.(new AppLog({
|
|
155
|
+
level: APP_LOGS_TYPES.error,
|
|
156
|
+
message,
|
|
157
|
+
details: error
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
_onSuccessMessage(message, details) {
|
|
161
|
+
this.#onMessage?.(new AppLog({
|
|
162
|
+
level: APP_LOGS_TYPES.success,
|
|
163
|
+
message,
|
|
164
|
+
details
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FEATURE_CORES_TYPES, SyncFeatureInitializer } from '@dreamcommerce/star_core';
|
|
2
|
+
import { THEME_WATCH_FEATURE_NAME } from './theme_watch_constants.js';
|
|
3
|
+
import { ThemeWatchService } from './service/theme_watch_service.js';
|
|
4
|
+
import { THEME_PUSH_API_NAME } from '../push/theme_push_constants.js';
|
|
5
|
+
import { ThemeWatchApi } from './api/theme_watch_api.js';
|
|
6
|
+
import { Browser } from '../../../class/browser/browser.js';
|
|
7
|
+
//TODO to pownna byc inicjalizacja w komponencie reaktowym/komendzie watch.tsx
|
|
8
|
+
export class ThemeWatchInitializer extends SyncFeatureInitializer {
|
|
9
|
+
static featureName = THEME_WATCH_FEATURE_NAME;
|
|
10
|
+
init() {
|
|
11
|
+
const themePushApi = this.getApiSync(THEME_PUSH_API_NAME);
|
|
12
|
+
const service = new ThemeWatchService({ themePushApi, browserClient: Browser });
|
|
13
|
+
return {
|
|
14
|
+
cores: [
|
|
15
|
+
{
|
|
16
|
+
type: FEATURE_CORES_TYPES.api,
|
|
17
|
+
instance: new ThemeWatchApi(service)
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
package/build/theme/index.js
CHANGED
|
@@ -15,6 +15,8 @@ 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';
|
|
18
20
|
export const COMMANDS = {
|
|
19
21
|
[THEME_COMMANDS_NAME.list]: ThemeListCommand,
|
|
20
22
|
[THEME_COMMANDS_NAME.pull]: ThemePullCommand,
|
|
@@ -23,14 +25,16 @@ export const COMMANDS = {
|
|
|
23
25
|
[THEME_COMMANDS_NAME.verify]: ThemeVerifyCommand,
|
|
24
26
|
[THEME_COMMANDS_NAME.info]: ThemeInfoCommand,
|
|
25
27
|
[THEME_COMMANDS_NAME.delete]: ThemeDeleteCommand,
|
|
26
|
-
[THEME_COMMANDS_NAME.publish]: ThemePublishCommand
|
|
28
|
+
[THEME_COMMANDS_NAME.publish]: ThemePublishCommand,
|
|
29
|
+
[THEME_COMMANDS_NAME.watch]: ThemeWatchCommand
|
|
27
30
|
};
|
|
28
31
|
export const COMMAND_TO_FEATURES_MAP = {
|
|
29
32
|
[THEME_COMMANDS_NAME.pull]: [ThemeMergeInitializer, ThemeFetchInitializer],
|
|
30
33
|
[THEME_COMMANDS_NAME.init]: ThemeInitInitializer,
|
|
31
34
|
[THEME_COMMANDS_NAME.push]: [ThemeFetchInitializer, ThemePushInitializer],
|
|
32
35
|
[THEME_COMMANDS_NAME.verify]: ThemeVerifyInitializer,
|
|
33
|
-
[THEME_COMMANDS_NAME.delete]: ThemeDeleteInitializer
|
|
36
|
+
[THEME_COMMANDS_NAME.delete]: ThemeDeleteInitializer,
|
|
37
|
+
[THEME_COMMANDS_NAME.watch]: [ThemeFetchInitializer, ThemePushInitializer, ThemeWatchInitializer]
|
|
34
38
|
};
|
|
35
39
|
export const getThemeInitializersForCommand = (commandName) => {
|
|
36
40
|
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 }) => {
|
|
9
9
|
const unpermittedActions = [];
|
|
10
10
|
await _checkPermissions({
|
|
11
11
|
permissions,
|
|
@@ -14,15 +14,14 @@ export const validateDirectory = async ({ rootDirectory, permissions, checksums,
|
|
|
14
14
|
parts: [],
|
|
15
15
|
checksumsPart: checksums,
|
|
16
16
|
rootDirectory,
|
|
17
|
-
unpermittedActions
|
|
18
|
-
ignoreInstance
|
|
17
|
+
unpermittedActions
|
|
19
18
|
});
|
|
20
19
|
return {
|
|
21
20
|
isValid: !unpermittedActions.length,
|
|
22
21
|
unpermittedActions
|
|
23
22
|
};
|
|
24
23
|
};
|
|
25
|
-
const _checkPermissions = async ({ permissionPart, permissionParts, parts = [], checksumsPart, rootDirectory, unpermittedActions, permissions
|
|
24
|
+
const _checkPermissions = async ({ permissionPart, permissionParts, parts = [], checksumsPart, rootDirectory, unpermittedActions, permissions }) => {
|
|
26
25
|
if (!permissionPart)
|
|
27
26
|
return;
|
|
28
27
|
//TODO required files in a custom module directory
|
|
@@ -41,11 +40,6 @@ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [],
|
|
|
41
40
|
filesInsideCurrentDirectoryObject[path] = true;
|
|
42
41
|
});
|
|
43
42
|
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
|
-
}
|
|
49
43
|
const { permission, permissionKey } = _getPermission({
|
|
50
44
|
path: file,
|
|
51
45
|
fullPath: join(fullPath, file),
|
|
@@ -71,8 +65,7 @@ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [],
|
|
|
71
65
|
checksumsPart: checksumsPart?.[file] ?? {},
|
|
72
66
|
rootDirectory,
|
|
73
67
|
unpermittedActions,
|
|
74
|
-
permissions
|
|
75
|
-
ignoreInstance
|
|
68
|
+
permissions
|
|
76
69
|
});
|
|
77
70
|
}
|
|
78
71
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Box } from '../box.js';
|
|
2
|
+
import { Text } from '../text.js';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import TextInput from 'ink-text-input';
|
|
5
|
+
import { COMMAND_PREFIX } from '../../theme/commands/watch/theme_watch_constants.js';
|
|
6
|
+
export const CommandInput = ({ onSubmit, commands = [] }) => {
|
|
7
|
+
const [query, setQuery] = useState('');
|
|
8
|
+
const showSuggestions = query.startsWith(COMMAND_PREFIX);
|
|
9
|
+
const searchTerm = query.substring(1);
|
|
10
|
+
const filteredCommands = commands.filter((cmd) => cmd.key.startsWith(searchTerm));
|
|
11
|
+
const handleSubmit = (value) => {
|
|
12
|
+
onSubmit(value);
|
|
13
|
+
setQuery('');
|
|
14
|
+
};
|
|
15
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
16
|
+
React.createElement(Box, { borderStyle: "round", borderColor: "blue", paddingX: 1, flexDirection: "row" },
|
|
17
|
+
React.createElement(Text, { color: "blue" }, '> '),
|
|
18
|
+
React.createElement(TextInput, { value: query, onChange: setQuery, onSubmit: handleSubmit, placeholder: `Type ${COMMAND_PREFIX} to see commands...` })),
|
|
19
|
+
showSuggestions ? (React.createElement(Box, { flexDirection: "column", marginTop: 1, marginLeft: 2 },
|
|
20
|
+
filteredCommands.map((cmd) => (React.createElement(Box, { key: cmd.key },
|
|
21
|
+
React.createElement(Box, { width: 15 },
|
|
22
|
+
React.createElement(Text, { color: "cyan", bold: true }, cmd.key)),
|
|
23
|
+
React.createElement(Text, { color: "gray" }, cmd.description)))),
|
|
24
|
+
filteredCommands.length === 0 && React.createElement(Text, { color: "red" }, "No matching commands found"))) : null));
|
|
25
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { CSS_TEXT_COLORS } from '../color_constants.js';
|
|
2
|
+
export const LOG_TYPES = {
|
|
3
|
+
info: 'info',
|
|
4
|
+
success: 'success',
|
|
5
|
+
error: 'error',
|
|
6
|
+
warning: 'warning',
|
|
7
|
+
custom: 'custom'
|
|
8
|
+
};
|
|
9
|
+
export const MAP_LOG_TYPE_TO_TEXT_COLOR = {
|
|
10
|
+
[LOG_TYPES.info]: CSS_TEXT_COLORS.info,
|
|
11
|
+
[LOG_TYPES.success]: CSS_TEXT_COLORS.success,
|
|
12
|
+
[LOG_TYPES.error]: CSS_TEXT_COLORS.danger,
|
|
13
|
+
[LOG_TYPES.warning]: CSS_TEXT_COLORS.warning
|
|
14
|
+
};
|
|
15
|
+
export const MAP_LOG_TYPE_TO_LABEL = {
|
|
16
|
+
[LOG_TYPES.info]: 'INFO',
|
|
17
|
+
[LOG_TYPES.success]: 'SUCCESS',
|
|
18
|
+
[LOG_TYPES.error]: 'ERROR',
|
|
19
|
+
[LOG_TYPES.warning]: 'WARNING'
|
|
20
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Box } from '../box.js';
|
|
2
|
+
import { Text } from '../text.js';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { LOG_TYPES, MAP_LOG_TYPE_TO_LABEL, MAP_LOG_TYPE_TO_TEXT_COLOR } from './logs_constants.js';
|
|
5
|
+
export const LogsList = ({ logs }) => {
|
|
6
|
+
return (React.createElement(Box, { flexDirection: "column", marginBottom: 1, width: "100%" }, logs.map((log) => {
|
|
7
|
+
if (log.type === LOG_TYPES.custom)
|
|
8
|
+
return React.createElement(Box, { key: log.id }, log.content);
|
|
9
|
+
const logLabel = MAP_LOG_TYPE_TO_LABEL[log.type];
|
|
10
|
+
const logColor = MAP_LOG_TYPE_TO_TEXT_COLOR[log.type];
|
|
11
|
+
return typeof log.content === 'string' ? (React.createElement(Text, { key: log.id },
|
|
12
|
+
React.createElement(Text, { color: logColor },
|
|
13
|
+
"[",
|
|
14
|
+
logLabel,
|
|
15
|
+
"] "),
|
|
16
|
+
log.content)) : (log.content);
|
|
17
|
+
})));
|
|
18
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { LOG_TYPES } from './logs_constants.js';
|
|
3
|
+
import { LogEntry } from './log_entry.js';
|
|
4
|
+
import { v4 as uuid } from 'uuid';
|
|
5
|
+
export const useLogs = (maxLogs) => {
|
|
6
|
+
const [logs, setLogs] = useState([]);
|
|
7
|
+
const addLog = (content, type = LOG_TYPES.info) => {
|
|
8
|
+
setLogs((prev) => {
|
|
9
|
+
const newLogs = [
|
|
10
|
+
...prev,
|
|
11
|
+
new LogEntry({
|
|
12
|
+
id: uuid(),
|
|
13
|
+
type,
|
|
14
|
+
content,
|
|
15
|
+
timestamp: Date.now()
|
|
16
|
+
})
|
|
17
|
+
];
|
|
18
|
+
return newLogs.slice(-maxLogs);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
const clearLogs = () => setLogs([]);
|
|
22
|
+
return { logs, addLog, clearLogs };
|
|
23
|
+
};
|