@shoper/cli 0.8.1-3 → 0.8.1-5
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 +108 -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
package/README.md
CHANGED
|
@@ -2,14 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
# Shoper CLI
|
|
4
4
|
|
|
5
|
-
Learn more in the [commands docs](https://storefront.developers.shoper.pl/cli/)
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
### .shoperignore
|
|
10
|
-
|
|
11
|
-
Plik `.shoperignore` pozwala na wykluczenie określonych plików i katalogów z pakowania, uploadowania oraz weryfikacji checksum.
|
|
12
|
-
|
|
13
|
-
Używa tej samej składni co `.gitignore`. Plik jest automatycznie tworzony podczas inicjalizacji lub pobierania motywu.
|
|
14
|
-
|
|
15
|
-
Więcej informacji: [SHOPERIGNORE.md](docs/SHOPERIGNORE.md)
|
|
5
|
+
Learn more in the [commands docs](https://storefront.developers.shoper.pl/cli/)
|
|
@@ -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,47 +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
|
-
import { filterFiles } from '../../utils/shoperignore/shoperignore_utils.js';
|
|
10
|
-
export class ThemeArchive {
|
|
11
|
-
#themeRootDir;
|
|
12
|
-
constructor(themeRootDir) {
|
|
13
|
-
this.#themeRootDir = themeRootDir;
|
|
14
|
-
}
|
|
15
|
-
async createFullArchive({ dist, actionValue, actionType, logger }) {
|
|
16
|
-
const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(this.#themeRootDir);
|
|
17
|
-
if (!filesStructure)
|
|
18
|
-
throw ThemeFileStructureErrorsFactory.createNoFilesStructureError();
|
|
19
|
-
try {
|
|
20
|
-
const filesInThemeDirectory = await globs(ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
|
|
21
|
-
actionType,
|
|
22
|
-
actionValue,
|
|
23
|
-
filesStructure
|
|
24
|
-
}), {
|
|
25
|
-
suppressErrors: true,
|
|
26
|
-
onlyFiles: true,
|
|
27
|
-
cwd: this.#themeRootDir
|
|
28
|
-
});
|
|
29
|
-
const filteredFiles = await filterFiles(filesInThemeDirectory, this.#themeRootDir);
|
|
30
|
-
await this._formatJsonFiles(this.#themeRootDir, filteredFiles);
|
|
31
|
-
await createZip({
|
|
32
|
-
baseDir: this.#themeRootDir,
|
|
33
|
-
dist,
|
|
34
|
-
files: filteredFiles,
|
|
35
|
-
logger
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
throw ThemeArchiveErrorsFactory.createErrorWhileCreatingThemeArchive(err);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async _formatJsonFiles(themeRootDir, filesToUpload) {
|
|
43
|
-
await Promise.all(filesToUpload
|
|
44
|
-
.filter((path) => extname(path).toLowerCase() === '.json')
|
|
45
|
-
.map((jsonFile) => formatJSONFile(join(themeRootDir, jsonFile))));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
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,10 +67,22 @@ 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;
|
|
@@ -87,6 +94,8 @@ export class ThemeChecksums {
|
|
|
87
94
|
async updateAllChecksums() {
|
|
88
95
|
this.#loggerApi?.debug('Updating all checksums.');
|
|
89
96
|
const checksums = await this.computeThemeChecksums();
|
|
97
|
+
this.#initialChecksums = checksums;
|
|
98
|
+
this.#currentChecksums = checksums;
|
|
90
99
|
return new Promise((resolve, reject) => {
|
|
91
100
|
const currentChecksumFilePath = this.#currentChecksumsFilePath;
|
|
92
101
|
const initialChecksumFilePath = this.#initialChecksumsFilePath;
|
|
@@ -114,6 +123,7 @@ export class ThemeChecksums {
|
|
|
114
123
|
async updateCurrentChecksums() {
|
|
115
124
|
this.#loggerApi?.debug('Updating current checksums.');
|
|
116
125
|
const checksums = await this.computeThemeChecksums();
|
|
126
|
+
this.#currentChecksums = checksums;
|
|
117
127
|
return new Promise((resolve, reject) => {
|
|
118
128
|
const currentChecksumFilePath = this.#currentChecksumsFilePath;
|
|
119
129
|
const checksumStream = createWriteStream(currentChecksumFilePath);
|
|
@@ -144,8 +154,7 @@ export class ThemeChecksums {
|
|
|
144
154
|
join(SHOPER_THEME_METADATA_DIR, THEME_INITIAL_CHECKSUMS_FILE_NAME),
|
|
145
155
|
join(SHOPER_THEME_METADATA_DIR, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME)
|
|
146
156
|
];
|
|
147
|
-
const
|
|
148
|
-
const filesToComputeChecksums = allFiles
|
|
157
|
+
const filesToComputeChecksums = (await ThemePushUtils.getAllFilesThatAreSendToRemote(this.#themeDir))
|
|
149
158
|
.filter((path) => !filesToIgnoreInChecksums.some((ignorePath) => normalize(path) === ignorePath))
|
|
150
159
|
.map((relativePath) => join(this.#themeDir, relativePath));
|
|
151
160
|
this.#loggerApi?.debug('Computing checksums from file structure.', { details: { count: filesToComputeChecksums.length } });
|
|
@@ -155,6 +164,37 @@ export class ThemeChecksums {
|
|
|
155
164
|
this.#loggerApi?.debug('Finished computing theme checksums.', { details: { count: Object.keys(allChecksums).length } });
|
|
156
165
|
return allChecksums;
|
|
157
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
|
+
}
|
|
158
198
|
async _getInitialChecksumsVerification() {
|
|
159
199
|
return await readJSONFile(this.#initialChecksumsVerificationFilePath);
|
|
160
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,89 @@
|
|
|
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
|
+
import { retro } from 'gradient-string';
|
|
25
|
+
export class ThemeWatchCommand extends BaseThemeCommand {
|
|
26
|
+
static summary = `Watch local theme files for changes and update your store preview in real time. ${retro('[BETA]')}`;
|
|
27
|
+
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).`;
|
|
28
|
+
static examples = [
|
|
29
|
+
{
|
|
30
|
+
description: 'This will replace the store version with your local copy on files modification.',
|
|
31
|
+
command: '<%= config.bin %> <%= command.id %>'
|
|
32
|
+
}
|
|
33
|
+
];
|
|
34
|
+
static flags = {
|
|
35
|
+
open: Flags.boolean({
|
|
36
|
+
description: 'Open the store preview in your default web browser when watching starts.',
|
|
37
|
+
required: false,
|
|
38
|
+
default: false
|
|
39
|
+
})
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
43
|
+
const loggerApi = this.getApi(LOGGER_API_NAME);
|
|
44
|
+
const themeWatchApi = this.getApi(THEME_WATCH_API_NAME);
|
|
45
|
+
const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
|
|
46
|
+
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
47
|
+
const credentials = cliAuthApi.getCredentials();
|
|
48
|
+
const { flags } = await this.parse(ThemeWatchCommand);
|
|
49
|
+
if (!credentials) {
|
|
50
|
+
renderOnce(React.createElement(MissingCredentialsError, null));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const executionContext = await executionContextApi.getExecutionContext();
|
|
54
|
+
if (executionContext.type !== EXECUTION_CONTEXTS.theme) {
|
|
55
|
+
renderOnce(React.createElement(OutsideOfThemeDirectoryContextError, null));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const themeChecksums = new ThemeChecksums({
|
|
60
|
+
themeDir: executionContext.themeRootDir,
|
|
61
|
+
loggerApi
|
|
62
|
+
});
|
|
63
|
+
await ThemeMetaDataUtils.ensureThemeWorkUrlMatch(executionContext.themeRootDir, credentials.shopUrl);
|
|
64
|
+
const watchAction = themeActionsApi.getThemeAction({
|
|
65
|
+
actionType: THEME_ACTIONS_TYPES.partialPush,
|
|
66
|
+
themeId: executionContext.themeId,
|
|
67
|
+
credentials
|
|
68
|
+
});
|
|
69
|
+
if (!watchAction) {
|
|
70
|
+
renderOnce(React.createElement(UnpermittedCommandError, { themeId: executionContext.themeId, commandName: "watch" }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const filesStructure = await ThemeFilesUtils.getThemeFilesStructure(executionContext.themeRootDir);
|
|
74
|
+
if (!filesStructure) {
|
|
75
|
+
renderOnce(React.createElement(MissingThemeFiles, { files: `${SHOPER_THEME_METADATA_DIR}/${THEME_FILES_STRUCTURE_FILE_NAME}` }));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const themeFilesUploadApi = new ThemeFilesUpload({
|
|
79
|
+
themeRootDir: executionContext.themeRootDir,
|
|
80
|
+
credentials,
|
|
81
|
+
themeFilesUploadHttpApi: new ThemeFilesUploadHttpApi(this.getApi(HTTP_REQUESTER_API_NAME))
|
|
82
|
+
});
|
|
83
|
+
return render(React.createElement(Watch, { executionContext: executionContext, themeChecksums: themeChecksums, credentials: credentials, themeWatchApi: themeWatchApi, themeFilesUploadApi: themeFilesUploadApi, filesStructure: filesStructure, action: watchAction, openBrowser: flags.open }));
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
renderOnce(React.createElement(ThemeError, { err: err, executionContext: executionContext }));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -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 = '/';
|