@shoper/cli 0.1.0-7 → 0.1.0-9

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.
Files changed (132) hide show
  1. package/build/cli/auth/api/cli_auth_api.js +15 -2
  2. package/build/cli/auth/cli_auth_constants.js +6 -0
  3. package/build/cli/auth/cli_auth_errors_factory.js +10 -0
  4. package/build/cli/auth/cli_auth_initializer.js +19 -0
  5. package/build/cli/auth/cli_auth_utils.js +22 -0
  6. package/build/cli/auth/model/cli_credentials.js +10 -0
  7. package/build/cli/auth/service/cli_auth_service.js +34 -0
  8. package/build/cli/auth/tokens/api/cli_auth_tokens_api.js +43 -1
  9. package/build/cli/auth/tokens/cli_auth_tokens_constants.js +6 -0
  10. package/build/cli/auth/tokens/cli_auth_tokens_errors_factory.js +10 -0
  11. package/build/cli/auth/tokens/cli_auth_tokens_initalizer.js +35 -1
  12. package/build/cli/auth/tokens/cli_auth_tokens_utils.js +26 -0
  13. package/build/cli/auth/tokens/service/cli_auth_tokens_service.js +84 -4
  14. package/build/cli/auth/tokens/service/cli_auth_tokens_service_constants.js +2 -0
  15. package/build/cli/class/base_cli_command.js +3 -0
  16. package/build/cli/class/errors/app_error/app_error.js +17 -0
  17. package/build/cli/class/errors/app_error/app_error_constants.js +7 -0
  18. package/build/cli/class/errors/file_system_errors_factory.js +26 -0
  19. package/build/cli/class/errors/http_errors_factory.js +21 -0
  20. package/build/cli/cli_constants.js +1 -0
  21. package/build/cli/commands/auth/cli_auth_add_token_command.js +32 -0
  22. package/build/cli/commands/auth/cli_auth_list_tokens_command.js +17 -0
  23. package/build/cli/commands/auth/cli_auth_remove_token_command.js +37 -0
  24. package/build/cli/commands/auth/cli_auth_switch_token_command.js +36 -0
  25. package/build/cli/commands/cli_update_command.js +7 -3
  26. package/build/cli/commands/commands_constants.js +5 -1
  27. package/build/cli/commands/files_diff_command.js +174 -0
  28. package/build/cli/commands/utils/prompt_for_token_utils.js +25 -0
  29. package/build/cli/core/cli_setup.js +20 -7
  30. package/build/cli/features/caches/json_cache/json_cache.js +50 -0
  31. package/build/cli/features/caches/memory_cache.js +6 -0
  32. package/build/cli/features/data_directory/cli_data_directory_constants.js +0 -1
  33. package/build/cli/features/data_directory/cli_data_directory_utils.js +3 -4
  34. package/build/cli/features/data_directory/service/cli_data_directory_service.js +2 -2
  35. package/build/cli/features/execution_context/execution_context_service.js +3 -5
  36. package/build/cli/features/http_requester/http_client.js +32 -1
  37. package/build/cli/features/version/service/cli_version_service.js +2 -4
  38. package/build/cli/hooks/authorization/ensure_authorization_hook.js +11 -6
  39. package/build/cli/hooks/authorization/ensure_authorization_hook_constants.js +8 -1
  40. package/build/index.js +34 -1
  41. package/build/theme/class/fetch_resources/fetch_resources.js +127 -0
  42. package/build/theme/class/fetch_resources/fetch_resources_constants.js +2 -0
  43. package/build/theme/class/fetch_resources/fetch_resources_errors_factory.js +11 -0
  44. package/build/theme/class/fetch_resources/fetch_resources_utils.js +35 -0
  45. package/build/theme/commands/theme_commands_constants.js +8 -0
  46. package/build/theme/commands/theme_init_command.js +53 -0
  47. package/build/theme/commands/theme_list_command.js +16 -0
  48. package/build/theme/commands/theme_pull_command.js +126 -0
  49. package/build/theme/commands/theme_push_command.js +65 -0
  50. package/build/theme/commands/theme_show_changes_command.js +60 -0
  51. package/build/theme/commands/theme_verify_command.js +43 -0
  52. package/build/theme/features/theme/actions/api/theme_actions_api.js +19 -0
  53. package/build/theme/features/theme/actions/service/theme_actions_service.js +92 -0
  54. package/build/theme/features/theme/actions/service/theme_actions_service_constants.js +2 -0
  55. package/build/theme/features/theme/actions/theme_actions_constants.js +15 -0
  56. package/build/theme/features/theme/actions/theme_actions_initializer.js +27 -0
  57. package/build/theme/features/theme/actions/theme_actions_utils.js +8 -0
  58. package/build/theme/features/theme/directory/theme_directory_utils.js +61 -14
  59. package/build/theme/features/theme/fetch/api/theme_fetch_api.js +16 -0
  60. package/build/theme/features/theme/fetch/http/theme_fetch_http_api.js +18 -0
  61. package/build/theme/features/theme/fetch/service/theme_fetch_service.js +113 -0
  62. package/build/theme/features/theme/fetch/theme_fetch_constants.js +7 -0
  63. package/build/theme/features/theme/fetch/theme_fetch_initializer.js +23 -0
  64. package/build/theme/features/theme/init/api/theme_init_api.js +13 -0
  65. package/build/theme/features/theme/init/http/theme_init_http_api.js +18 -0
  66. package/build/theme/features/theme/init/service/theme_init_service.js +28 -0
  67. package/build/theme/features/theme/init/theme_init_constants.js +2 -0
  68. package/build/theme/features/theme/init/theme_init_initializer.js +22 -0
  69. package/build/theme/features/theme/merge/api/theme_merge_api.js +28 -0
  70. package/build/theme/features/theme/merge/service/theme_merge_service.js +56 -0
  71. package/build/theme/features/theme/merge/theme_merge_constants.js +9 -0
  72. package/build/theme/features/theme/merge/theme_merge_initializer.js +18 -0
  73. package/build/theme/features/theme/publish/theme_publish_constants.js +2 -0
  74. package/build/theme/features/theme/publish/theme_publish_utils.js +7 -0
  75. package/build/theme/features/theme/push/api/theme_push_api.js +13 -0
  76. package/build/theme/features/theme/push/http_api/theme_push_http_api.js +21 -0
  77. package/build/theme/features/theme/push/service/theme_push_service.js +200 -0
  78. package/build/theme/features/theme/push/service/theme_push_service_constants.js +1 -0
  79. package/build/theme/features/theme/push/theme_push_constants.js +4 -0
  80. package/build/theme/features/theme/push/theme_push_errors_factory.js +41 -0
  81. package/build/theme/features/theme/push/theme_push_initializer.js +27 -0
  82. package/build/theme/features/theme/theme_constants.js +6 -0
  83. package/build/theme/features/theme/utils/checksums/theme_checksums_error_factory.js +10 -0
  84. package/build/theme/features/theme/utils/checksums/theme_checksums_utils.js +94 -0
  85. package/build/theme/features/theme/utils/directories/theme_resources_with_id_directory_utils.js +80 -0
  86. package/build/theme/features/theme/utils/theme_images_utils.js +30 -0
  87. package/build/theme/features/themes/list/api/themes_list_api.js +13 -0
  88. package/build/theme/features/themes/{http/shoper_themes_http_api.js → list/http/themes_list_http_api.js} +3 -2
  89. package/build/theme/features/themes/{model/theme_metadata.js → list/model/theme_list_metadata.js} +3 -1
  90. package/build/theme/features/themes/list/services/themes_list_service.js +37 -0
  91. package/build/theme/features/themes/list/themes_list_constants.js +2 -0
  92. package/build/theme/features/themes/list/themes_list_initializer.js +20 -0
  93. package/build/theme/hooks/ensure_theme_meta_data_untouched.js +17 -0
  94. package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date.js +21 -0
  95. package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js +7 -0
  96. package/build/theme/hooks/themes_actions/ensure_themes_actions_hook.js +45 -0
  97. package/build/theme/hooks/themes_actions/ensure_themes_actions_hook_constants.js +7 -0
  98. package/build/theme/index.js +13 -4
  99. package/build/theme/utils/directory_validator/directory_validator_constants.js +13 -0
  100. package/build/theme/utils/directory_validator/directory_validator_utils.js +185 -0
  101. package/build/utils/checksums/checksums_utils.js +95 -0
  102. package/build/utils/checksums/checksums_utils_constants.js +1 -0
  103. package/build/utils/date_utils.js +3 -0
  104. package/build/utils/download_file/download_file_errors_factory.js +9 -0
  105. package/build/utils/download_file/download_file_utils.js +46 -0
  106. package/build/utils/fs/errors/stream_read_error.js +11 -0
  107. package/build/utils/fs/errors/stream_write_error.js +11 -0
  108. package/build/utils/fs/fs_utils.js +155 -0
  109. package/build/utils/http_utils.js +10 -0
  110. package/build/utils/path_utils.js +37 -0
  111. package/build/utils/stream_transforms/json_indent_transform.js +21 -0
  112. package/build/utils/url_utils.js +9 -0
  113. package/build/utils/zip/create_zip_utils.js +80 -0
  114. package/build/utils/zip/errors/create_zip_error.js +11 -0
  115. package/build/utils/zip/errors/open_zip_error.js +11 -0
  116. package/build/utils/zip/extract_zip_utils.js +104 -0
  117. package/oclif.config.js +4 -2
  118. package/package.json +36 -7
  119. package/build/cli/hooks/migration/migration_hook.js +0 -4
  120. package/build/theme/commands/list_command.js +0 -12
  121. package/build/theme/commands/pull_command.js +0 -24
  122. package/build/theme/features/theme/directory/theme_directories_utils.js +0 -10
  123. package/build/theme/features/theme/pull/api/shoper_theme_pull_api.js +0 -9
  124. package/build/theme/features/theme/pull/http/shoper_theme_pull_http_api.js +0 -14
  125. package/build/theme/features/theme/pull/service/shoper_theme_pull_service.js +0 -68
  126. package/build/theme/features/theme/pull/shoper_theme_pull_initializer.js +0 -22
  127. package/build/theme/features/themes/api/shoper_themes_api.js +0 -14
  128. package/build/theme/features/themes/services/shoper_themes_service.js +0 -15
  129. package/build/theme/features/themes/shoper_themes_constants.js +0 -2
  130. package/build/theme/features/themes/shoper_themes_initalizer.js +0 -20
  131. package/build/utils/fs.js +0 -44
  132. package/build/utils/path.js +0 -13
