@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
|
@@ -29,7 +29,7 @@ export const cliSetup = async () => {
|
|
|
29
29
|
LoggerInitializer,
|
|
30
30
|
CliAuthTokensInitializer,
|
|
31
31
|
CliAuthInitializer,
|
|
32
|
-
...
|
|
32
|
+
...getInitializersBasedOnCommand()
|
|
33
33
|
],
|
|
34
34
|
options: {
|
|
35
35
|
featuresTracking: false,
|
|
@@ -37,15 +37,15 @@ export const cliSetup = async () => {
|
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
};
|
|
40
|
-
const
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
40
|
+
const getInitializersBasedOnCommand = () => {
|
|
41
|
+
if (isCommandWithTopic(process.argv[2]))
|
|
42
|
+
return getInitializersBasedOnCommandWithTopic();
|
|
43
43
|
return [];
|
|
44
44
|
};
|
|
45
|
-
const
|
|
45
|
+
const isCommandWithTopic = (arg) => {
|
|
46
46
|
return arg === THEME_TOPIC_NAME || arg === CLI_AUTH_TOPIC_NAME;
|
|
47
47
|
};
|
|
48
|
-
const
|
|
48
|
+
const getInitializersBasedOnCommandWithTopic = () => {
|
|
49
49
|
const topic = process.argv[2];
|
|
50
50
|
const command = process.argv[3];
|
|
51
51
|
switch (topic) {
|
package/build/cli/index.js
CHANGED
package/build/index.js
CHANGED
|
@@ -31,7 +31,7 @@ process.on('uncaughtException', (err) => {
|
|
|
31
31
|
const loggerApi = getApiSync(LOGGER_API_NAME);
|
|
32
32
|
if (loggerApi)
|
|
33
33
|
loggerApi.fatal('Uncaught Exception', { error: err });
|
|
34
|
-
process.exit(1);
|
|
34
|
+
// process.exit(1);
|
|
35
35
|
});
|
|
36
36
|
process.on('unhandledRejection', (reason) => {
|
|
37
37
|
console.error('Unhandled Rejection:', reason);
|
|
@@ -42,7 +42,7 @@ process.on('unhandledRejection', (reason) => {
|
|
|
42
42
|
reason
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
-
process.exit(1);
|
|
45
|
+
// process.exit(1);
|
|
46
46
|
});
|
|
47
47
|
const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT'];
|
|
48
48
|
signals.forEach((signal) => {
|
|
@@ -1,45 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { createZip } from '../../../utils/zip/create_zip_utils.js';
|
|
3
|
-
import { ThemeFilesStructureUtils } from '../../features/theme/utils/files_structure/theme_files_structure_utils.js';
|
|
4
|
-
import { extname, join } from '../../../utils/path_utils.js';
|
|
5
|
-
import { formatJSONFile } from '../../../utils/fs/fs_utils.js';
|
|
6
|
-
import { ThemeArchiveErrorsFactory } from './theme_archive_errors_factory.js';
|
|
7
|
-
import { ThemeFileStructureErrorsFactory } from '../../features/theme/utils/files_structure/theme_file_structure_errors_factory.js';
|
|
8
|
-
import { ThemeActionsUtils } from '../../features/theme/actions/theme_actions_utils.js';
|
|
9
|
-
export class ThemeArchive {
|
|
10
|
-
#themeRootDir;
|
|
11
|
-
constructor(themeRootDir) {
|
|
12
|
-
this.#themeRootDir = themeRootDir;
|
|
13
|
-
}
|
|
14
|
-
async createFullArchive({ dist, actionValue, actionType, logger }) {
|
|
15
|
-
const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(this.#themeRootDir);
|
|
16
|
-
if (!filesStructure)
|
|
17
|
-
throw ThemeFileStructureErrorsFactory.createNoFilesStructureError();
|
|
18
|
-
try {
|
|
19
|
-
const filesInThemeDirectory = await globs(ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
|
|
20
|
-
actionType,
|
|
21
|
-
actionValue,
|
|
22
|
-
filesStructure
|
|
23
|
-
}), {
|
|
24
|
-
suppressErrors: true,
|
|
25
|
-
onlyFiles: true,
|
|
26
|
-
cwd: this.#themeRootDir
|
|
27
|
-
});
|
|
28
|
-
await this._formatJsonFiles(this.#themeRootDir, filesInThemeDirectory);
|
|
29
|
-
return createZip({
|
|
30
|
-
files: filesInThemeDirectory,
|
|
31
|
-
logger,
|
|
32
|
-
baseDir: this.#themeRootDir,
|
|
33
|
-
dist
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
catch (err) {
|
|
37
|
-
throw ThemeArchiveErrorsFactory.createErrorWhileCreatingThemeArchive(err);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async _formatJsonFiles(themeRootDir, filesToUpload) {
|
|
41
|
-
await Promise.all(filesToUpload
|
|
42
|
-
.filter((path) => extname(path).toLowerCase() === '.json')
|
|
43
|
-
.map((jsonFile) => formatJSONFile(join(themeRootDir, jsonFile))));
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import puppeteer from 'puppeteer';
|
|
2
|
+
import { v4 as uuid } from 'uuid';
|
|
3
|
+
export class Browser {
|
|
4
|
+
#browser = null;
|
|
5
|
+
#tabs = new Map();
|
|
6
|
+
#onError;
|
|
7
|
+
#onInfo;
|
|
8
|
+
#onDisconnected;
|
|
9
|
+
constructor({ onError, onInfo, onDisconnected } = {}) {
|
|
10
|
+
this.#onError = onError;
|
|
11
|
+
this.#onInfo = onInfo;
|
|
12
|
+
this.#onDisconnected = onDisconnected;
|
|
13
|
+
}
|
|
14
|
+
async launch() {
|
|
15
|
+
this.#onInfo?.(`Launching browser...`);
|
|
16
|
+
try {
|
|
17
|
+
if (!this.#browser) {
|
|
18
|
+
this.#onInfo?.(`Launching browser...`);
|
|
19
|
+
this.#browser = await puppeteer.launch({
|
|
20
|
+
headless: false,
|
|
21
|
+
defaultViewport: null
|
|
22
|
+
});
|
|
23
|
+
this.#browser.on('disconnected', () => {
|
|
24
|
+
this.#onDisconnected?.('Browser disconnected');
|
|
25
|
+
this.#browser = null;
|
|
26
|
+
this.#tabs.clear();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.#onError?.(`Error launch browser:`, error);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async openTab(url, tabId) {
|
|
36
|
+
try {
|
|
37
|
+
if (!this.#browser || !this.isBrowserOpen()) {
|
|
38
|
+
this.#onInfo?.(`Browser not open, launching browser...`);
|
|
39
|
+
await this.launch();
|
|
40
|
+
}
|
|
41
|
+
if (!this.#browser)
|
|
42
|
+
return;
|
|
43
|
+
const id = tabId ?? `tab-${uuid()}`;
|
|
44
|
+
this.#onInfo?.(`Opening new tab ${id}: ${url}`);
|
|
45
|
+
const page = await this.#browser.newPage();
|
|
46
|
+
await page.goto(url, { waitUntil: 'networkidle2' });
|
|
47
|
+
this.#tabs.set(id, { page, url });
|
|
48
|
+
this.#onInfo?.(`Tab ${id} opened successfully`);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
this.#onError?.(`Error opening tab:`, error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async refresh(tabId) {
|
|
56
|
+
const tab = this.#tabs.get(tabId);
|
|
57
|
+
if (!tab) {
|
|
58
|
+
this.#onError?.(`Tab ${tabId} not found`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
this.#onInfo?.(`Refreshing tab ${tabId}...`);
|
|
63
|
+
await tab.page.reload({ waitUntil: 'networkidle2' });
|
|
64
|
+
this.#onInfo?.(`Tab ${tabId} refreshed`);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
this.#onError?.(`Error refreshing tab ${tabId}:`, error);
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async closeTab(tabId) {
|
|
72
|
+
const tab = this.#tabs.get(tabId);
|
|
73
|
+
if (!tab) {
|
|
74
|
+
this.#onError?.(`Tab ${tabId} not found`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
await tab.page.close();
|
|
79
|
+
this.#tabs.delete(tabId);
|
|
80
|
+
this.#onInfo?.(`Tab ${tabId} closed`);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
this.#onError?.(`Error closing tab ${tabId}:`, error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async close() {
|
|
88
|
+
if (!this.isBrowserOpen())
|
|
89
|
+
return;
|
|
90
|
+
await this.#browser?.close();
|
|
91
|
+
this.#browser = null;
|
|
92
|
+
this.#tabs.clear();
|
|
93
|
+
this.#onInfo?.('Browser closed with all tabs');
|
|
94
|
+
}
|
|
95
|
+
isBrowserOpen() {
|
|
96
|
+
return this.#browser !== null && this.#browser.connected;
|
|
97
|
+
}
|
|
98
|
+
isTabOpen(tabId) {
|
|
99
|
+
const tab = this.#tabs.get(tabId);
|
|
100
|
+
return tab !== undefined && !tab.page.isClosed();
|
|
101
|
+
}
|
|
102
|
+
getOpenTabs() {
|
|
103
|
+
return Array.from(this.#tabs.keys()).filter((tabId) => this.isTabOpen(tabId));
|
|
104
|
+
}
|
|
105
|
+
getTabUrl(tabId) {
|
|
106
|
+
return this.#tabs.get(tabId)?.url ?? null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -10,6 +10,7 @@ import { normalize } from 'node:path';
|
|
|
10
10
|
import { ThemePushUtils } from '../../features/theme/push/theme_push_utils.js';
|
|
11
11
|
import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
|
|
12
12
|
import { THEME_CURRENT_CHECKSUMS_FILE_NAME, THEME_CURRENT_CHECKSUMS_VERITY_FILE_NAME, THEME_INITIAL_CHECKSUMS_FILE_NAME, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME } from '../../features/theme/theme_constants.js';
|
|
13
|
+
import { FILE_STATES } from '../../../utils/fs/fs_constants.js';
|
|
13
14
|
export class ThemeChecksums {
|
|
14
15
|
#themeDir;
|
|
15
16
|
#loggerApi;
|
|
@@ -33,12 +34,6 @@ export class ThemeChecksums {
|
|
|
33
34
|
this.#initialChecksums = await this._getChecksums(this.#initialChecksumsFilePath);
|
|
34
35
|
return this.#initialChecksums;
|
|
35
36
|
}
|
|
36
|
-
getInitialChecksumsSync() {
|
|
37
|
-
if (this.#initialChecksums)
|
|
38
|
-
return this.#initialChecksums;
|
|
39
|
-
this.#initialChecksums = this._getChecksumsSync(this.#initialChecksumsFilePath);
|
|
40
|
-
return this.#initialChecksums;
|
|
41
|
-
}
|
|
42
37
|
async getCurrentChecksums() {
|
|
43
38
|
if (this.#currentChecksums)
|
|
44
39
|
return this.#currentChecksums;
|
|
@@ -72,24 +67,35 @@ export class ThemeChecksums {
|
|
|
72
67
|
const initialChecksum = await this.getInitialChecksumFromPath(path);
|
|
73
68
|
return !initialChecksum && (await fileExists(join(this.#themeDir, path)));
|
|
74
69
|
}
|
|
75
|
-
async
|
|
76
|
-
const currentChecksum = await this.getCurrentChecksumFromPath(path);
|
|
70
|
+
async getFileState(path) {
|
|
77
71
|
const initialChecksum = await this.getInitialChecksumFromPath(path);
|
|
78
|
-
|
|
72
|
+
const currentChecksum = await this.getCurrentChecksumFromPath(path);
|
|
73
|
+
const fileExistsInFs = await fileExists(join(this.#themeDir, path));
|
|
74
|
+
if (!initialChecksum && fileExistsInFs) {
|
|
75
|
+
return FILE_STATES.created;
|
|
76
|
+
}
|
|
77
|
+
else if (initialChecksum && !fileExistsInFs) {
|
|
78
|
+
return FILE_STATES.deleted;
|
|
79
|
+
}
|
|
80
|
+
else if (initialChecksum && currentChecksum && initialChecksum !== currentChecksum) {
|
|
81
|
+
return FILE_STATES.modified;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
return FILE_STATES.unchanged;
|
|
85
|
+
}
|
|
79
86
|
}
|
|
80
87
|
async verify() {
|
|
81
88
|
const initialChecksumFilePath = this.#initialChecksumsFilePath;
|
|
82
89
|
const initialChecksumVerifyFilePath = this.#initialChecksumsVerificationFilePath;
|
|
83
|
-
console.log('Verifying theme checksums:', { initialChecksumFilePath, initialChecksumVerifyFilePath });
|
|
84
90
|
const initialChecksum = await computeFileChecksum(initialChecksumFilePath);
|
|
85
91
|
const initialChecksumVerify = await readJSONFile(initialChecksumVerifyFilePath);
|
|
86
|
-
console.log('initialChecksum current:', initialChecksum);
|
|
87
|
-
console.log('initialChecksumVerify:', initialChecksumVerify);
|
|
88
92
|
return initialChecksum === initialChecksumVerify;
|
|
89
93
|
}
|
|
90
94
|
async updateAllChecksums() {
|
|
91
95
|
this.#loggerApi?.debug('Updating all checksums.');
|
|
92
96
|
const checksums = await this.computeThemeChecksums();
|
|
97
|
+
this.#initialChecksums = checksums;
|
|
98
|
+
this.#currentChecksums = checksums;
|
|
93
99
|
return new Promise((resolve, reject) => {
|
|
94
100
|
const currentChecksumFilePath = this.#currentChecksumsFilePath;
|
|
95
101
|
const initialChecksumFilePath = this.#initialChecksumsFilePath;
|
|
@@ -117,6 +123,7 @@ export class ThemeChecksums {
|
|
|
117
123
|
async updateCurrentChecksums() {
|
|
118
124
|
this.#loggerApi?.debug('Updating current checksums.');
|
|
119
125
|
const checksums = await this.computeThemeChecksums();
|
|
126
|
+
this.#currentChecksums = checksums;
|
|
120
127
|
return new Promise((resolve, reject) => {
|
|
121
128
|
const currentChecksumFilePath = this.#currentChecksumsFilePath;
|
|
122
129
|
const checksumStream = createWriteStream(currentChecksumFilePath);
|
|
@@ -157,6 +164,37 @@ export class ThemeChecksums {
|
|
|
157
164
|
this.#loggerApi?.debug('Finished computing theme checksums.', { details: { count: Object.keys(allChecksums).length } });
|
|
158
165
|
return allChecksums;
|
|
159
166
|
}
|
|
167
|
+
async groupFilesByStatus(files) {
|
|
168
|
+
const initialChecksums = await this.getInitialChecksums();
|
|
169
|
+
const currentChecksums = await this.getCurrentChecksums();
|
|
170
|
+
return files.reduce((acc, filePath) => {
|
|
171
|
+
const initialChecksum = initialChecksums[toCurrentPlatformPath(filePath)];
|
|
172
|
+
const currentChecksum = currentChecksums[toCurrentPlatformPath(filePath)];
|
|
173
|
+
if (!initialChecksum) {
|
|
174
|
+
acc.created.push(filePath);
|
|
175
|
+
}
|
|
176
|
+
else if (currentChecksum && initialChecksum !== currentChecksum) {
|
|
177
|
+
acc.modified.push(filePath);
|
|
178
|
+
}
|
|
179
|
+
else if (initialChecksum && !currentChecksum) {
|
|
180
|
+
acc.deleted.push(filePath);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
acc.unchanged.push(filePath);
|
|
184
|
+
}
|
|
185
|
+
return acc;
|
|
186
|
+
}, {
|
|
187
|
+
created: [],
|
|
188
|
+
modified: [],
|
|
189
|
+
unchanged: [],
|
|
190
|
+
deleted: []
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
async getDeletedFiles() {
|
|
194
|
+
const initialChecksums = await this.getInitialChecksums();
|
|
195
|
+
const currentChecksums = await this.getCurrentChecksums();
|
|
196
|
+
return Object.keys(initialChecksums).filter((filePath) => !currentChecksums[filePath]);
|
|
197
|
+
}
|
|
160
198
|
async _getInitialChecksumsVerification() {
|
|
161
199
|
return await readJSONFile(this.#initialChecksumsVerificationFilePath);
|
|
162
200
|
}
|
|
@@ -24,8 +24,9 @@ import { Info } from '../../../ui/message_box/info.js';
|
|
|
24
24
|
import { directoryExists } from '../../../utils/fs/fs_utils.js';
|
|
25
25
|
import { ThemeMetaDataUtils } from '../../features/theme/utils/meta_data/theme_meta_data_utils.js';
|
|
26
26
|
import { ThemeChecksums } from '../../class/checksums/theme_checksums.js';
|
|
27
|
-
import { ThemeError } from '../ui/theme_error.js';
|
|
28
27
|
import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
|
|
28
|
+
import { ThemeError } from '../ui/theme_error.js';
|
|
29
|
+
import { MODULES_DIRECTORY_NAME } from '../../features/theme/theme_constants.js';
|
|
29
30
|
//TODO jak jest error w pullu wowczas usuwamy docelowo pociagniety skin
|
|
30
31
|
export class ThemePullCommand extends BaseThemeCommand {
|
|
31
32
|
static summary = 'Downloads the current version of your theme from the store and overwrites your local theme files.';
|
|
@@ -160,9 +161,9 @@ export class ThemePullCommand extends BaseThemeCommand {
|
|
|
160
161
|
dist: tmpDir
|
|
161
162
|
}
|
|
162
163
|
});
|
|
163
|
-
const localModulesPath = join(executionContext.themeRootDir,
|
|
164
|
+
const localModulesPath = join(executionContext.themeRootDir, MODULES_DIRECTORY_NAME);
|
|
164
165
|
const fetchedThemePath = join(tmpDir, name);
|
|
165
|
-
const fetchedModulesPath = join(fetchedThemePath,
|
|
166
|
+
const fetchedModulesPath = join(fetchedThemePath, MODULES_DIRECTORY_NAME);
|
|
166
167
|
if ((await directoryExists(localModulesPath)) && (await directoryExists(fetchedModulesPath)))
|
|
167
168
|
await ThemeResourcesWithIdDirectoryUtils.updateDirectoryNamesOfResourcesWithIdAccordingToLocalThemeNames(localModulesPath, fetchedModulesPath, fetchedThemePath, loggerApi);
|
|
168
169
|
this.#spinner.stop();
|
|
@@ -4,8 +4,6 @@ import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/fea
|
|
|
4
4
|
import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../../features/theme/actions/theme_actions_constants.js';
|
|
5
5
|
import { THEME_PUSH_API_NAME } from '../../features/theme/push/theme_push_constants.js';
|
|
6
6
|
import { ThemeMetaDataUtils } from '../../features/theme/utils/meta_data/theme_meta_data_utils.js';
|
|
7
|
-
import { mapChecksumToTree } from '../../../utils/checksums/checksums_utils.js';
|
|
8
|
-
import { mapToPermissionsTree } from '../../utils/directory_validator/directory_validator_utils.js';
|
|
9
7
|
import { renderOnce } from '../../../ui/ui_utils.js';
|
|
10
8
|
import { UnpermittedCommandError } from '../ui/unpermitted_command_error.js';
|
|
11
9
|
import React from 'react';
|
|
@@ -14,7 +12,7 @@ import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_c
|
|
|
14
12
|
import { ThemePushedSuccess } from './ui/theme_pushed_success.js';
|
|
15
13
|
import ora from 'ora';
|
|
16
14
|
import { ThemePushSkipInfo } from './ui/theme_push_skip_into.js';
|
|
17
|
-
import {
|
|
15
|
+
import { ThemeFilesUtils } from '../../features/theme/utils/files/theme_files_utils.js';
|
|
18
16
|
import { ThemeInfoUtils } from '../../features/theme/info/theme_info_utils.js';
|
|
19
17
|
import { ThemeChecksums } from '../../class/checksums/theme_checksums.js';
|
|
20
18
|
import { ThemeFilesUpload } from '../../class/files_upload/theme_files_upload.js';
|
|
@@ -24,6 +22,8 @@ import { MissingThemeFiles } from '../ui/missing_theme_files.js';
|
|
|
24
22
|
import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
|
|
25
23
|
import { THEME_FILES_STRUCTURE_FILE_NAME } from '../../features/theme/theme_constants.js';
|
|
26
24
|
import { ThemeError } from '../ui/theme_error.js';
|
|
25
|
+
import { mapToPermissionsTree } from '../../utils/directory_validator/directory_validator_utils.js';
|
|
26
|
+
import { mapChecksumToTree } from '../../../utils/checksums/checksums_utils.js';
|
|
27
27
|
import { ThemeUnpermittedActionsError } from './ui/theme_unpermitted_actions_error.js';
|
|
28
28
|
import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
|
|
29
29
|
export class ThemePushCommand extends BaseThemeCommand {
|
|
@@ -68,7 +68,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
const initialChecksums = await themeChecksums.getInitialChecksums();
|
|
71
|
-
const permissions = await
|
|
71
|
+
const permissions = await ThemeFilesUtils.getFilesPermissions(executionContext.themeRootDir);
|
|
72
72
|
const themeFilesUploadApi = new ThemeFilesUpload({
|
|
73
73
|
themeRootDir: executionContext.themeRootDir,
|
|
74
74
|
credentials,
|
|
@@ -80,7 +80,7 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
80
80
|
renderOnce(React.createElement(ThemePushSkipInfo, null));
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
-
const validationResult = await
|
|
83
|
+
const validationResult = await ThemeFilesUtils.validateThemeDirectoryStructure({
|
|
84
84
|
//TDO przeniesc do theme checksums
|
|
85
85
|
checksums: mapChecksumToTree(initialChecksums),
|
|
86
86
|
permissions: mapToPermissionsTree(permissions),
|
|
@@ -92,16 +92,25 @@ export class ThemePushCommand extends BaseThemeCommand {
|
|
|
92
92
|
renderOnce(React.createElement(ThemeUnpermittedActionsError, { unpermittedActions: validationResult.unpermittedActions }));
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
|
-
const filesStructure = await
|
|
95
|
+
const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(executionContext.themeRootDir);
|
|
96
96
|
if (!filesStructure) {
|
|
97
97
|
renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
|
|
98
98
|
return;
|
|
99
99
|
}
|
|
100
100
|
spinner = ora('Pushing theme...').start();
|
|
101
|
-
|
|
101
|
+
const partialPushAction = themeActionsApi.getThemeAction({
|
|
102
|
+
actionType: THEME_ACTIONS_TYPES.partialPush,
|
|
103
|
+
themeId: executionContext.themeId,
|
|
104
|
+
credentials
|
|
105
|
+
});
|
|
106
|
+
if (!pushAction) {
|
|
107
|
+
renderOnce(React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "push" }));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
await themePushApi.partialPush({
|
|
102
111
|
credentials,
|
|
103
112
|
filesStructure,
|
|
104
|
-
|
|
113
|
+
action: partialPushAction,
|
|
105
114
|
themeChecksums,
|
|
106
115
|
executionContext,
|
|
107
116
|
themeFilesUploadApi
|
|
@@ -7,5 +7,6 @@ export const THEME_COMMANDS_NAME = {
|
|
|
7
7
|
verify: `${THEME_TOPIC_NAME}:verify`,
|
|
8
8
|
info: `${THEME_TOPIC_NAME}:info`,
|
|
9
9
|
delete: `${THEME_TOPIC_NAME}:delete`,
|
|
10
|
-
publish: `${THEME_TOPIC_NAME}:publish
|
|
10
|
+
publish: `${THEME_TOPIC_NAME}:publish`,
|
|
11
|
+
watch: `${THEME_TOPIC_NAME}:watch`
|
|
11
12
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { BaseThemeCommand } from '../class/base_theme_command.js';
|
|
2
2
|
import { CLI_AUTH_API_NAME } from '../../cli/auth/cli_auth_constants.js';
|
|
3
3
|
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
|
|
4
|
-
import { ThemeFilesStructureUtils } from '../features/theme/utils/files_structure/theme_files_structure_utils.js';
|
|
5
4
|
import { ThemeChecksums } from '../class/checksums/theme_checksums.js';
|
|
6
5
|
import { THEME_VERIFY_API_NAME } from '../features/theme/verify/theme_verify_constants.js';
|
|
7
6
|
import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
|
|
@@ -18,6 +17,7 @@ import { MissingCredentialsError } from '../../cli/commands/auth/ui/missing_cred
|
|
|
18
17
|
import { OutsideOfThemeDirectoryContextError } from './ui/ouside_of_theme_directory_context_error.js';
|
|
19
18
|
import ora from 'ora';
|
|
20
19
|
import { ThemeError } from './ui/theme_error.js';
|
|
20
|
+
import { ThemeFilesUtils } from '../features/theme/utils/files/theme_files_utils.js';
|
|
21
21
|
import { LOGGER_API_NAME } from '../../cli/utilities/features/logger/logger_constants.js';
|
|
22
22
|
export class ThemeVerifyCommand extends BaseThemeCommand {
|
|
23
23
|
static description = 'Verify theme files structure';
|
|
@@ -39,7 +39,7 @@ export class ThemeVerifyCommand extends BaseThemeCommand {
|
|
|
39
39
|
themeDir: executionContext.themeRootDir,
|
|
40
40
|
loggerApi
|
|
41
41
|
});
|
|
42
|
-
const permissions = await
|
|
42
|
+
const permissions = await ThemeFilesUtils.getFilesPermissions(executionContext.themeRootDir);
|
|
43
43
|
if (!themeChecksums || !permissions)
|
|
44
44
|
this.error('Theme checksums or permissions not found. Please ensure you are in the correct theme directory.');
|
|
45
45
|
let spinner;
|
|
@@ -56,7 +56,7 @@ export class ThemeVerifyCommand extends BaseThemeCommand {
|
|
|
56
56
|
themeId: executionContext.themeId,
|
|
57
57
|
credentials
|
|
58
58
|
});
|
|
59
|
-
const filesStructure = await
|
|
59
|
+
const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(executionContext.themeRootDir);
|
|
60
60
|
if (!filesStructure) {
|
|
61
61
|
renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
|
|
62
62
|
return;
|
|
@@ -9,18 +9,18 @@ import React from 'react';
|
|
|
9
9
|
import { ThemeWorkUrlMismatch } from './theme_work_url_mismatch.js';
|
|
10
10
|
import { EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
|
|
11
11
|
export const ThemeError = ({ err, executionContext }) => {
|
|
12
|
-
if (err?.code === THEME_ACTION_NOT_FOUND_ERROR_CODE && executionContext?.type === EXECUTION_CONTEXTS.theme) {
|
|
12
|
+
if (err?.tags?.code === THEME_ACTION_NOT_FOUND_ERROR_CODE && executionContext?.type === EXECUTION_CONTEXTS.theme) {
|
|
13
13
|
return (React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "verify" }));
|
|
14
14
|
}
|
|
15
|
-
if (err?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
|
|
15
|
+
if (err?.tags?.code === THEME_ARCHIVE_UPLOAD_ERROR) {
|
|
16
16
|
return React.createElement(ValidationErrors, { errors: err?.details });
|
|
17
17
|
}
|
|
18
|
-
if (err?.code === THEME_WORK_URL_MISMATCH_ERROR) {
|
|
18
|
+
if (err?.tags?.code === THEME_WORK_URL_MISMATCH_ERROR) {
|
|
19
19
|
return React.createElement(ThemeWorkUrlMismatch, { command: "verify" });
|
|
20
20
|
}
|
|
21
|
-
if (err?.code === THEME_FILES_UPLOAD_ERROR) {
|
|
22
|
-
return (React.createElement(Error, { header: "Uploading theme files to the shop failed
|
|
23
|
-
React.createElement(Text, null, "
|
|
21
|
+
if (err?.tags?.code === THEME_FILES_UPLOAD_ERROR) {
|
|
22
|
+
return (React.createElement(Error, { header: "Uploading theme files to the shop failed." },
|
|
23
|
+
React.createElement(Text, null, "Please ensure that the files are not corrupted and that the file extensions are supported or file size is not too large.")));
|
|
24
24
|
}
|
|
25
25
|
if (err?.message) {
|
|
26
26
|
return React.createElement(ValidationErrors, { errors: err.message });
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { BaseThemeCommand } from '../../class/base_theme_command.js';
|
|
2
|
+
import { CLI_AUTH_API_NAME } from '../../../cli/auth/cli_auth_constants.js';
|
|
3
|
+
import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../../features/theme/actions/theme_actions_constants.js';
|
|
4
|
+
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
|
|
5
|
+
import { THEME_WATCH_API_NAME } from '../../features/theme/watch/theme_watch_constants.js';
|
|
6
|
+
import { render, renderOnce } from '../../../ui/ui_utils.js';
|
|
7
|
+
import { MissingCredentialsError } from '../../../cli/commands/auth/ui/missing_credentials_error.js';
|
|
8
|
+
import { OutsideOfThemeDirectoryContextError } from '../ui/ouside_of_theme_directory_context_error.js';
|
|
9
|
+
import { UnpermittedCommandError } from '../ui/unpermitted_command_error.js';
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { ThemeChecksums } from '../../class/checksums/theme_checksums.js';
|
|
12
|
+
import { ThemeMetaDataUtils } from '../../features/theme/utils/meta_data/theme_meta_data_utils.js';
|
|
13
|
+
import { ThemeFilesUtils } from '../../features/theme/utils/files/theme_files_utils.js';
|
|
14
|
+
import { MissingThemeFiles } from '../ui/missing_theme_files.js';
|
|
15
|
+
import { SHOPER_THEME_METADATA_DIR } from '../../constants/directory_contstants.js';
|
|
16
|
+
import { THEME_FILES_STRUCTURE_FILE_NAME } from '../../features/theme/theme_constants.js';
|
|
17
|
+
import { ThemeFilesUpload } from '../../class/files_upload/theme_files_upload.js';
|
|
18
|
+
import { ThemeFilesUploadHttpApi } from '../../class/files_upload/theme_files_upload_http_api.js';
|
|
19
|
+
import { HTTP_REQUESTER_API_NAME } from '@dreamcommerce/star_core';
|
|
20
|
+
import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
|
|
21
|
+
import { Watch } from './watch.js';
|
|
22
|
+
import { Flags } from '@oclif/core';
|
|
23
|
+
import { ThemeError } from '../ui/theme_error.js';
|
|
24
|
+
export class ThemeWatchCommand extends BaseThemeCommand {
|
|
25
|
+
static summary = 'Watch local theme files for changes and update your store preview in real time.';
|
|
26
|
+
static description = `Continuously monitors your local theme files for any changes and uploads them automatically to your store. Your store preview will refresh in real time to reflect updates immediately.\n\nYou must run this command from a specific theme directory (ID not needed).`;
|
|
27
|
+
static examples = [
|
|
28
|
+
{
|
|
29
|
+
description: 'This will replace the store version with your local copy on files modification.',
|
|
30
|
+
command: '<%= config.bin %> <%= command.id %>'
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
static flags = {
|
|
34
|
+
open: Flags.boolean({
|
|
35
|
+
description: 'Open the store preview in your default web browser when watching starts.',
|
|
36
|
+
required: false,
|
|
37
|
+
default: false
|
|
38
|
+
})
|
|
39
|
+
};
|
|
40
|
+
async run() {
|
|
41
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
42
|
+
const loggerApi = this.getApi(LOGGER_API_NAME);
|
|
43
|
+
const themeWatchApi = this.getApi(THEME_WATCH_API_NAME);
|
|
44
|
+
const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
|
|
45
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
46
|
+
const credentials = cliAuthApi.getCredentials();
|
|
47
|
+
const { flags } = await this.parse(ThemeWatchCommand);
|
|
48
|
+
if (!credentials) {
|
|
49
|
+
renderOnce(React.createElement(MissingCredentialsError, null));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
53
|
+
if (executionContext.type !== EXECUTION_CONTEXTS.theme) {
|
|
54
|
+
renderOnce(React.createElement(OutsideOfThemeDirectoryContextError, null));
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const themeChecksums = new ThemeChecksums({
|
|
59
|
+
themeDir: executionContext.themeRootDir,
|
|
60
|
+
loggerApi
|
|
61
|
+
});
|
|
62
|
+
await ThemeMetaDataUtils.ensureThemeWorkUrlMatch(executionContext.themeRootDir, credentials.shopUrl);
|
|
63
|
+
const watchAction = themeActionsApi.getThemeAction({
|
|
64
|
+
actionType: THEME_ACTIONS_TYPES.partialPush,
|
|
65
|
+
themeId: executionContext.themeId,
|
|
66
|
+
credentials
|
|
67
|
+
});
|
|
68
|
+
if (!watchAction) {
|
|
69
|
+
renderOnce(React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "watch" }));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(executionContext.themeRootDir);
|
|
73
|
+
if (!filesStructure) {
|
|
74
|
+
renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
const themeFilesUploadApi = new ThemeFilesUpload({
|
|
78
|
+
themeRootDir: executionContext.themeRootDir,
|
|
79
|
+
credentials,
|
|
80
|
+
themeFilesUploadHttpApi: new ThemeFilesUploadHttpApi(this.getApi(HTTP_REQUESTER_API_NAME))
|
|
81
|
+
});
|
|
82
|
+
return render(React.createElement(Watch, { executionContext: executionContext, themeChecksums: themeChecksums, credentials: credentials, themeWatchApi: themeWatchApi, themeFilesUploadApi: themeFilesUploadApi, filesStructure: filesStructure, action: watchAction, openBrowser: flags.open }));
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
renderOnce(React.createElement(ThemeError, { err: err, executionContext: executionContext }));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const WATCH_COMMANDS_NAMES = {
|
|
2
|
+
quit: 'quit',
|
|
3
|
+
open: 'open',
|
|
4
|
+
clear: 'clear'
|
|
5
|
+
};
|
|
6
|
+
export const WATCH_COMMANDS = [
|
|
7
|
+
{
|
|
8
|
+
key: WATCH_COMMANDS_NAMES.quit,
|
|
9
|
+
description: 'Exit watch mode'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
key: WATCH_COMMANDS_NAMES.open,
|
|
13
|
+
description: "Open the theme in a new tab in the browser if it's not already open."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: WATCH_COMMANDS_NAMES.clear,
|
|
17
|
+
description: 'Clear the logs from the screen.'
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
export const MAX_WATCH_LOGS = 300;
|
|
21
|
+
export const COMMAND_PREFIX = '/';
|
|
@@ -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
|
+
}
|