@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.
- package/build/cli/auth/api/cli_auth_api.js +15 -2
- package/build/cli/auth/cli_auth_constants.js +6 -0
- package/build/cli/auth/cli_auth_errors_factory.js +10 -0
- package/build/cli/auth/cli_auth_initializer.js +19 -0
- package/build/cli/auth/cli_auth_utils.js +22 -0
- package/build/cli/auth/model/cli_credentials.js +10 -0
- package/build/cli/auth/service/cli_auth_service.js +34 -0
- package/build/cli/auth/tokens/api/cli_auth_tokens_api.js +43 -1
- package/build/cli/auth/tokens/cli_auth_tokens_constants.js +6 -0
- package/build/cli/auth/tokens/cli_auth_tokens_errors_factory.js +10 -0
- package/build/cli/auth/tokens/cli_auth_tokens_initalizer.js +35 -1
- package/build/cli/auth/tokens/cli_auth_tokens_utils.js +26 -0
- package/build/cli/auth/tokens/service/cli_auth_tokens_service.js +84 -4
- package/build/cli/auth/tokens/service/cli_auth_tokens_service_constants.js +2 -0
- package/build/cli/class/base_cli_command.js +3 -0
- package/build/cli/class/errors/app_error/app_error.js +17 -0
- package/build/cli/class/errors/app_error/app_error_constants.js +7 -0
- package/build/cli/class/errors/file_system_errors_factory.js +26 -0
- package/build/cli/class/errors/http_errors_factory.js +21 -0
- package/build/cli/cli_constants.js +1 -0
- package/build/cli/commands/auth/cli_auth_add_token_command.js +32 -0
- package/build/cli/commands/auth/cli_auth_list_tokens_command.js +17 -0
- package/build/cli/commands/auth/cli_auth_remove_token_command.js +37 -0
- package/build/cli/commands/auth/cli_auth_switch_token_command.js +36 -0
- package/build/cli/commands/cli_update_command.js +7 -3
- package/build/cli/commands/commands_constants.js +5 -1
- package/build/cli/commands/files_diff_command.js +174 -0
- package/build/cli/commands/utils/prompt_for_token_utils.js +25 -0
- package/build/cli/core/cli_setup.js +20 -7
- package/build/cli/features/caches/json_cache/json_cache.js +50 -0
- package/build/cli/features/caches/memory_cache.js +6 -0
- package/build/cli/features/data_directory/cli_data_directory_constants.js +0 -1
- package/build/cli/features/data_directory/cli_data_directory_utils.js +3 -4
- package/build/cli/features/data_directory/service/cli_data_directory_service.js +2 -2
- package/build/cli/features/execution_context/execution_context_service.js +3 -5
- package/build/cli/features/http_requester/http_client.js +32 -1
- package/build/cli/features/version/service/cli_version_service.js +2 -4
- package/build/cli/hooks/authorization/ensure_authorization_hook.js +11 -6
- package/build/cli/hooks/authorization/ensure_authorization_hook_constants.js +8 -1
- package/build/index.js +34 -1
- package/build/theme/class/fetch_resources/fetch_resources.js +127 -0
- package/build/theme/class/fetch_resources/fetch_resources_constants.js +2 -0
- package/build/theme/class/fetch_resources/fetch_resources_errors_factory.js +11 -0
- package/build/theme/class/fetch_resources/fetch_resources_utils.js +35 -0
- package/build/theme/commands/theme_commands_constants.js +8 -0
- package/build/theme/commands/theme_init_command.js +53 -0
- package/build/theme/commands/theme_list_command.js +16 -0
- package/build/theme/commands/theme_pull_command.js +126 -0
- package/build/theme/commands/theme_push_command.js +65 -0
- package/build/theme/commands/theme_show_changes_command.js +60 -0
- package/build/theme/commands/theme_verify_command.js +43 -0
- package/build/theme/features/theme/actions/api/theme_actions_api.js +19 -0
- package/build/theme/features/theme/actions/service/theme_actions_service.js +92 -0
- package/build/theme/features/theme/actions/service/theme_actions_service_constants.js +2 -0
- package/build/theme/features/theme/actions/theme_actions_constants.js +15 -0
- package/build/theme/features/theme/actions/theme_actions_initializer.js +27 -0
- package/build/theme/features/theme/actions/theme_actions_utils.js +8 -0
- package/build/theme/features/theme/directory/theme_directory_utils.js +61 -14
- package/build/theme/features/theme/fetch/api/theme_fetch_api.js +16 -0
- package/build/theme/features/theme/fetch/http/theme_fetch_http_api.js +18 -0
- package/build/theme/features/theme/fetch/service/theme_fetch_service.js +113 -0
- package/build/theme/features/theme/fetch/theme_fetch_constants.js +7 -0
- package/build/theme/features/theme/fetch/theme_fetch_initializer.js +23 -0
- package/build/theme/features/theme/init/api/theme_init_api.js +13 -0
- package/build/theme/features/theme/init/http/theme_init_http_api.js +18 -0
- package/build/theme/features/theme/init/service/theme_init_service.js +28 -0
- package/build/theme/features/theme/init/theme_init_constants.js +2 -0
- package/build/theme/features/theme/init/theme_init_initializer.js +22 -0
- package/build/theme/features/theme/merge/api/theme_merge_api.js +28 -0
- package/build/theme/features/theme/merge/service/theme_merge_service.js +56 -0
- package/build/theme/features/theme/merge/theme_merge_constants.js +9 -0
- package/build/theme/features/theme/merge/theme_merge_initializer.js +18 -0
- package/build/theme/features/theme/publish/theme_publish_constants.js +2 -0
- package/build/theme/features/theme/publish/theme_publish_utils.js +7 -0
- package/build/theme/features/theme/push/api/theme_push_api.js +13 -0
- package/build/theme/features/theme/push/http_api/theme_push_http_api.js +21 -0
- package/build/theme/features/theme/push/service/theme_push_service.js +200 -0
- package/build/theme/features/theme/push/service/theme_push_service_constants.js +1 -0
- package/build/theme/features/theme/push/theme_push_constants.js +4 -0
- package/build/theme/features/theme/push/theme_push_errors_factory.js +41 -0
- package/build/theme/features/theme/push/theme_push_initializer.js +27 -0
- package/build/theme/features/theme/theme_constants.js +6 -0
- package/build/theme/features/theme/utils/checksums/theme_checksums_error_factory.js +10 -0
- package/build/theme/features/theme/utils/checksums/theme_checksums_utils.js +94 -0
- package/build/theme/features/theme/utils/directories/theme_resources_with_id_directory_utils.js +80 -0
- package/build/theme/features/theme/utils/theme_images_utils.js +30 -0
- package/build/theme/features/themes/list/api/themes_list_api.js +13 -0
- package/build/theme/features/themes/{http/shoper_themes_http_api.js → list/http/themes_list_http_api.js} +3 -2
- package/build/theme/features/themes/{model/theme_metadata.js → list/model/theme_list_metadata.js} +3 -1
- package/build/theme/features/themes/list/services/themes_list_service.js +37 -0
- package/build/theme/features/themes/list/themes_list_constants.js +2 -0
- package/build/theme/features/themes/list/themes_list_initializer.js +20 -0
- package/build/theme/hooks/ensure_theme_meta_data_untouched.js +17 -0
- package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date.js +21 -0
- package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js +7 -0
- package/build/theme/hooks/themes_actions/ensure_themes_actions_hook.js +45 -0
- package/build/theme/hooks/themes_actions/ensure_themes_actions_hook_constants.js +7 -0
- package/build/theme/index.js +13 -4
- package/build/theme/utils/directory_validator/directory_validator_constants.js +13 -0
- package/build/theme/utils/directory_validator/directory_validator_utils.js +185 -0
- package/build/utils/checksums/checksums_utils.js +95 -0
- package/build/utils/checksums/checksums_utils_constants.js +1 -0
- package/build/utils/date_utils.js +3 -0
- package/build/utils/download_file/download_file_errors_factory.js +9 -0
- package/build/utils/download_file/download_file_utils.js +46 -0
- package/build/utils/fs/errors/stream_read_error.js +11 -0
- package/build/utils/fs/errors/stream_write_error.js +11 -0
- package/build/utils/fs/fs_utils.js +155 -0
- package/build/utils/http_utils.js +10 -0
- package/build/utils/path_utils.js +37 -0
- package/build/utils/stream_transforms/json_indent_transform.js +21 -0
- package/build/utils/url_utils.js +9 -0
- package/build/utils/zip/create_zip_utils.js +80 -0
- package/build/utils/zip/errors/create_zip_error.js +11 -0
- package/build/utils/zip/errors/open_zip_error.js +11 -0
- package/build/utils/zip/extract_zip_utils.js +104 -0
- package/oclif.config.js +4 -2
- package/package.json +36 -7
- package/build/cli/hooks/migration/migration_hook.js +0 -4
- package/build/theme/commands/list_command.js +0 -12
- package/build/theme/commands/pull_command.js +0 -24
- package/build/theme/features/theme/directory/theme_directories_utils.js +0 -10
- package/build/theme/features/theme/pull/api/shoper_theme_pull_api.js +0 -9
- package/build/theme/features/theme/pull/http/shoper_theme_pull_http_api.js +0 -14
- package/build/theme/features/theme/pull/service/shoper_theme_pull_service.js +0 -68
- package/build/theme/features/theme/pull/shoper_theme_pull_initializer.js +0 -22
- package/build/theme/features/themes/api/shoper_themes_api.js +0 -14
- package/build/theme/features/themes/services/shoper_themes_service.js +0 -15
- package/build/theme/features/themes/shoper_themes_constants.js +0 -2
- package/build/theme/features/themes/shoper_themes_initalizer.js +0 -20
- package/build/utils/fs.js +0 -44
- package/build/utils/path.js +0 -13
package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js
ADDED
|
@@ -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;
|
package/build/theme/index.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { ThemeListCommand } from './commands/
|
|
2
|
-
import { ThemePullCommand } from './commands/
|
|
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
|
-
|
|
5
|
-
|
|
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,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;
|