@@ -0,0 +1,7 @@
1
+ import { THEME_COMMANDS_NAME } from '../../commands/theme_commands_constants.js';
2
+ export const THEME_COMMANDS_THAT_REQUIRES_UP_TO_DATE_CHECKSUMS = [
3
+ THEME_COMMANDS_NAME.push,
4
+ THEME_COMMANDS_NAME.showChanges,
5
+ THEME_COMMANDS_NAME.push,
6
+ THEME_COMMANDS_NAME.verify
7
+ ];
@@ -0,0 +1,45 @@
1
+ import { useApi } from '../../../cli/hooks/ensure_cli_initialized_hook.js';
2
+ import { THEME_ACTIONS_API_NAME } from '../../features/theme/actions/theme_actions_constants.js';
3
+ import { CLI_AUTH_API_NAME } from '../../../cli/auth/cli_auth_constants.js';
4
+ import { THEME_COMMANDS_THAT_REQUIRED_ACTIONS_LIST } from './ensure_themes_actions_hook_constants.js';
5
+ import { CLI_AUTH_TOKENS_API_NAME } from '../../../cli/auth/tokens/cli_auth_tokens_constants.js';
6
+ import { promptForToken } from '../../../cli/commands/utils/prompt_for_token_utils.js';
7
+ import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../../cli/features/execution_context/execution_context_constants.js';
8
+ const ensureThemesActionsHook = async ({ Command, argv }) => {
9
+ if (!THEME_COMMANDS_THAT_REQUIRED_ACTIONS_LIST.includes(Command.id))
10
+ return;
11
+ const themesActionsApi = useApi(THEME_ACTIONS_API_NAME);
12
+ const cliAuthApi = useApi(CLI_AUTH_API_NAME);
13
+ const executionContextApi = useApi(EXECUTION_CONTEXT_API_NAME);
14
+ const executionContext = await executionContextApi.getExecutionContext();
15
+ let themeId = argv[0];
16
+ if (!themeId && executionContext.type === EXECUTION_CONTEXTS.theme)
17
+ themeId = executionContext.themeId;
18
+ const credentials = cliAuthApi.getCredentials();
19
+ if (!credentials) {
20
+ console.error('Authorization is required to ensure themes actions.');
21
+ process.exit(1);
22
+ }
23
+ try {
24
+ await themesActionsApi.ensureThemesActions({
25
+ credentials,
26
+ themeId
27
+ });
28
+ }
29
+ catch (err) {
30
+ if (err.code === 'unauthorized') {
31
+ console.error('Unauthorized. Please authorize first.');
32
+ const cliAuthTokensApi = useApi(CLI_AUTH_TOKENS_API_NAME);
33
+ await promptForToken(cliAuthTokensApi);
34
+ await themesActionsApi.ensureThemesActions({
35
+ credentials,
36
+ themeId
37
+ });
38
+ }
39
+ else {
40
+ console.error(err);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ };
45
+ export default ensureThemesActionsHook;
@@ -0,0 +1,7 @@
1
+ import { THEME_COMMANDS_NAME } from '../../commands/theme_commands_constants.js';
2
+ export const THEME_COMMANDS_THAT_REQUIRED_ACTIONS_LIST = [
3
+ THEME_COMMANDS_NAME.pull,
4
+ THEME_COMMANDS_NAME.init,
5
+ THEME_COMMANDS_NAME.push,
6
+ THEME_COMMANDS_NAME.showChanges
7
+ ];
@@ -1,6 +1,15 @@
1
- import { ThemeListCommand } from './commands/list_command.js';
2
- import { ThemePullCommand } from './commands/pull_command.js';
1
+ import { ThemeListCommand } from './commands/theme_list_command.js';
2
+ import { ThemePullCommand } from './commands/theme_pull_command.js';
3
+ import { ThemeInitCommand } from './commands/theme_init_command.js';
4
+ import { THEME_COMMANDS_NAME } from './commands/theme_commands_constants.js';
5
+ import { ThemePushCommand } from './commands/theme_push_command.js';
6
+ import { ThemeShowChangesCommand } from './commands/theme_show_changes_command.js';
7
+ import { ThemeVerifyCommand } from './commands/theme_verify_command.js';
3
8
  export const COMMANDS = {
4
- 'theme:list': ThemeListCommand,
5
- 'theme:pull': ThemePullCommand
9
+ [THEME_COMMANDS_NAME.list]: ThemeListCommand,
10
+ [THEME_COMMANDS_NAME.pull]: ThemePullCommand,
11
+ [THEME_COMMANDS_NAME.init]: ThemeInitCommand,
12
+ [THEME_COMMANDS_NAME.push]: ThemePushCommand,
13
+ [THEME_COMMANDS_NAME.showChanges]: ThemeShowChangesCommand,
14
+ [THEME_COMMANDS_NAME.verify]: ThemeVerifyCommand
6
15
  };
@@ -0,0 +1,13 @@
1
+ export const FILES_MODIFICATION_TYPES = {
2
+ add: 'add',
3
+ remove: 'remove',
4
+ edit: 'edit'
5
+ };
6
+ export const DEFAULT_PERMISSION_FOR_ALL_FILES_KEY = '*';
7
+ export const DEFAULT_PERMISSION_FOR_DIRECTORY_KEY = '*/';
8
+ export const PERMISSION_KEY = '__permission__';
9
+ export const DEFAULT_DIRECTORY_STRUCTURE_PERMISSION = {
10
+ canAdd: false,
11
+ canEdit: false,
12
+ canDelete: false
13
+ };
@@ -0,0 +1,185 @@
1
+ import { join, looksLikeDirectory, platformSeparator } from '../../../utils/path_utils.js';
2
+ import { fileExists, getAllFilesAndDirectoriesInside, isDirectory } from '../../../utils/fs/fs_utils.js';
3
+ import uniq from 'lodash/uniq.js';
4
+ import { computeFileChecksum } from '../../../utils/checksums/checksums_utils.js';
5
+ import { DEFAULT_DIRECTORY_STRUCTURE_PERMISSION, DEFAULT_PERMISSION_FOR_ALL_FILES_KEY, DEFAULT_PERMISSION_FOR_DIRECTORY_KEY, FILES_MODIFICATION_TYPES, PERMISSION_KEY } from './directory_validator_constants.js';
6
+ import { CHECKSUM_KEY } from '../../../utils/checksums/checksums_utils_constants.js';
7
+ import _ from 'lodash';
8
+ export const validateDirectory = async ({ rootDirectory, permissions, checksums }) => {
9
+ const unpermittedActions = [];
10
+ await _checkPermissions({
11
+ permissions,
12
+ permissionPart: permissions,
13
+ permissionParts: [],
14
+ parts: [],
15
+ checksumsPart: checksums,
16
+ rootDirectory,
17
+ unpermittedActions
18
+ });
19
+ return {
20
+ isValid: !unpermittedActions.length,
21
+ unpermittedActions
22
+ };
23
+ };
24
+ const _checkPermissions = async ({ permissionPart, permissionParts, parts = [], checksumsPart, rootDirectory, unpermittedActions, permissions }) => {
25
+ if (!permissionPart)
26
+ return;
27
+ //TODO required files in a custom module directory
28
+ const fullPath = join(rootDirectory, ...parts);
29
+ const filesInsideCurrentDirectory = (await getAllFilesAndDirectoriesInside(fullPath, {
30
+ recursive: false
31
+ })).map((file) => (file.isDirectory() ? join(file.name, platformSeparator) : file.name));
32
+ const files = uniq([
33
+ ...filesInsideCurrentDirectory,
34
+ //TODO fix another way
35
+ ...Object.keys(checksumsPart ?? {}).filter((key) => key !== CHECKSUM_KEY && key !== './')
36
+ ]);
37
+ const parentPath = join(...parts);
38
+ const filesInsideCurrentDirectoryObject = {};
39
+ filesInsideCurrentDirectory.forEach((path) => {
40
+ filesInsideCurrentDirectoryObject[path] = true;
41
+ });
42
+ for (const file of files) {
43
+ const { permission, permissionKey } = _getPermission({
44
+ path: file,
45
+ fullPath: join(fullPath, file),
46
+ permissionPart,
47
+ permissionParts,
48
+ permissions
49
+ });
50
+ if (!permissionKey) {
51
+ console.log('permission', permission);
52
+ console.log('parts', parts);
53
+ console.log('permissionPart', permissionPart);
54
+ console.log('permissionParts', permissionParts);
55
+ console.log('permissions', permissions);
56
+ console.log('file', file);
57
+ console.log('fullPath', fullPath);
58
+ // console.log('permission', permission, 'permissionKey', permissionKey);
59
+ }
60
+ const action = await _checkPermission({
61
+ permission: permission[PERMISSION_KEY],
62
+ path: file,
63
+ checksumsPart: checksumsPart ?? {},
64
+ filesInDirectory: filesInsideCurrentDirectoryObject,
65
+ parentPath,
66
+ rootDirectory
67
+ });
68
+ if (action)
69
+ unpermittedActions.push(action);
70
+ if ((await fileExists(join(rootDirectory, join(...parts, file)))) && (await isDirectory(join(fullPath, file)))) {
71
+ await _checkPermissions({
72
+ permissionPart: permission,
73
+ parts: [...parts, file],
74
+ permissionParts: permissionKey ? [...permissionParts, permissionKey] : permissionParts,
75
+ checksumsPart: checksumsPart?.[file] ?? {},
76
+ rootDirectory,
77
+ unpermittedActions,
78
+ permissions
79
+ });
80
+ }
81
+ }
82
+ return;
83
+ };
84
+ const _getPermission = ({ permissions, permissionParts, permissionPart, path, fullPath }) => {
85
+ const specificPermission = permissionPart[path];
86
+ const defaultDirectoryPermission = permissionPart[DEFAULT_PERMISSION_FOR_DIRECTORY_KEY];
87
+ const defaultFilePermission = permissionPart[DEFAULT_PERMISSION_FOR_ALL_FILES_KEY];
88
+ if (specificPermission)
89
+ return {
90
+ permission: specificPermission,
91
+ permissionKey: path
92
+ };
93
+ if (looksLikeDirectory(fullPath) && (defaultDirectoryPermission || defaultFilePermission))
94
+ return {
95
+ permission: defaultDirectoryPermission ?? defaultFilePermission,
96
+ permissionKey: defaultDirectoryPermission ? DEFAULT_PERMISSION_FOR_DIRECTORY_KEY : DEFAULT_PERMISSION_FOR_ALL_FILES_KEY
97
+ };
98
+ if (defaultFilePermission) {
99
+ return {
100
+ permission: defaultFilePermission,
101
+ permissionKey: DEFAULT_PERMISSION_FOR_ALL_FILES_KEY
102
+ };
103
+ }
104
+ return _getClosestPermission(permissions, permissionParts);
105
+ };
106
+ const _checkPermission = async ({ permission, path, checksumsPart, filesInDirectory, rootDirectory, parentPath }) => {
107
+ const relativePath = join(parentPath, path);
108
+ const fullPath = join(rootDirectory, relativePath);
109
+ if (!permission.canDelete && !filesInDirectory[path] && checksumsPart[path]) {
110
+ return {
111
+ type: FILES_MODIFICATION_TYPES.remove,
112
+ path,
113
+ relativePath
114
+ };
115
+ }
116
+ if (!permission.canAdd && filesInDirectory[path] && !checksumsPart[path]) {
117
+ return {
118
+ type: FILES_MODIFICATION_TYPES.add,
119
+ path,
120
+ relativePath
121
+ };
122
+ }
123
+ if ((await fileExists(fullPath)) && !looksLikeDirectory(join(rootDirectory, path)) && !permission.canEdit && checksumsPart[path]) {
124
+ const currentChecksum = await computeFileChecksum(fullPath);
125
+ if (currentChecksum !== checksumsPart[path][CHECKSUM_KEY]) {
126
+ return {
127
+ type: FILES_MODIFICATION_TYPES.edit,
128
+ path,
129
+ relativePath
130
+ };
131
+ }
132
+ }
133
+ };
134
+ const _getClosestPermission = (permissions, parts) => {
135
+ if (!parts.length) {
136
+ return {
137
+ permission: {
138
+ [DEFAULT_PERMISSION_FOR_DIRECTORY_KEY]: {
139
+ [PERMISSION_KEY]: DEFAULT_DIRECTORY_STRUCTURE_PERMISSION
140
+ },
141
+ [DEFAULT_PERMISSION_FOR_ALL_FILES_KEY]: {
142
+ [PERMISSION_KEY]: DEFAULT_DIRECTORY_STRUCTURE_PERMISSION
143
+ }
144
+ }
145
+ };
146
+ }
147
+ const permissionPart = _.at(permissions, parts.join('.'))?.[0];
148
+ if (!permissionPart)
149
+ return _getClosestPermission(permissions, parts.slice(0, -1));
150
+ if (permissionPart[DEFAULT_PERMISSION_FOR_DIRECTORY_KEY])
151
+ return {
152
+ permission: permissionPart[DEFAULT_PERMISSION_FOR_DIRECTORY_KEY],
153
+ permissionKey: DEFAULT_PERMISSION_FOR_DIRECTORY_KEY
154
+ };
155
+ if (permissionPart[DEFAULT_PERMISSION_FOR_ALL_FILES_KEY])
156
+ return {
157
+ permission: permissionPart[DEFAULT_PERMISSION_FOR_ALL_FILES_KEY],
158
+ permissionKey: DEFAULT_PERMISSION_FOR_ALL_FILES_KEY
159
+ };
160
+ return _getClosestPermission(permissions, parts.slice(0, -1));
161
+ };
162
+ export const mapToPermissionsTree = (paths) => {
163
+ const tree = {};
164
+ Object.entries(paths).forEach(([path, permissions]) => {
165
+ //TODO Cross platform
166
+ const parts = path.split('/').filter(Boolean);
167
+ let currentLevel = tree;
168
+ parts.forEach((part, index) => {
169
+ if (index === parts.length - 1) {
170
+ // TODO cross platform
171
+ const finalPart = path.endsWith('/') ? `${part}/` : part;
172
+ if (!currentLevel[finalPart])
173
+ currentLevel[finalPart] = {};
174
+ currentLevel[finalPart][PERMISSION_KEY] = { ...permissions };
175
+ }
176
+ if (index !== parts.length - 1) {
177
+ const finalPart = `${part}/`;
178
+ if (!currentLevel[finalPart])
179
+ currentLevel[finalPart] = {};
180
+ currentLevel = currentLevel[finalPart];
181
+ }
182
+ });
183
+ });
184
+ return tree;
185
+ };
@@ -0,0 +1,95 @@
1
+ import crypto, { createHash } from 'node:crypto';
2
+ import { createReadStream } from 'node:fs';
3
+ import { dirname, join, platformSeparator, relative } from '../path_utils.js';
4
+ import klaw from 'klaw';
5
+ import { CHECKSUM_KEY } from './checksums_utils_constants.js';
6
+ export const computeChecksum = (data, algorithm = 'md5') => {
7
+ const hash = crypto.createHash(algorithm);
8
+ hash.update(data);
9
+ return hash.digest('hex');
10
+ };
11
+ export const computeFileChecksum = async (filePath, algorithm = 'md5') => {
12
+ return new Promise((resolve, reject) => {
13
+ const hash = createHash(algorithm);
14
+ const stream = createReadStream(filePath);
15
+ stream.on('data', (data) => {
16
+ hash.update(data);
17
+ });
18
+ stream.on('close', () => {
19
+ resolve(hash.digest('hex'));
20
+ });
21
+ stream.on('error', (err) => {
22
+ reject(err);
23
+ });
24
+ });
25
+ };
26
+ export const computeChecksumsFromSource = (source) => {
27
+ return new Promise((resolve, reject) => {
28
+ const files = [];
29
+ klaw(source)
30
+ .on('data', (item) => {
31
+ if (!item.stats.isDirectory())
32
+ files.push(item.path);
33
+ })
34
+ .on('end', () => {
35
+ computeChecksumsFromFilesStructure(files, source).then(({ filesChecksumsInDirectories, filesChecksums }) => {
36
+ resolve({ ...filesChecksums, ...computeDirectoriesChecksums(filesChecksumsInDirectories) });
37
+ });
38
+ });
39
+ });
40
+ };
41
+ export const computeChecksumsFromFilesStructure = async (files, rootDir) => {
42
+ const filesChecksums = {};
43
+ const filesChecksumsInDirectories = {};
44
+ for (const filePath of files) {
45
+ const checksum = await computeFileChecksum(filePath);
46
+ const relativePath = relative(rootDir, filePath);
47
+ filesChecksums[relativePath] = checksum;
48
+ const zipDir = join(dirname(relativePath), platformSeparator);
49
+ const zipParts = zipDir.split(platformSeparator);
50
+ zipParts.pop();
51
+ const currentParts = [];
52
+ zipParts.forEach((part) => {
53
+ currentParts.push(part);
54
+ const partsDir = join(...currentParts, platformSeparator);
55
+ if (!filesChecksumsInDirectories[partsDir])
56
+ filesChecksumsInDirectories[partsDir] = [];
57
+ filesChecksumsInDirectories[partsDir].push(checksum);
58
+ });
59
+ }
60
+ return {
61
+ filesChecksums,
62
+ filesChecksumsInDirectories
63
+ };
64
+ };
65
+ export const computeDirectoriesChecksums = (filesChecksumsInDirectories) => {
66
+ const checksums = {};
67
+ Object.entries(filesChecksumsInDirectories).forEach(([directoryPath, filesChecksums]) => {
68
+ checksums[directoryPath] = computeChecksum(filesChecksums.sort().join(''));
69
+ });
70
+ return checksums;
71
+ };
72
+ export const mapChecksumToTree = (checksums) => {
73
+ const tree = {};
74
+ Object.entries(checksums).forEach(([path, checksum]) => {
75
+ const parts = path.split(platformSeparator).filter(Boolean);
76
+ let currentLevel = tree;
77
+ parts.filter(Boolean).forEach((part, index) => {
78
+ if (index === parts.length - 1) {
79
+ // TODO cross platform
80
+ const finalPart = path.endsWith('/') ? `${part}/` : part;
81
+ if (!currentLevel[finalPart])
82
+ currentLevel[finalPart] = {};
83
+ currentLevel[finalPart][CHECKSUM_KEY] = checksum;
84
+ }
85
+ if (index !== parts.length - 1) {
86
+ // TODO cross platform
87
+ const finalPart = `${part}/`;
88
+ if (!currentLevel[finalPart])
89
+ currentLevel[finalPart] = {};
90
+ currentLevel = currentLevel[finalPart];
91
+ }
92
+ });
93
+ });
94
+ return tree;
95
+ };
@@ -0,0 +1 @@
1
+ export const CHECKSUM_KEY = '__checksum__';
@@ -0,0 +1,3 @@
1
+ export const getNMinutesFromNow = (n) => {
2
+ return Date.now() + n * 60 * 1000;
3
+ };
@@ -0,0 +1,9 @@
1
+ import { AppError } from '../../cli/class/errors/app_error/app_error.js';
2
+ export class DownloadFileErrorsFactory {
3
+ static downloadError(status) {
4
+ return new AppError({
5
+ message: 'Download error',
6
+ code: status ?? 'download_error'
7
+ });
8
+ }
9
+ }
@@ -0,0 +1,46 @@
1
+ import { getFileNameFromHeaders } from '../http_utils.js';
2
+ import { createWriteStream } from 'fs';
3
+ import { URL } from 'url';
4
+ import { basename, extname, join, parse } from '../path_utils.js';
5
+ import { FileSystemErrorsFactory } from '../../cli/class/errors/file_system_errors_factory.js';
6
+ import { HttpErrorsFactory } from '../../cli/class/errors/http_errors_factory.js';
7
+ import { DownloadFileErrorsFactory } from './download_file_errors_factory.js';
8
+ export const downloadFile = async ({ dist, request }) => {
9
+ try {
10
+ const resp = (await request);
11
+ const headers = resp.headers;
12
+ const url = new URL(resp.config.url);
13
+ const filename = getFileNameFromHeaders(headers) ?? basename(url.pathname);
14
+ const file = createWriteStream(join(dist, filename));
15
+ resp.data.pipe(file);
16
+ return new Promise((resolve, reject) => {
17
+ file.on('finish', () => {
18
+ resolve({
19
+ filename,
20
+ ext: extname(filename),
21
+ basename: parse(filename).name
22
+ });
23
+ });
24
+ file.on('error', (err) => {
25
+ if (err.code === 'ENOSPC')
26
+ reject(FileSystemErrorsFactory.createDiskFullError());
27
+ reject(DownloadFileErrorsFactory.downloadError(err.response.status));
28
+ });
29
+ });
30
+ }
31
+ catch (err) {
32
+ switch (err.response?.status) {
33
+ case 403:
34
+ throw HttpErrorsFactory.createForbiddenError();
35
+ case 401:
36
+ throw HttpErrorsFactory.createUnauthorizedError();
37
+ case 404:
38
+ throw HttpErrorsFactory.createNotFoundError();
39
+ default:
40
+ if (err.response?.status !== 200) {
41
+ throw DownloadFileErrorsFactory.downloadError(err.response.status);
42
+ }
43
+ }
44
+ }
45
+ throw DownloadFileErrorsFactory.downloadError('Unknown error');
46
+ };
@@ -0,0 +1,11 @@
1
+ import { AppError } from '../../../cli/class/errors/app_error/app_error.js';
2
+ export class StreamReadError extends AppError {
3
+ constructor({ filePath, details, stack }) {
4
+ super({
5
+ code: 'STREAM_READ_ERROR',
6
+ message: `Error while reading stream for file: ${filePath}`,
7
+ details,
8
+ stack
9
+ });
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ import { AppError } from '../../../cli/class/errors/app_error/app_error.js';
2
+ export class StreamWriteError extends AppError {
3
+ constructor({ filePath, details, stack }) {
4
+ super({
5
+ code: 'STREAM_WRITE_ERROR',
6
+ message: `Error while writing to stream for file: ${filePath}`,
7
+ details,
8
+ stack
9
+ });
10
+ }
11
+ }
@@ -0,0 +1,155 @@
1
+ import fsPromises from 'node:fs/promises';
2
+ import fs, { createReadStream } from 'node:fs';
3
+ import { isHiddenFile } from 'is-hidden-file';
4
+ import process from 'node:process';
5
+ import { basename, join, resolve } from '../path_utils.js';
6
+ import { parse as parsePath } from 'node:path';
7
+ import { FileSystemErrorsFactory } from '../../cli/class/errors/file_system_errors_factory.js';
8
+ import { JSON_FILE_INDENT } from '../../cli/cli_constants.js';
9
+ import tmp from 'tmp-promise';
10
+ import { pipeline } from 'node:stream/promises';
11
+ import { jsonIndentTransform } from '../stream_transforms/json_indent_transform.js';
12
+ import { move } from 'fs-extra';
13
+ export const fileExists = async (path) => {
14
+ try {
15
+ await fsPromises.access(path);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ };
22
+ export const directoryExists = async (path) => {
23
+ return fileExists(path);
24
+ };
25
+ export const readFile = async (path, options) => {
26
+ return fsPromises.readFile(path, options);
27
+ };
28
+ export const writeFile = async (path, data, options) => {
29
+ return fsPromises.writeFile(path, data, options);
30
+ };
31
+ export const readJSONFile = async (path, options = { encoding: 'utf-8', flag: 'r' }) => {
32
+ const fileContent = await readFile(path, options);
33
+ if (typeof fileContent !== 'string')
34
+ throw new Error('File content is not a string');
35
+ try {
36
+ return JSON.parse(fileContent);
37
+ }
38
+ catch (error) {
39
+ throw new Error(`Failed to parse JSON from file ${path}: ${error}`);
40
+ }
41
+ };
42
+ export const writeJSONFile = async (path, data) => {
43
+ try {
44
+ await writeFile(path, JSON.stringify(data, null, JSON_FILE_INDENT), {
45
+ encoding: 'utf-8',
46
+ flag: 'w'
47
+ });
48
+ }
49
+ catch (error) {
50
+ throw new Error(`Failed to write JSON to file ${path}: ${error}`);
51
+ }
52
+ };
53
+ export const formatJSONFile = async (path, indent = JSON_FILE_INDENT) => {
54
+ const { path: tmpDir, cleanup } = await tmp.dir({ unsafeCleanup: true });
55
+ const fileName = basename(path);
56
+ const formattedTmpFilePath = join(tmpDir, fileName);
57
+ const readStream = createReadStream(path);
58
+ const writeStream = fs.createWriteStream(formattedTmpFilePath);
59
+ await pipeline(readStream, jsonIndentTransform(indent), writeStream);
60
+ // await copyFile(formattedTmpFilePath, path, { force: true });
61
+ await move(formattedTmpFilePath, path, { overwrite: true });
62
+ await cleanup();
63
+ };
64
+ export const openFile = async (path, flags, mode) => {
65
+ return fsPromises.open(path, flags, mode);
66
+ };
67
+ export const makeDirectory = async (path, options = {}) => {
68
+ return await fsPromises.mkdir(path, { recursive: false, ...options });
69
+ };
70
+ export const removeDirectory = async (path, options = {}) => {
71
+ return removeFile(path, options);
72
+ };
73
+ export const removeDirectorySync = (path, options = {}) => {
74
+ return removeFileSync(path, { recursive: true, force: true, ...options });
75
+ };
76
+ export const removeFileSync = (path, options = {}) => {
77
+ return fs.rmSync(path, { recursive: true, force: true, ...options });
78
+ };
79
+ export const removeFiles = async (files, options = {}) => {
80
+ return Promise.all(files.map((file) => removeFile(file, options)));
81
+ };
82
+ export const removeFile = async (path, options = {}) => {
83
+ return fsPromises.rm(path, { recursive: true, force: true, ...options });
84
+ };
85
+ export const getAllFilesAndDirectoriesInside = async (path, options) => {
86
+ const { recursive = true, withFileTypes = true } = options ?? {};
87
+ return await fsPromises.readdir(path, {
88
+ recursive,
89
+ withFileTypes
90
+ });
91
+ };
92
+ export const getAllDirectoriesNamesInside = async (path, options) => {
93
+ const { recursive = true, hidden = true } = options;
94
+ const files = await fsPromises.readdir(path, {
95
+ recursive,
96
+ withFileTypes: true
97
+ });
98
+ return files.filter((file) => file.isDirectory() && (hidden ? true : !isHiddenFile(file.name))).map((file) => file.name);
99
+ };
100
+ export const getAllFilesNamesInside = async (path, options = {}) => {
101
+ const { recursive = true, hidden = true } = options ?? {};
102
+ const files = await fsPromises.readdir(path, {
103
+ recursive,
104
+ withFileTypes: true
105
+ });
106
+ return files.filter((file) => !file.isDirectory() && (hidden ? true : !isHiddenFile(file.name))).map((file) => file.name);
107
+ };
108
+ export const closestFile = async (fileToFind, path = process.cwd()) => {
109
+ let currPath = path;
110
+ const rootDir = getOSRootDirectory();
111
+ while (currPath !== rootDir) {
112
+ const exists = await fileExists(resolve(currPath, fileToFind));
113
+ if (exists)
114
+ return currPath;
115
+ currPath = resolve(currPath, '..');
116
+ }
117
+ };
118
+ export const closestDirectory = async (path) => {
119
+ return closestFile(path);
120
+ };
121
+ export const getOSRootDirectory = () => parsePath(process.cwd()).root;
122
+ export const isDirectory = async (path) => {
123
+ const stats = await fsPromises.stat(path);
124
+ return stats.isDirectory();
125
+ };
126
+ export const copyFile = async (source, destination, options = {
127
+ force: true,
128
+ recursive: true
129
+ }) => {
130
+ try {
131
+ await fsPromises.cp(source, destination, options);
132
+ }
133
+ catch (err) {
134
+ throw FileSystemErrorsFactory.createErrorCopyingFile(source, destination, err);
135
+ }
136
+ };
137
+ export const copyFileSync = (source, destination, options = {
138
+ force: true,
139
+ recursive: true
140
+ }) => {
141
+ try {
142
+ fs.cpSync(source, destination, options);
143
+ }
144
+ catch (err) {
145
+ throw FileSystemErrorsFactory.createErrorCopyingFile(source, destination, err);
146
+ }
147
+ };
148
+ export const renameFile = async (source, destination) => {
149
+ try {
150
+ await fsPromises.rename(source, destination);
151
+ }
152
+ catch (err) {
153
+ throw FileSystemErrorsFactory.createErrorMovingFile(source, destination, err);
154
+ }
155
+ };
@@ -0,0 +1,10 @@
1
+ export const getFileNameFromHeaders = (headers) => {
2
+ const contentDisposition = headers['content-disposition'];
3
+ if (!contentDisposition)
4
+ return;
5
+ const matches = contentDisposition.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
6
+ if (!matches || !matches[1])
7
+ return;
8
+ const filename = matches[1].replace(/['"]/g, '');
9
+ return decodeURIComponent(filename);
10
+ };
@@ -0,0 +1,37 @@
1
+ import { basename as pathBaseName, dirname as pathDirname, extname as pathExtname, join as pathJoin, normalize, parse as parsePath, relative as pathRelative, resolve as resolvePath, sep } from 'node:path';
2
+ import fs from 'node:fs';
3
+ export const resolve = (...paths) => {
4
+ return resolvePath(...paths);
5
+ };
6
+ export const parse = (path) => {
7
+ return parsePath(path);
8
+ };
9
+ export const join = (...paths) => {
10
+ return pathJoin(...paths);
11
+ };
12
+ export const relative = (from, to) => {
13
+ return pathRelative(from, to);
14
+ };
15
+ export const basename = (path, ext) => pathBaseName(path, ext);
16
+ export const dirname = (path) => pathDirname(path);
17
+ export const extname = (path) => pathExtname(path);
18
+ export const hasParentDirectory = (path) => {
19
+ return dirname(path) !== '.';
20
+ };
21
+ export const looksLikeDirectory = (path) => {
22
+ const normalized = normalize(path);
23
+ if (normalized.endsWith(sep))
24
+ return true;
25
+ const base = basename(normalized);
26
+ const isHidden = base.startsWith('.') && !base.slice(1).includes('.');
27
+ const hasExtension = base.slice(1).includes('.');
28
+ if (isHidden) {
29
+ const stat = fs.lstatSync(path);
30
+ return stat.isDirectory();
31
+ }
32
+ return isHidden || !hasExtension;
33
+ };
34
+ export const toUnixPath = (filePath) => {
35
+ return normalize(filePath).replace(/\\/g, '/');
36
+ };
37
+ export const platformSeparator = sep;