Skip to content

Commit 6fc04a2

Browse files
author
Michal Paukert
authored
Merge pull request #12 from Kentico/backup-tool
Integrate kontent backup tool
2 parents 74d853f + 5a8ec5d commit 6fc04a2

File tree

5 files changed

+168
-15
lines changed

5 files changed

+168
-15
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ The supported commands are divided into groups according to their target, at thi
117117
* After each run of a migration script, the CLI logs the execution into a status file. This file holds data for the next run to prevent running the same migration script more than once. You can choose to override this behavior, for example for debugging purposes, by using the `--force` parameter.
118118
* You can choose whether you want to keep executing the migration scripts even if one migration script fails (option `--continue-on-error`) or whether you want to run in the debug mode (option `--debug`) and get additional information for certain issues logged into the console.
119119

120+
* `backup --action [backup|restore|clean]` - This command enables you to use [Kontent backup manager](https://github.com/Kentico/kontent-backup-manager-js)
121+
* The purpose of this tool is to backup & restore [Kentico Kontent projects](https://kontent.ai/). This project uses CM API to both get & restore data.
122+
120123
### Debugging
121124

122125
If you come across an error and you're not sure how to fix it, execute your migration script as follows and setup your debugger to the specified port.

package.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kentico/kontent-cli",
3-
"version": "0.0.8",
3+
"version": "0.0.9",
44
"description": "Command line interface tool that can be used for generating and running Kontent migration scripts",
55
"main": "./lib/index.js",
66
"types": "./lib/types/index.d.ts",
@@ -47,7 +47,9 @@
4747
"dependencies": {
4848
"chalk": "^4.0.0",
4949
"yargs": "^15.3.1",
50-
"@kentico/kontent-management": "~0.3.19"
50+
"@kentico/kontent-management": "~0.3.19",
51+
"@kentico/kontent-backup-manager": "~1.2.2",
52+
"dotenv": "^8.2.0"
5153
},
5254
"peerDependencies": {
5355
"rxjs": "^6.5.5"
@@ -57,15 +59,14 @@
5759
"@babel/preset-env": "^7.9.5",
5860
"@babel/preset-typescript": "^7.9.0",
5961
"@types/jest": "^25.2.1",
60-
"@types/node": "^13.11.1",
62+
"@types/node": "^13.13.2",
6163
"@types/yargs": "^15.0.4",
62-
"babel-jest": "^25.3.0",
63-
"dotenv": "^8.2.0",
64+
"babel-jest": "^25.4.0",
6465
"husky": "^4.2.5",
65-
"jest": "^25.3.0",
66-
"lint-staged": "^10.1.3",
67-
"prettier": "^2.0.4",
68-
"ts-jest": "^25.3.1",
66+
"jest": "^25.4.0",
67+
"lint-staged": "^10.1.7",
68+
"prettier": "^2.0.5",
69+
"ts-jest": "^25.4.0",
6970
"tslint": "^6.1.1",
7071
"tslint-config-prettier": "^1.18.0",
7172
"tslint-config-standard": "^9.0.0",

src/cmds/backup.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import yargs from 'yargs';
2+
import chalk from 'chalk';
3+
import { environmentConfigExists, getEnvironmentsConfig } from '../utils/environmentUtils';
4+
import { CleanService, ExportService, ImportService, ZipService } from '@kentico/kontent-backup-manager';
5+
import { getFileBackupName } from '../utils/fileUtils';
6+
import { IProcessedItem } from '@kentico/kontent-backup-manager/_commonjs/src';
7+
8+
const kontentBackupCommand: yargs.CommandModule = {
9+
command: 'backup',
10+
describe: 'Kontent backup tool to backup & restore Kentico Kontent projects through Management API.',
11+
builder: (yargs: any) =>
12+
yargs
13+
.options({
14+
action: {
15+
alias: 'a',
16+
describe: 'Action for backup',
17+
type: 'string',
18+
},
19+
name: {
20+
alias: 'n',
21+
describe: 'Name of zip file',
22+
type: 'string',
23+
},
24+
log: {
25+
alias: 'l',
26+
describe: 'Enables/Disables logging',
27+
type: 'boolean',
28+
default: true,
29+
},
30+
'project-id': {
31+
alias: 'p',
32+
describe: 'Project ID to run the migration script on',
33+
type: 'string',
34+
},
35+
'api-key': {
36+
alias: 'k',
37+
describe: 'Management API key',
38+
type: 'string',
39+
},
40+
environment: {
41+
alias: 'e',
42+
describe: 'Environment name',
43+
type: 'string',
44+
},
45+
})
46+
.conflicts('environment', 'api-key')
47+
.conflicts('environment', 'project-id')
48+
.check((args: any) => {
49+
if (!args.environment && !(args.projectId && args.apiKey)) {
50+
throw new Error(chalk.red('Specify an environment or a project ID with its Management API key.'));
51+
}
52+
53+
if (args.environment) {
54+
if (!environmentConfigExists()) {
55+
throw new Error(chalk.red(`Cannot find the environment configuration file. Add an environment named \"${args.environment}\" first.`));
56+
}
57+
58+
const environments = getEnvironmentsConfig();
59+
60+
if (!environments[args.environment]) {
61+
throw new Error(chalk.red(`Cannot find the \"${args.environment}\" environment.`));
62+
}
63+
}
64+
65+
return true;
66+
}),
67+
handler: async (argv: any) => {
68+
let projectId = argv.projectId;
69+
let apiKey = argv.apiKey;
70+
if (argv.environment) {
71+
const environments = getEnvironmentsConfig();
72+
73+
projectId = environments[argv.environment].projectId || argv.projectId;
74+
apiKey = environments[argv.environment].apiKey || argv.apiKey;
75+
}
76+
77+
const defaultBackupName = getFileBackupName();
78+
const zipService = new ZipService({
79+
filename: argv.name || defaultBackupName,
80+
enableLog: argv.log,
81+
});
82+
83+
console.log('Starting backup tool');
84+
85+
switch (argv.action) {
86+
case 'backup':
87+
const exportService = new ExportService({
88+
apiKey: apiKey,
89+
projectId: projectId,
90+
onExport: (item: IProcessedItem) => {
91+
if (argv.log) {
92+
console.log(`Exported: ${item.title} | ${item.type}`);
93+
}
94+
},
95+
});
96+
const exportedData = await exportService.exportAllAsync();
97+
await zipService.createZipAsync(exportedData);
98+
break;
99+
100+
case 'restore':
101+
const zipData = await zipService.extractZipAsync();
102+
const importService = new ImportService({
103+
onImport: (item: IProcessedItem) => {
104+
if (argv.log) {
105+
console.log(`Imported: ${item.title} | ${item.type}`);
106+
}
107+
},
108+
projectId: projectId,
109+
apiKey: apiKey,
110+
enableLog: argv.log,
111+
fixLanguages: true,
112+
workflowIdForImportedItems: '00000000-0000-0000-0000-000000000000',
113+
});
114+
await importService.importFromSourceAsync(zipData);
115+
break;
116+
117+
case 'clean':
118+
const cleanService = new CleanService({
119+
onDelete: (item: IProcessedItem) => {
120+
if (argv.log) {
121+
console.log(`Deleted: ${item.title} | ${item.type}`);
122+
}
123+
},
124+
projectId: projectId,
125+
apiKey: apiKey,
126+
});
127+
128+
await cleanService.cleanAllAsync();
129+
break;
130+
131+
default:
132+
throw new Error('Unknown action type');
133+
}
134+
135+
console.log('Completed');
136+
process.exit(0);
137+
},
138+
};
139+
140+
// yargs needs exported command in exports object
141+
Object.assign(exports, kontentBackupCommand);

src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ const createMigrationTool = (): number => {
1616
.example('kontent', 'migration run --name migration01 --project-id <YOUR_PROJECT_ID> --api-key <YOUR_MANAGEMENT_API_KEY>')
1717
.example('kontent', 'migration run --name migration01 --environment DEV --debug true')
1818
.example('kontent', 'migration run --all --environment DEV')
19+
20+
.example('kontent', 'backup --action backup --project-id <YOUR_PROJECT_ID> --api-key <YOUR_MANAGEMENT_API_KEY>')
21+
.example('kontent', 'backup --action backup --environment <YOUR_ENVIRONMENT>')
22+
.example('kontent', 'backup --action restore --name backup_file --project-id <YOUR_PROJECT_ID> --api-key <YOUR_MANAGEMENT_API_KEY>')
23+
.example('kontent', 'backup --action clean --project-id <YOUR_PROJECT_ID> --api-key <YOUR_MANAGEMENT_API_KEY>')
1924
.strict().argv;
2025

2126
return 0;

src/utils/fileUtils.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import * as path from 'path';
55
export const listFiles = (fileExtension: string): Dirent[] => {
66
return fs
77
.readdirSync(getMigrationDirectory(), { withFileTypes: true })
8-
.filter(f => f.isFile())
9-
.filter(f => f.name.endsWith(fileExtension));
8+
.filter((f) => f.isFile())
9+
.filter((f) => f.name.endsWith(fileExtension));
1010
};
1111

1212
export const fileExists = (filePath: PathLike): boolean => {
@@ -22,12 +22,15 @@ export const getFileWithExtension = (filename: string, defaultExtension: string
2222
const hasFileExtension = path.extname(filename);
2323

2424
if (hasFileExtension) {
25-
const normalized = filename
26-
.split(defaultExtension)
27-
.slice(0, -1)
28-
.join(defaultExtension);
25+
const normalized = filename.split(defaultExtension).slice(0, -1).join(defaultExtension);
2926
return normalized + defaultExtension;
3027
} else {
3128
return filename + defaultExtension;
3229
}
3330
};
31+
32+
export const getFileBackupName = () => {
33+
const currentDate = new Date();
34+
const formatted = `${currentDate.getFullYear()}-${currentDate.getMonth()}-${currentDate.getDate()}-${currentDate.getTime()}`;
35+
return `backup-${formatted}`;
36+
};

0 commit comments

Comments
 (0)