@shoper/cli 0.8.0 → 0.8.1-10

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.
Files changed (52) hide show
  1. package/README.md +1 -11
  2. package/build/cli/class/errors/http/http_errors_factory.js +1 -1
  3. package/build/cli/core/cli_setup.js +6 -6
  4. package/build/cli/index.js +1 -0
  5. package/build/cli/utilities/features/logger/logs/app_logs_constants.js +2 -1
  6. package/build/index.js +2 -2
  7. package/build/theme/class/archive/theme_archive.js +1 -47
  8. package/build/theme/class/browser/browser.js +114 -0
  9. package/build/theme/class/checksums/theme_checksums.js +51 -11
  10. package/build/theme/commands/pull/theme_pull_command.js +4 -3
  11. package/build/theme/commands/push/theme_push_command.js +17 -8
  12. package/build/theme/commands/theme_commands_constants.js +2 -1
  13. package/build/theme/commands/theme_verify_command.js +3 -3
  14. package/build/theme/commands/ui/theme_error.js +6 -6
  15. package/build/theme/commands/watch/theme_watch_command.js +89 -0
  16. package/build/theme/commands/watch/theme_watch_constants.js +21 -0
  17. package/build/theme/commands/watch/theme_watch_utils.js +32 -0
  18. package/build/theme/commands/watch/theme_watching_info.js +14 -0
  19. package/build/theme/commands/watch/watch.js +55 -0
  20. package/build/theme/features/theme/actions/theme_actions_constants.js +2 -1
  21. package/build/theme/features/theme/actions/theme_actions_utils.js +68 -10
  22. package/build/theme/features/theme/fetch/service/theme_fetch_service.js +2 -4
  23. package/build/theme/features/theme/init/service/theme_init_service.js +2 -4
  24. package/build/theme/features/theme/merge/service/theme_merge_service.js +2 -2
  25. package/build/theme/features/theme/push/api/theme_push_api.js +2 -2
  26. package/build/theme/features/theme/push/service/theme_push_service.js +93 -33
  27. package/build/theme/features/theme/push/service/theme_push_service_types.js +1 -0
  28. package/build/theme/features/theme/push/theme_push_utils.js +14 -10
  29. package/build/theme/features/theme/utils/archive/theme_archive_utils.js +26 -0
  30. package/build/theme/{class/archive/theme_archive_errors_factory.js → features/theme/utils/archive/theme_archive_utils_errors_factory.js} +1 -1
  31. package/build/theme/features/theme/utils/files/them_files_constants.js +1 -0
  32. package/build/theme/features/theme/utils/{files_structure/theme_files_structure_utils.js → files/theme_files_utils.js} +36 -28
  33. package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +1 -9
  34. package/build/theme/features/theme/utils/meta_data/theme_meta_data_utils.js +0 -28
  35. package/build/theme/features/theme/verify/verify/theme_verify_service.js +19 -12
  36. package/build/theme/features/theme/watch/api/theme_watch_api.js +19 -0
  37. package/build/theme/features/theme/watch/service/theme_watch_service.js +181 -0
  38. package/build/theme/features/theme/watch/theme_watch_constants.js +5 -0
  39. package/build/theme/features/theme/watch/theme_watch_initializer.js +22 -0
  40. package/build/theme/index.js +6 -2
  41. package/build/theme/utils/directory_validator/directory_validator_utils.js +4 -11
  42. package/build/ui/command_input/command_input.js +25 -0
  43. package/build/ui/logs/log_entry.js +12 -0
  44. package/build/ui/logs/logs_constants.js +20 -0
  45. package/build/ui/logs/logs_list.js +18 -0
  46. package/build/ui/logs/use_logs.js +23 -0
  47. package/build/ui/ui_dump/ui_dump.js +9 -4
  48. package/build/utils/array_utils.js +3 -0
  49. package/build/utils/fs/fs_constants.js +6 -0
  50. package/build/utils/fs/fs_utils.js +1 -1
  51. package/package.json +11 -6
  52. 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/)
@@ -15,7 +15,7 @@ export class HttpErrorsFactory {
15
15
  }
16
16
  static createNotFoundError() {
17
17
  return new AppError({
18
- message: 'Not found',
18
+ message: '404 Not found',
19
19
  code: HTTP_NOT_FOUND_ERROR_CODE
20
20
  });
21
21
  }
