@hasna/connectors 1.3.21 → 1.3.23
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/bin/index.js +274 -71
- package/bin/mcp.js +458 -197
- package/bin/serve.js +77 -33
- package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
- package/connectors/connect-dropbox/src/api/index.ts +4 -0
- package/connectors/connect-dropbox/src/cli/index.ts +151 -0
- package/connectors/connect-github/src/api/bulk.ts +328 -0
- package/connectors/connect-github/src/api/index.ts +4 -0
- package/connectors/connect-github/src/cli/index.ts +145 -0
- package/connectors/connect-gmail/src/api/history.ts +82 -0
- package/connectors/connect-gmail/src/api/index.ts +12 -0
- package/connectors/connect-gmail/src/api/settings.ts +280 -0
- package/connectors/connect-gmail/src/api/watch.ts +40 -0
- package/connectors/connect-gmail/src/cli/index.ts +237 -0
- package/connectors/connect-googleads/src/api/bulk.ts +354 -0
- package/connectors/connect-googleads/src/api/index.ts +4 -0
- package/connectors/connect-googleads/src/cli/index.ts +192 -0
- package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
- package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
- package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
- package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
- package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
- package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
- package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
- package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
- package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
- package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
- package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
- package/connectors/connect-googledocs/src/api/index.ts +4 -0
- package/connectors/connect-googledocs/src/cli/index.ts +187 -0
- package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
- package/connectors/connect-googledrive/src/api/changes.ts +139 -0
- package/connectors/connect-googledrive/src/api/client.ts +34 -0
- package/connectors/connect-googledrive/src/api/index.ts +12 -0
- package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
- package/connectors/connect-googledrive/src/cli/index.ts +624 -0
- package/connectors/connect-googledrive/src/index.ts +2 -0
- package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
- package/connectors/connect-googlephotos/src/api/index.ts +8 -0
- package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
- package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
- package/connectors/connect-googletasks/src/api/index.ts +1 -0
- package/connectors/connect-googletasks/src/cli/index.ts +178 -0
- package/connectors/connect-linear/src/api/bulk.ts +219 -0
- package/connectors/connect-linear/src/api/index.ts +4 -0
- package/connectors/connect-linear/src/cli/index.ts +116 -0
- package/connectors/connect-shopify/src/api/bulk.ts +288 -0
- package/connectors/connect-shopify/src/api/index.ts +4 -0
- package/connectors/connect-shopify/src/cli/index.ts +108 -0
- package/connectors/connect-slack/src/api/bulk.ts +237 -0
- package/connectors/connect-slack/src/api/index.ts +4 -0
- package/connectors/connect-slack/src/cli/index.ts +157 -0
- package/connectors/connect-stripe/src/api/bulk.ts +488 -0
- package/connectors/connect-stripe/src/api/index.ts +4 -0
- package/connectors/connect-stripe/src/cli/index.ts +206 -0
- package/dist/index.js +75 -33
- package/dist/lib/runner.d.ts +15 -5
- package/dist/server/auth.d.ts +4 -0
- package/package.json +2 -2
- package/connectors/connect-cloudflare/bun.lock +0 -32
- package/connectors/connect-gmail/bin/index.js +0 -7027
- package/connectors/connect-gmail/dist/cli/index.js +0 -7035
- package/connectors/connect-gmail/dist/index.js +0 -2174
- package/dist/server/server.test.d.ts +0 -1
package/bin/serve.js
CHANGED
|
@@ -17,7 +17,7 @@ var __export = (target, all) => {
|
|
|
17
17
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
18
18
|
var __require = import.meta.require;
|
|
19
19
|
|
|
20
|
-
// node_modules/@hasna/cloud/dist/index.js
|
|
20
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/@hasna/cloud/dist/index.js
|
|
21
21
|
import { createRequire } from "module";
|
|
22
22
|
import { Database } from "bun:sqlite";
|
|
23
23
|
import {
|
|
@@ -10327,7 +10327,7 @@ import { existsSync as existsSync7 } from "fs";
|
|
|
10327
10327
|
import { dirname as dirname3, join as join8 } from "path";
|
|
10328
10328
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
10329
10329
|
|
|
10330
|
-
// node_modules/zod/v3/external.js
|
|
10330
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/external.js
|
|
10331
10331
|
var exports_external2 = {};
|
|
10332
10332
|
__export(exports_external2, {
|
|
10333
10333
|
void: () => voidType2,
|
|
@@ -10439,7 +10439,7 @@ __export(exports_external2, {
|
|
|
10439
10439
|
BRAND: () => BRAND2
|
|
10440
10440
|
});
|
|
10441
10441
|
|
|
10442
|
-
// node_modules/zod/v3/helpers/util.js
|
|
10442
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/helpers/util.js
|
|
10443
10443
|
var util2;
|
|
10444
10444
|
(function(util3) {
|
|
10445
10445
|
util3.assertEqual = (_) => {};
|
|
@@ -10570,7 +10570,7 @@ var getParsedType2 = (data) => {
|
|
|
10570
10570
|
}
|
|
10571
10571
|
};
|
|
10572
10572
|
|
|
10573
|
-
// node_modules/zod/v3/ZodError.js
|
|
10573
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/ZodError.js
|
|
10574
10574
|
var ZodIssueCode2 = util2.arrayToEnum([
|
|
10575
10575
|
"invalid_type",
|
|
10576
10576
|
"invalid_literal",
|
|
@@ -10689,7 +10689,7 @@ ZodError2.create = (issues) => {
|
|
|
10689
10689
|
return error;
|
|
10690
10690
|
};
|
|
10691
10691
|
|
|
10692
|
-
// node_modules/zod/v3/locales/en.js
|
|
10692
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/locales/en.js
|
|
10693
10693
|
var errorMap2 = (issue, _ctx) => {
|
|
10694
10694
|
let message;
|
|
10695
10695
|
switch (issue.code) {
|
|
@@ -10792,7 +10792,7 @@ var errorMap2 = (issue, _ctx) => {
|
|
|
10792
10792
|
};
|
|
10793
10793
|
var en_default2 = errorMap2;
|
|
10794
10794
|
|
|
10795
|
-
// node_modules/zod/v3/errors.js
|
|
10795
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/errors.js
|
|
10796
10796
|
var overrideErrorMap2 = en_default2;
|
|
10797
10797
|
function setErrorMap2(map) {
|
|
10798
10798
|
overrideErrorMap2 = map;
|
|
@@ -10800,7 +10800,7 @@ function setErrorMap2(map) {
|
|
|
10800
10800
|
function getErrorMap2() {
|
|
10801
10801
|
return overrideErrorMap2;
|
|
10802
10802
|
}
|
|
10803
|
-
// node_modules/zod/v3/helpers/parseUtil.js
|
|
10803
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/helpers/parseUtil.js
|
|
10804
10804
|
var makeIssue2 = (params) => {
|
|
10805
10805
|
const { data, path, errorMaps, issueData } = params;
|
|
10806
10806
|
const fullPath = [...path, ...issueData.path || []];
|
|
@@ -10906,14 +10906,14 @@ var isAborted2 = (x) => x.status === "aborted";
|
|
|
10906
10906
|
var isDirty2 = (x) => x.status === "dirty";
|
|
10907
10907
|
var isValid2 = (x) => x.status === "valid";
|
|
10908
10908
|
var isAsync2 = (x) => typeof Promise !== "undefined" && x instanceof Promise;
|
|
10909
|
-
// node_modules/zod/v3/helpers/errorUtil.js
|
|
10909
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/helpers/errorUtil.js
|
|
10910
10910
|
var errorUtil2;
|
|
10911
10911
|
(function(errorUtil3) {
|
|
10912
10912
|
errorUtil3.errToObj = (message) => typeof message === "string" ? { message } : message || {};
|
|
10913
10913
|
errorUtil3.toString = (message) => typeof message === "string" ? message : message?.message;
|
|
10914
10914
|
})(errorUtil2 || (errorUtil2 = {}));
|
|
10915
10915
|
|
|
10916
|
-
// node_modules/zod/v3/types.js
|
|
10916
|
+
// ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/types.js
|
|
10917
10917
|
class ParseInputLazyPath2 {
|
|
10918
10918
|
constructor(parent, value, path, key) {
|
|
10919
10919
|
this._cachedPath = [];
|
|
@@ -15160,7 +15160,7 @@ var githubConnector = defineConnector({
|
|
|
15160
15160
|
// package.json
|
|
15161
15161
|
var package_default = {
|
|
15162
15162
|
name: "@hasna/connectors",
|
|
15163
|
-
version: "1.3.
|
|
15163
|
+
version: "1.3.23",
|
|
15164
15164
|
description: "Open source connector library - Install API connectors with a single command",
|
|
15165
15165
|
type: "module",
|
|
15166
15166
|
bin: {
|
|
@@ -15191,7 +15191,7 @@ var package_default = {
|
|
|
15191
15191
|
typecheck: "tsc --noEmit",
|
|
15192
15192
|
test: "bun test",
|
|
15193
15193
|
"sdk:build": "cd sdk && bun run build",
|
|
15194
|
-
prepublishOnly: "bun
|
|
15194
|
+
prepublishOnly: "bun run build && bun test"
|
|
15195
15195
|
},
|
|
15196
15196
|
keywords: [
|
|
15197
15197
|
"connectors",
|
|
@@ -23528,6 +23528,53 @@ function getConnectorCliPath(name) {
|
|
|
23528
23528
|
function hasConnectorCommandSurface(name) {
|
|
23529
23529
|
return Boolean(getInternalCommandRuntime(name)?.commands.length || getConnectorCliPath(name));
|
|
23530
23530
|
}
|
|
23531
|
+
function normalizeCommanderUsage(usage) {
|
|
23532
|
+
const commandToken = usage.trim().split(/\s+/)[0];
|
|
23533
|
+
if (!commandToken || commandToken === "help")
|
|
23534
|
+
return null;
|
|
23535
|
+
const aliases = commandToken.split("|").filter(Boolean);
|
|
23536
|
+
const [name, ...rest] = aliases;
|
|
23537
|
+
if (!name)
|
|
23538
|
+
return null;
|
|
23539
|
+
return {
|
|
23540
|
+
name,
|
|
23541
|
+
aliases: rest,
|
|
23542
|
+
usage: usage.trim()
|
|
23543
|
+
};
|
|
23544
|
+
}
|
|
23545
|
+
function parseCommanderHelpOperations(helpText) {
|
|
23546
|
+
const operations = [];
|
|
23547
|
+
const lines = helpText.split(`
|
|
23548
|
+
`);
|
|
23549
|
+
let inCommands = false;
|
|
23550
|
+
for (const line of lines) {
|
|
23551
|
+
if (line.trim().startsWith("Commands:")) {
|
|
23552
|
+
inCommands = true;
|
|
23553
|
+
continue;
|
|
23554
|
+
}
|
|
23555
|
+
if (!inCommands)
|
|
23556
|
+
continue;
|
|
23557
|
+
if (line.trim() === "") {
|
|
23558
|
+
if (operations.length > 0)
|
|
23559
|
+
inCommands = false;
|
|
23560
|
+
continue;
|
|
23561
|
+
}
|
|
23562
|
+
const row = line.match(/^ {2}(\S.*?)(?: {2,}(.+))?$/);
|
|
23563
|
+
if (!row)
|
|
23564
|
+
continue;
|
|
23565
|
+
const usage = row[1].trim();
|
|
23566
|
+
const summary = (row[2] ?? "").trim();
|
|
23567
|
+
const normalized = normalizeCommanderUsage(usage);
|
|
23568
|
+
if (!normalized)
|
|
23569
|
+
continue;
|
|
23570
|
+
operations.push({
|
|
23571
|
+
...normalized,
|
|
23572
|
+
summary,
|
|
23573
|
+
source: "cli"
|
|
23574
|
+
});
|
|
23575
|
+
}
|
|
23576
|
+
return operations;
|
|
23577
|
+
}
|
|
23531
23578
|
function runConnectorCommand2(name, args, timeoutMs = 30000) {
|
|
23532
23579
|
getConnectorsHome();
|
|
23533
23580
|
const internalRuntime = getInternalCommandRuntime(name);
|
|
@@ -23594,38 +23641,33 @@ function runLegacyConnectorCommand(name, args, timeoutMs = 30000) {
|
|
|
23594
23641
|
async function getConnectorOperations(name) {
|
|
23595
23642
|
const internalRuntime = getInternalCommandRuntime(name);
|
|
23596
23643
|
if (internalRuntime?.commands.length) {
|
|
23644
|
+
const operations2 = internalRuntime.commands.map((command) => ({
|
|
23645
|
+
name: command.name,
|
|
23646
|
+
aliases: [],
|
|
23647
|
+
usage: command.name,
|
|
23648
|
+
summary: command.summary,
|
|
23649
|
+
source: "internal"
|
|
23650
|
+
}));
|
|
23597
23651
|
return {
|
|
23598
|
-
commands:
|
|
23652
|
+
commands: operations2.map((operation) => operation.name),
|
|
23653
|
+
operations: operations2,
|
|
23599
23654
|
helpText: internalRuntime.helpText ?? buildInternalHelpText(name, internalRuntime.commands),
|
|
23600
23655
|
hasCli: true
|
|
23601
23656
|
};
|
|
23602
23657
|
}
|
|
23603
23658
|
const cliPath = getConnectorCliPath(name);
|
|
23604
23659
|
if (!cliPath) {
|
|
23605
|
-
return { commands: [], helpText: "", hasCli: false };
|
|
23660
|
+
return { commands: [], operations: [], helpText: "", hasCli: false };
|
|
23606
23661
|
}
|
|
23607
23662
|
const result = await runConnectorCommand2(name, ["--help"]);
|
|
23608
23663
|
const helpText = result.stdout || result.stderr;
|
|
23609
|
-
const
|
|
23610
|
-
|
|
23611
|
-
|
|
23612
|
-
|
|
23613
|
-
|
|
23614
|
-
|
|
23615
|
-
|
|
23616
|
-
continue;
|
|
23617
|
-
}
|
|
23618
|
-
if (inCommands) {
|
|
23619
|
-
const match = line.match(/^\s{2,}(\S+)/);
|
|
23620
|
-
if (match && match[1] !== "help") {
|
|
23621
|
-
commands.push(match[1]);
|
|
23622
|
-
}
|
|
23623
|
-
if (line.trim() === "" && commands.length > 0) {
|
|
23624
|
-
inCommands = false;
|
|
23625
|
-
}
|
|
23626
|
-
}
|
|
23627
|
-
}
|
|
23628
|
-
return { commands, helpText, hasCli: true };
|
|
23664
|
+
const operations = parseCommanderHelpOperations(helpText);
|
|
23665
|
+
return {
|
|
23666
|
+
commands: operations.map((operation) => operation.name),
|
|
23667
|
+
operations,
|
|
23668
|
+
helpText,
|
|
23669
|
+
hasCli: true
|
|
23670
|
+
};
|
|
23629
23671
|
}
|
|
23630
23672
|
async function getConnectorCommandHelp(name, command) {
|
|
23631
23673
|
const internalRuntime = getInternalCommandRuntime(name);
|
|
@@ -24349,7 +24391,9 @@ Dashboard not found at: ${dashboardDir}`);
|
|
|
24349
24391
|
return json({
|
|
24350
24392
|
connector: name,
|
|
24351
24393
|
displayName: meta.displayName,
|
|
24394
|
+
auth: getAuthStatus(name),
|
|
24352
24395
|
commands: ops.commands,
|
|
24396
|
+
operations: ops.operations,
|
|
24353
24397
|
helpText: ops.helpText
|
|
24354
24398
|
}, 200, port);
|
|
24355
24399
|
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { DropboxClient } from './client';
|
|
2
|
+
import type { Metadata, FileMetadata, RelocationBatchV2Result } from '../types';
|
|
3
|
+
|
|
4
|
+
// ============================================
|
|
5
|
+
// Bulk Operation Types
|
|
6
|
+
// ============================================
|
|
7
|
+
|
|
8
|
+
export interface BulkOperationOptions {
|
|
9
|
+
/** Maximum concurrent API calls (default: 10) */
|
|
10
|
+
concurrency?: number;
|
|
11
|
+
/** Dry run - don't actually modify */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
/** Progress callback */
|
|
14
|
+
onProgress?: (current: number, total: number, item: unknown) => void;
|
|
15
|
+
/** Error callback */
|
|
16
|
+
onError?: (error: Error, item: unknown) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface PathSummary {
|
|
20
|
+
path: string;
|
|
21
|
+
displayName?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface BulkDeleteOptions extends BulkOperationOptions {
|
|
25
|
+
/** Paths to delete */
|
|
26
|
+
paths: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface BulkDeleteResult {
|
|
30
|
+
total: number;
|
|
31
|
+
success: number;
|
|
32
|
+
failed: number;
|
|
33
|
+
errors: Array<{ path: string; error: string }>;
|
|
34
|
+
deletedPaths: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CopyEntry {
|
|
38
|
+
fromPath: string;
|
|
39
|
+
toPath: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface BulkCopyOptions extends BulkOperationOptions {
|
|
43
|
+
/** Source/destination path pairs */
|
|
44
|
+
entries: CopyEntry[];
|
|
45
|
+
/** Autorename if destination exists */
|
|
46
|
+
autorename?: boolean;
|
|
47
|
+
/** Allow shared folders */
|
|
48
|
+
allowSharedFolder?: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface CopyResult {
|
|
52
|
+
fromPath: string;
|
|
53
|
+
toPath: string;
|
|
54
|
+
metadata?: Metadata;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface BulkCopyResult {
|
|
58
|
+
total: number;
|
|
59
|
+
success: number;
|
|
60
|
+
failed: number;
|
|
61
|
+
errors: Array<{ fromPath: string; toPath: string; error: string }>;
|
|
62
|
+
results: CopyResult[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MoveEntry {
|
|
66
|
+
fromPath: string;
|
|
67
|
+
toPath: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface BulkMoveOptions extends BulkOperationOptions {
|
|
71
|
+
/** Source/destination path pairs */
|
|
72
|
+
entries: MoveEntry[];
|
|
73
|
+
/** Autorename if destination exists */
|
|
74
|
+
autorename?: boolean;
|
|
75
|
+
/** Allow shared folders */
|
|
76
|
+
allowSharedFolder?: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface MoveResult {
|
|
80
|
+
fromPath: string;
|
|
81
|
+
toPath: string;
|
|
82
|
+
metadata?: Metadata;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface BulkMoveResult {
|
|
86
|
+
total: number;
|
|
87
|
+
success: number;
|
|
88
|
+
failed: number;
|
|
89
|
+
errors: Array<{ fromPath: string; toPath: string; error: string }>;
|
|
90
|
+
results: MoveResult[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================
|
|
94
|
+
// Bulk Operations API
|
|
95
|
+
// ============================================
|
|
96
|
+
|
|
97
|
+
export class BulkApi {
|
|
98
|
+
private readonly client: DropboxClient;
|
|
99
|
+
|
|
100
|
+
constructor(client: DropboxClient) {
|
|
101
|
+
this.client = client;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================
|
|
105
|
+
// Delete Batch
|
|
106
|
+
// ============================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Bulk delete files and folders
|
|
110
|
+
*/
|
|
111
|
+
async delete(options: BulkDeleteOptions): Promise<BulkDeleteResult> {
|
|
112
|
+
const { paths, concurrency = 10, dryRun = false, onProgress, onError } = options;
|
|
113
|
+
|
|
114
|
+
const result: BulkDeleteResult = {
|
|
115
|
+
total: paths.length,
|
|
116
|
+
success: 0,
|
|
117
|
+
failed: 0,
|
|
118
|
+
errors: [],
|
|
119
|
+
deletedPaths: [],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (paths.length === 0) return result;
|
|
123
|
+
|
|
124
|
+
const chunks = this.chunkArray(paths, concurrency);
|
|
125
|
+
|
|
126
|
+
for (const chunk of chunks) {
|
|
127
|
+
await Promise.all(
|
|
128
|
+
chunk.map(async (path) => {
|
|
129
|
+
try {
|
|
130
|
+
if (dryRun) {
|
|
131
|
+
result.success++;
|
|
132
|
+
} else {
|
|
133
|
+
await this.client.post('/files/delete_v2', { path });
|
|
134
|
+
result.success++;
|
|
135
|
+
result.deletedPaths.push(path);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, path);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
result.failed++;
|
|
141
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
142
|
+
result.errors.push({ path, error: errorMessage });
|
|
143
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), path);
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================
|
|
153
|
+
// Copy Batch
|
|
154
|
+
// ============================================
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Bulk copy files and folders
|
|
158
|
+
*/
|
|
159
|
+
async copy(options: BulkCopyOptions): Promise<BulkCopyResult> {
|
|
160
|
+
const { entries, concurrency = 10, dryRun = false, autorename = false, allowSharedFolder = false, onProgress, onError } = options;
|
|
161
|
+
|
|
162
|
+
const result: BulkCopyResult = {
|
|
163
|
+
total: entries.length,
|
|
164
|
+
success: 0,
|
|
165
|
+
failed: 0,
|
|
166
|
+
errors: [],
|
|
167
|
+
results: [],
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (entries.length === 0) return result;
|
|
171
|
+
|
|
172
|
+
const chunks = this.chunkArray(entries, concurrency);
|
|
173
|
+
|
|
174
|
+
for (const chunk of chunks) {
|
|
175
|
+
await Promise.all(
|
|
176
|
+
chunk.map(async (entry) => {
|
|
177
|
+
try {
|
|
178
|
+
if (dryRun) {
|
|
179
|
+
result.success++;
|
|
180
|
+
} else {
|
|
181
|
+
const response = await this.client.post<RelocationBatchV2Result>('/files/copy_v2', {
|
|
182
|
+
from_path: entry.fromPath,
|
|
183
|
+
to_path: entry.toPath,
|
|
184
|
+
autorename,
|
|
185
|
+
allow_shared_folder: allowSharedFolder,
|
|
186
|
+
});
|
|
187
|
+
result.success++;
|
|
188
|
+
result.results.push({
|
|
189
|
+
fromPath: entry.fromPath,
|
|
190
|
+
toPath: entry.toPath,
|
|
191
|
+
metadata: (response as unknown as { metadata: Metadata }).metadata,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, entry);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
result.failed++;
|
|
198
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
199
|
+
result.errors.push({
|
|
200
|
+
fromPath: entry.fromPath,
|
|
201
|
+
toPath: entry.toPath,
|
|
202
|
+
error: errorMessage,
|
|
203
|
+
});
|
|
204
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), entry);
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ============================================
|
|
214
|
+
// Move Batch
|
|
215
|
+
// ============================================
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Bulk move files and folders
|
|
219
|
+
*/
|
|
220
|
+
async move(options: BulkMoveOptions): Promise<BulkMoveResult> {
|
|
221
|
+
const { entries, concurrency = 10, dryRun = false, autorename = false, allowSharedFolder = false, onProgress, onError } = options;
|
|
222
|
+
|
|
223
|
+
const result: BulkMoveResult = {
|
|
224
|
+
total: entries.length,
|
|
225
|
+
success: 0,
|
|
226
|
+
failed: 0,
|
|
227
|
+
errors: [],
|
|
228
|
+
results: [],
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (entries.length === 0) return result;
|
|
232
|
+
|
|
233
|
+
const chunks = this.chunkArray(entries, concurrency);
|
|
234
|
+
|
|
235
|
+
for (const chunk of chunks) {
|
|
236
|
+
await Promise.all(
|
|
237
|
+
chunk.map(async (entry) => {
|
|
238
|
+
try {
|
|
239
|
+
if (dryRun) {
|
|
240
|
+
result.success++;
|
|
241
|
+
} else {
|
|
242
|
+
const response = await this.client.post<RelocationBatchV2Result>('/files/move_v2', {
|
|
243
|
+
from_path: entry.fromPath,
|
|
244
|
+
to_path: entry.toPath,
|
|
245
|
+
autorename,
|
|
246
|
+
allow_shared_folder: allowSharedFolder,
|
|
247
|
+
});
|
|
248
|
+
result.success++;
|
|
249
|
+
result.results.push({
|
|
250
|
+
fromPath: entry.fromPath,
|
|
251
|
+
toPath: entry.toPath,
|
|
252
|
+
metadata: (response as unknown as { metadata: Metadata }).metadata,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (onProgress) onProgress(result.success + result.failed, result.total, entry);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
result.failed++;
|
|
259
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
260
|
+
result.errors.push({
|
|
261
|
+
fromPath: entry.fromPath,
|
|
262
|
+
toPath: entry.toPath,
|
|
263
|
+
error: errorMessage,
|
|
264
|
+
});
|
|
265
|
+
if (onError) onError(err instanceof Error ? err : new Error(errorMessage), entry);
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ============================================
|
|
275
|
+
// Helper Methods
|
|
276
|
+
// ============================================
|
|
277
|
+
|
|
278
|
+
private chunkArray<T>(array: T[], size: number): T[][] {
|
|
279
|
+
const chunks: T[][] = [];
|
|
280
|
+
for (let i = 0; i < array.length; i += size) {
|
|
281
|
+
chunks.push(array.slice(i, i + size));
|
|
282
|
+
}
|
|
283
|
+
return chunks;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -15,15 +15,18 @@ import type {
|
|
|
15
15
|
DeleteResult,
|
|
16
16
|
} from '../types';
|
|
17
17
|
import { DropboxClient } from './client';
|
|
18
|
+
import { BulkApi } from './bulk';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Dropbox API wrapper
|
|
21
22
|
*/
|
|
22
23
|
export class Dropbox {
|
|
23
24
|
private readonly client: DropboxClient;
|
|
25
|
+
public readonly bulk: BulkApi;
|
|
24
26
|
|
|
25
27
|
constructor(config: DropboxConfig) {
|
|
26
28
|
this.client = new DropboxClient(config);
|
|
29
|
+
this.bulk = new BulkApi(this.client);
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
/**
|
|
@@ -393,3 +396,4 @@ export class Dropbox {
|
|
|
393
396
|
}
|
|
394
397
|
|
|
395
398
|
export { DropboxClient } from './client';
|
|
399
|
+
export { BulkApi } from './bulk';
|
|
@@ -623,5 +623,156 @@ accountCmd
|
|
|
623
623
|
}
|
|
624
624
|
});
|
|
625
625
|
|
|
626
|
+
// ============================================
|
|
627
|
+
// Bulk Commands
|
|
628
|
+
// ============================================
|
|
629
|
+
|
|
630
|
+
const bulkCmd = program.command('bulk').description('Bulk operations on files and folders');
|
|
631
|
+
|
|
632
|
+
bulkCmd
|
|
633
|
+
.command('delete')
|
|
634
|
+
.description('Bulk delete files and folders')
|
|
635
|
+
.requiredOption('--paths <paths>', 'Comma-separated list of paths')
|
|
636
|
+
.option('--concurrency <number>', 'Max concurrent requests', '10')
|
|
637
|
+
.option('--dry-run', 'Preview without making changes')
|
|
638
|
+
.action(async (opts) => {
|
|
639
|
+
const paths = opts.paths.split(',').map((p: string) => p.trim()).filter(Boolean);
|
|
640
|
+
const concurrency = parseInt(opts.concurrency, 10);
|
|
641
|
+
|
|
642
|
+
if (paths.length === 0) {
|
|
643
|
+
console.error(chalk.red('Error: No paths provided'));
|
|
644
|
+
process.exit(1);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const client = getClient();
|
|
648
|
+
console.log(chalk.blue(`Deleting ${paths.length} path(s) (concurrency: ${concurrency})...`));
|
|
649
|
+
if (opts.dryRun) console.log(chalk.yellow('(dry run)'));
|
|
650
|
+
|
|
651
|
+
const result = await client.bulk.delete({
|
|
652
|
+
paths,
|
|
653
|
+
concurrency,
|
|
654
|
+
dryRun: opts.dryRun,
|
|
655
|
+
onProgress: (current, total) => {
|
|
656
|
+
process.stdout.write(`\rProgress: ${current}/${total}`);
|
|
657
|
+
},
|
|
658
|
+
onError: (err, path) => {
|
|
659
|
+
console.error(`\n Failed: ${path} - ${err.message}`);
|
|
660
|
+
},
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
console.log();
|
|
664
|
+
console.log(chalk.green(`Done: ${result.success} deleted, ${result.failed} failed`));
|
|
665
|
+
if (result.errors.length > 0) {
|
|
666
|
+
console.log(chalk.yellow('Errors:'));
|
|
667
|
+
for (const e of result.errors) {
|
|
668
|
+
console.log(` - ${e.path}: ${e.error}`);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
bulkCmd
|
|
674
|
+
.command('copy')
|
|
675
|
+
.description('Bulk copy files and folders')
|
|
676
|
+
.requiredOption('--entries <entries>', 'JSON array of {fromPath, toPath}')
|
|
677
|
+
.option('--concurrency <number>', 'Max concurrent requests', '10')
|
|
678
|
+
.option('--dry-run', 'Preview without making changes')
|
|
679
|
+
.option('--autorename', 'Autorename if destination exists')
|
|
680
|
+
.option('--allow-shared-folder', 'Allow shared folders')
|
|
681
|
+
.action(async (opts) => {
|
|
682
|
+
let entries: Array<{ fromPath: string; toPath: string }>;
|
|
683
|
+
try {
|
|
684
|
+
entries = JSON.parse(opts.entries) as Array<{ fromPath: string; toPath: string }>;
|
|
685
|
+
} catch {
|
|
686
|
+
console.error(chalk.red('Error: Invalid JSON for --entries'));
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (entries.length === 0) {
|
|
691
|
+
console.error(chalk.red('Error: No entries provided'));
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const client = getClient();
|
|
696
|
+
const concurrency = parseInt(opts.concurrency, 10);
|
|
697
|
+
console.log(chalk.blue(`Copying ${entries.length} item(s)...`));
|
|
698
|
+
if (opts.dryRun) console.log(chalk.yellow('(dry run)'));
|
|
699
|
+
|
|
700
|
+
const result = await client.bulk.copy({
|
|
701
|
+
entries,
|
|
702
|
+
concurrency,
|
|
703
|
+
dryRun: opts.dryRun,
|
|
704
|
+
autorename: opts.autorename,
|
|
705
|
+
allowSharedFolder: opts.allowSharedFolder,
|
|
706
|
+
onProgress: (current, total) => {
|
|
707
|
+
process.stdout.write(`\rProgress: ${current}/${total}`);
|
|
708
|
+
},
|
|
709
|
+
onError: (err, entry) => {
|
|
710
|
+
const e = entry as { fromPath: string };
|
|
711
|
+
console.error(`\n Failed: ${e.fromPath} - ${err.message}`);
|
|
712
|
+
},
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
console.log();
|
|
716
|
+
console.log(chalk.green(`Done: ${result.success} copied, ${result.failed} failed`));
|
|
717
|
+
if (result.errors.length > 0) {
|
|
718
|
+
console.log(chalk.yellow('Errors:'));
|
|
719
|
+
for (const e of result.errors) {
|
|
720
|
+
console.log(` - ${e.fromPath} -> ${e.toPath}: ${e.error}`);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
bulkCmd
|
|
726
|
+
.command('move')
|
|
727
|
+
.description('Bulk move files and folders')
|
|
728
|
+
.requiredOption('--entries <entries>', 'JSON array of {fromPath, toPath}')
|
|
729
|
+
.option('--concurrency <number>', 'Max concurrent requests', '10')
|
|
730
|
+
.option('--dry-run', 'Preview without making changes')
|
|
731
|
+
.option('--autorename', 'Autorename if destination exists')
|
|
732
|
+
.option('--allow-shared-folder', 'Allow shared folders')
|
|
733
|
+
.action(async (opts) => {
|
|
734
|
+
let entries: Array<{ fromPath: string; toPath: string }>;
|
|
735
|
+
try {
|
|
736
|
+
entries = JSON.parse(opts.entries) as Array<{ fromPath: string; toPath: string }>;
|
|
737
|
+
} catch {
|
|
738
|
+
console.error(chalk.red('Error: Invalid JSON for --entries'));
|
|
739
|
+
process.exit(1);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (entries.length === 0) {
|
|
743
|
+
console.error(chalk.red('Error: No entries provided'));
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const client = getClient();
|
|
748
|
+
const concurrency = parseInt(opts.concurrency, 10);
|
|
749
|
+
console.log(chalk.blue(`Moving ${entries.length} item(s)...`));
|
|
750
|
+
if (opts.dryRun) console.log(chalk.yellow('(dry run)'));
|
|
751
|
+
|
|
752
|
+
const result = await client.bulk.move({
|
|
753
|
+
entries,
|
|
754
|
+
concurrency,
|
|
755
|
+
dryRun: opts.dryRun,
|
|
756
|
+
autorename: opts.autorename,
|
|
757
|
+
allowSharedFolder: opts.allowSharedFolder,
|
|
758
|
+
onProgress: (current, total) => {
|
|
759
|
+
process.stdout.write(`\rProgress: ${current}/${total}`);
|
|
760
|
+
},
|
|
761
|
+
onError: (err, entry) => {
|
|
762
|
+
const e = entry as { fromPath: string };
|
|
763
|
+
console.error(`\n Failed: ${e.fromPath} - ${err.message}`);
|
|
764
|
+
},
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
console.log();
|
|
768
|
+
console.log(chalk.green(`Done: ${result.success} moved, ${result.failed} failed`));
|
|
769
|
+
if (result.errors.length > 0) {
|
|
770
|
+
console.log(chalk.yellow('Errors:'));
|
|
771
|
+
for (const e of result.errors) {
|
|
772
|
+
console.log(` - ${e.fromPath} -> ${e.toPath}: ${e.error}`);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
|
|
626
777
|
// Parse and execute
|
|
627
778
|
program.parse();
|