@datatruck/cli 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import type { ConfigType } from "../Config/Config";
2
2
  import { PackageConfigType } from "../Config/PackageConfig";
3
+ import { RepositoryConfigType } from "../Config/RepositoryConfig";
3
4
  import { PreSnapshot } from "../Repository/RepositoryAbstract";
4
5
  import { DataFormat } from "../utils/DataFormat";
5
6
  import { Listr3TaskResultEnd } from "../utils/list";
@@ -8,7 +9,7 @@ import { Streams } from "../utils/stream";
8
9
  import { IfRequireKeys } from "../utils/ts";
9
10
  export type BackupActionOptions = {
10
11
  repositoryNames?: string[];
11
- repositoryTypes?: string[];
12
+ repositoryTypes?: RepositoryConfigType["type"][];
12
13
  packageNames?: string[];
13
14
  packageTaskNames?: string[];
14
15
  tags?: string[];
@@ -19,11 +20,16 @@ export type BackupActionOptions = {
19
20
  progress?: "auto" | "interval" | boolean;
20
21
  progressInterval?: number;
21
22
  streams?: Streams;
23
+ prune?: boolean;
22
24
  };
23
25
  type Context = {
24
26
  snapshot: {
25
27
  id: string;
26
28
  };
29
+ prune: {
30
+ total: number;
31
+ pruned: number;
32
+ };
27
33
  task: {
28
34
  taskName: string;
29
35
  packageName: string;
@@ -18,6 +18,7 @@ const temp_1 = require("../utils/temp");
18
18
  const assert_1 = require("assert");
19
19
  const chalk_1 = __importDefault(require("chalk"));
20
20
  const crypto_1 = require("crypto");
21
+ const dayjs_1 = __importDefault(require("dayjs"));
21
22
  class BackupAction {
22
23
  config;
23
24
  options;
@@ -33,9 +34,12 @@ class BackupAction {
33
34
  });
34
35
  }
35
36
  prepareSnapshot() {
37
+ const date = this.options.date ?? new Date().toISOString();
38
+ if (!(0, dayjs_1.default)(date, "YYYY-MM-DDTHH:mm:ss.SSS[Z]", true).isValid())
39
+ throw new Error(`Invalid snapshot date: ${date}`);
36
40
  return {
37
41
  id: (0, crypto_1.randomUUID)().replaceAll("-", ""),
38
- date: this.options.date ?? new Date().toISOString(),
42
+ date,
39
43
  };
40
44
  }
41
45
  getPackages(snapshot) {
@@ -102,21 +106,28 @@ class BackupAction {
102
106
  let title = item.key.slice(0, 1).toUpperCase() + item.key.slice(1);
103
107
  return item.key === "backup" && color ? chalk_1.default.cyan(title) : title;
104
108
  };
105
- const renderData = (item, color) => {
109
+ const renderData = (item, color, result = []) => {
106
110
  const g = (v) => (color ? `${chalk_1.default.gray(`(${v})`)}` : `(${v})`);
107
- return item.key === "snapshot"
108
- ? item.data.id
109
- : item.key === "task"
110
- ? `${item.data.packageName} ${g(item.data.taskName)}`
111
- : item.key === "backup"
112
- ? `${item.data.packageName} ${g(item.data.repositoryName)}`
113
- : item.key === "copy"
114
- ? `${item.data.packageName} ${g(item.data.mirrorRepositoryName)}`
115
- : item.key === "summary"
116
- ? `Errors: ${item.data.errors}`
117
- : item.key === "report"
118
- ? item.data.type
119
- : "";
111
+ return item.key === "prune"
112
+ ? `${item.data.pruned}/${item.data.total}`
113
+ : item.key === "snapshot"
114
+ ? item.data.id
115
+ : item.key === "task"
116
+ ? `${item.data.packageName} ${g(item.data.taskName)}`
117
+ : item.key === "backup"
118
+ ? `${item.data.packageName} ${g(item.data.repositoryName)}`
119
+ : item.key === "copy"
120
+ ? `${item.data.packageName} ${g(item.data.mirrorRepositoryName)}`
121
+ : item.key === "summary"
122
+ ? (0, cli_1.renderObject)({
123
+ errors: item.data.errors,
124
+ backups: result.filter((r) => !r.error && r.key === "backup")
125
+ .length,
126
+ copies: result.filter((r) => !r.error && r.key === "copy").length,
127
+ })
128
+ : item.key === "report"
129
+ ? item.data.type
130
+ : "";
120
131
  };
121
132
  return new DataFormat_1.DataFormat({
122
133
  streams: options.streams,
@@ -124,9 +135,9 @@ class BackupAction {
124
135
  list: () => result
125
136
  .filter((item) => item.key !== "cleanup")
126
137
  .map((item) => {
127
- const icon = (0, cli_1.resultColumn)(item.error, false);
138
+ const icon = (0, cli_1.renderResult)(item.error, false);
128
139
  const title = renderTitle(item);
129
- const data = renderData(item, false);
140
+ const data = renderData(item, false, result);
130
141
  return `${icon} ${title}: ${data}`;
131
142
  }),
132
143
  table: {
@@ -140,11 +151,11 @@ class BackupAction {
140
151
  rows: () => result
141
152
  .filter((item) => item.key !== "cleanup")
142
153
  .map((item) => [
143
- (0, cli_1.resultColumn)(item.error),
154
+ (0, cli_1.renderResult)(item.error),
144
155
  renderTitle(item, true),
145
156
  renderData(item, true),
146
157
  (0, date_1.duration)(item.elapsed),
147
- (0, cli_1.errorColumn)(item.error, options.verbose),
158
+ (0, cli_1.renderError)(item.error, options.verbose),
148
159
  ]),
149
160
  },
150
161
  });
@@ -162,164 +173,169 @@ class BackupAction {
162
173
  progressManager: pm,
163
174
  });
164
175
  return l
165
- .add(l.$tasks({
166
- key: "snapshot",
167
- data: { id: "" },
168
- exitOnError: false,
169
- title: {
170
- initial: "Prepare snapshots",
171
- started: "Preparing snapshots",
172
- failed: "Snapshots prepare failed",
173
- },
174
- run: async (task, data) => {
175
- const { minFreeDiskSpace } = this.config;
176
- if (minFreeDiskSpace)
177
- await (0, temp_1.ensureFreeDiskTempSpace)(minFreeDiskSpace);
178
- const snapshot = this.prepareSnapshot();
179
- const packages = this.getPackages(snapshot);
180
- const snapshotId = (data.id = snapshot.id.slice(0, 8));
181
- task.title = `Snapshots prepared: ${snapshotId} (${packages.length} packages)`;
182
- return packages.flatMap((pkg) => {
183
- let taskResult = {};
184
- const gc = new temp_1.GargabeCollector();
185
- const repositories = this.getRepositoryNames(pkg.repositoryNames ?? []);
186
- const mirrorRepositories = repositories
187
- .filter((r) => r.mirrors.length)
188
- .flatMap(({ name, mirrors }) => mirrors.map((mirror) => ({ name, mirror })));
189
- return l.$tasks(!!pkg.task &&
190
- l.$task({
191
- key: "task",
192
- keyIndex: pkg.name,
193
- data: { taskName: pkg.task.name, packageName: pkg.name },
194
- title: {
195
- initial: `Execute task: ${pkg.task?.name}`,
196
- started: `Executing task: ${pkg.task?.name}`,
197
- failed: `Task execute failed: ${pkg.task?.name}`,
198
- completed: `Task executed: ${pkg.task?.name}`,
199
- },
200
- exitOnError: false,
201
- runWrapper: gc.cleanupIfFail.bind(gc),
202
- run: async (task) => {
203
- taskResult = await (0, TaskFactory_1.createTask)(pkg.task).backup({
204
- options,
205
- package: pkg,
206
- snapshot,
207
- onProgress: (p) => pm.update(p, (t) => (task.output = t)),
208
- });
209
- },
210
- }), ...repositories.map(({ name: repositoryName }) => l.$task({
211
- key: "backup",
212
- keyIndex: [pkg.name, repositoryName],
213
- data: {
214
- packageName: pkg.name,
215
- repositoryName: repositoryName,
216
- },
217
- title: {
218
- initial: `Create backup: ${repositoryName}`,
219
- started: `Creating backup: ${repositoryName}`,
220
- completed: `Backup created: ${repositoryName}`,
221
- failed: `Backup create failed: ${repositoryName}`,
222
- },
223
- exitOnError: false,
224
- runWrapper: gc.cleanupOnFinish.bind(gc),
225
- run: async (task) => {
226
- const taskSummary = pkg.task
227
- ? l.result("task", pkg.name)
228
- : undefined;
229
- if (taskSummary?.error)
230
- throw new Error(`Task failed`);
231
- await this.backup({
232
- pkg,
233
- repositoryName,
234
- snapshot,
235
- snapshotPath: taskResult?.snapshotPath,
236
- onProgress: (p) => this.pm.update(p, (t) => (task.output = t)),
237
- });
238
- },
239
- })), l.$task({
240
- key: "cleanup",
241
- keyIndex: pkg.name,
242
- data: {},
243
- title: {
244
- initial: "Clean task files",
245
- started: "Cleaning task files",
246
- completed: "Task files cleaned",
247
- failed: "Task files clean failed",
248
- },
249
- exitOnError: false,
250
- enabled: gc.pending,
251
- run: () => gc.cleanup(),
252
- }), ...mirrorRepositories.map(({ name, mirror }) => l.$task({
253
- key: "copy",
254
- keyIndex: [pkg.name, mirror],
255
- data: {
256
- packageName: pkg.name,
257
- repositoryName: name,
258
- mirrorRepositoryName: mirror,
259
- },
260
- title: {
261
- initial: `Copy snapshot: ${mirror}`,
262
- started: `Copying snapshot: ${mirror}`,
263
- completed: `Snapshot copied: ${mirror}`,
264
- failed: `Snapshot copy failed: ${mirror}`,
265
- },
266
- exitOnError: false,
267
- runWrapper: gc.cleanup.bind(gc),
268
- run: async (task) => {
269
- const backupSummary = l.result("backup", [
270
- pkg.name,
271
- name,
272
- ]);
273
- if (backupSummary.error)
274
- throw new Error(`Backup failed`);
275
- await this.copy({
276
- repositoryName: name,
277
- mirrorRepositoryName: mirror,
278
- pkg,
279
- snapshot,
280
- onProgress: (p) => pm.update(p, (t) => (task.output = t)),
281
- });
282
- },
283
- })), ...(this.config.reports || []).map((report, index) => {
284
- const reportIndex = index + 1;
285
- return l.$task({
286
- title: {
287
- initial: `Send report ${reportIndex}`,
288
- started: `Sending report ${reportIndex}`,
289
- completed: `Report sent: ${reportIndex}`,
290
- failed: `Report send failed: ${reportIndex}`,
291
- },
292
- key: "report",
293
- keyIndex: index,
294
- data: { type: report.run.type },
295
- exitOnError: false,
296
- run: async (task) => {
297
- const result = l
298
- .getResult()
299
- .filter((r) => r.key !== "report");
300
- const success = result.every((r) => !r.error);
301
- const enabled = !report.when ||
302
- (report.when === "success" && success) ||
303
- (report.when === "error" && !success);
304
- if (!enabled)
305
- return task.skip(`Report send skipped: ${reportIndex}`);
306
- const text = this.dataFormat(result).format(report.format ?? "list");
307
- await (0, steps_1.runSteps)(report.run, {
308
- verbose: this.options.verbose,
309
- process: { vars: { DTT_REPORT: text } },
310
- telegram: { vars: { TEXT: text } },
311
- node: {
312
- vars: {
313
- dtt: { report: text, result },
314
- },
176
+ .add([
177
+ l.$task({
178
+ key: "snapshot",
179
+ data: { id: "" },
180
+ exitOnError: false,
181
+ title: {
182
+ initial: "Prepare snapshots",
183
+ started: "Preparing snapshots",
184
+ failed: "Snapshots prepare failed",
185
+ },
186
+ run: async (task, data) => {
187
+ const { minFreeDiskSpace } = this.config;
188
+ if (minFreeDiskSpace)
189
+ await (0, temp_1.ensureFreeDiskTempSpace)(minFreeDiskSpace);
190
+ const snapshot = this.prepareSnapshot();
191
+ const packages = this.getPackages(snapshot);
192
+ const snapshotId = (data.id = snapshot.id.slice(0, 8));
193
+ task.title = `Snapshots prepared: ${snapshotId} (${packages.length} packages)`;
194
+ return [
195
+ ...packages.flatMap((pkg) => {
196
+ let taskResult = {};
197
+ const gc = new temp_1.GargabeCollector();
198
+ const repositories = this.getRepositoryNames(pkg.repositoryNames ?? []);
199
+ const mirrorRepositories = repositories
200
+ .filter((r) => r.mirrors.length)
201
+ .flatMap(({ name, mirrors }) => mirrors.map((mirror) => ({ name, mirror })));
202
+ return l.$tasks(!!pkg.task &&
203
+ l.$task({
204
+ key: "task",
205
+ keyIndex: pkg.name,
206
+ data: { taskName: pkg.task.name, packageName: pkg.name },
207
+ title: {
208
+ initial: `Execute task: ${pkg.task?.name}`,
209
+ started: `Executing task: ${pkg.task?.name}`,
210
+ failed: `Task execute failed: ${pkg.task?.name}`,
211
+ completed: `Task executed: ${pkg.task?.name}`,
315
212
  },
316
- });
317
- },
318
- });
319
- }));
320
- });
321
- },
322
- }))
213
+ exitOnError: false,
214
+ runWrapper: gc.cleanupIfFail.bind(gc),
215
+ run: async (task) => {
216
+ taskResult = await (0, TaskFactory_1.createTask)(pkg.task).backup({
217
+ options,
218
+ package: pkg,
219
+ snapshot,
220
+ onProgress: (p) => pm.update(p, (t) => (task.output = t)),
221
+ });
222
+ },
223
+ }), ...repositories.map(({ name: repositoryName }) => l.$task({
224
+ key: "backup",
225
+ keyIndex: [pkg.name, repositoryName],
226
+ data: {
227
+ packageName: pkg.name,
228
+ repositoryName: repositoryName,
229
+ },
230
+ title: {
231
+ initial: `Create backup: ${repositoryName}`,
232
+ started: `Creating backup: ${repositoryName}`,
233
+ completed: `Backup created: ${repositoryName}`,
234
+ failed: `Backup create failed: ${repositoryName}`,
235
+ },
236
+ exitOnError: false,
237
+ runWrapper: gc.cleanupOnFinish.bind(gc),
238
+ run: async (task) => {
239
+ const taskSummary = pkg.task
240
+ ? l.result("task", pkg.name)
241
+ : undefined;
242
+ if (taskSummary?.error)
243
+ throw new Error(`Task failed`);
244
+ await this.backup({
245
+ pkg,
246
+ repositoryName,
247
+ snapshot,
248
+ snapshotPath: taskResult?.snapshotPath,
249
+ onProgress: (p) => this.pm.update(p, (t) => (task.output = t)),
250
+ });
251
+ },
252
+ })), l.$task({
253
+ key: "cleanup",
254
+ keyIndex: pkg.name,
255
+ data: {},
256
+ title: {
257
+ initial: "Clean task files",
258
+ started: "Cleaning task files",
259
+ completed: "Task files cleaned",
260
+ failed: "Task files clean failed",
261
+ },
262
+ exitOnError: false,
263
+ enabled: gc.pending,
264
+ run: () => gc.cleanup(),
265
+ }), ...mirrorRepositories.map(({ name, mirror }) => l.$task({
266
+ key: "copy",
267
+ keyIndex: [pkg.name, mirror],
268
+ data: {
269
+ packageName: pkg.name,
270
+ repositoryName: name,
271
+ mirrorRepositoryName: mirror,
272
+ },
273
+ title: {
274
+ initial: `Copy snapshot: ${mirror}`,
275
+ started: `Copying snapshot: ${mirror}`,
276
+ completed: `Snapshot copied: ${mirror}`,
277
+ failed: `Snapshot copy failed: ${mirror}`,
278
+ },
279
+ exitOnError: false,
280
+ runWrapper: gc.cleanup.bind(gc),
281
+ run: async (task) => {
282
+ const backupSummary = l.result("backup", [
283
+ pkg.name,
284
+ name,
285
+ ]);
286
+ if (backupSummary.error)
287
+ throw new Error(`Backup failed`);
288
+ await this.copy({
289
+ repositoryName: name,
290
+ mirrorRepositoryName: mirror,
291
+ pkg,
292
+ snapshot,
293
+ onProgress: (p) => pm.update(p, (t) => (task.output = t)),
294
+ });
295
+ },
296
+ })));
297
+ }),
298
+ ...(this.config.reports || []).map((report, index) => {
299
+ const reportIndex = index + 1;
300
+ return l.$task({
301
+ title: {
302
+ initial: `Send report ${reportIndex}`,
303
+ started: `Sending report ${reportIndex}`,
304
+ completed: `Report sent: ${reportIndex}`,
305
+ failed: `Report send failed: ${reportIndex}`,
306
+ },
307
+ key: "report",
308
+ keyIndex: index,
309
+ data: { type: report.run.type },
310
+ exitOnError: false,
311
+ run: async (task) => {
312
+ const result = l
313
+ .getResult()
314
+ .filter((r) => r.key !== "report");
315
+ const success = result.every((r) => !r.error);
316
+ const enabled = !report.when ||
317
+ (report.when === "success" && success) ||
318
+ (report.when === "error" && !success);
319
+ if (!enabled)
320
+ return task.skip(`Report send skipped: ${reportIndex}`);
321
+ const text = this.dataFormat(result).format(report.format ?? "list");
322
+ await (0, steps_1.runSteps)(report.run, {
323
+ verbose: this.options.verbose,
324
+ process: { vars: { DTT_REPORT: text } },
325
+ telegram: { vars: { TEXT: text } },
326
+ node: {
327
+ vars: {
328
+ dtt: { report: text, result },
329
+ },
330
+ },
331
+ });
332
+ },
333
+ });
334
+ }),
335
+ ];
336
+ },
337
+ }),
338
+ ])
323
339
  .exec();
324
340
  }
325
341
  }
@@ -5,7 +5,8 @@ import { Listr3TaskResultEnd } from "../utils/list";
5
5
  import { Streams } from "../utils/stream";
6
6
  import { IfRequireKeys } from "../utils/ts";
7
7
  export type CopyActionOptionsType = {
8
- ids: string[];
8
+ ids?: string[];
9
+ last?: number;
9
10
  repositoryName: string;
10
11
  packageNames?: string[];
11
12
  packageTaskNames?: string[];
@@ -27,6 +28,7 @@ export type Context = {
27
28
  packageName: string;
28
29
  repositoryName: string;
29
30
  mirrorRepositoryName: string;
31
+ skipped: boolean;
30
32
  };
31
33
  };
32
34
  export declare class CopyAction<TRequired extends boolean = true> {
@@ -8,6 +8,7 @@ const RepositoryFactory_1 = require("../Factory/RepositoryFactory");
8
8
  const DataFormat_1 = require("../utils/DataFormat");
9
9
  const cli_1 = require("../utils/cli");
10
10
  const config_1 = require("../utils/datatruck/config");
11
+ const snapshot_1 = require("../utils/datatruck/snapshot");
11
12
  const date_1 = require("../utils/date");
12
13
  const list_1 = require("../utils/list");
13
14
  const progress_1 = require("../utils/progress");
@@ -25,13 +26,22 @@ class CopyAction {
25
26
  let title = item.key.slice(0, 1).toUpperCase() + item.key.slice(1);
26
27
  return item.key === "copy" && color ? chalk_1.default.cyan(title) : title;
27
28
  };
28
- const renderData = (item, color) => {
29
+ const renderData = (item, color, items = []) => {
29
30
  const g = (v) => (color ? `${chalk_1.default.gray(`(${v})`)}` : `(${v})`);
30
31
  return item.key === "snapshots"
31
32
  ? item.data.snapshots.length
32
33
  : item.key === "copy"
33
- ? `${item.data.packageName} ${g(item.data.mirrorRepositoryName)}`
34
- : "";
34
+ ? `${item.data.packageName} ${g([
35
+ item.data.snapshotId.slice(0, 8),
36
+ item.data.mirrorRepositoryName,
37
+ ].join(" "))}`
38
+ : item.key === "summary"
39
+ ? (0, cli_1.renderObject)({
40
+ errors: item.data.errors,
41
+ copied: items.filter((i) => i.key === "copy" && !i.error && !i.data.skipped).length,
42
+ skipped: items.filter((i) => i.key === "copy" && !i.error && i.data.skipped).length,
43
+ })
44
+ : "";
35
45
  };
36
46
  return new DataFormat_1.DataFormat({
37
47
  streams: options.streams,
@@ -45,11 +55,11 @@ class CopyAction {
45
55
  { value: "Error", width: 50 },
46
56
  ],
47
57
  rows: () => result.map((item) => [
48
- (0, cli_1.resultColumn)(item.error),
58
+ (0, cli_1.renderResult)(item.error),
49
59
  renderTitle(item, true),
50
- renderData(item, true),
60
+ renderData(item, true, result),
51
61
  (0, date_1.duration)(item.elapsed),
52
- (0, cli_1.errorColumn)(item.error, options.verbose),
62
+ (0, cli_1.renderError)(item.error, options.verbose),
53
63
  ]),
54
64
  },
55
65
  });
@@ -78,13 +88,17 @@ class CopyAction {
78
88
  await (0, temp_1.ensureFreeDiskTempSpace)(this.config.minFreeDiskSpace);
79
89
  const sourceRepoConfig = (0, config_1.findRepositoryOrFail)(this.config, this.options.repositoryName);
80
90
  const repo = (0, RepositoryFactory_1.createRepo)(sourceRepoConfig);
81
- const snapshots = await repo.fetchSnapshots({
91
+ let snapshots = await repo.fetchSnapshots({
82
92
  options: {
83
93
  ids: this.options.ids,
84
94
  packageNames: this.options.packageNames,
85
95
  packageTaskNames: this.options.packageTaskNames,
86
96
  },
87
97
  });
98
+ if (this.options.last)
99
+ snapshots = (0, snapshot_1.groupAndFilter)(snapshots, ["packageName"], {
100
+ last: this.options.last,
101
+ }).map(({ item }) => item);
88
102
  data.snapshots = snapshots;
89
103
  task.title = `Snapshots fetched: ${snapshots.length}`;
90
104
  if (!snapshots.length)
@@ -97,45 +111,52 @@ class CopyAction {
97
111
  .map((r) => r.name);
98
112
  if (!repositoryNames2.length)
99
113
  throw new Error("No mirror snapshots found");
100
- return snapshots.flatMap((snapshot) => repositoryNames2.map((repo2) => l.$task({
101
- key: "copy",
102
- keyIndex: [snapshot.packageName, repo2],
103
- data: {
104
- snapshotId: snapshot.id,
105
- packageName: snapshot.packageName,
106
- repositoryName: sourceRepoConfig.name,
107
- mirrorRepositoryName: repo2,
108
- },
109
- title: {
110
- initial: `Copy snapshot: ${repo2}`,
111
- started: `Copying snapshot: ${repo2}`,
112
- completed: `Snapshot copied: ${repo2}`,
113
- failed: `Snapshot copy failed: ${repo2}`,
114
- },
115
- exitOnError: false,
116
- run: async (task) => {
117
- const mirrorConfig = (0, config_1.findRepositoryOrFail)(this.config, repo2);
118
- const mirrorRepo = (0, RepositoryFactory_1.createRepo)(mirrorConfig);
119
- (0, config_1.ensureSameRepositoryType)(sourceRepoConfig, mirrorConfig);
120
- const currentCopies = await mirrorRepo.fetchSnapshots({
121
- options: {
122
- ids: [snapshot.id],
123
- packageNames: [snapshot.packageName],
124
- },
125
- });
126
- if (currentCopies.length)
127
- return task.skip(`Already exists at ${mirrorConfig.name}`);
128
- if (this.config.minFreeDiskSpace)
129
- await mirrorRepo.ensureFreeDiskSpace(mirrorConfig.config, this.config.minFreeDiskSpace);
130
- await repo.copy({
131
- mirrorRepositoryConfig: mirrorConfig.config,
132
- options: { verbose: this.options.verbose },
133
- package: { name: snapshot.packageName },
134
- snapshot,
135
- onProgress: (p) => pm.update(p, (d) => (task.output = d)),
136
- });
137
- },
138
- })));
114
+ return snapshots.flatMap((snapshot) => repositoryNames2.map((repo2) => {
115
+ const id = snapshot.id.slice(0, 8);
116
+ const pkgName = snapshot.packageName;
117
+ return l.$task({
118
+ key: "copy",
119
+ keyIndex: [snapshot.packageName, repo2, snapshot.id],
120
+ data: {
121
+ snapshotId: snapshot.id,
122
+ packageName: snapshot.packageName,
123
+ repositoryName: sourceRepoConfig.name,
124
+ mirrorRepositoryName: repo2,
125
+ skipped: false,
126
+ },
127
+ title: {
128
+ initial: `Copy snapshot: ${pkgName} (${id}) » ${repo2}`,
129
+ started: `Copying snapshot: ${pkgName} (${id}) » ${repo2}`,
130
+ completed: `Snapshot copied: ${pkgName} (${id}) » ${repo2}`,
131
+ failed: `Snapshot copy failed: ${pkgName} (${id}) » ${repo2}`,
132
+ },
133
+ exitOnError: false,
134
+ run: async (task, data) => {
135
+ const mirrorConfig = (0, config_1.findRepositoryOrFail)(this.config, repo2);
136
+ const mirrorRepo = (0, RepositoryFactory_1.createRepo)(mirrorConfig);
137
+ (0, config_1.ensureSameRepositoryType)(sourceRepoConfig, mirrorConfig);
138
+ const currentCopies = await mirrorRepo.fetchSnapshots({
139
+ options: {
140
+ ids: [snapshot.id],
141
+ packageNames: [snapshot.packageName],
142
+ },
143
+ });
144
+ if (currentCopies.length) {
145
+ data.skipped = true;
146
+ return task.skip(`Already exists at ${mirrorConfig.name}: ${pkgName} (${id})`);
147
+ }
148
+ if (this.config.minFreeDiskSpace)
149
+ await mirrorRepo.ensureFreeDiskSpace(mirrorConfig.config, this.config.minFreeDiskSpace);
150
+ await repo.copy({
151
+ mirrorRepositoryConfig: mirrorConfig.config,
152
+ options: { verbose: this.options.verbose },
153
+ package: { name: snapshot.packageName },
154
+ snapshot,
155
+ onProgress: (p) => pm.update(p, (d) => (task.output = d)),
156
+ });
157
+ },
158
+ });
159
+ }));
139
160
  },
140
161
  }))
141
162
  .exec();
@@ -1,20 +1,14 @@
1
1
  import type { ConfigType } from "../Config/Config";
2
2
  import { RepositoryConfigType } from "../Config/RepositoryConfig";
3
+ import { KeepObject } from "../utils/date";
3
4
  import { IfRequireKeys } from "../utils/ts";
4
5
  import { ExtendedSnapshot, SnapshotsActionOptions } from "./SnapshotsAction";
5
- export type PruneActionsOptions = {
6
+ export type PruneActionsOptions = KeepObject & {
6
7
  ids?: string[];
7
8
  packageNames?: string[];
8
9
  repositoryNames?: string[];
9
10
  repositoryTypes?: RepositoryConfigType["type"][];
10
11
  tags?: string[];
11
- keepLast?: number;
12
- keepMinutely?: number;
13
- keepHourly?: number;
14
- keepDaily?: number;
15
- keepWeekly?: number;
16
- keepMonthly?: number;
17
- keepYearly?: number;
18
12
  verbose?: boolean;
19
13
  groupBy?: SnapshotsActionOptions["groupBy"];
20
14
  dryRun?: boolean;