@@ -29,7 +29,7 @@ export const cliSetup = async () => {
29
29
  LoggerInitializer,
30
30
  CliAuthTokensInitializer,
31
31
  CliAuthInitializer,
32
- ...getCommandBaseInitializers()
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 getCommandBaseInitializers = () => {
41
- if (isCommandsTopic(process.argv[2]))
42
- return getCommandWithTopicBaseInitializers();
40
+ const getInitializersBasedOnCommand = () => {
41
+ if (isCommandWithTopic(process.argv[2]))
42
+ return getInitializersBasedOnCommandWithTopic();
43
43
  return [];
44
44
  };
45
- const isCommandsTopic = (arg) => {
45
+ const isCommandWithTopic = (arg) => {
46
46
  return arg === THEME_TOPIC_NAME || arg === CLI_AUTH_TOPIC_NAME;
47
47
  };
48
- const getCommandWithTopicBaseInitializers = () => {
48
+ const getInitializersBasedOnCommandWithTopic = () => {
49
49
  const topic = process.argv[2];
50
50
  const command = process.argv[3];
51
51
  switch (topic) {
@@ -6,6 +6,7 @@ export const runCLI = async () => {
6
6
  await execute({ dir: import.meta.url });
7
7
  }
8
8
  catch (error) {
9
+ console.log('Error executing CLI command:', error);
9
10
  // TODO handle error
10
11
  }
11
12
  };
@@ -3,5 +3,6 @@ export const APP_LOGS_TYPES = {
3
3
  error: 'error',
4
4
  warn: 'warn',
5
5
  info: 'info',
6
- debug: 'debug'
6
+ debug: 'debug',
7
+ success: 'success'
7
8
  };
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
- import globs from 'fast-glob';
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,114 @@
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
+ args: process.platform === 'linux' ? ['--no-sandbox'] : []
23
+ });
24
+ this.#browser.on('disconnected', () => {
25
+ this.#onDisconnected?.('Browser disconnected');
26
+ this.#browser = null;
27
+ this.#tabs.clear();
28
+ });
29
+ }
30
+ }
31
+ catch (error) {
32
+ this.#onError?.(`Error launch browser:`, error);
33
+ throw error;
34
+ }
35
+ }
36
+ async openTab(url, tabId) {
37
+ try {
38
+ if (!this.#browser || !this.isBrowserOpen()) {
39
+ this.#onInfo?.(`Browser not open, launching browser...`);
40
+ await this.launch();
41
+ }
42
+ if (!this.#browser)
43
+ return;
44
+ const id = tabId ?? `tab-${uuid()}`;
45
+ this.#onInfo?.(`Opening new tab ${id}: ${url}`);
46
+ const page = await this.#browser.newPage();
47
+ await page.setCacheEnabled(false);
48
+ await page.goto(url, { waitUntil: 'networkidle2' });
49
+ this.#tabs.set(id, { page, url });
50
+ this.#onInfo?.(`Tab ${id} opened successfully`);
51
+ }
52
+ catch (error) {
53
+ this.#onError?.(`Error opening tab:`, error);
54
+ throw error;
55
+ }
56
+ }
57
+ async refresh(tabId) {
58
+ const tab = this.#tabs.get(tabId);
59
+ if (!tab) {
60
+ this.#onError?.(`Tab ${tabId} not found`);
61
+ return;
62
+ }
63
+ try {
64
+ this.#onInfo?.(`Refreshing tab ${tabId}...`);
65
+ await tab.page.reload({ waitUntil: 'networkidle2' });
66
+ this.#onInfo?.(`Tab ${tabId} refreshed`);
67
+ }
68
+ catch (error) {
69
+ this.#onError?.(`Error refreshing tab ${tabId}:`, error);
70
+ throw error;
71
+ }
72
+ }
73
+ async refreshAll() {
74
+ const openTabs = this.getOpenTabs();
75
+ await Promise.allSettled(openTabs.map((tabId) => this.refresh(tabId)));
76
+ }
77
+ async closeTab(tabId) {
78
+ const tab = this.#tabs.get(tabId);
79
+ if (!tab) {
80
+ this.#onError?.(`Tab ${tabId} not found`);
81
+ return;
82
+ }
83
+ try {
84
+ await tab.page.close();
85
+ this.#tabs.delete(tabId);
86
+ this.#onInfo?.(`Tab ${tabId} closed`);
87
+ }
88
+ catch (error) {
89
+ this.#onError?.(`Error closing tab ${tabId}:`, error);
90
+ throw error;
91
+ }
92
+ }
93
+ async close() {
94
+ if (!this.isBrowserOpen())
95
+ return;
96
+ await this.#browser?.close();
97
+ this.#browser = null;
98
+ this.#tabs.clear();
99
+ this.#onInfo?.('Browser closed with all tabs');
100
+ }
101
+ isBrowserOpen() {
102
+ return this.#browser !== null && this.#browser.connected;
103
+ }
104
+ isTabOpen(tabId) {
105
+ const tab = this.#tabs.get(tabId);
106
+ return tab !== undefined && !tab.page.isClosed();
107
+ }
108
+ getOpenTabs() {
109
+ return Array.from(this.#tabs.keys()).filter((tabId) => this.isTabOpen(tabId));
110
+ }
111
+ getTabUrl(tabId) {
112
+ return this.#tabs.get(tabId)?.url ?? null;
113
+ }
114
+ }
@@ -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 hasThemeFileBeenModified(path) {
76
- const currentChecksum = await this.getCurrentChecksumFromPath(path);
70
+ async getFileState(path) {
77
71
  const initialChecksum = await this.getInitialChecksumFromPath(path);
78
- return !!currentChecksum && !!initialChecksum && currentChecksum !== initialChecksum;
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 allFiles = await ThemePushUtils.getAllFilesThatAreSendToRemote(this.#themeDir);
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, 'modules');
164
+ const localModulesPath = join(executionContext.themeRootDir, MODULES_DIRECTORY_NAME);
164
165
  const fetchedThemePath = join(tmpDir, name);
165
- const fetchedModulesPath = join(fetchedThemePath, 'modules');
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 { ThemeFilesStructureUtils } from '../../features/theme/utils/files_structure/theme_files_structure_utils.js';
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 ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
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 ThemeFilesStructureUtils.validateThemeDirectoryStructure({
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 ThemeFilesStructureUtils.getThemeFilesStructure(executionContext.themeRootDir);
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
- await themePushApi.push({
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
- pushAction,
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 ThemeFilesStructureUtils.getFilesPermissions(executionContext.themeRootDir);
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 ThemeFilesStructureUtils.getThemeFilesStructure(executionContext.themeRootDir);
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.\n" },
23
- React.createElement(Text, null, "The rejected files have been removed locally. Please ensure that the files are not corrupted and that the file extensions are supported.")));
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 = '/';