joplin-plugin-backup 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -2,8 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
A plugin to extend Joplin with a manual and automatic backup function.
|
|
4
4
|
|
|
5
|
+
<!-- markdownlint-disable MD033 -->
|
|
6
|
+
<!-- markdownlint-disable MD028 -->
|
|
7
|
+
<!-- markdownlint-disable MD007 -->
|
|
8
|
+
|
|
5
9
|
<img src=img/main.jpg>
|
|
6
10
|
|
|
11
|
+
<!-- prettier-ignore-start -->
|
|
12
|
+
<!-- TOC depthfrom:2 orderedlist:false -->
|
|
13
|
+
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Automatic](#automatic)
|
|
16
|
+
- [Manual](#manual)
|
|
17
|
+
- [Usage](#usage)
|
|
18
|
+
- [Options](#options)
|
|
19
|
+
- [Keyboard Shortcuts](#keyboard-shortcuts)
|
|
20
|
+
- [What is backed up](#what-is-backed-up)
|
|
21
|
+
- [Restore](#restore)
|
|
22
|
+
- [Settings](#settings)
|
|
23
|
+
- [Notes](#notes)
|
|
24
|
+
- [FAQ](#faq)
|
|
25
|
+
- [Internal Joplin links betwen notes are lost](#internal-joplin-links-betwen-notes-are-lost)
|
|
26
|
+
- [Combine multiple JEX Files to one](#combine-multiple-jex-files-to-one)
|
|
27
|
+
- [Open a JEX Backup file](#open-a-jex-backup-file)
|
|
28
|
+
- [Changelog](#changelog)
|
|
29
|
+
- [Links](#links)
|
|
30
|
+
|
|
31
|
+
<!-- /TOC -->
|
|
32
|
+
<!-- prettier-ignore-end -->
|
|
33
|
+
|
|
7
34
|
## Installation
|
|
8
35
|
|
|
9
36
|
### Automatic
|
|
@@ -33,7 +60,7 @@ Go to `Tools > Options > Backup`
|
|
|
33
60
|
|
|
34
61
|
| Option | Description | Default |
|
|
35
62
|
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
|
|
36
|
-
| `Backup path` | Where to save the backups to. <br>This path is exclusive for the Joplin backups, there should be no other data in it!
|
|
63
|
+
| `Backup path` | Where to save the backups to. <br>This path is exclusive for the Joplin backups, there should be no other data in it when you disable the `Create Subfolder` settings! | |
|
|
37
64
|
| `Keep x backups` | How many backups should be kept | `1` |
|
|
38
65
|
| `Backups interval in hours` | Create a backup every X hours | `24` |
|
|
39
66
|
| `Only on change` | Creates a backup at the specified backup interval only if there was a change to a `note`, `tag`, `resource` or `notebook` | `false` |
|
|
@@ -44,6 +71,8 @@ Go to `Tools > Options > Backup`
|
|
|
44
71
|
| `Temporary export path` | The data is first exported into this path before it is copied to the backup `Backup path`. | `` |
|
|
45
72
|
| `Backup set name` | Name of the backup set if multiple backups are to be keep. [Available moment tokens](https://momentjs.com/docs/#/displaying/format/), which can be used with `{<TOKEN>}` | `{YYYYMMDDHHmm}` |
|
|
46
73
|
| `Single JEX` | Create only one JEX file for all, this option is recommended to prevent the loss of internal note links or folder structure during a restore! | `true` |
|
|
74
|
+
| `Export format` | Selection of the export format of the notes. | `jex` |
|
|
75
|
+
| `Command on Backup finish` | Execute command when backup is finished. | |
|
|
47
76
|
| `Create Subfolder` | Create a sub folder `JoplinBackup` in the configured `Backup path`. Deactivate only if there is no other data in the `Backup path`! | `true` |
|
|
48
77
|
| `Backup plugins` | Backup the plugin folder from the Joplin profile with all installed plugin jpl files. | `true` |
|
|
49
78
|
|
|
@@ -79,7 +108,7 @@ The notes are imported via `File > Import > JEX - Joplin Export File`.
|
|
|
79
108
|
> Individual notes cannot be restored from the JEX file!
|
|
80
109
|
|
|
81
110
|
The notes are imported additionally, no check for duplicates is performed.
|
|
82
|
-
If the
|
|
111
|
+
If the notebook in which the note was located already exists in your Joplin, then a "(1)" will be appended to the folder name.
|
|
83
112
|
|
|
84
113
|
## FAQ
|
|
85
114
|
|
|
@@ -88,7 +117,7 @@ If the folder in which the note was located already exists in you Joplin, than t
|
|
|
88
117
|
If several JEX files are imported and the notes have links to each other, these links will be lost.
|
|
89
118
|
Therefore it is recommended to create a Single JEX Backup!
|
|
90
119
|
|
|
91
|
-
###
|
|
120
|
+
### Combine multiple JEX Files to one
|
|
92
121
|
|
|
93
122
|
By combining the JEX files into one, the Joplin internal links will work again after the import.
|
|
94
123
|
|
package/__test__/backup.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ function getTestPaths(): any {
|
|
|
15
15
|
);
|
|
16
16
|
testPath.joplinProfile = path.join(testPath.base, "joplin-desktop");
|
|
17
17
|
testPath.templates = path.join(testPath.joplinProfile, "templates");
|
|
18
|
+
testPath.plugins = path.join(testPath.joplinProfile, "plugins");
|
|
18
19
|
return testPath;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -39,6 +40,7 @@ async function createTestStructure() {
|
|
|
39
40
|
fs.emptyDirSync(test.backupBasePath);
|
|
40
41
|
fs.emptyDirSync(test.joplinProfile);
|
|
41
42
|
fs.emptyDirSync(test.templates);
|
|
43
|
+
fs.emptyDirSync(test.plugins);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
const testPath = getTestPaths();
|
|
@@ -573,6 +575,45 @@ describe("Backup", function () {
|
|
|
573
575
|
});
|
|
574
576
|
|
|
575
577
|
describe("Backup retention", function () {
|
|
578
|
+
it(`file/Folder deletion`, async () => {
|
|
579
|
+
const backupRetention = 2;
|
|
580
|
+
const set1 = path.join(testPath.backupBasePath, "202101011630");
|
|
581
|
+
const set2 = path.join(testPath.backupBasePath, "202101021630.7z");
|
|
582
|
+
const set3 = path.join(testPath.backupBasePath, "202101031630");
|
|
583
|
+
const set4 = path.join(testPath.backupBasePath, "202101041630.7z");
|
|
584
|
+
|
|
585
|
+
fs.emptyDirSync(set1);
|
|
586
|
+
fs.closeSync(fs.openSync(set2, "w"));
|
|
587
|
+
fs.emptyDirSync(set3);
|
|
588
|
+
fs.closeSync(fs.openSync(set4, "w"));
|
|
589
|
+
|
|
590
|
+
const backupInfo = [
|
|
591
|
+
{ name: "202101011630", date: 1609515000 },
|
|
592
|
+
{ name: "202101021630.7z", date: 1609601400 },
|
|
593
|
+
{ name: "202101031630", date: 1609687800 },
|
|
594
|
+
{ name: "202101041630.7z", date: 1609774200 },
|
|
595
|
+
];
|
|
596
|
+
|
|
597
|
+
when(spyOnsSettingsValue)
|
|
598
|
+
.calledWith("backupInfo")
|
|
599
|
+
.mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
|
|
600
|
+
|
|
601
|
+
expect(fs.existsSync(set1)).toBe(true);
|
|
602
|
+
expect(fs.existsSync(set2)).toBe(true);
|
|
603
|
+
expect(fs.existsSync(set3)).toBe(true);
|
|
604
|
+
expect(fs.existsSync(set4)).toBe(true);
|
|
605
|
+
await backup.deleteOldBackupSets(
|
|
606
|
+
testPath.backupBasePath,
|
|
607
|
+
backupRetention
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
expect(fs.existsSync(set1)).toBe(false);
|
|
611
|
+
expect(fs.existsSync(set2)).toBe(false);
|
|
612
|
+
expect(fs.existsSync(set3)).toBe(true);
|
|
613
|
+
expect(fs.existsSync(set4)).toBe(true);
|
|
614
|
+
expect(fs.readdirSync(testPath.backupBasePath).length).toBe(2);
|
|
615
|
+
});
|
|
616
|
+
|
|
576
617
|
it(`Backups < retention`, async () => {
|
|
577
618
|
const backupRetention = 3;
|
|
578
619
|
const folder1 = path.join(testPath.backupBasePath, "202101011630");
|
|
@@ -585,11 +626,15 @@ describe("Backup", function () {
|
|
|
585
626
|
{ name: "202101011630", date: 1 },
|
|
586
627
|
{ name: "202101021630", date: 2 },
|
|
587
628
|
];
|
|
588
|
-
|
|
629
|
+
|
|
589
630
|
when(spyOnsSettingsValue)
|
|
590
|
-
|
|
631
|
+
.calledWith("backupInfo")
|
|
632
|
+
.mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
|
|
591
633
|
|
|
592
|
-
backup.deleteOldBackupSets(
|
|
634
|
+
await backup.deleteOldBackupSets(
|
|
635
|
+
testPath.backupBasePath,
|
|
636
|
+
backupRetention
|
|
637
|
+
);
|
|
593
638
|
|
|
594
639
|
const folderAnz = fs
|
|
595
640
|
.readdirSync(testPath.backupBasePath, { withFileTypes: true })
|
|
@@ -616,11 +661,15 @@ describe("Backup", function () {
|
|
|
616
661
|
{ name: "202101021630", date: 2 },
|
|
617
662
|
{ name: "202101031630", date: 3 },
|
|
618
663
|
];
|
|
619
|
-
|
|
664
|
+
|
|
620
665
|
when(spyOnsSettingsValue)
|
|
621
|
-
|
|
666
|
+
.calledWith("backupInfo")
|
|
667
|
+
.mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
|
|
622
668
|
|
|
623
|
-
backup.deleteOldBackupSets(
|
|
669
|
+
await backup.deleteOldBackupSets(
|
|
670
|
+
testPath.backupBasePath,
|
|
671
|
+
backupRetention
|
|
672
|
+
);
|
|
624
673
|
const folderAnz = fs
|
|
625
674
|
.readdirSync(testPath.backupBasePath, { withFileTypes: true })
|
|
626
675
|
.filter((dirent) => dirent.isDirectory()).length;
|
|
@@ -653,9 +702,10 @@ describe("Backup", function () {
|
|
|
653
702
|
{ name: "202101041630", date: 4 },
|
|
654
703
|
{ name: "202101051630", date: 5 },
|
|
655
704
|
];
|
|
656
|
-
|
|
705
|
+
|
|
657
706
|
when(spyOnsSettingsValue)
|
|
658
|
-
|
|
707
|
+
.calledWith("backupInfo")
|
|
708
|
+
.mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
|
|
659
709
|
|
|
660
710
|
await backup.deleteOldBackupSets(
|
|
661
711
|
testPath.backupBasePath,
|
|
@@ -835,12 +885,14 @@ describe("Backup", function () {
|
|
|
835
885
|
const userstyle = path.join(testPath.joplinProfile, "userstyle.css");
|
|
836
886
|
const userchrome = path.join(testPath.joplinProfile, "userchrome.css");
|
|
837
887
|
const keymap = path.join(testPath.joplinProfile, "keymap-desktop.json");
|
|
888
|
+
const plugin = path.join(testPath.plugins, "test-plugin.jpl");
|
|
838
889
|
|
|
839
890
|
fs.writeFileSync(template, "template");
|
|
840
891
|
fs.writeFileSync(settings, "settings");
|
|
841
892
|
fs.writeFileSync(userstyle, "userstyle");
|
|
842
893
|
fs.writeFileSync(userchrome, "userchrome");
|
|
843
894
|
fs.writeFileSync(keymap, "keymap");
|
|
895
|
+
fs.writeFileSync(plugin, "plugin");
|
|
844
896
|
|
|
845
897
|
fs.emptyDirSync(testPath.activeBackupJob);
|
|
846
898
|
|
|
@@ -870,8 +922,15 @@ describe("Backup", function () {
|
|
|
870
922
|
"profile",
|
|
871
923
|
"keymap-desktop.json"
|
|
872
924
|
);
|
|
925
|
+
const backupPlugin = path.join(
|
|
926
|
+
testPath.activeBackupJob,
|
|
927
|
+
"profile",
|
|
928
|
+
"plugins",
|
|
929
|
+
"test-plugin.jpl"
|
|
930
|
+
);
|
|
873
931
|
|
|
874
932
|
backup.activeBackupPath = testPath.activeBackupJob;
|
|
933
|
+
backup.backupPlugins = true;
|
|
875
934
|
await backup.backupProfileData();
|
|
876
935
|
|
|
877
936
|
expect(fs.existsSync(backupTemplate)).toBe(true);
|
|
@@ -879,6 +938,7 @@ describe("Backup", function () {
|
|
|
879
938
|
expect(fs.existsSync(backupUserstyle)).toBe(true);
|
|
880
939
|
expect(fs.existsSync(backupUserchrome)).toBe(true);
|
|
881
940
|
expect(fs.existsSync(backupKeymap)).toBe(true);
|
|
941
|
+
expect(fs.existsSync(backupPlugin)).toBe(true);
|
|
882
942
|
|
|
883
943
|
expect(backup.log.error).toHaveBeenCalledTimes(0);
|
|
884
944
|
expect(backup.log.warn).toHaveBeenCalledTimes(0);
|
package/__test__/help.test.ts
CHANGED
|
@@ -71,4 +71,79 @@ describe("Test helper", function () {
|
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
+
it(`Compare versions`, async () => {
|
|
75
|
+
const testCases = [
|
|
76
|
+
{
|
|
77
|
+
version1: "2.9.12",
|
|
78
|
+
version2: "2.9.12",
|
|
79
|
+
expected: 0,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
version1: "2.9.12",
|
|
83
|
+
version2: "2.9.13",
|
|
84
|
+
expected: -1,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
version1: "2.9.13",
|
|
88
|
+
version2: "2.9.12",
|
|
89
|
+
expected: 1,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
version1: "2.10.6",
|
|
93
|
+
version2: "2.9.12",
|
|
94
|
+
expected: 1,
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
version1: "3.10.6",
|
|
98
|
+
version2: "2.11.8",
|
|
99
|
+
expected: 1,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
version1: "2.10.6",
|
|
103
|
+
version2: "3.11.8",
|
|
104
|
+
expected: -1,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
version1: "2",
|
|
108
|
+
version2: "2.1",
|
|
109
|
+
expected: -1,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
version1: "2.1",
|
|
113
|
+
version2: "2",
|
|
114
|
+
expected: 1,
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
version1: "3",
|
|
118
|
+
version2: "2",
|
|
119
|
+
expected: 1,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
version1: "2",
|
|
123
|
+
version2: "2",
|
|
124
|
+
expected: 0,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
version1: "3.11.8",
|
|
128
|
+
version2: "3.11.8-a",
|
|
129
|
+
expected: 0,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
version1: "2",
|
|
133
|
+
version2: "",
|
|
134
|
+
expected: -2,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
version1: "3.a.8",
|
|
138
|
+
version2: "3.11.8",
|
|
139
|
+
expected: -1,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
for (const testCase of testCases) {
|
|
144
|
+
expect(
|
|
145
|
+
await helper.versionCompare(testCase.version1, testCase.version2)
|
|
146
|
+
).toBe(testCase.expected);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
74
149
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joplin-plugin-backup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dist": "webpack --joplin-plugin-config buildMain && webpack --joplin-plugin-config buildExtraScripts && webpack --joplin-plugin-config createArchive",
|
|
6
6
|
"prepare": "npm run dist && husky install",
|
|
Binary file
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"id": "io.github.jackgruber.backup",
|
|
4
4
|
"app_min_version": "2.1.3",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.2.0",
|
|
6
6
|
"name": "Simple Backup",
|
|
7
7
|
"description": "Plugin to create manual and automatic backups.",
|
|
8
8
|
"author": "JackGruber",
|
|
@@ -16,6 +16,6 @@
|
|
|
16
16
|
"7zip",
|
|
17
17
|
"encrypted"
|
|
18
18
|
],
|
|
19
|
-
"_publish_hash": "sha256:
|
|
20
|
-
"_publish_commit": "master:
|
|
19
|
+
"_publish_hash": "sha256:5b9605100911c96c157853884f1fb8fc0e4374697f493dcbeb45fd09068e469d",
|
|
20
|
+
"_publish_commit": "master:1a5f98eef052081f501e9bcda2fd1f69f2dbeb04"
|
|
21
21
|
}
|