@shoper/cli 0.9.2 → 0.9.4-2

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.
@@ -1 +1,47 @@
1
- export {};
1
+ import globs from 'fast-glob';
2
+ import { createZip } from '../../../utils/zip/create_zip_utils.js';
3
+ import { ThemeFilesUtils } from '../../features/theme/utils/files/theme_files_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 { ThemeActionsUtils } from '../../features/theme/actions/theme_actions_utils.js';
8
+ import { filterFiles } from '../../utils/shoperignore/shoperignore_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 ThemeFilesUtils.getThemeFilesStructure(this.#themeRootDir);
16
+ if (!filesStructure) {
17
+ throw new Error('Files structure not found');
18
+ }
19
+ try {
20
+ const filesInThemeDirectory = await globs(ThemeActionsUtils.getFilesGlobsThatMatchesAction({
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
+ }
@@ -0,0 +1,11 @@
1
+ import { AppError } from '../../../cli/utilities/features/logger/logs/app_error.js';
2
+ export class ThemeArchiveErrorsFactory {
3
+ static createErrorWhileCreatingThemeArchive(error) {
4
+ return new AppError({
5
+ code: 'theme_archive.error_creating_archive',
6
+ message: `Error while creating theme archive`,
7
+ level: 'error',
8
+ error
9
+ });
10
+ }
11
+ }
@@ -25,6 +25,8 @@ 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';
28
30
  import { LOGGER_API_NAME } from '../../../cli/utilities/features/logger/logger_constants.js';
29
31
  export class ThemePushCommand extends BaseThemeCommand {
30
32
  static summary = 'Uploads your local theme files to the store and overwrites the current version of the theme in your store.';
@@ -86,8 +88,11 @@ export class ThemePushCommand extends BaseThemeCommand {
86
88
  permissions: mapToPermissionsTree(permissions),
87
89
  rootDirectory: executionContext.themeRootDir
88
90
  });
89
- //TODO jak wysyla folder z nazwa posiadajaco \ na windows, wychodzimy
90
- //TODO validacja folderów przed pushem
91
+ const folderStructureValidation = await validateThemeFolderStructure(executionContext.themeRootDir);
92
+ if (!folderStructureValidation.isValid) {
93
+ renderOnce(React.createElement(ThemeInvalidFoldersError, { invalidDirectories: folderStructureValidation.invalidDirectories }));
94
+ return;
95
+ }
91
96
  if (!validationResult.isValid) {
92
97
  renderOnce(React.createElement(ThemeUnpermittedActionsError, { unpermittedActions: validationResult.unpermittedActions }));
93
98
  return;
@@ -0,0 +1,16 @@
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
+ };
@@ -0,0 +1,16 @@
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
+ };
@@ -0,0 +1,8 @@
1
+ export const ALLOWED_THEME_TOP_LEVEL_DIRECTORIES = [
2
+ '.shoper',
3
+ 'settings',
4
+ 'styles',
5
+ 'macros',
6
+ 'modules',
7
+ 'skinstore'
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 } from '../path_utils.js';
8
+ import { join, toUnixPath } 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);
32
33
  if (await isDirectory(fullPath)) {
33
- logger.debug('Adding empty directory to zip', { details: { directory: file } });
34
- zipfile.addEmptyDirectory(file);
34
+ logger.debug('Adding empty directory to zip', { details: { directory: zipEntryName } });
35
+ zipfile.addEmptyDirectory(zipEntryName);
35
36
  }
36
37
  else {
37
- logger.debug('Adding file to zip', { details: { file, fullPath } });
38
- zipfile.addFile(fullPath, file, {
39
- //TODO params
38
+ logger.debug('Adding file to zip', { details: { file: zipEntryName, fullPath } });
39
+ zipfile.addFile(fullPath, zipEntryName, {
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.9.2",
5
+ "version": "0.9.4-2",
6
6
  "description": "CLI tool for Shoper",
7
7
  "author": "Joanna Firek",
8
8
  "license": "MIT",