@shoper/cli 0.6.4-3 → 0.8.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.
- package/README.md +11 -1
- package/build/theme/class/archive/theme_archive.js +7 -5
- package/build/theme/class/checksums/theme_checksums.js +2 -4
- package/build/theme/features/theme/actions/theme_actions_utils.js +5 -1
- package/build/theme/features/theme/fetch/service/theme_fetch_service.js +2 -0
- package/build/theme/features/theme/init/service/theme_init_service.js +2 -0
- package/build/theme/features/theme/push/theme_push_utils.js +7 -4
- package/build/theme/features/theme/utils/files_structure/theme_files_structure_utils.js +5 -2
- package/build/theme/features/theme/utils/hidden_directory/hidden_directory_utils.js +0 -1
- package/build/theme/features/theme/utils/meta_data/theme_meta_data_utils.js +28 -0
- package/build/theme/utils/directory_validator/directory_validator_utils.js +11 -4
- package/build/theme/utils/shoperignore/shoperignore_utils.js +36 -0
- package/build/utils/checksums/checksums_utils.js +0 -4
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,4 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
# Shoper CLI
|
|
4
4
|
|
|
5
|
-
Learn more in the [commands docs](https://storefront.developers.shoper.pl/cli/)
|
|
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)
|
|
@@ -6,6 +6,7 @@ import { formatJSONFile } from '../../../utils/fs/fs_utils.js';
|
|
|
6
6
|
import { ThemeArchiveErrorsFactory } from './theme_archive_errors_factory.js';
|
|
7
7
|
import { ThemeFileStructureErrorsFactory } from '../../features/theme/utils/files_structure/theme_file_structure_errors_factory.js';
|
|
8
8
|
import { ThemeActionsUtils } from '../../features/theme/actions/theme_actions_utils.js';
|
|
9
|
+
import { filterFiles } from '../../utils/shoperignore/shoperignore_utils.js';
|
|
9
10
|
export class ThemeArchive {
|
|
10
11
|
#themeRootDir;
|
|
11
12
|
constructor(themeRootDir) {
|
|
@@ -25,12 +26,13 @@ export class ThemeArchive {
|
|
|
25
26
|
onlyFiles: true,
|
|
26
27
|
cwd: this.#themeRootDir
|
|
27
28
|
});
|
|
28
|
-
await
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
logger,
|
|
29
|
+
const filteredFiles = await filterFiles(filesInThemeDirectory, this.#themeRootDir);
|
|
30
|
+
await this._formatJsonFiles(this.#themeRootDir, filteredFiles);
|
|
31
|
+
await createZip({
|
|
32
32
|
baseDir: this.#themeRootDir,
|
|
33
|
-
dist
|
|
33
|
+
dist,
|
|
34
|
+
files: filteredFiles,
|
|
35
|
+
logger
|
|
34
36
|
});
|
|
35
37
|
}
|
|
36
38
|
catch (err) {
|
|
@@ -80,11 +80,8 @@ export class ThemeChecksums {
|
|
|
80
80
|
async verify() {
|
|
81
81
|
const initialChecksumFilePath = this.#initialChecksumsFilePath;
|
|
82
82
|
const initialChecksumVerifyFilePath = this.#initialChecksumsVerificationFilePath;
|
|
83
|
-
console.log('Verifying theme checksums:', { initialChecksumFilePath, initialChecksumVerifyFilePath });
|
|
84
83
|
const initialChecksum = await computeFileChecksum(initialChecksumFilePath);
|
|
85
84
|
const initialChecksumVerify = await readJSONFile(initialChecksumVerifyFilePath);
|
|
86
|
-
console.log('initialChecksum current:', initialChecksum);
|
|
87
|
-
console.log('initialChecksumVerify:', initialChecksumVerify);
|
|
88
85
|
return initialChecksum === initialChecksumVerify;
|
|
89
86
|
}
|
|
90
87
|
async updateAllChecksums() {
|
|
@@ -147,7 +144,8 @@ export class ThemeChecksums {
|
|
|
147
144
|
join(SHOPER_THEME_METADATA_DIR, THEME_INITIAL_CHECKSUMS_FILE_NAME),
|
|
148
145
|
join(SHOPER_THEME_METADATA_DIR, THEME_INITIAL_CHECKSUMS_VERITY_FILE_NAME)
|
|
149
146
|
];
|
|
150
|
-
const
|
|
147
|
+
const allFiles = await ThemePushUtils.getAllFilesThatAreSendToRemote(this.#themeDir);
|
|
148
|
+
const filesToComputeChecksums = allFiles
|
|
151
149
|
.filter((path) => !filesToIgnoreInChecksums.some((ignorePath) => normalize(path) === ignorePath))
|
|
152
150
|
.map((relativePath) => join(this.#themeDir, relativePath));
|
|
153
151
|
this.#loggerApi?.debug('Computing checksums from file structure.', { details: { count: filesToComputeChecksums.length } });
|
|
@@ -3,6 +3,7 @@ import { basename, looksLikeDirectory, toUnixPath } from '../../../../utils/path
|
|
|
3
3
|
import { THEME_ACTION_DATA_TYPE } from './theme_actions_constants.js';
|
|
4
4
|
import globs from 'fast-glob';
|
|
5
5
|
import { THEME_PUSH_WILDCARD_GLOBS_FOR_FILES } from '../push/service/theme_push_service_constants.js';
|
|
6
|
+
import { filterFiles, loadShoperIgnore } from '../../../utils/shoperignore/shoperignore_utils.js';
|
|
6
7
|
export class ThemeActionsUtils {
|
|
7
8
|
static getFilesGlobsThatMatchesActionName({ filesStructure, actionValue, actionType }) {
|
|
8
9
|
return Object.entries(filesStructure).reduce((acc, [filePath, fileStructureItem]) => {
|
|
@@ -14,6 +15,8 @@ export class ThemeActionsUtils {
|
|
|
14
15
|
}
|
|
15
16
|
static async getFilesRecordsFromActionData({ themeRootDir, themeAction, filesStructure }) {
|
|
16
17
|
const filesRecords = [];
|
|
18
|
+
// Load the ignore instance once before the loops to avoid reading .shoperignore multiple times
|
|
19
|
+
const ignoreInstance = await loadShoperIgnore(themeRootDir);
|
|
17
20
|
for (const [actionKey, actionData] of Object.entries(themeAction.data)) {
|
|
18
21
|
if (actionData.type === THEME_ACTION_DATA_TYPE.file) {
|
|
19
22
|
const filesGlobs = ThemeActionsUtils.getFilesGlobsThatMatchesActionName({
|
|
@@ -29,13 +32,14 @@ export class ThemeActionsUtils {
|
|
|
29
32
|
onlyFiles: true,
|
|
30
33
|
cwd: themeRootDir
|
|
31
34
|
});
|
|
35
|
+
const filteredFiles = await filterFiles(files, themeRootDir, ignoreInstance);
|
|
32
36
|
let processedFileGlob = fileGlob;
|
|
33
37
|
if (looksLikeDirectory(fileGlob)) {
|
|
34
38
|
processedFileGlob = fileGlob.endsWith(THEME_PUSH_WILDCARD_GLOBS_FOR_FILES)
|
|
35
39
|
? fileGlob.slice(0, fileGlob.length - 2)
|
|
36
40
|
: fileGlob;
|
|
37
41
|
}
|
|
38
|
-
for (const filePath of
|
|
42
|
+
for (const filePath of filteredFiles) {
|
|
39
43
|
filesRecords.push({
|
|
40
44
|
actionData,
|
|
41
45
|
actionKey,
|
|
@@ -66,6 +66,8 @@ export class ThemeFetchService {
|
|
|
66
66
|
}
|
|
67
67
|
this.#loggerApi.debug('Creating .gitignore file.');
|
|
68
68
|
await ThemeMetaDataUtils.createGitIgnoreFile(themeDir);
|
|
69
|
+
this.#loggerApi.debug('Creating .shoperignore file.');
|
|
70
|
+
await ThemeMetaDataUtils.createShoperIgnoreFile(themeDir);
|
|
69
71
|
this.#loggerApi.debug('Updating metadata file.');
|
|
70
72
|
await ThemeMetaDataUtils.updateMetadataFileWithWorkUrl(themeDir, credentials.shopUrl);
|
|
71
73
|
this.#loggerApi.debug('Updating theme checksums');
|
|
@@ -52,6 +52,8 @@ export class ThemeInitService {
|
|
|
52
52
|
}
|
|
53
53
|
this.#loggerApi.debug('Creating .gitignore file.');
|
|
54
54
|
await ThemeMetaDataUtils.createGitIgnoreFile(themeDir);
|
|
55
|
+
this.#loggerApi.debug('Creating .shoperignore file.');
|
|
56
|
+
await ThemeMetaDataUtils.createShoperIgnoreFile(themeDir);
|
|
55
57
|
this.#loggerApi.debug('Updating metadata file.');
|
|
56
58
|
await ThemeMetaDataUtils.updateMetadataFileWithWorkUrl(themeDir, credentials.shopUrl);
|
|
57
59
|
this.#loggerApi.debug('Updating theme checksums.');
|
|
@@ -2,6 +2,7 @@ import globs from 'fast-glob';
|
|
|
2
2
|
import { AppError } from '../../../../cli/utilities/features/logger/logs/app_error.js';
|
|
3
3
|
import { toUnixPath } from '../../../../utils/path_utils.js';
|
|
4
4
|
import { ThemeFilesStructureUtils } from '../utils/files_structure/theme_files_structure_utils.js';
|
|
5
|
+
import { filterFiles } from '../../../utils/shoperignore/shoperignore_utils.js';
|
|
5
6
|
export class ThemePushUtils {
|
|
6
7
|
static async getAllFilesThatAreSendToRemote(themeDir) {
|
|
7
8
|
const filesStructure = await ThemeFilesStructureUtils.getThemeFilesStructure(themeDir);
|
|
@@ -16,10 +17,12 @@ export class ThemePushUtils {
|
|
|
16
17
|
.filter((path) => path !== '*');
|
|
17
18
|
//need unix styles globs
|
|
18
19
|
filesToArchive.push('styles/src/**/*');
|
|
19
|
-
|
|
20
|
-
suppressErrors: true,
|
|
20
|
+
const allFiles = await globs(filesToArchive, {
|
|
21
21
|
onlyFiles: true,
|
|
22
|
-
cwd: themeDir
|
|
23
|
-
|
|
22
|
+
cwd: themeDir,
|
|
23
|
+
suppressErrors: true
|
|
24
|
+
});
|
|
25
|
+
const filteredFiles = await filterFiles(allFiles, themeDir);
|
|
26
|
+
return filteredFiles.sort();
|
|
24
27
|
}
|
|
25
28
|
}
|
|
@@ -7,6 +7,7 @@ import { validateDirectory } from '../../../../utils/directory_validator/directo
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { THEME_FILES_LIST_FILE_NAME } from '../../push/theme_push_constants.js';
|
|
9
9
|
import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
|
|
10
|
+
import { loadShoperIgnore } from '../../../../utils/shoperignore/shoperignore_utils.js';
|
|
10
11
|
export class ThemeFilesStructureUtils {
|
|
11
12
|
static async getThemeFilesStructure(themeDirectory) {
|
|
12
13
|
const filesStructure = await readJSONFile(join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME));
|
|
@@ -28,10 +29,12 @@ export class ThemeFilesStructureUtils {
|
|
|
28
29
|
}, {});
|
|
29
30
|
}
|
|
30
31
|
static async validateThemeDirectoryStructure({ checksums, permissions, rootDirectory }) {
|
|
32
|
+
const ignoreInstance = await loadShoperIgnore(rootDirectory);
|
|
31
33
|
return await validateDirectory({
|
|
32
34
|
permissions,
|
|
33
35
|
checksums,
|
|
34
|
-
rootDirectory
|
|
36
|
+
rootDirectory,
|
|
37
|
+
ignoreInstance
|
|
35
38
|
});
|
|
36
39
|
}
|
|
37
40
|
static async getThemeRootDirectories(themeDirectory) {
|
|
@@ -106,7 +109,7 @@ export class ThemeFilesStructureUtils {
|
|
|
106
109
|
},
|
|
107
110
|
_links: { [THEME_ACTIONS_TYPES.push]: '*' }
|
|
108
111
|
};
|
|
109
|
-
['.gitignore'].forEach((fileName) => {
|
|
112
|
+
['.gitignore', '.shoperignore'].forEach((fileName) => {
|
|
110
113
|
fileStructure[fileName] = {
|
|
111
114
|
permissions: {
|
|
112
115
|
canAdd: true,
|
|
@@ -7,7 +7,6 @@ import { ThemeChecksums } from '../../../../class/checksums/theme_checksums.js';
|
|
|
7
7
|
export class HiddenDirectoryUtils {
|
|
8
8
|
static async ensureFilesInsideThemeMetaDataDirectoryUntouched(themeDirectory, logger) {
|
|
9
9
|
const themeMetadataPath = this.getThemeHiddenDirectoryPath(themeDirectory);
|
|
10
|
-
console.log('themeMetadataPath', themeMetadataPath);
|
|
11
10
|
const themeChecksums = new ThemeChecksums({
|
|
12
11
|
themeDir: themeDirectory,
|
|
13
12
|
loggerApi: logger
|
|
@@ -58,4 +58,32 @@ export class ThemeMetaDataUtils {
|
|
|
58
58
|
});
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
+
static async createShoperIgnoreFile(themeDir) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const shoperIgnoreContent = `# .shoperignore
|
|
64
|
+
# Pliki i katalogi wymienione tutaj będą wykluczane z:
|
|
65
|
+
# - pakowania do archiwum podczas publikacji
|
|
66
|
+
# - uploadowania na serwer
|
|
67
|
+
# - weryfikacji checksum
|
|
68
|
+
|
|
69
|
+
# Przykłady:
|
|
70
|
+
# node_modules/
|
|
71
|
+
# .env
|
|
72
|
+
# .env.*
|
|
73
|
+
# *.log
|
|
74
|
+
# temp/
|
|
75
|
+
# .vscode/
|
|
76
|
+
# .idea/
|
|
77
|
+
`;
|
|
78
|
+
const writeStream = createWriteStream(join(themeDir, '.shoperignore'));
|
|
79
|
+
writeStream.write(shoperIgnoreContent);
|
|
80
|
+
writeStream.end();
|
|
81
|
+
writeStream.on('error', (err) => {
|
|
82
|
+
reject(err);
|
|
83
|
+
});
|
|
84
|
+
writeStream.on('finish', () => {
|
|
85
|
+
resolve();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
61
89
|
}
|
|
@@ -5,7 +5,7 @@ import { computeFileChecksum } from '../../../utils/checksums/checksums_utils.js
|
|
|
5
5
|
import { DEFAULT_PERMISSION_FOR_ALL_FILES_KEY, DEFAULT_PERMISSION_FOR_DIRECTORY_KEY, FILES_MODIFICATION_TYPES, PERMISSION_KEY } from './directory_validator_constants.js';
|
|
6
6
|
import { CHECKSUM_KEY } from '../../../utils/checksums/checksums_utils_constants.js';
|
|
7
7
|
import _ from 'lodash';
|
|
8
|
-
export const validateDirectory = async ({ rootDirectory, permissions, checksums }) => {
|
|
8
|
+
export const validateDirectory = async ({ rootDirectory, permissions, checksums, ignoreInstance }) => {
|
|
9
9
|
const unpermittedActions = [];
|
|
10
10
|
await _checkPermissions({
|
|
11
11
|
permissions,
|
|
@@ -14,14 +14,15 @@ export const validateDirectory = async ({ rootDirectory, permissions, checksums
|
|
|
14
14
|
parts: [],
|
|
15
15
|
checksumsPart: checksums,
|
|
16
16
|
rootDirectory,
|
|
17
|
-
unpermittedActions
|
|
17
|
+
unpermittedActions,
|
|
18
|
+
ignoreInstance
|
|
18
19
|
});
|
|
19
20
|
return {
|
|
20
21
|
isValid: !unpermittedActions.length,
|
|
21
22
|
unpermittedActions
|
|
22
23
|
};
|
|
23
24
|
};
|
|
24
|
-
const _checkPermissions = async ({ permissionPart, permissionParts, parts = [], checksumsPart, rootDirectory, unpermittedActions, permissions }) => {
|
|
25
|
+
const _checkPermissions = async ({ permissionPart, permissionParts, parts = [], checksumsPart, rootDirectory, unpermittedActions, permissions, ignoreInstance }) => {
|
|
25
26
|
if (!permissionPart)
|
|
26
27
|
return;
|
|
27
28
|
//TODO required files in a custom module directory
|
|
@@ -40,6 +41,11 @@ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [],
|
|
|
40
41
|
filesInsideCurrentDirectoryObject[path] = true;
|
|
41
42
|
});
|
|
42
43
|
for (const file of files) {
|
|
44
|
+
const relativePath = join(parentPath, file);
|
|
45
|
+
// Skip files that are in .shoperignore
|
|
46
|
+
if (ignoreInstance && ignoreInstance.ignores(relativePath)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
43
49
|
const { permission, permissionKey } = _getPermission({
|
|
44
50
|
path: file,
|
|
45
51
|
fullPath: join(fullPath, file),
|
|
@@ -65,7 +71,8 @@ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [],
|
|
|
65
71
|
checksumsPart: checksumsPart?.[file] ?? {},
|
|
66
72
|
rootDirectory,
|
|
67
73
|
unpermittedActions,
|
|
68
|
-
permissions
|
|
74
|
+
permissions,
|
|
75
|
+
ignoreInstance
|
|
69
76
|
});
|
|
70
77
|
}
|
|
71
78
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from '../../../utils/path_utils.js';
|
|
3
|
+
import { fileExists } from '../../../utils/fs/fs_utils.js';
|
|
4
|
+
import ignore from 'ignore';
|
|
5
|
+
export const SHOPER_IGNORE_FILE_NAME = '.shoperignore';
|
|
6
|
+
export async function loadShoperIgnore(themeRootDir) {
|
|
7
|
+
const shoperIgnorePath = join(themeRootDir, SHOPER_IGNORE_FILE_NAME);
|
|
8
|
+
if (!(await fileExists(shoperIgnorePath))) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const content = await readFile(shoperIgnorePath, 'utf-8');
|
|
13
|
+
const ig = ignore();
|
|
14
|
+
ig.add('.shoperignore');
|
|
15
|
+
ig.add('.gitignore');
|
|
16
|
+
ig.add(content);
|
|
17
|
+
return ig;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function filterFiles(files, themeRootDir, ignoreInstance) {
|
|
24
|
+
const ig = ignoreInstance !== undefined ? ignoreInstance : await loadShoperIgnore(themeRootDir);
|
|
25
|
+
if (!ig) {
|
|
26
|
+
return files;
|
|
27
|
+
}
|
|
28
|
+
return files.filter((file) => !ig.ignores(file));
|
|
29
|
+
}
|
|
30
|
+
export async function isIgnored(filePath, themeRootDir, ignoreInstance) {
|
|
31
|
+
const ig = ignoreInstance !== undefined ? ignoreInstance : await loadShoperIgnore(themeRootDir);
|
|
32
|
+
if (!ig) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return ig.ignores(filePath);
|
|
36
|
+
}
|
|
@@ -12,16 +12,12 @@ export const computeFileChecksum = async (filePath, algorithm = 'md5') => {
|
|
|
12
12
|
const hash = createHash(algorithm);
|
|
13
13
|
const stream = createReadStream(filePath);
|
|
14
14
|
stream.on('data', (data) => {
|
|
15
|
-
console.log('data chunk received for hashing');
|
|
16
|
-
console.log('data', data);
|
|
17
15
|
hash.update(data);
|
|
18
16
|
});
|
|
19
17
|
stream.on('close', () => {
|
|
20
|
-
console.log('file read completed, finalizing checksum computation');
|
|
21
18
|
resolve(hash.digest('hex'));
|
|
22
19
|
});
|
|
23
20
|
stream.on('error', (err) => {
|
|
24
|
-
console.log('error while reading file for checksum computation', err);
|
|
25
21
|
reject(err);
|
|
26
22
|
});
|
|
27
23
|
});
|
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.
|
|
5
|
+
"version": "0.8.0",
|
|
6
6
|
"description": "CLI tool for Shoper",
|
|
7
7
|
"author": "Joanna Firek",
|
|
8
8
|
"license": "MIT",
|
|
@@ -48,9 +48,11 @@
|
|
|
48
48
|
"chalk": "5.4.1",
|
|
49
49
|
"conf": "13.1.0",
|
|
50
50
|
"fast-glob": "3.3.3",
|
|
51
|
+
"figlet": "1.9.4",
|
|
51
52
|
"figures": "6.1.0",
|
|
52
53
|
"fs-extra": "11.3.0",
|
|
53
54
|
"fs-tree-diff": "2.0.1",
|
|
55
|
+
"ignore": "7.0.5",
|
|
54
56
|
"ink": "6.0.1",
|
|
55
57
|
"ink-link": "4.1.0",
|
|
56
58
|
"ink-gradient": "3.0.0",
|
|
@@ -74,8 +76,7 @@
|
|
|
74
76
|
"uuid": "11.1.0",
|
|
75
77
|
"walk-sync": "3.0.0",
|
|
76
78
|
"yauzl": "3.2.0",
|
|
77
|
-
"yazl": "3.3.1"
|
|
78
|
-
"figlet": "1.9.4"
|
|
79
|
+
"yazl": "3.3.1"
|
|
79
80
|
},
|
|
80
81
|
"devDependencies": {
|
|
81
82
|
"@babel/core": "7.27.1",
|