joplin-plugin-backup 1.0.3 → 1.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/.github/workflows/auto-assign-issue.yml +14 -0
- package/README.md +28 -4
- package/__test__/backup.test.ts +141 -32
- package/__test__/help.test.ts +74 -0
- package/__test__/pw.test.ts +82 -0
- package/__test__/sevenZip.test.ts +3 -1
- package/__test__/sevenZipUpdateBinPath.test.ts +16 -0
- package/package.json +10 -5
- package/publish/io.github.jackgruber.backup.jpl +0 -0
- package/publish/io.github.jackgruber.backup.json +3 -3
- package/CHANGELOG.md +0 -84
package/README.md
CHANGED
|
@@ -33,8 +33,7 @@ Go to `Tools > Options > Backup`
|
|
|
33
33
|
|
|
34
34
|
| Option | Description | Default |
|
|
35
35
|
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- |
|
|
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!
|
|
37
|
-
| `Single JEX` | Create only one JEX file for all notebooks | `false` |
|
|
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 when you disable the `Create Subfolder` settings! | |
|
|
38
37
|
| `Keep x backups` | How many backups should be kept | `1` |
|
|
39
38
|
| `Backups interval in hours` | Create a backup every X hours | `24` |
|
|
40
39
|
| `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 +43,9 @@ Go to `Tools > Options > Backup`
|
|
|
44
43
|
| `Zip compression Level` | Compression level for zip archive archive | `Copy (no compression)` |
|
|
45
44
|
| `Temporary export path` | The data is first exported into this path before it is copied to the backup `Backup path`. | `` |
|
|
46
45
|
| `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
|
+
| `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` |
|
|
47
|
+
| `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
|
+
| `Backup plugins` | Backup the plugin folder from the Joplin profile with all installed plugin jpl files. | `true` |
|
|
47
49
|
|
|
48
50
|
## Keyboard Shortcuts
|
|
49
51
|
|
|
@@ -53,19 +55,20 @@ Under `Options > Keyboard Shortcuts` you can assign a keyboard shortcut for the
|
|
|
53
55
|
|
|
54
56
|
## What is backed up
|
|
55
57
|
|
|
56
|
-
- Notebooks as JEX export (Empty
|
|
58
|
+
- Notebooks as JEX export (Empty notebooks are not backed up)
|
|
57
59
|
- The `settings.json` (Joplin settings)
|
|
58
60
|
- The `keymap-desktop.json` (Keyboard shortcuts)
|
|
59
61
|
- The `userchrome.css` (Your Joplin customization)
|
|
60
62
|
- The `userstyle.css` (Your Joplin customization)
|
|
61
63
|
- The `templates` folder (Note templates)
|
|
64
|
+
- The `plugin` folder (All installed plugins, no plugin settings!)
|
|
62
65
|
|
|
63
66
|
## Restore
|
|
64
67
|
|
|
65
68
|
### Settings
|
|
66
69
|
|
|
67
70
|
To restore the Settings, copy the desired files from `<Backup Path>\Profile` to the Joplin directory `.config\joplin-desktop`.
|
|
68
|
-
The exact path can be found in Joplin under `Tools > Options >
|
|
71
|
+
The exact path can be found in Joplin under `Tools > Options > General`:
|
|
69
72
|
|
|
70
73
|
<img src=img/joplin_path_in_gui.jpg>
|
|
71
74
|
|
|
@@ -78,6 +81,27 @@ The notes are imported via `File > Import > JEX - Joplin Export File`.
|
|
|
78
81
|
The notes are imported additionally, no check for duplicates is performed.
|
|
79
82
|
If the folder in which the note was located already exists in you Joplin, than the folder name is extended by one (1).
|
|
80
83
|
|
|
84
|
+
## FAQ
|
|
85
|
+
|
|
86
|
+
### Internal Joplin links betwen notes are lost
|
|
87
|
+
|
|
88
|
+
If several JEX files are imported and the notes have links to each other, these links will be lost.
|
|
89
|
+
Therefore it is recommended to create a Single JEX Backup!
|
|
90
|
+
|
|
91
|
+
### Compine multiple JEX Files to one
|
|
92
|
+
|
|
93
|
+
By combining the JEX files into one, the Joplin internal links will work again after the import.
|
|
94
|
+
|
|
95
|
+
1. Open one of the JEX files in a ZIP program like 7-Zip
|
|
96
|
+
2. Open a second JEX and add all files to the first JEX
|
|
97
|
+
3. Repeat step 2 for all files
|
|
98
|
+
4. Import first JEX which now contains all notes
|
|
99
|
+
|
|
100
|
+
## Open a JEX Backup file
|
|
101
|
+
|
|
102
|
+
A Joplin JEX Backup file is a tar archive which can be opened with any zip program that supports TAR archive.
|
|
103
|
+
The file names in the archive correspond to the Joplin internal IDs.
|
|
104
|
+
|
|
81
105
|
## Changelog
|
|
82
106
|
|
|
83
107
|
See [CHANGELOG.md](CHANGELOG.md)
|
package/__test__/backup.test.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Backup } from "../src/Backup";
|
|
2
2
|
import * as fs from "fs-extra";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import { joplinWrapper } from "../src/joplinWrapper";
|
|
5
4
|
import { when } from "jest-when";
|
|
6
5
|
import { sevenZip } from "../src/sevenZip";
|
|
6
|
+
import joplin from "api";
|
|
7
7
|
|
|
8
8
|
function getTestPaths(): any {
|
|
9
9
|
const testPath: any = {};
|
|
@@ -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
|
|
|
@@ -24,12 +25,13 @@ let spyOnLogVerbose = null;
|
|
|
24
25
|
let spyOnLogInfo = null;
|
|
25
26
|
let spyOnLogWarn = null;
|
|
26
27
|
let spyOnLogError = null;
|
|
28
|
+
let spyOnShowError = null;
|
|
27
29
|
let spyOnSaveBackupInfo = null;
|
|
28
30
|
|
|
29
|
-
const spyOnsSettingsValue = jest.spyOn(
|
|
30
|
-
const spyOnGlobalValue = jest.spyOn(
|
|
31
|
+
const spyOnsSettingsValue = jest.spyOn(joplin.settings, "value");
|
|
32
|
+
const spyOnGlobalValue = jest.spyOn(joplin.settings, "globalValue");
|
|
31
33
|
const spyOnSettingsSetValue = jest
|
|
32
|
-
.spyOn(
|
|
34
|
+
.spyOn(joplin.settings, "setValue")
|
|
33
35
|
.mockImplementation();
|
|
34
36
|
|
|
35
37
|
async function createTestStructure() {
|
|
@@ -38,6 +40,7 @@ async function createTestStructure() {
|
|
|
38
40
|
fs.emptyDirSync(test.backupBasePath);
|
|
39
41
|
fs.emptyDirSync(test.joplinProfile);
|
|
40
42
|
fs.emptyDirSync(test.templates);
|
|
43
|
+
fs.emptyDirSync(test.plugins);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
const testPath = getTestPaths();
|
|
@@ -73,6 +76,10 @@ describe("Backup", function () {
|
|
|
73
76
|
spyOnLogError = jest
|
|
74
77
|
.spyOn(backup.log, "error")
|
|
75
78
|
.mockImplementation(() => {});
|
|
79
|
+
|
|
80
|
+
spyOnShowError = jest
|
|
81
|
+
.spyOn(backup, "showError")
|
|
82
|
+
.mockImplementation(() => {});
|
|
76
83
|
});
|
|
77
84
|
|
|
78
85
|
afterEach(async () => {
|
|
@@ -80,6 +87,7 @@ describe("Backup", function () {
|
|
|
80
87
|
spyOnLogInfo.mockReset();
|
|
81
88
|
spyOnLogWarn.mockReset();
|
|
82
89
|
spyOnLogError.mockReset();
|
|
90
|
+
spyOnShowError.mockReset();
|
|
83
91
|
spyOnsSettingsValue.mockReset();
|
|
84
92
|
spyOnGlobalValue.mockReset();
|
|
85
93
|
spyOnSaveBackupInfo.mockReset();
|
|
@@ -88,29 +96,72 @@ describe("Backup", function () {
|
|
|
88
96
|
afterAll(async () => {
|
|
89
97
|
fs.removeSync(testPath.base);
|
|
90
98
|
});
|
|
91
|
-
|
|
92
99
|
describe("Backup path", function () {
|
|
93
|
-
it(`
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
it(`Path tests`, async () => {
|
|
101
|
+
const testCases = [
|
|
102
|
+
{
|
|
103
|
+
backupPath: testPath.joplinProfile,
|
|
104
|
+
createSubfolder: false,
|
|
105
|
+
expectedBackupPath: null,
|
|
106
|
+
expectedBackupPathExist: null,
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
backupPath: testPath.joplinProfile,
|
|
110
|
+
createSubfolder: true,
|
|
111
|
+
expectedBackupPath: path.join(testPath.joplinProfile, "JoplinBackup"),
|
|
112
|
+
expectedBackupPathExist: true,
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
backupPath: testPath.backupBasePath,
|
|
116
|
+
createSubfolder: false,
|
|
117
|
+
expectedBackupPath: testPath.backupBasePath,
|
|
118
|
+
expectedBackupPathExist: true,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
backupPath: testPath.backupBasePath,
|
|
122
|
+
createSubfolder: true,
|
|
123
|
+
expectedBackupPath: path.join(
|
|
124
|
+
testPath.backupBasePath,
|
|
125
|
+
"JoplinBackup"
|
|
126
|
+
),
|
|
127
|
+
expectedBackupPathExist: true,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
backupPath: path.join(testPath.backupBasePath, "NotExisting"),
|
|
131
|
+
createSubfolder: false,
|
|
132
|
+
expectedBackupPath: path.join(testPath.backupBasePath, "NotExisting"),
|
|
133
|
+
expectedBackupPathExist: false,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
backupPath: path.join(testPath.backupBasePath, "NotExisting"),
|
|
137
|
+
createSubfolder: true,
|
|
138
|
+
expectedBackupPath: path.join(
|
|
139
|
+
testPath.backupBasePath,
|
|
140
|
+
"NotExisting",
|
|
141
|
+
"JoplinBackup"
|
|
142
|
+
),
|
|
143
|
+
expectedBackupPathExist: false,
|
|
144
|
+
},
|
|
145
|
+
];
|
|
104
146
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
147
|
+
for (const testCase of testCases) {
|
|
148
|
+
when(spyOnsSettingsValue)
|
|
149
|
+
.calledWith("path")
|
|
150
|
+
.mockImplementation(() => Promise.resolve(testCase.backupPath));
|
|
151
|
+
backup.createSubfolder = testCase.createSubfolder;
|
|
152
|
+
await backup.loadBackupPath();
|
|
153
|
+
expect(backup.backupBasePath).toBe(testCase.expectedBackupPath);
|
|
154
|
+
expect(backup.backupBasePath).not.toBe(testPath.joplinProfile);
|
|
155
|
+
|
|
156
|
+
if (testCase.expectedBackupPathExist !== null) {
|
|
157
|
+
expect(fs.existsSync(backup.backupBasePath)).toBe(
|
|
158
|
+
testCase.expectedBackupPathExist
|
|
159
|
+
);
|
|
160
|
+
}
|
|
111
161
|
|
|
112
|
-
|
|
113
|
-
|
|
162
|
+
expect(backup.log.error).toHaveBeenCalledTimes(0);
|
|
163
|
+
expect(backup.log.warn).toHaveBeenCalledTimes(0);
|
|
164
|
+
}
|
|
114
165
|
});
|
|
115
166
|
|
|
116
167
|
it(`relative paths`, async () => {
|
|
@@ -524,6 +575,45 @@ describe("Backup", function () {
|
|
|
524
575
|
});
|
|
525
576
|
|
|
526
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
|
+
|
|
527
617
|
it(`Backups < retention`, async () => {
|
|
528
618
|
const backupRetention = 3;
|
|
529
619
|
const folder1 = path.join(testPath.backupBasePath, "202101011630");
|
|
@@ -536,11 +626,15 @@ describe("Backup", function () {
|
|
|
536
626
|
{ name: "202101011630", date: 1 },
|
|
537
627
|
{ name: "202101021630", date: 2 },
|
|
538
628
|
];
|
|
539
|
-
|
|
629
|
+
|
|
540
630
|
when(spyOnsSettingsValue)
|
|
541
|
-
|
|
631
|
+
.calledWith("backupInfo")
|
|
632
|
+
.mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
|
|
542
633
|
|
|
543
|
-
backup.deleteOldBackupSets(
|
|
634
|
+
await backup.deleteOldBackupSets(
|
|
635
|
+
testPath.backupBasePath,
|
|
636
|
+
backupRetention
|
|
637
|
+
);
|
|
544
638
|
|
|
545
639
|
const folderAnz = fs
|
|
546
640
|
.readdirSync(testPath.backupBasePath, { withFileTypes: true })
|
|
@@ -567,11 +661,15 @@ describe("Backup", function () {
|
|
|
567
661
|
{ name: "202101021630", date: 2 },
|
|
568
662
|
{ name: "202101031630", date: 3 },
|
|
569
663
|
];
|
|
570
|
-
|
|
664
|
+
|
|
571
665
|
when(spyOnsSettingsValue)
|
|
572
|
-
|
|
666
|
+
.calledWith("backupInfo")
|
|
667
|
+
.mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
|
|
573
668
|
|
|
574
|
-
backup.deleteOldBackupSets(
|
|
669
|
+
await backup.deleteOldBackupSets(
|
|
670
|
+
testPath.backupBasePath,
|
|
671
|
+
backupRetention
|
|
672
|
+
);
|
|
575
673
|
const folderAnz = fs
|
|
576
674
|
.readdirSync(testPath.backupBasePath, { withFileTypes: true })
|
|
577
675
|
.filter((dirent) => dirent.isDirectory()).length;
|
|
@@ -604,9 +702,10 @@ describe("Backup", function () {
|
|
|
604
702
|
{ name: "202101041630", date: 4 },
|
|
605
703
|
{ name: "202101051630", date: 5 },
|
|
606
704
|
];
|
|
607
|
-
|
|
705
|
+
|
|
608
706
|
when(spyOnsSettingsValue)
|
|
609
|
-
|
|
707
|
+
.calledWith("backupInfo")
|
|
708
|
+
.mockImplementation(() => Promise.resolve(JSON.stringify(backupInfo)));
|
|
610
709
|
|
|
611
710
|
await backup.deleteOldBackupSets(
|
|
612
711
|
testPath.backupBasePath,
|
|
@@ -786,12 +885,14 @@ describe("Backup", function () {
|
|
|
786
885
|
const userstyle = path.join(testPath.joplinProfile, "userstyle.css");
|
|
787
886
|
const userchrome = path.join(testPath.joplinProfile, "userchrome.css");
|
|
788
887
|
const keymap = path.join(testPath.joplinProfile, "keymap-desktop.json");
|
|
888
|
+
const plugin = path.join(testPath.plugins, "test-plugin.jpl");
|
|
789
889
|
|
|
790
890
|
fs.writeFileSync(template, "template");
|
|
791
891
|
fs.writeFileSync(settings, "settings");
|
|
792
892
|
fs.writeFileSync(userstyle, "userstyle");
|
|
793
893
|
fs.writeFileSync(userchrome, "userchrome");
|
|
794
894
|
fs.writeFileSync(keymap, "keymap");
|
|
895
|
+
fs.writeFileSync(plugin, "plugin");
|
|
795
896
|
|
|
796
897
|
fs.emptyDirSync(testPath.activeBackupJob);
|
|
797
898
|
|
|
@@ -821,8 +922,15 @@ describe("Backup", function () {
|
|
|
821
922
|
"profile",
|
|
822
923
|
"keymap-desktop.json"
|
|
823
924
|
);
|
|
925
|
+
const backupPlugin = path.join(
|
|
926
|
+
testPath.activeBackupJob,
|
|
927
|
+
"profile",
|
|
928
|
+
"plugins",
|
|
929
|
+
"test-plugin.jpl"
|
|
930
|
+
);
|
|
824
931
|
|
|
825
932
|
backup.activeBackupPath = testPath.activeBackupJob;
|
|
933
|
+
backup.backupPlugins = true;
|
|
826
934
|
await backup.backupProfileData();
|
|
827
935
|
|
|
828
936
|
expect(fs.existsSync(backupTemplate)).toBe(true);
|
|
@@ -830,6 +938,7 @@ describe("Backup", function () {
|
|
|
830
938
|
expect(fs.existsSync(backupUserstyle)).toBe(true);
|
|
831
939
|
expect(fs.existsSync(backupUserchrome)).toBe(true);
|
|
832
940
|
expect(fs.existsSync(backupKeymap)).toBe(true);
|
|
941
|
+
expect(fs.existsSync(backupPlugin)).toBe(true);
|
|
833
942
|
|
|
834
943
|
expect(backup.log.error).toHaveBeenCalledTimes(0);
|
|
835
944
|
expect(backup.log.warn).toHaveBeenCalledTimes(0);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { helper } from "../src/helper";
|
|
2
|
+
|
|
3
|
+
describe("Test helper", function () {
|
|
4
|
+
it(`validFileName`, async () => {
|
|
5
|
+
const testCases = [
|
|
6
|
+
{
|
|
7
|
+
fileName: "some test file.txt",
|
|
8
|
+
expected: true,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
fileName: "some ^test file.txt",
|
|
12
|
+
expected: true,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
fileName: "some :test file.txt",
|
|
16
|
+
expected: false,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
fileName: "some \\test file.txt",
|
|
20
|
+
expected: false,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
fileName: "some |test file.txt",
|
|
24
|
+
expected: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
fileName: "some /test file.txt",
|
|
28
|
+
expected: false,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
fileName: "some *test file.txt",
|
|
32
|
+
expected: false,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
fileName: "some ?test file.txt",
|
|
36
|
+
expected: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
fileName: "some <test file.txt",
|
|
40
|
+
expected: false,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
fileName: "some >test file.txt",
|
|
44
|
+
expected: false,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
fileName: "com9.txt",
|
|
48
|
+
expected: false,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
fileName: "nul.txt",
|
|
52
|
+
expected: false,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
fileName: "prn.txt",
|
|
56
|
+
expected: false,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
fileName: "con.txt",
|
|
60
|
+
expected: false,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
fileName: "lpt5.txt",
|
|
64
|
+
expected: false,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const testCase of testCases) {
|
|
69
|
+
expect(await helper.validFileName(testCase.fileName)).toBe(
|
|
70
|
+
testCase.expected
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Backup } from "../src/Backup";
|
|
2
|
+
import joplin from "api";
|
|
3
|
+
import { when } from "jest-when";
|
|
4
|
+
|
|
5
|
+
let backup = null;
|
|
6
|
+
|
|
7
|
+
describe("Password", function () {
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
backup = new Backup() as any;
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it(`Check`, async () => {
|
|
13
|
+
const spyOnsSettingsValue = jest.spyOn(joplin.settings, "value");
|
|
14
|
+
const spyOnsSettingsSetValue = jest.spyOn(joplin.settings, "setValue");
|
|
15
|
+
|
|
16
|
+
const testCases = [
|
|
17
|
+
{
|
|
18
|
+
usePassword: false,
|
|
19
|
+
password: "",
|
|
20
|
+
passwordRepeat: "",
|
|
21
|
+
expected: 0,
|
|
22
|
+
called: 2,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
usePassword: false,
|
|
26
|
+
password: "test",
|
|
27
|
+
passwordRepeat: "test",
|
|
28
|
+
expected: 0,
|
|
29
|
+
called: 2,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
usePassword: false,
|
|
33
|
+
password: "testA",
|
|
34
|
+
passwordRepeat: "testB",
|
|
35
|
+
expected: 0,
|
|
36
|
+
called: 2,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
usePassword: true,
|
|
40
|
+
password: "test",
|
|
41
|
+
passwordRepeat: "test",
|
|
42
|
+
expected: 1,
|
|
43
|
+
called: 0,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
usePassword: true,
|
|
47
|
+
password: "testA",
|
|
48
|
+
passwordRepeat: "testB",
|
|
49
|
+
expected: -1,
|
|
50
|
+
called: 2,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
usePassword: true,
|
|
54
|
+
password: " ",
|
|
55
|
+
passwordRepeat: " ",
|
|
56
|
+
expected: -1,
|
|
57
|
+
called: 2,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
usePassword: true,
|
|
61
|
+
password: "",
|
|
62
|
+
passwordRepeat: " ",
|
|
63
|
+
expected: -1,
|
|
64
|
+
called: 2,
|
|
65
|
+
},
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
for (const testCase of testCases) {
|
|
69
|
+
/* prettier-ignore */
|
|
70
|
+
when(spyOnsSettingsValue)
|
|
71
|
+
.mockImplementation(() => Promise.resolve("no mockImplementation"))
|
|
72
|
+
.calledWith("usePassword").mockImplementation(() => Promise.resolve(testCase.usePassword))
|
|
73
|
+
.calledWith("password").mockImplementation(() => Promise.resolve(testCase.password))
|
|
74
|
+
.calledWith("passwordRepeat").mockImplementation(() => Promise.resolve(testCase.passwordRepeat));
|
|
75
|
+
expect(await backup.checkPassword()).toBe(testCase.expected);
|
|
76
|
+
|
|
77
|
+
await backup.enablePassword();
|
|
78
|
+
expect(spyOnsSettingsSetValue).toBeCalledTimes(testCase.called);
|
|
79
|
+
spyOnsSettingsSetValue.mockReset();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -32,7 +32,9 @@ describe("Test sevenZip", function () {
|
|
|
32
32
|
expect(fs.existsSync(file3)).toBe(true);
|
|
33
33
|
expect(fs.existsSync(zip)).toBe(false);
|
|
34
34
|
|
|
35
|
-
expect(await sevenZip.add(zip, testBaseDir
|
|
35
|
+
expect(await sevenZip.add(zip, path.join(testBaseDir, "*"), "secret")).toBe(
|
|
36
|
+
true
|
|
37
|
+
);
|
|
36
38
|
expect(fs.existsSync(zip)).toBe(true);
|
|
37
39
|
|
|
38
40
|
expect(await sevenZip.passwordProtected(zip)).toBe(true);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { sevenZip, pathTo7zip } from "../src/sevenZip";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import joplin from "api";
|
|
4
|
+
|
|
5
|
+
it(`Set bin from joplin`, async () => {
|
|
6
|
+
const pathBevor = pathTo7zip;
|
|
7
|
+
const pathAdd = "addJoplinPath";
|
|
8
|
+
const pathAfter = path.join(pathAdd, "7zip-bin", pathTo7zip);
|
|
9
|
+
|
|
10
|
+
jest.spyOn(joplin.plugins, "installationDir").mockImplementation(async () => {
|
|
11
|
+
return pathAdd;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await sevenZip.updateBinPath();
|
|
15
|
+
expect(pathTo7zip).toBe(pathAfter);
|
|
16
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "joplin-plugin-backup",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
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",
|
|
7
7
|
"update": "npm install -g generator-joplin && yo joplin --update",
|
|
8
|
+
"release": "npm test && node ./node_modules/joplinplugindevtools/dist/createRelease.js",
|
|
9
|
+
"preRelease": "npm test && node ./node_modules/joplinplugindevtools/dist/createRelease.js --prerelease",
|
|
10
|
+
"gitRelease": "node ./node_modules/joplinplugindevtools/dist/createRelease.js --upload",
|
|
11
|
+
"gitPreRelease": "node ./node_modules/joplinplugindevtools/dist/createRelease.js --upload --prerelease",
|
|
8
12
|
"test": "jest"
|
|
9
13
|
},
|
|
10
14
|
"license": "MIT",
|
|
@@ -14,14 +18,18 @@
|
|
|
14
18
|
"devDependencies": {
|
|
15
19
|
"@types/jest": "^26.0.23",
|
|
16
20
|
"@types/node": "^14.0.14",
|
|
21
|
+
"axios": "^0.21.1",
|
|
17
22
|
"chalk": "^4.1.0",
|
|
18
23
|
"copy-webpack-plugin": "^6.1.0",
|
|
24
|
+
"dotenv": "^10.0.0",
|
|
19
25
|
"fs-extra": "^9.0.1",
|
|
20
26
|
"glob": "^7.1.6",
|
|
21
27
|
"husky": "^6.0.0",
|
|
22
28
|
"jest": "^27.0.4",
|
|
23
29
|
"jest-when": "^3.3.1",
|
|
30
|
+
"joplinplugindevtools": "^1.0.14",
|
|
24
31
|
"lint-staged": "^11.0.0",
|
|
32
|
+
"mime": "^2.5.2",
|
|
25
33
|
"on-build-webpack": "^0.1.0",
|
|
26
34
|
"prettier": "2.3.0",
|
|
27
35
|
"tar": "^6.0.5",
|
|
@@ -56,11 +64,8 @@
|
|
|
56
64
|
"js"
|
|
57
65
|
],
|
|
58
66
|
"moduleNameMapper": {
|
|
59
|
-
"^api$": "<rootDir>/
|
|
67
|
+
"^api$": "<rootDir>/node_modules/joplinplugindevtools/dist/apiMock.js",
|
|
60
68
|
"^api/types$": "<rootDir>/api/types"
|
|
61
|
-
},
|
|
62
|
-
"globals": {
|
|
63
|
-
"joplin": true
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
}
|
|
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.1.1",
|
|
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:616b034ec484e83bf972adab454c9c00d23d9bd0b21574beba7915e42ff7415a",
|
|
20
|
+
"_publish_commit": "master:98221472e60f34026ac739368c2831193f564654"
|
|
21
21
|
}
|
package/CHANGELOG.md
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## not released
|
|
4
|
-
|
|
5
|
-
## v1.0.3 (2021-08-11)
|
|
6
|
-
|
|
7
|
-
- Fix: #19 Backups failed from Joplin version v2.2.5 and higher, due to the removed template function
|
|
8
|
-
|
|
9
|
-
## v1.0.2 (2021-07-19)
|
|
10
|
-
|
|
11
|
-
- Improved: Use of moments token
|
|
12
|
-
- Fix: #16 Prevent multiple simultaneous backup runs
|
|
13
|
-
- Add: #11 Make zip compression level selectable
|
|
14
|
-
- Fix: Delete old backup set information, if the backup set no longer exists
|
|
15
|
-
|
|
16
|
-
## v1.0.1 (2021-07-03)
|
|
17
|
-
|
|
18
|
-
Release for Joplin plugin manager
|
|
19
|
-
|
|
20
|
-
## v1.0.0 [pre-release] (2021-06-20)
|
|
21
|
-
|
|
22
|
-
- Add: Option for encrypted backups
|
|
23
|
-
|
|
24
|
-
> ❗️ Requires at least Joplin `2.1.3` ❗️
|
|
25
|
-
|
|
26
|
-
## v0.9.0 [pre-release] (2021-06-19)
|
|
27
|
-
|
|
28
|
-
- Add: Relative path could be used for `Backup Path`
|
|
29
|
-
- Fix: An error with `Only on change`, which was not working properly
|
|
30
|
-
- Add: Option to create zip archive
|
|
31
|
-
- Add: Option to specify the `Backup set name` if multiple backups are to be keep.
|
|
32
|
-
|
|
33
|
-
## v0.5.3 (2021-04-03)
|
|
34
|
-
|
|
35
|
-
- Add: Backup settings.json
|
|
36
|
-
- Optimize: #7 Better error message when a backup is created twice in a row in the same minute
|
|
37
|
-
|
|
38
|
-
## v0.5.2 (2021-02-13)
|
|
39
|
-
|
|
40
|
-
- Only internal changes
|
|
41
|
-
|
|
42
|
-
## v0.5.1 (2021-02-07)
|
|
43
|
-
|
|
44
|
-
- Fix: Incomplete backup.log with only one backup set
|
|
45
|
-
|
|
46
|
-
## v0.5.0 (2021-02-07)
|
|
47
|
-
|
|
48
|
-
- Add: Option for Backuplogfile
|
|
49
|
-
- Add: Option to create a backup only if something has changed #3
|
|
50
|
-
|
|
51
|
-
## v0.4.1 (2021-01-23)
|
|
52
|
-
|
|
53
|
-
- Optimize: Store profile data in `profile` folder
|
|
54
|
-
|
|
55
|
-
## v0.4.0 (2021-01-21)
|
|
56
|
-
|
|
57
|
-
- Add `templates` to backup
|
|
58
|
-
- Optimize: Delete old backups at the end of the backup job
|
|
59
|
-
|
|
60
|
-
## v0.3.1 (2021-01-21)
|
|
61
|
-
|
|
62
|
-
- Fix #1: Unsupported characters in filename
|
|
63
|
-
|
|
64
|
-
## v0.3.0 (2021-01-17)
|
|
65
|
-
|
|
66
|
-
- Fix: Backup not only the last 50 notebooks
|
|
67
|
-
- Add: Backup userchrome.css and userstyle.css
|
|
68
|
-
- Add: Option to create single file JEX for all notebooks
|
|
69
|
-
|
|
70
|
-
## v0.2.2 (2021-01-17)
|
|
71
|
-
|
|
72
|
-
- Fix: Check if keymap-desktop.json exists
|
|
73
|
-
|
|
74
|
-
## v0.2.1 (2021-01-16)
|
|
75
|
-
|
|
76
|
-
- remove seconds from folder
|
|
77
|
-
|
|
78
|
-
## v0.2.0 (2021-01-14)
|
|
79
|
-
|
|
80
|
-
- Add: Automatic backups every X hours
|
|
81
|
-
|
|
82
|
-
## v0.1.0 (2021-01-14)
|
|
83
|
-
|
|
84
|
-
- First version
|