@shoper/cli 0.8.3-0 → 0.9.0

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.
@@ -18,7 +18,8 @@ export class Browser {
18
18
  this.#onInfo?.(`Launching browser...`);
19
19
  this.#browser = await puppeteer.launch({
20
20
  headless: false,
21
- defaultViewport: null
21
+ defaultViewport: null,
22
+ args: process.platform === 'linux' ? ['--no-sandbox'] : []
22
23
  });
23
24
  this.#browser.on('disconnected', () => {
24
25
  this.#onDisconnected?.('Browser disconnected');
@@ -69,6 +70,10 @@ export class Browser {
69
70
  throw error;
70
71
  }
71
72
  }
73
+ async refreshAll() {
74
+ const openTabs = this.getOpenTabs();
75
+ await Promise.allSettled(openTabs.map((tabId) => this.refresh(tabId)));
76
+ }
72
77
  async closeTab(tabId) {
73
78
  const tab = this.#tabs.get(tabId);
74
79
  if (!tab) {
@@ -25,8 +25,6 @@ import { ThemeError } from '../ui/theme_error.js';
25
25
  import { mapToPermissionsTree } from '../../utils/directory_validator/directory_validator_utils.js';
26
26
  import { mapChecksumToTree } from '../../../utils/checksums/checksums_utils.js';
27
27
  import { ThemeUnpermittedActionsError } from './ui/theme_unpermitted_actions_error.js';
28
- import { validateThemeFolderStructure } from '../../utils/push_validators/push_path_validator_utils.js';
29
- import { ThemeInvalidFoldersError } from './ui/theme_invalid_folders_error.js';
30
28
  import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
31
29
  export class ThemePushCommand extends BaseThemeCommand {
32
30
  static summary = 'Uploads your local theme files to the store and overwrites the current version of the theme in your store.';
@@ -88,11 +86,8 @@ export class ThemePushCommand extends BaseThemeCommand {
88
86
  permissions: mapToPermissionsTree(permissions),
89
87
  rootDirectory: executionContext.themeRootDir
90
88
  });
91
- const folderStructureValidation = await validateThemeFolderStructure(executionContext.themeRootDir);
92
- if (!folderStructureValidation.isValid) {
93
- renderOnce(React.createElement(ThemeInvalidFoldersError, { invalidDirectories: folderStructureValidation.invalidDirectories }));
94
- return;
95
- }
89
+ //TODO jak wysyla folder z nazwa posiadajaco \ na windows, wychodzimy
90
+ //TODO validacja folderów przed pushem
96
91
  if (!validationResult.isValid) {
97
92
  renderOnce(React.createElement(ThemeUnpermittedActionsError, { unpermittedActions: validationResult.unpermittedActions }));
98
93
  return;
@@ -2,7 +2,7 @@ import { AppLog } from '../../../../../cli/utilities/features/logger/logs/app_lo
2
2
  import chokidar from 'chokidar';
3
3
  import { APP_LOGS_TYPES } from '../../../../../cli/utilities/features/logger/logs/app_logs_constants.js';
4
4
  import { relative } from '../../../../../utils/path_utils.js';
5
- import { SHOP_BROWSER_TAB_ID, THEME_BATCH_DELAY_MS } from '../theme_watch_constants.js';
5
+ import { SHOP_BROWSER_TAB_ID, STORE_FRONT_BROWSER_TAB_ID, THEME_BATCH_DELAY_MS } from '../theme_watch_constants.js';
6
6
  import { FILE_STATES } from '../../../../../utils/fs/fs_constants.js';
7
7
  export class ThemeWatchService {
8
8
  #themePushApi;
@@ -14,6 +14,7 @@ export class ThemeWatchService {
14
14
  #changedFiles = new Set();
15
15
  #watcher = null;
16
16
  #urlForWatchedTheme = null;
17
+ #storeFrontUrl = null;
17
18
  constructor({ themePushApi, browserClient }) {
18
19
  this.#themePushApi = themePushApi;
19
20
  this.#browserApi = new browserClient({
@@ -26,9 +27,11 @@ export class ThemeWatchService {
26
27
  this.#onMessage = onMessage || null;
27
28
  const themePath = executionContext.themeRootDir;
28
29
  this.#urlForWatchedTheme = `${credentials.shopUrl}/admin/configSkins/skin-preview/id/${executionContext.themeId}`;
30
+ this.#storeFrontUrl = credentials.shopUrl;
29
31
  if (openBrowser) {
30
32
  await this.#browserApi.launch();
31
33
  await this.#browserApi.openTab(this.#urlForWatchedTheme, SHOP_BROWSER_TAB_ID);
34
+ await this.#browserApi.openTab(this.#storeFrontUrl, STORE_FRONT_BROWSER_TAB_ID);
32
35
  }
33
36
  this._onInfoMessage(`Watching for changes in: ${themePath}`);
34
37
  this.#watcher = chokidar.watch(themePath, {
@@ -82,21 +85,30 @@ export class ThemeWatchService {
82
85
  .on('ready', () => this._onInfoMessage('Initial scan complete. Ready for changes.'));
83
86
  }
84
87
  async ensureBrowserTabOpen() {
85
- if (!this.#urlForWatchedTheme)
88
+ if (!this.#urlForWatchedTheme || !this.#storeFrontUrl)
86
89
  return;
87
90
  if (!this.#browserApi.isBrowserOpen()) {
88
91
  this._onInfoMessage(`Browser is not open, launching...`);
89
92
  await this.#browserApi.launch();
90
93
  await this.#browserApi.openTab(this.#urlForWatchedTheme, SHOP_BROWSER_TAB_ID);
91
- this._onInfoMessage(`Browser opened with new tab`);
94
+ await this.#browserApi.openTab(this.#storeFrontUrl, STORE_FRONT_BROWSER_TAB_ID);
95
+ this._onInfoMessage(`Browser opened with new tabs`);
92
96
  return;
93
97
  }
98
+ let reopened = false;
94
99
  if (!this.#browserApi.isTabOpen(SHOP_BROWSER_TAB_ID)) {
95
- this._onInfoMessage(`Tab was closed, reopening...`);
100
+ this._onInfoMessage(`Admin preview tab was closed, reopening...`);
96
101
  await this.#browserApi.openTab(this.#urlForWatchedTheme, SHOP_BROWSER_TAB_ID);
97
- return;
102
+ reopened = true;
103
+ }
104
+ if (!this.#browserApi.isTabOpen(STORE_FRONT_BROWSER_TAB_ID)) {
105
+ this._onInfoMessage(`Store front tab was closed, reopening...`);
106
+ await this.#browserApi.openTab(this.#storeFrontUrl, STORE_FRONT_BROWSER_TAB_ID);
107
+ reopened = true;
108
+ }
109
+ if (!reopened) {
110
+ this._onInfoMessage(`Browser and tabs already open`);
98
111
  }
99
- this._onInfoMessage(`Browser and tab already open`);
100
112
  }
101
113
  async stopWatching() {
102
114
  this._onInfoMessage('Shutting down...');
@@ -105,6 +117,7 @@ export class ThemeWatchService {
105
117
  await this.#watcher?.close();
106
118
  await this.#browserApi.close();
107
119
  this.#urlForWatchedTheme = null;
120
+ this.#storeFrontUrl = null;
108
121
  }
109
122
  _clearQueuedBatch() {
110
123
  if (this.#batchTimeout) {
@@ -112,13 +125,14 @@ export class ThemeWatchService {
112
125
  this.#batchTimeout = null;
113
126
  }
114
127
  }
115
- async _refreshBrowserTab() {
116
- if (!SHOP_BROWSER_TAB_ID || !this.#browserApi.isTabOpen(SHOP_BROWSER_TAB_ID))
128
+ async _refreshBrowserTabs() {
129
+ const openTabs = this.#browserApi.getOpenTabs();
130
+ if (openTabs.length === 0)
117
131
  return;
118
132
  try {
119
- this._onInfoMessage(`Refreshing browser tab...`);
120
- await this.#browserApi.refresh(SHOP_BROWSER_TAB_ID);
121
- this._onInfoMessage(`Browser tab refreshed`);
133
+ this._onInfoMessage(`Refreshing browser tabs...`);
134
+ await this.#browserApi.refreshAll();
135
+ this._onInfoMessage(`Browser tabs refreshed`);
122
136
  }
123
137
  catch (error) {
124
138
  this._onErrorMessage(`Error refreshing browser:`, error);
@@ -132,7 +146,7 @@ export class ThemeWatchService {
132
146
  this._onInfoMessage(`Pushing theme...`);
133
147
  await this.#themePushApi.partialPush(params);
134
148
  this._onSuccessMessage(`Theme pushed successfully`);
135
- await this._refreshBrowserTab();
149
+ await this._refreshBrowserTabs();
136
150
  if (this.#pendingPush) {
137
151
  this._onInfoMessage(`Executing queued push...`);
138
152
  this.#isPushing = false;
@@ -2,3 +2,4 @@ export const THEME_WATCH_FEATURE_NAME = 'ThemeWatch';
2
2
  export const THEME_WATCH_API_NAME = 'ThemeWatchApi';
3
3
  export const THEME_BATCH_DELAY_MS = 750;
4
4
  export const SHOP_BROWSER_TAB_ID = 'shop-page';
5
+ export const STORE_FRONT_BROWSER_TAB_ID = 'store-front';
@@ -3,5 +3,6 @@ export const THEME_COMMANDS_THAT_REQUIRED_ACTIONS_LIST = [
3
3
  THEME_COMMANDS_NAME.pull,
4
4
  THEME_COMMANDS_NAME.init,
5
5
  THEME_COMMANDS_NAME.push,
6
- THEME_COMMANDS_NAME.delete
6
+ THEME_COMMANDS_NAME.delete,
7
+ THEME_COMMANDS_NAME.watch
7
8
  ];
@@ -5,7 +5,7 @@ import { createWriteStream } from 'node:fs';
5
5
  import { StreamReadError } from '../fs/errors/stream_read_error.js';
6
6
  import { CreateZipError } from './errors/create_zip_error.js';
7
7
  import { StreamWriteError } from '../fs/errors/stream_write_error.js';
8
- import { join, toUnixPath } from '../path_utils.js';
8
+ import { join } from '../path_utils.js';
9
9
  import { AppError } from '../../cli/utilities/features/logger/logs/app_error.js';
10
10
  export const createZip = async ({ files, dist, baseDir = process.cwd(), logger }) => {
11
11
  const zipfile = new yazl.ZipFile();
@@ -29,14 +29,14 @@ export const createZip = async ({ files, dist, baseDir = process.cwd(), logger }
29
29
  code: 'FILE_NOT_FOUND',
30
30
  details: { file, baseDir }
31
31
  });
32
- const zipEntryName = toUnixPath(file);
33
32
  if (await isDirectory(fullPath)) {
34
- logger.debug('Adding empty directory to zip', { details: { directory: zipEntryName } });
35
- zipfile.addEmptyDirectory(zipEntryName);
33
+ logger.debug('Adding empty directory to zip', { details: { directory: file } });
34
+ zipfile.addEmptyDirectory(file);
36
35
  }
37
36
  else {
38
- logger.debug('Adding file to zip', { details: { file: zipEntryName, fullPath } });
39
- zipfile.addFile(fullPath, zipEntryName, {
37
+ logger.debug('Adding file to zip', { details: { file, fullPath } });
38
+ zipfile.addFile(fullPath, file, {
39
+ //TODO params
40
40
  compress: true
41
41
  });
42
42
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@shoper/cli",
3
3
  "packageManager": "yarn@3.2.0",
4
4
  "sideEffects": false,
5
- "version": "0.8.3-0",
5
+ "version": "0.9.0",
6
6
  "description": "CLI tool for Shoper",
7
7
  "author": "Joanna Firek",
8
8
  "license": "MIT",
@@ -1,16 +0,0 @@
1
- import { List } from '../../../../ui/list/list.js';
2
- import { Error } from '../../../../ui/message_box/error.js';
3
- import { Text } from '../../../../ui/text.js';
4
- import React from 'react';
5
- import { ALLOWED_THEME_TOP_LEVEL_DIRECTORIES } from '../../../utils/push_validators/push_validator_constants.js';
6
- export const ThemeInvalidFoldersError = ({ invalidDirectories }) => {
7
- const items = invalidDirectories.map((dir) => ({
8
- content: dir
9
- }));
10
- return (React.createElement(Error, { header: "Invalid theme folder structure" },
11
- React.createElement(Text, null, "The following directories are not allowed in the theme root:"),
12
- React.createElement(List, { items: items }),
13
- React.createElement(Text, null,
14
- "Allowed top-level directories: ",
15
- ALLOWED_THEME_TOP_LEVEL_DIRECTORIES.join(', '))));
16
- };
@@ -1,16 +0,0 @@
1
- import { getAllDirectoriesNamesInside } from '../../../utils/fs/fs_utils.js';
2
- import { ALLOWED_THEME_TOP_LEVEL_DIRECTORIES } from './push_validator_constants.js';
3
- export const validateThemeFolderStructure = async (themeRootDir) => {
4
- const directories = await getAllDirectoriesNamesInside(themeRootDir, { recursive: false, hidden: true });
5
- const allowedSet = new Set(ALLOWED_THEME_TOP_LEVEL_DIRECTORIES);
6
- const invalidDirectories = directories.filter((dir) => {
7
- if (dir.startsWith('.') && !allowedSet.has(dir)) {
8
- return false;
9
- }
10
- return !allowedSet.has(dir);
11
- });
12
- return {
13
- isValid: invalidDirectories.length === 0,
14
- invalidDirectories
15
- };
16
- };
@@ -1,8 +0,0 @@
1
- export const ALLOWED_THEME_TOP_LEVEL_DIRECTORIES = [
2
- '.shoper',
3
- 'settings',
4
- 'styles',
5
- 'macros',
6
- 'modules',
7
- 'skinstore'
8
- ];