@shoper/cli 0.9.2 → 0.9.4-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/theme/class/archive/theme_archive.js +47 -1
- package/build/theme/class/archive/theme_archive_errors_factory.js +11 -0
- package/build/theme/commands/push/theme_push_command.js +7 -2
- package/build/theme/commands/push/ui/theme_invalid_folders_error.js +16 -0
- package/build/theme/features/theme/actions/theme_actions_utils.js +1 -1
- package/build/theme/utils/push_validators/push_path_validator_utils.js +16 -0
- package/build/theme/utils/push_validators/push_validator_constants.js +8 -0
- package/build/theme/utils/shoperignore/shoperignore_utils.js +1 -2
- package/build/utils/zip/create_zip_utils.js +6 -6
- package/package.json +1 -1
|
@@ -1 +1,47 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
+
};
|
|
@@ -63,7 +63,7 @@ export class ThemeActionsUtils {
|
|
|
63
63
|
? fileGlob.slice(0, fileGlob.length - 2)
|
|
64
64
|
: fileGlob;
|
|
65
65
|
}
|
|
66
|
-
const deletedFiles = difference(checksumMatchedFiles,
|
|
66
|
+
const deletedFiles = difference(checksumMatchedFiles, files);
|
|
67
67
|
for (const filePath of [...filteredFiles, ...deletedFiles]) {
|
|
68
68
|
filesRecords.push({
|
|
69
69
|
actionData,
|
|
@@ -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
|
+
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from '../../../utils/path_utils.js';
|
|
3
3
|
import { fileExists } from '../../../utils/fs/fs_utils.js';
|
|
4
|
-
import
|
|
5
|
-
const ignore = ignoreModule;
|
|
4
|
+
import ignore from 'ignore';
|
|
6
5
|
export const SHOPER_IGNORE_FILE_NAME = '.shoperignore';
|
|
7
6
|
export async function loadShoperIgnore(themeRootDir) {
|
|
8
7
|
const shoperIgnorePath = join(themeRootDir, SHOPER_IGNORE_FILE_NAME);
|
|
@@ -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:
|
|
34
|
-
zipfile.addEmptyDirectory(
|
|
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,
|
|
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
|
}
|