@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 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 this._formatJsonFiles(this.#themeRootDir, filesInThemeDirectory);
29
- return createZip({
30
- files: filesInThemeDirectory,
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 filesToComputeChecksums = (await ThemePushUtils.getAllFilesThatAreSendToRemote(this.#themeDir))
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 files) {
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
- return await globs(filesToArchive, {
20
- suppressErrors: true,
20
+ const allFiles = await globs(filesToArchive, {
21
21
  onlyFiles: true,
22
- cwd: themeDir
23
- }).then((files) => files.sort());
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.6.4-3",
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",