@shoper/cli 0.2.0 → 0.2.1-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli/class/base_command.js +14 -0
- package/build/cli/commands/auth/cli_auth_remove_token_command.js +1 -3
- package/build/cli/commands/auth/cli_auth_switch_token_command.js +1 -2
- package/build/cli/commands/commands_constants.js +5 -4
- package/build/cli/core/cli_setup.js +33 -18
- package/build/cli/hooks/authorization/ensure_authorization_hook_constants.js +0 -1
- package/build/theme/class/archive/theme_archive.js +44 -0
- package/build/theme/class/archive/theme_archive_errors_factory.js +11 -0
- package/build/theme/class/checksums/theme_checksums.js +3 -7
- package/build/theme/class/files_upload/theme_files_upload.js +61 -0
- package/build/theme/class/files_upload/theme_files_upload_http_api.js +23 -0
- package/build/theme/commands/delete/theme_delete_command.js +4 -19
- package/build/theme/commands/info/theme_info_command.js +3 -18
- package/build/theme/commands/init/theme_init_command.js +4 -17
- package/build/theme/commands/publish/theme_publish_command.js +4 -20
- package/build/theme/commands/pull/theme_pull_command.js +6 -26
- package/build/theme/commands/push/theme_push_command.js +17 -39
- package/build/theme/commands/theme_commands_constants.js +9 -9
- package/build/theme/commands/theme_verify_command.js +57 -18
- package/build/theme/commands/ui/theme_error.js +29 -0
- package/build/theme/features/theme/actions/theme_actions_constants.js +2 -1
- package/build/theme/features/theme/actions/theme_actions_utils.js +41 -1
- package/build/theme/features/theme/init/theme_init_initializer.js +3 -3
- package/build/theme/features/theme/push/service/theme_push_service.js +42 -164
- package/build/theme/features/theme/push/theme_push_initializer.js +1 -4
- package/build/theme/features/theme/utils/files_structure/theme_file_structure_errors_factory.js +10 -0
- package/build/theme/features/theme/utils/files_structure/theme_files_structure_utils.js +38 -2
- package/build/theme/features/theme/utils/theme_images_utils.js +0 -18
- package/build/theme/features/theme/verify/api/theme_verify_api.js +13 -0
- package/build/theme/features/theme/verify/http/theme_verify_http_api.js +30 -0
- package/build/theme/features/theme/verify/theme_verify_constants.js +2 -0
- package/build/theme/features/theme/verify/theme_verify_initializer.js +19 -0
- package/build/theme/features/theme/verify/verify/theme_verify_service.js +55 -0
- package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js +0 -1
- package/build/theme/hooks/themes_actions/ensure_themes_actions_hook_constants.js +1 -2
- package/build/theme/index.js +23 -3
- package/oclif.config.js +1 -1
- package/package.json +3 -3
- package/build/cli/commands/files_diff_command.js +0 -174
- package/build/theme/commands/theme_show_changes_command.js +0 -61
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { readJSONFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
|
|
2
|
-
import { join, looksLikeDirectory, mapKeysPathsToWindowPlatform } from '../../../../../utils/path_utils.js';
|
|
1
|
+
import { readJSONFile, removeFile, writeJSONFile } from '../../../../../utils/fs/fs_utils.js';
|
|
2
|
+
import { join, looksLikeDirectory, mapKeysPathsToWindowPlatform, toUnixPath } from '../../../../../utils/path_utils.js';
|
|
3
3
|
import { SHOPER_THEME_METADATA_DIR } from '../../../../constants/directory_contstants.js';
|
|
4
4
|
import { THEME_FILES_STRUCTURE_FILE_NAME } from '../../theme_constants.js';
|
|
5
5
|
import { isWindowsOs } from '../../../../../utils/platform_utils.js';
|
|
6
6
|
import { validateDirectory } from '../../../../utils/directory_validator/directory_validator_utils.js';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { THEME_FILES_LIST_FILE_NAME } from '../../push/theme_push_constants.js';
|
|
7
9
|
export class ThemeFilesStructureUtils {
|
|
8
10
|
static async getThemeFilesStructure(themeDirectory) {
|
|
9
11
|
const filesStructure = await readJSONFile(join(themeDirectory, SHOPER_THEME_METADATA_DIR, THEME_FILES_STRUCTURE_FILE_NAME));
|
|
@@ -37,4 +39,38 @@ export class ThemeFilesStructureUtils {
|
|
|
37
39
|
return looksLikeDirectory(path);
|
|
38
40
|
});
|
|
39
41
|
}
|
|
42
|
+
static mapFilesRecordsToFilesList(filesRecords, localFileNameToUploaded = {}) {
|
|
43
|
+
return filesRecords.reduce((acc, { fileGlob, fileName }) => {
|
|
44
|
+
const name = localFileNameToUploaded[fileName] ?? fileName;
|
|
45
|
+
if (looksLikeDirectory(fileGlob)) {
|
|
46
|
+
const existingFiles = acc[fileGlob] || [];
|
|
47
|
+
return {
|
|
48
|
+
...acc,
|
|
49
|
+
[fileGlob]: [...existingFiles, name]
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
return {
|
|
54
|
+
...acc,
|
|
55
|
+
[fileGlob]: name
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}, {});
|
|
59
|
+
}
|
|
60
|
+
static async createAFilesListFile(themeRootDir, filesList) {
|
|
61
|
+
if (!filesList || !Object.keys(filesList).length)
|
|
62
|
+
return;
|
|
63
|
+
const toUnixStyleFilesList = Object.entries(filesList).reduce((acc, [filePath, value]) => {
|
|
64
|
+
const unixPath = toUnixPath(filePath);
|
|
65
|
+
const finalPath = looksLikeDirectory(unixPath) ? `${unixPath}${path.posix.sep}` : unixPath;
|
|
66
|
+
return {
|
|
67
|
+
...acc,
|
|
68
|
+
[finalPath]: value
|
|
69
|
+
};
|
|
70
|
+
}, {});
|
|
71
|
+
await writeJSONFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME), toUnixStyleFilesList);
|
|
72
|
+
}
|
|
73
|
+
static async removeAFilesListFile(themeRootDir) {
|
|
74
|
+
await removeFile(join(themeRootDir, THEME_FILES_LIST_FILE_NAME));
|
|
75
|
+
}
|
|
40
76
|
}
|
|
@@ -1,24 +1,6 @@
|
|
|
1
1
|
import { join } from '../../../../utils/path_utils.js';
|
|
2
2
|
import { removeFile } from '../../../../utils/fs/fs_utils.js';
|
|
3
3
|
export class ThemeImagesUtils {
|
|
4
|
-
static updateOriginalFilenameToUploadedFilename(filesList, uploadedImageData) {
|
|
5
|
-
const newFilesList = { ...filesList };
|
|
6
|
-
uploadedImageData
|
|
7
|
-
.filter(({ uploadedFilename }) => Boolean(uploadedFilename))
|
|
8
|
-
.forEach(({ originalFilename, uploadedFilename, location }) => {
|
|
9
|
-
if (typeof newFilesList[location] === 'string' && uploadedFilename) {
|
|
10
|
-
newFilesList[location] = uploadedFilename;
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
if (Array.isArray(newFilesList[location]) && uploadedFilename) {
|
|
14
|
-
const indexOfOriginalFilename = newFilesList[location].indexOf(originalFilename);
|
|
15
|
-
if (indexOfOriginalFilename === -1 && !uploadedFilename)
|
|
16
|
-
return;
|
|
17
|
-
newFilesList[location].splice(indexOfOriginalFilename, 1, uploadedFilename);
|
|
18
|
-
}
|
|
19
|
-
});
|
|
20
|
-
return newFilesList;
|
|
21
|
-
}
|
|
22
4
|
static async removeUploadedOriginalFiles(themeRootDir, uploadedImagesData) {
|
|
23
5
|
await Promise.all(uploadedImagesData
|
|
24
6
|
.filter(({ originalFilename, uploadedFilename }) => Boolean(originalFilename) && Boolean(uploadedFilename))
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { FeatureApi } from '@dreamcommerce/star_core';
|
|
2
|
+
import { THEME_VERIFY_API_NAME } from '../theme_verify_constants.js';
|
|
3
|
+
export class ThemeVerifyApi extends FeatureApi {
|
|
4
|
+
moduleName = THEME_VERIFY_API_NAME;
|
|
5
|
+
#service;
|
|
6
|
+
constructor(service) {
|
|
7
|
+
super();
|
|
8
|
+
this.#service = service;
|
|
9
|
+
}
|
|
10
|
+
async verifyTheme(props) {
|
|
11
|
+
return this.#service.verifyTheme(props);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class ThemeVerifyHttpApi {
|
|
2
|
+
#httpApi;
|
|
3
|
+
constructor(httpApi) {
|
|
4
|
+
this.#httpApi = httpApi;
|
|
5
|
+
}
|
|
6
|
+
verifyTheme(props) {
|
|
7
|
+
return this._verifyThemeData(props);
|
|
8
|
+
}
|
|
9
|
+
verifySkinstoreFiles(props) {
|
|
10
|
+
return this._verifyThemeData(props);
|
|
11
|
+
}
|
|
12
|
+
verifyThumbnail(props) {
|
|
13
|
+
return this._verifyThemeData(props);
|
|
14
|
+
}
|
|
15
|
+
_verifyThemeData({ actionData, shopUrl, stream }) {
|
|
16
|
+
const { method, url } = actionData;
|
|
17
|
+
return this.#httpApi.fetch({
|
|
18
|
+
url: `${shopUrl}${url}`,
|
|
19
|
+
method,
|
|
20
|
+
data: stream,
|
|
21
|
+
sanitizeOptions: {
|
|
22
|
+
disable: true
|
|
23
|
+
},
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/octet-stream'
|
|
26
|
+
},
|
|
27
|
+
isPrivate: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { FEATURE_CORES_TYPES, HTTP_REQUESTER_API_NAME, SyncFeatureInitializer } from '@dreamcommerce/star_core';
|
|
2
|
+
import { ThemeVerifyApi } from './api/theme_verify_api.js';
|
|
3
|
+
import { THEME_VERIFY_FEATURE_NAME } from './theme_verify_constants.js';
|
|
4
|
+
import { ThemeVerifyService } from './verify/theme_verify_service.js';
|
|
5
|
+
export class ThemeVerifyInitializer extends SyncFeatureInitializer {
|
|
6
|
+
static featureName = THEME_VERIFY_FEATURE_NAME;
|
|
7
|
+
init() {
|
|
8
|
+
const httpApi = this.getApiSync(HTTP_REQUESTER_API_NAME);
|
|
9
|
+
const service = new ThemeVerifyService();
|
|
10
|
+
return {
|
|
11
|
+
cores: [
|
|
12
|
+
{
|
|
13
|
+
type: FEATURE_CORES_TYPES.api,
|
|
14
|
+
instance: new ThemeVerifyApi(service)
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import tmp from 'tmp-promise';
|
|
2
|
+
import { ThemePublishUtils } from '../../skinstore/theme_publish_utils.js';
|
|
3
|
+
import { ThemePushErrorsFactory } from '../../push/theme_push_errors_factory.js';
|
|
4
|
+
import { join } from '../../../../../utils/path_utils.js';
|
|
5
|
+
import { v4 as uuid } from 'uuid';
|
|
6
|
+
import { ThemeArchive } from '../../../../class/archive/theme_archive.js';
|
|
7
|
+
import { THEME_WILDCARD_ACTION_NAME } from '../../actions/service/theme_actions_service_constants.js';
|
|
8
|
+
import { THEME_ACTIONS_TYPES } from '../../actions/theme_actions_constants.js';
|
|
9
|
+
import { ThemeFilesStructureUtils } from '../../utils/files_structure/theme_files_structure_utils.js';
|
|
10
|
+
import { ThemeActionsUtils } from '../../actions/theme_actions_utils.js';
|
|
11
|
+
import { ArrayUtils } from '@dreamcommerce/utilities';
|
|
12
|
+
export class ThemeVerifyService {
|
|
13
|
+
async verifyTheme({ verifyAction, executionContext, credentials, themeChecksums, filesStructure, themeFilesUploadApi }) {
|
|
14
|
+
const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
|
|
15
|
+
const themeRootDir = executionContext.themeRootDir;
|
|
16
|
+
if (await themeChecksums.hasThemeFileBeenCreated(ThemePublishUtils.getSkinStoreSettingsFilePath(themeRootDir)))
|
|
17
|
+
throw ThemePushErrorsFactory.createErrorWhilePushingUnpublishedThemeWithSkinstoreData(credentials.shopUrl);
|
|
18
|
+
try {
|
|
19
|
+
//TODO to do api?
|
|
20
|
+
const filesRecords = await ThemeActionsUtils.getFilesRecordsFromActionData({
|
|
21
|
+
themeRootDir,
|
|
22
|
+
themeAction: verifyAction,
|
|
23
|
+
filesStructure
|
|
24
|
+
});
|
|
25
|
+
const filesToVerify = await ArrayUtils.asyncFilter(filesRecords, async ({ path }) => (await themeChecksums.hasThemeFileBeenModified(path)) || !(await themeChecksums.hasThemeFileBeenCreated(path)));
|
|
26
|
+
if (filesToVerify.length) {
|
|
27
|
+
const { rejectedImageData } = await themeFilesUploadApi.uploadFiles(filesToVerify);
|
|
28
|
+
if (rejectedImageData.length)
|
|
29
|
+
return {
|
|
30
|
+
isSuccess: false,
|
|
31
|
+
messages: rejectedImageData.flatMap((file) => file.messages)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
await this._createFilesList(themeRootDir, filesToVerify);
|
|
35
|
+
const themeArchivePath = join(tmpDir, `${uuid()}.zip`);
|
|
36
|
+
await new ThemeArchive(themeRootDir).createFullArchive({
|
|
37
|
+
dist: themeArchivePath,
|
|
38
|
+
actionValue: THEME_WILDCARD_ACTION_NAME,
|
|
39
|
+
actionType: THEME_ACTIONS_TYPES.push
|
|
40
|
+
});
|
|
41
|
+
const { isSuccess, messages } = await themeFilesUploadApi.uploadArchive({
|
|
42
|
+
action: verifyAction,
|
|
43
|
+
themeArchivePath,
|
|
44
|
+
credentials
|
|
45
|
+
});
|
|
46
|
+
return { isSuccess, messages };
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await ThemeFilesStructureUtils.removeAFilesListFile(themeRootDir);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async _createFilesList(themeRootDir, filesRecords) {
|
|
53
|
+
await ThemeFilesStructureUtils.createAFilesListFile(themeRootDir, ThemeFilesStructureUtils.mapFilesRecordsToFilesList(filesRecords));
|
|
54
|
+
}
|
|
55
|
+
}
|
package/build/theme/hooks/theme_checksums/ensure_theme_current_checksums_up_to_date_constants.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { THEME_COMMANDS_NAME } from '../../commands/theme_commands_constants.js';
|
|
2
2
|
export const THEME_COMMANDS_THAT_REQUIRES_UP_TO_DATE_CHECKSUMS = [
|
|
3
3
|
THEME_COMMANDS_NAME.push,
|
|
4
|
-
THEME_COMMANDS_NAME.showChanges,
|
|
5
4
|
THEME_COMMANDS_NAME.pull,
|
|
6
5
|
THEME_COMMANDS_NAME.verify
|
|
7
6
|
];
|
package/build/theme/index.js
CHANGED
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
import { ThemeListCommand } from './commands/list/theme_list_command.js';
|
|
2
2
|
import { ThemePullCommand } from './commands/pull/theme_pull_command.js';
|
|
3
3
|
import { ThemeInitCommand } from './commands/init/theme_init_command.js';
|
|
4
|
-
import { THEME_COMMANDS_NAME } from './commands/theme_commands_constants.js';
|
|
4
|
+
import { THEME_COMMANDS_NAME, THEME_TOPIC_NAME } from './commands/theme_commands_constants.js';
|
|
5
5
|
import { ThemePushCommand } from './commands/push/theme_push_command.js';
|
|
6
|
-
import { ThemeShowChangesCommand } from './commands/theme_show_changes_command.js';
|
|
7
6
|
import { ThemeVerifyCommand } from './commands/theme_verify_command.js';
|
|
8
7
|
import { ThemeInfoCommand } from './commands/info/theme_info_command.js';
|
|
9
8
|
import { ThemeDeleteCommand } from './commands/delete/theme_delete_command.js';
|
|
10
9
|
import { ThemePublishCommand } from './commands/publish/theme_publish_command.js';
|
|
10
|
+
import { ThemesListInitializer } from './features/themes/list/themes_list_initializer.js';
|
|
11
|
+
import { ThemeMergeInitializer } from './features/theme/merge/theme_merge_initializer.js';
|
|
12
|
+
import { ThemeFetchInitializer } from './features/theme/fetch/theme_fetch_initializer.js';
|
|
13
|
+
import { ThemeInitInitializer } from './features/theme/init/theme_init_initializer.js';
|
|
14
|
+
import { ThemePushInitializer } from './features/theme/push/theme_push_initializer.js';
|
|
15
|
+
import { ThemeVerifyInitializer } from './features/theme/verify/theme_verify_initializer.js';
|
|
16
|
+
import { ThemeDeleteInitializer } from './features/theme/delete/theme_delete_initalizer.js';
|
|
17
|
+
import { ThemeActionsInitializer } from './features/theme/actions/theme_actions_initializer.js';
|
|
11
18
|
export const COMMANDS = {
|
|
12
19
|
[THEME_COMMANDS_NAME.list]: ThemeListCommand,
|
|
13
20
|
[THEME_COMMANDS_NAME.pull]: ThemePullCommand,
|
|
14
21
|
[THEME_COMMANDS_NAME.init]: ThemeInitCommand,
|
|
15
22
|
[THEME_COMMANDS_NAME.push]: ThemePushCommand,
|
|
16
|
-
[THEME_COMMANDS_NAME.showChanges]: ThemeShowChangesCommand,
|
|
17
23
|
[THEME_COMMANDS_NAME.verify]: ThemeVerifyCommand,
|
|
18
24
|
[THEME_COMMANDS_NAME.info]: ThemeInfoCommand,
|
|
19
25
|
[THEME_COMMANDS_NAME.delete]: ThemeDeleteCommand,
|
|
20
26
|
[THEME_COMMANDS_NAME.publish]: ThemePublishCommand
|
|
21
27
|
};
|
|
28
|
+
export const COMMAND_TO_FEATURES_MAP = {
|
|
29
|
+
[THEME_COMMANDS_NAME.pull]: [ThemeMergeInitializer, ThemeFetchInitializer],
|
|
30
|
+
[THEME_COMMANDS_NAME.init]: ThemeInitInitializer,
|
|
31
|
+
[THEME_COMMANDS_NAME.push]: [ThemeFetchInitializer, ThemePushInitializer],
|
|
32
|
+
[THEME_COMMANDS_NAME.verify]: ThemeVerifyInitializer,
|
|
33
|
+
[THEME_COMMANDS_NAME.delete]: ThemeDeleteInitializer
|
|
34
|
+
};
|
|
35
|
+
export const getThemeInitializersForCommand = (commandName) => {
|
|
36
|
+
if (!commandName)
|
|
37
|
+
return [];
|
|
38
|
+
const alwaysIncludedInitializers = [ThemesListInitializer, ThemeActionsInitializer];
|
|
39
|
+
const initializers = COMMAND_TO_FEATURES_MAP[`${THEME_TOPIC_NAME}:${commandName}`] ?? [];
|
|
40
|
+
return [...alwaysIncludedInitializers, ...(Array.isArray(initializers) ? initializers : [initializers])];
|
|
41
|
+
};
|
package/oclif.config.js
CHANGED
|
@@ -2,7 +2,7 @@ export default {
|
|
|
2
2
|
bin: 'shoper',
|
|
3
3
|
scope: 'shoper',
|
|
4
4
|
dirname: 'shoper_cli',
|
|
5
|
-
plugins: ['@oclif/plugin-help', '@oclif/plugin-
|
|
5
|
+
plugins: ['@oclif/plugin-help', '@oclif/plugin-warn-if-update-available', '@oclif/plugin-version'],
|
|
6
6
|
'warn-if-update-available': {
|
|
7
7
|
frequency: 1,
|
|
8
8
|
frequencyUnit: 'days',
|
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.2.
|
|
5
|
+
"version": "0.2.1-1",
|
|
6
6
|
"description": "CLI tool for Shoper",
|
|
7
7
|
"author": "Joanna Firek",
|
|
8
8
|
"license": "MIT",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"type": "module",
|
|
19
19
|
"module": "build/index.js",
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
21
|
+
"node": ">=21.19.5",
|
|
22
22
|
"npm": ">=10.8.2"
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@dreamcommerce/star_core": "1.8.5",
|
|
34
|
+
"@dreamcommerce/utilities": "1.20.1",
|
|
34
35
|
"@oclif/core": "4.2.10",
|
|
35
36
|
"@oclif/plugin-autocomplete": "3.2.27",
|
|
36
37
|
"@oclif/plugin-help": "6.2.27",
|
|
37
|
-
"@oclif/plugin-not-found": "3.2.49",
|
|
38
38
|
"@oclif/plugin-version": "2.2.27",
|
|
39
39
|
"@oclif/plugin-warn-if-update-available": "3.1.38",
|
|
40
40
|
"axios": "1.8.4",
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
//@ts-nocheck
|
|
2
|
-
import { BaseCliCommand } from '../class/base_cli_command.js';
|
|
3
|
-
import * as readline from 'node:readline/promises';
|
|
4
|
-
import * as fs from 'node:fs';
|
|
5
|
-
import { Args } from '@oclif/core';
|
|
6
|
-
import process from 'node:process';
|
|
7
|
-
import * as path from 'node:path';
|
|
8
|
-
import { diffWords } from 'diff';
|
|
9
|
-
export class CliFilesDiffCommand extends BaseCliCommand {
|
|
10
|
-
static summary = 'Differentiate between two files';
|
|
11
|
-
static args = {
|
|
12
|
-
file1: Args.string({
|
|
13
|
-
name: 'file1',
|
|
14
|
-
required: true,
|
|
15
|
-
description: 'First file to compare',
|
|
16
|
-
type: 'string'
|
|
17
|
-
}),
|
|
18
|
-
file2: Args.string({
|
|
19
|
-
name: 'file2',
|
|
20
|
-
required: true,
|
|
21
|
-
description: 'Second file to compare',
|
|
22
|
-
type: 'string'
|
|
23
|
-
})
|
|
24
|
-
};
|
|
25
|
-
async run() {
|
|
26
|
-
const { args } = await this.parse(CliFilesDiffCommand);
|
|
27
|
-
const inputAPath = args.file1;
|
|
28
|
-
const inputBPath = args.file2;
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
Promise.all([readLines(inputAPath), readLines(inputBPath)]).then((result) => {
|
|
31
|
-
const [aLines, bLines] = result;
|
|
32
|
-
// console.log('aLines', aLines);
|
|
33
|
-
// console.log('bLines', bLines);
|
|
34
|
-
merge(aLines, bLines);
|
|
35
|
-
resolve();
|
|
36
|
-
});
|
|
37
|
-
// aStream.on('line', (input) => {
|
|
38
|
-
// console.log('aStream line: ', input);
|
|
39
|
-
// });
|
|
40
|
-
//
|
|
41
|
-
// bStream.on('line', (input) => {
|
|
42
|
-
// console.log('bStream line: ', input);
|
|
43
|
-
// });
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const readLines = async (filePath) => {
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
const lines = [];
|
|
50
|
-
const inputStream = readline.createInterface({
|
|
51
|
-
input: fs.createReadStream(path.resolve(process.cwd(), filePath)),
|
|
52
|
-
crlfDelay: Infinity
|
|
53
|
-
});
|
|
54
|
-
inputStream.on('line', (line) => {
|
|
55
|
-
lines.push(line);
|
|
56
|
-
});
|
|
57
|
-
inputStream.on('close', () => {
|
|
58
|
-
resolve(lines);
|
|
59
|
-
});
|
|
60
|
-
inputStream.on('error', (err) => {
|
|
61
|
-
reject(err);
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
const merge = (aLines, bLines) => {
|
|
66
|
-
let hasConflict = false;
|
|
67
|
-
const maxLength = Math.max(aLines.length, bLines.length);
|
|
68
|
-
const outputPath = path.resolve(process.cwd(), 'merge.txt');
|
|
69
|
-
const outStream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
|
|
70
|
-
for (let i = 0; i < maxLength; i++) {
|
|
71
|
-
const a = aLines[i] ?? '';
|
|
72
|
-
const b = bLines[i] ?? '';
|
|
73
|
-
if (a === b) {
|
|
74
|
-
outStream.write(a + '\n');
|
|
75
|
-
}
|
|
76
|
-
else {
|
|
77
|
-
hasConflict = true;
|
|
78
|
-
console.log('⚠️ Conflict detected:');
|
|
79
|
-
console.log('a:', a);
|
|
80
|
-
console.log('b:', b);
|
|
81
|
-
const { aFormatted, bFormatted } = formatWordDiff(a, b);
|
|
82
|
-
console.log('aFormatted', aFormatted);
|
|
83
|
-
console.log('bFormatted', bFormatted);
|
|
84
|
-
outStream.write('<<<<<<< mine\n');
|
|
85
|
-
outStream.write(aFormatted + '\n');
|
|
86
|
-
outStream.write('=======\n');
|
|
87
|
-
outStream.write(bFormatted + '\n');
|
|
88
|
-
outStream.write('>>>>>>> theirs\n');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
outStream.end();
|
|
92
|
-
outStream.on('finish', () => {
|
|
93
|
-
if (hasConflict) {
|
|
94
|
-
console.log('⚠️ Conflicts detected. Check', outputPath);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
console.log('✅ Merged with no conflicts. Output written to', outputPath);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
function formatWordDiff(aLine, bLine) {
|
|
102
|
-
const changes = diffWords(aLine, bLine);
|
|
103
|
-
const aFormatted = changes.map((part) => (part.added ? '' : part.value)).join('');
|
|
104
|
-
const bFormatted = changes.map((part) => (part.removed ? '' : part.value)).join('');
|
|
105
|
-
return {
|
|
106
|
-
aFormatted: aFormatted.trimEnd(),
|
|
107
|
-
bFormatted: bFormatted.trimEnd()
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
// const fs = require('fs');
|
|
111
|
-
// const path = require('path');
|
|
112
|
-
// const readline = require('readline');
|
|
113
|
-
// const Diff = require('diff');
|
|
114
|
-
//
|
|
115
|
-
// async function mergeWithCommentsStream(fileAPath, fileBPath, outputPath) {
|
|
116
|
-
// const textA = await fs.promises.readFile(fileAPath, 'utf8');
|
|
117
|
-
// const textB = await fs.promises.readFile(fileBPath, 'utf8');
|
|
118
|
-
//
|
|
119
|
-
// const diffs = Diff.diffLines(textA, textB);
|
|
120
|
-
// const outputStream = fs.createWriteStream(outputPath, { encoding: 'utf8' });
|
|
121
|
-
//
|
|
122
|
-
// let currentState = 'equal'; // 'equal' | 'added' | 'removed'
|
|
123
|
-
//
|
|
124
|
-
// for (const part of diffs) {
|
|
125
|
-
// const lines = part.value.endsWith('\n') ? part.value.slice(0, -1).split('\n') : part.value.split('\n');
|
|
126
|
-
//
|
|
127
|
-
// if (part.added) {
|
|
128
|
-
// if (currentState !== 'added') {
|
|
129
|
-
// outputStream.write('added >>>>>\n');
|
|
130
|
-
// currentState = 'added';
|
|
131
|
-
// }
|
|
132
|
-
// for (const line of lines) {
|
|
133
|
-
// outputStream.write(line + '\n');
|
|
134
|
-
// }
|
|
135
|
-
// } else if (part.removed) {
|
|
136
|
-
// if (currentState !== 'removed') {
|
|
137
|
-
// outputStream.write('removed >>>>>\n');
|
|
138
|
-
// currentState = 'removed';
|
|
139
|
-
// }
|
|
140
|
-
// for (const line of lines) {
|
|
141
|
-
// outputStream.write(line + '\n');
|
|
142
|
-
// }
|
|
143
|
-
// } else {
|
|
144
|
-
// // Close any previous block
|
|
145
|
-
// if (currentState === 'added') {
|
|
146
|
-
// outputStream.write('end-added >>>\n');
|
|
147
|
-
// } else if (currentState === 'removed') {
|
|
148
|
-
// outputStream.write('end-removed >>>\n');
|
|
149
|
-
// }
|
|
150
|
-
// currentState = 'equal';
|
|
151
|
-
//
|
|
152
|
-
// for (const line of lines) {
|
|
153
|
-
// outputStream.write(line + '\n');
|
|
154
|
-
// }
|
|
155
|
-
// }
|
|
156
|
-
// }
|
|
157
|
-
//
|
|
158
|
-
// // Final cleanup in case the last block was added/removed
|
|
159
|
-
// if (currentState === 'added') {
|
|
160
|
-
// outputStream.write('end-added >>>\n');
|
|
161
|
-
// } else if (currentState === 'removed') {
|
|
162
|
-
// outputStream.write('end-removed >>>\n');
|
|
163
|
-
// }
|
|
164
|
-
//
|
|
165
|
-
// outputStream.end();
|
|
166
|
-
// console.log(`✅ Merge complete. Output written to: ${outputPath}`);
|
|
167
|
-
// }
|
|
168
|
-
//
|
|
169
|
-
// // Example usage
|
|
170
|
-
// const fileA = path.resolve('versionA.txt');
|
|
171
|
-
// const fileB = path.resolve('versionB.txt');
|
|
172
|
-
// const outputFile = path.resolve('mergedWithComments.txt');
|
|
173
|
-
//
|
|
174
|
-
// mergeWithCommentsStream(fileA, fileB, outputFile);
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { BaseThemeCommand } from '../class/base_theme_command.js';
|
|
2
|
-
import { CLI_AUTH_API_NAME } from '../../cli/auth/cli_auth_constants.js';
|
|
3
|
-
import { THEME_MERGE_API_NAME } from '../features/theme/merge/theme_merge_constants.js';
|
|
4
|
-
import { EXECUTION_CONTEXT_API_NAME, EXECUTION_CONTEXTS } from '../../cli/features/execution_context/execution_context_constants.js';
|
|
5
|
-
import { THEME_FETCH_API_NAME, THEME_FETCH_TYPES } from '../features/theme/fetch/theme_fetch_constants.js';
|
|
6
|
-
import tmp from 'tmp-promise';
|
|
7
|
-
import { THEME_ACTIONS_API_NAME, THEME_ACTIONS_TYPES } from '../features/theme/actions/theme_actions_constants.js';
|
|
8
|
-
import { join } from '../../utils/path_utils.js';
|
|
9
|
-
import ora from 'ora';
|
|
10
|
-
export class ThemeShowChangesCommand extends BaseThemeCommand {
|
|
11
|
-
static description = 'Show local theme changes';
|
|
12
|
-
static hidden = true;
|
|
13
|
-
async run() {
|
|
14
|
-
const cliAuthApi = this.getApi(CLI_AUTH_API_NAME);
|
|
15
|
-
const credentials = cliAuthApi.getCredentials();
|
|
16
|
-
if (!credentials)
|
|
17
|
-
this.error('Credentials not found. Please authorize first.');
|
|
18
|
-
const executionContextApi = this.getApi(EXECUTION_CONTEXT_API_NAME);
|
|
19
|
-
const themeMergeApi = this.getApi(THEME_MERGE_API_NAME);
|
|
20
|
-
const themeFetchApi = this.getApi(THEME_FETCH_API_NAME);
|
|
21
|
-
const themeActionsApi = this.getApi(THEME_ACTIONS_API_NAME);
|
|
22
|
-
const executionContext = await executionContextApi.getExecutionContext();
|
|
23
|
-
if (executionContext.type !== EXECUTION_CONTEXTS.theme)
|
|
24
|
-
this.error('You cannot run this command outside theme context.');
|
|
25
|
-
const { path: tmpDir } = await tmp.dir({ unsafeCleanup: true });
|
|
26
|
-
const pullAction = themeActionsApi.getThemeAction({
|
|
27
|
-
actionType: THEME_ACTIONS_TYPES.pull,
|
|
28
|
-
themeId: executionContext.themeId,
|
|
29
|
-
credentials
|
|
30
|
-
});
|
|
31
|
-
const spinner = ora('Pulling theme...').start();
|
|
32
|
-
try {
|
|
33
|
-
spinner.stopAndPersist({
|
|
34
|
-
symbol: '\u2713',
|
|
35
|
-
text: 'Calculating theme changes...'
|
|
36
|
-
});
|
|
37
|
-
const { name } = await themeFetchApi.fetchTheme({
|
|
38
|
-
credentials,
|
|
39
|
-
action: pullAction,
|
|
40
|
-
config: {
|
|
41
|
-
fetchType: THEME_FETCH_TYPES.full,
|
|
42
|
-
dist: tmpDir
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
const changes = await themeMergeApi.getChangesBetweenThemes(executionContext.themeRootDir, join(tmpDir, name));
|
|
46
|
-
spinner.clear();
|
|
47
|
-
if (!changes.length) {
|
|
48
|
-
this.log('No changes found in your theme.');
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
this.log('Changes:\n');
|
|
52
|
-
changes.forEach(([action, name]) => {
|
|
53
|
-
console.log(name, ' - ', action);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
//TODO
|
|
58
|
-
this.error(`Failed to fetch theme changes: ${err instanceof Error ? err.message : 'Unknown error'}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|