@datatruck/cli 0.9.0 → 0.11.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/Action/BackupAction.d.ts +1 -0
- package/Action/BackupAction.js +68 -1
- package/Action/ConfigAction.js +16 -0
- package/Command/BackupSessionsCommand.js +9 -2
- package/Command/ConfigCommand.d.ts +1 -1
- package/Command/ConfigCommand.js +7 -3
- package/Command/InitCommand.js +1 -1
- package/Command/PruneCommand.js +1 -1
- package/Command/RestoreSessionsCommand.js +9 -2
- package/Command/SnapshotsCommand.js +9 -2
- package/Config/RepositoryConfig.d.ts +1 -0
- package/Config/RepositoryConfig.js +1 -0
- package/Factory/CommandFactory.js +2 -2
- package/Repository/GitRepository.d.ts +2 -1
- package/Repository/GitRepository.js +3 -0
- package/Repository/LocalRepository.d.ts +2 -1
- package/Repository/LocalRepository.js +23 -1
- package/Repository/RepositoryAbstract.d.ts +8 -0
- package/Repository/ResticRepository.d.ts +2 -1
- package/Repository/ResticRepository.js +24 -0
- package/Task/MariadbTask.js +1 -1
- package/Task/MssqlTask.js +1 -1
- package/Task/SqlDumpTaskAbstract.js +1 -1
- package/cli.js +3 -2
- package/config.schema.json +3 -0
- package/package.json +6 -6
- package/util/DataFormat.d.ts +4 -2
- package/util/DataFormat.js +11 -1
- package/util/GitUtil.js +1 -2
- package/util/ResticUtil.d.ts +4 -0
- package/util/ResticUtil.js +16 -0
- package/util/cli-util.js +2 -2
- package/util/fs-util.d.ts +6 -0
- package/util/fs-util.js +60 -5
package/Action/BackupAction.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export declare class BackupAction<TRequired extends boolean = true> {
|
|
|
25
25
|
protected init(session: BackupSessionManager): Promise<[SnapshotType, PackageConfigType[]]>;
|
|
26
26
|
protected execTask(session: BackupSessionManager, pkg: PackageConfigType, task: TaskConfigType, snapshot: SnapshotType, targetPath: string | undefined): Promise<boolean>;
|
|
27
27
|
protected execRepository(session: BackupSessionManager, pkg: PackageConfigType, repo: RepositoryConfigType, snapshot: SnapshotType, targetPath: string | undefined): Promise<boolean>;
|
|
28
|
+
protected execCopyRepository(session: BackupSessionManager, pkg: PackageConfigType, repo: RepositoryConfigType, mirrorRepo: RepositoryConfigType, snapshot: SnapshotType): Promise<boolean>;
|
|
28
29
|
protected getError(pkg: PackageConfigType): AppError | null;
|
|
29
30
|
exec(session: BackupSessionManager): Promise<{
|
|
30
31
|
total: number;
|
package/Action/BackupAction.js
CHANGED
|
@@ -144,6 +144,50 @@ class BackupAction {
|
|
|
144
144
|
});
|
|
145
145
|
return error ? false : true;
|
|
146
146
|
}
|
|
147
|
+
async execCopyRepository(session, pkg, repo, mirrorRepo, snapshot) {
|
|
148
|
+
const repositoryId = session.findRepositoryId({
|
|
149
|
+
packageName: pkg.name,
|
|
150
|
+
repositoryName: mirrorRepo.name,
|
|
151
|
+
});
|
|
152
|
+
await session.startRepository({
|
|
153
|
+
id: repositoryId,
|
|
154
|
+
});
|
|
155
|
+
let error;
|
|
156
|
+
if (this.taskErrors[pkg.name]?.length) {
|
|
157
|
+
error = new AppError_1.AppError("Task failed");
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
try {
|
|
161
|
+
const repoInstance = (0, RepositoryFactory_1.RepositoryFactory)(repo);
|
|
162
|
+
await repoInstance.onCopyBackup({
|
|
163
|
+
options: this.options,
|
|
164
|
+
package: pkg,
|
|
165
|
+
snapshot,
|
|
166
|
+
mirrorRepositoryConfig: mirrorRepo.config,
|
|
167
|
+
onProgress: async (data) => {
|
|
168
|
+
await session.progressRepository({
|
|
169
|
+
id: repositoryId,
|
|
170
|
+
progressCurrent: data.current,
|
|
171
|
+
progressPercent: data.percent,
|
|
172
|
+
progressStep: data.step,
|
|
173
|
+
progressStepPercent: data.stepPercent,
|
|
174
|
+
progressTotal: data.total,
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
catch (_) {
|
|
180
|
+
if (!this.repoErrors[pkg.name])
|
|
181
|
+
this.repoErrors[pkg.name] = [];
|
|
182
|
+
this.repoErrors[pkg.name].push((error = _));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
await session.endRepository({
|
|
186
|
+
id: repositoryId,
|
|
187
|
+
error: error?.stack,
|
|
188
|
+
});
|
|
189
|
+
return error ? false : true;
|
|
190
|
+
}
|
|
147
191
|
getError(pkg) {
|
|
148
192
|
const taskErrors = this.taskErrors[pkg.name]?.length;
|
|
149
193
|
const repoErrors = this.repoErrors[pkg.name]?.length;
|
|
@@ -182,10 +226,33 @@ class BackupAction {
|
|
|
182
226
|
});
|
|
183
227
|
await this.execTask(session, pkg, pkg.task, snapshot, (targetPath = result?.targetPath));
|
|
184
228
|
}
|
|
185
|
-
|
|
229
|
+
const mirrorRepoMap = {};
|
|
230
|
+
const allMirrorRepoNames = [];
|
|
231
|
+
const repoNames = pkg.repositoryNames ?? [];
|
|
232
|
+
for (const repoName of repoNames) {
|
|
233
|
+
const repo = (0, config_util_1.findRepositoryOrFail)(this.config, repoName);
|
|
234
|
+
if (repo.mirrorRepoNames)
|
|
235
|
+
mirrorRepoMap[repoName] = repo.mirrorRepoNames.filter((mirrorRepoName) => {
|
|
236
|
+
allMirrorRepoNames.push(mirrorRepoName);
|
|
237
|
+
return repoNames.includes(mirrorRepoName);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
for (const repoName of repoNames) {
|
|
241
|
+
if (allMirrorRepoNames.includes(repoName))
|
|
242
|
+
continue;
|
|
186
243
|
const repo = (0, config_util_1.findRepositoryOrFail)(this.config, repoName);
|
|
187
244
|
await this.execRepository(session, pkg, repo, snapshot, targetPath);
|
|
188
245
|
}
|
|
246
|
+
for (const repoName of repoNames) {
|
|
247
|
+
const repo = (0, config_util_1.findRepositoryOrFail)(this.config, repoName);
|
|
248
|
+
const mirrorRepoNames = mirrorRepoMap[repoName];
|
|
249
|
+
if (mirrorRepoNames) {
|
|
250
|
+
for (const mirrorRepoName of mirrorRepoNames) {
|
|
251
|
+
const mirrorRepo = (0, config_util_1.findRepositoryOrFail)(this.config, mirrorRepoName);
|
|
252
|
+
await this.execCopyRepository(session, pkg, repo, mirrorRepo, snapshot);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
189
256
|
const error = this.getError(pkg);
|
|
190
257
|
if (error)
|
|
191
258
|
errors++;
|
package/Action/ConfigAction.js
CHANGED
|
@@ -22,11 +22,27 @@ class ConfigAction {
|
|
|
22
22
|
}
|
|
23
23
|
static check(config) {
|
|
24
24
|
const repositoryNames = [];
|
|
25
|
+
const mirrorRepoNames = [];
|
|
26
|
+
const repos = {};
|
|
25
27
|
for (const repo of config.repositories) {
|
|
28
|
+
repos[repo.name] = repo;
|
|
26
29
|
if (repositoryNames.includes(repo.name))
|
|
27
30
|
throw new AppError_1.AppError(`Duplicated repository name: ${repo.name}`);
|
|
28
31
|
repositoryNames.push(repo.name);
|
|
29
32
|
}
|
|
33
|
+
for (const repo of config.repositories) {
|
|
34
|
+
if (repo.mirrorRepoNames) {
|
|
35
|
+
for (const mirrorRepoName of repo.mirrorRepoNames) {
|
|
36
|
+
if (!repos[mirrorRepoName])
|
|
37
|
+
throw new AppError_1.AppError(`Mirror repository name not found: ${mirrorRepoName}`);
|
|
38
|
+
if (repos[mirrorRepoName].type !== repo.type)
|
|
39
|
+
throw new AppError_1.AppError(`Mirror repository type is incompatible: ${mirrorRepoName}`);
|
|
40
|
+
if (mirrorRepoNames.includes(mirrorRepoName))
|
|
41
|
+
throw new AppError_1.AppError(`Mirror repository is already used`);
|
|
42
|
+
mirrorRepoNames.push(mirrorRepoName);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
30
46
|
const packageNames = [];
|
|
31
47
|
for (const pkg of config.packages) {
|
|
32
48
|
if (packageNames.includes(pkg.name))
|
|
@@ -51,8 +51,9 @@ class BackupSessionsCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
51
51
|
}),
|
|
52
52
|
verbose: verbose > 1,
|
|
53
53
|
});
|
|
54
|
+
const items = await action.exec(manager);
|
|
54
55
|
const dataFormat = new DataFormat_1.DataFormat({
|
|
55
|
-
items
|
|
56
|
+
items,
|
|
56
57
|
table: {
|
|
57
58
|
labels: [
|
|
58
59
|
" ",
|
|
@@ -77,7 +78,13 @@ class BackupSessionsCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
77
78
|
},
|
|
78
79
|
});
|
|
79
80
|
if (this.globalOptions.outputFormat)
|
|
80
|
-
console.
|
|
81
|
+
console.info(dataFormat.format(this.globalOptions.outputFormat, {
|
|
82
|
+
tpl: {
|
|
83
|
+
sids: () => items.map((i) => i.snapshotId).join(),
|
|
84
|
+
ssids: () => items.map((i) => i.snapshotId.slice(0, 8)).join(),
|
|
85
|
+
pkgNames: () => items.map((i) => i.packageName).join(),
|
|
86
|
+
},
|
|
87
|
+
}));
|
|
81
88
|
return 0;
|
|
82
89
|
}
|
|
83
90
|
}
|
|
@@ -8,7 +8,7 @@ export declare type ConfigCommandOptionsType<TResolved = false> = {
|
|
|
8
8
|
repositoryType?: If<TResolved, RepositoryConfigType["type"][]>;
|
|
9
9
|
};
|
|
10
10
|
export declare type ConfigCommandLogType = {
|
|
11
|
-
|
|
11
|
+
packageName: string;
|
|
12
12
|
repositoryNames: string[];
|
|
13
13
|
taskName: string | undefined;
|
|
14
14
|
}[];
|
package/Command/ConfigCommand.js
CHANGED
|
@@ -40,7 +40,7 @@ class ConfigCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
40
40
|
repositoryTypes: this.options.repositoryType,
|
|
41
41
|
});
|
|
42
42
|
const summaryConfig = packages.flatMap((pkg) => ({
|
|
43
|
-
|
|
43
|
+
packageName: pkg.name,
|
|
44
44
|
repositoryNames: pkg.repositoryNames ?? [],
|
|
45
45
|
taskName: pkg.task?.name,
|
|
46
46
|
}));
|
|
@@ -49,14 +49,18 @@ class ConfigCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
49
49
|
table: {
|
|
50
50
|
labels: ["Package", "Repository", "Task"],
|
|
51
51
|
handler: (item) => [
|
|
52
|
-
item.
|
|
52
|
+
item.packageName,
|
|
53
53
|
item.repositoryNames.join(", "),
|
|
54
54
|
item.taskName ?? "",
|
|
55
55
|
],
|
|
56
56
|
},
|
|
57
57
|
});
|
|
58
58
|
if (this.globalOptions.outputFormat)
|
|
59
|
-
console.
|
|
59
|
+
console.info(dataFormat.format(this.globalOptions.outputFormat, {
|
|
60
|
+
tpl: {
|
|
61
|
+
pkgNames: () => summaryConfig.map((i) => i.packageName).join(),
|
|
62
|
+
},
|
|
63
|
+
}));
|
|
60
64
|
return 0;
|
|
61
65
|
}
|
|
62
66
|
}
|
package/Command/InitCommand.js
CHANGED
|
@@ -56,7 +56,7 @@ class InitCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
56
56
|
},
|
|
57
57
|
});
|
|
58
58
|
if (this.globalOptions.outputFormat)
|
|
59
|
-
console.
|
|
59
|
+
console.info(dataFormat.format(this.globalOptions.outputFormat));
|
|
60
60
|
return 0;
|
|
61
61
|
}
|
|
62
62
|
}
|
package/Command/PruneCommand.js
CHANGED
|
@@ -144,7 +144,7 @@ class PruneCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
144
144
|
},
|
|
145
145
|
});
|
|
146
146
|
if (this.globalOptions.outputFormat)
|
|
147
|
-
console.
|
|
147
|
+
console.info(dataFormat.format(this.globalOptions.outputFormat));
|
|
148
148
|
if (!this.options.confirm && !this.options.dryRun) {
|
|
149
149
|
const answer = await (0, cli_util_1.confirm)(`Delete ${pruneResult.prune}/${pruneResult.total} snapshots?`);
|
|
150
150
|
if (answer)
|
|
@@ -50,8 +50,9 @@ class RestoreSessionsCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
50
50
|
verbose: verbose > 1,
|
|
51
51
|
}),
|
|
52
52
|
});
|
|
53
|
+
const items = await action.exec(manager);
|
|
53
54
|
const dataFormat = new DataFormat_1.DataFormat({
|
|
54
|
-
items
|
|
55
|
+
items,
|
|
55
56
|
table: {
|
|
56
57
|
labels: [
|
|
57
58
|
" ",
|
|
@@ -76,7 +77,13 @@ class RestoreSessionsCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
76
77
|
},
|
|
77
78
|
});
|
|
78
79
|
if (this.globalOptions.outputFormat)
|
|
79
|
-
console.
|
|
80
|
+
console.info(dataFormat.format(this.globalOptions.outputFormat, {
|
|
81
|
+
tpl: {
|
|
82
|
+
sids: () => items.map((i) => i.snapshotId).join(),
|
|
83
|
+
ssids: () => items.map((i) => i.snapshotId.slice(0, 8)).join(),
|
|
84
|
+
pkgNames: () => items.map((i) => i.packageName).join(),
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
80
87
|
return 0;
|
|
81
88
|
}
|
|
82
89
|
}
|
|
@@ -106,8 +106,9 @@ class SnapshotsCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
106
106
|
verbose: verbose > 0,
|
|
107
107
|
tags: this.options.tag,
|
|
108
108
|
});
|
|
109
|
+
const items = await snapshots.exec();
|
|
109
110
|
const dataFormat = new DataFormat_1.DataFormat({
|
|
110
|
-
items
|
|
111
|
+
items,
|
|
111
112
|
table: {
|
|
112
113
|
labels: [
|
|
113
114
|
"Id.",
|
|
@@ -128,7 +129,13 @@ class SnapshotsCommand extends CommandAbstract_1.CommandAbstract {
|
|
|
128
129
|
},
|
|
129
130
|
});
|
|
130
131
|
if (this.globalOptions.outputFormat)
|
|
131
|
-
console.
|
|
132
|
+
console.info(dataFormat.format(this.globalOptions.outputFormat, {
|
|
133
|
+
tpl: {
|
|
134
|
+
sids: () => items.map((i) => i.id).join(),
|
|
135
|
+
ssids: () => items.map((i) => i.shortId).join(),
|
|
136
|
+
pkgNames: () => items.map((i) => i.packageName).join(),
|
|
137
|
+
},
|
|
138
|
+
}));
|
|
132
139
|
return 0;
|
|
133
140
|
}
|
|
134
141
|
}
|
|
@@ -7,6 +7,7 @@ export declare type RepositoryConfigTypeType = RepositoryConfigType["type"];
|
|
|
7
7
|
export declare type RepositoryConfigEnabledActionType = "backup" | "init" | "prune" | "restore" | "snapshots";
|
|
8
8
|
export declare type RepositoryConfigType = {
|
|
9
9
|
name: string;
|
|
10
|
+
mirrorRepoNames?: string[];
|
|
10
11
|
enabled?: boolean | {
|
|
11
12
|
[K in "defaults" | RepositoryConfigEnabledActionType]?: boolean;
|
|
12
13
|
};
|
|
@@ -35,11 +35,11 @@ exports.exec = exec;
|
|
|
35
35
|
function makeParseLog(type) {
|
|
36
36
|
const data = [];
|
|
37
37
|
const consoleLog = console.log;
|
|
38
|
-
console.log = (...items) => {
|
|
38
|
+
console.log = console.info = (...items) => {
|
|
39
39
|
data.push(...items);
|
|
40
40
|
};
|
|
41
41
|
return function parseLog() {
|
|
42
|
-
console.log = consoleLog;
|
|
42
|
+
console.log = console.info = consoleLog;
|
|
43
43
|
return JSON.parse(data.flat().join("\n"));
|
|
44
44
|
};
|
|
45
45
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RepositoryAbstract, BackupDataType, InitDataType, RestoreDataType, SnapshotsDataType, SnapshotResultType, SnapshotTagEnum, SnapshotTagObjectType, PruneDataType } from "./RepositoryAbstract";
|
|
1
|
+
import { RepositoryAbstract, BackupDataType, InitDataType, RestoreDataType, SnapshotsDataType, SnapshotResultType, SnapshotTagEnum, SnapshotTagObjectType, PruneDataType, CopyBackupType } from "./RepositoryAbstract";
|
|
2
2
|
import { JSONSchema7 } from "json-schema";
|
|
3
3
|
export declare type GitRepositoryConfigType = {
|
|
4
4
|
repo: string;
|
|
@@ -25,5 +25,6 @@ export declare class GitRepository extends RepositoryAbstract<GitRepositoryConfi
|
|
|
25
25
|
onPrune(data: PruneDataType): Promise<void>;
|
|
26
26
|
onSnapshots(data: SnapshotsDataType): Promise<SnapshotResultType[]>;
|
|
27
27
|
onBackup(data: BackupDataType<GitPackageRepositoryConfigType>): Promise<void>;
|
|
28
|
+
onCopyBackup(data: CopyBackupType<GitRepositoryConfigType>): Promise<void>;
|
|
28
29
|
onRestore(data: RestoreDataType<GitPackageRepositoryConfigType>): Promise<void>;
|
|
29
30
|
}
|
|
@@ -215,6 +215,9 @@ class GitRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
215
215
|
recursive: true,
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
|
+
onCopyBackup(data) {
|
|
219
|
+
throw new Error("Method not implemented.");
|
|
220
|
+
}
|
|
218
221
|
async onRestore(data) {
|
|
219
222
|
const restorePath = data.targetPath ?? data.package.restorePath;
|
|
220
223
|
(0, assert_1.ok)(restorePath);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RepositoryAbstract, BackupDataType, InitDataType, RestoreDataType, SnapshotsDataType, SnapshotResultType, PruneDataType } from "./RepositoryAbstract";
|
|
1
|
+
import { RepositoryAbstract, BackupDataType, InitDataType, RestoreDataType, SnapshotsDataType, SnapshotResultType, PruneDataType, CopyBackupType } from "./RepositoryAbstract";
|
|
2
2
|
import type { JSONSchema7 } from "json-schema";
|
|
3
3
|
export declare type MetaDataType = {
|
|
4
4
|
id: string;
|
|
@@ -51,6 +51,7 @@ export declare class LocalRepository extends RepositoryAbstract<LocalRepositoryC
|
|
|
51
51
|
onSnapshots(data: SnapshotsDataType): Promise<SnapshotResultType[]>;
|
|
52
52
|
private normalizeCompressConfig;
|
|
53
53
|
onBackup(data: BackupDataType<LocalPackageRepositoryConfigType>): Promise<void>;
|
|
54
|
+
onCopyBackup(data: CopyBackupType<LocalRepositoryConfigType>): Promise<void>;
|
|
54
55
|
onRestore(data: RestoreDataType<LocalPackageRepositoryConfigType>): Promise<void>;
|
|
55
56
|
}
|
|
56
57
|
export {};
|
|
@@ -122,7 +122,7 @@ class LocalRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
122
122
|
async onSnapshots(data) {
|
|
123
123
|
if (!(await (0, fs_util_1.checkDir)(this.config.outPath)))
|
|
124
124
|
throw new Error(`Repository (${this.repository.name}) out path does not exist: ${this.config.outPath}`);
|
|
125
|
-
const snapshotNames = await (0,
|
|
125
|
+
const snapshotNames = await (0, fs_util_1.readDir)(this.config.outPath);
|
|
126
126
|
const snapshots = [];
|
|
127
127
|
const packagePatterns = (0, string_util_1.makePathPatterns)(data.options.packageNames);
|
|
128
128
|
const taskPatterns = (0, string_util_1.makePathPatterns)(data.options.packageTaskNames);
|
|
@@ -299,6 +299,28 @@ class LocalRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
299
299
|
(0, cli_util_1.logExec)(`Writing metadata into ${metaPath}`);
|
|
300
300
|
await (0, promises_1.writeFile)(metaPath, LocalRepository.stringifyMetaData(meta));
|
|
301
301
|
}
|
|
302
|
+
async onCopyBackup(data) {
|
|
303
|
+
const snapshotName = LocalRepository.buildSnapshotName({
|
|
304
|
+
snapshotId: data.snapshot.id,
|
|
305
|
+
snapshotDate: data.snapshot.date,
|
|
306
|
+
packageName: data.package.name,
|
|
307
|
+
});
|
|
308
|
+
const sourcePath = (0, path_1.resolve)((0, path_1.join)(this.config.outPath, snapshotName));
|
|
309
|
+
const targetPath = (0, path_1.resolve)((0, path_1.join)(data.mirrorRepositoryConfig.outPath, snapshotName));
|
|
310
|
+
const sourceMetaPath = `${sourcePath}.json`;
|
|
311
|
+
const targetMetaPath = `${targetPath}.json`;
|
|
312
|
+
if (data.options.verbose)
|
|
313
|
+
(0, cli_util_1.logExec)(`Copying files to ${targetPath}`);
|
|
314
|
+
await (0, promises_1.mkdir)(targetPath);
|
|
315
|
+
await (0, fs_util_1.cpy)({
|
|
316
|
+
input: {
|
|
317
|
+
type: "glob",
|
|
318
|
+
sourcePath,
|
|
319
|
+
},
|
|
320
|
+
targetPath,
|
|
321
|
+
});
|
|
322
|
+
await (0, promises_1.copyFile)(sourceMetaPath, targetMetaPath);
|
|
323
|
+
}
|
|
302
324
|
async onRestore(data) {
|
|
303
325
|
const relRestorePath = data.targetPath ?? data.package.restorePath;
|
|
304
326
|
(0, assert_1.ok)(relRestorePath);
|
|
@@ -27,6 +27,13 @@ export declare type InitDataType = {
|
|
|
27
27
|
export declare type SnapshotsDataType = {
|
|
28
28
|
options: Pick<SnapshotsActionOptionsType, "ids" | "packageNames" | "packageTaskNames" | "verbose" | "tags">;
|
|
29
29
|
};
|
|
30
|
+
export declare type CopyBackupType<TRepositoryConfig> = {
|
|
31
|
+
options: BackupActionOptionsType;
|
|
32
|
+
snapshot: SnapshotType;
|
|
33
|
+
package: PackageConfigType;
|
|
34
|
+
mirrorRepositoryConfig: TRepositoryConfig;
|
|
35
|
+
onProgress: (data: ProgressDataType) => Promise<void>;
|
|
36
|
+
};
|
|
30
37
|
export declare type BackupDataType<TPackageConfig> = {
|
|
31
38
|
options: BackupActionOptionsType;
|
|
32
39
|
snapshot: SnapshotType;
|
|
@@ -75,6 +82,7 @@ export declare abstract class RepositoryAbstract<TConfig> {
|
|
|
75
82
|
abstract onInit(data: InitDataType): Promise<void>;
|
|
76
83
|
abstract onPrune(data: PruneDataType): Promise<void>;
|
|
77
84
|
abstract onSnapshots(data: SnapshotsDataType): Promise<SnapshotResultType[]>;
|
|
85
|
+
abstract onCopyBackup(data: CopyBackupType<TConfig>): Promise<void>;
|
|
78
86
|
abstract onBackup(data: BackupDataType<unknown>): Promise<void>;
|
|
79
87
|
abstract onRestore(data: RestoreDataType<unknown>): Promise<void>;
|
|
80
88
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RepositoryType } from "../util/ResticUtil";
|
|
2
|
-
import { RepositoryAbstract, BackupDataType, InitDataType, RestoreDataType, SnapshotsDataType, SnapshotResultType, SnapshotTagObjectType, SnapshotTagEnum, PruneDataType } from "./RepositoryAbstract";
|
|
2
|
+
import { RepositoryAbstract, BackupDataType, InitDataType, RestoreDataType, SnapshotsDataType, SnapshotResultType, SnapshotTagObjectType, SnapshotTagEnum, PruneDataType, CopyBackupType } from "./RepositoryAbstract";
|
|
3
3
|
import { JSONSchema7 } from "json-schema";
|
|
4
4
|
export declare type ResticRepositoryConfigType = {
|
|
5
5
|
password: string | {
|
|
@@ -36,5 +36,6 @@ export declare class ResticRepository extends RepositoryAbstract<ResticRepositor
|
|
|
36
36
|
onSnapshots(data: SnapshotsDataType): Promise<SnapshotResultType[]>;
|
|
37
37
|
onPrune(data: PruneDataType): Promise<void>;
|
|
38
38
|
onBackup(data: BackupDataType<ResticPackageRepositoryConfigType>): Promise<void>;
|
|
39
|
+
onCopyBackup(data: CopyBackupType<ResticRepositoryConfigType>): Promise<void>;
|
|
39
40
|
onRestore(data: RestoreDataType<ResticPackageRepositoryConfigType>): Promise<void>;
|
|
40
41
|
}
|
|
@@ -271,6 +271,30 @@ class ResticRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
271
271
|
percent: 100,
|
|
272
272
|
});
|
|
273
273
|
}
|
|
274
|
+
async onCopyBackup(data) {
|
|
275
|
+
const config = data.mirrorRepositoryConfig;
|
|
276
|
+
const [snapshot] = await this.onSnapshots({
|
|
277
|
+
options: {
|
|
278
|
+
ids: [data.snapshot.id],
|
|
279
|
+
packageNames: [data.package.name],
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
if (!snapshot)
|
|
283
|
+
throw new AppError_1.AppError(`Snapshot not found`);
|
|
284
|
+
const restic = new ResticUtil_1.ResticUtil({
|
|
285
|
+
env: {
|
|
286
|
+
...(await this.buildEnv()),
|
|
287
|
+
...(typeof config.password === "string"
|
|
288
|
+
? { RESTIC_PASSWORD2: config.password }
|
|
289
|
+
: { RESTIC_PASSWORD_FILE2: (0, path_1.resolve)(config.password.path) }),
|
|
290
|
+
RESTIC_REPOSITORY2: await ResticUtil_1.ResticUtil.formatRepository(config.repository),
|
|
291
|
+
},
|
|
292
|
+
log: data.options.verbose,
|
|
293
|
+
});
|
|
294
|
+
await restic.copy({
|
|
295
|
+
id: snapshot.originalId,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
274
298
|
async onRestore(data) {
|
|
275
299
|
const restorePath = data.targetPath ?? data.package.restorePath;
|
|
276
300
|
(0, assert_1.ok)(restorePath);
|
package/Task/MariadbTask.js
CHANGED
|
@@ -137,7 +137,7 @@ class MariadbTask extends TaskAbstract_1.TaskAbstract {
|
|
|
137
137
|
const restorePath = data.package.restorePath;
|
|
138
138
|
(0, assert_1.ok)(typeof restorePath === "string");
|
|
139
139
|
await (0, fs_util_1.mkdirIfNotExists)(restorePath);
|
|
140
|
-
const files = await (0,
|
|
140
|
+
const files = await (0, fs_util_1.readDir)(restorePath);
|
|
141
141
|
for (const file of files) {
|
|
142
142
|
if (file.startsWith("ib_logfile")) {
|
|
143
143
|
const filePath = (0, path_1.join)(restorePath, file);
|
package/Task/MssqlTask.js
CHANGED
|
@@ -84,7 +84,7 @@ class MssqlTask extends TaskAbstract_1.TaskAbstract {
|
|
|
84
84
|
const restorePath = data.package.restorePath;
|
|
85
85
|
(0, assert_1.ok)(typeof restorePath === "string");
|
|
86
86
|
await (0, fs_util_1.mkdirIfNotExists)(restorePath);
|
|
87
|
-
const files = await (0,
|
|
87
|
+
const files = await (0, fs_util_1.readDir)(restorePath);
|
|
88
88
|
for (const file of files) {
|
|
89
89
|
if (!file.endsWith(MssqlTask.SUFFIX))
|
|
90
90
|
continue;
|
|
@@ -160,7 +160,7 @@ class SqlDumpTaskAbstract extends TaskAbstract_1.TaskAbstract {
|
|
|
160
160
|
database: database.name,
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
const items = (await (0,
|
|
163
|
+
const items = (await (0, fs_util_1.readDir)(restorePath))
|
|
164
164
|
.map(parseSqlFile)
|
|
165
165
|
.filter((v) => !!v);
|
|
166
166
|
// Database check
|
package/cli.js
CHANGED
|
@@ -78,9 +78,10 @@ const cwd = process.cwd();
|
|
|
78
78
|
program.name(subname);
|
|
79
79
|
program.version(version);
|
|
80
80
|
program.description(description);
|
|
81
|
+
program.usage("dtt");
|
|
81
82
|
program.option("-v,--verbose", "Verbose", (_, previous) => previous + 1, 0);
|
|
82
83
|
program.option("-c,--config <path>", "Config path", process.env["DATATRUCK_CONFIG"] ?? (cwd.endsWith(path_1.sep) ? cwd : `${cwd}${path_1.sep}`));
|
|
83
|
-
program.option("-o,--output-format <format>", "Output format (json, pjson, yaml, table, custom
|
|
84
|
+
program.option("-o,--output-format <format>", "Output format (json, pjson, yaml, table, custom=$, tpl=name)", "table");
|
|
84
85
|
makeCommand(CommandFactory_1.CommandEnum.config).alias("c");
|
|
85
86
|
makeCommand(CommandFactory_1.CommandEnum.init).alias("i");
|
|
86
87
|
makeCommand(CommandFactory_1.CommandEnum.snapshots).alias("s");
|
|
@@ -104,7 +105,7 @@ function parseArgs(args) {
|
|
|
104
105
|
(0, process_util_1.onExit)((eventName, error) => {
|
|
105
106
|
if (eventName !== "exit") {
|
|
106
107
|
process.stdout.write(cli_util_1.showCursorCommand);
|
|
107
|
-
console.
|
|
108
|
+
console.info(`\nClosing... (reason: ${eventName})`);
|
|
108
109
|
if (error instanceof Error)
|
|
109
110
|
console.error((0, chalk_1.red)(error.stack));
|
|
110
111
|
}
|
package/config.schema.json
CHANGED
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datatruck/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"ajv": "^8.11.0",
|
|
6
6
|
"async": "^3.2.4",
|
|
7
7
|
"chalk": "^4.1.2",
|
|
8
8
|
"cli-table3": "^0.6.2",
|
|
9
|
-
"commander": "^9.
|
|
10
|
-
"dayjs": "^1.11.
|
|
9
|
+
"commander": "^9.4.0",
|
|
10
|
+
"dayjs": "^1.11.5",
|
|
11
11
|
"fast-glob": "^3.2.11",
|
|
12
12
|
"micromatch": "^4.0.5",
|
|
13
|
-
"sqlite": "^4.1.
|
|
14
|
-
"sqlite3": "^5.0.
|
|
13
|
+
"sqlite": "^4.1.2",
|
|
14
|
+
"sqlite3": "^5.0.11"
|
|
15
15
|
},
|
|
16
16
|
"optionalDependencies": {
|
|
17
|
-
"ts-node": "^10.
|
|
17
|
+
"ts-node": "^10.9.1",
|
|
18
18
|
"yaml": "^2.1.1"
|
|
19
19
|
},
|
|
20
20
|
"engine": {
|
package/util/DataFormat.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare type FormatType = "json" | "pjson" | "table" | "yaml" | "custom";
|
|
1
|
+
export declare type FormatType = "json" | "pjson" | "table" | "yaml" | "custom" | "tpl";
|
|
2
2
|
export declare class DataFormat<TItem extends Record<string, unknown>> {
|
|
3
3
|
readonly options: {
|
|
4
4
|
items: TItem[];
|
|
@@ -20,5 +20,7 @@ export declare class DataFormat<TItem extends Record<string, unknown>> {
|
|
|
20
20
|
protected formatToPrettyJson(): string;
|
|
21
21
|
protected formatToYaml(): any;
|
|
22
22
|
protected formatToTable(): string;
|
|
23
|
-
format(format: FormatType
|
|
23
|
+
format(format: FormatType, options?: {
|
|
24
|
+
tpl?: Record<string, () => string>;
|
|
25
|
+
}): any;
|
|
24
26
|
}
|
package/util/DataFormat.js
CHANGED
|
@@ -8,6 +8,7 @@ const AppError_1 = require("../Error/AppError");
|
|
|
8
8
|
const cli_table3_1 = __importDefault(require("cli-table3"));
|
|
9
9
|
const util_1 = require("util");
|
|
10
10
|
const customPrefix = "custom=";
|
|
11
|
+
const tplPrefix = "tpl=";
|
|
11
12
|
class DataFormat {
|
|
12
13
|
constructor(options) {
|
|
13
14
|
this.options = options;
|
|
@@ -34,7 +35,7 @@ class DataFormat {
|
|
|
34
35
|
table.push(this.options.table.handler(item));
|
|
35
36
|
return table.toString();
|
|
36
37
|
}
|
|
37
|
-
format(format) {
|
|
38
|
+
format(format, options) {
|
|
38
39
|
if (format === "table") {
|
|
39
40
|
return this.formatToTable();
|
|
40
41
|
}
|
|
@@ -51,6 +52,15 @@ class DataFormat {
|
|
|
51
52
|
const code = format.slice(customPrefix.length);
|
|
52
53
|
return runCustomCode(this.options.items, code);
|
|
53
54
|
}
|
|
55
|
+
else if (format.startsWith(tplPrefix)) {
|
|
56
|
+
const name = format.slice(tplPrefix.length);
|
|
57
|
+
const tpl = options?.tpl || {};
|
|
58
|
+
if (!(name in tpl)) {
|
|
59
|
+
const tplNames = Object.keys(tpl).join(", ");
|
|
60
|
+
throw new AppError_1.AppError(`Template name not found: ${name} (valid names: ${tplNames})`);
|
|
61
|
+
}
|
|
62
|
+
return tpl[name]();
|
|
63
|
+
}
|
|
54
64
|
else {
|
|
55
65
|
throw new AppError_1.AppError(`Invalid output format: ${format}`);
|
|
56
66
|
}
|
package/util/GitUtil.js
CHANGED
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GitUtil = void 0;
|
|
4
4
|
const fs_util_1 = require("./fs-util");
|
|
5
5
|
const process_util_1 = require("./process-util");
|
|
6
|
-
const promises_1 = require("fs/promises");
|
|
7
6
|
class GitUtil {
|
|
8
7
|
constructor(options) {
|
|
9
8
|
this.options = options;
|
|
@@ -16,7 +15,7 @@ class GitUtil {
|
|
|
16
15
|
}
|
|
17
16
|
async canBeInit(repo) {
|
|
18
17
|
return ((0, fs_util_1.isLocalDir)(repo) &&
|
|
19
|
-
(!(await (0, fs_util_1.checkDir)(repo)) || !(await (0,
|
|
18
|
+
(!(await (0, fs_util_1.checkDir)(repo)) || !(await (0, fs_util_1.readDir)(repo)).length));
|
|
20
19
|
}
|
|
21
20
|
async clone(options) {
|
|
22
21
|
return await this.exec([
|
package/util/ResticUtil.d.ts
CHANGED
|
@@ -86,6 +86,10 @@ export declare class ResticUtil {
|
|
|
86
86
|
allowEmptySnapshot?: boolean;
|
|
87
87
|
onStream?: (data: BackupStreamType) => void;
|
|
88
88
|
}): Promise<ExecResultType>;
|
|
89
|
+
copy(options: {
|
|
90
|
+
id: string;
|
|
91
|
+
onStream?: (data: BackupStreamType) => Promise<void>;
|
|
92
|
+
}): Promise<ExecResultType>;
|
|
89
93
|
restore(options: {
|
|
90
94
|
id: string;
|
|
91
95
|
target: string;
|
package/util/ResticUtil.js
CHANGED
|
@@ -154,6 +154,22 @@ class ResticUtil {
|
|
|
154
154
|
throw error;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
+
async copy(options) {
|
|
158
|
+
return await this.exec(["copy", "--json", options.id], {
|
|
159
|
+
stderr: {
|
|
160
|
+
toExitCode: true,
|
|
161
|
+
},
|
|
162
|
+
stdout: {
|
|
163
|
+
...(options.onStream && {
|
|
164
|
+
onData: async (data) => {
|
|
165
|
+
if (data.startsWith("{") && data.endsWith("}")) {
|
|
166
|
+
await options.onStream?.(JSON.parse(data));
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
157
173
|
async restore(options) {
|
|
158
174
|
return await this.exec(["restore", "--json", options.id, "--target", options.target], {
|
|
159
175
|
stderr: {
|
package/util/cli-util.js
CHANGED
|
@@ -32,11 +32,11 @@ function logVars(data) {
|
|
|
32
32
|
let first = true;
|
|
33
33
|
for (const key in data) {
|
|
34
34
|
if (first) {
|
|
35
|
-
console.
|
|
35
|
+
console.info();
|
|
36
36
|
first = false;
|
|
37
37
|
}
|
|
38
38
|
const value = data[key];
|
|
39
|
-
console.
|
|
39
|
+
console.info(`${chalk_1.default.cyan(key)}${chalk_1.default.grey(":")} ${chalk_1.default.white(value ?? "")}`);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
exports.logVars = logVars;
|
package/util/fs-util.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
import { Stats } from "fs";
|
|
3
5
|
import { Interface } from "readline";
|
|
6
|
+
export declare const isWSLSystem: boolean;
|
|
4
7
|
export declare function isLocalDir(path: string): boolean;
|
|
5
8
|
export declare function isDirEmpty(path: string): Promise<boolean>;
|
|
6
9
|
export declare function mkdirIfNotExists(path: string): Promise<string>;
|
|
@@ -24,6 +27,7 @@ export declare function mkTmpDir(prefix: string, id?: string): Promise<string>;
|
|
|
24
27
|
export declare function readPartialFile(path: string, positions: [number, number?]): Promise<string>;
|
|
25
28
|
export declare function checkFile(path: string): Promise<boolean>;
|
|
26
29
|
export declare function checkDir(path: string): Promise<boolean>;
|
|
30
|
+
export declare function readDir(path: string): Promise<string[]>;
|
|
27
31
|
export declare function forEachFile(dirPath: string, cb: (path: string, dir: boolean) => void, includeDir?: boolean): Promise<void>;
|
|
28
32
|
export declare function writeGitIgnoreList(options: {
|
|
29
33
|
paths: NodeJS.ReadableStream | string[];
|
|
@@ -46,6 +50,8 @@ export declare function writePathLists(options: {
|
|
|
46
50
|
multipleStats: Record<string, number>;
|
|
47
51
|
};
|
|
48
52
|
}>;
|
|
53
|
+
export declare function copyFileWithStreams(source: string, target: string): Promise<unknown>;
|
|
54
|
+
export declare function updateFileStats(path: string, fileInfo: Stats): Promise<void>;
|
|
49
55
|
export declare function cpy(options: {
|
|
50
56
|
input: {
|
|
51
57
|
type: "glob";
|
package/util/fs-util.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.cpy = exports.writePathLists = exports.writeGitIgnoreList = exports.forEachFile = exports.checkDir = exports.checkFile = exports.readPartialFile = exports.mkTmpDir = exports.tmpDir = exports.sessionTmpDir = exports.parentTmpDir = exports.existsFile = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.readdirIfExists = exports.writeJSONFile = exports.existsDir = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isDirEmpty = exports.isLocalDir = void 0;
|
|
6
|
+
exports.cpy = exports.updateFileStats = exports.copyFileWithStreams = exports.writePathLists = exports.writeGitIgnoreList = exports.forEachFile = exports.readDir = exports.checkDir = exports.checkFile = exports.readPartialFile = exports.mkTmpDir = exports.tmpDir = exports.sessionTmpDir = exports.parentTmpDir = exports.existsFile = exports.findFile = exports.parsePackageFile = exports.parseFile = exports.parseFileExtensions = exports.readdirIfExists = exports.writeJSONFile = exports.existsDir = exports.ensureEmptyDir = exports.mkdirIfNotExists = exports.isDirEmpty = exports.isLocalDir = exports.isWSLSystem = void 0;
|
|
7
7
|
const globalData_1 = __importDefault(require("../globalData"));
|
|
8
8
|
const path_util_1 = require("./path-util");
|
|
9
9
|
const async_1 = require("async");
|
|
@@ -13,15 +13,17 @@ const fs_1 = require("fs");
|
|
|
13
13
|
const fs_2 = require("fs");
|
|
14
14
|
const promises_1 = require("fs/promises");
|
|
15
15
|
const micromatch_1 = require("micromatch");
|
|
16
|
+
const os_1 = require("os");
|
|
16
17
|
const path_1 = require("path");
|
|
17
18
|
const path_2 = require("path");
|
|
18
19
|
const readline_1 = require("readline");
|
|
20
|
+
exports.isWSLSystem = (0, os_1.release)().includes("microsoft-standard-WSL");
|
|
19
21
|
function isLocalDir(path) {
|
|
20
22
|
return /^[\/\.]|([A-Z]:)/i.test(path);
|
|
21
23
|
}
|
|
22
24
|
exports.isLocalDir = isLocalDir;
|
|
23
25
|
async function isDirEmpty(path) {
|
|
24
|
-
const files = await (
|
|
26
|
+
const files = await readDir(path);
|
|
25
27
|
return !files.length;
|
|
26
28
|
}
|
|
27
29
|
exports.isDirEmpty = isDirEmpty;
|
|
@@ -57,7 +59,7 @@ exports.writeJSONFile = writeJSONFile;
|
|
|
57
59
|
async function readdirIfExists(path) {
|
|
58
60
|
if (!(await existsDir(path)))
|
|
59
61
|
return [];
|
|
60
|
-
return await (
|
|
62
|
+
return await readDir(path);
|
|
61
63
|
}
|
|
62
64
|
exports.readdirIfExists = readdirIfExists;
|
|
63
65
|
exports.parseFileExtensions = ["json", "js", "ts", "yaml", "yml"];
|
|
@@ -179,8 +181,25 @@ async function checkDir(path) {
|
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
exports.checkDir = checkDir;
|
|
184
|
+
async function readDir(path) {
|
|
185
|
+
try {
|
|
186
|
+
return await (0, promises_1.readdir)(path);
|
|
187
|
+
}
|
|
188
|
+
catch (anyError) {
|
|
189
|
+
const nodeError = anyError;
|
|
190
|
+
if (nodeError.code === "ENOENT") {
|
|
191
|
+
const error = new Error(nodeError.message);
|
|
192
|
+
error.code = nodeError.code;
|
|
193
|
+
error.errno = nodeError.errno;
|
|
194
|
+
error.path = nodeError.path;
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
throw anyError;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
exports.readDir = readDir;
|
|
182
201
|
async function forEachFile(dirPath, cb, includeDir) {
|
|
183
|
-
const files = await (
|
|
202
|
+
const files = await readDir(dirPath);
|
|
184
203
|
for (const file of files) {
|
|
185
204
|
const filePath = (0, path_1.join)(dirPath, file);
|
|
186
205
|
if ((await (0, promises_1.stat)(filePath)).isDirectory()) {
|
|
@@ -315,6 +334,30 @@ async function writePathLists(options) {
|
|
|
315
334
|
};
|
|
316
335
|
}
|
|
317
336
|
exports.writePathLists = writePathLists;
|
|
337
|
+
async function copyFileWithStreams(source, target) {
|
|
338
|
+
const r = (0, fs_1.createReadStream)(source);
|
|
339
|
+
const w = (0, fs_2.createWriteStream)(target);
|
|
340
|
+
try {
|
|
341
|
+
return await new Promise((resolve, reject) => {
|
|
342
|
+
r.on("error", reject);
|
|
343
|
+
w.on("error", reject);
|
|
344
|
+
w.on("finish", resolve);
|
|
345
|
+
r.pipe(w);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
r.destroy();
|
|
350
|
+
w.end();
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
exports.copyFileWithStreams = copyFileWithStreams;
|
|
355
|
+
async function updateFileStats(path, fileInfo) {
|
|
356
|
+
await (0, promises_1.utimes)(path, fileInfo.atime, fileInfo.mtime);
|
|
357
|
+
await (0, promises_1.chmod)(path, fileInfo.mode);
|
|
358
|
+
await (0, promises_1.chown)(path, fileInfo.uid, fileInfo.gid);
|
|
359
|
+
}
|
|
360
|
+
exports.updateFileStats = updateFileStats;
|
|
318
361
|
async function cpy(options) {
|
|
319
362
|
const stats = { paths: 0, files: 0, dirs: 0 };
|
|
320
363
|
const dirs = new Set();
|
|
@@ -350,7 +393,19 @@ async function cpy(options) {
|
|
|
350
393
|
const dir = (0, path_1.dirname)(entryTargetPath);
|
|
351
394
|
await makeRecursiveDir(dir);
|
|
352
395
|
stats.files++;
|
|
353
|
-
|
|
396
|
+
// https://github.com/nodejs/node/issues/44261
|
|
397
|
+
if (exports.isWSLSystem) {
|
|
398
|
+
const fileInfo = await (0, promises_1.stat)(entrySourcePath);
|
|
399
|
+
const isWritable = (fileInfo.mode & 0o200) === 0o200;
|
|
400
|
+
if (!isWritable) {
|
|
401
|
+
await copyFileWithStreams(entrySourcePath, entryTargetPath);
|
|
402
|
+
await updateFileStats(entryTargetPath, fileInfo);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
await (0, promises_1.cp)(entrySourcePath, entryTargetPath, {
|
|
407
|
+
preserveTimestamps: true,
|
|
408
|
+
});
|
|
354
409
|
}
|
|
355
410
|
};
|
|
356
411
|
const { input } = options;
|