@nocobase/plugin-backups 2.1.17 → 2.1.19
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/dist/externalVersion.js +11 -11
- package/dist/node_modules/archiver/package.json +1 -1
- package/dist/node_modules/cron-parser/package.json +1 -1
- package/dist/node_modules/fs-extra/package.json +1 -1
- package/dist/node_modules/semver/package.json +1 -1
- package/dist/node_modules/yauzl/package.json +1 -1
- package/dist/server/adapters/database.js +151 -70
- package/dist/server/managers/backup.d.ts +6 -2
- package/dist/server/managers/backup.js +7 -1
- package/package.json +2 -2
package/dist/externalVersion.js
CHANGED
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
module.exports = {
|
|
11
|
-
"@nocobase/client": "2.1.
|
|
11
|
+
"@nocobase/client": "2.1.19",
|
|
12
12
|
"react": "18.2.0",
|
|
13
|
-
"@nocobase/flow-engine": "2.1.
|
|
14
|
-
"@nocobase/client-v2": "2.1.
|
|
15
|
-
"@nocobase/cache": "2.1.
|
|
16
|
-
"@nocobase/lock-manager": "2.1.
|
|
17
|
-
"@nocobase/server": "2.1.
|
|
13
|
+
"@nocobase/flow-engine": "2.1.19",
|
|
14
|
+
"@nocobase/client-v2": "2.1.19",
|
|
15
|
+
"@nocobase/cache": "2.1.19",
|
|
16
|
+
"@nocobase/lock-manager": "2.1.19",
|
|
17
|
+
"@nocobase/server": "2.1.19",
|
|
18
18
|
"lodash": "4.18.1",
|
|
19
|
-
"@nocobase/database": "2.1.
|
|
19
|
+
"@nocobase/database": "2.1.19",
|
|
20
20
|
"sequelize": "6.35.2",
|
|
21
|
-
"@nocobase/test": "2.1.
|
|
21
|
+
"@nocobase/test": "2.1.19",
|
|
22
22
|
"@formily/antd-v5": "1.2.3",
|
|
23
23
|
"@formily/react": "2.3.7",
|
|
24
24
|
"@formily/reactive": "2.3.7",
|
|
@@ -27,7 +27,7 @@ module.exports = {
|
|
|
27
27
|
"@ant-design/icons": "5.6.1",
|
|
28
28
|
"ahooks": "3.7.8",
|
|
29
29
|
"dayjs": "1.11.13",
|
|
30
|
-
"@nocobase/plugin-file-manager": "2.1.
|
|
31
|
-
"@nocobase/resourcer": "2.1.
|
|
32
|
-
"@nocobase/utils": "2.1.
|
|
30
|
+
"@nocobase/plugin-file-manager": "2.1.19",
|
|
31
|
+
"@nocobase/resourcer": "2.1.19",
|
|
32
|
+
"@nocobase/utils": "2.1.19"
|
|
33
33
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"archiver","version":"5.3.2","description":"a streaming interface for archive generation","homepage":"https://github.com/archiverjs/node-archiver","author":{"name":"Chris Talkington","url":"http://christalkington.com/"},"repository":{"type":"git","url":"https://github.com/archiverjs/node-archiver.git"},"bugs":{"url":"https://github.com/archiverjs/node-archiver/issues"},"license":"MIT","main":"index.js","files":["index.js","lib"],"engines":{"node":">= 10"},"scripts":{"test":"mocha --reporter dot","bench":"node benchmark/simple/pack-zip.js"},"dependencies":{"archiver-utils":"^2.1.0","async":"^3.2.4","buffer-crc32":"^0.2.1","readable-stream":"^3.6.0","readdir-glob":"^1.1.2","tar-stream":"^2.2.0","zip-stream":"^4.1.0"},"devDependencies":{"archiver-jsdoc-theme":"^1.1.3","chai":"^4.3.7","jsdoc":"^3.6.4","mkdirp":"^2.1.5","mocha":"^9.0.2","rimraf":"^4.3.1","stream-bench":"^0.1.2","tar":"^6.1.13","yauzl":"^2.9.0"},"keywords":["archive","archiver","stream","zip","tar"],"publishConfig":{"registry":"https://registry.npmjs.org/"},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"archiver","version":"5.3.2","description":"a streaming interface for archive generation","homepage":"https://github.com/archiverjs/node-archiver","author":{"name":"Chris Talkington","url":"http://christalkington.com/"},"repository":{"type":"git","url":"https://github.com/archiverjs/node-archiver.git"},"bugs":{"url":"https://github.com/archiverjs/node-archiver/issues"},"license":"MIT","main":"index.js","files":["index.js","lib"],"engines":{"node":">= 10"},"scripts":{"test":"mocha --reporter dot","bench":"node benchmark/simple/pack-zip.js"},"dependencies":{"archiver-utils":"^2.1.0","async":"^3.2.4","buffer-crc32":"^0.2.1","readable-stream":"^3.6.0","readdir-glob":"^1.1.2","tar-stream":"^2.2.0","zip-stream":"^4.1.0"},"devDependencies":{"archiver-jsdoc-theme":"^1.1.3","chai":"^4.3.7","jsdoc":"^3.6.4","mkdirp":"^2.1.5","mocha":"^9.0.2","rimraf":"^4.3.1","stream-bench":"^0.1.2","tar":"^6.1.13","yauzl":"^2.9.0"},"keywords":["archive","archiver","stream","zip","tar"],"publishConfig":{"registry":"https://registry.npmjs.org/"},"_lastModified":"2026-07-03T14:32:49.510Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"cron-parser","version":"4.4.0","description":"Node.js library for parsing crontab instructions","main":"lib/parser.js","types":"index.d.ts","typesVersions":{"<4.1":{"*":["types/ts3/*"]}},"directories":{"test":"test"},"scripts":{"test:tsd":"tsd","test:unit":"TZ=UTC tap ./test/*.js","test:cover":"TZ=UTC tap --coverage-report=html ./test/*.js","lint":"eslint .","lint:fix":"eslint --fix .","test":"npm run lint && npm run test:unit && npm run test:tsd"},"repository":{"type":"git","url":"https://github.com/harrisiirak/cron-parser.git"},"keywords":["cron","crontab","parser"],"author":"Harri Siirak","contributors":["Nicholas Clawson","Daniel Prentis <daniel@salsitasoft.com>","Renault John Lecoultre","Richard Astbury <richard.astbury@gmail.com>","Meaglin Wasabi <Meaglin.wasabi@gmail.com>","Mike Kusold <hello@mikekusold.com>","Alex Kit <alex.kit@atmajs.com>","Santiago Gimeno <santiago.gimeno@gmail.com>","Daniel <darc.tec@gmail.com>","Christian Steininger <christian.steininger.cs@gmail.com>","Mykola Piskovyi <m.piskovyi@gmail.com>","Brian Vaughn <brian.david.vaughn@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Yasuhiroki <yasuhiroki.duck@gmail.com>","Nicholas Clawson <nickclaw@gmail.com>","Brendan Warkentin <faazshift@gmail.com>","Charlie Fish <fishcharlie.code@gmail.com>","Ian Graves <ian+diskimage@iangrav.es>","Andy Thompson <me@andytson.com>","Regev Brody <regevbr@gmail.com>"],"license":"MIT","dependencies":{"luxon":"^1.28.0"},"devDependencies":{"eslint":"^8.2.0","sinon":"^10.0.0","tap":"^16.0.1","tsd":"^0.19.0"},"engines":{"node":">=0.8"},"browser":{"fs":false},"tap":{"check-coverage":false},"tsd":{"directory":"test","compilerOptions":{"lib":["es2017","dom"]}},"_lastModified":"2026-07-03T14:32:48.867Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"fs-extra","version":"9.1.0","description":"fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as recursive mkdir, copy, and remove.","engines":{"node":">=10"},"homepage":"https://github.com/jprichardson/node-fs-extra","repository":{"type":"git","url":"https://github.com/jprichardson/node-fs-extra"},"keywords":["fs","file","file system","copy","directory","extra","mkdirp","mkdir","mkdirs","recursive","json","read","write","extra","delete","remove","touch","create","text","output","move","promise"],"author":"JP Richardson <jprichardson@gmail.com>","license":"MIT","dependencies":{"at-least-node":"^1.0.0","graceful-fs":"^4.2.0","jsonfile":"^6.0.1","universalify":"^2.0.0"},"devDependencies":{"coveralls":"^3.0.0","klaw":"^2.1.1","klaw-sync":"^3.0.2","minimist":"^1.1.1","mocha":"^5.0.5","nyc":"^15.0.0","proxyquire":"^2.0.1","read-dir-files":"^0.1.1","standard":"^14.1.0"},"main":"./lib/index.js","files":["lib/","!lib/**/__tests__/"],"scripts":{"full-ci":"npm run lint && npm run coverage","coverage":"nyc -r lcovonly npm run unit","coveralls":"coveralls < coverage/lcov.info","lint":"standard","test-find":"find ./lib/**/__tests__ -name *.test.js | xargs mocha","test":"npm run lint && npm run unit","unit":"node test.js"},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"fs-extra","version":"9.1.0","description":"fs-extra contains methods that aren't included in the vanilla Node.js fs package. Such as recursive mkdir, copy, and remove.","engines":{"node":">=10"},"homepage":"https://github.com/jprichardson/node-fs-extra","repository":{"type":"git","url":"https://github.com/jprichardson/node-fs-extra"},"keywords":["fs","file","file system","copy","directory","extra","mkdirp","mkdir","mkdirs","recursive","json","read","write","extra","delete","remove","touch","create","text","output","move","promise"],"author":"JP Richardson <jprichardson@gmail.com>","license":"MIT","dependencies":{"at-least-node":"^1.0.0","graceful-fs":"^4.2.0","jsonfile":"^6.0.1","universalify":"^2.0.0"},"devDependencies":{"coveralls":"^3.0.0","klaw":"^2.1.1","klaw-sync":"^3.0.2","minimist":"^1.1.1","mocha":"^5.0.5","nyc":"^15.0.0","proxyquire":"^2.0.1","read-dir-files":"^0.1.1","standard":"^14.1.0"},"main":"./lib/index.js","files":["lib/","!lib/**/__tests__/"],"scripts":{"full-ci":"npm run lint && npm run coverage","coverage":"nyc -r lcovonly npm run unit","coveralls":"coveralls < coverage/lcov.info","lint":"standard","test-find":"find ./lib/**/__tests__ -name *.test.js | xargs mocha","test":"npm run lint && npm run unit","unit":"node test.js"},"_lastModified":"2026-07-03T14:32:49.891Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"semver","version":"7.6.0","description":"The semantic version parser used by npm.","main":"index.js","scripts":{"test":"tap","snap":"tap","lint":"eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"","postlint":"template-oss-check","lintfix":"npm run lint -- --fix","posttest":"npm run lint","template-oss-apply":"template-oss-apply --force"},"devDependencies":{"@npmcli/eslint-config":"^4.0.0","@npmcli/template-oss":"4.21.3","tap":"^16.0.0"},"license":"ISC","repository":{"type":"git","url":"https://github.com/npm/node-semver.git"},"bin":{"semver":"bin/semver.js"},"files":["bin/","lib/","classes/","functions/","internal/","ranges/","index.js","preload.js","range.bnf"],"tap":{"timeout":30,"coverage-map":"map.js","nyc-arg":["--exclude","tap-snapshots/**"]},"engines":{"node":">=10"},"dependencies":{"lru-cache":"^6.0.0"},"author":"GitHub Inc.","templateOSS":{"//@npmcli/template-oss":"This file is partially managed by @npmcli/template-oss. Edits may be overwritten.","version":"4.21.3","engines":">=10","distPaths":["classes/","functions/","internal/","ranges/","index.js","preload.js","range.bnf"],"allowPaths":["/classes/","/functions/","/internal/","/ranges/","/index.js","/preload.js","/range.bnf"],"publish":"true"},"_lastModified":"2026-07-
|
|
1
|
+
{"name":"semver","version":"7.6.0","description":"The semantic version parser used by npm.","main":"index.js","scripts":{"test":"tap","snap":"tap","lint":"eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"","postlint":"template-oss-check","lintfix":"npm run lint -- --fix","posttest":"npm run lint","template-oss-apply":"template-oss-apply --force"},"devDependencies":{"@npmcli/eslint-config":"^4.0.0","@npmcli/template-oss":"4.21.3","tap":"^16.0.0"},"license":"ISC","repository":{"type":"git","url":"https://github.com/npm/node-semver.git"},"bin":{"semver":"bin/semver.js"},"files":["bin/","lib/","classes/","functions/","internal/","ranges/","index.js","preload.js","range.bnf"],"tap":{"timeout":30,"coverage-map":"map.js","nyc-arg":["--exclude","tap-snapshots/**"]},"engines":{"node":">=10"},"dependencies":{"lru-cache":"^6.0.0"},"author":"GitHub Inc.","templateOSS":{"//@npmcli/template-oss":"This file is partially managed by @npmcli/template-oss. Edits may be overwritten.","version":"4.21.3","engines":">=10","distPaths":["classes/","functions/","internal/","ranges/","index.js","preload.js","range.bnf"],"allowPaths":["/classes/","/functions/","/internal/","/ranges/","/index.js","/preload.js","/range.bnf"],"publish":"true"},"_lastModified":"2026-07-03T14:32:50.078Z"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"yauzl","version":"3.3.0","description":"yet another unzip library for node","engines":{"node":">=12"},"main":"index.js","scripts":{"test":"node test/test.js"},"repository":{"type":"git","url":"git+https://github.com/thejoshwolfe/yauzl.git"},"keywords":["unzip","zip","stream","archive","file"],"author":"Josh Wolfe <thejoshwolfe@gmail.com>","license":"MIT","bugs":{"url":"https://github.com/thejoshwolfe/yauzl/issues"},"homepage":"https://github.com/thejoshwolfe/yauzl","dependencies":{"buffer-crc32":"~0.2.3","pend":"~1.2.0"},"devDependencies":{"bl":"^6.0.11"},"files":["fd-slicer.js","index.js"],"_lastModified":"2026-07-
|
|
1
|
+
{"name":"yauzl","version":"3.3.0","description":"yet another unzip library for node","engines":{"node":">=12"},"main":"index.js","scripts":{"test":"node test/test.js"},"repository":{"type":"git","url":"git+https://github.com/thejoshwolfe/yauzl.git"},"keywords":["unzip","zip","stream","archive","file"],"author":"Josh Wolfe <thejoshwolfe@gmail.com>","license":"MIT","bugs":{"url":"https://github.com/thejoshwolfe/yauzl/issues"},"homepage":"https://github.com/thejoshwolfe/yauzl","dependencies":{"buffer-crc32":"~0.2.3","pend":"~1.2.0"},"devDependencies":{"bl":"^6.0.11"},"files":["fd-slicer.js","index.js"],"_lastModified":"2026-07-03T14:32:49.653Z"}
|
|
@@ -48,7 +48,6 @@ var import_promises = require("stream/promises");
|
|
|
48
48
|
var import_util = require("util");
|
|
49
49
|
var import_utils = require("../utils");
|
|
50
50
|
const exec = (0, import_util.promisify)(import_child_process.exec);
|
|
51
|
-
const D$$ = import_os.default.platform() === "win32" ? "$$" : "\\$\\$";
|
|
52
51
|
const STREAM_BUFFER_SIZE = 2 * 1024 * 1024;
|
|
53
52
|
const run = async (command, envVars = {}) => {
|
|
54
53
|
try {
|
|
@@ -65,16 +64,45 @@ const run = async (command, envVars = {}) => {
|
|
|
65
64
|
throw new Error(`${error.message}`);
|
|
66
65
|
}
|
|
67
66
|
};
|
|
67
|
+
const runSpawn = async (command, args, envVars = {}) => {
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const child = (0, import_child_process.spawn)(command, args, {
|
|
70
|
+
env: { ...process.env, ...envVars },
|
|
71
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
72
|
+
});
|
|
73
|
+
const stdout = [];
|
|
74
|
+
const stderr = [];
|
|
75
|
+
let settled = false;
|
|
76
|
+
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
|
77
|
+
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
|
78
|
+
child.on("error", (error) => {
|
|
79
|
+
settled = true;
|
|
80
|
+
reject(error);
|
|
81
|
+
});
|
|
82
|
+
child.on("close", (code) => {
|
|
83
|
+
if (settled) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (code === 0) {
|
|
87
|
+
settled = true;
|
|
88
|
+
resolve(Buffer.concat(stdout).toString());
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const errorMessage = Buffer.concat(stderr).toString().trim() || `${command} exited with code ${code}`;
|
|
92
|
+
settled = true;
|
|
93
|
+
reject(new Error(errorMessage));
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
};
|
|
68
97
|
const formatPathInEnv = (path2) => {
|
|
69
|
-
if (path2
|
|
70
|
-
return
|
|
98
|
+
if ((path2 == null ? void 0 : path2.startsWith('"')) && path2.endsWith('"') || (path2 == null ? void 0 : path2.startsWith("'")) && path2.endsWith("'")) {
|
|
99
|
+
return path2.slice(1, -1);
|
|
71
100
|
}
|
|
72
101
|
return path2;
|
|
73
102
|
};
|
|
74
103
|
const escapeStringLiteral = (value) => String(value).replace(/'/g, "''");
|
|
75
104
|
const quotePgIdentifier = (value) => `"${String(value).replace(/"/g, '""')}"`;
|
|
76
|
-
const
|
|
77
|
-
const quotePgTablePattern = (table) => quoteShellArg(String(table).split(".").map(quotePgIdentifier).join("."));
|
|
105
|
+
const quotePgTablePattern = (table) => String(table).split(".").map(quotePgIdentifier).join(".");
|
|
78
106
|
const isPgRestoreSchemaTocEntry = (line) => /^\d+;\s+\d+\s+\d+\s+SCHEMA\s+-\s+/.test(line);
|
|
79
107
|
const parsePgTableReference = (table, defaultSchema) => {
|
|
80
108
|
const parts = String(table).split(".");
|
|
@@ -110,21 +138,16 @@ class BaseDBAdapter {
|
|
|
110
138
|
async clientVersion(_) {
|
|
111
139
|
}
|
|
112
140
|
assertCommand = (command) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
} catch (error) {
|
|
141
|
+
const result = (0, import_child_process.spawnSync)(command, ["--version"], { encoding: "utf8" });
|
|
142
|
+
if (result.error || result.status !== 0) {
|
|
116
143
|
throw new Error(
|
|
117
144
|
`Command ${command} not found, please install it first. Check reference here: https://docs.nocobase.com/ops-management/backup-manager/`
|
|
118
145
|
);
|
|
119
146
|
}
|
|
120
147
|
};
|
|
121
148
|
hasCommand(command) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return true;
|
|
125
|
-
} catch (_error) {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
149
|
+
const result = (0, import_child_process.spawnSync)(command, ["--version"], { encoding: "utf8" });
|
|
150
|
+
return !result.error && result.status === 0;
|
|
128
151
|
}
|
|
129
152
|
}
|
|
130
153
|
class MySQLAdapter extends BaseDBAdapter {
|
|
@@ -426,27 +449,40 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
426
449
|
async clientVersion(op) {
|
|
427
450
|
const cmd = op === "backup" ? this.getBackupCommandName() : this.getRestoreCommandName();
|
|
428
451
|
try {
|
|
429
|
-
return
|
|
452
|
+
return await runSpawn(cmd, ["--version"]);
|
|
430
453
|
} catch (_error) {
|
|
431
454
|
return void 0;
|
|
432
455
|
}
|
|
433
456
|
}
|
|
434
457
|
async backup({ dir, includeTables, excludeTables }) {
|
|
435
|
-
const {
|
|
458
|
+
const { database, password } = this.dbOpts;
|
|
436
459
|
const filePath = `${dir}/data`;
|
|
437
460
|
const backupSchema = this.getBackupSchema();
|
|
438
|
-
const schemaOption = backupSchema ? `--schema=${backupSchema}` : "";
|
|
439
461
|
const expandedExcludeTables = Array.isArray(excludeTables) ? [.../* @__PURE__ */ new Set([...excludeTables, ...await this.getOwnedSequenceTables(excludeTables, backupSchema)])] : [];
|
|
440
|
-
const
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
462
|
+
const includeArgs = Array.isArray(includeTables) && includeTables.length ? includeTables.flatMap((table) => ["-t", quotePgTablePattern(qualifyPgTablePattern(table, backupSchema))]) : [];
|
|
463
|
+
const excludeArgs = expandedExcludeTables.length ? expandedExcludeTables.flatMap((table) => [
|
|
464
|
+
"-T",
|
|
465
|
+
quotePgTablePattern(qualifyPgTablePattern(table, backupSchema))
|
|
466
|
+
]) : [];
|
|
467
|
+
const args = [
|
|
468
|
+
...includeArgs,
|
|
469
|
+
...excludeArgs,
|
|
470
|
+
...this.getConnectionArgs(false),
|
|
471
|
+
"-F",
|
|
472
|
+
"c",
|
|
473
|
+
"-b",
|
|
474
|
+
"--quote-all-identifiers",
|
|
475
|
+
...backupSchema ? [`--schema=${backupSchema}`] : [],
|
|
476
|
+
"-f",
|
|
477
|
+
filePath,
|
|
478
|
+
database
|
|
479
|
+
];
|
|
480
|
+
await runSpawn(this.getBackupCommandName(), args, this.getPasswordEnvVars(password));
|
|
444
481
|
}
|
|
445
482
|
async getOwnedSequenceTables(excludeTables, backupSchema) {
|
|
446
483
|
if (!Array.isArray(excludeTables) || !excludeTables.length) {
|
|
447
484
|
return [];
|
|
448
485
|
}
|
|
449
|
-
const { username, host, port, database, password } = this.dbOpts;
|
|
450
486
|
const tableRefs = excludeTables.map((table) => parsePgTableReference(table, backupSchema)).filter((ref) => ref.table);
|
|
451
487
|
if (!tableRefs.length) {
|
|
452
488
|
return [];
|
|
@@ -468,10 +504,32 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
468
504
|
AND dep.deptype IN ('a', 'i')
|
|
469
505
|
AND (excluded.schema_name IS NULL OR tbl_ns.nspname = excluded.schema_name)
|
|
470
506
|
`;
|
|
471
|
-
const
|
|
472
|
-
const output = String(await run(command, this.getPasswordEnvVars(password)));
|
|
507
|
+
const output = String(await this.runSql(query, ["-At"]));
|
|
473
508
|
return output.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
474
509
|
}
|
|
510
|
+
getConnectionArgs(includeDatabase = true) {
|
|
511
|
+
const { username, host, port, database } = this.dbOpts;
|
|
512
|
+
return [
|
|
513
|
+
"-U",
|
|
514
|
+
username,
|
|
515
|
+
"-h",
|
|
516
|
+
host,
|
|
517
|
+
...port ? ["-p", String(port)] : [],
|
|
518
|
+
...includeDatabase ? ["-d", database] : []
|
|
519
|
+
];
|
|
520
|
+
}
|
|
521
|
+
runSql(query, extraArgs = [], toolchain = this.backupToolchain) {
|
|
522
|
+
const { password } = this.dbOpts;
|
|
523
|
+
return runSpawn(
|
|
524
|
+
this.getSqlCommandName(toolchain),
|
|
525
|
+
[...this.getConnectionArgs(), ...extraArgs, "-c", query],
|
|
526
|
+
this.getPasswordEnvVars(password, toolchain)
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
runRestore(args, toolchain = this.backupToolchain) {
|
|
530
|
+
const { password } = this.dbOpts;
|
|
531
|
+
return runSpawn(this.getRestoreCommandName(toolchain), args, this.getPasswordEnvVars(password, toolchain));
|
|
532
|
+
}
|
|
475
533
|
async restore({
|
|
476
534
|
filePath,
|
|
477
535
|
schema,
|
|
@@ -479,7 +537,6 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
479
537
|
restoreMode,
|
|
480
538
|
toolchain = this.backupToolchain
|
|
481
539
|
}) {
|
|
482
|
-
const { username, host, port, database, password } = this.dbOpts;
|
|
483
540
|
let schemaOption = this.dbOpts.schema;
|
|
484
541
|
if (schema && !schemaOption) {
|
|
485
542
|
schemaOption = "public";
|
|
@@ -495,8 +552,8 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
495
552
|
const schemaNameCondition = schemaOption ? `WHERE schemaname = '${schemaOption}'` : `WHERE schemaname NOT IN ('pg_catalog', 'information_schema')`;
|
|
496
553
|
const relnamespaceCondition = schemaOption ? `WHERE relnamespace = '${schemaOption}'::regnamespace` : `WHERE tgrelid IN (SELECT oid FROM pg_class WHERE relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname IN ('pg_catalog', 'information_schema')))`;
|
|
497
554
|
if (restoreMode === "preserveTables") {
|
|
498
|
-
const
|
|
499
|
-
DO
|
|
555
|
+
const dropViewsQuery = `
|
|
556
|
+
DO $$ DECLARE r RECORD;
|
|
500
557
|
BEGIN
|
|
501
558
|
FOR r IN (
|
|
502
559
|
SELECT schemaname, viewname, false AS materialized FROM pg_views ${schemaNameCondition}
|
|
@@ -513,11 +570,11 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
513
570
|
WHEN OTHERS THEN
|
|
514
571
|
END;
|
|
515
572
|
END LOOP;
|
|
516
|
-
END
|
|
517
|
-
await
|
|
573
|
+
END $$;`;
|
|
574
|
+
await this.runSql(dropViewsQuery, [], toolchain);
|
|
518
575
|
} else if (!skipDropAllTables) {
|
|
519
|
-
const
|
|
520
|
-
DO
|
|
576
|
+
const dropDataQuery = `
|
|
577
|
+
DO $$ DECLARE r RECORD;
|
|
521
578
|
BEGIN
|
|
522
579
|
FOR r IN (SELECT viewname,schemaname FROM pg_views ${schemaNameCondition}) LOOP
|
|
523
580
|
BEGIN
|
|
@@ -555,14 +612,23 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
555
612
|
END;
|
|
556
613
|
END LOOP;
|
|
557
614
|
|
|
558
|
-
END
|
|
559
|
-
await
|
|
615
|
+
END $$;`;
|
|
616
|
+
await this.runSql(dropDataQuery, [], toolchain);
|
|
560
617
|
}
|
|
561
618
|
if (schema === schemaOption || !schemaOption) {
|
|
562
619
|
const restoreList = restoreMode === "preserveTables" ? await this.createRestoreListWithoutSchema(filePath, toolchain) : void 0;
|
|
563
|
-
const
|
|
620
|
+
const restoreArgs = [
|
|
621
|
+
...this.getConnectionArgs(),
|
|
622
|
+
"--clean",
|
|
623
|
+
"--if-exists",
|
|
624
|
+
"--no-owner",
|
|
625
|
+
"-j",
|
|
626
|
+
String(j),
|
|
627
|
+
...restoreList ? ["-L", restoreList.filePath] : [],
|
|
628
|
+
filePath
|
|
629
|
+
];
|
|
564
630
|
try {
|
|
565
|
-
await
|
|
631
|
+
await this.runRestore(restoreArgs, toolchain);
|
|
566
632
|
} finally {
|
|
567
633
|
if (restoreList) {
|
|
568
634
|
await fsPromises.unlink(restoreList.filePath).catch(() => {
|
|
@@ -571,12 +637,12 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
571
637
|
}
|
|
572
638
|
} else {
|
|
573
639
|
const srcSchema = schema || "public";
|
|
574
|
-
const
|
|
575
|
-
await this.restoreSchema(srcSchema, schemaOption,
|
|
640
|
+
const pgRestoreArgs = this.buildSchemaRestoreArgs(srcSchema, schemaOption, filePath, j, toolchain);
|
|
641
|
+
await this.restoreSchema(srcSchema, schemaOption, pgRestoreArgs, toolchain);
|
|
576
642
|
}
|
|
577
643
|
}
|
|
578
644
|
async createRestoreListWithoutSchema(filePath, toolchain = this.backupToolchain) {
|
|
579
|
-
const listOutput = String(await
|
|
645
|
+
const listOutput = String(await this.runRestore(["--list", filePath], toolchain));
|
|
580
646
|
const filteredList = listOutput.split("\n").filter((line) => !isPgRestoreSchemaTocEntry(line)).join("\n");
|
|
581
647
|
const listFilePath = import_path.default.join(
|
|
582
648
|
import_os.default.tmpdir(),
|
|
@@ -584,51 +650,57 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
584
650
|
);
|
|
585
651
|
await fsPromises.writeFile(listFilePath, filteredList);
|
|
586
652
|
return {
|
|
587
|
-
option: `-L ${quoteShellArg(listFilePath)}`,
|
|
588
653
|
filePath: listFilePath
|
|
589
654
|
};
|
|
590
655
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
656
|
+
buildSchemaRestoreArgs(srcSchema, _targetSchema, filePath, jobs, _toolchain = this.backupToolchain) {
|
|
657
|
+
return [
|
|
658
|
+
...this.getConnectionArgs(),
|
|
659
|
+
"-n",
|
|
660
|
+
srcSchema,
|
|
661
|
+
"--clean",
|
|
662
|
+
"--if-exists",
|
|
663
|
+
"--no-owner",
|
|
664
|
+
"-j",
|
|
665
|
+
String(jobs),
|
|
666
|
+
filePath
|
|
667
|
+
];
|
|
594
668
|
}
|
|
595
|
-
async restoreSchema(srcSchema, targetSchema,
|
|
596
|
-
const { username, host, port, database, password } = this.dbOpts;
|
|
669
|
+
async restoreSchema(srcSchema, targetSchema, pgRestoreArgs, toolchain = this.backupToolchain) {
|
|
597
670
|
const ts = Date.now();
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
DO ${D$$}
|
|
671
|
+
const preQuery = `
|
|
672
|
+
DO $$
|
|
601
673
|
BEGIN
|
|
602
674
|
IF EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = '${srcSchema}') THEN
|
|
603
675
|
EXECUTE 'ALTER SCHEMA ${srcSchema} RENAME TO ${srcSchema}_${ts}';
|
|
604
676
|
END IF;
|
|
605
677
|
EXECUTE 'CREATE SCHEMA ${srcSchema}';
|
|
606
|
-
END
|
|
607
|
-
const
|
|
608
|
-
DO
|
|
678
|
+
END $$;`;
|
|
679
|
+
const postQuery = `
|
|
680
|
+
DO $$
|
|
609
681
|
BEGIN
|
|
610
682
|
EXECUTE 'DROP SCHEMA ${targetSchema} CASCADE';
|
|
611
683
|
EXECUTE 'ALTER SCHEMA ${srcSchema} RENAME TO ${targetSchema}';
|
|
612
684
|
IF EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = '${srcSchema}_${ts}') THEN
|
|
613
685
|
EXECUTE 'ALTER SCHEMA ${srcSchema}_${ts} RENAME TO ${srcSchema}';
|
|
614
686
|
END IF;
|
|
615
|
-
END
|
|
616
|
-
await
|
|
687
|
+
END $$;`;
|
|
688
|
+
await this.runSql(preQuery, [], toolchain);
|
|
617
689
|
try {
|
|
618
|
-
await
|
|
690
|
+
await this.runRestore(pgRestoreArgs, toolchain);
|
|
619
691
|
} finally {
|
|
620
|
-
await
|
|
692
|
+
await this.runSql(postQuery, [], toolchain);
|
|
621
693
|
}
|
|
622
694
|
await this.syncCollectionSchemaMetadata(srcSchema, targetSchema, toolchain);
|
|
623
695
|
}
|
|
624
696
|
async syncCollectionSchemaMetadata(srcSchema, targetSchema, toolchain = this.backupToolchain) {
|
|
625
|
-
const {
|
|
697
|
+
const { tablePrefix } = this.dbOpts;
|
|
626
698
|
const collectionsTable = `${tablePrefix || ""}collections`;
|
|
627
699
|
const targetSchemaLiteral = escapeStringLiteral(targetSchema);
|
|
628
700
|
const srcSchemaLiteral = escapeStringLiteral(srcSchema);
|
|
629
701
|
const collectionsTableLiteral = escapeStringLiteral(collectionsTable);
|
|
630
|
-
const
|
|
631
|
-
DO
|
|
702
|
+
const updateCollectionSchemaQuery = `
|
|
703
|
+
DO $$
|
|
632
704
|
DECLARE
|
|
633
705
|
collections_table text := '${collectionsTableLiteral}';
|
|
634
706
|
BEGIN
|
|
@@ -646,8 +718,8 @@ class PostgresAdapter extends BaseDBAdapter {
|
|
|
646
718
|
'${srcSchemaLiteral}'
|
|
647
719
|
);
|
|
648
720
|
END IF;
|
|
649
|
-
END
|
|
650
|
-
await
|
|
721
|
+
END $$;`;
|
|
722
|
+
await this.runSql(updateCollectionSchemaQuery, [], toolchain);
|
|
651
723
|
}
|
|
652
724
|
}
|
|
653
725
|
class KingbaseAdapter extends PostgresAdapter {
|
|
@@ -684,23 +756,32 @@ class KingbaseAdapter extends PostgresAdapter {
|
|
|
684
756
|
getBackupSchema() {
|
|
685
757
|
return this.dbOpts.schema || "public";
|
|
686
758
|
}
|
|
687
|
-
|
|
759
|
+
buildSchemaRestoreArgs(srcSchema, targetSchema, filePath, jobs, toolchain = this.backupToolchain) {
|
|
688
760
|
if (toolchain === "postgres") {
|
|
689
|
-
return super.
|
|
761
|
+
return super.buildSchemaRestoreArgs(srcSchema, targetSchema, filePath, jobs, toolchain);
|
|
690
762
|
}
|
|
691
|
-
|
|
692
|
-
|
|
763
|
+
return [
|
|
764
|
+
...this.getConnectionArgs(),
|
|
765
|
+
"-g",
|
|
766
|
+
srcSchema,
|
|
767
|
+
"-G",
|
|
768
|
+
targetSchema,
|
|
769
|
+
"--clean",
|
|
770
|
+
"--if-exists",
|
|
771
|
+
"--no-owner",
|
|
772
|
+
"-j",
|
|
773
|
+
String(jobs),
|
|
774
|
+
filePath
|
|
775
|
+
];
|
|
693
776
|
}
|
|
694
|
-
async restoreSchema(srcSchema, targetSchema,
|
|
777
|
+
async restoreSchema(srcSchema, targetSchema, restoreArgs, toolchain = this.backupToolchain) {
|
|
695
778
|
if (toolchain === "postgres") {
|
|
696
|
-
await super.restoreSchema(srcSchema, targetSchema,
|
|
779
|
+
await super.restoreSchema(srcSchema, targetSchema, restoreArgs, toolchain);
|
|
697
780
|
return;
|
|
698
781
|
}
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
await run(createSchemaCommand, this.getPasswordEnvVars(password, toolchain));
|
|
703
|
-
await run(restoreCommand, this.getPasswordEnvVars(password, toolchain));
|
|
782
|
+
const targetSchemaIdentifier = quotePgIdentifier(targetSchema);
|
|
783
|
+
await this.runSql(`CREATE SCHEMA IF NOT EXISTS ${targetSchemaIdentifier};`, [], toolchain);
|
|
784
|
+
await this.runRestore(restoreArgs, toolchain);
|
|
704
785
|
await this.syncCollectionSchemaMetadata(srcSchema, targetSchema, toolchain);
|
|
705
786
|
}
|
|
706
787
|
}
|
|
@@ -11,7 +11,7 @@ import { ResourcerContext } from '@nocobase/resourcer';
|
|
|
11
11
|
import Application from '@nocobase/server';
|
|
12
12
|
import { Readable } from 'stream';
|
|
13
13
|
export interface BackupSettings {
|
|
14
|
-
storageId?: string;
|
|
14
|
+
storageId?: string | number | null;
|
|
15
15
|
encryptionPassword: string;
|
|
16
16
|
enableFilesBackup: boolean;
|
|
17
17
|
keep?: number;
|
|
@@ -42,11 +42,14 @@ export interface BackupTaskResult {
|
|
|
42
42
|
message?: string;
|
|
43
43
|
inProgress: boolean;
|
|
44
44
|
}
|
|
45
|
+
type BackupSettingsInput = BackupSettings & {
|
|
46
|
+
toJSON?: () => Partial<BackupSettings>;
|
|
47
|
+
};
|
|
45
48
|
export declare class BackupManager {
|
|
46
49
|
#private;
|
|
47
50
|
app: Application;
|
|
48
51
|
ctx: ResourcerContext | null;
|
|
49
|
-
constructor(app: Application, ctx: ResourcerContext | null, settings:
|
|
52
|
+
constructor(app: Application, ctx: ResourcerContext | null, settings: BackupSettingsInput);
|
|
50
53
|
protected set backupPrefix(backupPrefix: string);
|
|
51
54
|
protected set backupDir(backupDir: string);
|
|
52
55
|
protected set tempDir(tempDir: string);
|
|
@@ -58,3 +61,4 @@ export declare class BackupManager {
|
|
|
58
61
|
list(): Promise<BackupFile[]>;
|
|
59
62
|
createReadStream(fileName: string): Promise<Readable>;
|
|
60
63
|
}
|
|
64
|
+
export {};
|
|
@@ -66,7 +66,7 @@ class BackupManager {
|
|
|
66
66
|
constructor(app, ctx, settings) {
|
|
67
67
|
this.app = app;
|
|
68
68
|
this.ctx = ctx;
|
|
69
|
-
this.#settings = settings;
|
|
69
|
+
this.#settings = this.#normalizeBackupSettings(settings);
|
|
70
70
|
this.#dbAdapter = (0, import_database.getDBAdapter)(app.db.options);
|
|
71
71
|
this.#backupTasksCacheName = import_utils2.BACKUP_TASKS_CACHE_NAME;
|
|
72
72
|
this.#backupPrefix = "backup_";
|
|
@@ -90,6 +90,12 @@ class BackupManager {
|
|
|
90
90
|
set backupTasksCacheName(backupTasksCacheName) {
|
|
91
91
|
this.#backupTasksCacheName = backupTasksCacheName;
|
|
92
92
|
}
|
|
93
|
+
#normalizeBackupSettings(settings) {
|
|
94
|
+
if (typeof settings.toJSON === "function") {
|
|
95
|
+
return settings.toJSON();
|
|
96
|
+
}
|
|
97
|
+
return { ...settings };
|
|
98
|
+
}
|
|
93
99
|
async createBackupName() {
|
|
94
100
|
await this.#dbAdapter.check("backup");
|
|
95
101
|
await import_promises.default.mkdir(this.#backupDir, { recursive: true });
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"displayName.zh-CN": "备份管理器",
|
|
5
5
|
"description": "Provides backup and restore functionality, supports scheduled backups, and ensures data security and quick recovery.",
|
|
6
6
|
"description.zh-CN": "提供备份与还原功能,支持定时备份,确保数据安全与快速恢复。",
|
|
7
|
-
"version": "2.1.
|
|
7
|
+
"version": "2.1.19",
|
|
8
8
|
"license": "Apache-2.0",
|
|
9
9
|
"main": "dist/server/index.js",
|
|
10
10
|
"nocobase": {
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"keywords": [
|
|
51
51
|
"System management"
|
|
52
52
|
],
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "c316683666cfb478cb355b971c8f067a4f925fcb"
|
|
54
54
|
}
|